From a006163583099a49b7c52303b606af4c668c4bdd Mon Sep 17 00:00:00 2001 From: Samuel Reid <43227667+cranktakular@users.noreply.github.com> Date: Tue, 12 Sep 2023 17:11:10 +1000 Subject: [PATCH 01/79] Slight enhance of Coinbase tests Continual enhance of Coinbase tests The revamp continues Oh jeez the Orderbook part's unfinished don't look Coinbase revamp, Orderbook still unfinished --- exchanges/coinbasepro/coinbasepro.go | 1191 ++++++++++-------- exchanges/coinbasepro/coinbasepro_test.go | 508 +++++++- exchanges/coinbasepro/coinbasepro_types.go | 508 ++++++-- exchanges/coinbasepro/coinbasepro_wrapper.go | 421 ++++--- 4 files changed, 1759 insertions(+), 869 deletions(-) diff --git a/exchanges/coinbasepro/coinbasepro.go b/exchanges/coinbasepro/coinbasepro.go index ab1365c2fe5..3f7b4a67629 100644 --- a/exchanges/coinbasepro/coinbasepro.go +++ b/exchanges/coinbasepro/coinbasepro.go @@ -21,171 +21,662 @@ import ( ) const ( - coinbaseproAPIURL = "https://api.pro.coinbase.com/" - coinbaseproSandboxAPIURL = "https://api-public.sandbox.pro.coinbase.com/" - coinbaseproAPIVersion = "0" + coinbaseproAPIURL = "https://api.exchange.coinbase.com/" + coinbaseproSandboxAPIURL = "https://api-public.sandbox.exchange.coinbase.com/" + coinbaseproAPIVersion = "0" + coinbaseproAccounts = "accounts" + coinbaseproHolds = "holds" + coinbaseproLedger = "ledger" + coinbaseproTransfers = "transfers" + coinbaseproAddressBook = "address-book" + coinbaseproAddress = "addresses" + coinbaseproConversions = "conversions" + coinbaseproCurrencies = "currencies" + coinbaseproDepositCoinbase = "deposits/coinbase-account" + coinbaseproPaymentMethodDeposit = "deposits/payment-method" + coinbaseproPaymentMethod = "payment-methods" + coinbaseproFeeEstimate = "withdrawals/fee-estimate" + coinbaseproFees = "fees" + coinbaseproFills = "fills" + coinbaseproOrders = "orders" + coinbaseproOracle = "oracle" + coinbaseproProducts = "products" coinbaseproOrderbook = "book" coinbaseproTicker = "ticker" coinbaseproTrades = "trades" coinbaseproHistory = "candles" coinbaseproStats = "stats" - coinbaseproCurrencies = "currencies" - coinbaseproAccounts = "accounts" - coinbaseproLedger = "ledger" - coinbaseproHolds = "holds" - coinbaseproOrders = "orders" - coinbaseproFills = "fills" - coinbaseproTransfers = "transfers" coinbaseproReports = "reports" coinbaseproTime = "time" + coinbaseproTravelRules = "travel-rules" coinbaseproMarginTransfer = "profiles/margin-transfer" coinbaseproPosition = "position" coinbaseproPositionClose = "position/close" - coinbaseproPaymentMethod = "payment-methods" - coinbaseproPaymentMethodDeposit = "deposits/payment-method" - coinbaseproDepositCoinbase = "deposits/coinbase-account" coinbaseproWithdrawalPaymentMethod = "withdrawals/payment-method" - coinbaseproWithdrawalCoinbase = "withdrawals/coinbase" + coinbaseproWithdrawalCoinbase = "withdrawals/coinbase-account" coinbaseproWithdrawalCrypto = "withdrawals/crypto" coinbaseproCoinbaseAccounts = "coinbase-accounts" coinbaseproTrailingVolume = "users/self/trailing-volume" ) +const ( + pageNone = "" + pageBefore = "before" + pageAfter = "after" +) + // CoinbasePro is the overarching type across the coinbasepro package type CoinbasePro struct { exchange.Base } -// GetProducts returns supported currency pairs on the exchange with specific -// information about the pair -func (c *CoinbasePro) GetProducts(ctx context.Context) ([]Product, error) { +// GetAccounts returns a list of trading accounts associated with the APIKEYS +func (c *CoinbasePro) GetAccounts(ctx context.Context) ([]AccountResponse, error) { + var resp []AccountResponse + + return resp, + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, coinbaseproAccounts, nil, &resp) +} + +// GetAccount returns information for a single account. Use this endpoint when +// account_id is known +func (c *CoinbasePro) GetAccount(ctx context.Context, accountID string) (AccountResponse, error) { + resp := AccountResponse{} + path := fmt.Sprintf("%s/%s", coinbaseproAccounts, accountID) + + return resp, + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp) +} + +// GetHolds returns the holds that are placed on an account for any active +// orders or pending withdraw requests. As an order is filled, the hold amount +// is updated. If an order is canceled, any remaining hold is removed. For a +// withdraw, once it is completed, the hold is removed. +func (c *CoinbasePro) GetHolds(ctx context.Context, accountID, direction, step string, limit int64) ([]AccountHolds, error) { + var resp []AccountHolds + var params Params + params.urlVals = url.Values{} + path := fmt.Sprintf("%s/%s/%s", coinbaseproAccounts, accountID, coinbaseproHolds) + + params.PrepareDSL(direction, step, limit) + + path = common.EncodeURLValues(path, params.urlVals) + + return resp, + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp) +} + +// GetAccountLedger returns a list of ledger activity. Anything that increases +// or decreases your account balance. Items are paginated and sorted latest +// first. +func (c *CoinbasePro) GetAccountLedger(ctx context.Context, accountID, direction, step, pID string, startDate, endDate time.Time, limit int64) ([]AccountLedgerResponse, error) { + var resp []AccountLedgerResponse + var params Params + params.urlVals = url.Values{} + + err := params.PrepareDateString(startDate, endDate) + if err != nil { + return resp, err + } + + path := fmt.Sprintf("%s/%s/%s", coinbaseproAccounts, accountID, coinbaseproLedger) + + params.PrepareDSL(direction, step, limit) + + if pID != "" { + params.urlVals.Set("profile_id", pID) + } + + path = common.EncodeURLValues(path, params.urlVals) + + return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp) +} + +// GetAccountTransfers returns a history of withdrawal and or deposit +// transactions for a single account +func (c *CoinbasePro) GetAccountTransfers(ctx context.Context, accountID, direction, step, transferType string, limit int64) ([]TransferHistory, error) { + var resp []TransferHistory + var params Params + params.urlVals = url.Values{} + path := fmt.Sprintf("%s/%s/%s", coinbaseproAccounts, accountID, coinbaseproTransfers) + + params.PrepareDSL(direction, step, limit) + params.urlVals.Set("type", transferType) + + path = common.EncodeURLValues(path, params.urlVals) + + return resp, + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp) +} + +// GetAddressBook returns all addresses stored in the address book +func (c *CoinbasePro) GetAddressBook(ctx context.Context) ([]GetAddressResponse, error) { + var resp []GetAddressResponse + + return resp, + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, coinbaseproAddressBook, nil, &resp) +} + +// AddAddresses adds new addresses to the address book +func (c *CoinbasePro) AddAddresses(ctx context.Context, req []AddAddressRequest) ([]AddAddressResponse, error) { + var resp []AddAddressResponse + + params := make(map[string]interface{}) + params["addresses"] = req + + return resp, + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproAddressBook, params, &resp) +} + +// DeleteAddress deletes an address from the address book +func (c *CoinbasePro) DeleteAddress(ctx context.Context, addressID string) error { + path := fmt.Sprintf("%s/%s", coinbaseproAddressBook, addressID) + + return c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodDelete, path, nil, nil) +} + +// GetCoinbaseWallets returns all of the user's available Coinbase wallets +func (c *CoinbasePro) GetCoinbaseWallets(ctx context.Context) ([]CoinbaseAccounts, error) { + var resp []CoinbaseAccounts + + return resp, + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, coinbaseproCoinbaseAccounts, nil, &resp) +} + +// GenerateCryptoAddress generates a one-time address for deposting crypto +func (c *CoinbasePro) GenerateCryptoAddress(ctx context.Context, accountID, profileID, network string) (CryptoAddressResponse, error) { + resp := CryptoAddressResponse{} + path := fmt.Sprintf("%s/%s/%s", coinbaseproCoinbaseAccounts, accountID, coinbaseproAddress) + + // In this case, accountID has to come from GetCoinbaseWallets, not GetAccounts + + params := map[string]interface{}{"account_id": accountID, "profile_id": profileID, + "network": network} + + return resp, + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, path, params, &resp) +} + +// ConvertCurrency converts between two currencies in the specified profile +func (c *CoinbasePro) ConvertCurrency(ctx context.Context, profileID, from, to, nonce string, amount float64) (ConvertResponse, error) { + resp := ConvertResponse{} + + params := map[string]interface{}{"profile_id": profileID, "from": from, "to": to, + "amount": strconv.FormatFloat(amount, 'f', -1, 64), "nonce": nonce} + + return resp, + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproConversions, params, &resp) +} + +// GetConversionByID identifies the details of a past conversion, given its ID +func (c *CoinbasePro) GetConversionByID(ctx context.Context, conversionID, profileID string) (ConvertResponse, error) { + resp := ConvertResponse{} + path := fmt.Sprintf("%s/%s", coinbaseproConversions, conversionID) + var params Params + params.urlVals = url.Values{} + params.urlVals.Set("profile_id", profileID) + path = common.EncodeURLValues(path, params.urlVals) + + return resp, + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp) +} + +// GetCurrencies returns a list of currencies known by the exchange +// Warning: Currencies won't necessarily be available for trading +func (c *CoinbasePro) GetCurrencies(ctx context.Context) ([]Currency, error) { + var currencies []Currency + + return currencies, + c.SendHTTPRequest(ctx, exchange.RestSpot, coinbaseproCurrencies, ¤cies) +} + +// GetCurrenciesByID returns into on a single currency given its ID in ISO 4217, or +// in a custom code for currencies which lack an ISO 4217 code +func (c *CoinbasePro) GetCurrenciesByID(ctx context.Context, currencyID string) (Currency, error) { + resp := Currency{} + path := fmt.Sprintf("%s/%s", coinbaseproCurrencies, currencyID) + + return resp, c.SendHTTPRequest(ctx, exchange.RestSpot, path, &resp) +} + +// DepositViaCoinbase deposits funds from a Coinbase account +func (c *CoinbasePro) DepositViaCoinbase(ctx context.Context, profileID, currency, coinbaseAccountID string, amount float64) (DepositWithdrawalInfo, error) { + resp := DepositWithdrawalInfo{} + params := map[string]interface{}{"profile_id": profileID, + "amount": strconv.FormatFloat(amount, 'f', -1, 64), + "coinbase_account_id": coinbaseAccountID, "currency": currency} + + return resp, + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproDepositCoinbase, params, &resp) +} + +// DepositViaPaymentMethod deposits funds from a payment method. SEPA is not allowed +func (c *CoinbasePro) DepositViaPaymentMethod(ctx context.Context, profileID, paymentID, currency string, amount float64) (DepositWithdrawalInfo, error) { + resp := DepositWithdrawalInfo{} + params := map[string]interface{}{"profile_id": profileID, + "amount": strconv.FormatFloat(amount, 'f', -1, 64), + "payment_method_id": paymentID, "currency": currency} + + return resp, + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproPaymentMethodDeposit, params, &resp) +} + +// GetPayMethods returns a full list of payment methods +func (c *CoinbasePro) GetPayMethods(ctx context.Context) ([]PaymentMethod, error) { + var resp []PaymentMethod + + return resp, + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, coinbaseproPaymentMethod, nil, &resp) +} + +// GetAllTransfers returns all in-progress and completed transfers in and out of any +// of the user's accounts +func (c *CoinbasePro) GetAllTransfers(ctx context.Context, profileID, direction, step, transferType string, limit int64) ([]TransferHistory, error) { + resp := []TransferHistory{} + var params Params + params.urlVals = url.Values{} + params.urlVals.Set("profile_id", profileID) + params.PrepareDSL(direction, step, limit) + params.urlVals.Set("type", transferType) + path := common.EncodeURLValues(coinbaseproTransfers, params.urlVals) + + return resp, + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp) +} + +// GetTransferByID returns information on a single transfer when provided with its ID +func (c *CoinbasePro) GetTransferByID(ctx context.Context, transferID string) (TransferHistory, error) { + resp := TransferHistory{} + path := fmt.Sprintf("%s/%s", coinbaseproTransfers, transferID) + + return resp, + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp) +} + +// SendTravelInfoForTransfer sends travel rule information for a transfer +func (c *CoinbasePro) SendTravelInfoForTransfer(ctx context.Context, transferID, originName, originCountry string) (string, error) { + var resp string + path := fmt.Sprintf("%s/%s/%s", coinbaseproTransfers, transferID, + coinbaseproTravelRules) + params := map[string]interface{}{"transfer_id": transferID, + "originator_name": originName, "originator_country": originCountry} + + return resp, + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, path, params, &resp) +} + +// WithdrawViaCoinbase withdraws funds to a coinbase account. +func (c *CoinbasePro) WithdrawViaCoinbase(ctx context.Context, profileID, accountID, currency string, amount float64) (DepositWithdrawalInfo, error) { + resp := DepositWithdrawalInfo{} + req := map[string]interface{}{"profile_id": profileID, + "amount": strconv.FormatFloat(amount, 'f', -1, 64), + "coinbase_account_id": accountID, "currency": currency} + + return resp, + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproWithdrawalCoinbase, req, &resp) +} + +// WithdrawCrypto withdraws funds to a crypto address +func (c *CoinbasePro) WithdrawCrypto(ctx context.Context, profileID, currency, cryptoAddress, destinationTag, twoFactorCode, network string, amount float64, noDestinationTag, addNetworkFee bool, nonce int32) (DepositWithdrawalInfo, error) { + resp := DepositWithdrawalInfo{} + req := map[string]interface{}{"profile_id": profileID, + "amount": strconv.FormatFloat(amount, 'f', -1, 64), + "currency": currency, "crypto_address": cryptoAddress, + "destination_tag": destinationTag, "no_destination_tag": noDestinationTag, + "two_factor_code": twoFactorCode, "nonce": nonce, "network": network, + "add_network_fee": addNetworkFee} + + return resp, + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproWithdrawalCrypto, req, &resp) +} + +// GetWithdrawalFeeEstimate has Coinbase estimate the fee for withdrawing in a certain +// network to a certain address +func (c *CoinbasePro) GetWithdrawalFeeEstimate(ctx context.Context, currency, cryptoAddress, network string) (WithdrawalFeeEstimate, error) { + resp := WithdrawalFeeEstimate{} + if currency == "" { + return resp, errors.New("currency cannot be empty") + } + if cryptoAddress == "" { + return resp, errors.New("cryptoAddress cannot be empty") + } + var params Params + params.urlVals = url.Values{} + params.urlVals.Set("currency", currency) + params.urlVals.Set("crypto_address", cryptoAddress) + params.urlVals.Set("network", network) + path := common.EncodeURLValues(coinbaseproFeeEstimate, params.urlVals) + + return resp, + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp) +} + +// WithdrawViaPaymentMethod withdraws funds to a payment method +func (c *CoinbasePro) WithdrawViaPaymentMethod(ctx context.Context, profileID, paymentID, currency string, amount float64) (DepositWithdrawalInfo, error) { + resp := DepositWithdrawalInfo{} + req := map[string]interface{}{"profile_id": profileID, + "amount": strconv.FormatFloat(amount, 'f', -1, 64), + "payment_method_id": paymentID, "currency": currency} + + return resp, + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproWithdrawalPaymentMethod, req, &resp) +} + +// GetFees returns your current maker & taker fee rates, as well as your 30-day +// trailing volume. Quoted rates are subject to change. +func (c *CoinbasePro) GetFees(ctx context.Context) (FeeResponse, error) { + resp := FeeResponse{} + + return resp, + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, coinbaseproFees, nil, &resp) +} + +// GetFills returns a list of recent fills on this profile +func (c *CoinbasePro) GetFills(ctx context.Context, orderID, currencyPair, direction, step, marketType string, limit int64, startDate, endDate time.Time) ([]FillResponse, error) { + var resp []FillResponse + if orderID == "" && currencyPair == "" { + return resp, errors.New("requires either order id or product id") + } + var params Params + params.urlVals = url.Values{} + err := params.PrepareDateString(startDate, endDate) + if err != nil { + return resp, err + } + + if orderID != "" { + params.urlVals.Set("order_id", orderID) + } + if currencyPair != "" { + params.urlVals.Set("product_id", currencyPair) + } + + params.PrepareDSL(direction, step, limit) + params.urlVals.Set("market_type", marketType) + + path := common.EncodeURLValues(coinbaseproFills, params.urlVals) + + return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp) +} + +// GetOrders lists all open or unsettled orders +func (c *CoinbasePro) GetOrders(ctx context.Context, profileID, currencyPair, sortedBy, sorting, direction, step, marketType string, startDate, endDate time.Time, limit int64, status []string) ([]GeneralizedOrderResponse, error) { + var resp []GeneralizedOrderResponse + if limit < 1 { + return resp, errors.New("limit must be greater than 0") + } + var params Params + params.urlVals = make(url.Values) + err := params.PrepareDateString(startDate, endDate) + if err != nil { + return resp, err + } + + params.PrepareProfIDAndProdID(profileID, currencyPair) + params.urlVals.Set("sorted_by", sortedBy) + params.urlVals.Set("sorting", sorting) + params.PrepareDSL(direction, step, limit) + + for _, individualStatus := range status { + params.urlVals.Add("status", individualStatus) + } + + params.urlVals.Set("market_type", marketType) + + path := common.EncodeURLValues(coinbaseproOrders, params.urlVals) + + return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp) +} + +// CancelAllExistingOrders attempts to cancel all open orders. The exchange warns that +// this may need to be called multiple times to properly close all of them +func (c *CoinbasePro) CancelAllExistingOrders(ctx context.Context, profileID, currencyPair string) ([]string, error) { + var resp []string + var params Params + params.urlVals = url.Values{} + + params.PrepareProfIDAndProdID(profileID, currencyPair) + + path := common.EncodeURLValues(coinbaseproOrders, params.urlVals) + return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodDelete, path, nil, &resp) +} + +// PlaceOrder places either a limit, market, or stop order +func (c *CoinbasePro) PlaceOrder(ctx context.Context, profileID, orderType, side, currencyPair, stp, stop, timeInForce, cancelAfter, clientOID string, stopPrice, price, size, funds float64, postOnly bool) (GeneralizedOrderResponse, error) { + var resp GeneralizedOrderResponse + + if (orderType == order.Market.Lower() || orderType == "" || orderType == "stop") && + (price == 0 || size == 0) { + return resp, errors.New("price and size must be greater than 0 for limit or stop orders") + } + if orderType == order.Market.Lower() && (size == 0 && funds == 0) { + return resp, errors.New("size or funds must be greater than 0 for market orders") + } + + req := map[string]interface{}{"profile_id": profileID, "type": orderType, "side": side, + "product_id": currencyPair, "stp": stp, "stop": stop, + "stop_price": strconv.FormatFloat(stopPrice, 'f', -1, 64), + "price": strconv.FormatFloat(price, 'f', -1, 64), + "size": strconv.FormatFloat(size, 'f', -1, 64), + "funds": strconv.FormatFloat(funds, 'f', -1, 64), + "time_in_force": timeInForce, "cancel_after": cancelAfter, "post_only": postOnly, + "client_oid": clientOID} + + return resp, + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproOrders, req, &resp) +} + +// GetOrder returns a single order by order id. +func (c *CoinbasePro) GetOrder(ctx context.Context, orderID, marketType string, clientID bool) (GeneralizedOrderResponse, error) { + resp := GeneralizedOrderResponse{} + if orderID == "" { + return resp, errors.New("order id cannot be empty") + } + if clientID { + orderID = fmt.Sprintf("client:%s", orderID) + } + path := fmt.Sprintf("%s/%s", coinbaseproOrders, orderID) + var param Params + param.urlVals = url.Values{} + + param.urlVals.Set("market_type", marketType) + + path = common.EncodeURLValues(path, param.urlVals) + + return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp) +} + +// CancelExistingOrder cancels order by orderID +func (c *CoinbasePro) CancelExistingOrder(ctx context.Context, orderID, profileID, productID string, clientID bool) (string, error) { + var resp string + if orderID == "" { + return resp, errors.New("order id cannot be empty") + } + if clientID { + orderID = fmt.Sprintf("client:%s", orderID) + } + path := fmt.Sprintf("%s/%s", coinbaseproOrders, orderID) + var param Params + param.urlVals = url.Values{} + + param.urlVals.Set("profile_id", profileID) + param.urlVals.Set("product_id", productID) + + path = common.EncodeURLValues(path, param.urlVals) + + return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodDelete, path, nil, nil) +} + +// GetSignedPrices returns some cryptographically signed prices ready to be +// posted on-chain using Compound's Open Oracle smart contract +func (c *CoinbasePro) GetSignedPrices(ctx context.Context) (SignedPrices, error) { + resp := SignedPrices{} + + return resp, + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, coinbaseproOracle, nil, &resp) +} + +// GetProducts returns information on all currency pairs that are available for trading +func (c *CoinbasePro) GetProducts(ctx context.Context, productType string) ([]Product, error) { var products []Product - return products, c.SendHTTPRequest(ctx, exchange.RestSpot, coinbaseproProducts, &products) + var params Params + params.urlVals = url.Values{} + params.urlVals.Set("type", productType) + + path := common.EncodeURLValues(coinbaseproProducts, params.urlVals) + + return products, c.SendHTTPRequest(ctx, exchange.RestSpot, path, &products) } -// GetOrderbook returns orderbook by currency pair and level -func (c *CoinbasePro) GetOrderbook(ctx context.Context, symbol string, level int) (interface{}, error) { - orderbook := OrderbookResponse{} +// GetProduct returns information on a single specified currency pair +func (c *CoinbasePro) GetProduct(ctx context.Context, productID string) (Product, error) { + resp := Product{} + path := fmt.Sprintf("%s/%s", coinbaseproProducts, productID) + + return resp, c.SendHTTPRequest(ctx, exchange.RestSpot, path, &resp) +} +func (c *CoinbasePro) OrderbookHelper(obI OrderbookIntermediaryResponse, level int32) (*OrderbookFinalResponse, error) { + // obF := OrderbookFinalResponse{ + // Bids: make([]GenOrderDetail, len(obI.Bids)), + // Asks: make([]GenOrderDetail, len(obI.Asks)), + // Sequence: obI.Sequence, + // } + return nil, nil +} + +// GetOrderbook returns orderbook by currency pair and level +func (c *CoinbasePro) GetOrderbook(ctx context.Context, symbol string, level int32) (*OrderbookFinalResponse, error) { path := fmt.Sprintf("%s/%s/%s", coinbaseproProducts, symbol, coinbaseproOrderbook) if level > 0 { - levelStr := strconv.Itoa(level) - path = fmt.Sprintf("%s/%s/%s?level=%s", coinbaseproProducts, symbol, coinbaseproOrderbook, levelStr) + var params Params + params.urlVals = url.Values{} + params.urlVals.Set("level", strconv.Itoa(int(level))) + + path = common.EncodeURLValues(path, params.urlVals) } - if err := c.SendHTTPRequest(ctx, exchange.RestSpot, path, &orderbook); err != nil { + data := OrderbookIntermediaryResponse{} + err := c.SendHTTPRequest(ctx, exchange.RestSpot, path, &data) + if err != nil { return nil, err } - if level == 3 { - ob := OrderbookL3{ - Sequence: orderbook.Sequence, - Bids: make([]OrderL3, len(orderbook.Bids)), - Asks: make([]OrderL3, len(orderbook.Asks)), - } - ob.Sequence = orderbook.Sequence - for x := range orderbook.Asks { - priceConv, ok := orderbook.Asks[x][0].(string) - if !ok { - return nil, errors.New("unable to type assert price") - } - price, err := strconv.ParseFloat(priceConv, 64) - if err != nil { - return nil, err - } - amountConv, ok := orderbook.Asks[x][1].(string) - if !ok { - return nil, errors.New("unable to type assert amount") - } - amount, err := strconv.ParseFloat(amountConv, 64) - if err != nil { - return nil, err - } - ordID, ok := orderbook.Asks[x][2].(string) - if !ok { - return nil, errors.New("unable to type assert order ID") - } - ob.Asks[x] = OrderL3{Price: price, Amount: amount, OrderID: ordID} - } - for x := range orderbook.Bids { - priceConv, ok := orderbook.Bids[x][0].(string) - if !ok { - return nil, errors.New("unable to type assert price") - } - price, err := strconv.ParseFloat(priceConv, 64) - if err != nil { - return nil, err - } - amountConv, ok := orderbook.Bids[x][1].(string) - if !ok { - return nil, errors.New("unable to type assert amount") - } - amount, err := strconv.ParseFloat(amountConv, 64) - if err != nil { - return nil, err - } - ordID, ok := orderbook.Bids[x][2].(string) - if !ok { - return nil, errors.New("unable to type assert order ID") - } - ob.Bids[x] = OrderL3{Price: price, Amount: amount, OrderID: ordID} - } - return ob, nil - } - ob := OrderbookL1L2{ - Sequence: orderbook.Sequence, - Bids: make([]OrderL1L2, len(orderbook.Bids)), - Asks: make([]OrderL1L2, len(orderbook.Asks)), - } - for x := range orderbook.Asks { - priceConv, ok := orderbook.Asks[x][0].(string) - if !ok { - return nil, errors.New("unable to type assert price") - } - price, err := strconv.ParseFloat(priceConv, 64) - if err != nil { - return nil, err - } - amountConv, ok := orderbook.Asks[x][1].(string) - if !ok { - return nil, errors.New("unable to type assert amount") - } - amount, err := strconv.ParseFloat(amountConv, 64) - if err != nil { - return nil, err - } - numOrders, ok := orderbook.Asks[x][2].(float64) - if !ok { - return nil, errors.New("unable to type assert number of orders") - } - ob.Asks[x] = OrderL1L2{Price: price, Amount: amount, NumOrders: numOrders} - } - for x := range orderbook.Bids { - priceConv, ok := orderbook.Bids[x][0].(string) - if !ok { - return nil, errors.New("unable to type assert price") - } - price, err := strconv.ParseFloat(priceConv, 64) - if err != nil { - return nil, err - } - amountConv, ok := orderbook.Bids[x][1].(string) - if !ok { - return nil, errors.New("unable to type assert amount") - } - amount, err := strconv.ParseFloat(amountConv, 64) - if err != nil { - return nil, err - } - numOrders, ok := orderbook.Bids[x][2].(float64) - if !ok { - return nil, errors.New("unable to type assert number of orders") - } - ob.Bids[x] = OrderL1L2{Price: price, Amount: amount, NumOrders: numOrders} + resp, err := c.OrderbookHelper(data, level) + if err != nil { + return nil, err } - return ob, nil + + // if level == 3 { + // ob := OrderbookL3{ + // Sequence: orderbook.Sequence, + // Bids: make([]OrderL3, len(orderbook.Bids)), + // Asks: make([]OrderL3, len(orderbook.Asks)), + // } + // ob.Sequence = orderbook.Sequence + // for x := range orderbook.Asks { + // priceConv, ok := orderbook.Asks[x][0].(string) + // if !ok { + // return nil, errors.New("unable to type assert price") + // } + // price, err := strconv.ParseFloat(priceConv, 64) + // if err != nil { + // return nil, err + // } + // amountConv, ok := orderbook.Asks[x][1].(string) + // if !ok { + // return nil, errors.New("unable to type assert amount") + // } + // amount, err := strconv.ParseFloat(amountConv, 64) + // if err != nil { + // return nil, err + // } + // ordID, ok := orderbook.Asks[x][2].(string) + // if !ok { + // return nil, errors.New("unable to type assert order ID") + // } + // ob.Asks[x] = OrderL3{Price: price, Amount: amount, OrderID: ordID} + // } + // for x := range orderbook.Bids { + // priceConv, ok := orderbook.Bids[x][0].(string) + // if !ok { + // return nil, errors.New("unable to type assert price") + // } + // price, err := strconv.ParseFloat(priceConv, 64) + // if err != nil { + // return nil, err + // } + // amountConv, ok := orderbook.Bids[x][1].(string) + // if !ok { + // return nil, errors.New("unable to type assert amount") + // } + // amount, err := strconv.ParseFloat(amountConv, 64) + // if err != nil { + // return nil, err + // } + // ordID, ok := orderbook.Bids[x][2].(string) + // if !ok { + // return nil, errors.New("unable to type assert order ID") + // } + // ob.Bids[x] = OrderL3{Price: price, Amount: amount, OrderID: ordID} + // } + // return ob, nil + // } + // ob := OrderbookL1L2{ + // Sequence: orderbook.Sequence, + // Bids: make([]OrderL1L2, len(orderbook.Bids)), + // Asks: make([]OrderL1L2, len(orderbook.Asks)), + // } + // for x := range orderbook.Asks { + // priceConv, ok := orderbook.Asks[x][0].(string) + // if !ok { + // return nil, errors.New("unable to type assert price") + // } + // price, err := strconv.ParseFloat(priceConv, 64) + // if err != nil { + // return nil, err + // } + // amountConv, ok := orderbook.Asks[x][1].(string) + // if !ok { + // return nil, errors.New("unable to type assert amount") + // } + // amount, err := strconv.ParseFloat(amountConv, 64) + // if err != nil { + // return nil, err + // } + // numOrders, ok := orderbook.Asks[x][2].(float64) + // if !ok { + // return nil, errors.New("unable to type assert number of orders") + // } + // ob.Asks[x] = OrderL1L2{Price: price, Amount: amount, NumOrders: numOrders} + // } + // for x := range orderbook.Bids { + // priceConv, ok := orderbook.Bids[x][0].(string) + // if !ok { + // return nil, errors.New("unable to type assert price") + // } + // price, err := strconv.ParseFloat(priceConv, 64) + // if err != nil { + // return nil, err + // } + // amountConv, ok := orderbook.Bids[x][1].(string) + // if !ok { + // return nil, errors.New("unable to type assert amount") + // } + // amount, err := strconv.ParseFloat(amountConv, 64) + // if err != nil { + // return nil, err + // } + // numOrders, ok := orderbook.Bids[x][2].(float64) + // if !ok { + // return nil, errors.New("unable to type assert number of orders") + // } + // ob.Bids[x] = OrderL1L2{Price: price, Amount: amount, NumOrders: numOrders} + // } + // return ob, nil + return resp, nil } // GetTicker returns ticker by currency pair @@ -265,265 +756,12 @@ func (c *CoinbasePro) GetStats(ctx context.Context, currencyPair string) (Stats, return stats, c.SendHTTPRequest(ctx, exchange.RestSpot, path, &stats) } -// GetCurrencies returns a list of supported currency on the exchange -// Warning: Not all currencies may be currently in use for tradinc. -func (c *CoinbasePro) GetCurrencies(ctx context.Context) ([]Currency, error) { - var currencies []Currency - - return currencies, c.SendHTTPRequest(ctx, exchange.RestSpot, coinbaseproCurrencies, ¤cies) -} - // GetCurrentServerTime returns the API server time func (c *CoinbasePro) GetCurrentServerTime(ctx context.Context) (ServerTime, error) { serverTime := ServerTime{} return serverTime, c.SendHTTPRequest(ctx, exchange.RestSpot, coinbaseproTime, &serverTime) } -// GetAccounts returns a list of trading accounts associated with the APIKEYS -func (c *CoinbasePro) GetAccounts(ctx context.Context) ([]AccountResponse, error) { - var resp []AccountResponse - - return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, coinbaseproAccounts, nil, &resp) -} - -// GetAccount returns information for a single account. Use this endpoint when -// account_id is known -func (c *CoinbasePro) GetAccount(ctx context.Context, accountID string) (AccountResponse, error) { - resp := AccountResponse{} - path := fmt.Sprintf("%s/%s", coinbaseproAccounts, accountID) - - return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp) -} - -// GetAccountHistory returns a list of account activity. Account activity either -// increases or decreases your account balance. Items are paginated and sorted -// latest first. -func (c *CoinbasePro) GetAccountHistory(ctx context.Context, accountID string) ([]AccountLedgerResponse, error) { - var resp []AccountLedgerResponse - path := fmt.Sprintf("%s/%s/%s", coinbaseproAccounts, accountID, coinbaseproLedger) - - return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp) -} - -// GetHolds returns the holds that are placed on an account for any active -// orders or pending withdraw requests. As an order is filled, the hold amount -// is updated. If an order is canceled, any remaining hold is removed. For a -// withdraw, once it is completed, the hold is removed. -func (c *CoinbasePro) GetHolds(ctx context.Context, accountID string) ([]AccountHolds, error) { - var resp []AccountHolds - path := fmt.Sprintf("%s/%s/%s", coinbaseproAccounts, accountID, coinbaseproHolds) - - return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp) -} - -// PlaceLimitOrder places a new limit order. Orders can only be placed if the -// account has sufficient funds. Once an order is placed, account funds -// will be put on hold for the duration of the order. How much and which funds -// are put on hold depends on the order type and parameters specified. -// -// GENERAL PARAMS -// clientRef - [optional] Order ID selected by you to identify your order -// side - buy or sell -// productID - A valid product id -// stp - [optional] Self-trade prevention flag -// -// LIMIT ORDER PARAMS -// price - Price per bitcoin -// amount - Amount of BTC to buy or sell -// timeInforce - [optional] GTC, GTT, IOC, or FOK (default is GTC) -// cancelAfter - [optional] min, hour, day * Requires time_in_force to be GTT -// postOnly - [optional] Post only flag Invalid when time_in_force is IOC or FOK -func (c *CoinbasePro) PlaceLimitOrder(ctx context.Context, clientRef string, price, amount float64, side string, timeInforce RequestParamsTimeForceType, cancelAfter, productID, stp string, postOnly bool) (string, error) { - resp := GeneralizedOrderResponse{} - req := make(map[string]interface{}) - req["type"] = order.Limit.Lower() - req["price"] = strconv.FormatFloat(price, 'f', -1, 64) - req["size"] = strconv.FormatFloat(amount, 'f', -1, 64) - req["side"] = side - req["product_id"] = productID - - if cancelAfter != "" { - req["cancel_after"] = cancelAfter - } - if timeInforce != "" { - req["time_in_force"] = timeInforce - } - if clientRef != "" { - req["client_oid"] = clientRef - } - if stp != "" { - req["stp"] = stp - } - if postOnly { - req["post_only"] = postOnly - } - - err := c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproOrders, req, &resp) - if err != nil { - return "", err - } - - return resp.ID, nil -} - -// PlaceMarketOrder places a new market order. -// Orders can only be placed if the account has sufficient funds. Once an order -// is placed, account funds will be put on hold for the duration of the order. -// How much and which funds are put on hold depends on the order type and -// parameters specified. -// -// GENERAL PARAMS -// clientRef - [optional] Order ID selected by you to identify your order -// side - buy or sell -// productID - A valid product id -// stp - [optional] Self-trade prevention flag -// -// MARKET ORDER PARAMS -// size - [optional]* Desired amount in BTC -// funds [optional]* Desired amount of quote currency to use -// * One of size or funds is required. -func (c *CoinbasePro) PlaceMarketOrder(ctx context.Context, clientRef string, size, funds float64, side, productID, stp string) (string, error) { - resp := GeneralizedOrderResponse{} - req := make(map[string]interface{}) - req["side"] = side - req["product_id"] = productID - req["type"] = order.Market.Lower() - - if size != 0 { - req["size"] = strconv.FormatFloat(size, 'f', -1, 64) - } - if funds != 0 { - req["funds"] = strconv.FormatFloat(funds, 'f', -1, 64) - } - if clientRef != "" { - req["client_oid"] = clientRef - } - if stp != "" { - req["stp"] = stp - } - - err := c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproOrders, req, &resp) - if err != nil { - return "", err - } - - return resp.ID, nil -} - -// PlaceMarginOrder places a new market order. -// Orders can only be placed if the account has sufficient funds. Once an order -// is placed, account funds will be put on hold for the duration of the order. -// How much and which funds are put on hold depends on the order type and -// parameters specified. -// -// GENERAL PARAMS -// clientRef - [optional] Order ID selected by you to identify your order -// side - buy or sell -// productID - A valid product id -// stp - [optional] Self-trade prevention flag -// -// MARGIN ORDER PARAMS -// size - [optional]* Desired amount in BTC -// funds - [optional]* Desired amount of quote currency to use -func (c *CoinbasePro) PlaceMarginOrder(ctx context.Context, clientRef string, size, funds float64, side, productID, stp string) (string, error) { - resp := GeneralizedOrderResponse{} - req := make(map[string]interface{}) - req["side"] = side - req["product_id"] = productID - req["type"] = "margin" - - if size != 0 { - req["size"] = strconv.FormatFloat(size, 'f', -1, 64) - } - if funds != 0 { - req["funds"] = strconv.FormatFloat(funds, 'f', -1, 64) - } - if clientRef != "" { - req["client_oid"] = clientRef - } - if stp != "" { - req["stp"] = stp - } - - err := c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproOrders, req, &resp) - if err != nil { - return "", err - } - - return resp.ID, nil -} - -// CancelExistingOrder cancels order by orderID -func (c *CoinbasePro) CancelExistingOrder(ctx context.Context, orderID string) error { - path := fmt.Sprintf("%s/%s", coinbaseproOrders, orderID) - - return c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodDelete, path, nil, nil) -} - -// CancelAllExistingOrders cancels all open orders on the exchange and returns -// and array of order IDs -// currencyPair - [optional] all orders for a currencyPair string will be -// canceled -func (c *CoinbasePro) CancelAllExistingOrders(ctx context.Context, currencyPair string) ([]string, error) { - var resp []string - req := make(map[string]interface{}) - - if len(currencyPair) > 0 { - req["product_id"] = currencyPair - } - return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodDelete, coinbaseproOrders, req, &resp) -} - -// GetOrders lists current open orders. Only open or un-settled orders are -// returned. As soon as an order is no longer open and settled, it will no -// longer appear in the default request. -// status - can be a range of "open", "pending", "done" or "active" -// currencyPair - [optional] for example "BTC-USD" -func (c *CoinbasePro) GetOrders(ctx context.Context, status []string, currencyPair string) ([]GeneralizedOrderResponse, error) { - var resp []GeneralizedOrderResponse - params := url.Values{} - - for _, individualStatus := range status { - params.Add("status", individualStatus) - } - if currencyPair != "" { - params.Set("product_id", currencyPair) - } - - path := common.EncodeURLValues(coinbaseproOrders, params) - return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp) -} - -// GetOrder returns a single order by order id. -func (c *CoinbasePro) GetOrder(ctx context.Context, orderID string) (GeneralizedOrderResponse, error) { - resp := GeneralizedOrderResponse{} - path := fmt.Sprintf("%s/%s", coinbaseproOrders, orderID) - - return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp) -} - -// GetFills returns a list of recent fills -func (c *CoinbasePro) GetFills(ctx context.Context, orderID, currencyPair string) ([]FillResponse, error) { - var resp []FillResponse - params := url.Values{} - - if orderID != "" { - params.Set("order_id", orderID) - } - if currencyPair != "" { - params.Set("product_id", currencyPair) - } - if params.Get("order_id") == "" && params.Get("product_id") == "" { - return resp, errors.New("no parameters set") - } - - path := common.EncodeURLValues(coinbaseproFills, params) - return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp) -} - // MarginTransfer sends funds between a standard/default profile and a margin // profile. // A deposit will transfer funds from the default profile into the margin @@ -566,99 +804,6 @@ func (c *CoinbasePro) ClosePosition(ctx context.Context, repayOnly bool) (Accoun c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproPositionClose, req, &resp) } -// GetPayMethods returns a full list of payment methods -func (c *CoinbasePro) GetPayMethods(ctx context.Context) ([]PaymentMethod, error) { - var resp []PaymentMethod - - return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, coinbaseproPaymentMethod, nil, &resp) -} - -// DepositViaPaymentMethod deposits funds from a payment method. See the Payment -// Methods section for retrieving your payment methods. -// -// amount - The amount to deposit -// currency - The type of currency -// paymentID - ID of the payment method -func (c *CoinbasePro) DepositViaPaymentMethod(ctx context.Context, amount float64, currency, paymentID string) (DepositWithdrawalInfo, error) { - resp := DepositWithdrawalInfo{} - req := make(map[string]interface{}) - req["amount"] = amount - req["currency"] = currency - req["payment_method_id"] = paymentID - - return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproPaymentMethodDeposit, req, &resp) -} - -// DepositViaCoinbase deposits funds from a coinbase account. Move funds between -// a Coinbase account and coinbasepro trading account within daily limits. Moving -// funds between Coinbase and coinbasepro is instant and free. See the Coinbase -// Accounts section for retrieving your Coinbase accounts. -// -// amount - The amount to deposit -// currency - The type of currency -// accountID - ID of the coinbase account -func (c *CoinbasePro) DepositViaCoinbase(ctx context.Context, amount float64, currency, accountID string) (DepositWithdrawalInfo, error) { - resp := DepositWithdrawalInfo{} - req := make(map[string]interface{}) - req["amount"] = amount - req["currency"] = currency - req["coinbase_account_id"] = accountID - - return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproDepositCoinbase, req, &resp) -} - -// WithdrawViaPaymentMethod withdraws funds to a payment method -// -// amount - The amount to withdraw -// currency - The type of currency -// paymentID - ID of the payment method -func (c *CoinbasePro) WithdrawViaPaymentMethod(ctx context.Context, amount float64, currency, paymentID string) (DepositWithdrawalInfo, error) { - resp := DepositWithdrawalInfo{} - req := make(map[string]interface{}) - req["amount"] = amount - req["currency"] = currency - req["payment_method_id"] = paymentID - - return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproWithdrawalPaymentMethod, req, &resp) -} - -// /////////////////////// NO ROUTE FOUND ERROR //////////////////////////////// -// WithdrawViaCoinbase withdraws funds to a coinbase account. -// -// amount - The amount to withdraw -// currency - The type of currency -// accountID - ID of the coinbase account -// func (c *CoinbasePro) WithdrawViaCoinbase(amount float64, currency, accountID string) (DepositWithdrawalInfo, error) { -// resp := DepositWithdrawalInfo{} -// req := make(map[string]interface{}) -// req["amount"] = amount -// req["currency"] = currency -// req["coinbase_account_id"] = accountID -// -// return resp, -// c.SendAuthenticatedHTTPRequest(ctx,http.MethodPost, coinbaseproWithdrawalCoinbase, req, &resp) -// } - -// WithdrawCrypto withdraws funds to a crypto address -// -// amount - The amount to withdraw -// currency - The type of currency -// cryptoAddress - A crypto address of the recipient -func (c *CoinbasePro) WithdrawCrypto(ctx context.Context, amount float64, currency, cryptoAddress string) (DepositWithdrawalInfo, error) { - resp := DepositWithdrawalInfo{} - req := make(map[string]interface{}) - req["amount"] = amount - req["currency"] = currency - req["crypto_address"] = cryptoAddress - - return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproWithdrawalCrypto, req, &resp) -} - // GetCoinbaseAccounts returns a list of coinbase accounts func (c *CoinbasePro) GetCoinbaseAccounts(ctx context.Context) ([]CoinbaseAccounts, error) { var resp []CoinbaseAccounts @@ -722,34 +867,6 @@ func (c *CoinbasePro) GetTrailingVolume(ctx context.Context) ([]Volume, error) { c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, coinbaseproTrailingVolume, nil, &resp) } -// GetTransfers returns a history of withdrawal and or deposit transactions -func (c *CoinbasePro) GetTransfers(ctx context.Context, profileID, transferType string, limit int64, start, end time.Time) ([]TransferHistory, error) { - if !start.IsZero() && !end.IsZero() { - err := common.StartEndTimeCheck(start, end) - if err != nil { - return nil, err - } - } - req := make(map[string]interface{}) - if profileID != "" { - req["profile_id"] = profileID - } - if !start.IsZero() { - req["before"] = start.Format(time.RFC3339) - } - if !end.IsZero() { - req["after"] = end.Format(time.RFC3339) - } - if limit > 0 { - req["limit"] = limit - } - if transferType != "" { - req["type"] = transferType - } - var resp []TransferHistory - return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, coinbaseproTransfers, req, &resp) -} - // SendHTTPRequest sends an unauthenticated HTTP request func (c *CoinbasePro) SendHTTPRequest(ctx context.Context, ep exchange.URL, path string, result interface{}) error { endpoint, err := c.API.Endpoints.GetURL(ep) @@ -802,9 +919,9 @@ func (c *CoinbasePro) SendAuthenticatedHTTPRequest(ctx context.Context, ep excha } headers := make(map[string]string) + headers["CB-ACCESS-KEY"] = creds.Key headers["CB-ACCESS-SIGN"] = crypto.Base64Encode(hmac) headers["CB-ACCESS-TIMESTAMP"] = n - headers["CB-ACCESS-KEY"] = creds.Key headers["CB-ACCESS-PASSPHRASE"] = creds.ClientID headers["Content-Type"] = "application/json" @@ -901,3 +1018,67 @@ func getInternationalBankDepositFee(c currency.Code) float64 { return fee } + +// PrepareDSL adds the direction, step, and limit queries for pagination +func (p *Params) PrepareDSL(direction, step string, limit int64) { + p.urlVals.Set(direction, step) + if limit >= 0 { + p.urlVals.Set("limit", strconv.FormatInt(limit, 10)) + } +} + +// PrepareDateString encodes a set of parameters indicating start & end dates +func (p *Params) PrepareDateString(startDate, endDate time.Time) error { + err := common.StartEndTimeCheck(startDate, endDate) + + if err == nil { + p.urlVals.Set("start_date", startDate.Format(time.RFC3339)) + p.urlVals.Set("end_date", endDate.Format(time.RFC3339)) + } + + if err != nil { + if err.Error() == "start date unset" || err.Error() == "end date unset" { + return nil + } + } + + return err +} + +func (p *Params) PrepareProfIDAndProdID(profileID, currencyPair string) { + p.urlVals.Set("profile_id", profileID) + p.urlVals.Set("product_id", currencyPair) +} + +// PrepareAddAddress constructs an element of a slice to be passed to the +// AddAddresses function +func PrepareAddAddress(currency, address, destination_tag, label, vaspID string, + verifiedSelfHosted bool) (AddAddressRequest, error) { + if address == "" { + return AddAddressRequest{}, errors.New("address cannot be empty") + } + vIDCheck := []string{"Coinbase", "Anchorage", "Balance", "bitFlyer", "BitGo", + "Bittrex", "BlockFi", "Circle", "Coinhako", "Fidelity", "Gemini", "Huobi", + "Kraken", "Paxos", "PayPal", "Robinhood", "Shakepay", "StandardCustody", + "Tradestation", "Zero Hash", "Bitstamp"} + if vaspID != "" && !common.StringDataCompare(vIDCheck, vaspID) { + return AddAddressRequest{}, + errors.New("vaspID must be one of the following or empty: " + + strings.Join(vIDCheck, ", ")) + } + + req := AddAddressRequest{currency, To{address, destination_tag}, label, + verifiedSelfHosted, vaspID} + + // TODO: It also lets us add an arbitrary amount of strings under this object, + // but doesn't explain what they do. Investigate more later. + + return req, nil +} + +// func (c *CoinbasePro) GetTravelRules(ctx context.Context) ([]TravelRule, error) { +// var resp []TravelRule +// accounts, err := c.GetAccounts(ctx) +// path := fmt.Sprintf("/%s/", accounts[0].ID) +// return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp) +// } diff --git a/exchanges/coinbasepro/coinbasepro_test.go b/exchanges/coinbasepro/coinbasepro_test.go index af5c2ab37ef..701feb56d26 100644 --- a/exchanges/coinbasepro/coinbasepro_test.go +++ b/exchanges/coinbasepro/coinbasepro_test.go @@ -3,8 +3,10 @@ package coinbasepro import ( "context" "errors" + "fmt" "log" "net/http" + "net/url" "os" "sync" "testing" @@ -37,10 +39,16 @@ const ( apiSecret = "" clientID = "" // passphrase you made at API CREATION canManipulateRealOrders = false + testingInSandbox = false ) func TestMain(m *testing.M) { c.SetDefaults() + if testingInSandbox { + c.API.Endpoints.SetDefaultEndpoints(map[exchange.URL]string{ + exchange.RestSpot: coinbaseproSandboxAPIURL, + }) + } cfg := config.GetConfig() err := cfg.LoadConfig("../../testdata/configtest.json", true) if err != nil { @@ -77,22 +85,373 @@ func TestStart(t *testing.T) { testWg.Wait() } +func TestGetAccounts(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + var resp []AccountResponse + resp, err := c.GetAccounts(context.Background()) + if err != nil { + t.Error("CoinBasePro GetAccounts() error", err) + } + if resp[0].ID == "" { + t.Error("CoinBasePro GetAccounts() error, no ID returned") + } + if resp[0].Currency == "" { + t.Error("CoinBasePro GetAccounts() error, no Currency returned") + } + if resp[0].ProfileID == "" { + t.Error("CoinBasePro GetAccounts() error, no ProfileID returned") + } + +} + +func TestGetAccount(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + longResp, err := c.GetAccounts(context.Background()) + if err != nil { + t.Error("CoinBasePro GetAccounts() error", err) + } + shortResp, err := c.GetAccount(context.Background(), longResp[0].ID) + if err != nil { + t.Error("CoinBasePro GetAccount() error", err) + } + if shortResp != longResp[0] { + t.Error("CoinBasePro GetAccount() error, mismatched responses") + } +} + +func TestGetHolds(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + accID, err := c.GetAccounts(context.Background()) + if err != nil { + t.Error("CoinBasePro GetAccounts() error", err) + } + result, err := c.GetHolds(context.Background(), accID[0].ID, pageNone, "", 2) + if err != nil { + t.Error("CoinBasePro GetHolds() error", err) + } + t.Logf("%+v", result) +} + +func TestGetAccountLedger(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + accID, err := c.GetAccounts(context.Background()) + if err != nil { + t.Error("CoinBasePro GetAccounts() error", err) + } + _, err = c.GetAccountLedger(context.Background(), accID[0].ID, pageNone, "", "", time.Time{}, time.Time{}, 2) + if err == nil { + t.Error("CoinBasePro GetAccountLedger() error, expected error due to invalid time range") + } + _, err = c.GetAccountLedger(context.Background(), accID[0].ID, pageBefore, "", "a", + time.Unix(1, 1), time.Now(), 3) + if err != nil { + t.Error("CoinBasePro GetAccountLedger() error", err) + } +} + +func TestGetAccountTransfers(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + accID, err := c.GetAccounts(context.Background()) + if err != nil { + t.Error("CoinBasePro GetAccounts() error", err) + } + _, err = c.GetAccountTransfers(context.Background(), accID[0].ID, "", "", "", 3) + if err != nil { + t.Error(err) + } +} + +func TestGetAddressBook(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + _, err := c.GetAddressBook(context.Background()) + if err != nil { + t.Error("CoinBasePro GetAddressBook() error", err) + } +} + +func TestAddAddresses(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) + var req [1]AddAddressRequest + var err error + req[0], err = PrepareAddAddress("this test", "is not", "properly", "implemented", "Coinbase", false) + if err != nil { + t.Error(err) + } + _, err = c.AddAddresses(context.Background(), req[:]) + if err != nil { + t.Error("CoinBasePro AddAddresses() error", err) + } +} + +func TestDeleteAddress(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) + err := c.DeleteAddress(context.Background(), "Test not properly implemented") + if err != nil { + t.Error("CoinBasePro DeleteAddress() error", err) + } +} + +func TestGetCoinbaseWallets(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + res, err := c.GetCoinbaseAccounts(context.Background()) + if err != nil { + t.Error("CoinBasePro GetCoinbaseAccounts() error", err) + } + log.Printf("%+v", res) +} + +func TestGenerateCryptoAddress(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + accID, err := c.GetCoinbaseWallets(context.Background()) + if err != nil { + t.Error("CoinBasePro GetAccounts() error", err) + } + _, err = c.GenerateCryptoAddress(context.Background(), accID[0].ID, "", "") + if err != nil { + t.Error("CoinBasePro GenerateCryptoAddress() error", err) + } +} + +func TestConvertCurrency(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) + _, err := c.ConvertCurrency(context.Background(), "This test", " is not", "implemented", "quite yet", 0) + if err != nil { + t.Error("CoinBasePro ConvertCurrency() error", err) + } +} + +func TestGetConversionByID(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + _, err := c.GetConversionByID(context.Background(), "Test not", "implemented yet") + if err == nil { + t.Error("This really should have failed since a proper ID wasn't supplied.") + } +} + +func TestGetCurrencies(t *testing.T) { + _, err := c.GetCurrencies(context.Background()) + if err != nil { + t.Error("GetCurrencies() error", err) + } +} + +func TestGetCurrenciesByID(t *testing.T) { + resp, err := c.GetCurrenciesByID(context.Background(), "BTC") + if err != nil { + t.Error("GetCurrenciesByID() error", err) + } + if resp.Name != "Bitcoin" { + t.Errorf("GetCurrenciesByID() error, incorrect name returned, expected 'Bitcoin', got '%s'", + resp.Name) + } +} + +func TestDepositViaCoinbase(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) + _, err := c.DepositViaCoinbase(context.Background(), "This test", "is not", "yet implemented", 1) + if err != nil { + t.Error("CoinBasePro DepositViaCoinbase() error", err) + } +} + +func TestDepositViaPaymentMethod(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) + _, err := c.DepositViaPaymentMethod(context.Background(), "This test", "is not", "yet implemented", 1) + if err != nil { + t.Error("CoinBasePro DepositViaPaymentMethod() error", err) + } +} + +func TestGetPayMethods(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + _, err := c.GetPayMethods(context.Background()) + if err != nil { + t.Error("CoinBasePro GetPayMethods() error", err) + } +} + +func TestGetAllTransfers(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + _, err := c.GetAllTransfers(context.Background(), "", "", "", "", 3) + if err != nil { + t.Error("CoinBasePro GetAllTransfers() error", err) + } +} + +func TestGetTransferByID(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + _, err := c.GetTransferByID(context.Background(), "Not yet implemented") + if err == nil { + t.Error("This really should have failed since a proper ID wasn't supplied.") + } +} + +func TestSendTravelInfoForTransfer(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) + _, err := c.SendTravelInfoForTransfer(context.Background(), "This test", "is not", "yet implemented") + if err != nil { + t.Error("CoinBasePro SendTravelInfoForTransfer() error", err) + } +} + +func TestWithdrawViaCoinbase(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) + _, err := c.WithdrawViaCoinbase(context.Background(), "This test", "is not", "yet implemented", 1) + if err != nil { + t.Error("CoinBasePro WithdrawViaCoinbase() error", err) + } +} + +func TestWithdrawCrypto(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) + _, err := c.WithdrawCrypto(context.Background(), "This", "test", "is", "not", "implemented", "yet", 1, + false, false, 2) + if err != nil { + t.Error("CoinBasePro WithdrawCrypto() error", err) + } +} + +func TestGetWithdrawalFeeEstimate(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + _, err := c.GetWithdrawalFeeEstimate(context.Background(), "", "", "") + if err == nil { + t.Error("CoinBasePro GetWithdrawalFeeEstimate() error, expected error due to empty field") + } + _, err = c.GetWithdrawalFeeEstimate(context.Background(), "BTC", "", "") + if err == nil { + t.Error("CoinBasePro GetWithdrawalFeeEstimate() error, expected error due to empty field") + } + resp, err := c.GetWithdrawalFeeEstimate(context.Background(), "This test is not", "yet implemented", + "due to not knowing a valid network string") + if err == nil { + t.Error("This should have errored out due to an improper network string") + } + log.Printf("%+v, %+v", resp, err) +} + +func TestWithdrawViaPaymentMethod(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) + _, err := c.WithdrawViaPaymentMethod(context.Background(), "This test", "is not", "yet implemented", 1) + if err != nil { + t.Error("CoinBasePro WithdrawViaPaymentMethod() error", err) + } +} + +func TestGetFees(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + resp, err := c.GetFees(context.Background()) + if err != nil { + t.Error("CoinBasePro GetFees() error", err) + } + if resp.TakerFeeRate == 0 { + t.Error("CoinBasePro GetFees() error, expected non-zero value for taker fee rate") + } +} + +func TestGetFills(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + _, err := c.GetFills(context.Background(), "", "", "", "", "", 0, time.Time{}, time.Time{}) + if err == nil { + t.Error("CoinBasePro GetFills() error, expected error due to empty order and product ID") + } + + _, err = c.GetFills(context.Background(), "1", "", "", "", "", 0, time.Time{}, time.Time{}) + if err == nil { + t.Error("CoinBasePro GetFills() error, expected error due to null time range") + } + + _, err = c.GetFills(context.Background(), "", testPair.String(), "", "", "spot", 0, time.Unix(1, 1), time.Now()) + if err != nil { + t.Error("CoinBasePro GetFills() error", err) + } +} + +func TestGetOrders(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + status := []string{"open", "pending", "active", "done"} + + resp, err := c.GetOrders(context.Background(), "", "", "", "", "", "", "", time.Unix(1, 1), time.Now(), 5, status) + if err != nil { + t.Error("CoinBasePro GetOrders() error", err) + } + log.Printf("%+v", resp) +} + +func TestCancelAllExistingOrders(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) + _, err := c.CancelAllExistingOrders(context.Background(), "This test is not", "yet implemented") + if err != nil { + t.Error("CoinBasePro CancelAllExistingOrders() error", err) + } +} + +func TestPlaceOrder(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) + _, err := c.PlaceOrder(context.Background(), "this", "test", "has", "not", "been", "implemented", "awaiting", "sandbox", + "testing", 1, 1, 1, 1, false) + if err == nil { + t.Error("This probably should have errored out due to not being implemented") + } +} + +func TestGetOrder(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + _, err := c.GetOrder(context.Background(), "This test is not", "yet implemented", true) + if err == nil { + t.Error("This should have errored out due to not being implemented") + } +} + +func TestCancelExistingOrder(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) + _, err := c.CancelExistingOrder(context.Background(), "This test", "is not", "yet implemented", true) + if err != nil { + t.Error("CoinBasePro CancelExistingOrder() error", err) + } +} + +func TestGetSignedPrices(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + resp, err := c.GetSignedPrices(context.Background()) + if err != nil { + t.Error("CoinBasePro GetSignedPrices() error", err) + } + if resp.Timestamp == "" { + t.Error("CoinBasePro GetSignedPrices() error, expected non-empty timestamp") + } +} + func TestGetProducts(t *testing.T) { - _, err := c.GetProducts(context.Background()) + resp, err := c.GetProducts(context.Background(), "") + if err != nil { + t.Error("Coinbase, GetProducts() Error:", err) + } + if resp[0].ID == "" { + t.Error("Coinbase, GetProducts() Error, expected non-empty string") + } +} + +func TestGetProduct(t *testing.T) { + resp, err := c.GetProduct(context.Background(), "BTC-USD") if err != nil { - t.Errorf("Coinbase, GetProducts() Error: %s", err) + t.Error("Coinbase, GetProduct() Error:", err) + } + if resp.ID != "BTC-USD" { + t.Error("Coinbase, GetProduct() Error, expected BTC-USD") } } func TestGetOrderbook(t *testing.T) { - _, err := c.GetOrderbook(context.Background(), testPair.String(), 2) + resp, err := c.GetOrderbook(context.Background(), testPair.String(), 1) if err != nil { t.Error(err) } - _, err = c.GetOrderbook(context.Background(), testPair.String(), 3) + log.Printf("%+v", resp) + resp, err = c.GetOrderbook(context.Background(), testPair.String(), 3) if err != nil { t.Error(err) } + log.Printf("%+v", resp) } func TestGetTicker(t *testing.T) { @@ -137,13 +496,6 @@ func TestGetStats(t *testing.T) { } } -func TestGetCurrencies(t *testing.T) { - _, err := c.GetCurrencies(context.Background()) - if err != nil { - t.Error("GetCurrencies() error", err) - } -} - func TestGetCurrentServerTime(t *testing.T) { _, err := c.GetCurrentServerTime(context.Background()) if err != nil { @@ -179,49 +531,14 @@ func TestAuthRequests(t *testing.T) { if err == nil { t.Error("Expecting error") } - accountHistoryResponse, err := c.GetAccountHistory(context.Background(), - "13371337-1337-1337-1337-133713371337") - if len(accountHistoryResponse) > 0 { - t.Error("Expecting no data returned") - } if err == nil { t.Error("Expecting error") } - getHoldsResponse, err := c.GetHolds(context.Background(), - "13371337-1337-1337-1337-133713371337") - if len(getHoldsResponse) > 0 { - t.Error("Expecting no data returned") - } - if err == nil { - t.Error("Expecting error") - } - orderResponse, err := c.PlaceLimitOrder(context.Background(), - "", 0.001, 0.001, - order.Buy.Lower(), "", "", testPair.String(), "", false) - if orderResponse != "" { - t.Error("Expecting no data returned") - } - if err == nil { - t.Error("Expecting error") - } - marketOrderResponse, err := c.PlaceMarketOrder(context.Background(), - "", 1, 0, - order.Buy.Lower(), testPair.String(), "") - if marketOrderResponse != "" { - t.Error("Expecting no data returned") - } - if err == nil { - t.Error("Expecting error") - } - fillsResponse, err := c.GetFills(context.Background(), - "1337", testPair.String()) - if len(fillsResponse) > 0 { - t.Error("Expecting no data returned") - } - if err == nil { - t.Error("Expecting error") - } - _, err = c.GetFills(context.Background(), "", "") + // getHoldsResponse, err := c.GetHolds(context.Background(), + // "13371337-1337-1337-1337-133713371337") + // if len(getHoldsResponse) > 0 { + // t.Error("Expecting no data returned") + // } if err == nil { t.Error("Expecting error") } @@ -241,10 +558,6 @@ func TestAuthRequests(t *testing.T) { if err == nil { t.Error("Expecting error") } - _, err = c.GetPayMethods(context.Background()) - if err != nil { - t.Error("GetPayMethods() error", err) - } _, err = c.GetCoinbaseAccounts(context.Background()) if err != nil { t.Error("GetCoinbaseAccounts() error", err) @@ -1062,11 +1375,88 @@ func TestGetHistoricTrades(t *testing.T) { } } -func TestGetTransfers(t *testing.T) { +func TestPrepareDSL(t *testing.T) { t.Parallel() - sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - _, err := c.GetTransfers(context.Background(), "", "", 100, time.Time{}, time.Time{}) + var expectedResult Params + expectedResult.urlVals = map[string][]string{ + "before": {"1"}, + "limit": {"2"}, + } + var result Params + + result.urlVals = make(url.Values) + + result.PrepareDSL("before", "1", 2) + if fmt.Sprint(expectedResult) != fmt.Sprint(result) { + t.Errorf("CoinBasePro PrepareDSL(), Expected: %v, Received: %v", expectedResult, result) + } +} + +func TestPrepareDateString(t *testing.T) { + t.Parallel() + var expectedResult Params + expectedResult.urlVals = map[string][]string{ + "start_date": {"1970-01-01T00:00:01Z"}, + "end_date": {"1970-01-01T00:00:02Z"}, + } + var result Params + + result.urlVals = make(url.Values) + + err := result.PrepareDateString(time.Unix(1, 1).UTC(), time.Unix(2, 2).UTC()) if err != nil { - t.Error(err) + t.Error("CoinBasePro PrepareDateString() error", err) + } + if fmt.Sprint(expectedResult) != fmt.Sprint(result) { + t.Errorf("CoinBasePro PrepareDateString(), Expected: %v, Received: %v", expectedResult, result) + } + + var newTime time.Time + err = result.PrepareDateString(newTime, newTime) + if err != nil { + t.Error("CoinBasePro PrepareDateString() error", err) + } + + err = result.PrepareDateString(time.Unix(2, 2).UTC(), time.Unix(1, 1).UTC()) + if err == nil { + t.Error("CoinBasePro PrepareDateString() expected StartAfterEnd error") + } +} + +func TestPrepareProfIDAndProdID(t *testing.T) { + t.Parallel() + var expectedResult Params + expectedResult.urlVals = map[string][]string{ + "profile_id": {"123"}, + "product_id": {"BTC-USD"}, + } + var result Params + result.urlVals = make(url.Values) + + result.PrepareProfIDAndProdID("123", "BTC-USD") + if fmt.Sprint(expectedResult) != fmt.Sprint(result) { + t.Errorf("CoinBasePro PrepareProfIDAndProdID(), Expected: %v, Received: %v", expectedResult, result) + } +} + +func TestPrepareAddAddress(t *testing.T) { + t.Parallel() + + _, err := PrepareAddAddress("", "", "", "", "", false) + if err == nil { + t.Error("CoinBasePro PrepareAddAddress() Expected error for empty address") + } + _, err = PrepareAddAddress("", "test", "", "", "meow", false) + if err == nil { + t.Error("CoinBasePro PrepareAddAddress() Expected error for invalid vaspID") + } + + expectedResult := AddAddressRequest{"test", To{"woof", "meow"}, "whinny", false, "Coinbase"} + result, err := PrepareAddAddress("test", "woof", "meow", "whinny", "Coinbase", false) + if err != nil { + t.Error("CoinBasePro PrepareAddAddress() error", err) + } + if fmt.Sprint(expectedResult) != fmt.Sprint(result) { + t.Errorf("CoinBasePro PrepareAddAddress(), Expected: %v, Received: %v", expectedResult, result) } } diff --git a/exchanges/coinbasepro/coinbasepro_types.go b/exchanges/coinbasepro/coinbasepro_types.go index 2a8765edba7..d0b8efd9a64 100644 --- a/exchanges/coinbasepro/coinbasepro_types.go +++ b/exchanges/coinbasepro/coinbasepro_types.go @@ -1,6 +1,9 @@ package coinbasepro import ( + "encoding/json" + "net/url" + "strconv" "time" "github.com/thrasher-corp/gocryptotrader/currency" @@ -25,6 +28,7 @@ type Product struct { ForeignExchangeStableCoin bool `json:"fx_stablecoin"` MaxSlippagePercentage float64 `json:"max_slippage_percentage,string"` AuctionMode bool `json:"auction_mode"` + HighBidLimitPercentage string `json:"high_bid_limit_percentage"` } // Ticker holds basic ticker information @@ -62,16 +66,46 @@ type Stats struct { Open float64 `json:"open,string"` High float64 `json:"high,string"` Low float64 `json:"low,string"` - Volume float64 `json:"volume,string"` Last float64 `json:"last,string"` + Volume float64 `json:"volume,string"` Volume30Day float64 `json:"volume_30day,string"` } // Currency holds singular currency product information type Currency struct { - ID string - Name string - MinSize float64 `json:"min_size,string"` + ID string `json:"id"` + Name string `json:"name"` + MinSize float64 `json:"min_size,string"` + Status string `json:"status"` + Message string `json:"message"` + MaxPrecision float64 `json:"max_precision"` + ConvertibleTo []string `json:"convertible_to"` + Details struct { + Type string `json:"type"` + NetworkConfirmations int32 `json:"network_confirmations"` + SortOrder int32 `json:"sort_order"` + CryptoAddressLink string `json:"crypto_address_link"` + CryptoTransactionLink string `json:"crypto_transaction_link"` + PushPaymentMethods []string `json:"push_payment_methods"` + GroupTypes []string `json:"group_types"` + DisplayName string `json:"display_name"` + ProcessingTimeSeconds float64 `json:"processing_time_seconds"` + MinWithdrawalAmount float64 `json:"min_withdrawal_amount"` + MaxWithdrawalAmount float64 `json:"max_withdrawal_amount"` + } `json:"details"` + DefaultNetwork string `json:"default_network"` + SupportedNetworks []struct { + ID string `json:"id"` + Name string `json:"name"` + Status string `json:"status"` + ContactAddress string `json:"contact_address"` + CryptoAddressLink string `json:"crypto_address_link"` + CryptoTransactionLink string `json:"crypto_transaction_link"` + MinWithdrawalAmount float64 `json:"min_withdrawal_amount"` + MaxWithdrawalAmount float64 `json:"max_withdrawal_amount"` + NetworkConfirmations int32 `json:"network_confirmations"` + ProcessingTimeSeconds int32 `json:"processing_time_seconds"` + } } // ServerTime holds current requested server time information @@ -82,34 +116,35 @@ type ServerTime struct { // AccountResponse holds the details for the trading accounts type AccountResponse struct { - ID string `json:"id"` - Currency string `json:"currency"` - Balance float64 `json:"balance,string"` - Available float64 `json:"available,string"` - Hold float64 `json:"hold,string"` - ProfileID string `json:"profile_id"` - MarginEnabled bool `json:"margin_enabled"` - FundedAmount float64 `json:"funded_amount,string"` - DefaultAmount float64 `json:"default_amount,string"` + ID string `json:"id"` + Currency string `json:"currency"` + Balance float64 `json:"balance,string"` + Hold float64 `json:"hold,string"` + Available float64 `json:"available,string"` + ProfileID string `json:"profile_id"` + TradingEnabled bool `json:"trading_enabled"` + PendingDeposit string `json:"pending_deposit"` } // AccountLedgerResponse holds account history information type AccountLedgerResponse struct { - ID string `json:"id"` - CreatedAt time.Time `json:"created_at"` - Amount float64 `json:"amount,string"` - Balance float64 `json:"balance,string"` - Type string `json:"type"` - Details interface{} `json:"details"` + ID string `json:"id"` + Amount float64 `json:"amount,string"` + CreatedAt time.Time `json:"created_at"` + Balance float64 `json:"balance,string"` + Type string `json:"type"` + Details struct { + CoinbaseAccountID string `json:"coinbase_account_id"` + CoinbaseTransactionID string `json:"coinbase_transaction_id"` + CoinbasePaymentMethodID string `json:"coinbase_payment_method_id"` + } `json:"details"` } // AccountHolds contains the hold information about an account type AccountHolds struct { ID string `json:"id"` - AccountID string `json:"account_id"` CreatedAt time.Time `json:"created_at"` - UpdatedAt string `json:"updated_at"` - Amount float64 `json:"amount,string"` + UpdatedAt time.Time `json:"updated_at"` Type string `json:"type"` Reference string `json:"ref"` } @@ -121,21 +156,28 @@ type GeneralizedOrderResponse struct { Price float64 `json:"price,string"` Size float64 `json:"size,string"` ProductID string `json:"product_id"` + ProfileID string `json:"profile_id"` Side string `json:"side"` - Stp string `json:"stp"` + Funds float64 `json:"funds,string"` + SpecifiedFunds float64 `json:"specified_funds,string"` Type string `json:"type"` TimeInForce string `json:"time_in_force"` + ExpireTime time.Time `json:"expire_time"` PostOnly bool `json:"post_only"` CreatedAt time.Time `json:"created_at"` + DoneAt time.Time `json:"done_at"` + DoneReason string `json:"done_reason"` + RejectReason string `json:"reject_reason"` FillFees float64 `json:"fill_fees,string"` FilledSize float64 `json:"filled_size,string"` ExecutedValue float64 `json:"executed_value,string"` Status string `json:"status"` Settled bool `json:"settled"` - Funds float64 `json:"funds,string"` - SpecifiedFunds float64 `json:"specified_funds,string"` - DoneReason string `json:"done_reason"` - DoneAt time.Time `json:"done_at"` + Stop string `json:"stop"` + StopPrice float64 `json:"stop_price,string"` + FundingAmount float64 `json:"funding_amount,string"` + ClientOID string `json:"client_oid"` + MarketType string `json:"market_type"` } // Funding holds funding data @@ -219,22 +261,68 @@ type Account struct { // PaymentMethod holds payment method information type PaymentMethod struct { - ID string `json:"id"` - Type string `json:"type"` - Name string `json:"name"` - Currency string `json:"currency"` - PrimaryBuy bool `json:"primary_buy"` - PrimarySell bool `json:"primary_sell"` - AllowBuy bool `json:"allow_buy"` - AllowSell bool `json:"allow_sell"` - AllowDeposits bool `json:"allow_deposits"` - AllowWithdraw bool `json:"allow_withdraw"` - Limits struct { - Buy []LimitInfo `json:"buy"` - InstantBuy []LimitInfo `json:"instant_buy"` - Sell []LimitInfo `json:"sell"` - Deposit []LimitInfo `json:"deposit"` + ID string `json:"id"` + Type string `json:"type"` + Name string `json:"name"` + Currency string `json:"currency"` + PrimaryBuy bool `json:"primary_buy"` + PrimarySell bool `json:"primary_sell"` + InstantBuy bool `json:"instant_buy"` + InstantSell bool `json:"instant_sell"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + Resource string `json:"resource"` + ResourcePath string `json:"resource_path"` + Verified bool `json:"verified"` + Limits struct { + Type string `json:"type"` + Name string `json:"name"` } `json:"limits"` + AllowBuy bool `json:"allow_buy"` + AllowSell bool `json:"allow_sell"` + AllowDeposit bool `json:"allow_deposit"` + AllowWithdraw bool `json:"allow_withdraw"` + FiatAccount struct { + ID string `json:"id"` + Resource string `json:"resource"` + ResourcePath string `json:"resource_path"` + } `json:"fiat_account"` + CryptoAccount struct { + ID string `json:"id"` + Resource string `json:"resource"` + ResourcePath string `json:"resource_path"` + } `json:"crypto_account"` + AvailableBalance struct { + Amount float64 `json:"amount"` + Currency string `json:"currency"` + Scale string `json:"scale"` + } `json:"available_balance"` + PickerData struct { + Symbol string `json:"symbol"` + CustomerName string `json:"customer_name"` + AccountName string `json:"account_name"` + AccountNumber string `json:"account_number"` + AccountType string `json:"account_type"` + InstitutionCode string `json:"institution_code"` + InstitutionName string `json:"institution_name"` + Iban string `json:"iban"` + Swift string `json:"swift"` + PaypalEmail string `json:"paypal_email"` + PaypalOwner string `json:"paypal_owner"` + RoutingNumber string `json:"routing_number"` + InstitutionIdentifier string `json:"institution_identifier"` + BankName string `json:"bank_name"` + BranchName string `json:"branch_name"` + IconURL string `json:"icon_url"` + Balance struct { + Amount float64 `json:"amount,string"` + Currency string `json:"currency"` + } `json:"balance"` + } `json:"picker_data"` + HoldBusinessDays int64 `json:"hold_business_days"` + HoldDays int64 `json:"hold_days"` + VerificationMethod string `json:"verificationMethod"` + CDVStatus string `json:"cdvStatus"` } // LimitInfo is a sub-type for payment method @@ -252,6 +340,8 @@ type DepositWithdrawalInfo struct { Amount float64 `json:"amount,string"` Currency string `json:"currency"` PayoutAt time.Time `json:"payout_at"` + Fee float64 `json:"fee,string"` + Subtotal float64 `json:"subtotal,string"` } // CoinbaseAccounts holds coinbase account information @@ -263,29 +353,57 @@ type CoinbaseAccounts struct { Type string `json:"type"` Primary bool `json:"primary"` Active bool `json:"active"` + AvailableOnConsumer bool `json:"available_on_consumer"` + Ready bool `json:"ready"` WireDepositInformation struct { AccountNumber string `json:"account_number"` RoutingNumber string `json:"routing_number"` BankName string `json:"bank_name"` BankAddress string `json:"bank_address"` BankCountry struct { - Code string `json:"code"` Name string `json:"name"` + Code string `json:"code"` } `json:"bank_country"` AccountName string `json:"account_name"` AccountAddress string `json:"account_address"` Reference string `json:"reference"` } `json:"wire_deposit_information"` + SwiftDepositInformation struct { + AccountNumber string `json:"account_number"` + BankName string `json:"bank_name"` + BankAddress string `json:"bank_address"` + BankCountry struct { + Name string `json:"name"` + Code string `json:"code"` + } `json:"bank_country"` + AccountName string `json:"account_name"` + AccountAddress string `json:"account_address"` + Reference string `json:"reference"` + } `json:"swift_deposit_information"` SepaDepositInformation struct { - Iban string `json:"iban"` - Swift string `json:"swift"` - BankName string `json:"bank_name"` - BankAddress string `json:"bank_address"` - BankCountryName string `json:"bank_country_name"` - AccountName string `json:"account_name"` - AccountAddress string `json:"account_address"` - Reference string `json:"reference"` - } `json:"sep_deposit_information"` + Iban string `json:"iban"` + Swift string `json:"swift"` + BankName string `json:"bank_name"` + BankAddress string `json:"bank_address"` + BankCountry struct { + Name string `json:"name"` + Code string `json:"code"` + } `json:"bank_country"` + AccountName string `json:"account_name"` + AccountAddress string `json:"account_address"` + Reference string `json:"reference"` + } `json:"sepa_deposit_information"` + UkDepositInformation struct { + SortCode string `json:"sort_code"` + AccountNumber string `json:"account_number"` + BankName string `json:"bank_name"` + AccountName string `json:"account_name"` + Reference string `json:"reference"` + } `json:"uk_deposit_information"` + DestinationTagName string `json:"destination_tag_name"` + DestinationTagRegex string `json:"destination_tag_regex"` + HoldBalance float64 `json:"hold_balance,string"` + HoldCurrency string `json:"hold_currency"` } // Report holds historical information @@ -311,53 +429,123 @@ type Volume struct { RecordedAt string `json:"recorded_at"` } -// OrderL1L2 is a type used in layer conversion -type OrderL1L2 struct { +// // OrderL1L2 is a type used in layer conversion +// type OrderL1L2 struct { +// Price float64 +// Amount float64 +// NumOrders float64 +// } + +// // OrderL3 is a type used in layer conversion +// type OrderL3 struct { +// Price float64 +// Amount float64 +// OrderID string +// } + +// // OrderbookL1L2 holds level 1 and 2 order book information +// type OrderbookL1L2 struct { +// Bids []OrderL1L2 `json:"bids"` +// Asks []OrderL1L2 `json:"asks"` +// Sequence float64 `json:"sequence"` +// AuctionMode bool `json:"auction_mode"` +// Auction struct { +// OpenPrice float64 `json:"open_price,string"` +// OpenSize float64 `json:"open_size,string"` +// BestBidPrice float64 `json:"best_bid_price,string"` +// BestBidSize float64 `json:"best_bid_size,string"` +// BestAskPrice float64 `json:"best_ask_price,string"` +// BestAskSize float64 `json:"best_ask_size,string"` +// AuctionState string `json:"auction_state"` +// CanOpen string `json:"can_open"` +// Time time.Time `json:"time"` +// } +// Time time.Time `json:"time"` +// } + +// // OrderbookL3 holds level 3 order book information +// type OrderbookL3 struct { +// Bids []OrderL3 `json:"bids"` +// Asks []OrderL3 `json:"asks"` +// Sequence float64 `json:"sequence"` +// AuctionMode bool `json:"auction_mode"` +// Auction struct { +// OpenPrice float64 `json:"open_price,string"` +// OpenSize float64 `json:"open_size,string"` +// BestBidPrice float64 `json:"best_bid_price,string"` +// BestBidSize float64 `json:"best_bid_size,string"` +// BestAskPrice float64 `json:"best_ask_price,string"` +// BestAskSize float64 `json:"best_ask_size,string"` +// AuctionState string `json:"auction_state"` +// CanOpen string `json:"can_open"` +// Time time.Time `json:"time"` +// } +// Time time.Time `json:"time"` +// } + +// // OrderbookResponse is a generalized response for order books +type OrderbookIntermediaryResponse struct { + Bids [][3]interface{} `json:"bids"` + Asks [][3]interface{} `json:"asks"` + Sequence float64 `json:"sequence"` + AuctionMode bool `json:"auction_mode"` + Auction struct { + OpenPrice float64 `json:"open_price,string"` + OpenSize float64 `json:"open_size,string"` + BestBidPrice float64 `json:"best_bid_price,string"` + BestBidSize float64 `json:"best_bid_size,string"` + BestAskPrice float64 `json:"best_ask_price,string"` + BestAskSize float64 `json:"best_ask_size,string"` + AuctionState string `json:"auction_state"` + CanOpen string `json:"can_open"` + Time time.Time `json:"time"` + } + Time time.Time `json:"time"` +} + +type GenOrderDetail struct { Price float64 Amount float64 NumOrders float64 -} - -// OrderL3 is a type used in layer conversion -type OrderL3 struct { - Price float64 - Amount float64 - OrderID string -} - -// OrderbookL1L2 holds level 1 and 2 order book information -type OrderbookL1L2 struct { - Sequence int64 `json:"sequence"` - Bids []OrderL1L2 `json:"bids"` - Asks []OrderL1L2 `json:"asks"` -} - -// OrderbookL3 holds level 3 order book information -type OrderbookL3 struct { - Sequence int64 `json:"sequence"` - Bids []OrderL3 `json:"bids"` - Asks []OrderL3 `json:"asks"` -} - -// OrderbookResponse is a generalized response for order books -type OrderbookResponse struct { - Sequence int64 `json:"sequence"` - Bids [][3]interface{} `json:"bids"` - Asks [][3]interface{} `json:"asks"` + OrderID string +} + +type OrderbookFinalResponse struct { + Bids []GenOrderDetail `json:"bids"` + Asks []GenOrderDetail `json:"asks"` + Sequence float64 `json:"sequence"` + AuctionMode bool `json:"auction_mode"` + Auction struct { + OpenPrice float64 `json:"open_price,string"` + OpenSize float64 `json:"open_size,string"` + BestBidPrice float64 `json:"best_bid_price,string"` + BestBidSize float64 `json:"best_bid_size,string"` + BestAskPrice float64 `json:"best_ask_price,string"` + BestAskSize float64 `json:"best_ask_size,string"` + AuctionState string `json:"auction_state"` + CanOpen string `json:"can_open"` + Time time.Time `json:"time"` + } + Time time.Time `json:"time"` } // FillResponse contains fill information from the exchange type FillResponse struct { - TradeID int64 `json:"trade_id"` - ProductID string `json:"product_id"` - Price float64 `json:"price,string"` - Size float64 `json:"size,string"` - OrderID string `json:"order_id"` - CreatedAt time.Time `json:"created_at"` - Liquidity string `json:"liquidity"` - Fee float64 `json:"fee,string"` - Settled bool `json:"settled"` - Side string `json:"side"` + TradeID int32 `json:"trade_id"` + ProductID string `json:"product_id"` + OrderID string `json:"order_id"` + UserID string `json:"user_id"` + ProfileID string `json:"profile_id"` + Liquidity string `json:"liquidity"` + Price float64 `json:"price,string"` + Size float64 `json:"size,string"` + Fee float64 `json:"fee,string"` + CreatedAt time.Time `json:"created_at"` + Side string `json:"side"` + Settled bool `json:"settled"` + USDVolume float64 `json:"usd_volume,string"` + MarketType string `json:"market_type"` + FundingCurrency string `json:"funding_currency"` } // WebsocketSubscribe takes in subscription information @@ -504,15 +692,143 @@ var ( type TransferHistory struct { ID string `json:"id"` Type string `json:"type"` - CreatedAt string `json:"created_at"` - CompletedAt string `json:"completed_at"` + CreatedAt time.Time `json:"created_at"` + CompletedAt time.Time `json:"completed_at"` CanceledAt time.Time `json:"canceled_at"` ProcessedAt time.Time `json:"processed_at"` - UserNonce int64 `json:"user_nonce"` - Amount string `json:"amount"` + Amount float64 `json:"amount,string"` Details struct { CoinbaseAccountID string `json:"coinbase_account_id"` CoinbaseTransactionID string `json:"coinbase_transaction_id"` CoinbasePaymentMethodID string `json:"coinbase_payment_method_id"` } `json:"details"` + UserNonce int64 `json:"user_nonce"` +} + +type TravelRule struct { + ID string `json:"id"` + CreatedAt string `json:"created_at"` + Address string `json:"address"` + OriginName string `json:"originator_name"` + OriginCountry string `json:"originator_country"` +} + +type Params struct { + urlVals url.Values +} + +type GetAddressResponse struct { + ID string `json:"id"` + Address string `json:"address"` + DestinationTag string `json:"destination_tag"` + Currency string `json:"currency"` + Label string `json:"label"` + AddressBookAddedAt time.Time `json:"address_book_added_at"` + LastUsed time.Time `json:"last_used"` + VerifiedSelfHosted bool `json:"is_verified_self_hosted_wallet"` + VASPID string `json:"vasp_id"` +} + +type To struct { + Address string `json:"address"` + DestinationTag string `json:"destination_tag"` +} +type AddAddressRequest struct { + Currency string `json:"currency"` + To `json:"to"` + Label string `json:"label"` + VerifiedSelfHosted bool `json:"is_verified_self_hosted_wallet"` + VaspID string `json:"vasp_id"` + // TODO: It also lets us add an arbitrary amount of strings under this object, + // but doesn't explain what they do. Investigate more later. +} + +type AddAddressResponse struct { + ID string `json:"id"` + Address string `json:"address"` + AddressInfo struct { + Address string `json:"address"` + DisplayAddress string `json:"display_address"` + DestinationTag string `json:"destination_tag"` + } `json:"address_info"` + Currency string `json:"currency"` + Label string `json:"label"` + DisplayAddress string `json:"display_address"` + Trusted bool `json:"trusted"` + AddressBooked bool `json:"address_booked"` + AddressBookAddedAt time.Time `json:"address_book_added_at"` + LastUsed time.Time `json:"last_used"` + AddressBookEntryPendingUntil time.Time `json:"address_book_entry_pending_until"` + VerifiedSelfHosted bool `json:"is_verified_self_hosted_wallet"` + VaspID string `json:"vasp_id"` +} + +type CryptoAddressResponse struct { + ID string `json:"id"` + Address string `json:"address"` + AddressInfo struct { + Address string `json:"address"` + DestinationTag string `json:"destination_tag"` + } `json:"address_info"` + Name string `json:"name"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + Network string `json:"network"` + URIScheme string `json:"uri_scheme"` + Resource string `json:"resource"` + ResourcePath string `json:"resource_path"` + Warnings []struct { + Title string `json:"title"` + Details string `json:"details"` + ImageURL string `json:"image_url"` + } `json:"warnings"` + LegacyAddress string `json:"legacy_address"` + DestinationTag string `json:"destination_tag"` + DepositURI string `json:"deposit_uri"` + CallbackURL string `json:"callback_url"` +} + +type ConvertResponse struct { + ID string `json:"id"` + Amount string `json:"amount"` + FromAccountID string `json:"from_account_id"` + ToAccountID string `json:"to_account_id"` + From string `json:"from"` + To string `json:"to"` +} + +type WithdrawalFeeEstimate struct { + Fee string `json:"fee"` + FeeBeforeSubsidy string `json:"fee_before_subsidy"` +} + +type FeeResponse struct { + TakerFeeRate float64 `json:"taker_fee_rate,string"` + MakerFeeRate float64 `json:"maker_fee_rate,string"` + USDVolume float64 `json:"usd_volume,string"` +} + +type PriceMap map[string]float64 + +func (pm *PriceMap) UnmarshalJSON(data []byte) error { + var m map[string]string + if err := json.Unmarshal(data, &m); err != nil { + return err + } + *pm = make(PriceMap) + for k, v := range m { + f, err := strconv.ParseFloat(v, 64) + if err != nil { + return err + } + (*pm)[k] = f + } + return nil +} + +type SignedPrices struct { + Timestamp string `json:"timestamp"` + Messages []string `json:"messages"` + Signatures []string `json:"signatures"` + Prices PriceMap `json:"prices"` } diff --git a/exchanges/coinbasepro/coinbasepro_wrapper.go b/exchanges/coinbasepro/coinbasepro_wrapper.go index 5725293464d..dc3ebb47aa6 100644 --- a/exchanges/coinbasepro/coinbasepro_wrapper.go +++ b/exchanges/coinbasepro/coinbasepro_wrapper.go @@ -2,6 +2,7 @@ package coinbasepro import ( "context" + "errors" "fmt" "sort" "strconv" @@ -284,24 +285,24 @@ func (c *CoinbasePro) Run(ctx context.Context) { // FetchTradablePairs returns a list of the exchanges tradable pairs func (c *CoinbasePro) FetchTradablePairs(ctx context.Context, _ asset.Item) (currency.Pairs, error) { - products, err := c.GetProducts(ctx) - if err != nil { - return nil, err - } - - pairs := make([]currency.Pair, 0, len(products)) - for x := range products { - if products[x].TradingDisabled { - continue - } - var pair currency.Pair - pair, err = currency.NewPairDelimiter(products[x].ID, currency.DashDelimiter) - if err != nil { - return nil, err - } - pairs = append(pairs, pair) - } - return pairs, nil + // products, err := c.GetProducts(ctx) + // if err != nil { + // return nil, err + // } + + // pairs := make([]currency.Pair, 0, len(products)) + // for x := range products { + // if products[x].TradingDisabled { + // continue + // } + // var pair currency.Pair + // pair, err = currency.NewPairDelimiter(products[x].ID, currency.DashDelimiter) + // if err != nil { + // return nil, err + // } + // pairs = append(pairs, pair) + // } + return nil, errors.New("function not properly implemented") } // UpdateTradablePairs updates the exchanges available pairs and stores @@ -333,12 +334,12 @@ func (c *CoinbasePro) UpdateAccountInfo(ctx context.Context, assetType asset.Ite profileID := accountBalance[i].ProfileID currencies := accountCurrencies[profileID] accountCurrencies[profileID] = append(currencies, account.Balance{ - Currency: currency.NewCode(accountBalance[i].Currency), - Total: accountBalance[i].Balance, - Hold: accountBalance[i].Hold, - Free: accountBalance[i].Available, - AvailableWithoutBorrow: accountBalance[i].Available - accountBalance[i].FundedAmount, - Borrowed: accountBalance[i].FundedAmount, + Currency: currency.NewCode(accountBalance[i].Currency), + Total: accountBalance[i].Balance, + Hold: accountBalance[i].Hold, + Free: accountBalance[i].Available, /* + AvailableWithoutBorrow: accountBalance[i].Available - accountBalance[i].FundedAmount, + Borrowed: accountBalance[i].FundedAmount,*/ }) } @@ -439,46 +440,46 @@ func (c *CoinbasePro) UpdateOrderbook(ctx context.Context, p currency.Pair, asse if err := c.CurrencyPairs.IsAssetEnabled(assetType); err != nil { return nil, err } - book := &orderbook.Base{ - Exchange: c.Name, - Pair: p, - Asset: assetType, - VerifyOrderbook: c.CanVerifyOrderbook, - } - fPair, err := c.FormatExchangeCurrency(p, assetType) - if err != nil { - return book, err - } - - orderbookNew, err := c.GetOrderbook(ctx, fPair.String(), 2) - if err != nil { - return book, err - } - - obNew, ok := orderbookNew.(OrderbookL1L2) - if !ok { - return book, common.GetTypeAssertError("OrderbookL1L2", orderbookNew) - } - - book.Bids = make(orderbook.Items, len(obNew.Bids)) - for x := range obNew.Bids { - book.Bids[x] = orderbook.Item{ - Amount: obNew.Bids[x].Amount, - Price: obNew.Bids[x].Price, - } - } - - book.Asks = make(orderbook.Items, len(obNew.Asks)) - for x := range obNew.Asks { - book.Asks[x] = orderbook.Item{ - Amount: obNew.Asks[x].Amount, - Price: obNew.Asks[x].Price, - } - } - err = book.Process() - if err != nil { - return book, err - } + // book := &orderbook.Base{ + // Exchange: c.Name, + // Pair: p, + // Asset: assetType, + // VerifyOrderbook: c.CanVerifyOrderbook, + // } + // fPair, err := c.FormatExchangeCurrency(p, assetType) + // if err != nil { + // return book, err + // } + + // orderbookNew, err := c.GetOrderbook(ctx, fPair.String(), 2) + // if err != nil { + // return book, err + // } + + // obNew, ok := orderbookNew.(OrderbookL1L2) + // if !ok { + // return book, common.GetTypeAssertError("OrderbookL1L2", orderbookNew) + // } + + // book.Bids = make(orderbook.Items, len(obNew.Bids)) + // for x := range obNew.Bids { + // book.Bids[x] = orderbook.Item{ + // Amount: obNew.Bids[x].Amount, + // Price: obNew.Bids[x].Price, + // } + // } + + // book.Asks = make(orderbook.Items, len(obNew.Asks)) + // for x := range obNew.Asks { + // book.Asks[x] = orderbook.Item{ + // Amount: obNew.Asks[x].Amount, + // Price: obNew.Asks[x].Price, + // } + // } + // err = book.Process() + // if err != nil { + // return book, err + // } return orderbook.Get(c.Name, p, assetType) } @@ -546,42 +547,42 @@ func (c *CoinbasePro) SubmitOrder(ctx context.Context, s *order.Submit) (*order. return nil, err } - fPair, err := c.FormatExchangeCurrency(s.Pair, asset.Spot) - if err != nil { - return nil, err - } + // fPair, err := c.FormatExchangeCurrency(s.Pair, asset.Spot) + // if err != nil { + // return nil, err + // } var orderID string switch s.Type { case order.Market: - orderID, err = c.PlaceMarketOrder(ctx, - "", - s.Amount, - s.QuoteAmount, - s.Side.Lower(), - fPair.String(), - "") + // orderID, err = c.PlaceMarketOrder(ctx, + // "", + // s.Amount, + // s.QuoteAmount, + // s.Side.Lower(), + // fPair.String(), + // "") case order.Limit: - timeInForce := CoinbaseRequestParamsTimeGTC - if s.ImmediateOrCancel { - timeInForce = CoinbaseRequestParamsTimeIOC - } - orderID, err = c.PlaceLimitOrder(ctx, - "", - s.Price, - s.Amount, - s.Side.Lower(), - timeInForce, - "", - fPair.String(), - "", - false) + // timeInForce := CoinbaseRequestParamsTimeGTC + // if s.ImmediateOrCancel { + // timeInForce = CoinbaseRequestParamsTimeIOC + // } + // orderID, err = c.PlaceLimitOrder(ctx, + // "", + // s.Price, + // s.Amount, + // s.Side.Lower(), + // timeInForce, + // "", + // fPair.String(), + // "", + // false) default: - err = fmt.Errorf("%w %v", order.ErrUnsupportedOrderType, s.Type) - } - if err != nil { - return nil, err + // err = fmt.Errorf("%w %v", order.ErrUnsupportedOrderType, s.Type) } + // if err != nil { + // return nil, err + // } return s.DeriveSubmitResponse(orderID) } @@ -596,7 +597,7 @@ func (c *CoinbasePro) CancelOrder(ctx context.Context, o *order.Cancel) error { if err := o.Validate(o.StandardCancel()); err != nil { return err } - return c.CancelExistingOrder(ctx, o.OrderID) + return errors.New("function not properly implemented") } // CancelBatchOrders cancels an orders by their corresponding ID numbers @@ -606,70 +607,71 @@ func (c *CoinbasePro) CancelBatchOrders(_ context.Context, _ []order.Cancel) (*o // CancelAllOrders cancels all orders associated with a currency pair func (c *CoinbasePro) CancelAllOrders(ctx context.Context, _ *order.Cancel) (order.CancelAllResponse, error) { - // CancellAllExisting orders returns a list of successful cancellations, we're only interested in failures - _, err := c.CancelAllExistingOrders(ctx, "") + // // CancellAllExisting orders returns a list of successful cancellations, we're only interested in failures + // _, err := c.CancelAllExistingOrders(ctx, "") + err := errors.New("function not properly implemented") return order.CancelAllResponse{}, err } // GetOrderInfo returns order information based on order ID func (c *CoinbasePro) GetOrderInfo(ctx context.Context, orderID string, _ currency.Pair, _ asset.Item) (*order.Detail, error) { - genOrderDetail, err := c.GetOrder(ctx, orderID) - if err != nil { - return nil, fmt.Errorf("error retrieving order %s : %w", orderID, err) - } - orderStatus, err := order.StringToOrderStatus(genOrderDetail.Status) - if err != nil { - return nil, fmt.Errorf("error parsing order status: %w", err) - } - orderType, err := order.StringToOrderType(genOrderDetail.Type) - if err != nil { - return nil, fmt.Errorf("error parsing order type: %w", err) - } - orderSide, err := order.StringToOrderSide(genOrderDetail.Side) - if err != nil { - return nil, fmt.Errorf("error parsing order side: %w", err) - } - pair, err := currency.NewPairDelimiter(genOrderDetail.ProductID, "-") - if err != nil { - return nil, fmt.Errorf("error parsing order pair: %w", err) - } - - response := order.Detail{ - Exchange: c.GetName(), - OrderID: genOrderDetail.ID, - Pair: pair, - Side: orderSide, - Type: orderType, - Date: genOrderDetail.DoneAt, - Status: orderStatus, - Price: genOrderDetail.Price, - Amount: genOrderDetail.Size, - ExecutedAmount: genOrderDetail.FilledSize, - RemainingAmount: genOrderDetail.Size - genOrderDetail.FilledSize, - Fee: genOrderDetail.FillFees, - } - fillResponse, err := c.GetFills(ctx, orderID, genOrderDetail.ProductID) - if err != nil { - return nil, fmt.Errorf("error retrieving the order fills: %w", err) - } - for i := range fillResponse { - var fillSide order.Side - fillSide, err = order.StringToOrderSide(fillResponse[i].Side) - if err != nil { - return nil, fmt.Errorf("error parsing order Side: %w", err) - } - response.Trades = append(response.Trades, order.TradeHistory{ - Timestamp: fillResponse[i].CreatedAt, - TID: strconv.FormatInt(fillResponse[i].TradeID, 10), - Price: fillResponse[i].Price, - Amount: fillResponse[i].Size, - Exchange: c.GetName(), - Type: orderType, - Side: fillSide, - Fee: fillResponse[i].Fee, - }) - } - return &response, nil + // genOrderDetail, err := c.GetOrder(ctx, orderID) + // if err != nil { + // return nil, fmt.Errorf("error retrieving order %s : %w", orderID, err) + // } + // orderStatus, err := order.StringToOrderStatus(genOrderDetail.Status) + // if err != nil { + // return nil, fmt.Errorf("error parsing order status: %w", err) + // } + // orderType, err := order.StringToOrderType(genOrderDetail.Type) + // if err != nil { + // return nil, fmt.Errorf("error parsing order type: %w", err) + // } + // orderSide, err := order.StringToOrderSide(genOrderDetail.Side) + // if err != nil { + // return nil, fmt.Errorf("error parsing order side: %w", err) + // } + // pair, err := currency.NewPairDelimiter(genOrderDetail.ProductID, "-") + // if err != nil { + // return nil, fmt.Errorf("error parsing order pair: %w", err) + // } + + // response := order.Detail{ + // Exchange: c.GetName(), + // OrderID: genOrderDetail.ID, + // Pair: pair, + // Side: orderSide, + // Type: orderType, + // Date: genOrderDetail.DoneAt, + // Status: orderStatus, + // Price: genOrderDetail.Price, + // Amount: genOrderDetail.Size, + // ExecutedAmount: genOrderDetail.FilledSize, + // RemainingAmount: genOrderDetail.Size - genOrderDetail.FilledSize, + // Fee: genOrderDetail.FillFees, + // } + // fillResponse, err := c.GetFills(ctx, orderID, genOrderDetail.ProductID) + // if err != nil { + // return nil, fmt.Errorf("error retrieving the order fills: %w", err) + // } + // for i := range fillResponse { + // var fillSide order.Side + // fillSide, err = order.StringToOrderSide(fillResponse[i].Side) + // if err != nil { + // return nil, fmt.Errorf("error parsing order Side: %w", err) + // } + // response.Trades = append(response.Trades, order.TradeHistory{ + // Timestamp: fillResponse[i].CreatedAt, + // TID: strconv.FormatInt(fillResponse[i].TradeID, 10), + // Price: fillResponse[i].Price, + // Amount: fillResponse[i].Size, + // Exchange: c.GetName(), + // Type: orderType, + // Side: fillSide, + // Fee: fillResponse[i].Fee, + // }) + // } + return nil, errors.New("function not properly implemented") } // GetDepositAddress returns a deposit address for a specified currency @@ -683,16 +685,17 @@ func (c *CoinbasePro) WithdrawCryptocurrencyFunds(ctx context.Context, withdrawR if err := withdrawRequest.Validate(); err != nil { return nil, err } - resp, err := c.WithdrawCrypto(ctx, - withdrawRequest.Amount, - withdrawRequest.Currency.String(), - withdrawRequest.Crypto.Address) - if err != nil { - return nil, err - } - return &withdraw.ExchangeResponse{ - ID: resp.ID, - }, err + // resp, err := c.WithdrawCrypto(ctx, + // withdrawRequest.Amount, + // withdrawRequest.Currency.String(), + // withdrawRequest.Crypto.Address) + // if err != nil { + // return nil, err + // } + // return &withdraw.ExchangeResponse{ + // ID: resp.ID, + // }, err + return nil, common.ErrFunctionNotSupported } // WithdrawFiatFunds returns a withdrawal ID when a withdrawal is @@ -717,17 +720,17 @@ func (c *CoinbasePro) WithdrawFiatFunds(ctx context.Context, withdrawRequest *wi return nil, fmt.Errorf("could not find payment method '%v'. Check the name via the website and try again", withdrawRequest.Fiat.Bank.BankName) } - resp, err := c.WithdrawViaPaymentMethod(ctx, - withdrawRequest.Amount, - withdrawRequest.Currency.String(), - selectedWithdrawalMethod.ID) - if err != nil { - return nil, err - } + // resp, err := c.WithdrawViaPaymentMethod(ctx, + // withdrawRequest.Amount, + // withdrawRequest.Currency.String(), + // selectedWithdrawalMethod.ID) + // if err != nil { + return nil, err + // } - return &withdraw.ExchangeResponse{ - Status: resp.ID, - }, nil + // return &withdraw.ExchangeResponse{ + // Status: resp.ID, + // }, nil } // WithdrawFiatFundsToInternationalBank returns a withdrawal ID when a @@ -765,22 +768,22 @@ func (c *CoinbasePro) GetActiveOrders(ctx context.Context, req *order.MultiOrder return nil, err } var respOrders []GeneralizedOrderResponse - var fPair currency.Pair - for i := range req.Pairs { - fPair, err = c.FormatExchangeCurrency(req.Pairs[i], asset.Spot) - if err != nil { - return nil, err - } - - var resp []GeneralizedOrderResponse - resp, err = c.GetOrders(ctx, - []string{"open", "pending", "active"}, - fPair.String()) - if err != nil { - return nil, err - } - respOrders = append(respOrders, resp...) - } + // var fPair currency.Pair + // for i := range req.Pairs { + // // fPair, err = c.FormatExchangeCurrency(req.Pairs[i], asset.Spot) + // if err != nil { + // return nil, err + // } + + // var resp []GeneralizedOrderResponse + // // resp, err = c.GetOrders(ctx, + // // []string{"open", "pending", "active"}, + // // fPair.String()) + // if err != nil { + // return nil, err + // } + // respOrders = append(respOrders, resp...) + // } format, err := c.GetPairFormat(asset.Spot, false) if err != nil { @@ -827,26 +830,26 @@ func (c *CoinbasePro) GetOrderHistory(ctx context.Context, req *order.MultiOrder return nil, err } var respOrders []GeneralizedOrderResponse - if len(req.Pairs) > 0 { - var fPair currency.Pair - var resp []GeneralizedOrderResponse - for i := range req.Pairs { - fPair, err = c.FormatExchangeCurrency(req.Pairs[i], asset.Spot) - if err != nil { - return nil, err - } - resp, err = c.GetOrders(ctx, []string{"done"}, fPair.String()) - if err != nil { - return nil, err - } - respOrders = append(respOrders, resp...) - } - } else { - respOrders, err = c.GetOrders(ctx, []string{"done"}, "") - if err != nil { - return nil, err - } - } + // if len(req.Pairs) > 0 { + // var fPair currency.Pair + // var resp []GeneralizedOrderResponse + // for i := range req.Pairs { + // fPair, err = c.FormatExchangeCurrency(req.Pairs[i], asset.Spot) + // if err != nil { + // return nil, err + // } + // resp, err = c.GetOrders(ctx, []string{"done"}, fPair.String()) + // if err != nil { + // return nil, err + // } + // respOrders = append(respOrders, resp...) + // } + // } else { + // respOrders, err = c.GetOrders(ctx, []string{"done"}, "") + // if err != nil { + // return nil, err + // } + // } format, err := c.GetPairFormat(asset.Spot, false) if err != nil { From bd9f2ddcfa3cd33fa5c7b3673368f22792f87d0d Mon Sep 17 00:00:00 2001 From: Samuel Reid <43227667+cranktakular@users.noreply.github.com> Date: Mon, 9 Oct 2023 17:05:11 +1100 Subject: [PATCH 02/79] Coinbase revamp; CreateReport is still WIP --- exchanges/coinbasepro/coinbasepro.go | 592 +++++++++++-------- exchanges/coinbasepro/coinbasepro_test.go | 248 ++++++-- exchanges/coinbasepro/coinbasepro_types.go | 128 ++-- exchanges/coinbasepro/coinbasepro_wrapper.go | 185 +++--- 4 files changed, 720 insertions(+), 433 deletions(-) diff --git a/exchanges/coinbasepro/coinbasepro.go b/exchanges/coinbasepro/coinbasepro.go index f0793ae0dcf..18a9911ead8 100644 --- a/exchanges/coinbasepro/coinbasepro.go +++ b/exchanges/coinbasepro/coinbasepro.go @@ -6,6 +6,7 @@ import ( "encoding/json" "errors" "fmt" + "log" "net/http" "net/url" "strconv" @@ -40,14 +41,17 @@ const ( coinbaseproFills = "fills" coinbaseproOrders = "orders" coinbaseproOracle = "oracle" + coinbaseproProducts = "products" + coinbaseproOrderbook = "book" + coinbaseproHistory = "candles" + coinbaseproStats = "stats" + coinbaseproTicker = "ticker" + coinbaseproTrades = "trades" + coinbaseproProfiles = "profiles" + coinbaseproTransfer = "transfer" + coinbaseproDeactivate = "deactivate" + coinbaseproReports = "reports" - coinbaseproProducts = "products" - coinbaseproOrderbook = "book" - coinbaseproTicker = "ticker" - coinbaseproTrades = "trades" - coinbaseproHistory = "candles" - coinbaseproStats = "stats" - coinbaseproReports = "reports" coinbaseproTime = "time" coinbaseproTravelRules = "travel-rules" coinbaseproMarginTransfer = "profiles/margin-transfer" @@ -81,11 +85,11 @@ func (c *CoinbasePro) GetAccounts(ctx context.Context) ([]AccountResponse, error // GetAccount returns information for a single account. Use this endpoint when // account_id is known -func (c *CoinbasePro) GetAccount(ctx context.Context, accountID string) (AccountResponse, error) { - resp := AccountResponse{} +func (c *CoinbasePro) GetAccount(ctx context.Context, accountID string) (*AccountResponse, error) { path := fmt.Sprintf("%s/%s", coinbaseproAccounts, accountID) + resp := AccountResponse{} - return resp, + return &resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp) } @@ -94,15 +98,16 @@ func (c *CoinbasePro) GetAccount(ctx context.Context, accountID string) (Account // is updated. If an order is canceled, any remaining hold is removed. For a // withdraw, once it is completed, the hold is removed. func (c *CoinbasePro) GetHolds(ctx context.Context, accountID, direction, step string, limit int64) ([]AccountHolds, error) { - var resp []AccountHolds - var params Params - params.urlVals = url.Values{} path := fmt.Sprintf("%s/%s/%s", coinbaseproAccounts, accountID, coinbaseproHolds) + var params Params + params.urlVals = url.Values{} params.PrepareDSL(direction, step, limit) path = common.EncodeURLValues(path, params.urlVals) + var resp []AccountHolds + return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp) } @@ -111,13 +116,12 @@ func (c *CoinbasePro) GetHolds(ctx context.Context, accountID, direction, step s // or decreases your account balance. Items are paginated and sorted latest // first. func (c *CoinbasePro) GetAccountLedger(ctx context.Context, accountID, direction, step, pID string, startDate, endDate time.Time, limit int64) ([]AccountLedgerResponse, error) { - var resp []AccountLedgerResponse var params Params params.urlVals = url.Values{} err := params.PrepareDateString(startDate, endDate) if err != nil { - return resp, err + return nil, err } path := fmt.Sprintf("%s/%s/%s", coinbaseproAccounts, accountID, coinbaseproLedger) @@ -130,22 +134,26 @@ func (c *CoinbasePro) GetAccountLedger(ctx context.Context, accountID, direction path = common.EncodeURLValues(path, params.urlVals) + var resp []AccountLedgerResponse + return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp) } // GetAccountTransfers returns a history of withdrawal and or deposit // transactions for a single account func (c *CoinbasePro) GetAccountTransfers(ctx context.Context, accountID, direction, step, transferType string, limit int64) ([]TransferHistory, error) { - var resp []TransferHistory + path := fmt.Sprintf("%s/%s/%s", coinbaseproAccounts, accountID, coinbaseproTransfers) + var params Params params.urlVals = url.Values{} - path := fmt.Sprintf("%s/%s/%s", coinbaseproAccounts, accountID, coinbaseproTransfers) params.PrepareDSL(direction, step, limit) params.urlVals.Set("type", transferType) path = common.EncodeURLValues(path, params.urlVals) + var resp []TransferHistory + return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp) } @@ -160,11 +168,11 @@ func (c *CoinbasePro) GetAddressBook(ctx context.Context) ([]GetAddressResponse, // AddAddresses adds new addresses to the address book func (c *CoinbasePro) AddAddresses(ctx context.Context, req []AddAddressRequest) ([]AddAddressResponse, error) { - var resp []AddAddressResponse - params := make(map[string]interface{}) params["addresses"] = req + var resp []AddAddressResponse + return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproAddressBook, params, &resp) } @@ -185,8 +193,7 @@ func (c *CoinbasePro) GetCoinbaseWallets(ctx context.Context) ([]CoinbaseAccount } // GenerateCryptoAddress generates a one-time address for deposting crypto -func (c *CoinbasePro) GenerateCryptoAddress(ctx context.Context, accountID, profileID, network string) (CryptoAddressResponse, error) { - resp := CryptoAddressResponse{} +func (c *CoinbasePro) GenerateCryptoAddress(ctx context.Context, accountID, profileID, network string) (*CryptoAddressResponse, error) { path := fmt.Sprintf("%s/%s/%s", coinbaseproCoinbaseAccounts, accountID, coinbaseproAddress) // In this case, accountID has to come from GetCoinbaseWallets, not GetAccounts @@ -194,30 +201,33 @@ func (c *CoinbasePro) GenerateCryptoAddress(ctx context.Context, accountID, prof params := map[string]interface{}{"account_id": accountID, "profile_id": profileID, "network": network} - return resp, + resp := CryptoAddressResponse{} + + return &resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, path, params, &resp) } // ConvertCurrency converts between two currencies in the specified profile func (c *CoinbasePro) ConvertCurrency(ctx context.Context, profileID, from, to, nonce string, amount float64) (ConvertResponse, error) { - resp := ConvertResponse{} - params := map[string]interface{}{"profile_id": profileID, "from": from, "to": to, "amount": strconv.FormatFloat(amount, 'f', -1, 64), "nonce": nonce} + resp := ConvertResponse{} + return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproConversions, params, &resp) } // GetConversionByID identifies the details of a past conversion, given its ID func (c *CoinbasePro) GetConversionByID(ctx context.Context, conversionID, profileID string) (ConvertResponse, error) { - resp := ConvertResponse{} path := fmt.Sprintf("%s/%s", coinbaseproConversions, conversionID) var params Params params.urlVals = url.Values{} params.urlVals.Set("profile_id", profileID) path = common.EncodeURLValues(path, params.urlVals) + resp := ConvertResponse{} + return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp) } @@ -233,31 +243,34 @@ func (c *CoinbasePro) GetCurrencies(ctx context.Context) ([]Currency, error) { // GetCurrenciesByID returns into on a single currency given its ID in ISO 4217, or // in a custom code for currencies which lack an ISO 4217 code -func (c *CoinbasePro) GetCurrenciesByID(ctx context.Context, currencyID string) (Currency, error) { - resp := Currency{} +func (c *CoinbasePro) GetCurrenciesByID(ctx context.Context, currencyID string) (*Currency, error) { path := fmt.Sprintf("%s/%s", coinbaseproCurrencies, currencyID) - return resp, c.SendHTTPRequest(ctx, exchange.RestSpot, path, &resp) + resp := Currency{} + + return &resp, c.SendHTTPRequest(ctx, exchange.RestSpot, path, &resp) } // DepositViaCoinbase deposits funds from a Coinbase account func (c *CoinbasePro) DepositViaCoinbase(ctx context.Context, profileID, currency, coinbaseAccountID string, amount float64) (DepositWithdrawalInfo, error) { - resp := DepositWithdrawalInfo{} params := map[string]interface{}{"profile_id": profileID, "amount": strconv.FormatFloat(amount, 'f', -1, 64), "coinbase_account_id": coinbaseAccountID, "currency": currency} + resp := DepositWithdrawalInfo{} + return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproDepositCoinbase, params, &resp) } // DepositViaPaymentMethod deposits funds from a payment method. SEPA is not allowed func (c *CoinbasePro) DepositViaPaymentMethod(ctx context.Context, profileID, paymentID, currency string, amount float64) (DepositWithdrawalInfo, error) { - resp := DepositWithdrawalInfo{} params := map[string]interface{}{"profile_id": profileID, "amount": strconv.FormatFloat(amount, 'f', -1, 64), "payment_method_id": paymentID, "currency": currency} + resp := DepositWithdrawalInfo{} + return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproPaymentMethodDeposit, params, &resp) } @@ -273,7 +286,6 @@ func (c *CoinbasePro) GetPayMethods(ctx context.Context) ([]PaymentMethod, error // GetAllTransfers returns all in-progress and completed transfers in and out of any // of the user's accounts func (c *CoinbasePro) GetAllTransfers(ctx context.Context, profileID, direction, step, transferType string, limit int64) ([]TransferHistory, error) { - resp := []TransferHistory{} var params Params params.urlVals = url.Values{} params.urlVals.Set("profile_id", profileID) @@ -281,45 +293,48 @@ func (c *CoinbasePro) GetAllTransfers(ctx context.Context, profileID, direction, params.urlVals.Set("type", transferType) path := common.EncodeURLValues(coinbaseproTransfers, params.urlVals) + resp := []TransferHistory{} + return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp) } // GetTransferByID returns information on a single transfer when provided with its ID -func (c *CoinbasePro) GetTransferByID(ctx context.Context, transferID string) (TransferHistory, error) { - resp := TransferHistory{} +func (c *CoinbasePro) GetTransferByID(ctx context.Context, transferID string) (*TransferHistory, error) { path := fmt.Sprintf("%s/%s", coinbaseproTransfers, transferID) + resp := TransferHistory{} - return resp, + return &resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp) } // SendTravelInfoForTransfer sends travel rule information for a transfer func (c *CoinbasePro) SendTravelInfoForTransfer(ctx context.Context, transferID, originName, originCountry string) (string, error) { - var resp string path := fmt.Sprintf("%s/%s/%s", coinbaseproTransfers, transferID, coinbaseproTravelRules) params := map[string]interface{}{"transfer_id": transferID, "originator_name": originName, "originator_country": originCountry} + var resp string + return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, path, params, &resp) } // WithdrawViaCoinbase withdraws funds to a coinbase account. func (c *CoinbasePro) WithdrawViaCoinbase(ctx context.Context, profileID, accountID, currency string, amount float64) (DepositWithdrawalInfo, error) { - resp := DepositWithdrawalInfo{} req := map[string]interface{}{"profile_id": profileID, "amount": strconv.FormatFloat(amount, 'f', -1, 64), "coinbase_account_id": accountID, "currency": currency} + resp := DepositWithdrawalInfo{} + return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproWithdrawalCoinbase, req, &resp) } // WithdrawCrypto withdraws funds to a crypto address func (c *CoinbasePro) WithdrawCrypto(ctx context.Context, profileID, currency, cryptoAddress, destinationTag, twoFactorCode, network string, amount float64, noDestinationTag, addNetworkFee bool, nonce int32) (DepositWithdrawalInfo, error) { - resp := DepositWithdrawalInfo{} req := map[string]interface{}{"profile_id": profileID, "amount": strconv.FormatFloat(amount, 'f', -1, 64), "currency": currency, "crypto_address": cryptoAddress, @@ -327,6 +342,8 @@ func (c *CoinbasePro) WithdrawCrypto(ctx context.Context, profileID, currency, c "two_factor_code": twoFactorCode, "nonce": nonce, "network": network, "add_network_fee": addNetworkFee} + resp := DepositWithdrawalInfo{} + return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproWithdrawalCrypto, req, &resp) } @@ -354,11 +371,12 @@ func (c *CoinbasePro) GetWithdrawalFeeEstimate(ctx context.Context, currency, cr // WithdrawViaPaymentMethod withdraws funds to a payment method func (c *CoinbasePro) WithdrawViaPaymentMethod(ctx context.Context, profileID, paymentID, currency string, amount float64) (DepositWithdrawalInfo, error) { - resp := DepositWithdrawalInfo{} req := map[string]interface{}{"profile_id": profileID, "amount": strconv.FormatFloat(amount, 'f', -1, 64), "payment_method_id": paymentID, "currency": currency} + resp := DepositWithdrawalInfo{} + return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproWithdrawalPaymentMethod, req, &resp) } @@ -374,15 +392,14 @@ func (c *CoinbasePro) GetFees(ctx context.Context) (FeeResponse, error) { // GetFills returns a list of recent fills on this profile func (c *CoinbasePro) GetFills(ctx context.Context, orderID, currencyPair, direction, step, marketType string, limit int64, startDate, endDate time.Time) ([]FillResponse, error) { - var resp []FillResponse if orderID == "" && currencyPair == "" { - return resp, errors.New("requires either order id or product id") + return nil, errors.New("requires either order id or product id") } var params Params params.urlVals = url.Values{} err := params.PrepareDateString(startDate, endDate) if err != nil { - return resp, err + return nil, err } if orderID != "" { @@ -397,20 +414,21 @@ func (c *CoinbasePro) GetFills(ctx context.Context, orderID, currencyPair, direc path := common.EncodeURLValues(coinbaseproFills, params.urlVals) + var resp []FillResponse + return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp) } // GetOrders lists all open or unsettled orders func (c *CoinbasePro) GetOrders(ctx context.Context, profileID, currencyPair, sortedBy, sorting, direction, step, marketType string, startDate, endDate time.Time, limit int64, status []string) ([]GeneralizedOrderResponse, error) { - var resp []GeneralizedOrderResponse if limit < 1 { - return resp, errors.New("limit must be greater than 0") + return nil, errors.New("limit must be greater than 0") } var params Params params.urlVals = make(url.Values) err := params.PrepareDateString(startDate, endDate) if err != nil { - return resp, err + return nil, err } params.PrepareProfIDAndProdID(profileID, currencyPair) @@ -426,32 +444,36 @@ func (c *CoinbasePro) GetOrders(ctx context.Context, profileID, currencyPair, so path := common.EncodeURLValues(coinbaseproOrders, params.urlVals) + var resp []GeneralizedOrderResponse + return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp) } // CancelAllExistingOrders attempts to cancel all open orders. The exchange warns that // this may need to be called multiple times to properly close all of them func (c *CoinbasePro) CancelAllExistingOrders(ctx context.Context, profileID, currencyPair string) ([]string, error) { - var resp []string var params Params params.urlVals = url.Values{} params.PrepareProfIDAndProdID(profileID, currencyPair) path := common.EncodeURLValues(coinbaseproOrders, params.urlVals) + + var resp []string + return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodDelete, path, nil, &resp) } // PlaceOrder places either a limit, market, or stop order -func (c *CoinbasePro) PlaceOrder(ctx context.Context, profileID, orderType, side, currencyPair, stp, stop, timeInForce, cancelAfter, clientOID string, stopPrice, price, size, funds float64, postOnly bool) (GeneralizedOrderResponse, error) { +func (c *CoinbasePro) PlaceOrder(ctx context.Context, profileID, orderType, side, currencyPair, stp, stop, timeInForce, cancelAfter, clientOID string, stopPrice, price, size, funds float64, postOnly bool) (*GeneralizedOrderResponse, error) { var resp GeneralizedOrderResponse if (orderType == order.Market.Lower() || orderType == "" || orderType == "stop") && (price == 0 || size == 0) { - return resp, errors.New("price and size must be greater than 0 for limit or stop orders") + return &resp, errors.New("price and size must be greater than 0 for limit or stop orders") } if orderType == order.Market.Lower() && (size == 0 && funds == 0) { - return resp, errors.New("size or funds must be greater than 0 for market orders") + return &resp, errors.New("size or funds must be greater than 0 for market orders") } req := map[string]interface{}{"profile_id": profileID, "type": orderType, "side": side, @@ -463,28 +485,28 @@ func (c *CoinbasePro) PlaceOrder(ctx context.Context, profileID, orderType, side "time_in_force": timeInForce, "cancel_after": cancelAfter, "post_only": postOnly, "client_oid": clientOID} - return resp, + return &resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproOrders, req, &resp) } // GetOrder returns a single order by order id. -func (c *CoinbasePro) GetOrder(ctx context.Context, orderID, marketType string, clientID bool) (GeneralizedOrderResponse, error) { +func (c *CoinbasePro) GetOrder(ctx context.Context, orderID, marketType string, clientID bool) (*GeneralizedOrderResponse, error) { resp := GeneralizedOrderResponse{} if orderID == "" { - return resp, errors.New("order id cannot be empty") + return &resp, errors.New("order id cannot be empty") } if clientID { orderID = fmt.Sprintf("client:%s", orderID) } - path := fmt.Sprintf("%s/%s", coinbaseproOrders, orderID) var param Params param.urlVals = url.Values{} param.urlVals.Set("market_type", marketType) + path := fmt.Sprintf("%s/%s", coinbaseproOrders, orderID) path = common.EncodeURLValues(path, param.urlVals) - return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp) + return &resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp) } // CancelExistingOrder cancels order by orderID @@ -519,36 +541,36 @@ func (c *CoinbasePro) GetSignedPrices(ctx context.Context) (SignedPrices, error) // GetProducts returns information on all currency pairs that are available for trading func (c *CoinbasePro) GetProducts(ctx context.Context, productType string) ([]Product, error) { - var products []Product - var params Params params.urlVals = url.Values{} params.urlVals.Set("type", productType) path := common.EncodeURLValues(coinbaseproProducts, params.urlVals) + var products []Product + return products, c.SendHTTPRequest(ctx, exchange.RestSpot, path, &products) } // GetProduct returns information on a single specified currency pair -func (c *CoinbasePro) GetProduct(ctx context.Context, productID string) (Product, error) { - resp := Product{} +func (c *CoinbasePro) GetProduct(ctx context.Context, productID string) (*Product, error) { + if productID == "" { + return nil, errors.New("product id cannot be empty") + } + path := fmt.Sprintf("%s/%s", coinbaseproProducts, productID) - return resp, c.SendHTTPRequest(ctx, exchange.RestSpot, path, &resp) -} + resp := Product{} -func (c *CoinbasePro) OrderbookHelper(obI OrderbookIntermediaryResponse, level int32) (*OrderbookFinalResponse, error) { - // obF := OrderbookFinalResponse{ - // Bids: make([]GenOrderDetail, len(obI.Bids)), - // Asks: make([]GenOrderDetail, len(obI.Asks)), - // Sequence: obI.Sequence, - // } - return nil, nil + return &resp, c.SendHTTPRequest(ctx, exchange.RestSpot, path, &resp) } // GetOrderbook returns orderbook by currency pair and level func (c *CoinbasePro) GetOrderbook(ctx context.Context, symbol string, level int32) (*OrderbookFinalResponse, error) { + if symbol == "" { + return nil, errors.New("symbol cannot be empty") + } + path := fmt.Sprintf("%s/%s/%s", coinbaseproProducts, symbol, coinbaseproOrderbook) if level > 0 { var params Params @@ -564,169 +586,60 @@ func (c *CoinbasePro) GetOrderbook(ctx context.Context, symbol string, level int return nil, err } - resp, err := c.OrderbookHelper(data, level) + obF := OrderbookFinalResponse{ + Sequence: data.Sequence, + AuctionMode: data.AuctionMode, + Auction: data.Auction, + Time: data.Time, + } + + obF.Bids, err = OrderbookHelper(data.Bids, level) + if err != nil { + return nil, err + } + obF.Asks, err = OrderbookHelper(data.Asks, level) if err != nil { return nil, err } - // if level == 3 { - // ob := OrderbookL3{ - // Sequence: orderbook.Sequence, - // Bids: make([]OrderL3, len(orderbook.Bids)), - // Asks: make([]OrderL3, len(orderbook.Asks)), - // } - // ob.Sequence = orderbook.Sequence - // for x := range orderbook.Asks { - // priceConv, ok := orderbook.Asks[x][0].(string) - // if !ok { - // return nil, errors.New("unable to type assert price") - // } - // price, err := strconv.ParseFloat(priceConv, 64) - // if err != nil { - // return nil, err - // } - // amountConv, ok := orderbook.Asks[x][1].(string) - // if !ok { - // return nil, errors.New("unable to type assert amount") - // } - // amount, err := strconv.ParseFloat(amountConv, 64) - // if err != nil { - // return nil, err - // } - // ordID, ok := orderbook.Asks[x][2].(string) - // if !ok { - // return nil, errors.New("unable to type assert order ID") - // } - // ob.Asks[x] = OrderL3{Price: price, Amount: amount, OrderID: ordID} - // } - // for x := range orderbook.Bids { - // priceConv, ok := orderbook.Bids[x][0].(string) - // if !ok { - // return nil, errors.New("unable to type assert price") - // } - // price, err := strconv.ParseFloat(priceConv, 64) - // if err != nil { - // return nil, err - // } - // amountConv, ok := orderbook.Bids[x][1].(string) - // if !ok { - // return nil, errors.New("unable to type assert amount") - // } - // amount, err := strconv.ParseFloat(amountConv, 64) - // if err != nil { - // return nil, err - // } - // ordID, ok := orderbook.Bids[x][2].(string) - // if !ok { - // return nil, errors.New("unable to type assert order ID") - // } - // ob.Bids[x] = OrderL3{Price: price, Amount: amount, OrderID: ordID} - // } - // return ob, nil - // } - // ob := OrderbookL1L2{ - // Sequence: orderbook.Sequence, - // Bids: make([]OrderL1L2, len(orderbook.Bids)), - // Asks: make([]OrderL1L2, len(orderbook.Asks)), - // } - // for x := range orderbook.Asks { - // priceConv, ok := orderbook.Asks[x][0].(string) - // if !ok { - // return nil, errors.New("unable to type assert price") - // } - // price, err := strconv.ParseFloat(priceConv, 64) - // if err != nil { - // return nil, err - // } - // amountConv, ok := orderbook.Asks[x][1].(string) - // if !ok { - // return nil, errors.New("unable to type assert amount") - // } - // amount, err := strconv.ParseFloat(amountConv, 64) - // if err != nil { - // return nil, err - // } - // numOrders, ok := orderbook.Asks[x][2].(float64) - // if !ok { - // return nil, errors.New("unable to type assert number of orders") - // } - // ob.Asks[x] = OrderL1L2{Price: price, Amount: amount, NumOrders: numOrders} - // } - // for x := range orderbook.Bids { - // priceConv, ok := orderbook.Bids[x][0].(string) - // if !ok { - // return nil, errors.New("unable to type assert price") - // } - // price, err := strconv.ParseFloat(priceConv, 64) - // if err != nil { - // return nil, err - // } - // amountConv, ok := orderbook.Bids[x][1].(string) - // if !ok { - // return nil, errors.New("unable to type assert amount") - // } - // amount, err := strconv.ParseFloat(amountConv, 64) - // if err != nil { - // return nil, err - // } - // numOrders, ok := orderbook.Bids[x][2].(float64) - // if !ok { - // return nil, errors.New("unable to type assert number of orders") - // } - // ob.Bids[x] = OrderL1L2{Price: price, Amount: amount, NumOrders: numOrders} - // } - // return ob, nil - return resp, nil -} - -// GetTicker returns ticker by currency pair -// currencyPair - example "BTC-USD" -func (c *CoinbasePro) GetTicker(ctx context.Context, currencyPair string) (Ticker, error) { - tick := Ticker{} - path := fmt.Sprintf( - "%s/%s/%s", coinbaseproProducts, currencyPair, coinbaseproTicker) - return tick, c.SendHTTPRequest(ctx, exchange.RestSpot, path, &tick) -} - -// GetTrades listd the latest trades for a product -// currencyPair - example "BTC-USD" -func (c *CoinbasePro) GetTrades(ctx context.Context, currencyPair string) ([]Trade, error) { - var trades []Trade - path := fmt.Sprintf( - "%s/%s/%s", coinbaseproProducts, currencyPair, coinbaseproTrades) - return trades, c.SendHTTPRequest(ctx, exchange.RestSpot, path, &trades) + return &obF, nil } // GetHistoricRates returns historic rates for a product. Rates are returned in -// grouped buckets based on requested granularity. -func (c *CoinbasePro) GetHistoricRates(ctx context.Context, currencyPair, start, end string, granularity int64) ([]History, error) { - values := url.Values{} - - if len(start) > 0 { - values.Set("start", start) - } else { - values.Set("start", "") +// grouped buckets based on requested granularity. Contrary to the documentation, +// requests that return more than 300 data points aren't rejected; just truncated +// to the 300 most recent data points. +func (c *CoinbasePro) GetHistoricRates(ctx context.Context, currencyPair string, granularity int64, startDate, endDate time.Time) ([]History, error) { + if currencyPair == "" { + return nil, errors.New("currency pair cannot be empty") } - if len(end) > 0 { - values.Set("end", end) - } else { - values.Set("end", "") + var params Params + params.urlVals = url.Values{} + + err := params.PrepareDateString(startDate, endDate) + if err != nil { + return nil, err } - allowedGranularities := [6]int64{60, 300, 900, 3600, 21600, 86400} + log.Printf("The date params are %v and %v", params.urlVals.Get("start_date"), + params.urlVals.Get("end_date")) + + allowedGranularities := [7]int64{0, 60, 300, 900, 3600, 21600, 86400} validGran, _ := common.InArray(granularity, allowedGranularities) if !validGran { - return nil, errors.New("Invalid granularity value: " + strconv.FormatInt(granularity, 10) + ". Allowed values are {60, 300, 900, 3600, 21600, 86400}") + return nil, fmt.Errorf("invalid granularity %v, allowed granularities are: %+v", + granularity, allowedGranularities) } if granularity > 0 { - values.Set("granularity", strconv.FormatInt(granularity, 10)) + params.urlVals.Set("granularity", strconv.FormatInt(granularity, 10)) } var resp [][6]float64 + path := common.EncodeURLValues( fmt.Sprintf("%s/%s/%s", coinbaseproProducts, currencyPair, coinbaseproHistory), - values) + params.urlVals) if err := c.SendHTTPRequest(ctx, exchange.RestSpot, path, &resp); err != nil { return nil, err } @@ -746,16 +659,191 @@ func (c *CoinbasePro) GetHistoricRates(ctx context.Context, currencyPair, start, return history, nil } -// GetStats returns a 24 hr stat for the product. Volume is in base currency +// GetStats returns 30 day and 24 hour stats for the product. Volume is in base currency // units. open, high, low are in quote currency units. func (c *CoinbasePro) GetStats(ctx context.Context, currencyPair string) (Stats, error) { stats := Stats{} + if currencyPair == "" { + return stats, errors.New("currency pair cannot be empty") + } + path := fmt.Sprintf( "%s/%s/%s", coinbaseproProducts, currencyPair, coinbaseproStats) return stats, c.SendHTTPRequest(ctx, exchange.RestSpot, path, &stats) } +// GetTicker returns snapshot information about the last trade (tick), best bid/ask and +// 24h volume. +// currencyPair - example "BTC-USD" +func (c *CoinbasePro) GetTicker(ctx context.Context, currencyPair string) (*Ticker, error) { + if currencyPair == "" { + return nil, errors.New("currency pair cannot be empty") + } + path := fmt.Sprintf( + "%s/%s/%s", coinbaseproProducts, currencyPair, coinbaseproTicker) + tick := Ticker{} + + return &tick, c.SendHTTPRequest(ctx, exchange.RestSpot, path, &tick) +} + +// GetTrades lists the latest trades for a product +// currencyPair - example "BTC-USD" +func (c *CoinbasePro) GetTrades(ctx context.Context, currencyPair, direction, step string, limit int64) ([]Trade, error) { + if currencyPair == "" { + return nil, errors.New("currency pair cannot be empty") + } + + path := fmt.Sprintf( + "%s/%s/%s", coinbaseproProducts, currencyPair, coinbaseproTrades) + + var params Params + params.urlVals = url.Values{} + params.PrepareDSL(direction, step, limit) + + path = common.EncodeURLValues(path, params.urlVals) + + var trades []Trade + + return trades, c.SendHTTPRequest(ctx, exchange.RestSpot, path, &trades) +} + +// GetProfiles returna a list of all of the current user's profiles +func (c *CoinbasePro) GetProfiles(ctx context.Context, active *bool) ([]Profile, error) { + var params Params + params.urlVals = url.Values{} + + if active != nil { + params.urlVals.Set("active", strconv.FormatBool(*active)) + } + + var profiles []Profile + + path := common.EncodeURLValues(coinbaseproProfiles, params.urlVals) + + return profiles, + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &profiles) +} + +// CreateAProfile creates a new profile, failing if no name is provided, +// or if the user already has the max number of profiles +func (c *CoinbasePro) CreateAProfile(ctx context.Context, name string) (Profile, error) { + var resp Profile + if name == "" { + return resp, errors.New("name cannot be empty") + } + + req := map[string]interface{}{"name": name} + + return resp, + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproProfiles, req, &resp) +} + +// TransferBetweenProfiles transfers an amount of currency from one profile to another +func (c *CoinbasePro) TransferBetweenProfiles(ctx context.Context, from, to, currency string, amount float64) (string, error) { + var resp string + if from == "" || to == "" || currency == "" { + return resp, errors.New("from, to, and currency must all not be empty") + } + + req := map[string]interface{}{"from": from, "to": to, "currency": currency, + "amount": strconv.FormatFloat(amount, 'f', -1, 64)} + + path := fmt.Sprintf("%s/%s", coinbaseproProfiles, coinbaseproTransfer) + + return resp, + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, path, req, &resp) +} + +// GetProfileByID returns a single profile, provided its ID +func (c *CoinbasePro) GetProfileByID(ctx context.Context, profileID string, active *bool) (Profile, error) { + var params Params + params.urlVals = url.Values{} + if active != nil { + params.urlVals.Set("active", strconv.FormatBool(*active)) + } + + var resp Profile + path := fmt.Sprintf("%s/%s", coinbaseproProfiles, profileID) + path = common.EncodeURLValues(path, params.urlVals) + + return resp, + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp) +} + +// RenameProfile renames a profile, provided its ID +func (c *CoinbasePro) RenameProfile(ctx context.Context, profileID, newName string) (Profile, error) { + var resp Profile + if newName == "" { + return resp, errors.New("new name cannot be empty") + } + + req := map[string]interface{}{"profile_id": profileID, "name": newName} + + path := fmt.Sprintf("%s/%s", coinbaseproProfiles, profileID) + + return resp, + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPut, path, req, &resp) +} + +// DeleteProfile deletes a profile and transfers its funds to a specified +// proifle. Fails if there are any open orders on the proifle facing deletion +func (c *CoinbasePro) DeleteProfile(ctx context.Context, profileID, transferTo string) (string, error) { + var resp string + if profileID == "" || transferTo == "" { + return resp, errors.New("neither profileID nor transferTo can be empty") + } + + req := map[string]interface{}{"profile_id": profileID, "to": transferTo} + + path := fmt.Sprintf("%s/%s/%s", coinbaseproProfiles, profileID, coinbaseproDeactivate) + + return resp, + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodDelete, path, req, &resp) +} + +// GetReports returns a list of all user-generated reports +func (c *CoinbasePro) GetReports(ctx context.Context, profileID string, reportType string, after time.Time, limit int64, ignoreExpired bool) ([]Report, error) { + var resp []Report + + var params Params + params.urlVals = url.Values{} + + params.urlVals.Set("profile_id", profileID) + params.urlVals.Set("after", after.Format(time.RFC3339)) + if limit != 0 { + params.urlVals.Set("limit", strconv.FormatInt(limit, 10)) + } + + params.urlVals.Set("type", reportType) + params.urlVals.Set("ignore_expired", strconv.FormatBool(ignoreExpired)) + + path := common.EncodeURLValues(coinbaseproReports, params.urlVals) + + return resp, + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp) +} + +// CreateReport creates a new report +func (c *CoinbasePro) CreateReport(ctx context.Context, reportType, year, format, email, profileID, productID, accountID string, balanceDate, startDate, endDate time.Time) (CreateReportResponse, error) { + var resp CreateReportResponse + + if reportType == "" { + return resp, errors.New("report type cannot be empty") + } + if reportType == "1099k-transaction-history" && year == "" { + return resp, errors.New("year cannot be empty for 1099k-transaction-history reports") + } + + req := map[string]interface{}{"type": reportType, "year": year, "format": format, + "email": email, "profile_id": profileID} + + if reportType == "account" { + req["balance"] = ReportBalanceStruct{DateTime: balanceDate.Format(time.RFC3339)} + } + return resp, common.ErrNotYetImplemented +} + // GetCurrentServerTime returns the API server time func (c *CoinbasePro) GetCurrentServerTime(ctx context.Context) (ServerTime, error) { serverTime := ServerTime{} @@ -812,43 +900,6 @@ func (c *CoinbasePro) GetCoinbaseAccounts(ctx context.Context) ([]CoinbaseAccoun c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, coinbaseproCoinbaseAccounts, nil, &resp) } -// GetReport returns batches of historic information about your account in -// various human and machine readable forms. -// -// reportType - "fills" or "account" -// startDate - Starting date for the report (inclusive) -// endDate - Ending date for the report (inclusive) -// currencyPair - ID of the product to generate a fills report for. -// E.c. BTC-USD. *Required* if type is fills -// accountID - ID of the account to generate an account report for. *Required* -// if type is account -// format - pdf or csv (default is pdf) -// email - [optional] Email address to send the report to -func (c *CoinbasePro) GetReport(ctx context.Context, reportType, startDate, endDate, currencyPair, accountID, format, email string) (Report, error) { - resp := Report{} - req := make(map[string]interface{}) - req["type"] = reportType - req["start_date"] = startDate - req["end_date"] = endDate - req["format"] = "pdf" - - if currencyPair != "" { - req["product_id"] = currencyPair - } - if accountID != "" { - req["account_id"] = accountID - } - if format == "csv" { - req["format"] = format - } - if email != "" { - req["email"] = email - } - - return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproReports, req, &resp) -} - // GetReportStatus once a report request has been accepted for processing, the // status is available by polling the report resource endpoint. func (c *CoinbasePro) GetReportStatus(ctx context.Context, reportID string) (Report, error) { @@ -911,6 +962,8 @@ func (c *CoinbasePro) SendAuthenticatedHTTPRequest(ctx context.Context, ep excha n := strconv.FormatInt(time.Now().Unix(), 10) message := n + method + "/" + path + string(payload) + fmt.Println(message) + hmac, err := crypto.GetHMAC(crypto.HashSHA256, []byte(message), []byte(creds.Secret)) @@ -1076,6 +1129,51 @@ func PrepareAddAddress(currency, address, destination_tag, label, vaspID string, return req, nil } +// OrderbookHelper handles the transfer of bids and asks of unclear levels, to a +// generalised format +func OrderbookHelper(iOD InterOrderDetail, level int32) ([]GenOrderDetail, error) { + gOD := make([]GenOrderDetail, len(iOD)) + + for i := range iOD { + priceConv, ok := iOD[i][0].(string) + if !ok { + return nil, errors.New("unable to type assert price") + } + price, err := strconv.ParseFloat(priceConv, 64) + if err != nil { + return nil, err + } + gOD[i].Price = price + + amountConv, ok := iOD[i][1].(string) + if !ok { + return nil, errors.New("unable to type assert amount") + } + amount, err := strconv.ParseFloat(amountConv, 64) + if err != nil { + return nil, err + } + gOD[i].Amount = amount + + if level == 3 { + orderID, ok := iOD[i][2].(string) + if !ok { + return nil, errors.New("unable to type assert order ID") + } + gOD[i].OrderID = orderID + } else { + numOrders, ok := iOD[i][2].(float64) + if !ok { + return nil, errors.New("unable to type assert number of orders") + } + gOD[i].NumOrders = numOrders + } + + } + return gOD, nil + +} + // func (c *CoinbasePro) GetTravelRules(ctx context.Context) ([]TravelRule, error) { // var resp []TravelRule // accounts, err := c.GetAccounts(ctx) diff --git a/exchanges/coinbasepro/coinbasepro_test.go b/exchanges/coinbasepro/coinbasepro_test.go index 701feb56d26..fbc779de085 100644 --- a/exchanges/coinbasepro/coinbasepro_test.go +++ b/exchanges/coinbasepro/coinbasepro_test.go @@ -20,7 +20,6 @@ import ( "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" - "github.com/thrasher-corp/gocryptotrader/exchanges/kline" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues" "github.com/thrasher-corp/gocryptotrader/exchanges/stream" @@ -87,7 +86,6 @@ func TestStart(t *testing.T) { func TestGetAccounts(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - var resp []AccountResponse resp, err := c.GetAccounts(context.Background()) if err != nil { t.Error("CoinBasePro GetAccounts() error", err) @@ -114,7 +112,7 @@ func TestGetAccount(t *testing.T) { if err != nil { t.Error("CoinBasePro GetAccount() error", err) } - if shortResp != longResp[0] { + if *shortResp != longResp[0] { t.Error("CoinBasePro GetAccount() error, mismatched responses") } } @@ -125,11 +123,10 @@ func TestGetHolds(t *testing.T) { if err != nil { t.Error("CoinBasePro GetAccounts() error", err) } - result, err := c.GetHolds(context.Background(), accID[0].ID, pageNone, "", 2) + _, err = c.GetHolds(context.Background(), accID[0].ID, pageNone, "", 2) if err != nil { t.Error("CoinBasePro GetHolds() error", err) } - t.Logf("%+v", result) } func TestGetAccountLedger(t *testing.T) { @@ -138,10 +135,6 @@ func TestGetAccountLedger(t *testing.T) { if err != nil { t.Error("CoinBasePro GetAccounts() error", err) } - _, err = c.GetAccountLedger(context.Background(), accID[0].ID, pageNone, "", "", time.Time{}, time.Time{}, 2) - if err == nil { - t.Error("CoinBasePro GetAccountLedger() error, expected error due to invalid time range") - } _, err = c.GetAccountLedger(context.Background(), accID[0].ID, pageBefore, "", "a", time.Unix(1, 1), time.Now(), 3) if err != nil { @@ -193,11 +186,10 @@ func TestDeleteAddress(t *testing.T) { func TestGetCoinbaseWallets(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - res, err := c.GetCoinbaseAccounts(context.Background()) + _, err := c.GetCoinbaseAccounts(context.Background()) if err != nil { t.Error("CoinBasePro GetCoinbaseAccounts() error", err) } - log.Printf("%+v", res) } func TestGenerateCryptoAddress(t *testing.T) { @@ -432,6 +424,10 @@ func TestGetProducts(t *testing.T) { } func TestGetProduct(t *testing.T) { + _, err := c.GetProduct(context.Background(), "") + if err == nil { + t.Error("Coinbase, GetProduct() Error, expected an error due to nonexistent pair") + } resp, err := c.GetProduct(context.Background(), "BTC-USD") if err != nil { t.Error("Coinbase, GetProduct() Error:", err) @@ -442,57 +438,168 @@ func TestGetProduct(t *testing.T) { } func TestGetOrderbook(t *testing.T) { - resp, err := c.GetOrderbook(context.Background(), testPair.String(), 1) + _, err := c.GetOrderbook(context.Background(), "", 1) + if err == nil { + t.Error("Coinbase, GetOrderbook() Error, expected an error due to nonexistent pair") + } + _, err = c.GetOrderbook(context.Background(), "There's no way this doesn't cause an error", 1) + if err == nil { + t.Error("Coinbase, GetOrderbook() Error, expected an error due to invalid pair") + } + _, err = c.GetOrderbook(context.Background(), testPair.String(), 1) if err != nil { - t.Error(err) + t.Error("Coinbase, GetOrderbook() Error", err) } - log.Printf("%+v", resp) - resp, err = c.GetOrderbook(context.Background(), testPair.String(), 3) + _, err = c.GetOrderbook(context.Background(), testPair.String(), 3) if err != nil { - t.Error(err) + t.Error("Coinbase, GetOrderbook() Error", err) + } +} + +func TestGetHistoricRates(t *testing.T) { + _, err := c.GetHistoricRates(context.Background(), "", 0, time.Time{}, time.Time{}) + if err == nil { + t.Error("Coinbase, GetHistoricRates() Error, expected an error due to nonexistent pair") + } + _, err = c.GetHistoricRates(context.Background(), testPair.String(), 0, time.Now(), time.Unix(1, 1)) + if err == nil { + t.Error("Coinbase, GetHistoricRates() Error, expected an error due to invalid time") + } + _, err = c.GetHistoricRates(context.Background(), testPair.String(), 2<<60-2<<20, time.Time{}, time.Time{}) + if err == nil { + t.Error("Coinbase, GetHistoricRates() Error, expected an error due to invalid granularity") + } + _, err = c.GetHistoricRates(context.Background(), "Invalid pair.woof", 60, time.Time{}, time.Time{}) + if err == nil { + t.Error("Coinbase, GetHistoricRates() Error, expected an error due to invalid pair") + } + resp, err := c.GetHistoricRates(context.Background(), testPair.String(), 0, time.Unix(1, 1), time.Now()) + if err != nil { + t.Error("Coinbase, GetHistoricRates() Error", err) + } + if resp[0].High == 0 { + t.Error("Coinbase, GetHistoricRates() Error, expected non-zero value") + } +} + +func TestGetStats(t *testing.T) { + _, err := c.GetStats(context.Background(), "") + if err == nil { + t.Error("Coinbase, GetStats() Error, expected an error due to nonexistent pair") + } + _, err = c.GetStats(context.Background(), testPair.String()) + if err != nil { + t.Error("GetStats() error", err) } - log.Printf("%+v", resp) } func TestGetTicker(t *testing.T) { - _, err := c.GetTicker(context.Background(), testPair.String()) + _, err := c.GetTicker(context.Background(), "") + if err == nil { + t.Error("Coinbase, GetTicker() Error, expected an error due to nonexistent pair") + } + _, err = c.GetTicker(context.Background(), testPair.String()) if err != nil { t.Error("GetTicker() error", err) } } func TestGetTrades(t *testing.T) { - _, err := c.GetTrades(context.Background(), testPair.String()) + _, err := c.GetTrades(context.Background(), "", "", "", 1) + if err == nil { + t.Error("Coinbase, GetTrades() Error, expected an error due to nonexistent pair") + } + _, err = c.GetTrades(context.Background(), testPair.String(), "", "", 1) if err != nil { t.Error("GetTrades() error", err) } } -func TestGetHistoricRatesGranularityCheck(t *testing.T) { - end := time.Now() - start := end.Add(-time.Hour * 2) - _, err := c.GetHistoricCandles(context.Background(), - testPair, asset.Spot, kline.OneHour, start, end) +func TestGetProfiles(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + active := true + _, err := c.GetProfiles(context.Background(), &active) if err != nil { - t.Fatal(err) + t.Error("GetProfiles() error", err) } } -func TestCoinbasePro_GetHistoricCandlesExtended(t *testing.T) { - start := time.Unix(1546300800, 0) - end := time.Unix(1577836799, 0) +func TestCreateAProfile(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) + _, err := c.CreateAProfile(context.Background(), "") + if err == nil { + t.Error("Coinbase, CreateAProfile() Error, expected an error due to empty name") + } + // The names 'default' and 'margin' are reserved, so consider using those for tests + _, err = c.CreateAProfile(context.Background(), "GCT Test Profile") + if err != nil { + t.Error("CreateAProfile() error", err) + } +} - _, err := c.GetHistoricCandlesExtended(context.Background(), - testPair, asset.Spot, kline.OneDay, start, end) +func TestTransferBetweenProfiles(t *testing.T) { + sharedtestvalues.SkipTestIfCannotManipulateOrders(t, c, canManipulateRealOrders) + _, err := c.TransferBetweenProfiles(context.Background(), "", "", "", 0) + if err == nil { + t.Error("Coinbase, TransferBetweenProfiles() Error, expected an error due to empty fields") + } + _, err = c.TransferBetweenProfiles(context.Background(), "this test", "has not", "been implemented", 0) + if err == nil { + t.Error("Coinbase, TransferBetweenProfiles() Error, expected an error due to un-implemented test") + } +} + +func TestGetProfileByID(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + resp, err := c.GetProfiles(context.Background(), nil) if err != nil { - t.Fatal(err) + t.Error("GetProfiles() error", err) + } + active := true + resp2, err := c.GetProfileByID(context.Background(), resp[0].ID, &active) + if err != nil { + t.Error("GetProfileByID() error", err) + } + if resp2.ID != resp[0].ID { + t.Error("GetProfileByID() error, expected matching ID's") } } -func TestGetStats(t *testing.T) { - _, err := c.GetStats(context.Background(), testPair.String()) +func TestRenameProfile(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) + _, err := c.RenameProfile(context.Background(), "", "") + if err == nil { + t.Error("Coinbase, RenameProfile() Error, expected an error due to empty fields") + } + _, err = c.RenameProfile(context.Background(), "this test has", "not been implemented") + if err == nil { + t.Error("Coinbase, RenameProfile() Error, expected an error due to un-implemented test") + } +} + +func TestDeleteProfile(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) + _, err := c.DeleteProfile(context.Background(), "", "") + if err == nil { + t.Error("Coinbase, DeleteProfile() Error, expected an error due to empty fields") + } + _, err = c.DeleteProfile(context.Background(), "this test has", "not been implemented") + if err == nil { + t.Error("Coinbase, DeleteProfile() Error, expected an error due to un-implemented test") + } +} + +func TestGetReport(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + prof, err := c.GetProfiles(context.Background(), nil) if err != nil { - t.Error("GetStats() error", err) + t.Error("GetProfiles() error", err) + } + c.Verbose = true + resp, err := c.GetReports(context.Background(), prof[0].ID, "account", time.Time{}, 1000, false) + log.Printf("%+v", resp) + if err != nil { + t.Error("GetReport() error", err) } } @@ -1460,3 +1567,76 @@ func TestPrepareAddAddress(t *testing.T) { t.Errorf("CoinBasePro PrepareAddAddress(), Expected: %v, Received: %v", expectedResult, result) } } + +func TestOrderbookHelper(t *testing.T) { + t.Parallel() + req := make(InterOrderDetail, 1) + req[0][0] = true + req[0][1] = false + req[0][2] = true + + _, err := OrderbookHelper(req, 2) + if err == nil { + t.Error("CoinBasePro OrderbookHelper(), expected unable to type assert price error") + } + req[0][0] = "egg" + _, err = OrderbookHelper(req, 2) + if err == nil { + t.Error("CoinBasePro OrderbookHelper(), expected invalid ParseFloat error") + } + req[0][0] = "1.1" + _, err = OrderbookHelper(req, 2) + if err == nil { + t.Error("CoinBasePro OrderbookHelper(), expected unable to type assert amount error") + } + req[0][1] = "meow" + _, err = OrderbookHelper(req, 2) + if err == nil { + t.Error("CoinBasePro OrderbookHelper(), expected invalid ParseFloat error") + } + req[0][1] = "2.2" + _, err = OrderbookHelper(req, 2) + if err == nil { + t.Error("CoinBasePro OrderbookHelper(), expected unable to type assert number of orders error") + } + req[0][2] = 3.3 + _, err = OrderbookHelper(req, 2) + if err != nil { + t.Error("CoinBasePro OrderbookHelper() error", err) + } + _, err = OrderbookHelper(req, 3) + if err == nil { + t.Error("CoinBasePro OrderbookHelper(), expected unable to type assert order ID error") + } + req[0][2] = "woof" + _, err = OrderbookHelper(req, 3) + if err != nil { + t.Error("CoinBasePro OrderbookHelper() error", err) + } + + log.Printf("Err: %v", err) +} + +// 8837708 143.0 ns/op 24 B/op 5 allocs/op +// func BenchmarkXxx(b *testing.B) { +// for x := 0; x < b.N; x++ { +// _ = strconv.FormatInt(60, 10) +// _ = strconv.FormatInt(300, 10) +// _ = strconv.FormatInt(900, 10) +// _ = strconv.FormatInt(3600, 10) +// _ = strconv.FormatInt(21600, 10) +// _ = strconv.FormatInt(86400, 10) +// } +// } + +// 8350056 154.3 ns/op 24 B/op 5 allocs/op +// func BenchmarkXxx2(b *testing.B) { +// for x := 0; x < b.N; x++ { +// _ = strconv.Itoa(60) +// _ = strconv.Itoa(300) +// _ = strconv.Itoa(900) +// _ = strconv.Itoa(3600) +// _ = strconv.Itoa(21600) +// _ = strconv.Itoa(86400) +// } +// } diff --git a/exchanges/coinbasepro/coinbasepro_types.go b/exchanges/coinbasepro/coinbasepro_types.go index d0b8efd9a64..c4069d732c2 100644 --- a/exchanges/coinbasepro/coinbasepro_types.go +++ b/exchanges/coinbasepro/coinbasepro_types.go @@ -33,12 +33,12 @@ type Product struct { // Ticker holds basic ticker information type Ticker struct { - TradeID int64 `json:"trade_id"` Ask float64 `json:"ask,string"` Bid float64 `json:"bid,string"` + Volume float64 `json:"volume,string"` + TradeID int32 `json:"trade_id"` Price float64 `json:"price,string"` Size float64 `json:"size,string"` - Volume float64 `json:"volume,string"` Time time.Time `json:"time"` } @@ -78,7 +78,7 @@ type Currency struct { MinSize float64 `json:"min_size,string"` Status string `json:"status"` Message string `json:"message"` - MaxPrecision float64 `json:"max_precision"` + MaxPrecision float64 `json:"max_precision,string"` ConvertibleTo []string `json:"convertible_to"` Details struct { Type string `json:"type"` @@ -410,15 +410,51 @@ type CoinbaseAccounts struct { type Report struct { ID string `json:"id"` Type string `json:"type"` - Status string `json:"status"` CreatedAt time.Time `json:"created_at"` CompletedAt time.Time `json:"completed_at"` ExpiresAt time.Time `json:"expires_at"` + Status string `json:"status"` + UserID string `json:"user_id"` FileURL string `json:"file_url"` Params struct { StartDate time.Time `json:"start_date"` EndDate time.Time `json:"end_date"` + Format string `json:"format"` + ProductID string `json:"product_id"` + AccountID string `json:"account_id"` + ProfileID string `json:"profile_id"` + Email string `json:"email"` + User struct { + ID string `json:"id"` + CreatedAt time.Time `json:"created_at"` + ActiveAt time.Time `json:"active_at"` + Name string `json:"name"` + Email string `json:"email"` + Roles []interface{} `json:"roles"` + IsBanned bool `json:"is_banned"` + UserType string `json:"user_type"` + FulfillsNewRequirements bool `json:"fulfills_new_requirements"` + Flags interface{} `json:"flags"` + Details interface{} `json:"details"` + Preferences struct { + PreferredMarket string `json:"preferred_market"` + MarginTermsCompletedInUTC time.Time `json:"margin_terms_completed_in_utc"` + MarginTutorialCompletedInUTC time.Time `json:"margin_tutorial_completed_in_utc"` + } `json:"preferences"` + HasDefault bool `json:"has_default"` + StateCode string `json:"state_code"` + CBDataFromCache bool `json:"cb_data_from_cache"` + TwoFactorMethod string `json:"two_factor_method"` + LegalName string `json:"legal_name"` + TermsAccepted time.Time `json:"terms_accepted"` + HasClawbackPaymentPending bool `json:"has_clawback_payment_pending"` + HasRestrictedAssets bool `json:"has_restricted_assets"` + } `json:"user"` + NewYorkState bool `json:"new_york_state"` + DateTime time.Time `json:"date_time"` + GroupByProfile bool `json:"group_by_profile"` } `json:"params"` + FileCount uint64 `json:"file_count"` } // Volume type contains trailing volume information @@ -429,64 +465,13 @@ type Volume struct { RecordedAt string `json:"recorded_at"` } -// // OrderL1L2 is a type used in layer conversion -// type OrderL1L2 struct { -// Price float64 -// Amount float64 -// NumOrders float64 -// } - -// // OrderL3 is a type used in layer conversion -// type OrderL3 struct { -// Price float64 -// Amount float64 -// OrderID string -// } - -// // OrderbookL1L2 holds level 1 and 2 order book information -// type OrderbookL1L2 struct { -// Bids []OrderL1L2 `json:"bids"` -// Asks []OrderL1L2 `json:"asks"` -// Sequence float64 `json:"sequence"` -// AuctionMode bool `json:"auction_mode"` -// Auction struct { -// OpenPrice float64 `json:"open_price,string"` -// OpenSize float64 `json:"open_size,string"` -// BestBidPrice float64 `json:"best_bid_price,string"` -// BestBidSize float64 `json:"best_bid_size,string"` -// BestAskPrice float64 `json:"best_ask_price,string"` -// BestAskSize float64 `json:"best_ask_size,string"` -// AuctionState string `json:"auction_state"` -// CanOpen string `json:"can_open"` -// Time time.Time `json:"time"` -// } -// Time time.Time `json:"time"` -// } - -// // OrderbookL3 holds level 3 order book information -// type OrderbookL3 struct { -// Bids []OrderL3 `json:"bids"` -// Asks []OrderL3 `json:"asks"` -// Sequence float64 `json:"sequence"` -// AuctionMode bool `json:"auction_mode"` -// Auction struct { -// OpenPrice float64 `json:"open_price,string"` -// OpenSize float64 `json:"open_size,string"` -// BestBidPrice float64 `json:"best_bid_price,string"` -// BestBidSize float64 `json:"best_bid_size,string"` -// BestAskPrice float64 `json:"best_ask_price,string"` -// BestAskSize float64 `json:"best_ask_size,string"` -// AuctionState string `json:"auction_state"` -// CanOpen string `json:"can_open"` -// Time time.Time `json:"time"` -// } -// Time time.Time `json:"time"` -// } - -// // OrderbookResponse is a generalized response for order books +// InterOrderDetail is used to make intermediary orderbook handling easier +type InterOrderDetail [][3]interface{} + +// OrderbookIntermediaryResponse is used while processing the orderbook type OrderbookIntermediaryResponse struct { - Bids [][3]interface{} `json:"bids"` - Asks [][3]interface{} `json:"asks"` + Bids InterOrderDetail `json:"bids"` + Asks InterOrderDetail `json:"asks"` Sequence float64 `json:"sequence"` AuctionMode bool `json:"auction_mode"` Auction struct { @@ -503,6 +488,7 @@ type OrderbookIntermediaryResponse struct { Time time.Time `json:"time"` } +// GenOrderDetail is a subtype used for the orderbook type GenOrderDetail struct { Price float64 Amount float64 @@ -510,6 +496,7 @@ type GenOrderDetail struct { OrderID string } +// OrderbookResponse is the final state of the orderbook type OrderbookFinalResponse struct { Bids []GenOrderDetail `json:"bids"` Asks []GenOrderDetail `json:"asks"` @@ -832,3 +819,22 @@ type SignedPrices struct { Signatures []string `json:"signatures"` Prices PriceMap `json:"prices"` } + +type Profile struct { + ID string `json:"id"` + UserID string `json:"user_id"` + Name string `json:"name"` + Active bool `json:"active"` + IsDefault bool `json:"is_default"` + CreatedAt time.Time `json:"created_at"` +} + +type CreateReportResponse struct { + ID string `json:"id"` + Type string `json:"type"` + Status string `json:"status"` +} + +type ReportBalanceStruct struct { + DateTime string +} diff --git a/exchanges/coinbasepro/coinbasepro_wrapper.go b/exchanges/coinbasepro/coinbasepro_wrapper.go index 81cc740d9d0..74074e6c197 100644 --- a/exchanges/coinbasepro/coinbasepro_wrapper.go +++ b/exchanges/coinbasepro/coinbasepro_wrapper.go @@ -325,37 +325,37 @@ func (c *CoinbasePro) UpdateTradablePairs(ctx context.Context, forceUpdate bool) func (c *CoinbasePro) UpdateAccountInfo(ctx context.Context, assetType asset.Item) (account.Holdings, error) { var response account.Holdings response.Exchange = c.Name - accountBalance, err := c.GetAccounts(ctx) - if err != nil { - return response, err - } + // accountBalance, err := c.GetAccounts(ctx) + // if err != nil { + // return response, err + // } - accountCurrencies := make(map[string][]account.Balance) - for i := range accountBalance { - profileID := accountBalance[i].ProfileID - currencies := accountCurrencies[profileID] - accountCurrencies[profileID] = append(currencies, account.Balance{ - Currency: currency.NewCode(accountBalance[i].Currency), - Total: accountBalance[i].Balance, - Hold: accountBalance[i].Hold, - Free: accountBalance[i].Available, /* - AvailableWithoutBorrow: accountBalance[i].Available - accountBalance[i].FundedAmount, - Borrowed: accountBalance[i].FundedAmount,*/ - }) - } + // accountCurrencies := make(map[string][]account.Balance) + // for i := range accountBalance { + // profileID := accountBalance[i].ProfileID + // currencies := accountCurrencies[profileID] + // accountCurrencies[profileID] = append(currencies, account.Balance{ + // Currency: currency.NewCode(accountBalance[i].Currency), + // Total: accountBalance[i].Balance, + // Hold: accountBalance[i].Hold, + // Free: accountBalance[i].Available, + // AvailableWithoutBorrow: accountBalance[i].Available - accountBalance[i].FundedAmount, + // Borrowed: accountBalance[i].FundedAmount, + // }) + // } - if response.Accounts, err = account.CollectBalances(accountCurrencies, assetType); err != nil { - return account.Holdings{}, err - } + // 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 - } - err = account.Process(&response, creds) - if err != nil { - return account.Holdings{}, err - } + // creds, err := c.GetCredentials(ctx) + // if err != nil { + // return account.Holdings{}, err + // } + // err = account.Process(&response, creds) + // if err != nil { + // return account.Holdings{}, err + // } return response, nil } @@ -505,7 +505,7 @@ func (c *CoinbasePro) GetRecentTrades(ctx context.Context, p currency.Pair, asse return nil, err } var tradeData []Trade - tradeData, err = c.GetTrades(ctx, p.String()) + // tradeData, err = c.GetTrades(ctx, p.String()) if err != nil { return nil, err } @@ -705,18 +705,18 @@ func (c *CoinbasePro) WithdrawFiatFunds(ctx context.Context, withdrawRequest *wi if err := withdrawRequest.Validate(); err != nil { return nil, err } - paymentMethods, err := c.GetPayMethods(ctx) - if err != nil { - return nil, err - } + // paymentMethods, err := c.GetPayMethods(ctx) + // if err != nil { + // return nil, err + // } selectedWithdrawalMethod := PaymentMethod{} - for i := range paymentMethods { - if withdrawRequest.Fiat.Bank.BankName == paymentMethods[i].Name { - selectedWithdrawalMethod = paymentMethods[i] - break - } - } + // for i := range paymentMethods { + // if withdrawRequest.Fiat.Bank.BankName == paymentMethods[i].Name { + // selectedWithdrawalMethod = paymentMethods[i] + // break + // } + // } if selectedWithdrawalMethod.ID == "" { return nil, fmt.Errorf("could not find payment method '%v'. Check the name via the website and try again", withdrawRequest.Fiat.Bank.BankName) } @@ -726,12 +726,13 @@ func (c *CoinbasePro) WithdrawFiatFunds(ctx context.Context, withdrawRequest *wi // withdrawRequest.Currency.String(), // selectedWithdrawalMethod.ID) // if err != nil { - return nil, err + // return nil, err // } // return &withdraw.ExchangeResponse{ // Status: resp.ID, // }, nil + return nil, common.ErrFunctionNotSupported } // WithdrawFiatFundsToInternationalBank returns a withdrawal ID when a @@ -907,65 +908,67 @@ func (c *CoinbasePro) GetOrderHistory(ctx context.Context, req *order.MultiOrder // GetHistoricCandles returns a set of candle between two time periods for a // designated time period func (c *CoinbasePro) GetHistoricCandles(ctx context.Context, pair currency.Pair, a asset.Item, interval kline.Interval, start, end time.Time) (*kline.Item, error) { - req, err := c.GetKlineRequest(pair, a, interval, start, end, false) - if err != nil { - return nil, err - } + // req, err := c.GetKlineRequest(pair, a, interval, start, end, false) + // if err != nil { + // return nil, err + // } - history, err := c.GetHistoricRates(ctx, - req.RequestFormatted.String(), - start.Format(time.RFC3339), - end.Format(time.RFC3339), - int64(req.ExchangeInterval.Duration().Seconds())) - if err != nil { - return nil, err - } + // history, err := c.GetHistoricRates(ctx, + // req.RequestFormatted.String(), + // start.Format(time.RFC3339), + // end.Format(time.RFC3339), + // int64(req.ExchangeInterval.Duration().Seconds())) + // if err != nil { + // return nil, err + // } - timeSeries := make([]kline.Candle, len(history)) - for x := range history { - timeSeries[x] = kline.Candle{ - Time: history[x].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) + // timeSeries := make([]kline.Candle, len(history)) + // for x := range history { + // timeSeries[x] = kline.Candle{ + // Time: history[x].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) + return nil, common.ErrFunctionNotSupported } // GetHistoricCandlesExtended returns candles between a time period for a set time interval func (c *CoinbasePro) GetHistoricCandlesExtended(ctx context.Context, pair currency.Pair, a asset.Item, interval kline.Interval, start, end time.Time) (*kline.Item, error) { - req, err := c.GetKlineExtendedRequest(pair, a, interval, start, end) - if err != nil { - return nil, err - } + // req, err := c.GetKlineExtendedRequest(pair, a, interval, start, end) + // if err != nil { + // return nil, err + // } - timeSeries := make([]kline.Candle, 0, req.Size()) - for x := range req.RangeHolder.Ranges { - var history []History - history, err = c.GetHistoricRates(ctx, - req.RequestFormatted.String(), - req.RangeHolder.Ranges[x].Start.Time.Format(time.RFC3339), - req.RangeHolder.Ranges[x].End.Time.Format(time.RFC3339), - int64(req.ExchangeInterval.Duration().Seconds())) - if err != nil { - return nil, err - } + // timeSeries := make([]kline.Candle, 0, req.Size()) + // for x := range req.RangeHolder.Ranges { + // var history []History + // history, err = c.GetHistoricRates(ctx, + // req.RequestFormatted.String(), + // req.RangeHolder.Ranges[x].Start.Time.Format(time.RFC3339), + // req.RangeHolder.Ranges[x].End.Time.Format(time.RFC3339), + // int64(req.ExchangeInterval.Duration().Seconds())) + // if err != nil { + // return nil, err + // } - for i := range history { - timeSeries = append(timeSeries, kline.Candle{ - Time: history[i].Time, - Low: history[i].Low, - High: history[i].High, - Open: history[i].Open, - Close: history[i].Close, - Volume: history[i].Volume, - }) - } - } - return req.ProcessResponse(timeSeries) + // for i := range history { + // timeSeries = append(timeSeries, kline.Candle{ + // Time: history[i].Time, + // Low: history[i].Low, + // High: history[i].High, + // Open: history[i].Open, + // Close: history[i].Close, + // Volume: history[i].Volume, + // }) + // } + // } + // return req.ProcessResponse(timeSeries) + return nil, common.ErrFunctionNotSupported } // ValidateAPICredentials validates current credentials used for wrapper From 25fcd2e7d064d503ca51006a11564c08dc75f9a6 Mon Sep 17 00:00:00 2001 From: Samuel Reid <43227667+cranktakular@users.noreply.github.com> Date: Mon, 16 Oct 2023 16:54:27 +1100 Subject: [PATCH 03/79] More coinbase improvements; onto sandbox testing --- exchanges/coinbasepro/coinbasepro.go | 629 +++++---- exchanges/coinbasepro/coinbasepro_test.go | 1204 ++++++++++-------- exchanges/coinbasepro/coinbasepro_types.go | 357 ++++-- exchanges/coinbasepro/coinbasepro_wrapper.go | 28 +- exchanges/coinbasepro/ratelimit.go | 4 +- 5 files changed, 1331 insertions(+), 891 deletions(-) diff --git a/exchanges/coinbasepro/coinbasepro.go b/exchanges/coinbasepro/coinbasepro.go index 18a9911ead8..3e6fb42992a 100644 --- a/exchanges/coinbasepro/coinbasepro.go +++ b/exchanges/coinbasepro/coinbasepro.go @@ -6,7 +6,6 @@ import ( "encoding/json" "errors" "fmt" - "log" "net/http" "net/url" "strconv" @@ -15,53 +14,57 @@ import ( "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/common/crypto" - "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/request" ) const ( - coinbaseproAPIURL = "https://api.exchange.coinbase.com/" - coinbaseproSandboxAPIURL = "https://api-public.sandbox.exchange.coinbase.com/" - coinbaseproAPIVersion = "0" - coinbaseproAccounts = "accounts" - coinbaseproHolds = "holds" - coinbaseproLedger = "ledger" - coinbaseproTransfers = "transfers" - coinbaseproAddressBook = "address-book" - coinbaseproAddress = "addresses" - coinbaseproConversions = "conversions" - coinbaseproCurrencies = "currencies" - coinbaseproDepositCoinbase = "deposits/coinbase-account" - coinbaseproPaymentMethodDeposit = "deposits/payment-method" - coinbaseproPaymentMethod = "payment-methods" - coinbaseproFeeEstimate = "withdrawals/fee-estimate" - coinbaseproFees = "fees" - coinbaseproFills = "fills" - coinbaseproOrders = "orders" - coinbaseproOracle = "oracle" - coinbaseproProducts = "products" - coinbaseproOrderbook = "book" - coinbaseproHistory = "candles" - coinbaseproStats = "stats" - coinbaseproTicker = "ticker" - coinbaseproTrades = "trades" - coinbaseproProfiles = "profiles" - coinbaseproTransfer = "transfer" - coinbaseproDeactivate = "deactivate" - coinbaseproReports = "reports" - - coinbaseproTime = "time" + coinbaseproAPIURL = "https://api.exchange.coinbase.com/" + coinbaseproSandboxAPIURL = "https://api-public.sandbox.exchange.coinbase.com/" + coinbaseproAPIVersion = "0" + coinbaseproAccounts = "accounts" + coinbaseproHolds = "holds" + coinbaseproLedger = "ledger" + coinbaseproTransfers = "transfers" + coinbaseproAddressBook = "address-book" + coinbaseproCoinbaseAccounts = "coinbase-accounts" + coinbaseproAddress = "addresses" + coinbaseproConversions = "conversions" + coinbaseproCurrencies = "currencies" + coinbaseproDepositCoinbase = "deposits/coinbase-account" + coinbaseproPaymentMethodDeposit = "deposits/payment-method" + coinbaseproPaymentMethod = "payment-methods" coinbaseproTravelRules = "travel-rules" - coinbaseproMarginTransfer = "profiles/margin-transfer" - coinbaseproPosition = "position" - coinbaseproPositionClose = "position/close" - coinbaseproWithdrawalPaymentMethod = "withdrawals/payment-method" coinbaseproWithdrawalCoinbase = "withdrawals/coinbase-account" coinbaseproWithdrawalCrypto = "withdrawals/crypto" - coinbaseproCoinbaseAccounts = "coinbase-accounts" - coinbaseproTrailingVolume = "users/self/trailing-volume" + coinbaseproFeeEstimate = "withdrawals/fee-estimate" + coinbaseproWithdrawalPaymentMethod = "withdrawals/payment-method" + coinbaseproFees = "fees" + coinbaseproFills = "fills" + coinbaseproOrders = "orders" + coinbaseproOracle = "oracle" + coinbaseproProducts = "products" + coinbaseproOrderbook = "book" + coinbaseproHistory = "candles" + coinbaseproStats = "stats" + coinbaseproTicker = "ticker" + coinbaseproTrades = "trades" + coinbaseproProfiles = "profiles" + coinbaseproTransfer = "transfer" + coinbaseproDeactivate = "deactivate" + coinbaseproReports = "reports" + coinbaseproUsers = "users" + coinbaseproSettlementPreferences = "settlement-preferences" + coinbaseproWrappedAssets = "wrapped-assets" + coinbaseproStakeWraps = "stake-wrap" + coinbaseproConversionRate = "conversion-rate" + + // coinbaseproTime = "time" + // coinbaseproMarginTransfer = "profiles/margin-transfer" + // coinbaseproPosition = "position" + // coinbaseproPositionClose = "position/close" + // coinbaseproTrailingVolume = "users/self/trailing-volume" ) const ( @@ -75,17 +78,16 @@ type CoinbasePro struct { exchange.Base } -// GetAccounts returns a list of trading accounts associated with the APIKEYS -func (c *CoinbasePro) GetAccounts(ctx context.Context) ([]AccountResponse, error) { +// GetAllAccounts returns information on all trading accounts associated with the API key +func (c *CoinbasePro) GetAllAccounts(ctx context.Context) ([]AccountResponse, error) { var resp []AccountResponse return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, coinbaseproAccounts, nil, &resp) } -// GetAccount returns information for a single account. Use this endpoint when -// account_id is known -func (c *CoinbasePro) GetAccount(ctx context.Context, accountID string) (*AccountResponse, error) { +// GetAccountByID returns information for a single account +func (c *CoinbasePro) GetAccountByID(ctx context.Context, accountID string) (*AccountResponse, error) { path := fmt.Sprintf("%s/%s", coinbaseproAccounts, accountID) resp := AccountResponse{} @@ -93,10 +95,7 @@ func (c *CoinbasePro) GetAccount(ctx context.Context, accountID string) (*Accoun c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp) } -// GetHolds returns the holds that are placed on an account for any active -// orders or pending withdraw requests. As an order is filled, the hold amount -// is updated. If an order is canceled, any remaining hold is removed. For a -// withdraw, once it is completed, the hold is removed. +// GetHolds returns information on the holds of an account func (c *CoinbasePro) GetHolds(ctx context.Context, accountID, direction, step string, limit int64) ([]AccountHolds, error) { path := fmt.Sprintf("%s/%s/%s", coinbaseproAccounts, accountID, coinbaseproHolds) @@ -112,9 +111,7 @@ func (c *CoinbasePro) GetHolds(ctx context.Context, accountID, direction, step s c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp) } -// GetAccountLedger returns a list of ledger activity. Anything that increases -// or decreases your account balance. Items are paginated and sorted latest -// first. +// GetAccountLedger returns a list of ledger activity func (c *CoinbasePro) GetAccountLedger(ctx context.Context, accountID, direction, step, pID string, startDate, endDate time.Time, limit int64) ([]AccountLedgerResponse, error) { var params Params params.urlVals = url.Values{} @@ -141,7 +138,7 @@ func (c *CoinbasePro) GetAccountLedger(ctx context.Context, accountID, direction // GetAccountTransfers returns a history of withdrawal and or deposit // transactions for a single account -func (c *CoinbasePro) GetAccountTransfers(ctx context.Context, accountID, direction, step, transferType string, limit int64) ([]TransferHistory, error) { +func (c *CoinbasePro) GetAccountTransfers(ctx context.Context, accountID, direction, step, transferType string, limit int64) ([]TransferResponse, error) { path := fmt.Sprintf("%s/%s/%s", coinbaseproAccounts, accountID, coinbaseproTransfers) var params Params @@ -152,7 +149,7 @@ func (c *CoinbasePro) GetAccountTransfers(ctx context.Context, accountID, direct path = common.EncodeURLValues(path, params.urlVals) - var resp []TransferHistory + var resp []TransferResponse return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp) @@ -218,7 +215,7 @@ func (c *CoinbasePro) ConvertCurrency(ctx context.Context, profileID, from, to, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproConversions, params, &resp) } -// GetConversionByID identifies the details of a past conversion, given its ID +// GetConversionByID returns the details of a past conversion, given its ID func (c *CoinbasePro) GetConversionByID(ctx context.Context, conversionID, profileID string) (ConvertResponse, error) { path := fmt.Sprintf("%s/%s", coinbaseproConversions, conversionID) var params Params @@ -232,18 +229,18 @@ func (c *CoinbasePro) GetConversionByID(ctx context.Context, conversionID, profi c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp) } -// GetCurrencies returns a list of currencies known by the exchange +// GetAllCurrencies returns a list of currencies known by the exchange // Warning: Currencies won't necessarily be available for trading -func (c *CoinbasePro) GetCurrencies(ctx context.Context) ([]Currency, error) { +func (c *CoinbasePro) GetAllCurrencies(ctx context.Context) ([]Currency, error) { var currencies []Currency return currencies, c.SendHTTPRequest(ctx, exchange.RestSpot, coinbaseproCurrencies, ¤cies) } -// GetCurrenciesByID returns into on a single currency given its ID in ISO 4217, or +// GetCurrencyByID returns info on a single currency given its ID in ISO 4217, or // in a custom code for currencies which lack an ISO 4217 code -func (c *CoinbasePro) GetCurrenciesByID(ctx context.Context, currencyID string) (*Currency, error) { +func (c *CoinbasePro) GetCurrencyByID(ctx context.Context, currencyID string) (*Currency, error) { path := fmt.Sprintf("%s/%s", coinbaseproCurrencies, currencyID) resp := Currency{} @@ -285,7 +282,7 @@ func (c *CoinbasePro) GetPayMethods(ctx context.Context) ([]PaymentMethod, error // GetAllTransfers returns all in-progress and completed transfers in and out of any // of the user's accounts -func (c *CoinbasePro) GetAllTransfers(ctx context.Context, profileID, direction, step, transferType string, limit int64) ([]TransferHistory, error) { +func (c *CoinbasePro) GetAllTransfers(ctx context.Context, profileID, direction, step, transferType string, limit int64) ([]TransferResponse, error) { var params Params params.urlVals = url.Values{} params.urlVals.Set("profile_id", profileID) @@ -293,16 +290,16 @@ func (c *CoinbasePro) GetAllTransfers(ctx context.Context, profileID, direction, params.urlVals.Set("type", transferType) path := common.EncodeURLValues(coinbaseproTransfers, params.urlVals) - resp := []TransferHistory{} + resp := []TransferResponse{} return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp) } // GetTransferByID returns information on a single transfer when provided with its ID -func (c *CoinbasePro) GetTransferByID(ctx context.Context, transferID string) (*TransferHistory, error) { +func (c *CoinbasePro) GetTransferByID(ctx context.Context, transferID string) (*TransferResponse, error) { path := fmt.Sprintf("%s/%s", coinbaseproTransfers, transferID) - resp := TransferHistory{} + resp := TransferResponse{} return &resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp) @@ -390,7 +387,7 @@ func (c *CoinbasePro) GetFees(ctx context.Context) (FeeResponse, error) { c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, coinbaseproFees, nil, &resp) } -// GetFills returns a list of recent fills on this profile +// GetFills returns information of recent fills on the specified profile func (c *CoinbasePro) GetFills(ctx context.Context, orderID, currencyPair, direction, step, marketType string, limit int64, startDate, endDate time.Time) ([]FillResponse, error) { if orderID == "" && currencyPair == "" { return nil, errors.New("requires either order id or product id") @@ -419,8 +416,8 @@ func (c *CoinbasePro) GetFills(ctx context.Context, orderID, currencyPair, direc return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp) } -// GetOrders lists all open or unsettled orders -func (c *CoinbasePro) GetOrders(ctx context.Context, profileID, currencyPair, sortedBy, sorting, direction, step, marketType string, startDate, endDate time.Time, limit int64, status []string) ([]GeneralizedOrderResponse, error) { +// GetAllOrders lists all open or unsettled orders +func (c *CoinbasePro) GetAllOrders(ctx context.Context, profileID, currencyPair, sortedBy, sorting, direction, step, marketType string, startDate, endDate time.Time, limit int64, status []string) ([]GeneralizedOrderResponse, error) { if limit < 1 { return nil, errors.New("limit must be greater than 0") } @@ -475,22 +472,30 @@ func (c *CoinbasePro) PlaceOrder(ctx context.Context, profileID, orderType, side if orderType == order.Market.Lower() && (size == 0 && funds == 0) { return &resp, errors.New("size or funds must be greater than 0 for market orders") } + if side != order.Buy.Lower() && side != order.Sell.Lower() { + return &resp, errors.New("side must be buy or sell") + } req := map[string]interface{}{"profile_id": profileID, "type": orderType, "side": side, "product_id": currencyPair, "stp": stp, "stop": stop, - "stop_price": strconv.FormatFloat(stopPrice, 'f', -1, 64), "price": strconv.FormatFloat(price, 'f', -1, 64), "size": strconv.FormatFloat(size, 'f', -1, 64), - "funds": strconv.FormatFloat(funds, 'f', -1, 64), "time_in_force": timeInForce, "cancel_after": cancelAfter, "post_only": postOnly, "client_oid": clientOID} + if stopPrice != 0 { + req["stop_price"] = strconv.FormatFloat(stopPrice, 'f', -1, 64) + } + if funds != 0 { + req["funds"] = strconv.FormatFloat(funds, 'f', -1, 64) + } + return &resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproOrders, req, &resp) } -// GetOrder returns a single order by order id. -func (c *CoinbasePro) GetOrder(ctx context.Context, orderID, marketType string, clientID bool) (*GeneralizedOrderResponse, error) { +// GetOrderByID returns a single order by order id. +func (c *CoinbasePro) GetOrderByID(ctx context.Context, orderID, marketType string, clientID bool) (*GeneralizedOrderResponse, error) { resp := GeneralizedOrderResponse{} if orderID == "" { return &resp, errors.New("order id cannot be empty") @@ -501,7 +506,11 @@ func (c *CoinbasePro) GetOrder(ctx context.Context, orderID, marketType string, var param Params param.urlVals = url.Values{} - param.urlVals.Set("market_type", marketType) + // Spot's currently the only supported market type; passing in anything else here will + // cause the request to time out after a minute + if marketType == "spot" { + param.urlVals.Set("market_type", marketType) + } path := fmt.Sprintf("%s/%s", coinbaseproOrders, orderID) path = common.EncodeURLValues(path, param.urlVals) @@ -522,8 +531,7 @@ func (c *CoinbasePro) CancelExistingOrder(ctx context.Context, orderID, profileI var param Params param.urlVals = url.Values{} - param.urlVals.Set("profile_id", profileID) - param.urlVals.Set("product_id", productID) + param.PrepareProfIDAndProdID(profileID, productID) path = common.EncodeURLValues(path, param.urlVals) @@ -539,8 +547,8 @@ func (c *CoinbasePro) GetSignedPrices(ctx context.Context) (SignedPrices, error) c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, coinbaseproOracle, nil, &resp) } -// GetProducts returns information on all currency pairs that are available for trading -func (c *CoinbasePro) GetProducts(ctx context.Context, productType string) ([]Product, error) { +// GetAllProducts returns information on all currency pairs that are available for trading +func (c *CoinbasePro) GetAllProducts(ctx context.Context, productType string) ([]Product, error) { var params Params params.urlVals = url.Values{} params.urlVals.Set("type", productType) @@ -552,8 +560,8 @@ func (c *CoinbasePro) GetProducts(ctx context.Context, productType string) ([]Pr return products, c.SendHTTPRequest(ctx, exchange.RestSpot, path, &products) } -// GetProduct returns information on a single specified currency pair -func (c *CoinbasePro) GetProduct(ctx context.Context, productID string) (*Product, error) { +// GetProductByID returns information on a single specified currency pair +func (c *CoinbasePro) GetProductByID(ctx context.Context, productID string) (*Product, error) { if productID == "" { return nil, errors.New("product id cannot be empty") } @@ -622,9 +630,6 @@ func (c *CoinbasePro) GetHistoricRates(ctx context.Context, currencyPair string, return nil, err } - log.Printf("The date params are %v and %v", params.urlVals.Get("start_date"), - params.urlVals.Get("end_date")) - allowedGranularities := [7]int64{0, 60, 300, 900, 3600, 21600, 86400} validGran, _ := common.InArray(granularity, allowedGranularities) if !validGran { @@ -675,7 +680,6 @@ func (c *CoinbasePro) GetStats(ctx context.Context, currencyPair string) (Stats, // GetTicker returns snapshot information about the last trade (tick), best bid/ask and // 24h volume. -// currencyPair - example "BTC-USD" func (c *CoinbasePro) GetTicker(ctx context.Context, currencyPair string) (*Ticker, error) { if currencyPair == "" { return nil, errors.New("currency pair cannot be empty") @@ -687,8 +691,7 @@ func (c *CoinbasePro) GetTicker(ctx context.Context, currencyPair string) (*Tick return &tick, c.SendHTTPRequest(ctx, exchange.RestSpot, path, &tick) } -// GetTrades lists the latest trades for a product -// currencyPair - example "BTC-USD" +// GetTrades lists information on the latest trades for a product func (c *CoinbasePro) GetTrades(ctx context.Context, currencyPair, direction, step string, limit int64) ([]Trade, error) { if currencyPair == "" { return nil, errors.New("currency pair cannot be empty") @@ -708,8 +711,8 @@ func (c *CoinbasePro) GetTrades(ctx context.Context, currencyPair, direction, st return trades, c.SendHTTPRequest(ctx, exchange.RestSpot, path, &trades) } -// GetProfiles returna a list of all of the current user's profiles -func (c *CoinbasePro) GetProfiles(ctx context.Context, active *bool) ([]Profile, error) { +// GetAllProfiles returns information on all of the current user's profiles +func (c *CoinbasePro) GetAllProfiles(ctx context.Context, active *bool) ([]Profile, error) { var params Params params.urlVals = url.Values{} @@ -755,7 +758,7 @@ func (c *CoinbasePro) TransferBetweenProfiles(ctx context.Context, from, to, cur c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, path, req, &resp) } -// GetProfileByID returns a single profile, provided its ID +// GetProfileByID returns information on a single profile, provided its ID func (c *CoinbasePro) GetProfileByID(ctx context.Context, profileID string, active *bool) (Profile, error) { var params Params params.urlVals = url.Values{} @@ -787,7 +790,7 @@ func (c *CoinbasePro) RenameProfile(ctx context.Context, profileID, newName stri } // DeleteProfile deletes a profile and transfers its funds to a specified -// proifle. Fails if there are any open orders on the proifle facing deletion +// profile. Fails if there are any open orders on the profile facing deletion func (c *CoinbasePro) DeleteProfile(ctx context.Context, profileID, transferTo string) (string, error) { var resp string if profileID == "" || transferTo == "" { @@ -802,8 +805,8 @@ func (c *CoinbasePro) DeleteProfile(ctx context.Context, profileID, transferTo s c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodDelete, path, req, &resp) } -// GetReports returns a list of all user-generated reports -func (c *CoinbasePro) GetReports(ctx context.Context, profileID string, reportType string, after time.Time, limit int64, ignoreExpired bool) ([]Report, error) { +// GetAllReports returns a list of all user-generated reports +func (c *CoinbasePro) GetAllReports(ctx context.Context, profileID string, reportType string, after time.Time, limit int64, ignoreExpired bool) ([]Report, error) { var resp []Report var params Params @@ -839,85 +842,255 @@ func (c *CoinbasePro) CreateReport(ctx context.Context, reportType, year, format "email": email, "profile_id": profileID} if reportType == "account" { + req["account"] = ReportAccountStruct{StartDate: startDate.Format(time.RFC3339), + EndDate: endDate.Format(time.RFC3339), AccountID: accountID} + } + if reportType == "balance" { req["balance"] = ReportBalanceStruct{DateTime: balanceDate.Format(time.RFC3339)} } - return resp, common.ErrNotYetImplemented -} - -// GetCurrentServerTime returns the API server time -func (c *CoinbasePro) GetCurrentServerTime(ctx context.Context) (ServerTime, error) { - serverTime := ServerTime{} - return serverTime, c.SendHTTPRequest(ctx, exchange.RestSpot, coinbaseproTime, &serverTime) -} - -// MarginTransfer sends funds between a standard/default profile and a margin -// profile. -// A deposit will transfer funds from the default profile into the margin -// profile. A withdraw will transfer funds from the margin profile to the -// default profile. Withdraws will fail if they would set your margin ratio -// below the initial margin ratio requirement. -// -// amount - the amount to transfer between the default and margin profile -// transferType - either "deposit" or "withdraw" -// profileID - The id of the margin profile to deposit or withdraw from -// currency - currency to transfer, currently on "BTC" or "USD" -func (c *CoinbasePro) MarginTransfer(ctx context.Context, amount float64, transferType, profileID, currency string) (MarginTransfer, error) { - resp := MarginTransfer{} - req := make(map[string]interface{}) - req["type"] = transferType - req["amount"] = strconv.FormatFloat(amount, 'f', -1, 64) - req["currency"] = currency - req["margin_profile_id"] = profileID + if reportType == "fills" || reportType == "otc-fills" || reportType == "rfq-fills" || + reportType == "tax-invoice" { + req[reportType] = ReportFillsTaxStruct{StartDate: startDate.Format(time.RFC3339), + EndDate: endDate.Format(time.RFC3339), ProductID: productID} + } + return resp, + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproReports, req, &resp) +} + +// GetReportByID returns a single report, provided its ID +func (c *CoinbasePro) GetReportByID(ctx context.Context, reportID string) (Report, error) { + var resp Report + if reportID == "" { + return resp, errors.New("report id cannot be empty") + } + + path := fmt.Sprintf("%s/%s", coinbaseproReports, reportID) + + return resp, + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp) +} + +// GetTravelRules returns a list of all travel rule information +func (c *CoinbasePro) GetTravelRules(ctx context.Context, direction, step, address string, limit int64) ([]TravelRule, error) { + var resp []TravelRule + var params Params + params.urlVals = url.Values{} + + params.PrepareDSL(direction, step, limit) + params.urlVals.Set("address", address) + + path := common.EncodeURLValues(coinbaseproTravelRules, params.urlVals) + + return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp) +} + +// CreateTravelRule creates a travel rule entry +func (c *CoinbasePro) CreateTravelRule(ctx context.Context, address, originName, originCountry string) (TravelRule, error) { + var resp TravelRule + + req := map[string]interface{}{"address": address, "originator_name": originName, + "originator_country": originCountry} return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproMarginTransfer, req, &resp) + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproTravelRules, req, &resp) +} + +// DeleteTravelRule deletes a travel rule entry +func (c *CoinbasePro) DeleteTravelRule(ctx context.Context, address string) error { + if address == "" { + return errors.New("address cannot be empty") + } + + path := fmt.Sprintf("%s/%s", coinbaseproTravelRules, address) + + return c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodDelete, path, nil, nil) } -// GetPosition returns an overview of account profile. -func (c *CoinbasePro) GetPosition(ctx context.Context) (AccountOverview, error) { - resp := AccountOverview{} +// GetExchangeLimits returns information on payment method transfer limits, +// as well as buy/sell limits per currency +func (c *CoinbasePro) GetExchangeLimits(ctx context.Context, userID string) (ExchangeLimits, error) { + var resp ExchangeLimits + + path := fmt.Sprintf("%s/%s", coinbaseproUsers, userID) return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, coinbaseproPosition, nil, &resp) + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp) } -// ClosePosition closes a position and allowing you to repay position as well -// repayOnly - allows the position to be repaid -func (c *CoinbasePro) ClosePosition(ctx context.Context, repayOnly bool) (AccountOverview, error) { - resp := AccountOverview{} - req := make(map[string]interface{}) - req["repay_only"] = repayOnly +// UpdateSettlementPreference updates whether one wants their funds to +// automatically convert to USD, USDC, or to remain in the currency received +func (c *CoinbasePro) UpdateSettlementPreference(ctx context.Context, userID, preference string) (string, error) { + if userID == "" || preference == "" { + return "", errors.New("neither userID nor preference can be empty") + } + + req := map[string]interface{}{"settlement_preference": preference} + + path := fmt.Sprintf("%s/%s/%s", coinbaseproUsers, userID, coinbaseproSettlementPreferences) + + var resp string return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproPositionClose, req, &resp) + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPut, path, req, &resp) } -// GetCoinbaseAccounts returns a list of coinbase accounts -func (c *CoinbasePro) GetCoinbaseAccounts(ctx context.Context) ([]CoinbaseAccounts, error) { - var resp []CoinbaseAccounts +// GetAllWrappedAssets returns information on all supported wrapped assets +func (c *CoinbasePro) GetAllWrappedAssets(ctx context.Context) (AllWrappedAssetResponse, error) { + var resp AllWrappedAssetResponse return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, coinbaseproCoinbaseAccounts, nil, &resp) + c.SendHTTPRequest(ctx, exchange.RestSpot, coinbaseproWrappedAssets, &resp) } -// GetReportStatus once a report request has been accepted for processing, the -// status is available by polling the report resource endpoint. -func (c *CoinbasePro) GetReportStatus(ctx context.Context, reportID string) (Report, error) { - resp := Report{} - path := fmt.Sprintf("%s/%s", coinbaseproReports, reportID) +// GetAllStakeWraps returns details of all stake-wraps under the profile associated +// with the API key +func (c *CoinbasePro) GetAllStakeWraps(ctx context.Context, direction, from, to, status string, timestamp time.Time, limit int64) ([]StakeWrap, error) { + var resp []StakeWrap - return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp) + var params Params + params.urlVals = url.Values{} + + if !timestamp.IsZero() && !timestamp.Equal(time.Unix(0, 0)) { + params.PrepareDSL(direction, timestamp.Format(time.RFC3339), limit) + } else { + params.urlVals.Set("limit", strconv.FormatInt(limit, 10)) + } + + params.urlVals.Set("from", from) + params.urlVals.Set("to", to) + params.urlVals.Set("status", status) + + path := fmt.Sprintf("%s/%s", coinbaseproWrappedAssets, coinbaseproStakeWraps) + + path = common.EncodeURLValues(path, params.urlVals) + + return resp, + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp) } -// GetTrailingVolume this request will return your 30-day trailing volume for -// all products. -func (c *CoinbasePro) GetTrailingVolume(ctx context.Context) ([]Volume, error) { - var resp []Volume +// CreateStakeWrap stakes and wraps from one currency to another, under the profile +// associated with the API key +func (c *CoinbasePro) CreateStakeWrap(ctx context.Context, from, to string, amount float64) (StakeWrap, error) { + if from == "" || to == "" || amount == 0 { + return StakeWrap{}, errors.New("none of from, to, or amount can be empty or zero") + } + var resp StakeWrap + + req := map[string]interface{}{"from": from, "to": to, + "amount": strconv.FormatFloat(amount, 'f', -1, 64)} + + path := fmt.Sprintf("%s/%s", coinbaseproWrappedAssets, coinbaseproStakeWraps) + + return resp, + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, path, req, &resp) +} + +// GetStakeWrapByID returns details of a single stake-wrap +func (c *CoinbasePro) GetStakeWrapByID(ctx context.Context, stakeWrapID string) (StakeWrap, error) { + var resp StakeWrap + + if stakeWrapID == "" { + return resp, errors.New("stake wrap id cannot be empty") + } + + path := fmt.Sprintf("%s/%s/%s", coinbaseproWrappedAssets, coinbaseproStakeWraps, stakeWrapID) return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, coinbaseproTrailingVolume, nil, &resp) + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp) +} + +// GetWrappedAssetByID returns details of a single wrapped asset +func (c *CoinbasePro) GetWrappedAssetByID(ctx context.Context, wrappedAssetID string) (WrappedAssetResponse, error) { + var resp WrappedAssetResponse + + if wrappedAssetID == "" { + return resp, errors.New("wrapped asset id cannot be empty") + } + + path := fmt.Sprintf("%s/%s", coinbaseproWrappedAssets, wrappedAssetID) + + return resp, c.SendHTTPRequest(ctx, exchange.RestSpot, path, &resp) +} + +// GetWrappedAssetConversionRate returns the conversion rate for a wrapped asset +func (c *CoinbasePro) GetWrappedAssetConversionRate(ctx context.Context, wrappedAssetID string) (WrappedAssetConversionRate, error) { + var resp WrappedAssetConversionRate + + if wrappedAssetID == "" { + return resp, errors.New("wrapped asset id cannot be empty") + } + + path := fmt.Sprintf("%s/%s/%s", coinbaseproWrappedAssets, wrappedAssetID, coinbaseproConversionRate) + + return resp, c.SendHTTPRequest(ctx, exchange.RestSpot, path, &resp) } +// // GetCurrentServerTime returns the API server time +// func (c *CoinbasePro) GetCurrentServerTime(ctx context.Context) (ServerTime, error) { +// serverTime := ServerTime{} +// return serverTime, c.SendHTTPRequest(ctx, exchange.RestSpot, coinbaseproTime, &serverTime) +// } + +// // MarginTransfer sends funds between a standard/default profile and a margin +// // profile. +// // A deposit will transfer funds from the default profile into the margin +// // profile. A withdraw will transfer funds from the margin profile to the +// // default profile. Withdraws will fail if they would set your margin ratio +// // below the initial margin ratio requirement. +// // +// // amount - the amount to transfer between the default and margin profile +// // transferType - either "deposit" or "withdraw" +// // profileID - The id of the margin profile to deposit or withdraw from +// // currency - currency to transfer, currently on "BTC" or "USD" +// func (c *CoinbasePro) MarginTransfer(ctx context.Context, amount float64, transferType, profileID, currency string) (MarginTransfer, error) { +// resp := MarginTransfer{} +// req := make(map[string]interface{}) +// req["type"] = transferType +// req["amount"] = strconv.FormatFloat(amount, 'f', -1, 64) +// req["currency"] = currency +// req["margin_profile_id"] = profileID + +// return resp, +// c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproMarginTransfer, req, &resp) +// } + +// // GetPosition returns an overview of account profile. +// func (c *CoinbasePro) GetPosition(ctx context.Context) (AccountOverview, error) { +// resp := AccountOverview{} + +// return resp, +// c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, coinbaseproPosition, nil, &resp) +// } + +// // ClosePosition closes a position and allowing you to repay position as well +// // repayOnly - allows the position to be repaid +// func (c *CoinbasePro) ClosePosition(ctx context.Context, repayOnly bool) (AccountOverview, error) { +// resp := AccountOverview{} +// req := make(map[string]interface{}) +// req["repay_only"] = repayOnly + +// return resp, +// c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproPositionClose, req, &resp) +// } + +// // GetReportStatus once a report request has been accepted for processing, the +// // status is available by polling the report resource endpoint. +// func (c *CoinbasePro) GetReportStatus(ctx context.Context, reportID string) (Report, error) { +// resp := Report{} +// path := fmt.Sprintf("%s/%s", coinbaseproReports, reportID) + +// return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp) +// } + +// // GetTrailingVolume this request will return your 30-day trailing volume for +// // all products. +// func (c *CoinbasePro) GetTrailingVolume(ctx context.Context) ([]Volume, error) { +// var resp []Volume + +// return resp, +// c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, coinbaseproTrailingVolume, nil, &resp) +// } + // SendHTTPRequest sends an unauthenticated HTTP request func (c *CoinbasePro) SendHTTPRequest(ctx context.Context, ep exchange.URL, path string, result interface{}) error { endpoint, err := c.API.Endpoints.GetURL(ep) @@ -950,6 +1123,8 @@ func (c *CoinbasePro) SendAuthenticatedHTTPRequest(ctx context.Context, ep excha return err } + returnHead := http.Header{} + newRequest := func() (*request.Item, error) { payload := []byte("") if params != nil { @@ -979,98 +1154,96 @@ func (c *CoinbasePro) SendAuthenticatedHTTPRequest(ctx context.Context, ep excha headers["Content-Type"] = "application/json" return &request.Item{ - Method: method, - Path: endpoint + path, - Headers: headers, - Body: bytes.NewBuffer(payload), - Result: result, - Verbose: c.Verbose, - HTTPDebugging: c.HTTPDebugging, - HTTPRecording: c.HTTPRecording, + Method: method, + Path: endpoint + path, + Headers: headers, + Body: bytes.NewBuffer(payload), + Result: result, + Verbose: c.Verbose, + HTTPDebugging: c.HTTPDebugging, + HTTPRecording: c.HTTPRecording, + HeaderResponse: &returnHead, }, nil } - return c.SendPayload(ctx, request.Unset, newRequest, request.AuthenticatedRequest) + err = c.SendPayload(ctx, request.Unset, newRequest, request.AuthenticatedRequest) + fmt.Printf("Header returned: %+v\n", returnHead) + return err } -// GetFee returns an estimate of fee based on type of transaction -func (c *CoinbasePro) GetFee(ctx context.Context, feeBuilder *exchange.FeeBuilder) (float64, error) { - var fee float64 - switch feeBuilder.FeeType { - case exchange.CryptocurrencyTradeFee: - trailingVolume, err := c.GetTrailingVolume(ctx) - if err != nil { - return 0, err - } - fee = c.calculateTradingFee(trailingVolume, - feeBuilder.Pair.Base, - feeBuilder.Pair.Quote, - feeBuilder.Pair.Delimiter, - feeBuilder.PurchasePrice, - feeBuilder.Amount, - feeBuilder.IsMaker) - case exchange.InternationalBankWithdrawalFee: - fee = getInternationalBankWithdrawalFee(feeBuilder.FiatCurrency) - case exchange.InternationalBankDepositFee: - fee = getInternationalBankDepositFee(feeBuilder.FiatCurrency) - case exchange.OfflineTradeFee: - fee = getOfflineTradeFee(feeBuilder.PurchasePrice, feeBuilder.Amount) - } - - if fee < 0 { - fee = 0 - } - - return fee, nil -} - -// getOfflineTradeFee calculates the worst case-scenario trading fee -func getOfflineTradeFee(price, amount float64) float64 { - return 0.0025 * price * amount -} - -func (c *CoinbasePro) calculateTradingFee(trailingVolume []Volume, base, quote currency.Code, delimiter string, purchasePrice, amount float64, isMaker bool) float64 { - var fee float64 - for _, i := range trailingVolume { - if strings.EqualFold(i.ProductID, base.String()+delimiter+quote.String()) { - switch { - case isMaker: - fee = 0 - case i.Volume <= 10000000: - fee = 0.003 - case i.Volume > 10000000 && i.Volume <= 100000000: - fee = 0.002 - case i.Volume > 100000000: - fee = 0.001 - } - break - } - } - return fee * amount * purchasePrice -} +// // GetFee returns an estimate of fee based on type of transaction +// func (c *CoinbasePro) GetFee(ctx context.Context, feeBuilder *exchange.FeeBuilder) (float64, error) { +// var fee float64 +// switch feeBuilder.FeeType { +// case exchange.CryptocurrencyTradeFee: +// fees, err := c.GetFees(ctx) +// if err != nil { +// fee = fees.TakerFeeRate +// } else { +// fee = 0.006 +// } +// case exchange.InternationalBankWithdrawalFee: +// fee = getInternationalBankWithdrawalFee(feeBuilder.FiatCurrency) +// case exchange.InternationalBankDepositFee: +// fee = getInternationalBankDepositFee(feeBuilder.FiatCurrency) +// case exchange.OfflineTradeFee: +// fee = getOfflineTradeFee(feeBuilder.PurchasePrice, feeBuilder.Amount) +// } + +// if fee < 0 { +// fee = 0 +// } + +// return fee, nil +// } -func getInternationalBankWithdrawalFee(c currency.Code) float64 { - var fee float64 +// // getOfflineTradeFee calculates the worst case-scenario trading fee +// func getOfflineTradeFee(price, amount float64) float64 { +// return 0.0025 * price * amount +// } - if c.Equal(currency.USD) { - fee = 25 - } else if c.Equal(currency.EUR) { - fee = 0.15 - } +// func (c *CoinbasePro) calculateTradingFee(trailingVolume []Volume, base, quote currency.Code, delimiter string, purchasePrice, amount float64, isMaker bool) float64 { +// var fee float64 +// for _, i := range trailingVolume { +// if strings.EqualFold(i.ProductID, base.String()+delimiter+quote.String()) { +// switch { +// case isMaker: +// fee = 0 +// case i.Volume <= 10000000: +// fee = 0.003 +// case i.Volume > 10000000 && i.Volume <= 100000000: +// fee = 0.002 +// case i.Volume > 100000000: +// fee = 0.001 +// } +// break +// } +// } +// return fee * amount * purchasePrice +// } - return fee -} +// func getInternationalBankWithdrawalFee(c currency.Code) float64 { +// var fee float64 -func getInternationalBankDepositFee(c currency.Code) float64 { - var fee float64 +// if c.Equal(currency.USD) { +// fee = 25 +// } else if c.Equal(currency.EUR) { +// fee = 0.15 +// } - if c.Equal(currency.USD) { - fee = 10 - } else if c.Equal(currency.EUR) { - fee = 0.15 - } +// return fee +// } - return fee -} +// func getInternationalBankDepositFee(c currency.Code) float64 { +// var fee float64 + +// if c.Equal(currency.USD) { +// fee = 10 +// } else if c.Equal(currency.EUR) { +// fee = 0.15 +// } + +// return fee +// } // PrepareDSL adds the direction, step, and limit queries for pagination func (p *Params) PrepareDSL(direction, step string, limit int64) { @@ -1098,6 +1271,7 @@ func (p *Params) PrepareDateString(startDate, endDate time.Time) error { return err } +// PrepareProfIDAndProdID encodes a set of parameters indicating profile and product IDs func (p *Params) PrepareProfIDAndProdID(profileID, currencyPair string) { p.urlVals.Set("profile_id", profileID) p.urlVals.Set("product_id", currencyPair) @@ -1173,10 +1347,3 @@ func OrderbookHelper(iOD InterOrderDetail, level int32) ([]GenOrderDetail, error return gOD, nil } - -// func (c *CoinbasePro) GetTravelRules(ctx context.Context) ([]TravelRule, error) { -// var resp []TravelRule -// accounts, err := c.GetAccounts(ctx) -// path := fmt.Sprintf("/%s/", accounts[0].ID) -// return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp) -// } diff --git a/exchanges/coinbasepro/coinbasepro_test.go b/exchanges/coinbasepro/coinbasepro_test.go index fbc779de085..f86d37b95dd 100644 --- a/exchanges/coinbasepro/coinbasepro_test.go +++ b/exchanges/coinbasepro/coinbasepro_test.go @@ -13,18 +13,15 @@ import ( "time" "github.com/gorilla/websocket" + "github.com/stretchr/testify/assert" "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/common/convert" "github.com/thrasher-corp/gocryptotrader/config" - "github.com/thrasher-corp/gocryptotrader/core" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" - "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues" "github.com/thrasher-corp/gocryptotrader/exchanges/stream" - "github.com/thrasher-corp/gocryptotrader/portfolio/banking" - "github.com/thrasher-corp/gocryptotrader/portfolio/withdraw" ) var ( @@ -34,11 +31,11 @@ var ( // Please supply your APIKeys here for better testing const ( - apiKey = "" - apiSecret = "" - clientID = "" // passphrase you made at API CREATION - canManipulateRealOrders = false - testingInSandbox = false + apiKey = "2a77ecc0a721e8b7b481d6a2fcdeea5d" + apiSecret = "m/Ty7P9WvZsTn7Qu/hUqDGy85v1ow8irUSQRgSQ7jEag2QU0evyHjUr3Jv7bNpYG3iLHl92zHYe+GuDYGWGXxw==" + clientID = "se9b47msd8" // passphrase you made at API CREATION + canManipulateRealOrders = true + testingInSandbox = true ) func TestMain(m *testing.M) { @@ -57,16 +54,19 @@ func TestMain(m *testing.M) { if err != nil { log.Fatal("coinbasepro Setup() init error") } - gdxConfig.API.Credentials.Key = apiKey - gdxConfig.API.Credentials.Secret = apiSecret - gdxConfig.API.Credentials.ClientID = clientID - gdxConfig.API.AuthenticatedSupport = true - gdxConfig.API.AuthenticatedWebsocketSupport = true + if apiKey != "" { + gdxConfig.API.Credentials.Key = apiKey + gdxConfig.API.Credentials.Secret = apiSecret + gdxConfig.API.Credentials.ClientID = clientID + gdxConfig.API.AuthenticatedSupport = true + gdxConfig.API.AuthenticatedWebsocketSupport = true + } c.Websocket = sharedtestvalues.NewTestWebsocket() err = c.Setup(gdxConfig) if err != nil { log.Fatal("CoinbasePro setup error", err) } + c.Verbose = true os.Exit(m.Run()) } @@ -84,56 +84,48 @@ func TestStart(t *testing.T) { testWg.Wait() } -func TestGetAccounts(t *testing.T) { +func TestGetAllAccounts(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - resp, err := c.GetAccounts(context.Background()) + resp, err := c.GetAllAccounts(context.Background()) if err != nil { - t.Error("CoinBasePro GetAccounts() error", err) - } - if resp[0].ID == "" { - t.Error("CoinBasePro GetAccounts() error, no ID returned") - } - if resp[0].Currency == "" { - t.Error("CoinBasePro GetAccounts() error, no Currency returned") + t.Error("CoinBasePro GetAllAccounts() error", err) } - if resp[0].ProfileID == "" { - t.Error("CoinBasePro GetAccounts() error, no ProfileID returned") - } - + assert.NotEmpty(t, resp, "CoinBasePro GetAllAccounts() error, expected a non-empty response") } -func TestGetAccount(t *testing.T) { +func TestGetAccountByID(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - longResp, err := c.GetAccounts(context.Background()) + longResp, err := c.GetAllAccounts(context.Background()) if err != nil { - t.Error("CoinBasePro GetAccounts() error", err) + t.Error("CoinBasePro GetAllAccounts() error", err) } - shortResp, err := c.GetAccount(context.Background(), longResp[0].ID) + shortResp, err := c.GetAccountByID(context.Background(), longResp[0].ID) if err != nil { - t.Error("CoinBasePro GetAccount() error", err) + t.Error("CoinBasePro GetAccountByID() error", err) } if *shortResp != longResp[0] { - t.Error("CoinBasePro GetAccount() error, mismatched responses") + t.Error("CoinBasePro GetAccountByID() error, mismatched responses") } } func TestGetHolds(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - accID, err := c.GetAccounts(context.Background()) + accID, err := c.GetAllAccounts(context.Background()) if err != nil { - t.Error("CoinBasePro GetAccounts() error", err) + t.Error("CoinBasePro GetAllAccounts() error", err) } - _, err = c.GetHolds(context.Background(), accID[0].ID, pageNone, "", 2) + resp, err := c.GetHolds(context.Background(), accID[1].ID, pageNone, "1", 2) if err != nil { t.Error("CoinBasePro GetHolds() error", err) } + log.Printf("%+v", resp) } func TestGetAccountLedger(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - accID, err := c.GetAccounts(context.Background()) + accID, err := c.GetAllAccounts(context.Background()) if err != nil { - t.Error("CoinBasePro GetAccounts() error", err) + t.Error("CoinBasePro GetAllAccounts() error", err) } _, err = c.GetAccountLedger(context.Background(), accID[0].ID, pageBefore, "", "a", time.Unix(1, 1), time.Now(), 3) @@ -144,9 +136,9 @@ func TestGetAccountLedger(t *testing.T) { func TestGetAccountTransfers(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - accID, err := c.GetAccounts(context.Background()) + accID, err := c.GetAllAccounts(context.Background()) if err != nil { - t.Error("CoinBasePro GetAccounts() error", err) + t.Error("CoinBasePro GetAllAccounts() error", err) } _, err = c.GetAccountTransfers(context.Background(), accID[0].ID, "", "", "", 3) if err != nil { @@ -186,7 +178,7 @@ func TestDeleteAddress(t *testing.T) { func TestGetCoinbaseWallets(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - _, err := c.GetCoinbaseAccounts(context.Background()) + _, err := c.GetCoinbaseWallets(context.Background()) if err != nil { t.Error("CoinBasePro GetCoinbaseAccounts() error", err) } @@ -196,7 +188,7 @@ func TestGenerateCryptoAddress(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) accID, err := c.GetCoinbaseWallets(context.Background()) if err != nil { - t.Error("CoinBasePro GetAccounts() error", err) + t.Error("CoinBasePro GetAllAccounts() error", err) } _, err = c.GenerateCryptoAddress(context.Background(), accID[0].ID, "", "") if err != nil { @@ -220,20 +212,20 @@ func TestGetConversionByID(t *testing.T) { } } -func TestGetCurrencies(t *testing.T) { - _, err := c.GetCurrencies(context.Background()) +func TestGetAllCurrencies(t *testing.T) { + _, err := c.GetAllCurrencies(context.Background()) if err != nil { - t.Error("GetCurrencies() error", err) + t.Error("GetAllCurrencies() error", err) } } -func TestGetCurrenciesByID(t *testing.T) { - resp, err := c.GetCurrenciesByID(context.Background(), "BTC") +func TestGetCurrencyByID(t *testing.T) { + resp, err := c.GetCurrencyByID(context.Background(), "BTC") if err != nil { - t.Error("GetCurrenciesByID() error", err) + t.Error("GetCurrencyByID() error", err) } if resp.Name != "Bitcoin" { - t.Errorf("GetCurrenciesByID() error, incorrect name returned, expected 'Bitcoin', got '%s'", + t.Errorf("GetCurrencyByID() error, incorrect name returned, expected 'Bitcoin', got '%s'", resp.Name) } } @@ -313,12 +305,11 @@ func TestGetWithdrawalFeeEstimate(t *testing.T) { if err == nil { t.Error("CoinBasePro GetWithdrawalFeeEstimate() error, expected error due to empty field") } - resp, err := c.GetWithdrawalFeeEstimate(context.Background(), "This test is not", "yet implemented", + _, err = c.GetWithdrawalFeeEstimate(context.Background(), "This test is not", "yet implemented", "due to not knowing a valid network string") if err == nil { t.Error("This should have errored out due to an improper network string") } - log.Printf("%+v, %+v", resp, err) } func TestWithdrawViaPaymentMethod(t *testing.T) { @@ -358,15 +349,14 @@ func TestGetFills(t *testing.T) { } } -func TestGetOrders(t *testing.T) { +func TestGetAllOrders(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) status := []string{"open", "pending", "active", "done"} - resp, err := c.GetOrders(context.Background(), "", "", "", "", "", "", "", time.Unix(1, 1), time.Now(), 5, status) + _, err := c.GetAllOrders(context.Background(), "", "", "", "", "", "", "", time.Unix(1, 1), time.Now(), 5, status) if err != nil { - t.Error("CoinBasePro GetOrders() error", err) + t.Error("CoinBasePro GetAllOrders() error", err) } - log.Printf("%+v", resp) } func TestCancelAllExistingOrders(t *testing.T) { @@ -379,19 +369,21 @@ func TestCancelAllExistingOrders(t *testing.T) { func TestPlaceOrder(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) - _, err := c.PlaceOrder(context.Background(), "this", "test", "has", "not", "been", "implemented", "awaiting", "sandbox", - "testing", 1, 1, 1, 1, false) - if err == nil { - t.Error("This probably should have errored out due to not being implemented") + + for x := 0; x < 550; x++ { + _, _ = c.PlaceOrder(context.Background(), "this", "", "sell", "BTC-USD", "", "implemented", "", "sandbox", + "testing", 0, 2<<30, 1, 0, false) + } + + // log.Printf("Response: %+v\nError: %+v", resp, err) } -func TestGetOrder(t *testing.T) { +func TestGetOrderByID(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - _, err := c.GetOrder(context.Background(), "This test is not", "yet implemented", true) - if err == nil { - t.Error("This should have errored out due to not being implemented") - } + resp, err := c.GetOrderByID(context.Background(), "940d4bf3-933b-4714-a702-155f82c3e739", "spot", false) + + log.Printf("Response: %+v\nError: %+v", resp, err) } func TestCancelExistingOrder(t *testing.T) { @@ -413,27 +405,27 @@ func TestGetSignedPrices(t *testing.T) { } } -func TestGetProducts(t *testing.T) { - resp, err := c.GetProducts(context.Background(), "") +func TestGetAllProducts(t *testing.T) { + resp, err := c.GetAllProducts(context.Background(), "") if err != nil { - t.Error("Coinbase, GetProducts() Error:", err) + t.Error("Coinbase, GetAllProducts() Error:", err) } if resp[0].ID == "" { - t.Error("Coinbase, GetProducts() Error, expected non-empty string") + t.Error("Coinbase, GetAllProducts() Error, expected non-empty string") } } -func TestGetProduct(t *testing.T) { - _, err := c.GetProduct(context.Background(), "") +func TestGetProductByID(t *testing.T) { + _, err := c.GetProductByID(context.Background(), "") if err == nil { - t.Error("Coinbase, GetProduct() Error, expected an error due to nonexistent pair") + t.Error("Coinbase, GetProductByID() Error, expected an error due to nonexistent pair") } - resp, err := c.GetProduct(context.Background(), "BTC-USD") + resp, err := c.GetProductByID(context.Background(), "BTC-USD") if err != nil { - t.Error("Coinbase, GetProduct() Error:", err) + t.Error("Coinbase, GetProductByID() Error:", err) } if resp.ID != "BTC-USD" { - t.Error("Coinbase, GetProduct() Error, expected BTC-USD") + t.Error("Coinbase, GetProductByID() Error, expected BTC-USD") } } @@ -515,12 +507,12 @@ func TestGetTrades(t *testing.T) { } } -func TestGetProfiles(t *testing.T) { +func TestGetAllProfiles(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) active := true - _, err := c.GetProfiles(context.Background(), &active) + _, err := c.GetAllProfiles(context.Background(), &active) if err != nil { - t.Error("GetProfiles() error", err) + t.Error("GetAllProfiles() error", err) } } @@ -551,9 +543,9 @@ func TestTransferBetweenProfiles(t *testing.T) { func TestGetProfileByID(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - resp, err := c.GetProfiles(context.Background(), nil) + resp, err := c.GetAllProfiles(context.Background(), nil) if err != nil { - t.Error("GetProfiles() error", err) + t.Error("GetAllProfiles() error", err) } active := true resp2, err := c.GetProfileByID(context.Background(), resp[0].ID, &active) @@ -591,526 +583,665 @@ func TestDeleteProfile(t *testing.T) { func TestGetReport(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - prof, err := c.GetProfiles(context.Background(), nil) + prof, err := c.GetAllProfiles(context.Background(), nil) if err != nil { - t.Error("GetProfiles() error", err) + t.Error("GetAllProfiles() error", err) } - c.Verbose = true - resp, err := c.GetReports(context.Background(), prof[0].ID, "account", time.Time{}, 1000, false) - log.Printf("%+v", resp) + _, err = c.GetAllReports(context.Background(), prof[0].ID, "account", time.Time{}, 1000, false) if err != nil { - t.Error("GetReport() error", err) + t.Error("GetAllReports() error", err) } } -func TestGetCurrentServerTime(t *testing.T) { - _, err := c.GetCurrentServerTime(context.Background()) +func TestCreateReport(t *testing.T) { + sharedtestvalues.SkipTestIfCannotManipulateOrders(t, c, canManipulateRealOrders) + prof, err := c.GetAllProfiles(context.Background(), nil) if err != nil { - t.Error("GetServerTime() error", err) + t.Error("GetAllProfiles() error", err) + } + _, err = c.CreateReport(context.Background(), "this", "test", "is", "not", prof[0].ID, "yet", "implemented", + time.Time{}, time.Time{}, time.Time{}) + if err == nil { + t.Error("Coinbase, CreateReport() Error, expected an error due to un-implemented test") } } -func TestWrapperGetServerTime(t *testing.T) { - t.Parallel() - st, err := c.GetServerTime(context.Background(), asset.Spot) - if !errors.Is(err, nil) { - t.Fatalf("received: '%v' but expected: '%v'", err, nil) +func TestGetReportByID(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + prof, err := c.GetAllProfiles(context.Background(), nil) + if err != nil { + t.Error("GetAllProfiles() error", err) } - - if st.IsZero() { - t.Fatal("expected a time") + resp, err := c.GetAllReports(context.Background(), prof[0].ID, "account", time.Time{}, 1000, false) + if err != nil { + t.Error("GetAllReports() error", err) + } + if len(resp) == 0 { + t.Log("No reports found, skipping test") + } else { + _, err = c.GetReportByID(context.Background(), resp[0].ID) + if err != nil { + t.Error("GetReportByID() error", err) + } } } -func TestAuthRequests(t *testing.T) { - t.Parallel() +func TestGetTravelRules(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - - _, err := c.GetAccounts(context.Background()) + _, err := c.GetTravelRules(context.Background(), "", "", "", 0) if err != nil { - t.Error("GetAccounts() error", err) - } - accountResponse, err := c.GetAccount(context.Background(), - "13371337-1337-1337-1337-133713371337") - if accountResponse.ID != "" { - t.Error("Expecting no data returned") + t.Error("GetTravelRules() error", err) } +} + +func TestCreateTravelRule(t *testing.T) { + sharedtestvalues.SkipTestIfCannotManipulateOrders(t, c, canManipulateRealOrders) + _, err := c.CreateTravelRule(context.Background(), "this test", "not yet", "implemented") if err == nil { - t.Error("Expecting error") + t.Error("Coinbase, CreateTravelRule() Error, expected an error due to unimplemented test") } +} + +func TestDeleteTravelRule(t *testing.T) { + sharedtestvalues.SkipTestIfCannotManipulateOrders(t, c, canManipulateRealOrders) + err := c.DeleteTravelRule(context.Background(), "this test is not yet implemented") if err == nil { - t.Error("Expecting error") + t.Error("Coinbase, DeleteTravelRule() Error, expected an error due to unimplemented test") } - // getHoldsResponse, err := c.GetHolds(context.Background(), - // "13371337-1337-1337-1337-133713371337") - // if len(getHoldsResponse) > 0 { - // t.Error("Expecting no data returned") - // } - if err == nil { - t.Error("Expecting error") +} + +func TestGetExchangeLimits(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + acc, err := c.GetAllAccounts(context.Background()) + if err != nil { + t.Error("GetAllAccounts() error", err) } - marginTransferResponse, err := c.MarginTransfer(context.Background(), - 1, "withdraw", "13371337-1337-1337-1337-133713371337", "BTC") - if marginTransferResponse.ID != "" { - t.Error("Expecting no data returned") + _, err = c.GetExchangeLimits(context.Background(), acc[0].ID) + if err != nil { + t.Error("GetExchangeLimits() error", err) } +} + +func TestUpdateSettlementPreference(t *testing.T) { + sharedtestvalues.SkipTestIfCannotManipulateOrders(t, c, canManipulateRealOrders) + _, err := c.UpdateSettlementPreference(context.Background(), "this test", "not implemented") if err == nil { - t.Error("Expecting error") + t.Error("Coinbase, UpdateSettlementPreference() Error, expected an error due to unimplemented test") } - _, err = c.GetPosition(context.Background()) - if err == nil { - t.Error("Expecting error") +} + +func TestGetAllWrappedAssets(t *testing.T) { + _, err := c.GetAllWrappedAssets(context.Background()) + if err != nil { + t.Error("GetAllWrappedAssets() error", err) } - _, err = c.ClosePosition(context.Background(), false) - if err == nil { - t.Error("Expecting error") +} + +func TestGetAllStakeWraps(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + _, err := c.GetAllStakeWraps(context.Background(), "", "ETH", "CBETH", "", time.Time{}, 1) + if err != nil { + t.Error("GetAllStakeWraps() error", err) } - _, err = c.GetCoinbaseAccounts(context.Background()) + _, err = c.GetAllStakeWraps(context.Background(), "after", "ETH", "CBETH", "", time.Unix(1, 1), 1000) if err != nil { - t.Error("GetCoinbaseAccounts() error", err) + t.Error("GetAllStakeWraps() error", err) } } -func setFeeBuilder() *exchange.FeeBuilder { - return &exchange.FeeBuilder{ - Amount: 1, - FeeType: exchange.CryptocurrencyTradeFee, - Pair: testPair, - PurchasePrice: 1, +func TestCreateStakeWrap(t *testing.T) { + sharedtestvalues.SkipTestIfCannotManipulateOrders(t, c, canManipulateRealOrders) + _, err := c.CreateStakeWrap(context.Background(), "", "", 0) + if err == nil { + t.Error("Coinbase, CreateStakeWrap() Error, expected an error due to empty fields") + } + _, err = c.CreateStakeWrap(context.Background(), "this test", "is not implemented", 1) + if err == nil { + t.Error("Coinbase, CreateStakeWrap() Error, expected an error due to unimplemented test") } } -// TestGetFeeByTypeOfflineTradeFee logic test -func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { - var feeBuilder = setFeeBuilder() - _, err := c.GetFeeByType(context.Background(), feeBuilder) +func TestGetStakeWrapByID(t *testing.T) { + resp, err := c.GetAllStakeWraps(context.Background(), "", "ETH", "CBETH", "", time.Time{}, 1) if err != nil { - t.Fatal(err) + t.Error("GetAllStakeWraps() error", err) } - if !sharedtestvalues.AreAPICredentialsSet(c) { - if feeBuilder.FeeType != exchange.OfflineTradeFee { - t.Errorf("Expected %v, received %v", exchange.OfflineTradeFee, feeBuilder.FeeType) - } + if len(resp) == 0 { + t.Log("No stake wraps found, skipping test") } else { - if feeBuilder.FeeType != exchange.CryptocurrencyTradeFee { - t.Errorf("Expected %v, received %v", exchange.CryptocurrencyTradeFee, feeBuilder.FeeType) + _, err = c.GetStakeWrapByID(context.Background(), resp[0].ID) + if err != nil { + t.Error("GetStakeWrapByID() error", err) } } } -func TestGetFee(t *testing.T) { - var feeBuilder = setFeeBuilder() +func TestGetWrappedAssetByID(t *testing.T) { + _, err := c.GetWrappedAssetByID(context.Background(), "") + if err == nil { + t.Error("Coinbase, GetWrappedAssetByID() Error, expected an error due to empty fields") + } + _, err = c.GetWrappedAssetByID(context.Background(), "CBETH") + if err != nil { + t.Error("GetWrappedAssetByID() error", err) + } +} - if sharedtestvalues.AreAPICredentialsSet(c) { - // CryptocurrencyTradeFee Basic - if _, err := c.GetFee(context.Background(), feeBuilder); err != nil { - t.Error(err) - } +func TestGetWrappedAssetConversionRate(t *testing.T) { + _, err := c.GetWrappedAssetConversionRate(context.Background(), "") + if err == nil { + t.Error("Coinbase, GetWrappedAssetConversionRate() Error, expected an error due to empty fields") + } + _, err = c.GetWrappedAssetConversionRate(context.Background(), "CBETH") + if err != nil { + t.Error("GetWrappedAssetConversionRate() error", err) + } +} - // CryptocurrencyTradeFee High quantity - feeBuilder = setFeeBuilder() - feeBuilder.Amount = 1000 - feeBuilder.PurchasePrice = 1000 - if _, err := c.GetFee(context.Background(), feeBuilder); err != nil { - t.Error(err) - } +// func TestGetCurrentServerTime(t *testing.T) { +// _, err := c.GetCurrentServerTime(context.Background()) +// if err != nil { +// t.Error("GetServerTime() error", err) +// } +// } - // CryptocurrencyTradeFee IsMaker - feeBuilder = setFeeBuilder() - feeBuilder.IsMaker = true - if _, err := c.GetFee(context.Background(), feeBuilder); err != nil { - t.Error(err) - } +// func TestWrapperGetServerTime(t *testing.T) { +// t.Parallel() +// st, err := c.GetServerTime(context.Background(), asset.Spot) +// if !errors.Is(err, nil) { +// t.Fatalf("received: '%v' but expected: '%v'", err, nil) +// } - // CryptocurrencyTradeFee Negative purchase price - feeBuilder = setFeeBuilder() - feeBuilder.PurchasePrice = -1000 - if _, err := c.GetFee(context.Background(), feeBuilder); err != nil { - t.Error(err) - } - } +// if st.IsZero() { +// t.Fatal("expected a time") +// } +// } - // CryptocurrencyWithdrawalFee Basic - feeBuilder = setFeeBuilder() - feeBuilder.FeeType = exchange.CryptocurrencyWithdrawalFee - if _, err := c.GetFee(context.Background(), feeBuilder); err != nil { - t.Error(err) - } +// func TestAuthRequests(t *testing.T) { +// t.Parallel() +// sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - // CryptocurrencyDepositFee Basic - feeBuilder = setFeeBuilder() - feeBuilder.FeeType = exchange.CryptocurrencyDepositFee - if _, err := c.GetFee(context.Background(), feeBuilder); err != nil { - t.Error(err) - } +// _, err := c.GetAllAccounts(context.Background()) +// if err != nil { +// t.Error("GetAllAccounts() error", err) +// } +// accountResponse, err := c.GetAccountByID(context.Background(), +// "13371337-1337-1337-1337-133713371337") +// if accountResponse.ID != "" { +// t.Error("Expecting no data returned") +// } +// if err == nil { +// t.Error("Expecting error") +// } +// if err == nil { +// t.Error("Expecting error") +// } +// // getHoldsResponse, err := c.GetHolds(context.Background(), +// // "13371337-1337-1337-1337-133713371337") +// // if len(getHoldsResponse) > 0 { +// // t.Error("Expecting no data returned") +// // } +// if err == nil { +// t.Error("Expecting error") +// } +// marginTransferResponse, err := c.MarginTransfer(context.Background(), +// 1, "withdraw", "13371337-1337-1337-1337-133713371337", "BTC") +// if marginTransferResponse.ID != "" { +// t.Error("Expecting no data returned") +// } +// if err == nil { +// t.Error("Expecting error") +// } +// _, err = c.GetPosition(context.Background()) +// if err == nil { +// t.Error("Expecting error") +// } +// _, err = c.ClosePosition(context.Background(), false) +// if err == nil { +// t.Error("Expecting error") +// } +// } - // InternationalBankDepositFee Basic - feeBuilder = setFeeBuilder() - feeBuilder.FeeType = exchange.InternationalBankDepositFee - feeBuilder.FiatCurrency = currency.EUR - if _, err := c.GetFee(context.Background(), feeBuilder); err != nil { - t.Error(err) - } +// func setFeeBuilder() *exchange.FeeBuilder { +// return &exchange.FeeBuilder{ +// Amount: 1, +// FeeType: exchange.CryptocurrencyTradeFee, +// Pair: testPair, +// PurchasePrice: 1, +// } +// } - // InternationalBankWithdrawalFee Basic - feeBuilder = setFeeBuilder() - feeBuilder.FeeType = exchange.InternationalBankWithdrawalFee - feeBuilder.FiatCurrency = currency.USD - if _, err := c.GetFee(context.Background(), feeBuilder); err != nil { - t.Error(err) - } -} +// // TestGetFeeByTypeOfflineTradeFee logic test +// func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { +// var feeBuilder = setFeeBuilder() +// _, err := c.GetFeeByType(context.Background(), feeBuilder) +// if err != nil { +// t.Fatal(err) +// } +// if !sharedtestvalues.AreAPICredentialsSet(c) { +// if feeBuilder.FeeType != exchange.OfflineTradeFee { +// t.Errorf("Expected %v, received %v", exchange.OfflineTradeFee, feeBuilder.FeeType) +// } +// } else { +// if feeBuilder.FeeType != exchange.CryptocurrencyTradeFee { +// t.Errorf("Expected %v, received %v", exchange.CryptocurrencyTradeFee, feeBuilder.FeeType) +// } +// } +// } -func TestCalculateTradingFee(t *testing.T) { - t.Parallel() - // uppercase - var volume = []Volume{ - { - ProductID: "BTC_USD", - Volume: 100, - }, - } +// func TestGetFee(t *testing.T) { +// var feeBuilder = setFeeBuilder() + +// if sharedtestvalues.AreAPICredentialsSet(c) { +// // CryptocurrencyTradeFee Basic +// if _, err := c.GetFee(context.Background(), feeBuilder); err != nil { +// t.Error(err) +// } + +// // CryptocurrencyTradeFee High quantity +// feeBuilder = setFeeBuilder() +// feeBuilder.Amount = 1000 +// feeBuilder.PurchasePrice = 1000 +// if _, err := c.GetFee(context.Background(), feeBuilder); err != nil { +// t.Error(err) +// } + +// // CryptocurrencyTradeFee IsMaker +// feeBuilder = setFeeBuilder() +// feeBuilder.IsMaker = true +// if _, err := c.GetFee(context.Background(), feeBuilder); err != nil { +// t.Error(err) +// } + +// // CryptocurrencyTradeFee Negative purchase price +// feeBuilder = setFeeBuilder() +// feeBuilder.PurchasePrice = -1000 +// if _, err := c.GetFee(context.Background(), feeBuilder); err != nil { +// t.Error(err) +// } +// } - if resp := c.calculateTradingFee(volume, currency.BTC, currency.USD, "_", 1, 1, false); resp != float64(0.003) { - t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0.003), resp) - } +// // CryptocurrencyWithdrawalFee Basic +// feeBuilder = setFeeBuilder() +// feeBuilder.FeeType = exchange.CryptocurrencyWithdrawalFee +// if _, err := c.GetFee(context.Background(), feeBuilder); err != nil { +// t.Error(err) +// } - // lowercase - volume = []Volume{ - { - ProductID: "btc_usd", - Volume: 100, - }, - } +// // CryptocurrencyDepositFee Basic +// feeBuilder = setFeeBuilder() +// feeBuilder.FeeType = exchange.CryptocurrencyDepositFee +// if _, err := c.GetFee(context.Background(), feeBuilder); err != nil { +// t.Error(err) +// } - if resp := c.calculateTradingFee(volume, currency.BTC, currency.USD, "_", 1, 1, false); resp != float64(0.003) { - t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0.003), resp) - } +// // InternationalBankDepositFee Basic +// feeBuilder = setFeeBuilder() +// feeBuilder.FeeType = exchange.InternationalBankDepositFee +// feeBuilder.FiatCurrency = currency.EUR +// if _, err := c.GetFee(context.Background(), feeBuilder); err != nil { +// t.Error(err) +// } - // mixedCase - volume = []Volume{ - { - ProductID: "btc_USD", - Volume: 100, - }, - } +// // InternationalBankWithdrawalFee Basic +// feeBuilder = setFeeBuilder() +// feeBuilder.FeeType = exchange.InternationalBankWithdrawalFee +// feeBuilder.FiatCurrency = currency.USD +// if _, err := c.GetFee(context.Background(), feeBuilder); err != nil { +// t.Error(err) +// } +// } - if resp := c.calculateTradingFee(volume, currency.BTC, currency.USD, "_", 1, 1, false); resp != float64(0.003) { - t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0.003), resp) - } +// func TestCalculateTradingFee(t *testing.T) { +// t.Parallel() +// // uppercase +// var volume = []Volume{ +// { +// ProductID: "BTC_USD", +// Volume: 100, +// }, +// } - // medium volume - volume = []Volume{ - { - ProductID: "btc_USD", - Volume: 10000001, - }, - } +// if resp := c.calculateTradingFee(volume, currency.BTC, currency.USD, "_", 1, 1, false); resp != float64(0.003) { +// t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0.003), resp) +// } - if resp := c.calculateTradingFee(volume, currency.BTC, currency.USD, "_", 1, 1, false); resp != float64(0.002) { - t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0.002), resp) - } +// // lowercase +// volume = []Volume{ +// { +// ProductID: "btc_usd", +// Volume: 100, +// }, +// } - // high volume - volume = []Volume{ - { - ProductID: "btc_USD", - Volume: 100000010000, - }, - } +// if resp := c.calculateTradingFee(volume, currency.BTC, currency.USD, "_", 1, 1, false); resp != float64(0.003) { +// t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0.003), resp) +// } - if resp := c.calculateTradingFee(volume, currency.BTC, currency.USD, "_", 1, 1, false); resp != float64(0.001) { - t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0.001), resp) - } +// // mixedCase +// volume = []Volume{ +// { +// ProductID: "btc_USD", +// Volume: 100, +// }, +// } - // no match - volume = []Volume{ - { - ProductID: "btc_beeteesee", - Volume: 100000010000, - }, - } +// if resp := c.calculateTradingFee(volume, currency.BTC, currency.USD, "_", 1, 1, false); resp != float64(0.003) { +// t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0.003), resp) +// } - if resp := c.calculateTradingFee(volume, currency.BTC, currency.USD, "_", 1, 1, false); resp != float64(0) { - t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) - } +// // medium volume +// volume = []Volume{ +// { +// ProductID: "btc_USD", +// Volume: 10000001, +// }, +// } - // taker - volume = []Volume{ - { - ProductID: "btc_USD", - Volume: 100000010000, - }, - } +// if resp := c.calculateTradingFee(volume, currency.BTC, currency.USD, "_", 1, 1, false); resp != float64(0.002) { +// t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0.002), resp) +// } - if resp := c.calculateTradingFee(volume, currency.BTC, currency.USD, "_", 1, 1, true); resp != float64(0) { - t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) - } -} +// // high volume +// volume = []Volume{ +// { +// ProductID: "btc_USD", +// Volume: 100000010000, +// }, +// } -func TestFormatWithdrawPermissions(t *testing.T) { - expectedResult := exchange.AutoWithdrawCryptoWithAPIPermissionText + " & " + exchange.AutoWithdrawFiatWithAPIPermissionText - withdrawPermissions := c.FormatWithdrawPermissions() - if withdrawPermissions != expectedResult { - t.Errorf("Expected: %s, Received: %s", expectedResult, withdrawPermissions) - } -} +// if resp := c.calculateTradingFee(volume, currency.BTC, currency.USD, "_", 1, 1, false); resp != float64(0.001) { +// t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0.001), resp) +// } -func TestGetActiveOrders(t *testing.T) { - var getOrdersRequest = order.MultiOrderRequest{ - Type: order.AnyType, - AssetType: asset.Spot, - Pairs: []currency.Pair{testPair}, - Side: order.AnySide, - } +// // no match +// volume = []Volume{ +// { +// ProductID: "btc_beeteesee", +// Volume: 100000010000, +// }, +// } - _, err := c.GetActiveOrders(context.Background(), &getOrdersRequest) - if sharedtestvalues.AreAPICredentialsSet(c) && err != nil { - t.Errorf("Could not get open orders: %s", err) - } else if !sharedtestvalues.AreAPICredentialsSet(c) && err == nil { - t.Error("Expecting an error when no keys are set") - } -} +// if resp := c.calculateTradingFee(volume, currency.BTC, currency.USD, "_", 1, 1, false); resp != float64(0) { +// t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) +// } -func TestGetOrderHistory(t *testing.T) { - var getOrdersRequest = order.MultiOrderRequest{ - Type: order.AnyType, - AssetType: asset.Spot, - Pairs: []currency.Pair{testPair}, - Side: order.AnySide, - } +// // taker +// volume = []Volume{ +// { +// ProductID: "btc_USD", +// Volume: 100000010000, +// }, +// } - _, err := c.GetOrderHistory(context.Background(), &getOrdersRequest) - if sharedtestvalues.AreAPICredentialsSet(c) && err != nil { - t.Errorf("Could not get order history: %s", err) - } else if !sharedtestvalues.AreAPICredentialsSet(c) && err == nil { - t.Error("Expecting an error when no keys are set") - } +// if resp := c.calculateTradingFee(volume, currency.BTC, currency.USD, "_", 1, 1, true); resp != float64(0) { +// t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) +// } +// } - getOrdersRequest.Pairs = []currency.Pair{} - _, err = c.GetOrderHistory(context.Background(), &getOrdersRequest) - if sharedtestvalues.AreAPICredentialsSet(c) && err != nil { - t.Errorf("Could not get order history: %s", err) - } else if !sharedtestvalues.AreAPICredentialsSet(c) && err == nil { - t.Error("Expecting an error when no keys are set") - } +// func TestFormatWithdrawPermissions(t *testing.T) { +// expectedResult := exchange.AutoWithdrawCryptoWithAPIPermissionText + " & " + exchange.AutoWithdrawFiatWithAPIPermissionText +// withdrawPermissions := c.FormatWithdrawPermissions() +// if withdrawPermissions != expectedResult { +// t.Errorf("Expected: %s, Received: %s", expectedResult, withdrawPermissions) +// } +// } - getOrdersRequest.Pairs = nil - _, err = c.GetOrderHistory(context.Background(), &getOrdersRequest) - if sharedtestvalues.AreAPICredentialsSet(c) && err != nil { - t.Errorf("Could not get order history: %s", err) - } else if !sharedtestvalues.AreAPICredentialsSet(c) && err == nil { - t.Error("Expecting an error when no keys are set") - } -} +// func TestGetActiveOrders(t *testing.T) { +// var getOrdersRequest = order.MultiOrderRequest{ +// Type: order.AnyType, +// AssetType: asset.Spot, +// Pairs: []currency.Pair{testPair}, +// Side: order.AnySide, +// } -// Any tests below this line have the ability to impact your orders on the exchange. Enable canManipulateRealOrders to run them -// ---------------------------------------------------------------------------------------------------------------------------- +// _, err := c.GetActiveOrders(context.Background(), &getOrdersRequest) +// if sharedtestvalues.AreAPICredentialsSet(c) && err != nil { +// t.Errorf("Could not get open orders: %s", err) +// } else if !sharedtestvalues.AreAPICredentialsSet(c) && err == nil { +// t.Error("Expecting an error when no keys are set") +// } +// } -func TestSubmitOrder(t *testing.T) { - t.Parallel() - sharedtestvalues.SkipTestIfCannotManipulateOrders(t, c, canManipulateRealOrders) +// func TestGetOrderHistory(t *testing.T) { +// var getOrdersRequest = order.MultiOrderRequest{ +// Type: order.AnyType, +// AssetType: asset.Spot, +// Pairs: []currency.Pair{testPair}, +// Side: order.AnySide, +// } - // limit order - var orderSubmission = &order.Submit{ - Exchange: c.Name, - Pair: currency.Pair{ - Delimiter: "-", - Base: currency.BTC, - Quote: currency.USD, - }, - Side: order.Buy, - Type: order.Limit, - Price: 1, - Amount: 0.001, - ClientID: "meowOrder", - AssetType: asset.Spot, - } - response, err := c.SubmitOrder(context.Background(), orderSubmission) - if sharedtestvalues.AreAPICredentialsSet(c) && (err != nil || response.Status != order.New) { - t.Errorf("Order failed to be placed: %v", err) - } else if !sharedtestvalues.AreAPICredentialsSet(c) && err == nil { - t.Error("Expecting an error when no keys are set") - } - - // market order from amount - orderSubmission = &order.Submit{ - Exchange: c.Name, - Pair: currency.Pair{ - Delimiter: "-", - Base: currency.BTC, - Quote: currency.USD, - }, - Side: order.Buy, - Type: order.Market, - Amount: 0.001, - ClientID: "meowOrder", - AssetType: asset.Spot, - } - response, err = c.SubmitOrder(context.Background(), orderSubmission) - if sharedtestvalues.AreAPICredentialsSet(c) && (err != nil || response.Status != order.New) { - t.Errorf("Order failed to be placed: %v", err) - } else if !sharedtestvalues.AreAPICredentialsSet(c) && err == nil { - t.Error("Expecting an error when no keys are set") - } - - // market order from quote amount - orderSubmission = &order.Submit{ - Exchange: c.Name, - Pair: currency.Pair{ - Delimiter: "-", - Base: currency.BTC, - Quote: currency.USD, - }, - Side: order.Buy, - Type: order.Market, - QuoteAmount: 1, - ClientID: "meowOrder", - AssetType: asset.Spot, - } - response, err = c.SubmitOrder(context.Background(), orderSubmission) - if sharedtestvalues.AreAPICredentialsSet(c) && (err != nil || response.Status != order.New) { - t.Errorf("Order failed to be placed: %v", err) - } else if !sharedtestvalues.AreAPICredentialsSet(c) && err == nil { - t.Error("Expecting an error when no keys are set") - } -} +// _, err := c.GetOrderHistory(context.Background(), &getOrdersRequest) +// if sharedtestvalues.AreAPICredentialsSet(c) && err != nil { +// t.Errorf("Could not get order history: %s", err) +// } else if !sharedtestvalues.AreAPICredentialsSet(c) && err == nil { +// t.Error("Expecting an error when no keys are set") +// } -func TestCancelExchangeOrder(t *testing.T) { - t.Parallel() - sharedtestvalues.SkipTestIfCannotManipulateOrders(t, c, canManipulateRealOrders) +// getOrdersRequest.Pairs = []currency.Pair{} +// _, err = c.GetOrderHistory(context.Background(), &getOrdersRequest) +// if sharedtestvalues.AreAPICredentialsSet(c) && err != nil { +// t.Errorf("Could not get order history: %s", err) +// } else if !sharedtestvalues.AreAPICredentialsSet(c) && err == nil { +// t.Error("Expecting an error when no keys are set") +// } - var orderCancellation = &order.Cancel{ - OrderID: "1", - WalletAddress: core.BitcoinDonationAddress, - AccountID: "1", - Pair: testPair, - AssetType: asset.Spot, - } +// getOrdersRequest.Pairs = nil +// _, err = c.GetOrderHistory(context.Background(), &getOrdersRequest) +// if sharedtestvalues.AreAPICredentialsSet(c) && err != nil { +// t.Errorf("Could not get order history: %s", err) +// } else if !sharedtestvalues.AreAPICredentialsSet(c) && err == nil { +// t.Error("Expecting an error when no keys are set") +// } +// } - err := c.CancelOrder(context.Background(), orderCancellation) - if !sharedtestvalues.AreAPICredentialsSet(c) && err == nil { - t.Error("Expecting an error when no keys are set") - } - if sharedtestvalues.AreAPICredentialsSet(c) && err != nil { - t.Errorf("Could not cancel orders: %v", err) - } -} +// // Any tests below this line have the ability to impact your orders on the exchange. Enable canManipulateRealOrders to run them +// // ---------------------------------------------------------------------------------------------------------------------------- + +// func TestSubmitOrder(t *testing.T) { +// t.Parallel() +// sharedtestvalues.SkipTestIfCannotManipulateOrders(t, c, canManipulateRealOrders) + +// // limit order +// var orderSubmission = &order.Submit{ +// Exchange: c.Name, +// Pair: currency.Pair{ +// Delimiter: "-", +// Base: currency.BTC, +// Quote: currency.USD, +// }, +// Side: order.Buy, +// Type: order.Limit, +// Price: 1, +// Amount: 0.001, +// ClientID: "meowOrder", +// AssetType: asset.Spot, +// } +// response, err := c.SubmitOrder(context.Background(), orderSubmission) +// if sharedtestvalues.AreAPICredentialsSet(c) && (err != nil || response.Status != order.New) { +// t.Errorf("Order failed to be placed: %v", err) +// } else if !sharedtestvalues.AreAPICredentialsSet(c) && err == nil { +// t.Error("Expecting an error when no keys are set") +// } -func TestCancelAllExchangeOrders(t *testing.T) { - t.Parallel() - sharedtestvalues.SkipTestIfCannotManipulateOrders(t, c, canManipulateRealOrders) +// // market order from amount +// orderSubmission = &order.Submit{ +// Exchange: c.Name, +// Pair: currency.Pair{ +// Delimiter: "-", +// Base: currency.BTC, +// Quote: currency.USD, +// }, +// Side: order.Buy, +// Type: order.Market, +// Amount: 0.001, +// ClientID: "meowOrder", +// AssetType: asset.Spot, +// } +// response, err = c.SubmitOrder(context.Background(), orderSubmission) +// if sharedtestvalues.AreAPICredentialsSet(c) && (err != nil || response.Status != order.New) { +// t.Errorf("Order failed to be placed: %v", err) +// } else if !sharedtestvalues.AreAPICredentialsSet(c) && err == nil { +// t.Error("Expecting an error when no keys are set") +// } - var orderCancellation = &order.Cancel{ - OrderID: "1", - WalletAddress: core.BitcoinDonationAddress, - AccountID: "1", - Pair: testPair, - AssetType: asset.Spot, - } +// // market order from quote amount +// orderSubmission = &order.Submit{ +// Exchange: c.Name, +// Pair: currency.Pair{ +// Delimiter: "-", +// Base: currency.BTC, +// Quote: currency.USD, +// }, +// Side: order.Buy, +// Type: order.Market, +// QuoteAmount: 1, +// ClientID: "meowOrder", +// AssetType: asset.Spot, +// } +// response, err = c.SubmitOrder(context.Background(), orderSubmission) +// if sharedtestvalues.AreAPICredentialsSet(c) && (err != nil || response.Status != order.New) { +// t.Errorf("Order failed to be placed: %v", err) +// } else if !sharedtestvalues.AreAPICredentialsSet(c) && err == nil { +// t.Error("Expecting an error when no keys are set") +// } +// } - resp, err := c.CancelAllOrders(context.Background(), orderCancellation) +// func TestCancelExchangeOrder(t *testing.T) { +// t.Parallel() +// sharedtestvalues.SkipTestIfCannotManipulateOrders(t, c, canManipulateRealOrders) - if !sharedtestvalues.AreAPICredentialsSet(c) && err == nil { - t.Error("Expecting an error when no keys are set") - } - if sharedtestvalues.AreAPICredentialsSet(c) && err != nil { - t.Errorf("Could not cancel orders: %v", err) - } +// var orderCancellation = &order.Cancel{ +// OrderID: "1", +// WalletAddress: core.BitcoinDonationAddress, +// AccountID: "1", +// Pair: testPair, +// AssetType: asset.Spot, +// } - if len(resp.Status) > 0 { - t.Errorf("%v orders failed to cancel", len(resp.Status)) - } -} +// err := c.CancelOrder(context.Background(), orderCancellation) +// if !sharedtestvalues.AreAPICredentialsSet(c) && err == nil { +// t.Error("Expecting an error when no keys are set") +// } +// if sharedtestvalues.AreAPICredentialsSet(c) && err != nil { +// t.Errorf("Could not cancel orders: %v", err) +// } +// } -func TestModifyOrder(t *testing.T) { - t.Parallel() - sharedtestvalues.SkipTestIfCannotManipulateOrders(t, c, canManipulateRealOrders) +// func TestCancelAllExchangeOrders(t *testing.T) { +// t.Parallel() +// sharedtestvalues.SkipTestIfCannotManipulateOrders(t, c, canManipulateRealOrders) - _, err := c.ModifyOrder(context.Background(), - &order.Modify{AssetType: asset.Spot}) - if err == nil { - t.Error("ModifyOrder() Expected error") - } -} +// var orderCancellation = &order.Cancel{ +// OrderID: "1", +// WalletAddress: core.BitcoinDonationAddress, +// AccountID: "1", +// Pair: testPair, +// AssetType: asset.Spot, +// } -func TestWithdraw(t *testing.T) { - t.Parallel() - sharedtestvalues.SkipTestIfCannotManipulateOrders(t, c, canManipulateRealOrders) +// resp, err := c.CancelAllOrders(context.Background(), orderCancellation) - withdrawCryptoRequest := withdraw.Request{ - Exchange: c.Name, - Amount: -1, - Currency: currency.BTC, - Description: "WITHDRAW IT ALL", - Crypto: withdraw.CryptoRequest{ - Address: core.BitcoinDonationAddress, - }, - } +// if !sharedtestvalues.AreAPICredentialsSet(c) && err == nil { +// t.Error("Expecting an error when no keys are set") +// } +// if sharedtestvalues.AreAPICredentialsSet(c) && err != nil { +// t.Errorf("Could not cancel orders: %v", err) +// } - _, err := c.WithdrawCryptocurrencyFunds(context.Background(), - &withdrawCryptoRequest) - if !sharedtestvalues.AreAPICredentialsSet(c) && err == nil { - t.Error("Expecting an error when no keys are set") - } - if sharedtestvalues.AreAPICredentialsSet(c) && err != nil { - t.Errorf("Withdraw failed to be placed: %v", err) - } -} +// if len(resp.Status) > 0 { +// t.Errorf("%v orders failed to cancel", len(resp.Status)) +// } +// } -func TestWithdrawFiat(t *testing.T) { - t.Parallel() - sharedtestvalues.SkipTestIfCannotManipulateOrders(t, c, canManipulateRealOrders) +// func TestModifyOrder(t *testing.T) { +// t.Parallel() +// sharedtestvalues.SkipTestIfCannotManipulateOrders(t, c, canManipulateRealOrders) - var withdrawFiatRequest = withdraw.Request{ - Amount: 100, - Currency: currency.USD, - Fiat: withdraw.FiatRequest{ - Bank: banking.Account{ - BankName: "Federal Reserve Bank", - }, - }, - } +// _, err := c.ModifyOrder(context.Background(), +// &order.Modify{AssetType: asset.Spot}) +// if err == nil { +// t.Error("ModifyOrder() Expected error") +// } +// } - _, err := c.WithdrawFiatFunds(context.Background(), &withdrawFiatRequest) - if !sharedtestvalues.AreAPICredentialsSet(c) && err == nil { - t.Error("Expecting an error when no keys are set") - } - if sharedtestvalues.AreAPICredentialsSet(c) && err != nil { - t.Errorf("Withdraw failed to be placed: %v", err) - } -} +// func TestWithdraw(t *testing.T) { +// t.Parallel() +// sharedtestvalues.SkipTestIfCannotManipulateOrders(t, c, canManipulateRealOrders) + +// withdrawCryptoRequest := withdraw.Request{ +// Exchange: c.Name, +// Amount: -1, +// Currency: currency.BTC, +// Description: "WITHDRAW IT ALL", +// Crypto: withdraw.CryptoRequest{ +// Address: core.BitcoinDonationAddress, +// }, +// } -func TestWithdrawInternationalBank(t *testing.T) { - t.Parallel() - sharedtestvalues.SkipTestIfCannotManipulateOrders(t, c, canManipulateRealOrders) +// _, err := c.WithdrawCryptocurrencyFunds(context.Background(), +// &withdrawCryptoRequest) +// if !sharedtestvalues.AreAPICredentialsSet(c) && err == nil { +// t.Error("Expecting an error when no keys are set") +// } +// if sharedtestvalues.AreAPICredentialsSet(c) && err != nil { +// t.Errorf("Withdraw failed to be placed: %v", err) +// } +// } - var withdrawFiatRequest = withdraw.Request{ - Amount: 100, - Currency: currency.USD, - Fiat: withdraw.FiatRequest{ - Bank: banking.Account{ - BankName: "Federal Reserve Bank", - }, - }, - } +// func TestWithdrawFiat(t *testing.T) { +// t.Parallel() +// sharedtestvalues.SkipTestIfCannotManipulateOrders(t, c, canManipulateRealOrders) + +// var withdrawFiatRequest = withdraw.Request{ +// Amount: 100, +// Currency: currency.USD, +// Fiat: withdraw.FiatRequest{ +// Bank: banking.Account{ +// BankName: "Federal Reserve Bank", +// }, +// }, +// } - _, err := c.WithdrawFiatFundsToInternationalBank(context.Background(), - &withdrawFiatRequest) - if !sharedtestvalues.AreAPICredentialsSet(c) && err == nil { - t.Error("Expecting an error when no keys are set") - } - if sharedtestvalues.AreAPICredentialsSet(c) && err != nil { - t.Errorf("Withdraw failed to be placed: %v", err) - } -} +// _, err := c.WithdrawFiatFunds(context.Background(), &withdrawFiatRequest) +// if !sharedtestvalues.AreAPICredentialsSet(c) && err == nil { +// t.Error("Expecting an error when no keys are set") +// } +// if sharedtestvalues.AreAPICredentialsSet(c) && err != nil { +// t.Errorf("Withdraw failed to be placed: %v", err) +// } +// } -func TestGetDepositAddress(t *testing.T) { - _, err := c.GetDepositAddress(context.Background(), currency.BTC, "", "") - if err == nil { - t.Error("GetDepositAddress() error", err) - } -} +// func TestWithdrawInternationalBank(t *testing.T) { +// t.Parallel() +// sharedtestvalues.SkipTestIfCannotManipulateOrders(t, c, canManipulateRealOrders) + +// var withdrawFiatRequest = withdraw.Request{ +// Amount: 100, +// Currency: currency.USD, +// Fiat: withdraw.FiatRequest{ +// Bank: banking.Account{ +// BankName: "Federal Reserve Bank", +// }, +// }, +// } + +// _, err := c.WithdrawFiatFundsToInternationalBank(context.Background(), +// &withdrawFiatRequest) +// if !sharedtestvalues.AreAPICredentialsSet(c) && err == nil { +// t.Error("Expecting an error when no keys are set") +// } +// if sharedtestvalues.AreAPICredentialsSet(c) && err != nil { +// t.Errorf("Withdraw failed to be placed: %v", err) +// } +// } + +// func TestGetDepositAddress(t *testing.T) { +// _, err := c.GetDepositAddress(context.Background(), currency.BTC, "", "") +// if err == nil { +// t.Error("GetDepositAddress() error", err) +// } +// } // TestWsAuth dials websocket, sends login request. func TestWsAuth(t *testing.T) { @@ -1465,22 +1596,22 @@ func TestParseTime(t *testing.T) { } } -func TestGetRecentTrades(t *testing.T) { - t.Parallel() - _, err := c.GetRecentTrades(context.Background(), testPair, asset.Spot) - if err != nil { - t.Error(err) - } -} +// func TestGetRecentTrades(t *testing.T) { +// t.Parallel() +// _, err := c.GetRecentTrades(context.Background(), testPair, asset.Spot) +// if err != nil { +// t.Error(err) +// } +// } -func TestGetHistoricTrades(t *testing.T) { - t.Parallel() - _, err := c.GetHistoricTrades(context.Background(), - testPair, asset.Spot, time.Now().Add(-time.Minute*15), time.Now()) - if err != nil && err != common.ErrFunctionNotSupported { - t.Error(err) - } -} +// func TestGetHistoricTrades(t *testing.T) { +// t.Parallel() +// _, err := c.GetHistoricTrades(context.Background(), +// testPair, asset.Spot, time.Now().Add(-time.Minute*15), time.Now()) +// if err != nil && err != common.ErrFunctionNotSupported { +// t.Error(err) +// } +// } func TestPrepareDSL(t *testing.T) { t.Parallel() @@ -1613,8 +1744,6 @@ func TestOrderbookHelper(t *testing.T) { if err != nil { t.Error("CoinBasePro OrderbookHelper() error", err) } - - log.Printf("Err: %v", err) } // 8837708 143.0 ns/op 24 B/op 5 allocs/op @@ -1640,3 +1769,32 @@ func TestOrderbookHelper(t *testing.T) { // _ = strconv.Itoa(86400) // } // } + +// const reportType = "rfq-fills" + +// // Benchmark3Ifs-8 1000000000 0.2556 ns/op 0 B/op 0 allocs/op +// // 1000000000 0.2879 ns/op 0 B/op 0 allocs/op +// // Benchmark3Ifs-8 1000000000 0.2945 ns/op 0 B/op 0 allocs/op +// func Benchmark3Ifs(b *testing.B) { +// a := 0 +// for x := 0; x < b.N; x++ { +// if reportType == "fills" || reportType == "otc-fills" || reportType == "rfq-fills" { +// a++ +// } +// } +// log.Print(a) +// } + +// // BenchmarkNeedle-8 322462062 3.670 ns/op 0 B/op 0 allocs/op +// // BenchmarkNeedle-8 295766910 4.467 ns/op 0 B/op 0 allocs/op +// // BenchmarkNeedle-8 137813607 9.496 ns/op 0 B/op 0 allocs/op +// func BenchmarkNeedle(b *testing.B) { +// a := 0 +// for x := 0; x < b.N; x++ { +// rTCheck := []string{"fills", "otc-fills", "rfq-fills"} +// if common.StringDataCompare(rTCheck, reportType) { +// a++ +// } +// } +// log.Print(a) +// } diff --git a/exchanges/coinbasepro/coinbasepro_types.go b/exchanges/coinbasepro/coinbasepro_types.go index c4069d732c2..bf831b01ca7 100644 --- a/exchanges/coinbasepro/coinbasepro_types.go +++ b/exchanges/coinbasepro/coinbasepro_types.go @@ -6,10 +6,11 @@ import ( "strconv" "time" + "github.com/thrasher-corp/gocryptotrader/common/convert" "github.com/thrasher-corp/gocryptotrader/currency" ) -// Product holds product information +// Product holds product information, returned by GetAllProducts and GetProductByID type Product struct { ID string `json:"id"` BaseCurrency string `json:"base_currency"` @@ -31,7 +32,7 @@ type Product struct { HighBidLimitPercentage string `json:"high_bid_limit_percentage"` } -// Ticker holds basic ticker information +// Ticker holds basic ticker information, returned by GetTicker type Ticker struct { Ask float64 `json:"ask,string"` Bid float64 `json:"bid,string"` @@ -42,7 +43,7 @@ type Ticker struct { Time time.Time `json:"time"` } -// Trade holds executed trade information +// Trade holds executed trade information, returned by GetTrades type Trade struct { TradeID int64 `json:"trade_id"` Price float64 `json:"price,string"` @@ -51,7 +52,7 @@ type Trade struct { Side string `json:"side"` } -// History holds historic rate information +// History holds historic rate information, returned by GetHistoricRates type History struct { Time time.Time Low float64 @@ -61,7 +62,7 @@ type History struct { Volume float64 } -// Stats holds last 24 hr data for coinbasepro +// Stats holds 30 day and 24 hr data for a currency pair, returned by GetStats type Stats struct { Open float64 `json:"open,string"` High float64 `json:"high,string"` @@ -71,7 +72,7 @@ type Stats struct { Volume30Day float64 `json:"volume_30day,string"` } -// Currency holds singular currency product information +// Currency holds information on a currency, returned by GetAllCurrencies and GetCurrencyByID type Currency struct { ID string `json:"id"` Name string `json:"name"` @@ -109,12 +110,13 @@ type Currency struct { } // ServerTime holds current requested server time information -type ServerTime struct { - ISO time.Time `json:"iso"` - Epoch float64 `json:"epoch"` -} +// type ServerTime struct { +// ISO time.Time `json:"iso"` +// Epoch float64 `json:"epoch"` +// } -// AccountResponse holds the details for the trading accounts +// AccountResponse holds details for a trading account, returned by GetAllAccounts +// and GetAccountByID type AccountResponse struct { ID string `json:"id"` Currency string `json:"currency"` @@ -126,7 +128,7 @@ type AccountResponse struct { PendingDeposit string `json:"pending_deposit"` } -// AccountLedgerResponse holds account history information +// AccountLedgerResponse holds account history information, returned by GetAccountLedger type AccountLedgerResponse struct { ID string `json:"id"` Amount float64 `json:"amount,string"` @@ -140,17 +142,18 @@ type AccountLedgerResponse struct { } `json:"details"` } -// AccountHolds contains the hold information about an account +// AccountHolds contains information on a hold, returned by GetHolds type AccountHolds struct { ID string `json:"id"` CreatedAt time.Time `json:"created_at"` + Amount float64 `json:"amount,string"` UpdatedAt time.Time `json:"updated_at"` Type string `json:"type"` Reference string `json:"ref"` } -// GeneralizedOrderResponse is the generalized return type across order -// placement and information collation +// GeneralizedOrderResponse contains information on an order, returned by GetAllOrders, +// PlaceOrder, and GetOrderByID type GeneralizedOrderResponse struct { ID string `json:"id"` Price float64 `json:"price,string"` @@ -181,85 +184,85 @@ type GeneralizedOrderResponse struct { } // Funding holds funding data -type Funding struct { - ID string `json:"id"` - OrderID string `json:"order_id"` - ProfileID string `json:"profile_id"` - Amount float64 `json:"amount,string"` - Status string `json:"status"` - CreatedAt time.Time `json:"created_at"` - Currency string `json:"currency"` - RepaidAmount float64 `json:"repaid_amount"` - DefaultAmount float64 `json:"default_amount,string"` - RepaidDefault bool `json:"repaid_default"` -} +// type Funding struct { +// ID string `json:"id"` +// OrderID string `json:"order_id"` +// ProfileID string `json:"profile_id"` +// Amount float64 `json:"amount,string"` +// Status string `json:"status"` +// CreatedAt time.Time `json:"created_at"` +// Currency string `json:"currency"` +// RepaidAmount float64 `json:"repaid_amount"` +// DefaultAmount float64 `json:"default_amount,string"` +// RepaidDefault bool `json:"repaid_default"` +// } // MarginTransfer holds margin transfer details -type MarginTransfer struct { - CreatedAt time.Time `json:"created_at"` - ID string `json:"id"` - UserID string `json:"user_id"` - ProfileID string `json:"profile_id"` - MarginProfileID string `json:"margin_profile_id"` - Type string `json:"type"` - Amount float64 `json:"amount,string"` - Currency string `json:"currency"` - AccountID string `json:"account_id"` - MarginAccountID string `json:"margin_account_id"` - MarginProductID string `json:"margin_product_id"` - Status string `json:"status"` - Nonce int `json:"nonce"` -} +// type MarginTransfer struct { +// CreatedAt time.Time `json:"created_at"` +// ID string `json:"id"` +// UserID string `json:"user_id"` +// ProfileID string `json:"profile_id"` +// MarginProfileID string `json:"margin_profile_id"` +// Type string `json:"type"` +// Amount float64 `json:"amount,string"` +// Currency string `json:"currency"` +// AccountID string `json:"account_id"` +// MarginAccountID string `json:"margin_account_id"` +// MarginProductID string `json:"margin_product_id"` +// Status string `json:"status"` +// Nonce int `json:"nonce"` +// } // AccountOverview holds account information returned from position -type AccountOverview struct { - Status string `json:"status"` - Funding struct { - MaxFundingValue float64 `json:"max_funding_value,string"` - FundingValue float64 `json:"funding_value,string"` - OldestOutstanding struct { - ID string `json:"id"` - OrderID string `json:"order_id"` - CreatedAt time.Time `json:"created_at"` - Currency string `json:"currency"` - AccountID string `json:"account_id"` - Amount float64 `json:"amount,string"` - } `json:"oldest_outstanding"` - } `json:"funding"` - Accounts struct { - LTC Account `json:"LTC"` - ETH Account `json:"ETH"` - USD Account `json:"USD"` - BTC Account `json:"BTC"` - } `json:"accounts"` - MarginCall struct { - Active bool `json:"active"` - Price float64 `json:"price,string"` - Side string `json:"side"` - Size float64 `json:"size,string"` - Funds float64 `json:"funds,string"` - } `json:"margin_call"` - UserID string `json:"user_id"` - ProfileID string `json:"profile_id"` - Position struct { - Type string `json:"type"` - Size float64 `json:"size,string"` - Complement float64 `json:"complement,string"` - MaxSize float64 `json:"max_size,string"` - } `json:"position"` - ProductID string `json:"product_id"` -} +// type AccountOverview struct { +// Status string `json:"status"` +// Funding struct { +// MaxFundingValue float64 `json:"max_funding_value,string"` +// FundingValue float64 `json:"funding_value,string"` +// OldestOutstanding struct { +// ID string `json:"id"` +// OrderID string `json:"order_id"` +// CreatedAt time.Time `json:"created_at"` +// Currency string `json:"currency"` +// AccountID string `json:"account_id"` +// Amount float64 `json:"amount,string"` +// } `json:"oldest_outstanding"` +// } `json:"funding"` +// Accounts struct { +// LTC Account `json:"LTC"` +// ETH Account `json:"ETH"` +// USD Account `json:"USD"` +// BTC Account `json:"BTC"` +// } `json:"accounts"` +// MarginCall struct { +// Active bool `json:"active"` +// Price float64 `json:"price,string"` +// Side string `json:"side"` +// Size float64 `json:"size,string"` +// Funds float64 `json:"funds,string"` +// } `json:"margin_call"` +// UserID string `json:"user_id"` +// ProfileID string `json:"profile_id"` +// Position struct { +// Type string `json:"type"` +// Size float64 `json:"size,string"` +// Complement float64 `json:"complement,string"` +// MaxSize float64 `json:"max_size,string"` +// } `json:"position"` +// ProductID string `json:"product_id"` +// } // Account is a sub-type for account overview -type Account struct { - ID string `json:"id"` - Balance float64 `json:"balance,string"` - Hold float64 `json:"hold,string"` - FundedAmount float64 `json:"funded_amount,string"` - DefaultAmount float64 `json:"default_amount,string"` -} - -// PaymentMethod holds payment method information +// type Account struct { +// ID string `json:"id"` +// Balance float64 `json:"balance,string"` +// Hold float64 `json:"hold,string"` +// FundedAmount float64 `json:"funded_amount,string"` +// DefaultAmount float64 `json:"default_amount,string"` +// } + +// PaymentMethod holds payment method information, returned by GetPayMethods type PaymentMethod struct { ID string `json:"id"` Type string `json:"type"` @@ -326,15 +329,18 @@ type PaymentMethod struct { } // LimitInfo is a sub-type for payment method -type LimitInfo struct { - PeriodInDays int `json:"period_in_days"` - Total struct { - Amount float64 `json:"amount,string"` - Currency string `json:"currency"` - } `json:"total"` -} - -// DepositWithdrawalInfo holds returned deposit information +// type LimitInfo struct { +// PeriodInDays int `json:"period_in_days"` +// Total struct { +// Amount float64 `json:"amount,string"` +// Currency string `json:"currency"` +// } `json:"total"` +// } + +// DepositWithdrawalInfo holds information provided when depositing or +// withdrawing from payment methods. Returned by DepositViaCoinbase, +// DepositViaPaymentMethod, WithdrawViaCoinbase, WithdrawCrypto, and +// WithdrawViaPaymentMethod type DepositWithdrawalInfo struct { ID string `json:"id"` Amount float64 `json:"amount,string"` @@ -344,7 +350,7 @@ type DepositWithdrawalInfo struct { Subtotal float64 `json:"subtotal,string"` } -// CoinbaseAccounts holds coinbase account information +// CoinbaseAccounts holds coinbase account information, returned by GetCoinbaseWallets type CoinbaseAccounts struct { ID string `json:"id"` Name string `json:"name"` @@ -406,7 +412,8 @@ type CoinbaseAccounts struct { HoldCurrency string `json:"hold_currency"` } -// Report holds historical information +// Report holds information on user-generated reports, returned by GetAllReports +// and GetReportByID type Report struct { ID string `json:"id"` Type string `json:"type"` @@ -432,10 +439,12 @@ type Report struct { Email string `json:"email"` Roles []interface{} `json:"roles"` IsBanned bool `json:"is_banned"` + Permissions interface{} `json:"permissions"` UserType string `json:"user_type"` FulfillsNewRequirements bool `json:"fulfills_new_requirements"` Flags interface{} `json:"flags"` Details interface{} `json:"details"` + OauthClient string `json:"oauth_client"` Preferences struct { PreferredMarket string `json:"preferred_market"` MarginTermsCompletedInUTC time.Time `json:"margin_terms_completed_in_utc"` @@ -458,12 +467,12 @@ type Report struct { } // Volume type contains trailing volume information -type Volume struct { - ProductID string `json:"product_id"` - ExchangeVolume float64 `json:"exchange_volume,string"` - Volume float64 `json:"volume,string"` - RecordedAt string `json:"recorded_at"` -} +// type Volume struct { +// ProductID string `json:"product_id"` +// ExchangeVolume float64 `json:"exchange_volume,string"` +// Volume float64 `json:"volume,string"` +// RecordedAt string `json:"recorded_at"` +// } // InterOrderDetail is used to make intermediary orderbook handling easier type InterOrderDetail [][3]interface{} @@ -488,7 +497,7 @@ type OrderbookIntermediaryResponse struct { Time time.Time `json:"time"` } -// GenOrderDetail is a subtype used for the orderbook +// GenOrderDetail is a subtype used for the final state of the orderbook type GenOrderDetail struct { Price float64 Amount float64 @@ -496,7 +505,7 @@ type GenOrderDetail struct { OrderID string } -// OrderbookResponse is the final state of the orderbook +// OrderbookResponse is the final state of the orderbook, returned by GetOrderbook type OrderbookFinalResponse struct { Bids []GenOrderDetail `json:"bids"` Asks []GenOrderDetail `json:"asks"` @@ -516,7 +525,7 @@ type OrderbookFinalResponse struct { Time time.Time `json:"time"` } -// FillResponse contains fill information from the exchange +// FillResponse contains fill information, returned by GetFills type FillResponse struct { TradeID int32 `json:"trade_id"` ProductID string `json:"product_id"` @@ -665,18 +674,19 @@ type wsStatus struct { } // RequestParamsTimeForceType Time in force -type RequestParamsTimeForceType string +// type RequestParamsTimeForceType string -var ( - // CoinbaseRequestParamsTimeGTC GTC - CoinbaseRequestParamsTimeGTC = RequestParamsTimeForceType("GTC") +// var ( +// // CoinbaseRequestParamsTimeGTC GTC +// CoinbaseRequestParamsTimeGTC = RequestParamsTimeForceType("GTC") - // CoinbaseRequestParamsTimeIOC IOC - CoinbaseRequestParamsTimeIOC = RequestParamsTimeForceType("IOC") -) +// // CoinbaseRequestParamsTimeIOC IOC +// CoinbaseRequestParamsTimeIOC = RequestParamsTimeForceType("IOC") +// ) -// TransferHistory returns wallet transfer history -type TransferHistory struct { +// TransferResponse contains information on a transfer, returned by GetAccountTransfers, +// GetAllTransfers, and GetTransferByID +type TransferResponse struct { ID string `json:"id"` Type string `json:"type"` CreatedAt time.Time `json:"created_at"` @@ -692,18 +702,22 @@ type TransferHistory struct { UserNonce int64 `json:"user_nonce"` } +// TravelRule contains information on a travel rule, returned by GetTravelRules +// and CreateTravelRule type TravelRule struct { - ID string `json:"id"` - CreatedAt string `json:"created_at"` - Address string `json:"address"` - OriginName string `json:"originator_name"` - OriginCountry string `json:"originator_country"` + ID string `json:"id"` + CreatedAt time.Time `json:"created_at"` + Address string `json:"address"` + OriginName string `json:"originator_name"` + OriginCountry string `json:"originator_country"` } +// Params is used within functions to make the setting of parameters easier type Params struct { urlVals url.Values } +// GetAddressResponse contains information on addresses, returned by GetAddressBook type GetAddressResponse struct { ID string `json:"id"` Address string `json:"address"` @@ -716,10 +730,13 @@ type GetAddressResponse struct { VASPID string `json:"vasp_id"` } +// To is part of the struct expected by the exchange for the AddAddresses function type To struct { Address string `json:"address"` DestinationTag string `json:"destination_tag"` } + +// AddAddressRequest is the struct expected by the exchange for the AddAddresses function type AddAddressRequest struct { Currency string `json:"currency"` To `json:"to"` @@ -730,6 +747,8 @@ type AddAddressRequest struct { // but doesn't explain what they do. Investigate more later. } +// AddAddressResponse contains information on the addresses just added, returned by +// AddAddresses type AddAddressResponse struct { ID string `json:"id"` Address string `json:"address"` @@ -750,6 +769,8 @@ type AddAddressResponse struct { VaspID string `json:"vasp_id"` } +// CryptoAddressResponse contains information on the one-time address generated for +// depositing crypto, returned by GenerateCryptoAddress type CryptoAddressResponse struct { ID string `json:"id"` Address string `json:"address"` @@ -775,6 +796,8 @@ type CryptoAddressResponse struct { CallbackURL string `json:"callback_url"` } +// ConvertResponse contains information about a completed currency conversion, returned +// by ConvertCurrency and GetConversionByID type ConvertResponse struct { ID string `json:"id"` Amount string `json:"amount"` @@ -784,17 +807,22 @@ type ConvertResponse struct { To string `json:"to"` } +// WithdrawalFeeEstimate is the exchange's estimate of the fee for a withdrawal, returned +// by GetWithdrawalFeeEstimate type WithdrawalFeeEstimate struct { Fee string `json:"fee"` FeeBeforeSubsidy string `json:"fee_before_subsidy"` } +// FeeResponse contains current taker and maker fee rates, as well as 30-day trailing +// volume. Returned by GetFees type FeeResponse struct { TakerFeeRate float64 `json:"taker_fee_rate,string"` MakerFeeRate float64 `json:"maker_fee_rate,string"` USDVolume float64 `json:"usd_volume,string"` } +// PriceMap is used to properly unmarshal the response from GetSignedPrices type PriceMap map[string]float64 func (pm *PriceMap) UnmarshalJSON(data []byte) error { @@ -813,6 +841,9 @@ func (pm *PriceMap) UnmarshalJSON(data []byte) error { return nil } +// SignedPrices contains cryptographically signed prices, alongside other information +// necessary for them to be posted on-chain using Compound's Open Oracle smart contract. +// Returned by GetSignedPrices type SignedPrices struct { Timestamp string `json:"timestamp"` Messages []string `json:"messages"` @@ -820,6 +851,8 @@ type SignedPrices struct { Prices PriceMap `json:"prices"` } +// Profile contains information on a profile. Returned by GetAllProfiles, CreateAProfile, +// GetProfileByID, and RenameProfile type Profile struct { ID string `json:"id"` UserID string `json:"user_id"` @@ -829,12 +862,92 @@ type Profile struct { CreatedAt time.Time `json:"created_at"` } +// CreateReportResponse contains information on a newly-created report, returned by +// CreateReport type CreateReportResponse struct { ID string `json:"id"` Type string `json:"type"` Status string `json:"status"` } +// ReportBalanceStruct is used internally when crafting a CreateReport request type ReportBalanceStruct struct { DateTime string } + +// ReportFillsTaxStruct is used internally when crafting a CreateReport request +type ReportFillsTaxStruct struct { + StartDate string + EndDate string + ProductID string +} + +// ReportAccountStruct is used internally when crafting a CreateReport request +type ReportAccountStruct struct { + StartDate string + EndDate string + AccountID string +} + +// ExchangeLimits contains information on payment method transfer limits, returned +// by GetExchangeLimits +type ExchangeLimits struct { + TransferLimits struct { + Buy interface{} `json:"buy"` + Sell interface{} `json:"sell"` + ExchangeWithdraw interface{} `json:"exchange_withdraw"` + Ach []struct { + Max float64 `json:"max,string"` + Remaining float64 `json:"remaining,string"` + PeriodInDays int32 `json:"period_in_days"` + } `json:"ach"` + AchNoBalance interface{} `json:"ach_no_balance"` + CreditDebitCard interface{} `json:"credit_debit_card"` + Secure3DBuy interface{} `json:"secure3d_buy"` + PaypalBuy interface{} `json:"paypal_buy"` + PaypalWithdrawal interface{} `json:"paypal_withdrawal"` + IdealDeposit interface{} `json:"ideal_deposit"` + SofortDeposit interface{} `json:"sofort_deposit"` + InstantAchWithdrawal interface{} `json:"instant_ach_withdrawal"` + } `json:"transfer_limits"` + LimitCurrency string `json:"limit_currency"` +} + +// WrappedAssetResponse contains information on a wrapped asset, returned by +// GetWrappedAssetByID +type WrappedAssetResponse struct { + ID string `json:"id"` + CirculatingSupply float64 `json:"circulating_supply,string"` + TotalSupply float64 `json:"total_supply,string"` + ConversionRate float64 `json:"conversion_rate,string"` + APY convert.StringToFloat64 `json:"apy"` +} + +// AllWrappedAssetResponse contains information on all wrapped assets, returned by +// GetAllWrappedAssets +type AllWrappedAssetResponse struct { + WrappedAssetResponse []struct{} `json:"wrapped_assets"` +} + +// StakeWrap contains information on a stake wrap, returned by GetAllStakeWraps and +// GetStakeWrapByID +type StakeWrap struct { + ID string `json:"id"` + FromAmount float64 `json:"from_amount,string"` + ToAmount float64 `json:"to_amount,string"` + FromAccountID string `json:"from_account_id"` + ToAccountID string `json:"to_account_id"` + FromCurrency string `json:"from_currency"` + ToCurrency string `json:"to_currency"` + Status string `json:"status"` + ConversionRate float64 `json:"conversion_rate,string"` + CreatedAt time.Time `json:"created_at"` + CompletedAt time.Time `json:"completed_at"` + CanceledAt time.Time `json:"canceled_at"` +} + +// WrappedAssetConversionRate contains the conversion rate for a wrapped asset, returned +// by GetWrappedAssetConversionRate +type WrappedAssetConversionRate struct { + Amount float64 `json:"amount,string"` +} diff --git a/exchanges/coinbasepro/coinbasepro_wrapper.go b/exchanges/coinbasepro/coinbasepro_wrapper.go index 74074e6c197..a053dff41c2 100644 --- a/exchanges/coinbasepro/coinbasepro_wrapper.go +++ b/exchanges/coinbasepro/coinbasepro_wrapper.go @@ -753,14 +753,15 @@ func (c *CoinbasePro) WithdrawFiatFundsToInternationalBank(ctx context.Context, // GetFeeByType returns an estimate of fee based on type of transaction func (c *CoinbasePro) GetFeeByType(ctx context.Context, feeBuilder *exchange.FeeBuilder) (float64, error) { - if feeBuilder == nil { - return 0, fmt.Errorf("%T %w", feeBuilder, common.ErrNilPointer) - } - if !c.AreCredentialsValid(ctx) && // Todo check connection status - feeBuilder.FeeType == exchange.CryptocurrencyTradeFee { - feeBuilder.FeeType = exchange.OfflineTradeFee - } - return c.GetFee(ctx, feeBuilder) + // if feeBuilder == nil { + // return 0, fmt.Errorf("%T %w", feeBuilder, common.ErrNilPointer) + // } + // if !c.AreCredentialsValid(ctx) && // Todo check connection status + // feeBuilder.FeeType == exchange.CryptocurrencyTradeFee { + // feeBuilder.FeeType = exchange.OfflineTradeFee + // } + // return c.GetFee(ctx, feeBuilder) + return 99999, errors.New(common.ErrFunctionNotSupported.Error()) } // GetActiveOrders retrieves any orders that are active/open @@ -980,11 +981,12 @@ func (c *CoinbasePro) ValidateAPICredentials(ctx context.Context, assetType asse // GetServerTime returns the current exchange server time. func (c *CoinbasePro) GetServerTime(ctx context.Context, _ asset.Item) (time.Time, error) { - st, err := c.GetCurrentServerTime(ctx) - if err != nil { - return time.Time{}, err - } - return st.ISO, nil + // st, err := c.GetCurrentServerTime(ctx) + // if err != nil { + // return time.Time{}, err + // } + // return st.ISO, nil + return time.Time{}, errors.New(common.ErrFunctionNotSupported.Error()) } // GetFuturesContractDetails returns all contracts from the exchange by asset type diff --git a/exchanges/coinbasepro/ratelimit.go b/exchanges/coinbasepro/ratelimit.go index d9c6fa76f61..0761c87ddba 100644 --- a/exchanges/coinbasepro/ratelimit.go +++ b/exchanges/coinbasepro/ratelimit.go @@ -11,8 +11,8 @@ import ( // Coinbasepro rate limit conts const ( coinbaseproRateInterval = time.Second - coinbaseproAuthRate = 5 - coinbaseproUnauthRate = 2 + coinbaseproAuthRate = 10 + coinbaseproUnauthRate = 10 ) // RateLimit implements the request.Limiter interface From 5d3fd99f6dacf70996c88ca57b6eb4c78d88bb41 Mon Sep 17 00:00:00 2001 From: Samuel Reid <43227667+cranktakular@users.noreply.github.com> Date: Tue, 24 Oct 2023 09:10:02 +1100 Subject: [PATCH 04/79] Coinbase revamp continues --- exchanges/coinbasepro/coinbasepro_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/exchanges/coinbasepro/coinbasepro_test.go b/exchanges/coinbasepro/coinbasepro_test.go index f86d37b95dd..7021e6cc88f 100644 --- a/exchanges/coinbasepro/coinbasepro_test.go +++ b/exchanges/coinbasepro/coinbasepro_test.go @@ -31,9 +31,9 @@ var ( // Please supply your APIKeys here for better testing const ( - apiKey = "2a77ecc0a721e8b7b481d6a2fcdeea5d" - apiSecret = "m/Ty7P9WvZsTn7Qu/hUqDGy85v1ow8irUSQRgSQ7jEag2QU0evyHjUr3Jv7bNpYG3iLHl92zHYe+GuDYGWGXxw==" - clientID = "se9b47msd8" // passphrase you made at API CREATION + apiKey = "" + apiSecret = "" + clientID = "" // passphrase you made at API CREATION canManipulateRealOrders = true testingInSandbox = true ) From 3aca36bfe6898641f7eea0d6bc62f23e83577094 Mon Sep 17 00:00:00 2001 From: Samuel Reid <43227667+cranktakular@users.noreply.github.com> Date: Tue, 24 Oct 2023 09:18:22 +1100 Subject: [PATCH 05/79] Coinbase revamp continues --- exchanges/coinbasepro/coinbasepro.go | 629 +++++---- exchanges/coinbasepro/coinbasepro_test.go | 1198 ++++++++++-------- exchanges/coinbasepro/coinbasepro_types.go | 357 ++++-- exchanges/coinbasepro/coinbasepro_wrapper.go | 28 +- exchanges/coinbasepro/ratelimit.go | 4 +- 5 files changed, 1328 insertions(+), 888 deletions(-) diff --git a/exchanges/coinbasepro/coinbasepro.go b/exchanges/coinbasepro/coinbasepro.go index 18a9911ead8..3e6fb42992a 100644 --- a/exchanges/coinbasepro/coinbasepro.go +++ b/exchanges/coinbasepro/coinbasepro.go @@ -6,7 +6,6 @@ import ( "encoding/json" "errors" "fmt" - "log" "net/http" "net/url" "strconv" @@ -15,53 +14,57 @@ import ( "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/common/crypto" - "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/request" ) const ( - coinbaseproAPIURL = "https://api.exchange.coinbase.com/" - coinbaseproSandboxAPIURL = "https://api-public.sandbox.exchange.coinbase.com/" - coinbaseproAPIVersion = "0" - coinbaseproAccounts = "accounts" - coinbaseproHolds = "holds" - coinbaseproLedger = "ledger" - coinbaseproTransfers = "transfers" - coinbaseproAddressBook = "address-book" - coinbaseproAddress = "addresses" - coinbaseproConversions = "conversions" - coinbaseproCurrencies = "currencies" - coinbaseproDepositCoinbase = "deposits/coinbase-account" - coinbaseproPaymentMethodDeposit = "deposits/payment-method" - coinbaseproPaymentMethod = "payment-methods" - coinbaseproFeeEstimate = "withdrawals/fee-estimate" - coinbaseproFees = "fees" - coinbaseproFills = "fills" - coinbaseproOrders = "orders" - coinbaseproOracle = "oracle" - coinbaseproProducts = "products" - coinbaseproOrderbook = "book" - coinbaseproHistory = "candles" - coinbaseproStats = "stats" - coinbaseproTicker = "ticker" - coinbaseproTrades = "trades" - coinbaseproProfiles = "profiles" - coinbaseproTransfer = "transfer" - coinbaseproDeactivate = "deactivate" - coinbaseproReports = "reports" - - coinbaseproTime = "time" + coinbaseproAPIURL = "https://api.exchange.coinbase.com/" + coinbaseproSandboxAPIURL = "https://api-public.sandbox.exchange.coinbase.com/" + coinbaseproAPIVersion = "0" + coinbaseproAccounts = "accounts" + coinbaseproHolds = "holds" + coinbaseproLedger = "ledger" + coinbaseproTransfers = "transfers" + coinbaseproAddressBook = "address-book" + coinbaseproCoinbaseAccounts = "coinbase-accounts" + coinbaseproAddress = "addresses" + coinbaseproConversions = "conversions" + coinbaseproCurrencies = "currencies" + coinbaseproDepositCoinbase = "deposits/coinbase-account" + coinbaseproPaymentMethodDeposit = "deposits/payment-method" + coinbaseproPaymentMethod = "payment-methods" coinbaseproTravelRules = "travel-rules" - coinbaseproMarginTransfer = "profiles/margin-transfer" - coinbaseproPosition = "position" - coinbaseproPositionClose = "position/close" - coinbaseproWithdrawalPaymentMethod = "withdrawals/payment-method" coinbaseproWithdrawalCoinbase = "withdrawals/coinbase-account" coinbaseproWithdrawalCrypto = "withdrawals/crypto" - coinbaseproCoinbaseAccounts = "coinbase-accounts" - coinbaseproTrailingVolume = "users/self/trailing-volume" + coinbaseproFeeEstimate = "withdrawals/fee-estimate" + coinbaseproWithdrawalPaymentMethod = "withdrawals/payment-method" + coinbaseproFees = "fees" + coinbaseproFills = "fills" + coinbaseproOrders = "orders" + coinbaseproOracle = "oracle" + coinbaseproProducts = "products" + coinbaseproOrderbook = "book" + coinbaseproHistory = "candles" + coinbaseproStats = "stats" + coinbaseproTicker = "ticker" + coinbaseproTrades = "trades" + coinbaseproProfiles = "profiles" + coinbaseproTransfer = "transfer" + coinbaseproDeactivate = "deactivate" + coinbaseproReports = "reports" + coinbaseproUsers = "users" + coinbaseproSettlementPreferences = "settlement-preferences" + coinbaseproWrappedAssets = "wrapped-assets" + coinbaseproStakeWraps = "stake-wrap" + coinbaseproConversionRate = "conversion-rate" + + // coinbaseproTime = "time" + // coinbaseproMarginTransfer = "profiles/margin-transfer" + // coinbaseproPosition = "position" + // coinbaseproPositionClose = "position/close" + // coinbaseproTrailingVolume = "users/self/trailing-volume" ) const ( @@ -75,17 +78,16 @@ type CoinbasePro struct { exchange.Base } -// GetAccounts returns a list of trading accounts associated with the APIKEYS -func (c *CoinbasePro) GetAccounts(ctx context.Context) ([]AccountResponse, error) { +// GetAllAccounts returns information on all trading accounts associated with the API key +func (c *CoinbasePro) GetAllAccounts(ctx context.Context) ([]AccountResponse, error) { var resp []AccountResponse return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, coinbaseproAccounts, nil, &resp) } -// GetAccount returns information for a single account. Use this endpoint when -// account_id is known -func (c *CoinbasePro) GetAccount(ctx context.Context, accountID string) (*AccountResponse, error) { +// GetAccountByID returns information for a single account +func (c *CoinbasePro) GetAccountByID(ctx context.Context, accountID string) (*AccountResponse, error) { path := fmt.Sprintf("%s/%s", coinbaseproAccounts, accountID) resp := AccountResponse{} @@ -93,10 +95,7 @@ func (c *CoinbasePro) GetAccount(ctx context.Context, accountID string) (*Accoun c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp) } -// GetHolds returns the holds that are placed on an account for any active -// orders or pending withdraw requests. As an order is filled, the hold amount -// is updated. If an order is canceled, any remaining hold is removed. For a -// withdraw, once it is completed, the hold is removed. +// GetHolds returns information on the holds of an account func (c *CoinbasePro) GetHolds(ctx context.Context, accountID, direction, step string, limit int64) ([]AccountHolds, error) { path := fmt.Sprintf("%s/%s/%s", coinbaseproAccounts, accountID, coinbaseproHolds) @@ -112,9 +111,7 @@ func (c *CoinbasePro) GetHolds(ctx context.Context, accountID, direction, step s c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp) } -// GetAccountLedger returns a list of ledger activity. Anything that increases -// or decreases your account balance. Items are paginated and sorted latest -// first. +// GetAccountLedger returns a list of ledger activity func (c *CoinbasePro) GetAccountLedger(ctx context.Context, accountID, direction, step, pID string, startDate, endDate time.Time, limit int64) ([]AccountLedgerResponse, error) { var params Params params.urlVals = url.Values{} @@ -141,7 +138,7 @@ func (c *CoinbasePro) GetAccountLedger(ctx context.Context, accountID, direction // GetAccountTransfers returns a history of withdrawal and or deposit // transactions for a single account -func (c *CoinbasePro) GetAccountTransfers(ctx context.Context, accountID, direction, step, transferType string, limit int64) ([]TransferHistory, error) { +func (c *CoinbasePro) GetAccountTransfers(ctx context.Context, accountID, direction, step, transferType string, limit int64) ([]TransferResponse, error) { path := fmt.Sprintf("%s/%s/%s", coinbaseproAccounts, accountID, coinbaseproTransfers) var params Params @@ -152,7 +149,7 @@ func (c *CoinbasePro) GetAccountTransfers(ctx context.Context, accountID, direct path = common.EncodeURLValues(path, params.urlVals) - var resp []TransferHistory + var resp []TransferResponse return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp) @@ -218,7 +215,7 @@ func (c *CoinbasePro) ConvertCurrency(ctx context.Context, profileID, from, to, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproConversions, params, &resp) } -// GetConversionByID identifies the details of a past conversion, given its ID +// GetConversionByID returns the details of a past conversion, given its ID func (c *CoinbasePro) GetConversionByID(ctx context.Context, conversionID, profileID string) (ConvertResponse, error) { path := fmt.Sprintf("%s/%s", coinbaseproConversions, conversionID) var params Params @@ -232,18 +229,18 @@ func (c *CoinbasePro) GetConversionByID(ctx context.Context, conversionID, profi c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp) } -// GetCurrencies returns a list of currencies known by the exchange +// GetAllCurrencies returns a list of currencies known by the exchange // Warning: Currencies won't necessarily be available for trading -func (c *CoinbasePro) GetCurrencies(ctx context.Context) ([]Currency, error) { +func (c *CoinbasePro) GetAllCurrencies(ctx context.Context) ([]Currency, error) { var currencies []Currency return currencies, c.SendHTTPRequest(ctx, exchange.RestSpot, coinbaseproCurrencies, ¤cies) } -// GetCurrenciesByID returns into on a single currency given its ID in ISO 4217, or +// GetCurrencyByID returns info on a single currency given its ID in ISO 4217, or // in a custom code for currencies which lack an ISO 4217 code -func (c *CoinbasePro) GetCurrenciesByID(ctx context.Context, currencyID string) (*Currency, error) { +func (c *CoinbasePro) GetCurrencyByID(ctx context.Context, currencyID string) (*Currency, error) { path := fmt.Sprintf("%s/%s", coinbaseproCurrencies, currencyID) resp := Currency{} @@ -285,7 +282,7 @@ func (c *CoinbasePro) GetPayMethods(ctx context.Context) ([]PaymentMethod, error // GetAllTransfers returns all in-progress and completed transfers in and out of any // of the user's accounts -func (c *CoinbasePro) GetAllTransfers(ctx context.Context, profileID, direction, step, transferType string, limit int64) ([]TransferHistory, error) { +func (c *CoinbasePro) GetAllTransfers(ctx context.Context, profileID, direction, step, transferType string, limit int64) ([]TransferResponse, error) { var params Params params.urlVals = url.Values{} params.urlVals.Set("profile_id", profileID) @@ -293,16 +290,16 @@ func (c *CoinbasePro) GetAllTransfers(ctx context.Context, profileID, direction, params.urlVals.Set("type", transferType) path := common.EncodeURLValues(coinbaseproTransfers, params.urlVals) - resp := []TransferHistory{} + resp := []TransferResponse{} return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp) } // GetTransferByID returns information on a single transfer when provided with its ID -func (c *CoinbasePro) GetTransferByID(ctx context.Context, transferID string) (*TransferHistory, error) { +func (c *CoinbasePro) GetTransferByID(ctx context.Context, transferID string) (*TransferResponse, error) { path := fmt.Sprintf("%s/%s", coinbaseproTransfers, transferID) - resp := TransferHistory{} + resp := TransferResponse{} return &resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp) @@ -390,7 +387,7 @@ func (c *CoinbasePro) GetFees(ctx context.Context) (FeeResponse, error) { c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, coinbaseproFees, nil, &resp) } -// GetFills returns a list of recent fills on this profile +// GetFills returns information of recent fills on the specified profile func (c *CoinbasePro) GetFills(ctx context.Context, orderID, currencyPair, direction, step, marketType string, limit int64, startDate, endDate time.Time) ([]FillResponse, error) { if orderID == "" && currencyPair == "" { return nil, errors.New("requires either order id or product id") @@ -419,8 +416,8 @@ func (c *CoinbasePro) GetFills(ctx context.Context, orderID, currencyPair, direc return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp) } -// GetOrders lists all open or unsettled orders -func (c *CoinbasePro) GetOrders(ctx context.Context, profileID, currencyPair, sortedBy, sorting, direction, step, marketType string, startDate, endDate time.Time, limit int64, status []string) ([]GeneralizedOrderResponse, error) { +// GetAllOrders lists all open or unsettled orders +func (c *CoinbasePro) GetAllOrders(ctx context.Context, profileID, currencyPair, sortedBy, sorting, direction, step, marketType string, startDate, endDate time.Time, limit int64, status []string) ([]GeneralizedOrderResponse, error) { if limit < 1 { return nil, errors.New("limit must be greater than 0") } @@ -475,22 +472,30 @@ func (c *CoinbasePro) PlaceOrder(ctx context.Context, profileID, orderType, side if orderType == order.Market.Lower() && (size == 0 && funds == 0) { return &resp, errors.New("size or funds must be greater than 0 for market orders") } + if side != order.Buy.Lower() && side != order.Sell.Lower() { + return &resp, errors.New("side must be buy or sell") + } req := map[string]interface{}{"profile_id": profileID, "type": orderType, "side": side, "product_id": currencyPair, "stp": stp, "stop": stop, - "stop_price": strconv.FormatFloat(stopPrice, 'f', -1, 64), "price": strconv.FormatFloat(price, 'f', -1, 64), "size": strconv.FormatFloat(size, 'f', -1, 64), - "funds": strconv.FormatFloat(funds, 'f', -1, 64), "time_in_force": timeInForce, "cancel_after": cancelAfter, "post_only": postOnly, "client_oid": clientOID} + if stopPrice != 0 { + req["stop_price"] = strconv.FormatFloat(stopPrice, 'f', -1, 64) + } + if funds != 0 { + req["funds"] = strconv.FormatFloat(funds, 'f', -1, 64) + } + return &resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproOrders, req, &resp) } -// GetOrder returns a single order by order id. -func (c *CoinbasePro) GetOrder(ctx context.Context, orderID, marketType string, clientID bool) (*GeneralizedOrderResponse, error) { +// GetOrderByID returns a single order by order id. +func (c *CoinbasePro) GetOrderByID(ctx context.Context, orderID, marketType string, clientID bool) (*GeneralizedOrderResponse, error) { resp := GeneralizedOrderResponse{} if orderID == "" { return &resp, errors.New("order id cannot be empty") @@ -501,7 +506,11 @@ func (c *CoinbasePro) GetOrder(ctx context.Context, orderID, marketType string, var param Params param.urlVals = url.Values{} - param.urlVals.Set("market_type", marketType) + // Spot's currently the only supported market type; passing in anything else here will + // cause the request to time out after a minute + if marketType == "spot" { + param.urlVals.Set("market_type", marketType) + } path := fmt.Sprintf("%s/%s", coinbaseproOrders, orderID) path = common.EncodeURLValues(path, param.urlVals) @@ -522,8 +531,7 @@ func (c *CoinbasePro) CancelExistingOrder(ctx context.Context, orderID, profileI var param Params param.urlVals = url.Values{} - param.urlVals.Set("profile_id", profileID) - param.urlVals.Set("product_id", productID) + param.PrepareProfIDAndProdID(profileID, productID) path = common.EncodeURLValues(path, param.urlVals) @@ -539,8 +547,8 @@ func (c *CoinbasePro) GetSignedPrices(ctx context.Context) (SignedPrices, error) c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, coinbaseproOracle, nil, &resp) } -// GetProducts returns information on all currency pairs that are available for trading -func (c *CoinbasePro) GetProducts(ctx context.Context, productType string) ([]Product, error) { +// GetAllProducts returns information on all currency pairs that are available for trading +func (c *CoinbasePro) GetAllProducts(ctx context.Context, productType string) ([]Product, error) { var params Params params.urlVals = url.Values{} params.urlVals.Set("type", productType) @@ -552,8 +560,8 @@ func (c *CoinbasePro) GetProducts(ctx context.Context, productType string) ([]Pr return products, c.SendHTTPRequest(ctx, exchange.RestSpot, path, &products) } -// GetProduct returns information on a single specified currency pair -func (c *CoinbasePro) GetProduct(ctx context.Context, productID string) (*Product, error) { +// GetProductByID returns information on a single specified currency pair +func (c *CoinbasePro) GetProductByID(ctx context.Context, productID string) (*Product, error) { if productID == "" { return nil, errors.New("product id cannot be empty") } @@ -622,9 +630,6 @@ func (c *CoinbasePro) GetHistoricRates(ctx context.Context, currencyPair string, return nil, err } - log.Printf("The date params are %v and %v", params.urlVals.Get("start_date"), - params.urlVals.Get("end_date")) - allowedGranularities := [7]int64{0, 60, 300, 900, 3600, 21600, 86400} validGran, _ := common.InArray(granularity, allowedGranularities) if !validGran { @@ -675,7 +680,6 @@ func (c *CoinbasePro) GetStats(ctx context.Context, currencyPair string) (Stats, // GetTicker returns snapshot information about the last trade (tick), best bid/ask and // 24h volume. -// currencyPair - example "BTC-USD" func (c *CoinbasePro) GetTicker(ctx context.Context, currencyPair string) (*Ticker, error) { if currencyPair == "" { return nil, errors.New("currency pair cannot be empty") @@ -687,8 +691,7 @@ func (c *CoinbasePro) GetTicker(ctx context.Context, currencyPair string) (*Tick return &tick, c.SendHTTPRequest(ctx, exchange.RestSpot, path, &tick) } -// GetTrades lists the latest trades for a product -// currencyPair - example "BTC-USD" +// GetTrades lists information on the latest trades for a product func (c *CoinbasePro) GetTrades(ctx context.Context, currencyPair, direction, step string, limit int64) ([]Trade, error) { if currencyPair == "" { return nil, errors.New("currency pair cannot be empty") @@ -708,8 +711,8 @@ func (c *CoinbasePro) GetTrades(ctx context.Context, currencyPair, direction, st return trades, c.SendHTTPRequest(ctx, exchange.RestSpot, path, &trades) } -// GetProfiles returna a list of all of the current user's profiles -func (c *CoinbasePro) GetProfiles(ctx context.Context, active *bool) ([]Profile, error) { +// GetAllProfiles returns information on all of the current user's profiles +func (c *CoinbasePro) GetAllProfiles(ctx context.Context, active *bool) ([]Profile, error) { var params Params params.urlVals = url.Values{} @@ -755,7 +758,7 @@ func (c *CoinbasePro) TransferBetweenProfiles(ctx context.Context, from, to, cur c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, path, req, &resp) } -// GetProfileByID returns a single profile, provided its ID +// GetProfileByID returns information on a single profile, provided its ID func (c *CoinbasePro) GetProfileByID(ctx context.Context, profileID string, active *bool) (Profile, error) { var params Params params.urlVals = url.Values{} @@ -787,7 +790,7 @@ func (c *CoinbasePro) RenameProfile(ctx context.Context, profileID, newName stri } // DeleteProfile deletes a profile and transfers its funds to a specified -// proifle. Fails if there are any open orders on the proifle facing deletion +// profile. Fails if there are any open orders on the profile facing deletion func (c *CoinbasePro) DeleteProfile(ctx context.Context, profileID, transferTo string) (string, error) { var resp string if profileID == "" || transferTo == "" { @@ -802,8 +805,8 @@ func (c *CoinbasePro) DeleteProfile(ctx context.Context, profileID, transferTo s c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodDelete, path, req, &resp) } -// GetReports returns a list of all user-generated reports -func (c *CoinbasePro) GetReports(ctx context.Context, profileID string, reportType string, after time.Time, limit int64, ignoreExpired bool) ([]Report, error) { +// GetAllReports returns a list of all user-generated reports +func (c *CoinbasePro) GetAllReports(ctx context.Context, profileID string, reportType string, after time.Time, limit int64, ignoreExpired bool) ([]Report, error) { var resp []Report var params Params @@ -839,85 +842,255 @@ func (c *CoinbasePro) CreateReport(ctx context.Context, reportType, year, format "email": email, "profile_id": profileID} if reportType == "account" { + req["account"] = ReportAccountStruct{StartDate: startDate.Format(time.RFC3339), + EndDate: endDate.Format(time.RFC3339), AccountID: accountID} + } + if reportType == "balance" { req["balance"] = ReportBalanceStruct{DateTime: balanceDate.Format(time.RFC3339)} } - return resp, common.ErrNotYetImplemented -} - -// GetCurrentServerTime returns the API server time -func (c *CoinbasePro) GetCurrentServerTime(ctx context.Context) (ServerTime, error) { - serverTime := ServerTime{} - return serverTime, c.SendHTTPRequest(ctx, exchange.RestSpot, coinbaseproTime, &serverTime) -} - -// MarginTransfer sends funds between a standard/default profile and a margin -// profile. -// A deposit will transfer funds from the default profile into the margin -// profile. A withdraw will transfer funds from the margin profile to the -// default profile. Withdraws will fail if they would set your margin ratio -// below the initial margin ratio requirement. -// -// amount - the amount to transfer between the default and margin profile -// transferType - either "deposit" or "withdraw" -// profileID - The id of the margin profile to deposit or withdraw from -// currency - currency to transfer, currently on "BTC" or "USD" -func (c *CoinbasePro) MarginTransfer(ctx context.Context, amount float64, transferType, profileID, currency string) (MarginTransfer, error) { - resp := MarginTransfer{} - req := make(map[string]interface{}) - req["type"] = transferType - req["amount"] = strconv.FormatFloat(amount, 'f', -1, 64) - req["currency"] = currency - req["margin_profile_id"] = profileID + if reportType == "fills" || reportType == "otc-fills" || reportType == "rfq-fills" || + reportType == "tax-invoice" { + req[reportType] = ReportFillsTaxStruct{StartDate: startDate.Format(time.RFC3339), + EndDate: endDate.Format(time.RFC3339), ProductID: productID} + } + return resp, + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproReports, req, &resp) +} + +// GetReportByID returns a single report, provided its ID +func (c *CoinbasePro) GetReportByID(ctx context.Context, reportID string) (Report, error) { + var resp Report + if reportID == "" { + return resp, errors.New("report id cannot be empty") + } + + path := fmt.Sprintf("%s/%s", coinbaseproReports, reportID) + + return resp, + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp) +} + +// GetTravelRules returns a list of all travel rule information +func (c *CoinbasePro) GetTravelRules(ctx context.Context, direction, step, address string, limit int64) ([]TravelRule, error) { + var resp []TravelRule + var params Params + params.urlVals = url.Values{} + + params.PrepareDSL(direction, step, limit) + params.urlVals.Set("address", address) + + path := common.EncodeURLValues(coinbaseproTravelRules, params.urlVals) + + return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp) +} + +// CreateTravelRule creates a travel rule entry +func (c *CoinbasePro) CreateTravelRule(ctx context.Context, address, originName, originCountry string) (TravelRule, error) { + var resp TravelRule + + req := map[string]interface{}{"address": address, "originator_name": originName, + "originator_country": originCountry} return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproMarginTransfer, req, &resp) + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproTravelRules, req, &resp) +} + +// DeleteTravelRule deletes a travel rule entry +func (c *CoinbasePro) DeleteTravelRule(ctx context.Context, address string) error { + if address == "" { + return errors.New("address cannot be empty") + } + + path := fmt.Sprintf("%s/%s", coinbaseproTravelRules, address) + + return c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodDelete, path, nil, nil) } -// GetPosition returns an overview of account profile. -func (c *CoinbasePro) GetPosition(ctx context.Context) (AccountOverview, error) { - resp := AccountOverview{} +// GetExchangeLimits returns information on payment method transfer limits, +// as well as buy/sell limits per currency +func (c *CoinbasePro) GetExchangeLimits(ctx context.Context, userID string) (ExchangeLimits, error) { + var resp ExchangeLimits + + path := fmt.Sprintf("%s/%s", coinbaseproUsers, userID) return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, coinbaseproPosition, nil, &resp) + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp) } -// ClosePosition closes a position and allowing you to repay position as well -// repayOnly - allows the position to be repaid -func (c *CoinbasePro) ClosePosition(ctx context.Context, repayOnly bool) (AccountOverview, error) { - resp := AccountOverview{} - req := make(map[string]interface{}) - req["repay_only"] = repayOnly +// UpdateSettlementPreference updates whether one wants their funds to +// automatically convert to USD, USDC, or to remain in the currency received +func (c *CoinbasePro) UpdateSettlementPreference(ctx context.Context, userID, preference string) (string, error) { + if userID == "" || preference == "" { + return "", errors.New("neither userID nor preference can be empty") + } + + req := map[string]interface{}{"settlement_preference": preference} + + path := fmt.Sprintf("%s/%s/%s", coinbaseproUsers, userID, coinbaseproSettlementPreferences) + + var resp string return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproPositionClose, req, &resp) + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPut, path, req, &resp) } -// GetCoinbaseAccounts returns a list of coinbase accounts -func (c *CoinbasePro) GetCoinbaseAccounts(ctx context.Context) ([]CoinbaseAccounts, error) { - var resp []CoinbaseAccounts +// GetAllWrappedAssets returns information on all supported wrapped assets +func (c *CoinbasePro) GetAllWrappedAssets(ctx context.Context) (AllWrappedAssetResponse, error) { + var resp AllWrappedAssetResponse return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, coinbaseproCoinbaseAccounts, nil, &resp) + c.SendHTTPRequest(ctx, exchange.RestSpot, coinbaseproWrappedAssets, &resp) } -// GetReportStatus once a report request has been accepted for processing, the -// status is available by polling the report resource endpoint. -func (c *CoinbasePro) GetReportStatus(ctx context.Context, reportID string) (Report, error) { - resp := Report{} - path := fmt.Sprintf("%s/%s", coinbaseproReports, reportID) +// GetAllStakeWraps returns details of all stake-wraps under the profile associated +// with the API key +func (c *CoinbasePro) GetAllStakeWraps(ctx context.Context, direction, from, to, status string, timestamp time.Time, limit int64) ([]StakeWrap, error) { + var resp []StakeWrap - return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp) + var params Params + params.urlVals = url.Values{} + + if !timestamp.IsZero() && !timestamp.Equal(time.Unix(0, 0)) { + params.PrepareDSL(direction, timestamp.Format(time.RFC3339), limit) + } else { + params.urlVals.Set("limit", strconv.FormatInt(limit, 10)) + } + + params.urlVals.Set("from", from) + params.urlVals.Set("to", to) + params.urlVals.Set("status", status) + + path := fmt.Sprintf("%s/%s", coinbaseproWrappedAssets, coinbaseproStakeWraps) + + path = common.EncodeURLValues(path, params.urlVals) + + return resp, + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp) } -// GetTrailingVolume this request will return your 30-day trailing volume for -// all products. -func (c *CoinbasePro) GetTrailingVolume(ctx context.Context) ([]Volume, error) { - var resp []Volume +// CreateStakeWrap stakes and wraps from one currency to another, under the profile +// associated with the API key +func (c *CoinbasePro) CreateStakeWrap(ctx context.Context, from, to string, amount float64) (StakeWrap, error) { + if from == "" || to == "" || amount == 0 { + return StakeWrap{}, errors.New("none of from, to, or amount can be empty or zero") + } + var resp StakeWrap + + req := map[string]interface{}{"from": from, "to": to, + "amount": strconv.FormatFloat(amount, 'f', -1, 64)} + + path := fmt.Sprintf("%s/%s", coinbaseproWrappedAssets, coinbaseproStakeWraps) + + return resp, + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, path, req, &resp) +} + +// GetStakeWrapByID returns details of a single stake-wrap +func (c *CoinbasePro) GetStakeWrapByID(ctx context.Context, stakeWrapID string) (StakeWrap, error) { + var resp StakeWrap + + if stakeWrapID == "" { + return resp, errors.New("stake wrap id cannot be empty") + } + + path := fmt.Sprintf("%s/%s/%s", coinbaseproWrappedAssets, coinbaseproStakeWraps, stakeWrapID) return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, coinbaseproTrailingVolume, nil, &resp) + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp) +} + +// GetWrappedAssetByID returns details of a single wrapped asset +func (c *CoinbasePro) GetWrappedAssetByID(ctx context.Context, wrappedAssetID string) (WrappedAssetResponse, error) { + var resp WrappedAssetResponse + + if wrappedAssetID == "" { + return resp, errors.New("wrapped asset id cannot be empty") + } + + path := fmt.Sprintf("%s/%s", coinbaseproWrappedAssets, wrappedAssetID) + + return resp, c.SendHTTPRequest(ctx, exchange.RestSpot, path, &resp) +} + +// GetWrappedAssetConversionRate returns the conversion rate for a wrapped asset +func (c *CoinbasePro) GetWrappedAssetConversionRate(ctx context.Context, wrappedAssetID string) (WrappedAssetConversionRate, error) { + var resp WrappedAssetConversionRate + + if wrappedAssetID == "" { + return resp, errors.New("wrapped asset id cannot be empty") + } + + path := fmt.Sprintf("%s/%s/%s", coinbaseproWrappedAssets, wrappedAssetID, coinbaseproConversionRate) + + return resp, c.SendHTTPRequest(ctx, exchange.RestSpot, path, &resp) } +// // GetCurrentServerTime returns the API server time +// func (c *CoinbasePro) GetCurrentServerTime(ctx context.Context) (ServerTime, error) { +// serverTime := ServerTime{} +// return serverTime, c.SendHTTPRequest(ctx, exchange.RestSpot, coinbaseproTime, &serverTime) +// } + +// // MarginTransfer sends funds between a standard/default profile and a margin +// // profile. +// // A deposit will transfer funds from the default profile into the margin +// // profile. A withdraw will transfer funds from the margin profile to the +// // default profile. Withdraws will fail if they would set your margin ratio +// // below the initial margin ratio requirement. +// // +// // amount - the amount to transfer between the default and margin profile +// // transferType - either "deposit" or "withdraw" +// // profileID - The id of the margin profile to deposit or withdraw from +// // currency - currency to transfer, currently on "BTC" or "USD" +// func (c *CoinbasePro) MarginTransfer(ctx context.Context, amount float64, transferType, profileID, currency string) (MarginTransfer, error) { +// resp := MarginTransfer{} +// req := make(map[string]interface{}) +// req["type"] = transferType +// req["amount"] = strconv.FormatFloat(amount, 'f', -1, 64) +// req["currency"] = currency +// req["margin_profile_id"] = profileID + +// return resp, +// c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproMarginTransfer, req, &resp) +// } + +// // GetPosition returns an overview of account profile. +// func (c *CoinbasePro) GetPosition(ctx context.Context) (AccountOverview, error) { +// resp := AccountOverview{} + +// return resp, +// c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, coinbaseproPosition, nil, &resp) +// } + +// // ClosePosition closes a position and allowing you to repay position as well +// // repayOnly - allows the position to be repaid +// func (c *CoinbasePro) ClosePosition(ctx context.Context, repayOnly bool) (AccountOverview, error) { +// resp := AccountOverview{} +// req := make(map[string]interface{}) +// req["repay_only"] = repayOnly + +// return resp, +// c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproPositionClose, req, &resp) +// } + +// // GetReportStatus once a report request has been accepted for processing, the +// // status is available by polling the report resource endpoint. +// func (c *CoinbasePro) GetReportStatus(ctx context.Context, reportID string) (Report, error) { +// resp := Report{} +// path := fmt.Sprintf("%s/%s", coinbaseproReports, reportID) + +// return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp) +// } + +// // GetTrailingVolume this request will return your 30-day trailing volume for +// // all products. +// func (c *CoinbasePro) GetTrailingVolume(ctx context.Context) ([]Volume, error) { +// var resp []Volume + +// return resp, +// c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, coinbaseproTrailingVolume, nil, &resp) +// } + // SendHTTPRequest sends an unauthenticated HTTP request func (c *CoinbasePro) SendHTTPRequest(ctx context.Context, ep exchange.URL, path string, result interface{}) error { endpoint, err := c.API.Endpoints.GetURL(ep) @@ -950,6 +1123,8 @@ func (c *CoinbasePro) SendAuthenticatedHTTPRequest(ctx context.Context, ep excha return err } + returnHead := http.Header{} + newRequest := func() (*request.Item, error) { payload := []byte("") if params != nil { @@ -979,98 +1154,96 @@ func (c *CoinbasePro) SendAuthenticatedHTTPRequest(ctx context.Context, ep excha headers["Content-Type"] = "application/json" return &request.Item{ - Method: method, - Path: endpoint + path, - Headers: headers, - Body: bytes.NewBuffer(payload), - Result: result, - Verbose: c.Verbose, - HTTPDebugging: c.HTTPDebugging, - HTTPRecording: c.HTTPRecording, + Method: method, + Path: endpoint + path, + Headers: headers, + Body: bytes.NewBuffer(payload), + Result: result, + Verbose: c.Verbose, + HTTPDebugging: c.HTTPDebugging, + HTTPRecording: c.HTTPRecording, + HeaderResponse: &returnHead, }, nil } - return c.SendPayload(ctx, request.Unset, newRequest, request.AuthenticatedRequest) + err = c.SendPayload(ctx, request.Unset, newRequest, request.AuthenticatedRequest) + fmt.Printf("Header returned: %+v\n", returnHead) + return err } -// GetFee returns an estimate of fee based on type of transaction -func (c *CoinbasePro) GetFee(ctx context.Context, feeBuilder *exchange.FeeBuilder) (float64, error) { - var fee float64 - switch feeBuilder.FeeType { - case exchange.CryptocurrencyTradeFee: - trailingVolume, err := c.GetTrailingVolume(ctx) - if err != nil { - return 0, err - } - fee = c.calculateTradingFee(trailingVolume, - feeBuilder.Pair.Base, - feeBuilder.Pair.Quote, - feeBuilder.Pair.Delimiter, - feeBuilder.PurchasePrice, - feeBuilder.Amount, - feeBuilder.IsMaker) - case exchange.InternationalBankWithdrawalFee: - fee = getInternationalBankWithdrawalFee(feeBuilder.FiatCurrency) - case exchange.InternationalBankDepositFee: - fee = getInternationalBankDepositFee(feeBuilder.FiatCurrency) - case exchange.OfflineTradeFee: - fee = getOfflineTradeFee(feeBuilder.PurchasePrice, feeBuilder.Amount) - } - - if fee < 0 { - fee = 0 - } - - return fee, nil -} - -// getOfflineTradeFee calculates the worst case-scenario trading fee -func getOfflineTradeFee(price, amount float64) float64 { - return 0.0025 * price * amount -} - -func (c *CoinbasePro) calculateTradingFee(trailingVolume []Volume, base, quote currency.Code, delimiter string, purchasePrice, amount float64, isMaker bool) float64 { - var fee float64 - for _, i := range trailingVolume { - if strings.EqualFold(i.ProductID, base.String()+delimiter+quote.String()) { - switch { - case isMaker: - fee = 0 - case i.Volume <= 10000000: - fee = 0.003 - case i.Volume > 10000000 && i.Volume <= 100000000: - fee = 0.002 - case i.Volume > 100000000: - fee = 0.001 - } - break - } - } - return fee * amount * purchasePrice -} +// // GetFee returns an estimate of fee based on type of transaction +// func (c *CoinbasePro) GetFee(ctx context.Context, feeBuilder *exchange.FeeBuilder) (float64, error) { +// var fee float64 +// switch feeBuilder.FeeType { +// case exchange.CryptocurrencyTradeFee: +// fees, err := c.GetFees(ctx) +// if err != nil { +// fee = fees.TakerFeeRate +// } else { +// fee = 0.006 +// } +// case exchange.InternationalBankWithdrawalFee: +// fee = getInternationalBankWithdrawalFee(feeBuilder.FiatCurrency) +// case exchange.InternationalBankDepositFee: +// fee = getInternationalBankDepositFee(feeBuilder.FiatCurrency) +// case exchange.OfflineTradeFee: +// fee = getOfflineTradeFee(feeBuilder.PurchasePrice, feeBuilder.Amount) +// } + +// if fee < 0 { +// fee = 0 +// } + +// return fee, nil +// } -func getInternationalBankWithdrawalFee(c currency.Code) float64 { - var fee float64 +// // getOfflineTradeFee calculates the worst case-scenario trading fee +// func getOfflineTradeFee(price, amount float64) float64 { +// return 0.0025 * price * amount +// } - if c.Equal(currency.USD) { - fee = 25 - } else if c.Equal(currency.EUR) { - fee = 0.15 - } +// func (c *CoinbasePro) calculateTradingFee(trailingVolume []Volume, base, quote currency.Code, delimiter string, purchasePrice, amount float64, isMaker bool) float64 { +// var fee float64 +// for _, i := range trailingVolume { +// if strings.EqualFold(i.ProductID, base.String()+delimiter+quote.String()) { +// switch { +// case isMaker: +// fee = 0 +// case i.Volume <= 10000000: +// fee = 0.003 +// case i.Volume > 10000000 && i.Volume <= 100000000: +// fee = 0.002 +// case i.Volume > 100000000: +// fee = 0.001 +// } +// break +// } +// } +// return fee * amount * purchasePrice +// } - return fee -} +// func getInternationalBankWithdrawalFee(c currency.Code) float64 { +// var fee float64 -func getInternationalBankDepositFee(c currency.Code) float64 { - var fee float64 +// if c.Equal(currency.USD) { +// fee = 25 +// } else if c.Equal(currency.EUR) { +// fee = 0.15 +// } - if c.Equal(currency.USD) { - fee = 10 - } else if c.Equal(currency.EUR) { - fee = 0.15 - } +// return fee +// } - return fee -} +// func getInternationalBankDepositFee(c currency.Code) float64 { +// var fee float64 + +// if c.Equal(currency.USD) { +// fee = 10 +// } else if c.Equal(currency.EUR) { +// fee = 0.15 +// } + +// return fee +// } // PrepareDSL adds the direction, step, and limit queries for pagination func (p *Params) PrepareDSL(direction, step string, limit int64) { @@ -1098,6 +1271,7 @@ func (p *Params) PrepareDateString(startDate, endDate time.Time) error { return err } +// PrepareProfIDAndProdID encodes a set of parameters indicating profile and product IDs func (p *Params) PrepareProfIDAndProdID(profileID, currencyPair string) { p.urlVals.Set("profile_id", profileID) p.urlVals.Set("product_id", currencyPair) @@ -1173,10 +1347,3 @@ func OrderbookHelper(iOD InterOrderDetail, level int32) ([]GenOrderDetail, error return gOD, nil } - -// func (c *CoinbasePro) GetTravelRules(ctx context.Context) ([]TravelRule, error) { -// var resp []TravelRule -// accounts, err := c.GetAccounts(ctx) -// path := fmt.Sprintf("/%s/", accounts[0].ID) -// return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp) -// } diff --git a/exchanges/coinbasepro/coinbasepro_test.go b/exchanges/coinbasepro/coinbasepro_test.go index fbc779de085..7021e6cc88f 100644 --- a/exchanges/coinbasepro/coinbasepro_test.go +++ b/exchanges/coinbasepro/coinbasepro_test.go @@ -13,18 +13,15 @@ import ( "time" "github.com/gorilla/websocket" + "github.com/stretchr/testify/assert" "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/common/convert" "github.com/thrasher-corp/gocryptotrader/config" - "github.com/thrasher-corp/gocryptotrader/core" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" - "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues" "github.com/thrasher-corp/gocryptotrader/exchanges/stream" - "github.com/thrasher-corp/gocryptotrader/portfolio/banking" - "github.com/thrasher-corp/gocryptotrader/portfolio/withdraw" ) var ( @@ -37,8 +34,8 @@ const ( apiKey = "" apiSecret = "" clientID = "" // passphrase you made at API CREATION - canManipulateRealOrders = false - testingInSandbox = false + canManipulateRealOrders = true + testingInSandbox = true ) func TestMain(m *testing.M) { @@ -57,16 +54,19 @@ func TestMain(m *testing.M) { if err != nil { log.Fatal("coinbasepro Setup() init error") } - gdxConfig.API.Credentials.Key = apiKey - gdxConfig.API.Credentials.Secret = apiSecret - gdxConfig.API.Credentials.ClientID = clientID - gdxConfig.API.AuthenticatedSupport = true - gdxConfig.API.AuthenticatedWebsocketSupport = true + if apiKey != "" { + gdxConfig.API.Credentials.Key = apiKey + gdxConfig.API.Credentials.Secret = apiSecret + gdxConfig.API.Credentials.ClientID = clientID + gdxConfig.API.AuthenticatedSupport = true + gdxConfig.API.AuthenticatedWebsocketSupport = true + } c.Websocket = sharedtestvalues.NewTestWebsocket() err = c.Setup(gdxConfig) if err != nil { log.Fatal("CoinbasePro setup error", err) } + c.Verbose = true os.Exit(m.Run()) } @@ -84,56 +84,48 @@ func TestStart(t *testing.T) { testWg.Wait() } -func TestGetAccounts(t *testing.T) { +func TestGetAllAccounts(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - resp, err := c.GetAccounts(context.Background()) + resp, err := c.GetAllAccounts(context.Background()) if err != nil { - t.Error("CoinBasePro GetAccounts() error", err) - } - if resp[0].ID == "" { - t.Error("CoinBasePro GetAccounts() error, no ID returned") - } - if resp[0].Currency == "" { - t.Error("CoinBasePro GetAccounts() error, no Currency returned") + t.Error("CoinBasePro GetAllAccounts() error", err) } - if resp[0].ProfileID == "" { - t.Error("CoinBasePro GetAccounts() error, no ProfileID returned") - } - + assert.NotEmpty(t, resp, "CoinBasePro GetAllAccounts() error, expected a non-empty response") } -func TestGetAccount(t *testing.T) { +func TestGetAccountByID(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - longResp, err := c.GetAccounts(context.Background()) + longResp, err := c.GetAllAccounts(context.Background()) if err != nil { - t.Error("CoinBasePro GetAccounts() error", err) + t.Error("CoinBasePro GetAllAccounts() error", err) } - shortResp, err := c.GetAccount(context.Background(), longResp[0].ID) + shortResp, err := c.GetAccountByID(context.Background(), longResp[0].ID) if err != nil { - t.Error("CoinBasePro GetAccount() error", err) + t.Error("CoinBasePro GetAccountByID() error", err) } if *shortResp != longResp[0] { - t.Error("CoinBasePro GetAccount() error, mismatched responses") + t.Error("CoinBasePro GetAccountByID() error, mismatched responses") } } func TestGetHolds(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - accID, err := c.GetAccounts(context.Background()) + accID, err := c.GetAllAccounts(context.Background()) if err != nil { - t.Error("CoinBasePro GetAccounts() error", err) + t.Error("CoinBasePro GetAllAccounts() error", err) } - _, err = c.GetHolds(context.Background(), accID[0].ID, pageNone, "", 2) + resp, err := c.GetHolds(context.Background(), accID[1].ID, pageNone, "1", 2) if err != nil { t.Error("CoinBasePro GetHolds() error", err) } + log.Printf("%+v", resp) } func TestGetAccountLedger(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - accID, err := c.GetAccounts(context.Background()) + accID, err := c.GetAllAccounts(context.Background()) if err != nil { - t.Error("CoinBasePro GetAccounts() error", err) + t.Error("CoinBasePro GetAllAccounts() error", err) } _, err = c.GetAccountLedger(context.Background(), accID[0].ID, pageBefore, "", "a", time.Unix(1, 1), time.Now(), 3) @@ -144,9 +136,9 @@ func TestGetAccountLedger(t *testing.T) { func TestGetAccountTransfers(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - accID, err := c.GetAccounts(context.Background()) + accID, err := c.GetAllAccounts(context.Background()) if err != nil { - t.Error("CoinBasePro GetAccounts() error", err) + t.Error("CoinBasePro GetAllAccounts() error", err) } _, err = c.GetAccountTransfers(context.Background(), accID[0].ID, "", "", "", 3) if err != nil { @@ -186,7 +178,7 @@ func TestDeleteAddress(t *testing.T) { func TestGetCoinbaseWallets(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - _, err := c.GetCoinbaseAccounts(context.Background()) + _, err := c.GetCoinbaseWallets(context.Background()) if err != nil { t.Error("CoinBasePro GetCoinbaseAccounts() error", err) } @@ -196,7 +188,7 @@ func TestGenerateCryptoAddress(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) accID, err := c.GetCoinbaseWallets(context.Background()) if err != nil { - t.Error("CoinBasePro GetAccounts() error", err) + t.Error("CoinBasePro GetAllAccounts() error", err) } _, err = c.GenerateCryptoAddress(context.Background(), accID[0].ID, "", "") if err != nil { @@ -220,20 +212,20 @@ func TestGetConversionByID(t *testing.T) { } } -func TestGetCurrencies(t *testing.T) { - _, err := c.GetCurrencies(context.Background()) +func TestGetAllCurrencies(t *testing.T) { + _, err := c.GetAllCurrencies(context.Background()) if err != nil { - t.Error("GetCurrencies() error", err) + t.Error("GetAllCurrencies() error", err) } } -func TestGetCurrenciesByID(t *testing.T) { - resp, err := c.GetCurrenciesByID(context.Background(), "BTC") +func TestGetCurrencyByID(t *testing.T) { + resp, err := c.GetCurrencyByID(context.Background(), "BTC") if err != nil { - t.Error("GetCurrenciesByID() error", err) + t.Error("GetCurrencyByID() error", err) } if resp.Name != "Bitcoin" { - t.Errorf("GetCurrenciesByID() error, incorrect name returned, expected 'Bitcoin', got '%s'", + t.Errorf("GetCurrencyByID() error, incorrect name returned, expected 'Bitcoin', got '%s'", resp.Name) } } @@ -313,12 +305,11 @@ func TestGetWithdrawalFeeEstimate(t *testing.T) { if err == nil { t.Error("CoinBasePro GetWithdrawalFeeEstimate() error, expected error due to empty field") } - resp, err := c.GetWithdrawalFeeEstimate(context.Background(), "This test is not", "yet implemented", + _, err = c.GetWithdrawalFeeEstimate(context.Background(), "This test is not", "yet implemented", "due to not knowing a valid network string") if err == nil { t.Error("This should have errored out due to an improper network string") } - log.Printf("%+v, %+v", resp, err) } func TestWithdrawViaPaymentMethod(t *testing.T) { @@ -358,15 +349,14 @@ func TestGetFills(t *testing.T) { } } -func TestGetOrders(t *testing.T) { +func TestGetAllOrders(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) status := []string{"open", "pending", "active", "done"} - resp, err := c.GetOrders(context.Background(), "", "", "", "", "", "", "", time.Unix(1, 1), time.Now(), 5, status) + _, err := c.GetAllOrders(context.Background(), "", "", "", "", "", "", "", time.Unix(1, 1), time.Now(), 5, status) if err != nil { - t.Error("CoinBasePro GetOrders() error", err) + t.Error("CoinBasePro GetAllOrders() error", err) } - log.Printf("%+v", resp) } func TestCancelAllExistingOrders(t *testing.T) { @@ -379,19 +369,21 @@ func TestCancelAllExistingOrders(t *testing.T) { func TestPlaceOrder(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) - _, err := c.PlaceOrder(context.Background(), "this", "test", "has", "not", "been", "implemented", "awaiting", "sandbox", - "testing", 1, 1, 1, 1, false) - if err == nil { - t.Error("This probably should have errored out due to not being implemented") + + for x := 0; x < 550; x++ { + _, _ = c.PlaceOrder(context.Background(), "this", "", "sell", "BTC-USD", "", "implemented", "", "sandbox", + "testing", 0, 2<<30, 1, 0, false) + } + + // log.Printf("Response: %+v\nError: %+v", resp, err) } -func TestGetOrder(t *testing.T) { +func TestGetOrderByID(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - _, err := c.GetOrder(context.Background(), "This test is not", "yet implemented", true) - if err == nil { - t.Error("This should have errored out due to not being implemented") - } + resp, err := c.GetOrderByID(context.Background(), "940d4bf3-933b-4714-a702-155f82c3e739", "spot", false) + + log.Printf("Response: %+v\nError: %+v", resp, err) } func TestCancelExistingOrder(t *testing.T) { @@ -413,27 +405,27 @@ func TestGetSignedPrices(t *testing.T) { } } -func TestGetProducts(t *testing.T) { - resp, err := c.GetProducts(context.Background(), "") +func TestGetAllProducts(t *testing.T) { + resp, err := c.GetAllProducts(context.Background(), "") if err != nil { - t.Error("Coinbase, GetProducts() Error:", err) + t.Error("Coinbase, GetAllProducts() Error:", err) } if resp[0].ID == "" { - t.Error("Coinbase, GetProducts() Error, expected non-empty string") + t.Error("Coinbase, GetAllProducts() Error, expected non-empty string") } } -func TestGetProduct(t *testing.T) { - _, err := c.GetProduct(context.Background(), "") +func TestGetProductByID(t *testing.T) { + _, err := c.GetProductByID(context.Background(), "") if err == nil { - t.Error("Coinbase, GetProduct() Error, expected an error due to nonexistent pair") + t.Error("Coinbase, GetProductByID() Error, expected an error due to nonexistent pair") } - resp, err := c.GetProduct(context.Background(), "BTC-USD") + resp, err := c.GetProductByID(context.Background(), "BTC-USD") if err != nil { - t.Error("Coinbase, GetProduct() Error:", err) + t.Error("Coinbase, GetProductByID() Error:", err) } if resp.ID != "BTC-USD" { - t.Error("Coinbase, GetProduct() Error, expected BTC-USD") + t.Error("Coinbase, GetProductByID() Error, expected BTC-USD") } } @@ -515,12 +507,12 @@ func TestGetTrades(t *testing.T) { } } -func TestGetProfiles(t *testing.T) { +func TestGetAllProfiles(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) active := true - _, err := c.GetProfiles(context.Background(), &active) + _, err := c.GetAllProfiles(context.Background(), &active) if err != nil { - t.Error("GetProfiles() error", err) + t.Error("GetAllProfiles() error", err) } } @@ -551,9 +543,9 @@ func TestTransferBetweenProfiles(t *testing.T) { func TestGetProfileByID(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - resp, err := c.GetProfiles(context.Background(), nil) + resp, err := c.GetAllProfiles(context.Background(), nil) if err != nil { - t.Error("GetProfiles() error", err) + t.Error("GetAllProfiles() error", err) } active := true resp2, err := c.GetProfileByID(context.Background(), resp[0].ID, &active) @@ -591,526 +583,665 @@ func TestDeleteProfile(t *testing.T) { func TestGetReport(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - prof, err := c.GetProfiles(context.Background(), nil) + prof, err := c.GetAllProfiles(context.Background(), nil) if err != nil { - t.Error("GetProfiles() error", err) + t.Error("GetAllProfiles() error", err) } - c.Verbose = true - resp, err := c.GetReports(context.Background(), prof[0].ID, "account", time.Time{}, 1000, false) - log.Printf("%+v", resp) + _, err = c.GetAllReports(context.Background(), prof[0].ID, "account", time.Time{}, 1000, false) if err != nil { - t.Error("GetReport() error", err) + t.Error("GetAllReports() error", err) } } -func TestGetCurrentServerTime(t *testing.T) { - _, err := c.GetCurrentServerTime(context.Background()) +func TestCreateReport(t *testing.T) { + sharedtestvalues.SkipTestIfCannotManipulateOrders(t, c, canManipulateRealOrders) + prof, err := c.GetAllProfiles(context.Background(), nil) if err != nil { - t.Error("GetServerTime() error", err) + t.Error("GetAllProfiles() error", err) + } + _, err = c.CreateReport(context.Background(), "this", "test", "is", "not", prof[0].ID, "yet", "implemented", + time.Time{}, time.Time{}, time.Time{}) + if err == nil { + t.Error("Coinbase, CreateReport() Error, expected an error due to un-implemented test") } } -func TestWrapperGetServerTime(t *testing.T) { - t.Parallel() - st, err := c.GetServerTime(context.Background(), asset.Spot) - if !errors.Is(err, nil) { - t.Fatalf("received: '%v' but expected: '%v'", err, nil) +func TestGetReportByID(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + prof, err := c.GetAllProfiles(context.Background(), nil) + if err != nil { + t.Error("GetAllProfiles() error", err) } - - if st.IsZero() { - t.Fatal("expected a time") + resp, err := c.GetAllReports(context.Background(), prof[0].ID, "account", time.Time{}, 1000, false) + if err != nil { + t.Error("GetAllReports() error", err) + } + if len(resp) == 0 { + t.Log("No reports found, skipping test") + } else { + _, err = c.GetReportByID(context.Background(), resp[0].ID) + if err != nil { + t.Error("GetReportByID() error", err) + } } } -func TestAuthRequests(t *testing.T) { - t.Parallel() +func TestGetTravelRules(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - - _, err := c.GetAccounts(context.Background()) + _, err := c.GetTravelRules(context.Background(), "", "", "", 0) if err != nil { - t.Error("GetAccounts() error", err) - } - accountResponse, err := c.GetAccount(context.Background(), - "13371337-1337-1337-1337-133713371337") - if accountResponse.ID != "" { - t.Error("Expecting no data returned") + t.Error("GetTravelRules() error", err) } +} + +func TestCreateTravelRule(t *testing.T) { + sharedtestvalues.SkipTestIfCannotManipulateOrders(t, c, canManipulateRealOrders) + _, err := c.CreateTravelRule(context.Background(), "this test", "not yet", "implemented") if err == nil { - t.Error("Expecting error") + t.Error("Coinbase, CreateTravelRule() Error, expected an error due to unimplemented test") } +} + +func TestDeleteTravelRule(t *testing.T) { + sharedtestvalues.SkipTestIfCannotManipulateOrders(t, c, canManipulateRealOrders) + err := c.DeleteTravelRule(context.Background(), "this test is not yet implemented") if err == nil { - t.Error("Expecting error") + t.Error("Coinbase, DeleteTravelRule() Error, expected an error due to unimplemented test") } - // getHoldsResponse, err := c.GetHolds(context.Background(), - // "13371337-1337-1337-1337-133713371337") - // if len(getHoldsResponse) > 0 { - // t.Error("Expecting no data returned") - // } - if err == nil { - t.Error("Expecting error") +} + +func TestGetExchangeLimits(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + acc, err := c.GetAllAccounts(context.Background()) + if err != nil { + t.Error("GetAllAccounts() error", err) } - marginTransferResponse, err := c.MarginTransfer(context.Background(), - 1, "withdraw", "13371337-1337-1337-1337-133713371337", "BTC") - if marginTransferResponse.ID != "" { - t.Error("Expecting no data returned") + _, err = c.GetExchangeLimits(context.Background(), acc[0].ID) + if err != nil { + t.Error("GetExchangeLimits() error", err) } +} + +func TestUpdateSettlementPreference(t *testing.T) { + sharedtestvalues.SkipTestIfCannotManipulateOrders(t, c, canManipulateRealOrders) + _, err := c.UpdateSettlementPreference(context.Background(), "this test", "not implemented") if err == nil { - t.Error("Expecting error") + t.Error("Coinbase, UpdateSettlementPreference() Error, expected an error due to unimplemented test") } - _, err = c.GetPosition(context.Background()) - if err == nil { - t.Error("Expecting error") +} + +func TestGetAllWrappedAssets(t *testing.T) { + _, err := c.GetAllWrappedAssets(context.Background()) + if err != nil { + t.Error("GetAllWrappedAssets() error", err) } - _, err = c.ClosePosition(context.Background(), false) - if err == nil { - t.Error("Expecting error") +} + +func TestGetAllStakeWraps(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + _, err := c.GetAllStakeWraps(context.Background(), "", "ETH", "CBETH", "", time.Time{}, 1) + if err != nil { + t.Error("GetAllStakeWraps() error", err) } - _, err = c.GetCoinbaseAccounts(context.Background()) + _, err = c.GetAllStakeWraps(context.Background(), "after", "ETH", "CBETH", "", time.Unix(1, 1), 1000) if err != nil { - t.Error("GetCoinbaseAccounts() error", err) + t.Error("GetAllStakeWraps() error", err) } } -func setFeeBuilder() *exchange.FeeBuilder { - return &exchange.FeeBuilder{ - Amount: 1, - FeeType: exchange.CryptocurrencyTradeFee, - Pair: testPair, - PurchasePrice: 1, +func TestCreateStakeWrap(t *testing.T) { + sharedtestvalues.SkipTestIfCannotManipulateOrders(t, c, canManipulateRealOrders) + _, err := c.CreateStakeWrap(context.Background(), "", "", 0) + if err == nil { + t.Error("Coinbase, CreateStakeWrap() Error, expected an error due to empty fields") + } + _, err = c.CreateStakeWrap(context.Background(), "this test", "is not implemented", 1) + if err == nil { + t.Error("Coinbase, CreateStakeWrap() Error, expected an error due to unimplemented test") } } -// TestGetFeeByTypeOfflineTradeFee logic test -func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { - var feeBuilder = setFeeBuilder() - _, err := c.GetFeeByType(context.Background(), feeBuilder) +func TestGetStakeWrapByID(t *testing.T) { + resp, err := c.GetAllStakeWraps(context.Background(), "", "ETH", "CBETH", "", time.Time{}, 1) if err != nil { - t.Fatal(err) + t.Error("GetAllStakeWraps() error", err) } - if !sharedtestvalues.AreAPICredentialsSet(c) { - if feeBuilder.FeeType != exchange.OfflineTradeFee { - t.Errorf("Expected %v, received %v", exchange.OfflineTradeFee, feeBuilder.FeeType) - } + if len(resp) == 0 { + t.Log("No stake wraps found, skipping test") } else { - if feeBuilder.FeeType != exchange.CryptocurrencyTradeFee { - t.Errorf("Expected %v, received %v", exchange.CryptocurrencyTradeFee, feeBuilder.FeeType) + _, err = c.GetStakeWrapByID(context.Background(), resp[0].ID) + if err != nil { + t.Error("GetStakeWrapByID() error", err) } } } -func TestGetFee(t *testing.T) { - var feeBuilder = setFeeBuilder() +func TestGetWrappedAssetByID(t *testing.T) { + _, err := c.GetWrappedAssetByID(context.Background(), "") + if err == nil { + t.Error("Coinbase, GetWrappedAssetByID() Error, expected an error due to empty fields") + } + _, err = c.GetWrappedAssetByID(context.Background(), "CBETH") + if err != nil { + t.Error("GetWrappedAssetByID() error", err) + } +} - if sharedtestvalues.AreAPICredentialsSet(c) { - // CryptocurrencyTradeFee Basic - if _, err := c.GetFee(context.Background(), feeBuilder); err != nil { - t.Error(err) - } +func TestGetWrappedAssetConversionRate(t *testing.T) { + _, err := c.GetWrappedAssetConversionRate(context.Background(), "") + if err == nil { + t.Error("Coinbase, GetWrappedAssetConversionRate() Error, expected an error due to empty fields") + } + _, err = c.GetWrappedAssetConversionRate(context.Background(), "CBETH") + if err != nil { + t.Error("GetWrappedAssetConversionRate() error", err) + } +} - // CryptocurrencyTradeFee High quantity - feeBuilder = setFeeBuilder() - feeBuilder.Amount = 1000 - feeBuilder.PurchasePrice = 1000 - if _, err := c.GetFee(context.Background(), feeBuilder); err != nil { - t.Error(err) - } +// func TestGetCurrentServerTime(t *testing.T) { +// _, err := c.GetCurrentServerTime(context.Background()) +// if err != nil { +// t.Error("GetServerTime() error", err) +// } +// } - // CryptocurrencyTradeFee IsMaker - feeBuilder = setFeeBuilder() - feeBuilder.IsMaker = true - if _, err := c.GetFee(context.Background(), feeBuilder); err != nil { - t.Error(err) - } +// func TestWrapperGetServerTime(t *testing.T) { +// t.Parallel() +// st, err := c.GetServerTime(context.Background(), asset.Spot) +// if !errors.Is(err, nil) { +// t.Fatalf("received: '%v' but expected: '%v'", err, nil) +// } - // CryptocurrencyTradeFee Negative purchase price - feeBuilder = setFeeBuilder() - feeBuilder.PurchasePrice = -1000 - if _, err := c.GetFee(context.Background(), feeBuilder); err != nil { - t.Error(err) - } - } +// if st.IsZero() { +// t.Fatal("expected a time") +// } +// } - // CryptocurrencyWithdrawalFee Basic - feeBuilder = setFeeBuilder() - feeBuilder.FeeType = exchange.CryptocurrencyWithdrawalFee - if _, err := c.GetFee(context.Background(), feeBuilder); err != nil { - t.Error(err) - } +// func TestAuthRequests(t *testing.T) { +// t.Parallel() +// sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - // CryptocurrencyDepositFee Basic - feeBuilder = setFeeBuilder() - feeBuilder.FeeType = exchange.CryptocurrencyDepositFee - if _, err := c.GetFee(context.Background(), feeBuilder); err != nil { - t.Error(err) - } +// _, err := c.GetAllAccounts(context.Background()) +// if err != nil { +// t.Error("GetAllAccounts() error", err) +// } +// accountResponse, err := c.GetAccountByID(context.Background(), +// "13371337-1337-1337-1337-133713371337") +// if accountResponse.ID != "" { +// t.Error("Expecting no data returned") +// } +// if err == nil { +// t.Error("Expecting error") +// } +// if err == nil { +// t.Error("Expecting error") +// } +// // getHoldsResponse, err := c.GetHolds(context.Background(), +// // "13371337-1337-1337-1337-133713371337") +// // if len(getHoldsResponse) > 0 { +// // t.Error("Expecting no data returned") +// // } +// if err == nil { +// t.Error("Expecting error") +// } +// marginTransferResponse, err := c.MarginTransfer(context.Background(), +// 1, "withdraw", "13371337-1337-1337-1337-133713371337", "BTC") +// if marginTransferResponse.ID != "" { +// t.Error("Expecting no data returned") +// } +// if err == nil { +// t.Error("Expecting error") +// } +// _, err = c.GetPosition(context.Background()) +// if err == nil { +// t.Error("Expecting error") +// } +// _, err = c.ClosePosition(context.Background(), false) +// if err == nil { +// t.Error("Expecting error") +// } +// } - // InternationalBankDepositFee Basic - feeBuilder = setFeeBuilder() - feeBuilder.FeeType = exchange.InternationalBankDepositFee - feeBuilder.FiatCurrency = currency.EUR - if _, err := c.GetFee(context.Background(), feeBuilder); err != nil { - t.Error(err) - } +// func setFeeBuilder() *exchange.FeeBuilder { +// return &exchange.FeeBuilder{ +// Amount: 1, +// FeeType: exchange.CryptocurrencyTradeFee, +// Pair: testPair, +// PurchasePrice: 1, +// } +// } - // InternationalBankWithdrawalFee Basic - feeBuilder = setFeeBuilder() - feeBuilder.FeeType = exchange.InternationalBankWithdrawalFee - feeBuilder.FiatCurrency = currency.USD - if _, err := c.GetFee(context.Background(), feeBuilder); err != nil { - t.Error(err) - } -} +// // TestGetFeeByTypeOfflineTradeFee logic test +// func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { +// var feeBuilder = setFeeBuilder() +// _, err := c.GetFeeByType(context.Background(), feeBuilder) +// if err != nil { +// t.Fatal(err) +// } +// if !sharedtestvalues.AreAPICredentialsSet(c) { +// if feeBuilder.FeeType != exchange.OfflineTradeFee { +// t.Errorf("Expected %v, received %v", exchange.OfflineTradeFee, feeBuilder.FeeType) +// } +// } else { +// if feeBuilder.FeeType != exchange.CryptocurrencyTradeFee { +// t.Errorf("Expected %v, received %v", exchange.CryptocurrencyTradeFee, feeBuilder.FeeType) +// } +// } +// } -func TestCalculateTradingFee(t *testing.T) { - t.Parallel() - // uppercase - var volume = []Volume{ - { - ProductID: "BTC_USD", - Volume: 100, - }, - } +// func TestGetFee(t *testing.T) { +// var feeBuilder = setFeeBuilder() + +// if sharedtestvalues.AreAPICredentialsSet(c) { +// // CryptocurrencyTradeFee Basic +// if _, err := c.GetFee(context.Background(), feeBuilder); err != nil { +// t.Error(err) +// } + +// // CryptocurrencyTradeFee High quantity +// feeBuilder = setFeeBuilder() +// feeBuilder.Amount = 1000 +// feeBuilder.PurchasePrice = 1000 +// if _, err := c.GetFee(context.Background(), feeBuilder); err != nil { +// t.Error(err) +// } + +// // CryptocurrencyTradeFee IsMaker +// feeBuilder = setFeeBuilder() +// feeBuilder.IsMaker = true +// if _, err := c.GetFee(context.Background(), feeBuilder); err != nil { +// t.Error(err) +// } + +// // CryptocurrencyTradeFee Negative purchase price +// feeBuilder = setFeeBuilder() +// feeBuilder.PurchasePrice = -1000 +// if _, err := c.GetFee(context.Background(), feeBuilder); err != nil { +// t.Error(err) +// } +// } - if resp := c.calculateTradingFee(volume, currency.BTC, currency.USD, "_", 1, 1, false); resp != float64(0.003) { - t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0.003), resp) - } +// // CryptocurrencyWithdrawalFee Basic +// feeBuilder = setFeeBuilder() +// feeBuilder.FeeType = exchange.CryptocurrencyWithdrawalFee +// if _, err := c.GetFee(context.Background(), feeBuilder); err != nil { +// t.Error(err) +// } - // lowercase - volume = []Volume{ - { - ProductID: "btc_usd", - Volume: 100, - }, - } +// // CryptocurrencyDepositFee Basic +// feeBuilder = setFeeBuilder() +// feeBuilder.FeeType = exchange.CryptocurrencyDepositFee +// if _, err := c.GetFee(context.Background(), feeBuilder); err != nil { +// t.Error(err) +// } - if resp := c.calculateTradingFee(volume, currency.BTC, currency.USD, "_", 1, 1, false); resp != float64(0.003) { - t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0.003), resp) - } +// // InternationalBankDepositFee Basic +// feeBuilder = setFeeBuilder() +// feeBuilder.FeeType = exchange.InternationalBankDepositFee +// feeBuilder.FiatCurrency = currency.EUR +// if _, err := c.GetFee(context.Background(), feeBuilder); err != nil { +// t.Error(err) +// } - // mixedCase - volume = []Volume{ - { - ProductID: "btc_USD", - Volume: 100, - }, - } +// // InternationalBankWithdrawalFee Basic +// feeBuilder = setFeeBuilder() +// feeBuilder.FeeType = exchange.InternationalBankWithdrawalFee +// feeBuilder.FiatCurrency = currency.USD +// if _, err := c.GetFee(context.Background(), feeBuilder); err != nil { +// t.Error(err) +// } +// } - if resp := c.calculateTradingFee(volume, currency.BTC, currency.USD, "_", 1, 1, false); resp != float64(0.003) { - t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0.003), resp) - } +// func TestCalculateTradingFee(t *testing.T) { +// t.Parallel() +// // uppercase +// var volume = []Volume{ +// { +// ProductID: "BTC_USD", +// Volume: 100, +// }, +// } - // medium volume - volume = []Volume{ - { - ProductID: "btc_USD", - Volume: 10000001, - }, - } +// if resp := c.calculateTradingFee(volume, currency.BTC, currency.USD, "_", 1, 1, false); resp != float64(0.003) { +// t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0.003), resp) +// } - if resp := c.calculateTradingFee(volume, currency.BTC, currency.USD, "_", 1, 1, false); resp != float64(0.002) { - t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0.002), resp) - } +// // lowercase +// volume = []Volume{ +// { +// ProductID: "btc_usd", +// Volume: 100, +// }, +// } - // high volume - volume = []Volume{ - { - ProductID: "btc_USD", - Volume: 100000010000, - }, - } +// if resp := c.calculateTradingFee(volume, currency.BTC, currency.USD, "_", 1, 1, false); resp != float64(0.003) { +// t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0.003), resp) +// } - if resp := c.calculateTradingFee(volume, currency.BTC, currency.USD, "_", 1, 1, false); resp != float64(0.001) { - t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0.001), resp) - } +// // mixedCase +// volume = []Volume{ +// { +// ProductID: "btc_USD", +// Volume: 100, +// }, +// } - // no match - volume = []Volume{ - { - ProductID: "btc_beeteesee", - Volume: 100000010000, - }, - } +// if resp := c.calculateTradingFee(volume, currency.BTC, currency.USD, "_", 1, 1, false); resp != float64(0.003) { +// t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0.003), resp) +// } - if resp := c.calculateTradingFee(volume, currency.BTC, currency.USD, "_", 1, 1, false); resp != float64(0) { - t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) - } +// // medium volume +// volume = []Volume{ +// { +// ProductID: "btc_USD", +// Volume: 10000001, +// }, +// } - // taker - volume = []Volume{ - { - ProductID: "btc_USD", - Volume: 100000010000, - }, - } +// if resp := c.calculateTradingFee(volume, currency.BTC, currency.USD, "_", 1, 1, false); resp != float64(0.002) { +// t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0.002), resp) +// } - if resp := c.calculateTradingFee(volume, currency.BTC, currency.USD, "_", 1, 1, true); resp != float64(0) { - t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) - } -} +// // high volume +// volume = []Volume{ +// { +// ProductID: "btc_USD", +// Volume: 100000010000, +// }, +// } -func TestFormatWithdrawPermissions(t *testing.T) { - expectedResult := exchange.AutoWithdrawCryptoWithAPIPermissionText + " & " + exchange.AutoWithdrawFiatWithAPIPermissionText - withdrawPermissions := c.FormatWithdrawPermissions() - if withdrawPermissions != expectedResult { - t.Errorf("Expected: %s, Received: %s", expectedResult, withdrawPermissions) - } -} +// if resp := c.calculateTradingFee(volume, currency.BTC, currency.USD, "_", 1, 1, false); resp != float64(0.001) { +// t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0.001), resp) +// } -func TestGetActiveOrders(t *testing.T) { - var getOrdersRequest = order.MultiOrderRequest{ - Type: order.AnyType, - AssetType: asset.Spot, - Pairs: []currency.Pair{testPair}, - Side: order.AnySide, - } +// // no match +// volume = []Volume{ +// { +// ProductID: "btc_beeteesee", +// Volume: 100000010000, +// }, +// } - _, err := c.GetActiveOrders(context.Background(), &getOrdersRequest) - if sharedtestvalues.AreAPICredentialsSet(c) && err != nil { - t.Errorf("Could not get open orders: %s", err) - } else if !sharedtestvalues.AreAPICredentialsSet(c) && err == nil { - t.Error("Expecting an error when no keys are set") - } -} +// if resp := c.calculateTradingFee(volume, currency.BTC, currency.USD, "_", 1, 1, false); resp != float64(0) { +// t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) +// } -func TestGetOrderHistory(t *testing.T) { - var getOrdersRequest = order.MultiOrderRequest{ - Type: order.AnyType, - AssetType: asset.Spot, - Pairs: []currency.Pair{testPair}, - Side: order.AnySide, - } +// // taker +// volume = []Volume{ +// { +// ProductID: "btc_USD", +// Volume: 100000010000, +// }, +// } - _, err := c.GetOrderHistory(context.Background(), &getOrdersRequest) - if sharedtestvalues.AreAPICredentialsSet(c) && err != nil { - t.Errorf("Could not get order history: %s", err) - } else if !sharedtestvalues.AreAPICredentialsSet(c) && err == nil { - t.Error("Expecting an error when no keys are set") - } +// if resp := c.calculateTradingFee(volume, currency.BTC, currency.USD, "_", 1, 1, true); resp != float64(0) { +// t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) +// } +// } - getOrdersRequest.Pairs = []currency.Pair{} - _, err = c.GetOrderHistory(context.Background(), &getOrdersRequest) - if sharedtestvalues.AreAPICredentialsSet(c) && err != nil { - t.Errorf("Could not get order history: %s", err) - } else if !sharedtestvalues.AreAPICredentialsSet(c) && err == nil { - t.Error("Expecting an error when no keys are set") - } +// func TestFormatWithdrawPermissions(t *testing.T) { +// expectedResult := exchange.AutoWithdrawCryptoWithAPIPermissionText + " & " + exchange.AutoWithdrawFiatWithAPIPermissionText +// withdrawPermissions := c.FormatWithdrawPermissions() +// if withdrawPermissions != expectedResult { +// t.Errorf("Expected: %s, Received: %s", expectedResult, withdrawPermissions) +// } +// } - getOrdersRequest.Pairs = nil - _, err = c.GetOrderHistory(context.Background(), &getOrdersRequest) - if sharedtestvalues.AreAPICredentialsSet(c) && err != nil { - t.Errorf("Could not get order history: %s", err) - } else if !sharedtestvalues.AreAPICredentialsSet(c) && err == nil { - t.Error("Expecting an error when no keys are set") - } -} +// func TestGetActiveOrders(t *testing.T) { +// var getOrdersRequest = order.MultiOrderRequest{ +// Type: order.AnyType, +// AssetType: asset.Spot, +// Pairs: []currency.Pair{testPair}, +// Side: order.AnySide, +// } -// Any tests below this line have the ability to impact your orders on the exchange. Enable canManipulateRealOrders to run them -// ---------------------------------------------------------------------------------------------------------------------------- +// _, err := c.GetActiveOrders(context.Background(), &getOrdersRequest) +// if sharedtestvalues.AreAPICredentialsSet(c) && err != nil { +// t.Errorf("Could not get open orders: %s", err) +// } else if !sharedtestvalues.AreAPICredentialsSet(c) && err == nil { +// t.Error("Expecting an error when no keys are set") +// } +// } -func TestSubmitOrder(t *testing.T) { - t.Parallel() - sharedtestvalues.SkipTestIfCannotManipulateOrders(t, c, canManipulateRealOrders) +// func TestGetOrderHistory(t *testing.T) { +// var getOrdersRequest = order.MultiOrderRequest{ +// Type: order.AnyType, +// AssetType: asset.Spot, +// Pairs: []currency.Pair{testPair}, +// Side: order.AnySide, +// } - // limit order - var orderSubmission = &order.Submit{ - Exchange: c.Name, - Pair: currency.Pair{ - Delimiter: "-", - Base: currency.BTC, - Quote: currency.USD, - }, - Side: order.Buy, - Type: order.Limit, - Price: 1, - Amount: 0.001, - ClientID: "meowOrder", - AssetType: asset.Spot, - } - response, err := c.SubmitOrder(context.Background(), orderSubmission) - if sharedtestvalues.AreAPICredentialsSet(c) && (err != nil || response.Status != order.New) { - t.Errorf("Order failed to be placed: %v", err) - } else if !sharedtestvalues.AreAPICredentialsSet(c) && err == nil { - t.Error("Expecting an error when no keys are set") - } - - // market order from amount - orderSubmission = &order.Submit{ - Exchange: c.Name, - Pair: currency.Pair{ - Delimiter: "-", - Base: currency.BTC, - Quote: currency.USD, - }, - Side: order.Buy, - Type: order.Market, - Amount: 0.001, - ClientID: "meowOrder", - AssetType: asset.Spot, - } - response, err = c.SubmitOrder(context.Background(), orderSubmission) - if sharedtestvalues.AreAPICredentialsSet(c) && (err != nil || response.Status != order.New) { - t.Errorf("Order failed to be placed: %v", err) - } else if !sharedtestvalues.AreAPICredentialsSet(c) && err == nil { - t.Error("Expecting an error when no keys are set") - } - - // market order from quote amount - orderSubmission = &order.Submit{ - Exchange: c.Name, - Pair: currency.Pair{ - Delimiter: "-", - Base: currency.BTC, - Quote: currency.USD, - }, - Side: order.Buy, - Type: order.Market, - QuoteAmount: 1, - ClientID: "meowOrder", - AssetType: asset.Spot, - } - response, err = c.SubmitOrder(context.Background(), orderSubmission) - if sharedtestvalues.AreAPICredentialsSet(c) && (err != nil || response.Status != order.New) { - t.Errorf("Order failed to be placed: %v", err) - } else if !sharedtestvalues.AreAPICredentialsSet(c) && err == nil { - t.Error("Expecting an error when no keys are set") - } -} +// _, err := c.GetOrderHistory(context.Background(), &getOrdersRequest) +// if sharedtestvalues.AreAPICredentialsSet(c) && err != nil { +// t.Errorf("Could not get order history: %s", err) +// } else if !sharedtestvalues.AreAPICredentialsSet(c) && err == nil { +// t.Error("Expecting an error when no keys are set") +// } -func TestCancelExchangeOrder(t *testing.T) { - t.Parallel() - sharedtestvalues.SkipTestIfCannotManipulateOrders(t, c, canManipulateRealOrders) +// getOrdersRequest.Pairs = []currency.Pair{} +// _, err = c.GetOrderHistory(context.Background(), &getOrdersRequest) +// if sharedtestvalues.AreAPICredentialsSet(c) && err != nil { +// t.Errorf("Could not get order history: %s", err) +// } else if !sharedtestvalues.AreAPICredentialsSet(c) && err == nil { +// t.Error("Expecting an error when no keys are set") +// } - var orderCancellation = &order.Cancel{ - OrderID: "1", - WalletAddress: core.BitcoinDonationAddress, - AccountID: "1", - Pair: testPair, - AssetType: asset.Spot, - } +// getOrdersRequest.Pairs = nil +// _, err = c.GetOrderHistory(context.Background(), &getOrdersRequest) +// if sharedtestvalues.AreAPICredentialsSet(c) && err != nil { +// t.Errorf("Could not get order history: %s", err) +// } else if !sharedtestvalues.AreAPICredentialsSet(c) && err == nil { +// t.Error("Expecting an error when no keys are set") +// } +// } - err := c.CancelOrder(context.Background(), orderCancellation) - if !sharedtestvalues.AreAPICredentialsSet(c) && err == nil { - t.Error("Expecting an error when no keys are set") - } - if sharedtestvalues.AreAPICredentialsSet(c) && err != nil { - t.Errorf("Could not cancel orders: %v", err) - } -} +// // Any tests below this line have the ability to impact your orders on the exchange. Enable canManipulateRealOrders to run them +// // ---------------------------------------------------------------------------------------------------------------------------- + +// func TestSubmitOrder(t *testing.T) { +// t.Parallel() +// sharedtestvalues.SkipTestIfCannotManipulateOrders(t, c, canManipulateRealOrders) + +// // limit order +// var orderSubmission = &order.Submit{ +// Exchange: c.Name, +// Pair: currency.Pair{ +// Delimiter: "-", +// Base: currency.BTC, +// Quote: currency.USD, +// }, +// Side: order.Buy, +// Type: order.Limit, +// Price: 1, +// Amount: 0.001, +// ClientID: "meowOrder", +// AssetType: asset.Spot, +// } +// response, err := c.SubmitOrder(context.Background(), orderSubmission) +// if sharedtestvalues.AreAPICredentialsSet(c) && (err != nil || response.Status != order.New) { +// t.Errorf("Order failed to be placed: %v", err) +// } else if !sharedtestvalues.AreAPICredentialsSet(c) && err == nil { +// t.Error("Expecting an error when no keys are set") +// } -func TestCancelAllExchangeOrders(t *testing.T) { - t.Parallel() - sharedtestvalues.SkipTestIfCannotManipulateOrders(t, c, canManipulateRealOrders) +// // market order from amount +// orderSubmission = &order.Submit{ +// Exchange: c.Name, +// Pair: currency.Pair{ +// Delimiter: "-", +// Base: currency.BTC, +// Quote: currency.USD, +// }, +// Side: order.Buy, +// Type: order.Market, +// Amount: 0.001, +// ClientID: "meowOrder", +// AssetType: asset.Spot, +// } +// response, err = c.SubmitOrder(context.Background(), orderSubmission) +// if sharedtestvalues.AreAPICredentialsSet(c) && (err != nil || response.Status != order.New) { +// t.Errorf("Order failed to be placed: %v", err) +// } else if !sharedtestvalues.AreAPICredentialsSet(c) && err == nil { +// t.Error("Expecting an error when no keys are set") +// } - var orderCancellation = &order.Cancel{ - OrderID: "1", - WalletAddress: core.BitcoinDonationAddress, - AccountID: "1", - Pair: testPair, - AssetType: asset.Spot, - } +// // market order from quote amount +// orderSubmission = &order.Submit{ +// Exchange: c.Name, +// Pair: currency.Pair{ +// Delimiter: "-", +// Base: currency.BTC, +// Quote: currency.USD, +// }, +// Side: order.Buy, +// Type: order.Market, +// QuoteAmount: 1, +// ClientID: "meowOrder", +// AssetType: asset.Spot, +// } +// response, err = c.SubmitOrder(context.Background(), orderSubmission) +// if sharedtestvalues.AreAPICredentialsSet(c) && (err != nil || response.Status != order.New) { +// t.Errorf("Order failed to be placed: %v", err) +// } else if !sharedtestvalues.AreAPICredentialsSet(c) && err == nil { +// t.Error("Expecting an error when no keys are set") +// } +// } - resp, err := c.CancelAllOrders(context.Background(), orderCancellation) +// func TestCancelExchangeOrder(t *testing.T) { +// t.Parallel() +// sharedtestvalues.SkipTestIfCannotManipulateOrders(t, c, canManipulateRealOrders) - if !sharedtestvalues.AreAPICredentialsSet(c) && err == nil { - t.Error("Expecting an error when no keys are set") - } - if sharedtestvalues.AreAPICredentialsSet(c) && err != nil { - t.Errorf("Could not cancel orders: %v", err) - } +// var orderCancellation = &order.Cancel{ +// OrderID: "1", +// WalletAddress: core.BitcoinDonationAddress, +// AccountID: "1", +// Pair: testPair, +// AssetType: asset.Spot, +// } - if len(resp.Status) > 0 { - t.Errorf("%v orders failed to cancel", len(resp.Status)) - } -} +// err := c.CancelOrder(context.Background(), orderCancellation) +// if !sharedtestvalues.AreAPICredentialsSet(c) && err == nil { +// t.Error("Expecting an error when no keys are set") +// } +// if sharedtestvalues.AreAPICredentialsSet(c) && err != nil { +// t.Errorf("Could not cancel orders: %v", err) +// } +// } -func TestModifyOrder(t *testing.T) { - t.Parallel() - sharedtestvalues.SkipTestIfCannotManipulateOrders(t, c, canManipulateRealOrders) +// func TestCancelAllExchangeOrders(t *testing.T) { +// t.Parallel() +// sharedtestvalues.SkipTestIfCannotManipulateOrders(t, c, canManipulateRealOrders) - _, err := c.ModifyOrder(context.Background(), - &order.Modify{AssetType: asset.Spot}) - if err == nil { - t.Error("ModifyOrder() Expected error") - } -} +// var orderCancellation = &order.Cancel{ +// OrderID: "1", +// WalletAddress: core.BitcoinDonationAddress, +// AccountID: "1", +// Pair: testPair, +// AssetType: asset.Spot, +// } -func TestWithdraw(t *testing.T) { - t.Parallel() - sharedtestvalues.SkipTestIfCannotManipulateOrders(t, c, canManipulateRealOrders) +// resp, err := c.CancelAllOrders(context.Background(), orderCancellation) - withdrawCryptoRequest := withdraw.Request{ - Exchange: c.Name, - Amount: -1, - Currency: currency.BTC, - Description: "WITHDRAW IT ALL", - Crypto: withdraw.CryptoRequest{ - Address: core.BitcoinDonationAddress, - }, - } +// if !sharedtestvalues.AreAPICredentialsSet(c) && err == nil { +// t.Error("Expecting an error when no keys are set") +// } +// if sharedtestvalues.AreAPICredentialsSet(c) && err != nil { +// t.Errorf("Could not cancel orders: %v", err) +// } - _, err := c.WithdrawCryptocurrencyFunds(context.Background(), - &withdrawCryptoRequest) - if !sharedtestvalues.AreAPICredentialsSet(c) && err == nil { - t.Error("Expecting an error when no keys are set") - } - if sharedtestvalues.AreAPICredentialsSet(c) && err != nil { - t.Errorf("Withdraw failed to be placed: %v", err) - } -} +// if len(resp.Status) > 0 { +// t.Errorf("%v orders failed to cancel", len(resp.Status)) +// } +// } -func TestWithdrawFiat(t *testing.T) { - t.Parallel() - sharedtestvalues.SkipTestIfCannotManipulateOrders(t, c, canManipulateRealOrders) +// func TestModifyOrder(t *testing.T) { +// t.Parallel() +// sharedtestvalues.SkipTestIfCannotManipulateOrders(t, c, canManipulateRealOrders) - var withdrawFiatRequest = withdraw.Request{ - Amount: 100, - Currency: currency.USD, - Fiat: withdraw.FiatRequest{ - Bank: banking.Account{ - BankName: "Federal Reserve Bank", - }, - }, - } +// _, err := c.ModifyOrder(context.Background(), +// &order.Modify{AssetType: asset.Spot}) +// if err == nil { +// t.Error("ModifyOrder() Expected error") +// } +// } - _, err := c.WithdrawFiatFunds(context.Background(), &withdrawFiatRequest) - if !sharedtestvalues.AreAPICredentialsSet(c) && err == nil { - t.Error("Expecting an error when no keys are set") - } - if sharedtestvalues.AreAPICredentialsSet(c) && err != nil { - t.Errorf("Withdraw failed to be placed: %v", err) - } -} +// func TestWithdraw(t *testing.T) { +// t.Parallel() +// sharedtestvalues.SkipTestIfCannotManipulateOrders(t, c, canManipulateRealOrders) + +// withdrawCryptoRequest := withdraw.Request{ +// Exchange: c.Name, +// Amount: -1, +// Currency: currency.BTC, +// Description: "WITHDRAW IT ALL", +// Crypto: withdraw.CryptoRequest{ +// Address: core.BitcoinDonationAddress, +// }, +// } -func TestWithdrawInternationalBank(t *testing.T) { - t.Parallel() - sharedtestvalues.SkipTestIfCannotManipulateOrders(t, c, canManipulateRealOrders) +// _, err := c.WithdrawCryptocurrencyFunds(context.Background(), +// &withdrawCryptoRequest) +// if !sharedtestvalues.AreAPICredentialsSet(c) && err == nil { +// t.Error("Expecting an error when no keys are set") +// } +// if sharedtestvalues.AreAPICredentialsSet(c) && err != nil { +// t.Errorf("Withdraw failed to be placed: %v", err) +// } +// } - var withdrawFiatRequest = withdraw.Request{ - Amount: 100, - Currency: currency.USD, - Fiat: withdraw.FiatRequest{ - Bank: banking.Account{ - BankName: "Federal Reserve Bank", - }, - }, - } +// func TestWithdrawFiat(t *testing.T) { +// t.Parallel() +// sharedtestvalues.SkipTestIfCannotManipulateOrders(t, c, canManipulateRealOrders) + +// var withdrawFiatRequest = withdraw.Request{ +// Amount: 100, +// Currency: currency.USD, +// Fiat: withdraw.FiatRequest{ +// Bank: banking.Account{ +// BankName: "Federal Reserve Bank", +// }, +// }, +// } - _, err := c.WithdrawFiatFundsToInternationalBank(context.Background(), - &withdrawFiatRequest) - if !sharedtestvalues.AreAPICredentialsSet(c) && err == nil { - t.Error("Expecting an error when no keys are set") - } - if sharedtestvalues.AreAPICredentialsSet(c) && err != nil { - t.Errorf("Withdraw failed to be placed: %v", err) - } -} +// _, err := c.WithdrawFiatFunds(context.Background(), &withdrawFiatRequest) +// if !sharedtestvalues.AreAPICredentialsSet(c) && err == nil { +// t.Error("Expecting an error when no keys are set") +// } +// if sharedtestvalues.AreAPICredentialsSet(c) && err != nil { +// t.Errorf("Withdraw failed to be placed: %v", err) +// } +// } -func TestGetDepositAddress(t *testing.T) { - _, err := c.GetDepositAddress(context.Background(), currency.BTC, "", "") - if err == nil { - t.Error("GetDepositAddress() error", err) - } -} +// func TestWithdrawInternationalBank(t *testing.T) { +// t.Parallel() +// sharedtestvalues.SkipTestIfCannotManipulateOrders(t, c, canManipulateRealOrders) + +// var withdrawFiatRequest = withdraw.Request{ +// Amount: 100, +// Currency: currency.USD, +// Fiat: withdraw.FiatRequest{ +// Bank: banking.Account{ +// BankName: "Federal Reserve Bank", +// }, +// }, +// } + +// _, err := c.WithdrawFiatFundsToInternationalBank(context.Background(), +// &withdrawFiatRequest) +// if !sharedtestvalues.AreAPICredentialsSet(c) && err == nil { +// t.Error("Expecting an error when no keys are set") +// } +// if sharedtestvalues.AreAPICredentialsSet(c) && err != nil { +// t.Errorf("Withdraw failed to be placed: %v", err) +// } +// } + +// func TestGetDepositAddress(t *testing.T) { +// _, err := c.GetDepositAddress(context.Background(), currency.BTC, "", "") +// if err == nil { +// t.Error("GetDepositAddress() error", err) +// } +// } // TestWsAuth dials websocket, sends login request. func TestWsAuth(t *testing.T) { @@ -1465,22 +1596,22 @@ func TestParseTime(t *testing.T) { } } -func TestGetRecentTrades(t *testing.T) { - t.Parallel() - _, err := c.GetRecentTrades(context.Background(), testPair, asset.Spot) - if err != nil { - t.Error(err) - } -} +// func TestGetRecentTrades(t *testing.T) { +// t.Parallel() +// _, err := c.GetRecentTrades(context.Background(), testPair, asset.Spot) +// if err != nil { +// t.Error(err) +// } +// } -func TestGetHistoricTrades(t *testing.T) { - t.Parallel() - _, err := c.GetHistoricTrades(context.Background(), - testPair, asset.Spot, time.Now().Add(-time.Minute*15), time.Now()) - if err != nil && err != common.ErrFunctionNotSupported { - t.Error(err) - } -} +// func TestGetHistoricTrades(t *testing.T) { +// t.Parallel() +// _, err := c.GetHistoricTrades(context.Background(), +// testPair, asset.Spot, time.Now().Add(-time.Minute*15), time.Now()) +// if err != nil && err != common.ErrFunctionNotSupported { +// t.Error(err) +// } +// } func TestPrepareDSL(t *testing.T) { t.Parallel() @@ -1613,8 +1744,6 @@ func TestOrderbookHelper(t *testing.T) { if err != nil { t.Error("CoinBasePro OrderbookHelper() error", err) } - - log.Printf("Err: %v", err) } // 8837708 143.0 ns/op 24 B/op 5 allocs/op @@ -1640,3 +1769,32 @@ func TestOrderbookHelper(t *testing.T) { // _ = strconv.Itoa(86400) // } // } + +// const reportType = "rfq-fills" + +// // Benchmark3Ifs-8 1000000000 0.2556 ns/op 0 B/op 0 allocs/op +// // 1000000000 0.2879 ns/op 0 B/op 0 allocs/op +// // Benchmark3Ifs-8 1000000000 0.2945 ns/op 0 B/op 0 allocs/op +// func Benchmark3Ifs(b *testing.B) { +// a := 0 +// for x := 0; x < b.N; x++ { +// if reportType == "fills" || reportType == "otc-fills" || reportType == "rfq-fills" { +// a++ +// } +// } +// log.Print(a) +// } + +// // BenchmarkNeedle-8 322462062 3.670 ns/op 0 B/op 0 allocs/op +// // BenchmarkNeedle-8 295766910 4.467 ns/op 0 B/op 0 allocs/op +// // BenchmarkNeedle-8 137813607 9.496 ns/op 0 B/op 0 allocs/op +// func BenchmarkNeedle(b *testing.B) { +// a := 0 +// for x := 0; x < b.N; x++ { +// rTCheck := []string{"fills", "otc-fills", "rfq-fills"} +// if common.StringDataCompare(rTCheck, reportType) { +// a++ +// } +// } +// log.Print(a) +// } diff --git a/exchanges/coinbasepro/coinbasepro_types.go b/exchanges/coinbasepro/coinbasepro_types.go index c4069d732c2..bf831b01ca7 100644 --- a/exchanges/coinbasepro/coinbasepro_types.go +++ b/exchanges/coinbasepro/coinbasepro_types.go @@ -6,10 +6,11 @@ import ( "strconv" "time" + "github.com/thrasher-corp/gocryptotrader/common/convert" "github.com/thrasher-corp/gocryptotrader/currency" ) -// Product holds product information +// Product holds product information, returned by GetAllProducts and GetProductByID type Product struct { ID string `json:"id"` BaseCurrency string `json:"base_currency"` @@ -31,7 +32,7 @@ type Product struct { HighBidLimitPercentage string `json:"high_bid_limit_percentage"` } -// Ticker holds basic ticker information +// Ticker holds basic ticker information, returned by GetTicker type Ticker struct { Ask float64 `json:"ask,string"` Bid float64 `json:"bid,string"` @@ -42,7 +43,7 @@ type Ticker struct { Time time.Time `json:"time"` } -// Trade holds executed trade information +// Trade holds executed trade information, returned by GetTrades type Trade struct { TradeID int64 `json:"trade_id"` Price float64 `json:"price,string"` @@ -51,7 +52,7 @@ type Trade struct { Side string `json:"side"` } -// History holds historic rate information +// History holds historic rate information, returned by GetHistoricRates type History struct { Time time.Time Low float64 @@ -61,7 +62,7 @@ type History struct { Volume float64 } -// Stats holds last 24 hr data for coinbasepro +// Stats holds 30 day and 24 hr data for a currency pair, returned by GetStats type Stats struct { Open float64 `json:"open,string"` High float64 `json:"high,string"` @@ -71,7 +72,7 @@ type Stats struct { Volume30Day float64 `json:"volume_30day,string"` } -// Currency holds singular currency product information +// Currency holds information on a currency, returned by GetAllCurrencies and GetCurrencyByID type Currency struct { ID string `json:"id"` Name string `json:"name"` @@ -109,12 +110,13 @@ type Currency struct { } // ServerTime holds current requested server time information -type ServerTime struct { - ISO time.Time `json:"iso"` - Epoch float64 `json:"epoch"` -} +// type ServerTime struct { +// ISO time.Time `json:"iso"` +// Epoch float64 `json:"epoch"` +// } -// AccountResponse holds the details for the trading accounts +// AccountResponse holds details for a trading account, returned by GetAllAccounts +// and GetAccountByID type AccountResponse struct { ID string `json:"id"` Currency string `json:"currency"` @@ -126,7 +128,7 @@ type AccountResponse struct { PendingDeposit string `json:"pending_deposit"` } -// AccountLedgerResponse holds account history information +// AccountLedgerResponse holds account history information, returned by GetAccountLedger type AccountLedgerResponse struct { ID string `json:"id"` Amount float64 `json:"amount,string"` @@ -140,17 +142,18 @@ type AccountLedgerResponse struct { } `json:"details"` } -// AccountHolds contains the hold information about an account +// AccountHolds contains information on a hold, returned by GetHolds type AccountHolds struct { ID string `json:"id"` CreatedAt time.Time `json:"created_at"` + Amount float64 `json:"amount,string"` UpdatedAt time.Time `json:"updated_at"` Type string `json:"type"` Reference string `json:"ref"` } -// GeneralizedOrderResponse is the generalized return type across order -// placement and information collation +// GeneralizedOrderResponse contains information on an order, returned by GetAllOrders, +// PlaceOrder, and GetOrderByID type GeneralizedOrderResponse struct { ID string `json:"id"` Price float64 `json:"price,string"` @@ -181,85 +184,85 @@ type GeneralizedOrderResponse struct { } // Funding holds funding data -type Funding struct { - ID string `json:"id"` - OrderID string `json:"order_id"` - ProfileID string `json:"profile_id"` - Amount float64 `json:"amount,string"` - Status string `json:"status"` - CreatedAt time.Time `json:"created_at"` - Currency string `json:"currency"` - RepaidAmount float64 `json:"repaid_amount"` - DefaultAmount float64 `json:"default_amount,string"` - RepaidDefault bool `json:"repaid_default"` -} +// type Funding struct { +// ID string `json:"id"` +// OrderID string `json:"order_id"` +// ProfileID string `json:"profile_id"` +// Amount float64 `json:"amount,string"` +// Status string `json:"status"` +// CreatedAt time.Time `json:"created_at"` +// Currency string `json:"currency"` +// RepaidAmount float64 `json:"repaid_amount"` +// DefaultAmount float64 `json:"default_amount,string"` +// RepaidDefault bool `json:"repaid_default"` +// } // MarginTransfer holds margin transfer details -type MarginTransfer struct { - CreatedAt time.Time `json:"created_at"` - ID string `json:"id"` - UserID string `json:"user_id"` - ProfileID string `json:"profile_id"` - MarginProfileID string `json:"margin_profile_id"` - Type string `json:"type"` - Amount float64 `json:"amount,string"` - Currency string `json:"currency"` - AccountID string `json:"account_id"` - MarginAccountID string `json:"margin_account_id"` - MarginProductID string `json:"margin_product_id"` - Status string `json:"status"` - Nonce int `json:"nonce"` -} +// type MarginTransfer struct { +// CreatedAt time.Time `json:"created_at"` +// ID string `json:"id"` +// UserID string `json:"user_id"` +// ProfileID string `json:"profile_id"` +// MarginProfileID string `json:"margin_profile_id"` +// Type string `json:"type"` +// Amount float64 `json:"amount,string"` +// Currency string `json:"currency"` +// AccountID string `json:"account_id"` +// MarginAccountID string `json:"margin_account_id"` +// MarginProductID string `json:"margin_product_id"` +// Status string `json:"status"` +// Nonce int `json:"nonce"` +// } // AccountOverview holds account information returned from position -type AccountOverview struct { - Status string `json:"status"` - Funding struct { - MaxFundingValue float64 `json:"max_funding_value,string"` - FundingValue float64 `json:"funding_value,string"` - OldestOutstanding struct { - ID string `json:"id"` - OrderID string `json:"order_id"` - CreatedAt time.Time `json:"created_at"` - Currency string `json:"currency"` - AccountID string `json:"account_id"` - Amount float64 `json:"amount,string"` - } `json:"oldest_outstanding"` - } `json:"funding"` - Accounts struct { - LTC Account `json:"LTC"` - ETH Account `json:"ETH"` - USD Account `json:"USD"` - BTC Account `json:"BTC"` - } `json:"accounts"` - MarginCall struct { - Active bool `json:"active"` - Price float64 `json:"price,string"` - Side string `json:"side"` - Size float64 `json:"size,string"` - Funds float64 `json:"funds,string"` - } `json:"margin_call"` - UserID string `json:"user_id"` - ProfileID string `json:"profile_id"` - Position struct { - Type string `json:"type"` - Size float64 `json:"size,string"` - Complement float64 `json:"complement,string"` - MaxSize float64 `json:"max_size,string"` - } `json:"position"` - ProductID string `json:"product_id"` -} +// type AccountOverview struct { +// Status string `json:"status"` +// Funding struct { +// MaxFundingValue float64 `json:"max_funding_value,string"` +// FundingValue float64 `json:"funding_value,string"` +// OldestOutstanding struct { +// ID string `json:"id"` +// OrderID string `json:"order_id"` +// CreatedAt time.Time `json:"created_at"` +// Currency string `json:"currency"` +// AccountID string `json:"account_id"` +// Amount float64 `json:"amount,string"` +// } `json:"oldest_outstanding"` +// } `json:"funding"` +// Accounts struct { +// LTC Account `json:"LTC"` +// ETH Account `json:"ETH"` +// USD Account `json:"USD"` +// BTC Account `json:"BTC"` +// } `json:"accounts"` +// MarginCall struct { +// Active bool `json:"active"` +// Price float64 `json:"price,string"` +// Side string `json:"side"` +// Size float64 `json:"size,string"` +// Funds float64 `json:"funds,string"` +// } `json:"margin_call"` +// UserID string `json:"user_id"` +// ProfileID string `json:"profile_id"` +// Position struct { +// Type string `json:"type"` +// Size float64 `json:"size,string"` +// Complement float64 `json:"complement,string"` +// MaxSize float64 `json:"max_size,string"` +// } `json:"position"` +// ProductID string `json:"product_id"` +// } // Account is a sub-type for account overview -type Account struct { - ID string `json:"id"` - Balance float64 `json:"balance,string"` - Hold float64 `json:"hold,string"` - FundedAmount float64 `json:"funded_amount,string"` - DefaultAmount float64 `json:"default_amount,string"` -} - -// PaymentMethod holds payment method information +// type Account struct { +// ID string `json:"id"` +// Balance float64 `json:"balance,string"` +// Hold float64 `json:"hold,string"` +// FundedAmount float64 `json:"funded_amount,string"` +// DefaultAmount float64 `json:"default_amount,string"` +// } + +// PaymentMethod holds payment method information, returned by GetPayMethods type PaymentMethod struct { ID string `json:"id"` Type string `json:"type"` @@ -326,15 +329,18 @@ type PaymentMethod struct { } // LimitInfo is a sub-type for payment method -type LimitInfo struct { - PeriodInDays int `json:"period_in_days"` - Total struct { - Amount float64 `json:"amount,string"` - Currency string `json:"currency"` - } `json:"total"` -} - -// DepositWithdrawalInfo holds returned deposit information +// type LimitInfo struct { +// PeriodInDays int `json:"period_in_days"` +// Total struct { +// Amount float64 `json:"amount,string"` +// Currency string `json:"currency"` +// } `json:"total"` +// } + +// DepositWithdrawalInfo holds information provided when depositing or +// withdrawing from payment methods. Returned by DepositViaCoinbase, +// DepositViaPaymentMethod, WithdrawViaCoinbase, WithdrawCrypto, and +// WithdrawViaPaymentMethod type DepositWithdrawalInfo struct { ID string `json:"id"` Amount float64 `json:"amount,string"` @@ -344,7 +350,7 @@ type DepositWithdrawalInfo struct { Subtotal float64 `json:"subtotal,string"` } -// CoinbaseAccounts holds coinbase account information +// CoinbaseAccounts holds coinbase account information, returned by GetCoinbaseWallets type CoinbaseAccounts struct { ID string `json:"id"` Name string `json:"name"` @@ -406,7 +412,8 @@ type CoinbaseAccounts struct { HoldCurrency string `json:"hold_currency"` } -// Report holds historical information +// Report holds information on user-generated reports, returned by GetAllReports +// and GetReportByID type Report struct { ID string `json:"id"` Type string `json:"type"` @@ -432,10 +439,12 @@ type Report struct { Email string `json:"email"` Roles []interface{} `json:"roles"` IsBanned bool `json:"is_banned"` + Permissions interface{} `json:"permissions"` UserType string `json:"user_type"` FulfillsNewRequirements bool `json:"fulfills_new_requirements"` Flags interface{} `json:"flags"` Details interface{} `json:"details"` + OauthClient string `json:"oauth_client"` Preferences struct { PreferredMarket string `json:"preferred_market"` MarginTermsCompletedInUTC time.Time `json:"margin_terms_completed_in_utc"` @@ -458,12 +467,12 @@ type Report struct { } // Volume type contains trailing volume information -type Volume struct { - ProductID string `json:"product_id"` - ExchangeVolume float64 `json:"exchange_volume,string"` - Volume float64 `json:"volume,string"` - RecordedAt string `json:"recorded_at"` -} +// type Volume struct { +// ProductID string `json:"product_id"` +// ExchangeVolume float64 `json:"exchange_volume,string"` +// Volume float64 `json:"volume,string"` +// RecordedAt string `json:"recorded_at"` +// } // InterOrderDetail is used to make intermediary orderbook handling easier type InterOrderDetail [][3]interface{} @@ -488,7 +497,7 @@ type OrderbookIntermediaryResponse struct { Time time.Time `json:"time"` } -// GenOrderDetail is a subtype used for the orderbook +// GenOrderDetail is a subtype used for the final state of the orderbook type GenOrderDetail struct { Price float64 Amount float64 @@ -496,7 +505,7 @@ type GenOrderDetail struct { OrderID string } -// OrderbookResponse is the final state of the orderbook +// OrderbookResponse is the final state of the orderbook, returned by GetOrderbook type OrderbookFinalResponse struct { Bids []GenOrderDetail `json:"bids"` Asks []GenOrderDetail `json:"asks"` @@ -516,7 +525,7 @@ type OrderbookFinalResponse struct { Time time.Time `json:"time"` } -// FillResponse contains fill information from the exchange +// FillResponse contains fill information, returned by GetFills type FillResponse struct { TradeID int32 `json:"trade_id"` ProductID string `json:"product_id"` @@ -665,18 +674,19 @@ type wsStatus struct { } // RequestParamsTimeForceType Time in force -type RequestParamsTimeForceType string +// type RequestParamsTimeForceType string -var ( - // CoinbaseRequestParamsTimeGTC GTC - CoinbaseRequestParamsTimeGTC = RequestParamsTimeForceType("GTC") +// var ( +// // CoinbaseRequestParamsTimeGTC GTC +// CoinbaseRequestParamsTimeGTC = RequestParamsTimeForceType("GTC") - // CoinbaseRequestParamsTimeIOC IOC - CoinbaseRequestParamsTimeIOC = RequestParamsTimeForceType("IOC") -) +// // CoinbaseRequestParamsTimeIOC IOC +// CoinbaseRequestParamsTimeIOC = RequestParamsTimeForceType("IOC") +// ) -// TransferHistory returns wallet transfer history -type TransferHistory struct { +// TransferResponse contains information on a transfer, returned by GetAccountTransfers, +// GetAllTransfers, and GetTransferByID +type TransferResponse struct { ID string `json:"id"` Type string `json:"type"` CreatedAt time.Time `json:"created_at"` @@ -692,18 +702,22 @@ type TransferHistory struct { UserNonce int64 `json:"user_nonce"` } +// TravelRule contains information on a travel rule, returned by GetTravelRules +// and CreateTravelRule type TravelRule struct { - ID string `json:"id"` - CreatedAt string `json:"created_at"` - Address string `json:"address"` - OriginName string `json:"originator_name"` - OriginCountry string `json:"originator_country"` + ID string `json:"id"` + CreatedAt time.Time `json:"created_at"` + Address string `json:"address"` + OriginName string `json:"originator_name"` + OriginCountry string `json:"originator_country"` } +// Params is used within functions to make the setting of parameters easier type Params struct { urlVals url.Values } +// GetAddressResponse contains information on addresses, returned by GetAddressBook type GetAddressResponse struct { ID string `json:"id"` Address string `json:"address"` @@ -716,10 +730,13 @@ type GetAddressResponse struct { VASPID string `json:"vasp_id"` } +// To is part of the struct expected by the exchange for the AddAddresses function type To struct { Address string `json:"address"` DestinationTag string `json:"destination_tag"` } + +// AddAddressRequest is the struct expected by the exchange for the AddAddresses function type AddAddressRequest struct { Currency string `json:"currency"` To `json:"to"` @@ -730,6 +747,8 @@ type AddAddressRequest struct { // but doesn't explain what they do. Investigate more later. } +// AddAddressResponse contains information on the addresses just added, returned by +// AddAddresses type AddAddressResponse struct { ID string `json:"id"` Address string `json:"address"` @@ -750,6 +769,8 @@ type AddAddressResponse struct { VaspID string `json:"vasp_id"` } +// CryptoAddressResponse contains information on the one-time address generated for +// depositing crypto, returned by GenerateCryptoAddress type CryptoAddressResponse struct { ID string `json:"id"` Address string `json:"address"` @@ -775,6 +796,8 @@ type CryptoAddressResponse struct { CallbackURL string `json:"callback_url"` } +// ConvertResponse contains information about a completed currency conversion, returned +// by ConvertCurrency and GetConversionByID type ConvertResponse struct { ID string `json:"id"` Amount string `json:"amount"` @@ -784,17 +807,22 @@ type ConvertResponse struct { To string `json:"to"` } +// WithdrawalFeeEstimate is the exchange's estimate of the fee for a withdrawal, returned +// by GetWithdrawalFeeEstimate type WithdrawalFeeEstimate struct { Fee string `json:"fee"` FeeBeforeSubsidy string `json:"fee_before_subsidy"` } +// FeeResponse contains current taker and maker fee rates, as well as 30-day trailing +// volume. Returned by GetFees type FeeResponse struct { TakerFeeRate float64 `json:"taker_fee_rate,string"` MakerFeeRate float64 `json:"maker_fee_rate,string"` USDVolume float64 `json:"usd_volume,string"` } +// PriceMap is used to properly unmarshal the response from GetSignedPrices type PriceMap map[string]float64 func (pm *PriceMap) UnmarshalJSON(data []byte) error { @@ -813,6 +841,9 @@ func (pm *PriceMap) UnmarshalJSON(data []byte) error { return nil } +// SignedPrices contains cryptographically signed prices, alongside other information +// necessary for them to be posted on-chain using Compound's Open Oracle smart contract. +// Returned by GetSignedPrices type SignedPrices struct { Timestamp string `json:"timestamp"` Messages []string `json:"messages"` @@ -820,6 +851,8 @@ type SignedPrices struct { Prices PriceMap `json:"prices"` } +// Profile contains information on a profile. Returned by GetAllProfiles, CreateAProfile, +// GetProfileByID, and RenameProfile type Profile struct { ID string `json:"id"` UserID string `json:"user_id"` @@ -829,12 +862,92 @@ type Profile struct { CreatedAt time.Time `json:"created_at"` } +// CreateReportResponse contains information on a newly-created report, returned by +// CreateReport type CreateReportResponse struct { ID string `json:"id"` Type string `json:"type"` Status string `json:"status"` } +// ReportBalanceStruct is used internally when crafting a CreateReport request type ReportBalanceStruct struct { DateTime string } + +// ReportFillsTaxStruct is used internally when crafting a CreateReport request +type ReportFillsTaxStruct struct { + StartDate string + EndDate string + ProductID string +} + +// ReportAccountStruct is used internally when crafting a CreateReport request +type ReportAccountStruct struct { + StartDate string + EndDate string + AccountID string +} + +// ExchangeLimits contains information on payment method transfer limits, returned +// by GetExchangeLimits +type ExchangeLimits struct { + TransferLimits struct { + Buy interface{} `json:"buy"` + Sell interface{} `json:"sell"` + ExchangeWithdraw interface{} `json:"exchange_withdraw"` + Ach []struct { + Max float64 `json:"max,string"` + Remaining float64 `json:"remaining,string"` + PeriodInDays int32 `json:"period_in_days"` + } `json:"ach"` + AchNoBalance interface{} `json:"ach_no_balance"` + CreditDebitCard interface{} `json:"credit_debit_card"` + Secure3DBuy interface{} `json:"secure3d_buy"` + PaypalBuy interface{} `json:"paypal_buy"` + PaypalWithdrawal interface{} `json:"paypal_withdrawal"` + IdealDeposit interface{} `json:"ideal_deposit"` + SofortDeposit interface{} `json:"sofort_deposit"` + InstantAchWithdrawal interface{} `json:"instant_ach_withdrawal"` + } `json:"transfer_limits"` + LimitCurrency string `json:"limit_currency"` +} + +// WrappedAssetResponse contains information on a wrapped asset, returned by +// GetWrappedAssetByID +type WrappedAssetResponse struct { + ID string `json:"id"` + CirculatingSupply float64 `json:"circulating_supply,string"` + TotalSupply float64 `json:"total_supply,string"` + ConversionRate float64 `json:"conversion_rate,string"` + APY convert.StringToFloat64 `json:"apy"` +} + +// AllWrappedAssetResponse contains information on all wrapped assets, returned by +// GetAllWrappedAssets +type AllWrappedAssetResponse struct { + WrappedAssetResponse []struct{} `json:"wrapped_assets"` +} + +// StakeWrap contains information on a stake wrap, returned by GetAllStakeWraps and +// GetStakeWrapByID +type StakeWrap struct { + ID string `json:"id"` + FromAmount float64 `json:"from_amount,string"` + ToAmount float64 `json:"to_amount,string"` + FromAccountID string `json:"from_account_id"` + ToAccountID string `json:"to_account_id"` + FromCurrency string `json:"from_currency"` + ToCurrency string `json:"to_currency"` + Status string `json:"status"` + ConversionRate float64 `json:"conversion_rate,string"` + CreatedAt time.Time `json:"created_at"` + CompletedAt time.Time `json:"completed_at"` + CanceledAt time.Time `json:"canceled_at"` +} + +// WrappedAssetConversionRate contains the conversion rate for a wrapped asset, returned +// by GetWrappedAssetConversionRate +type WrappedAssetConversionRate struct { + Amount float64 `json:"amount,string"` +} diff --git a/exchanges/coinbasepro/coinbasepro_wrapper.go b/exchanges/coinbasepro/coinbasepro_wrapper.go index 74074e6c197..a053dff41c2 100644 --- a/exchanges/coinbasepro/coinbasepro_wrapper.go +++ b/exchanges/coinbasepro/coinbasepro_wrapper.go @@ -753,14 +753,15 @@ func (c *CoinbasePro) WithdrawFiatFundsToInternationalBank(ctx context.Context, // GetFeeByType returns an estimate of fee based on type of transaction func (c *CoinbasePro) GetFeeByType(ctx context.Context, feeBuilder *exchange.FeeBuilder) (float64, error) { - if feeBuilder == nil { - return 0, fmt.Errorf("%T %w", feeBuilder, common.ErrNilPointer) - } - if !c.AreCredentialsValid(ctx) && // Todo check connection status - feeBuilder.FeeType == exchange.CryptocurrencyTradeFee { - feeBuilder.FeeType = exchange.OfflineTradeFee - } - return c.GetFee(ctx, feeBuilder) + // if feeBuilder == nil { + // return 0, fmt.Errorf("%T %w", feeBuilder, common.ErrNilPointer) + // } + // if !c.AreCredentialsValid(ctx) && // Todo check connection status + // feeBuilder.FeeType == exchange.CryptocurrencyTradeFee { + // feeBuilder.FeeType = exchange.OfflineTradeFee + // } + // return c.GetFee(ctx, feeBuilder) + return 99999, errors.New(common.ErrFunctionNotSupported.Error()) } // GetActiveOrders retrieves any orders that are active/open @@ -980,11 +981,12 @@ func (c *CoinbasePro) ValidateAPICredentials(ctx context.Context, assetType asse // GetServerTime returns the current exchange server time. func (c *CoinbasePro) GetServerTime(ctx context.Context, _ asset.Item) (time.Time, error) { - st, err := c.GetCurrentServerTime(ctx) - if err != nil { - return time.Time{}, err - } - return st.ISO, nil + // st, err := c.GetCurrentServerTime(ctx) + // if err != nil { + // return time.Time{}, err + // } + // return st.ISO, nil + return time.Time{}, errors.New(common.ErrFunctionNotSupported.Error()) } // GetFuturesContractDetails returns all contracts from the exchange by asset type diff --git a/exchanges/coinbasepro/ratelimit.go b/exchanges/coinbasepro/ratelimit.go index d9c6fa76f61..0761c87ddba 100644 --- a/exchanges/coinbasepro/ratelimit.go +++ b/exchanges/coinbasepro/ratelimit.go @@ -11,8 +11,8 @@ import ( // Coinbasepro rate limit conts const ( coinbaseproRateInterval = time.Second - coinbaseproAuthRate = 5 - coinbaseproUnauthRate = 2 + coinbaseproAuthRate = 10 + coinbaseproUnauthRate = 10 ) // RateLimit implements the request.Limiter interface From eded1609887bb7e177d4a611e73356568bff2baa Mon Sep 17 00:00:00 2001 From: Samuel Reid <43227667+cranktakular@users.noreply.github.com> Date: Tue, 24 Oct 2023 17:03:22 +1100 Subject: [PATCH 06/79] Coinbasepro revamp is ceaseless --- exchanges/coinbasepro/coinbasepro.go | 145 ++++++++------- exchanges/coinbasepro/coinbasepro_test.go | 197 +++++++++++++++++---- exchanges/coinbasepro/coinbasepro_types.go | 132 +++++++++----- 3 files changed, 337 insertions(+), 137 deletions(-) diff --git a/exchanges/coinbasepro/coinbasepro.go b/exchanges/coinbasepro/coinbasepro.go index 3e6fb42992a..40d6440dca1 100644 --- a/exchanges/coinbasepro/coinbasepro.go +++ b/exchanges/coinbasepro/coinbasepro.go @@ -83,7 +83,7 @@ func (c *CoinbasePro) GetAllAccounts(ctx context.Context) ([]AccountResponse, er var resp []AccountResponse return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, coinbaseproAccounts, nil, &resp) + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, coinbaseproAccounts, nil, &resp, nil) } // GetAccountByID returns information for a single account @@ -92,33 +92,41 @@ func (c *CoinbasePro) GetAccountByID(ctx context.Context, accountID string) (*Ac resp := AccountResponse{} return &resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp) + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp, nil) } // GetHolds returns information on the holds of an account -func (c *CoinbasePro) GetHolds(ctx context.Context, accountID, direction, step string, limit int64) ([]AccountHolds, error) { +func (c *CoinbasePro) GetHolds(ctx context.Context, accountID, direction, step string, limit int64) ([]AccountHolds, ReturnedPaginationHeaders, error) { path := fmt.Sprintf("%s/%s/%s", coinbaseproAccounts, accountID, coinbaseproHolds) var params Params params.urlVals = url.Values{} + // Warning: This endpoint doesn't seem to properly support pagination, the headers + // indicating the cursor position are never actually present. Still, it's handled + // as if it works, in case it gets fixed. params.PrepareDSL(direction, step, limit) path = common.EncodeURLValues(path, params.urlVals) var resp []AccountHolds + retH := http.Header{} - return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp) + err := c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp, &retH) + + rph := ReturnedPaginationHeaders{before: retH.Get("CB-BEFORE"), after: retH.Get("CB-AFTER")} + + return resp, rph, err } // GetAccountLedger returns a list of ledger activity -func (c *CoinbasePro) GetAccountLedger(ctx context.Context, accountID, direction, step, pID string, startDate, endDate time.Time, limit int64) ([]AccountLedgerResponse, error) { +func (c *CoinbasePro) GetAccountLedger(ctx context.Context, accountID, direction, step, pID string, startDate, endDate time.Time, limit int64) ([]AccountLedgerResponse, ReturnedPaginationHeaders, error) { var params Params params.urlVals = url.Values{} + var rph ReturnedPaginationHeaders err := params.PrepareDateString(startDate, endDate) if err != nil { - return nil, err + return nil, rph, err } path := fmt.Sprintf("%s/%s/%s", coinbaseproAccounts, accountID, coinbaseproLedger) @@ -132,13 +140,18 @@ func (c *CoinbasePro) GetAccountLedger(ctx context.Context, accountID, direction path = common.EncodeURLValues(path, params.urlVals) var resp []AccountLedgerResponse + retH := http.Header{} - return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp) + err = c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp, &retH) + + rph = ReturnedPaginationHeaders{before: retH.Get("CB-BEFORE"), after: retH.Get("CB-AFTER")} + + return resp, rph, err } // GetAccountTransfers returns a history of withdrawal and or deposit // transactions for a single account -func (c *CoinbasePro) GetAccountTransfers(ctx context.Context, accountID, direction, step, transferType string, limit int64) ([]TransferResponse, error) { +func (c *CoinbasePro) GetAccountTransfers(ctx context.Context, accountID, direction, step, transferType string, limit int64) ([]TransferResponse, ReturnedPaginationHeaders, error) { path := fmt.Sprintf("%s/%s/%s", coinbaseproAccounts, accountID, coinbaseproTransfers) var params Params @@ -150,9 +163,13 @@ func (c *CoinbasePro) GetAccountTransfers(ctx context.Context, accountID, direct path = common.EncodeURLValues(path, params.urlVals) var resp []TransferResponse + retH := http.Header{} - return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp) + err := c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp, &retH) + + rph := ReturnedPaginationHeaders{before: retH.Get("CB-BEFORE"), after: retH.Get("CB-AFTER")} + + return resp, rph, err } // GetAddressBook returns all addresses stored in the address book @@ -160,25 +177,28 @@ func (c *CoinbasePro) GetAddressBook(ctx context.Context) ([]GetAddressResponse, var resp []GetAddressResponse return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, coinbaseproAddressBook, nil, &resp) + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, coinbaseproAddressBook, nil, &resp, nil) } // AddAddresses adds new addresses to the address book func (c *CoinbasePro) AddAddresses(ctx context.Context, req []AddAddressRequest) ([]AddAddressResponse, error) { params := make(map[string]interface{}) params["addresses"] = req + // The documentation also prompts us to add in an arbitrary amount of strings + // into the parameters, without specifying what they're for. Adding some seemed + // to do nothing var resp []AddAddressResponse return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproAddressBook, params, &resp) + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproAddressBook, params, &resp, nil) } // DeleteAddress deletes an address from the address book func (c *CoinbasePro) DeleteAddress(ctx context.Context, addressID string) error { path := fmt.Sprintf("%s/%s", coinbaseproAddressBook, addressID) - return c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodDelete, path, nil, nil) + return c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodDelete, path, nil, nil, nil) } // GetCoinbaseWallets returns all of the user's available Coinbase wallets @@ -186,7 +206,7 @@ func (c *CoinbasePro) GetCoinbaseWallets(ctx context.Context) ([]CoinbaseAccount var resp []CoinbaseAccounts return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, coinbaseproCoinbaseAccounts, nil, &resp) + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, coinbaseproCoinbaseAccounts, nil, &resp, nil) } // GenerateCryptoAddress generates a one-time address for deposting crypto @@ -201,7 +221,7 @@ func (c *CoinbasePro) GenerateCryptoAddress(ctx context.Context, accountID, prof resp := CryptoAddressResponse{} return &resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, path, params, &resp) + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, path, params, &resp, nil) } // ConvertCurrency converts between two currencies in the specified profile @@ -212,7 +232,7 @@ func (c *CoinbasePro) ConvertCurrency(ctx context.Context, profileID, from, to, resp := ConvertResponse{} return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproConversions, params, &resp) + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproConversions, params, &resp, nil) } // GetConversionByID returns the details of a past conversion, given its ID @@ -226,7 +246,7 @@ func (c *CoinbasePro) GetConversionByID(ctx context.Context, conversionID, profi resp := ConvertResponse{} return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp) + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp, nil) } // GetAllCurrencies returns a list of currencies known by the exchange @@ -257,7 +277,7 @@ func (c *CoinbasePro) DepositViaCoinbase(ctx context.Context, profileID, currenc resp := DepositWithdrawalInfo{} return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproDepositCoinbase, params, &resp) + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproDepositCoinbase, params, &resp, nil) } // DepositViaPaymentMethod deposits funds from a payment method. SEPA is not allowed @@ -269,7 +289,7 @@ func (c *CoinbasePro) DepositViaPaymentMethod(ctx context.Context, profileID, pa resp := DepositWithdrawalInfo{} return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproPaymentMethodDeposit, params, &resp) + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproPaymentMethodDeposit, params, &resp, nil) } // GetPayMethods returns a full list of payment methods @@ -277,12 +297,12 @@ func (c *CoinbasePro) GetPayMethods(ctx context.Context) ([]PaymentMethod, error var resp []PaymentMethod return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, coinbaseproPaymentMethod, nil, &resp) + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, coinbaseproPaymentMethod, nil, &resp, nil) } // GetAllTransfers returns all in-progress and completed transfers in and out of any // of the user's accounts -func (c *CoinbasePro) GetAllTransfers(ctx context.Context, profileID, direction, step, transferType string, limit int64) ([]TransferResponse, error) { +func (c *CoinbasePro) GetAllTransfers(ctx context.Context, profileID, direction, step, transferType string, limit int64) ([]TransferResponse, ReturnedPaginationHeaders, error) { var params Params params.urlVals = url.Values{} params.urlVals.Set("profile_id", profileID) @@ -291,9 +311,13 @@ func (c *CoinbasePro) GetAllTransfers(ctx context.Context, profileID, direction, path := common.EncodeURLValues(coinbaseproTransfers, params.urlVals) resp := []TransferResponse{} + retH := http.Header{} - return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp) + err := c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp, &retH) + + rph := ReturnedPaginationHeaders{before: retH.Get("CB-BEFORE"), after: retH.Get("CB-AFTER")} + + return resp, rph, err } // GetTransferByID returns information on a single transfer when provided with its ID @@ -302,7 +326,7 @@ func (c *CoinbasePro) GetTransferByID(ctx context.Context, transferID string) (* resp := TransferResponse{} return &resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp) + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp, nil) } // SendTravelInfoForTransfer sends travel rule information for a transfer @@ -315,7 +339,7 @@ func (c *CoinbasePro) SendTravelInfoForTransfer(ctx context.Context, transferID, var resp string return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, path, params, &resp) + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, path, params, &resp, nil) } // WithdrawViaCoinbase withdraws funds to a coinbase account. @@ -327,7 +351,7 @@ func (c *CoinbasePro) WithdrawViaCoinbase(ctx context.Context, profileID, accoun resp := DepositWithdrawalInfo{} return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproWithdrawalCoinbase, req, &resp) + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproWithdrawalCoinbase, req, &resp, nil) } // WithdrawCrypto withdraws funds to a crypto address @@ -342,7 +366,7 @@ func (c *CoinbasePro) WithdrawCrypto(ctx context.Context, profileID, currency, c resp := DepositWithdrawalInfo{} return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproWithdrawalCrypto, req, &resp) + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproWithdrawalCrypto, req, &resp, nil) } // GetWithdrawalFeeEstimate has Coinbase estimate the fee for withdrawing in a certain @@ -363,7 +387,7 @@ func (c *CoinbasePro) GetWithdrawalFeeEstimate(ctx context.Context, currency, cr path := common.EncodeURLValues(coinbaseproFeeEstimate, params.urlVals) return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp) + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp, nil) } // WithdrawViaPaymentMethod withdraws funds to a payment method @@ -375,7 +399,7 @@ func (c *CoinbasePro) WithdrawViaPaymentMethod(ctx context.Context, profileID, p resp := DepositWithdrawalInfo{} return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproWithdrawalPaymentMethod, req, &resp) + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproWithdrawalPaymentMethod, req, &resp, nil) } // GetFees returns your current maker & taker fee rates, as well as your 30-day @@ -384,7 +408,7 @@ func (c *CoinbasePro) GetFees(ctx context.Context) (FeeResponse, error) { resp := FeeResponse{} return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, coinbaseproFees, nil, &resp) + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, coinbaseproFees, nil, &resp, nil) } // GetFills returns information of recent fills on the specified profile @@ -413,7 +437,7 @@ func (c *CoinbasePro) GetFills(ctx context.Context, orderID, currencyPair, direc var resp []FillResponse - return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp) + return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp, nil) } // GetAllOrders lists all open or unsettled orders @@ -443,7 +467,7 @@ func (c *CoinbasePro) GetAllOrders(ctx context.Context, profileID, currencyPair, var resp []GeneralizedOrderResponse - return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp) + return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp, nil) } // CancelAllExistingOrders attempts to cancel all open orders. The exchange warns that @@ -458,7 +482,7 @@ func (c *CoinbasePro) CancelAllExistingOrders(ctx context.Context, profileID, cu var resp []string - return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodDelete, path, nil, &resp) + return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodDelete, path, nil, &resp, nil) } // PlaceOrder places either a limit, market, or stop order @@ -491,7 +515,7 @@ func (c *CoinbasePro) PlaceOrder(ctx context.Context, profileID, orderType, side } return &resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproOrders, req, &resp) + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproOrders, req, &resp, nil) } // GetOrderByID returns a single order by order id. @@ -515,7 +539,7 @@ func (c *CoinbasePro) GetOrderByID(ctx context.Context, orderID, marketType stri path := fmt.Sprintf("%s/%s", coinbaseproOrders, orderID) path = common.EncodeURLValues(path, param.urlVals) - return &resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp) + return &resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp, nil) } // CancelExistingOrder cancels order by orderID @@ -535,7 +559,7 @@ func (c *CoinbasePro) CancelExistingOrder(ctx context.Context, orderID, profileI path = common.EncodeURLValues(path, param.urlVals) - return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodDelete, path, nil, nil) + return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodDelete, path, nil, nil, nil) } // GetSignedPrices returns some cryptographically signed prices ready to be @@ -544,7 +568,7 @@ func (c *CoinbasePro) GetSignedPrices(ctx context.Context) (SignedPrices, error) resp := SignedPrices{} return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, coinbaseproOracle, nil, &resp) + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, coinbaseproOracle, nil, &resp, nil) } // GetAllProducts returns information on all currency pairs that are available for trading @@ -720,12 +744,12 @@ func (c *CoinbasePro) GetAllProfiles(ctx context.Context, active *bool) ([]Profi params.urlVals.Set("active", strconv.FormatBool(*active)) } - var profiles []Profile + var resp []Profile path := common.EncodeURLValues(coinbaseproProfiles, params.urlVals) - return profiles, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &profiles) + return resp, + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp, nil) } // CreateAProfile creates a new profile, failing if no name is provided, @@ -739,7 +763,7 @@ func (c *CoinbasePro) CreateAProfile(ctx context.Context, name string) (Profile, req := map[string]interface{}{"name": name} return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproProfiles, req, &resp) + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproProfiles, req, &resp, nil) } // TransferBetweenProfiles transfers an amount of currency from one profile to another @@ -755,7 +779,7 @@ func (c *CoinbasePro) TransferBetweenProfiles(ctx context.Context, from, to, cur path := fmt.Sprintf("%s/%s", coinbaseproProfiles, coinbaseproTransfer) return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, path, req, &resp) + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, path, req, &resp, nil) } // GetProfileByID returns information on a single profile, provided its ID @@ -771,7 +795,7 @@ func (c *CoinbasePro) GetProfileByID(ctx context.Context, profileID string, acti path = common.EncodeURLValues(path, params.urlVals) return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp) + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp, nil) } // RenameProfile renames a profile, provided its ID @@ -786,7 +810,7 @@ func (c *CoinbasePro) RenameProfile(ctx context.Context, profileID, newName stri path := fmt.Sprintf("%s/%s", coinbaseproProfiles, profileID) return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPut, path, req, &resp) + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPut, path, req, &resp, nil) } // DeleteProfile deletes a profile and transfers its funds to a specified @@ -802,7 +826,7 @@ func (c *CoinbasePro) DeleteProfile(ctx context.Context, profileID, transferTo s path := fmt.Sprintf("%s/%s/%s", coinbaseproProfiles, profileID, coinbaseproDeactivate) return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodDelete, path, req, &resp) + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodDelete, path, req, &resp, nil) } // GetAllReports returns a list of all user-generated reports @@ -824,7 +848,7 @@ func (c *CoinbasePro) GetAllReports(ctx context.Context, profileID string, repor path := common.EncodeURLValues(coinbaseproReports, params.urlVals) return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp) + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp, nil) } // CreateReport creates a new report @@ -854,7 +878,7 @@ func (c *CoinbasePro) CreateReport(ctx context.Context, reportType, year, format EndDate: endDate.Format(time.RFC3339), ProductID: productID} } return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproReports, req, &resp) + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproReports, req, &resp, nil) } // GetReportByID returns a single report, provided its ID @@ -867,7 +891,7 @@ func (c *CoinbasePro) GetReportByID(ctx context.Context, reportID string) (Repor path := fmt.Sprintf("%s/%s", coinbaseproReports, reportID) return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp) + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp, nil) } // GetTravelRules returns a list of all travel rule information @@ -881,7 +905,7 @@ func (c *CoinbasePro) GetTravelRules(ctx context.Context, direction, step, addre path := common.EncodeURLValues(coinbaseproTravelRules, params.urlVals) - return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp) + return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp, nil) } // CreateTravelRule creates a travel rule entry @@ -892,7 +916,7 @@ func (c *CoinbasePro) CreateTravelRule(ctx context.Context, address, originName, "originator_country": originCountry} return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproTravelRules, req, &resp) + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproTravelRules, req, &resp, nil) } // DeleteTravelRule deletes a travel rule entry @@ -903,7 +927,7 @@ func (c *CoinbasePro) DeleteTravelRule(ctx context.Context, address string) erro path := fmt.Sprintf("%s/%s", coinbaseproTravelRules, address) - return c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodDelete, path, nil, nil) + return c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodDelete, path, nil, nil, nil) } // GetExchangeLimits returns information on payment method transfer limits, @@ -914,7 +938,7 @@ func (c *CoinbasePro) GetExchangeLimits(ctx context.Context, userID string) (Exc path := fmt.Sprintf("%s/%s", coinbaseproUsers, userID) return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp) + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp, nil) } // UpdateSettlementPreference updates whether one wants their funds to @@ -931,7 +955,7 @@ func (c *CoinbasePro) UpdateSettlementPreference(ctx context.Context, userID, pr var resp string return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPut, path, req, &resp) + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPut, path, req, &resp, nil) } // GetAllWrappedAssets returns information on all supported wrapped assets @@ -965,7 +989,7 @@ func (c *CoinbasePro) GetAllStakeWraps(ctx context.Context, direction, from, to, path = common.EncodeURLValues(path, params.urlVals) return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp) + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp, nil) } // CreateStakeWrap stakes and wraps from one currency to another, under the profile @@ -982,7 +1006,7 @@ func (c *CoinbasePro) CreateStakeWrap(ctx context.Context, from, to string, amou path := fmt.Sprintf("%s/%s", coinbaseproWrappedAssets, coinbaseproStakeWraps) return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, path, req, &resp) + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, path, req, &resp, nil) } // GetStakeWrapByID returns details of a single stake-wrap @@ -996,7 +1020,7 @@ func (c *CoinbasePro) GetStakeWrapByID(ctx context.Context, stakeWrapID string) path := fmt.Sprintf("%s/%s/%s", coinbaseproWrappedAssets, coinbaseproStakeWraps, stakeWrapID) return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp) + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp, nil) } // GetWrappedAssetByID returns details of a single wrapped asset @@ -1113,7 +1137,7 @@ func (c *CoinbasePro) SendHTTPRequest(ctx context.Context, ep exchange.URL, path } // SendAuthenticatedHTTPRequest sends an authenticated HTTP request -func (c *CoinbasePro) SendAuthenticatedHTTPRequest(ctx context.Context, ep exchange.URL, method, path string, params map[string]interface{}, result interface{}) (err error) { +func (c *CoinbasePro) SendAuthenticatedHTTPRequest(ctx context.Context, ep exchange.URL, method, path string, params map[string]interface{}, result interface{}, returnHead *http.Header) (err error) { creds, err := c.GetCredentials(ctx) if err != nil { return err @@ -1123,8 +1147,6 @@ func (c *CoinbasePro) SendAuthenticatedHTTPRequest(ctx context.Context, ep excha return err } - returnHead := http.Header{} - newRequest := func() (*request.Item, error) { payload := []byte("") if params != nil { @@ -1162,11 +1184,10 @@ func (c *CoinbasePro) SendAuthenticatedHTTPRequest(ctx context.Context, ep excha Verbose: c.Verbose, HTTPDebugging: c.HTTPDebugging, HTTPRecording: c.HTTPRecording, - HeaderResponse: &returnHead, + HeaderResponse: returnHead, }, nil } err = c.SendPayload(ctx, request.Unset, newRequest, request.AuthenticatedRequest) - fmt.Printf("Header returned: %+v\n", returnHead) return err } diff --git a/exchanges/coinbasepro/coinbasepro_test.go b/exchanges/coinbasepro/coinbasepro_test.go index 7021e6cc88f..1fc39622ff5 100644 --- a/exchanges/coinbasepro/coinbasepro_test.go +++ b/exchanges/coinbasepro/coinbasepro_test.go @@ -34,10 +34,13 @@ const ( apiKey = "" apiSecret = "" clientID = "" // passphrase you made at API CREATION - canManipulateRealOrders = true + canManipulateRealOrders = false testingInSandbox = true ) +// Donation address +const testAddress = "bc1qk0jareu4jytc0cfrhr5wgshsq8282awpavfahc" + func TestMain(m *testing.M) { c.SetDefaults() if testingInSandbox { @@ -99,6 +102,9 @@ func TestGetAccountByID(t *testing.T) { if err != nil { t.Error("CoinBasePro GetAllAccounts() error", err) } + if len(longResp) == 0 { + t.Fatal("CoinBasePro GetAllAccounts() error, expected a non-empty response") + } shortResp, err := c.GetAccountByID(context.Background(), longResp[0].ID) if err != nil { t.Error("CoinBasePro GetAccountByID() error", err) @@ -114,11 +120,13 @@ func TestGetHolds(t *testing.T) { if err != nil { t.Error("CoinBasePro GetAllAccounts() error", err) } - resp, err := c.GetHolds(context.Background(), accID[1].ID, pageNone, "1", 2) + if len(accID) == 0 { + t.Fatal("CoinBasePro GetAllAccounts() error, expected a non-empty response") + } + _, _, err = c.GetHolds(context.Background(), accID[1].ID, pageNone, "2", 2) if err != nil { t.Error("CoinBasePro GetHolds() error", err) } - log.Printf("%+v", resp) } func TestGetAccountLedger(t *testing.T) { @@ -127,8 +135,15 @@ func TestGetAccountLedger(t *testing.T) { if err != nil { t.Error("CoinBasePro GetAllAccounts() error", err) } - _, err = c.GetAccountLedger(context.Background(), accID[0].ID, pageBefore, "", "a", - time.Unix(1, 1), time.Now(), 3) + _, _, err = c.GetAccountLedger(context.Background(), "", pageNone, "", "", time.Unix(2, 2), time.Unix(1, 1), 0) + if err == nil { + t.Error("CoinBasePro GetAccountLedger() error, expected an error due to invalid times") + } + if len(accID) == 0 { + t.Fatal("CoinBasePro GetAllAccounts() error, expected a non-empty response") + } + _, _, err = c.GetAccountLedger(context.Background(), accID[1].ID, pageNone, "1177507600", "a", + time.Unix(1, 1), time.Now(), 1000) if err != nil { t.Error("CoinBasePro GetAccountLedger() error", err) } @@ -140,7 +155,10 @@ func TestGetAccountTransfers(t *testing.T) { if err != nil { t.Error("CoinBasePro GetAllAccounts() error", err) } - _, err = c.GetAccountTransfers(context.Background(), accID[0].ID, "", "", "", 3) + if len(accID) == 0 { + t.Fatal("CoinBasePro GetAllAccounts() error, expected a non-empty response") + } + _, _, err = c.GetAccountTransfers(context.Background(), accID[1].ID, "", "", "", 3) if err != nil { t.Error(err) } @@ -148,29 +166,45 @@ func TestGetAccountTransfers(t *testing.T) { func TestGetAddressBook(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - _, err := c.GetAddressBook(context.Background()) + resp, err := c.GetAddressBook(context.Background()) if err != nil { t.Error("CoinBasePro GetAddressBook() error", err) } + assert.NotEmpty(t, resp, "CoinBasePro GetAddressBook() error, expected a non-empty response") } func TestAddAddresses(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) var req [1]AddAddressRequest var err error - req[0], err = PrepareAddAddress("this test", "is not", "properly", "implemented", "Coinbase", false) + req[0], err = PrepareAddAddress("BTC", testAddress, "", "implemented", "Coinbase", false) if err != nil { t.Error(err) } - _, err = c.AddAddresses(context.Background(), req[:]) + resp, err := c.AddAddresses(context.Background(), req[:]) if err != nil { t.Error("CoinBasePro AddAddresses() error", err) } + assert.NotEmpty(t, resp, "CoinBasePro AddAddresses() error, expected a non-empty response") } func TestDeleteAddress(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) - err := c.DeleteAddress(context.Background(), "Test not properly implemented") + var req [1]AddAddressRequest + var err error + req[0], err = PrepareAddAddress("BTC", testAddress, "", "implemented", "Coinbase", false) + if err != nil { + t.Error(err) + } + resp, err := c.AddAddresses(context.Background(), req[:]) + if err != nil { + t.Error("CoinBasePro AddAddresses() error", err) + } + if len(resp) == 0 { + t.Fatal("CoinBasePro AddAddresses() error, expected a non-empty response") + } + + err = c.DeleteAddress(context.Background(), resp[0].ID) if err != nil { t.Error("CoinBasePro DeleteAddress() error", err) } @@ -178,10 +212,11 @@ func TestDeleteAddress(t *testing.T) { func TestGetCoinbaseWallets(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - _, err := c.GetCoinbaseWallets(context.Background()) + resp, err := c.GetCoinbaseWallets(context.Background()) if err != nil { t.Error("CoinBasePro GetCoinbaseAccounts() error", err) } + assert.NotEmpty(t, resp, "CoinBasePro GetCoinbaseWallets() error, expected a non-empty response") } func TestGenerateCryptoAddress(t *testing.T) { @@ -190,26 +225,52 @@ func TestGenerateCryptoAddress(t *testing.T) { if err != nil { t.Error("CoinBasePro GetAllAccounts() error", err) } - _, err = c.GenerateCryptoAddress(context.Background(), accID[0].ID, "", "") + if len(accID) == 0 { + t.Fatal("CoinBasePro GetAllAccounts() error, expected a non-empty response") + } + resp, err := c.GenerateCryptoAddress(context.Background(), accID[0].ID, "", "") if err != nil { t.Error("CoinBasePro GenerateCryptoAddress() error", err) } + assert.NotEmpty(t, resp, "CoinBasePro GenerateCryptoAddress() error, expected a non-empty response") } func TestConvertCurrency(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) - _, err := c.ConvertCurrency(context.Background(), "This test", " is not", "implemented", "quite yet", 0) + actBool := true + profID, err := c.GetAllProfiles(context.Background(), &actBool) + if err != nil { + t.Error("CoinBasePro GetAllProfiles() error", err) + } + if len(profID) == 0 { + t.Fatal("CoinBasePro GetAllProfiles() error, expected a non-empty response") + } + resp, err := c.ConvertCurrency(context.Background(), profID[0].ID, "USD", "USDC", "", 1) if err != nil { t.Error("CoinBasePro ConvertCurrency() error", err) } + assert.NotEmpty(t, resp, "CoinBasePro ConvertCurrency() error, expected a non-empty response") } func TestGetConversionByID(t *testing.T) { - sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - _, err := c.GetConversionByID(context.Background(), "Test not", "implemented yet") - if err == nil { - t.Error("This really should have failed since a proper ID wasn't supplied.") + sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) + actBool := true + profID, err := c.GetAllProfiles(context.Background(), &actBool) + if err != nil { + t.Error("CoinBasePro GetAllProfiles() error", err) } + if len(profID) == 0 { + t.Fatal("CoinBasePro GetAllProfiles() error, expected a non-empty response") + } + resp, err := c.ConvertCurrency(context.Background(), profID[0].ID, "USD", "USDC", "", 1) + if err != nil { + t.Error("CoinBasePro ConvertCurrency() error", err) + } + resp2, err := c.GetConversionByID(context.Background(), resp.ID, profID[0].ID) + if err != nil { + t.Error("CoinBasePro GetConversionByID() error", err) + } + assert.NotEmpty(t, resp2, "CoinBasePro GetConversionByID() error, expected a non-empty response") } func TestGetAllCurrencies(t *testing.T) { @@ -232,18 +293,48 @@ func TestGetCurrencyByID(t *testing.T) { func TestDepositViaCoinbase(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) - _, err := c.DepositViaCoinbase(context.Background(), "This test", "is not", "yet implemented", 1) + accID, err := c.GetCoinbaseWallets(context.Background()) + if err != nil { + t.Error("CoinBasePro GetCoinbaseWallets() error", err) + } + if len(accID) == 0 { + t.Fatal("CoinBasePro GetCoinbaseWallets() error, expected a non-empty response") + } + resp, err := c.DepositViaCoinbase(context.Background(), "", "BTC", accID[1].ID, 1) if err != nil { t.Error("CoinBasePro DepositViaCoinbase() error", err) } + log.Printf("%+v", resp) } func TestDepositViaPaymentMethod(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) - _, err := c.DepositViaPaymentMethod(context.Background(), "This test", "is not", "yet implemented", 1) + profID, err := c.GetAllProfiles(context.Background(), nil) + if err != nil { + t.Error("CoinBasePro GetAllProfiles() error", err) + } + payID, err := c.GetPayMethods(context.Background()) if err != nil { - t.Error("CoinBasePro DepositViaPaymentMethod() error", err) + t.Error("CoinBasePro GetPayMethods() error", err) + } + for i := range payID { + if payID[i].Type == "ach_bank_account" || payID[i].Type == "wire" || payID[i].Type == "fedwire" || + payID[i].Type == "fiat_account" { + // The intention here is to use a payment method that isn't SEPA, but I'm not sure how they + // denote that. + resp, err := c.DepositViaPaymentMethod(context.Background(), profID[0].ID, payID[i].ID, "USD", 1) + if err != nil { + t.Error("CoinBasePro DepositViaPaymentMethod() error", err) + } + log.Printf("%+v", resp) + } } + + // resp, err := c.DepositViaPaymentMethod(context.Background(), profID[0].ID, idToUse, "USD", 1) + // if err != nil { + // t.Error("CoinBasePro DepositViaPaymentMethod() error", err) + // } + // log.Printf("%+v", resp) } func TestGetPayMethods(t *testing.T) { @@ -256,7 +347,7 @@ func TestGetPayMethods(t *testing.T) { func TestGetAllTransfers(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - _, err := c.GetAllTransfers(context.Background(), "", "", "", "", 3) + _, _, err := c.GetAllTransfers(context.Background(), "", "", "", "", 3) if err != nil { t.Error("CoinBasePro GetAllTransfers() error", err) } @@ -264,23 +355,59 @@ func TestGetAllTransfers(t *testing.T) { func TestGetTransferByID(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - _, err := c.GetTransferByID(context.Background(), "Not yet implemented") - if err == nil { - t.Error("This really should have failed since a proper ID wasn't supplied.") + resp, _, err := c.GetAllTransfers(context.Background(), "", "", "", "", 3) + if err != nil { + t.Error("CoinBasePro GetAllTransfers() error", err) + } + if len(resp) == 0 { + t.Log("TestGetTransferByID skipped due to there being zero transfers.") + } else { + _, err = c.GetTransferByID(context.Background(), resp[0].ID) + if err != nil { + t.Error("CoinBasePro GetTransferByID() error", err) + } } } func TestSendTravelInfoForTransfer(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) - _, err := c.SendTravelInfoForTransfer(context.Background(), "This test", "is not", "yet implemented") + resp, _, err := c.GetAllTransfers(context.Background(), "", "", "", "", 1000) if err != nil { - t.Error("CoinBasePro SendTravelInfoForTransfer() error", err) + t.Error("CoinBasePro GetAllTransfers() error", err) + } + if len(resp) == 0 { + t.Log("TestSendTravelInfoForTransfer skipped due to there being zero pending transfers.") + } else { + var tID string + var zeroValue ExchTime + for i := range resp { + if resp[i].CompletedAt == zeroValue && resp[i].CanceledAt == zeroValue && + resp[i].ProcessedAt == zeroValue { + tID = resp[i].ID + break + } + } + if tID == "" { + t.Log("TestSendTravelInfoForTransfer skipped due to there being zero pending transfers.") + } else { + _, err = c.SendTravelInfoForTransfer(context.Background(), tID, "GoCryptoTrader", "AU") + if err != nil { + t.Error("CoinBasePro SendTravelInfoForTransfer() error", err) + } + } } } func TestWithdrawViaCoinbase(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) - _, err := c.WithdrawViaCoinbase(context.Background(), "This test", "is not", "yet implemented", 1) + accID, err := c.GetCoinbaseWallets(context.Background()) + if err != nil { + t.Error("CoinBasePro GetCoinbaseWallets() error", err) + } + if len(accID) == 0 { + t.Fatal("CoinBasePro GetCoinbaseWallets() error, expected a non-empty response") + } + _, err = c.WithdrawViaCoinbase(context.Background(), "", accID[1].ID, "BTC", 1) if err != nil { t.Error("CoinBasePro WithdrawViaCoinbase() error", err) } @@ -288,7 +415,7 @@ func TestWithdrawViaCoinbase(t *testing.T) { func TestWithdrawCrypto(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) - _, err := c.WithdrawCrypto(context.Background(), "This", "test", "is", "not", "implemented", "yet", 1, + _, err := c.WithdrawCrypto(context.Background(), "This", "BTC", testAddress, "", "yet", "bitcoin", 1, false, false, 2) if err != nil { t.Error("CoinBasePro WithdrawCrypto() error", err) @@ -305,10 +432,9 @@ func TestGetWithdrawalFeeEstimate(t *testing.T) { if err == nil { t.Error("CoinBasePro GetWithdrawalFeeEstimate() error, expected error due to empty field") } - _, err = c.GetWithdrawalFeeEstimate(context.Background(), "This test is not", "yet implemented", - "due to not knowing a valid network string") - if err == nil { - t.Error("This should have errored out due to an improper network string") + _, err = c.GetWithdrawalFeeEstimate(context.Background(), "BTC", testAddress, "bitcoin") + if err != nil { + t.Error("CoinBasePro GetWithdrawalFeeEstimate() error", err) } } @@ -371,9 +497,12 @@ func TestPlaceOrder(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) for x := 0; x < 550; x++ { - _, _ = c.PlaceOrder(context.Background(), "this", "", "sell", "BTC-USD", "", "implemented", "", "sandbox", + resp, err := c.PlaceOrder(context.Background(), "this", "", "sell", "BTC-USD", "", "implemented", "", "sandbox", "testing", 0, 2<<30, 1, 0, false) - + if err != nil { + log.Printf("Damn, there was an error: %s", err) + } + log.Printf("The response is: %+v", resp) } // log.Printf("Response: %+v\nError: %+v", resp, err) diff --git a/exchanges/coinbasepro/coinbasepro_types.go b/exchanges/coinbasepro/coinbasepro_types.go index bf831b01ca7..f52db630e60 100644 --- a/exchanges/coinbasepro/coinbasepro_types.go +++ b/exchanges/coinbasepro/coinbasepro_types.go @@ -4,6 +4,7 @@ import ( "encoding/json" "net/url" "strconv" + "strings" "time" "github.com/thrasher-corp/gocryptotrader/common/convert" @@ -83,6 +84,7 @@ type Currency struct { ConvertibleTo []string `json:"convertible_to"` Details struct { Type string `json:"type"` + Symbol string `json:"symbol"` NetworkConfirmations int32 `json:"network_confirmations"` SortOrder int32 `json:"sort_order"` CryptoAddressLink string `json:"crypto_address_link"` @@ -136,9 +138,11 @@ type AccountLedgerResponse struct { Balance float64 `json:"balance,string"` Type string `json:"type"` Details struct { - CoinbaseAccountID string `json:"coinbase_account_id"` - CoinbaseTransactionID string `json:"coinbase_transaction_id"` - CoinbasePaymentMethodID string `json:"coinbase_payment_method_id"` + OrderID string `json:"order_id"` + ProductID string `json:"product_id"` + TradeID string `json:"trade_id"` + TransferID string `json:"transfer_id"` + TransferType string `json:"transfer_type"` } `json:"details"` } @@ -262,6 +266,19 @@ type GeneralizedOrderResponse struct { // DefaultAmount float64 `json:"default_amount,string"` // } +// AmCur is a sub-type used in LimitStruct +type AmCur struct { + Amount float64 `json:"amount,string"` + Currency string `json:"currency"` +} + +// LimitStruct is a sub-type used in PaymentMethod +type LimitStruct struct { + PeriodInDays int `json:"period_in_days"` + Total AmCur `json:"total"` + Remaining AmCur `json:"remaining"` +} + // PaymentMethod holds payment method information, returned by GetPayMethods type PaymentMethod struct { ID string `json:"id"` @@ -278,8 +295,11 @@ type PaymentMethod struct { ResourcePath string `json:"resource_path"` Verified bool `json:"verified"` Limits struct { - Type string `json:"type"` - Name string `json:"name"` + Type string `json:"type"` + Name string `json:"name"` + Buy []LimitStruct `json:"buy"` + InstantBuy []LimitStruct `json:"instant_buy"` + Sell []LimitStruct `json:"sell"` } `json:"limits"` AllowBuy bool `json:"allow_buy"` AllowSell bool `json:"allow_sell"` @@ -324,8 +344,8 @@ type PaymentMethod struct { } `json:"picker_data"` HoldBusinessDays int64 `json:"hold_business_days"` HoldDays int64 `json:"hold_days"` - VerificationMethod string `json:"verificationMethod"` - CDVStatus string `json:"cdvStatus"` + VerificationMethod string `json:"verification_method"` + CDVStatus string `json:"cdv_status"` } // LimitInfo is a sub-type for payment method @@ -387,17 +407,14 @@ type CoinbaseAccounts struct { Reference string `json:"reference"` } `json:"swift_deposit_information"` SepaDepositInformation struct { - Iban string `json:"iban"` - Swift string `json:"swift"` - BankName string `json:"bank_name"` - BankAddress string `json:"bank_address"` - BankCountry struct { - Name string `json:"name"` - Code string `json:"code"` - } `json:"bank_country"` - AccountName string `json:"account_name"` - AccountAddress string `json:"account_address"` - Reference string `json:"reference"` + Iban string `json:"iban"` + Swift string `json:"swift"` + BankName string `json:"bank_name"` + BankAddress string `json:"bank_address"` + BankCountryName string `json:"bank_country_name"` + AccountName string `json:"account_name"` + AccountAddress string `json:"account_address"` + Reference string `json:"reference"` } `json:"sepa_deposit_information"` UkDepositInformation struct { SortCode string `json:"sort_code"` @@ -687,19 +704,46 @@ type wsStatus struct { // TransferResponse contains information on a transfer, returned by GetAccountTransfers, // GetAllTransfers, and GetTransferByID type TransferResponse struct { - ID string `json:"id"` - Type string `json:"type"` - CreatedAt time.Time `json:"created_at"` - CompletedAt time.Time `json:"completed_at"` - CanceledAt time.Time `json:"canceled_at"` - ProcessedAt time.Time `json:"processed_at"` - Amount float64 `json:"amount,string"` + ID string `json:"id"` + Type string `json:"type"` + CreatedAt ExchTime `json:"created_at"` + CompletedAt ExchTime `json:"completed_at"` + CanceledAt ExchTime `json:"canceled_at"` + ProcessedAt ExchTime `json:"processed_at"` + AccountID string `json:"account_id"` + UserID string `json:"user_id"` + Amount float64 `json:"amount,string"` Details struct { - CoinbaseAccountID string `json:"coinbase_account_id"` - CoinbaseTransactionID string `json:"coinbase_transaction_id"` - CoinbasePaymentMethodID string `json:"coinbase_payment_method_id"` + CoinbasePayoutAt time.Time `json:"coinbase_payout_at"` + CoinbaseAccountID string `json:"coinbase_account_id"` + CoinbaseTransactionID string `json:"coinbase_transaction_id"` + CoinbaseDepositID string `json:"coinbase_deposit_id"` + CoinbasePaymentMethodID string `json:"coinbase_payment_method_id"` + CoinbasePaymentMethodType string `json:"coinbase_payment_method_type"` } `json:"details"` - UserNonce int64 `json:"user_nonce"` + UserNonce int64 `json:"user_nonce"` + ProfileID string `json:"profile_id"` + Currency string `json:"currency"` + Idem string `json:"idem"` +} + +type ExchTime time.Time + +func (t *ExchTime) UnmarshalJSON(b []byte) error { + s := strings.Trim(string(b), `"`) + if s == " " || s == "null" { + return nil + } + tt, err := time.Parse("2006-01-02 15:04:05.999999-07", s) + if err != nil { + return err + } + *t = ExchTime(tt) + return nil +} + +func (t ExchTime) String() string { + return time.Time(t).String() } // TravelRule contains information on a travel rule, returned by GetTravelRules @@ -743,8 +787,6 @@ type AddAddressRequest struct { Label string `json:"label"` VerifiedSelfHosted bool `json:"is_verified_self_hosted_wallet"` VaspID string `json:"vasp_id"` - // TODO: It also lets us add an arbitrary amount of strings under this object, - // but doesn't explain what they do. Investigate more later. } // AddAddressResponse contains information on the addresses just added, returned by @@ -778,14 +820,15 @@ type CryptoAddressResponse struct { Address string `json:"address"` DestinationTag string `json:"destination_tag"` } `json:"address_info"` - Name string `json:"name"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` - Network string `json:"network"` - URIScheme string `json:"uri_scheme"` - Resource string `json:"resource"` - ResourcePath string `json:"resource_path"` - Warnings []struct { + Name string `json:"name"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + Network string `json:"network"` + URIScheme string `json:"uri_scheme"` + Resource string `json:"resource"` + ResourcePath string `json:"resource_path"` + ExchangeDepositAddress bool `json:"exchange_deposit_address"` + Warnings []struct { Title string `json:"title"` Details string `json:"details"` ImageURL string `json:"image_url"` @@ -810,8 +853,8 @@ type ConvertResponse struct { // WithdrawalFeeEstimate is the exchange's estimate of the fee for a withdrawal, returned // by GetWithdrawalFeeEstimate type WithdrawalFeeEstimate struct { - Fee string `json:"fee"` - FeeBeforeSubsidy string `json:"fee_before_subsidy"` + Fee float64 `json:"fee"` + FeeBeforeSubsidy float64 `json:"fee_before_subsidy"` } // FeeResponse contains current taker and maker fee rates, as well as 30-day trailing @@ -951,3 +994,10 @@ type StakeWrap struct { type WrappedAssetConversionRate struct { Amount float64 `json:"amount,string"` } + +// ReturnedPaginationHeaders contains the pagination headers returned by the exchange, +// returned by GetHolds, GetAccountLedger, +type ReturnedPaginationHeaders struct { + before string + after string +} From 5a0ba23d141d78654693a53c3f206b2c6f5dfd10 Mon Sep 17 00:00:00 2001 From: Samuel Reid <43227667+cranktakular@users.noreply.github.com> Date: Wed, 1 Nov 2023 16:59:34 +1100 Subject: [PATCH 07/79] Coinbase revamp, starting on advanced trade API --- exchanges/coinbasepro/coinbasepro.go | 64 ++- exchanges/coinbasepro/coinbasepro_test.go | 483 +++++++++++++------ exchanges/coinbasepro/coinbasepro_types.go | 87 ++-- exchanges/coinbasepro/coinbasepro_wrapper.go | 2 +- exchanges/coinbasepro/ratelimit.go | 4 +- exchanges/request/retry.go | 2 +- 6 files changed, 435 insertions(+), 207 deletions(-) diff --git a/exchanges/coinbasepro/coinbasepro.go b/exchanges/coinbasepro/coinbasepro.go index 40d6440dca1..3c56a957d34 100644 --- a/exchanges/coinbasepro/coinbasepro.go +++ b/exchanges/coinbasepro/coinbasepro.go @@ -20,7 +20,7 @@ import ( ) const ( - coinbaseproAPIURL = "https://api.exchange.coinbase.com/" + coinbaseproAPIURL = "https://api.coinbase.com/" coinbaseproSandboxAPIURL = "https://api-public.sandbox.exchange.coinbase.com/" coinbaseproAPIVersion = "0" coinbaseproAccounts = "accounts" @@ -55,6 +55,7 @@ const ( coinbaseproDeactivate = "deactivate" coinbaseproReports = "reports" coinbaseproUsers = "users" + coinbaseproExchangeLimits = "exchange-limits" coinbaseproSettlementPreferences = "settlement-preferences" coinbaseproWrappedAssets = "wrapped-assets" coinbaseproStakeWraps = "stake-wrap" @@ -412,15 +413,17 @@ func (c *CoinbasePro) GetFees(ctx context.Context) (FeeResponse, error) { } // GetFills returns information of recent fills on the specified profile -func (c *CoinbasePro) GetFills(ctx context.Context, orderID, currencyPair, direction, step, marketType string, limit int64, startDate, endDate time.Time) ([]FillResponse, error) { +func (c *CoinbasePro) GetFills(ctx context.Context, orderID, currencyPair, direction, step, marketType string, limit int64, startDate, endDate time.Time) ([]FillResponse, ReturnedPaginationHeaders, error) { + var rph ReturnedPaginationHeaders + if orderID == "" && currencyPair == "" { - return nil, errors.New("requires either order id or product id") + return nil, rph, errors.New("requires either order id or product id") } var params Params params.urlVals = url.Values{} err := params.PrepareDateString(startDate, endDate) if err != nil { - return nil, err + return nil, rph, err } if orderID != "" { @@ -436,20 +439,26 @@ func (c *CoinbasePro) GetFills(ctx context.Context, orderID, currencyPair, direc path := common.EncodeURLValues(coinbaseproFills, params.urlVals) var resp []FillResponse + retH := http.Header{} - return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp, nil) + err = c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp, &retH) + + rph = ReturnedPaginationHeaders{before: retH.Get("CB-BEFORE"), after: retH.Get("CB-AFTER")} + + return resp, rph, err } -// GetAllOrders lists all open or unsettled orders -func (c *CoinbasePro) GetAllOrders(ctx context.Context, profileID, currencyPair, sortedBy, sorting, direction, step, marketType string, startDate, endDate time.Time, limit int64, status []string) ([]GeneralizedOrderResponse, error) { +// GetAllOrders lists orders, filtered by their status +func (c *CoinbasePro) GetAllOrders(ctx context.Context, profileID, currencyPair, sortedBy, sorting, direction, step, marketType string, startDate, endDate time.Time, limit int64, status []string) ([]GeneralizedOrderResponse, ReturnedPaginationHeaders, error) { + var rph ReturnedPaginationHeaders if limit < 1 { - return nil, errors.New("limit must be greater than 0") + return nil, rph, errors.New("limit must be greater than 0") } var params Params params.urlVals = make(url.Values) err := params.PrepareDateString(startDate, endDate) if err != nil { - return nil, err + return nil, rph, err } params.PrepareProfIDAndProdID(profileID, currencyPair) @@ -466,8 +475,13 @@ func (c *CoinbasePro) GetAllOrders(ctx context.Context, profileID, currencyPair, path := common.EncodeURLValues(coinbaseproOrders, params.urlVals) var resp []GeneralizedOrderResponse + retH := http.Header{} - return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp, nil) + err = c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp, &retH) + + rph = ReturnedPaginationHeaders{before: retH.Get("CB-BEFORE"), after: retH.Get("CB-AFTER")} + + return resp, rph, err } // CancelAllExistingOrders attempts to cancel all open orders. The exchange warns that @@ -489,7 +503,7 @@ func (c *CoinbasePro) CancelAllExistingOrders(ctx context.Context, profileID, cu func (c *CoinbasePro) PlaceOrder(ctx context.Context, profileID, orderType, side, currencyPair, stp, stop, timeInForce, cancelAfter, clientOID string, stopPrice, price, size, funds float64, postOnly bool) (*GeneralizedOrderResponse, error) { var resp GeneralizedOrderResponse - if (orderType == order.Market.Lower() || orderType == "" || orderType == "stop") && + if (orderType == order.Limit.Lower() || orderType == "" || orderType == "stop") && (price == 0 || size == 0) { return &resp, errors.New("price and size must be greater than 0 for limit or stop orders") } @@ -502,7 +516,6 @@ func (c *CoinbasePro) PlaceOrder(ctx context.Context, profileID, orderType, side req := map[string]interface{}{"profile_id": profileID, "type": orderType, "side": side, "product_id": currencyPair, "stp": stp, "stop": stop, - "price": strconv.FormatFloat(price, 'f', -1, 64), "size": strconv.FormatFloat(size, 'f', -1, 64), "time_in_force": timeInForce, "cancel_after": cancelAfter, "post_only": postOnly, "client_oid": clientOID} @@ -513,6 +526,9 @@ func (c *CoinbasePro) PlaceOrder(ctx context.Context, profileID, orderType, side if funds != 0 { req["funds"] = strconv.FormatFloat(funds, 'f', -1, 64) } + if price != 0 { + req["price"] = strconv.FormatFloat(price, 'f', -1, 64) + } return &resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproOrders, req, &resp, nil) @@ -530,7 +546,7 @@ func (c *CoinbasePro) GetOrderByID(ctx context.Context, orderID, marketType stri var param Params param.urlVals = url.Values{} - // Spot's currently the only supported market type; passing in anything else here will + // Spot's seemingly the only supported market type; passing in anything else here will // cause the request to time out after a minute if marketType == "spot" { param.urlVals.Set("market_type", marketType) @@ -861,6 +877,12 @@ func (c *CoinbasePro) CreateReport(ctx context.Context, reportType, year, format if reportType == "1099k-transaction-history" && year == "" { return resp, errors.New("year cannot be empty for 1099k-transaction-history reports") } + if reportType != "balance" { + err := common.StartEndTimeCheck(startDate, endDate) + if err != nil { + return resp, err + } + } req := map[string]interface{}{"type": reportType, "year": year, "format": format, "email": email, "profile_id": profileID} @@ -920,12 +942,12 @@ func (c *CoinbasePro) CreateTravelRule(ctx context.Context, address, originName, } // DeleteTravelRule deletes a travel rule entry -func (c *CoinbasePro) DeleteTravelRule(ctx context.Context, address string) error { - if address == "" { - return errors.New("address cannot be empty") +func (c *CoinbasePro) DeleteTravelRule(ctx context.Context, id string) error { + if id == "" { + return errors.New("id cannot be empty") } - path := fmt.Sprintf("%s/%s", coinbaseproTravelRules, address) + path := fmt.Sprintf("%s/%s", coinbaseproTravelRules, id) return c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodDelete, path, nil, nil, nil) } @@ -935,7 +957,7 @@ func (c *CoinbasePro) DeleteTravelRule(ctx context.Context, address string) erro func (c *CoinbasePro) GetExchangeLimits(ctx context.Context, userID string) (ExchangeLimits, error) { var resp ExchangeLimits - path := fmt.Sprintf("%s/%s", coinbaseproUsers, userID) + path := fmt.Sprintf("%s/%s/%s", coinbaseproUsers, userID, coinbaseproExchangeLimits) return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp, nil) @@ -1137,7 +1159,7 @@ func (c *CoinbasePro) SendHTTPRequest(ctx context.Context, ep exchange.URL, path } // SendAuthenticatedHTTPRequest sends an authenticated HTTP request -func (c *CoinbasePro) SendAuthenticatedHTTPRequest(ctx context.Context, ep exchange.URL, method, path string, params map[string]interface{}, result interface{}, returnHead *http.Header) (err error) { +func (c *CoinbasePro) SendAuthenticatedHTTPRequest(ctx context.Context, ep exchange.URL, method, path string, bodyParams map[string]interface{}, result interface{}, returnHead *http.Header) (err error) { creds, err := c.GetCredentials(ctx) if err != nil { return err @@ -1149,8 +1171,8 @@ func (c *CoinbasePro) SendAuthenticatedHTTPRequest(ctx context.Context, ep excha newRequest := func() (*request.Item, error) { payload := []byte("") - if params != nil { - payload, err = json.Marshal(params) + if bodyParams != nil { + payload, err = json.Marshal(bodyParams) if err != nil { return nil, err } diff --git a/exchanges/coinbasepro/coinbasepro_test.go b/exchanges/coinbasepro/coinbasepro_test.go index 1fc39622ff5..70af08850dd 100644 --- a/exchanges/coinbasepro/coinbasepro_test.go +++ b/exchanges/coinbasepro/coinbasepro_test.go @@ -15,7 +15,6 @@ import ( "github.com/gorilla/websocket" "github.com/stretchr/testify/assert" "github.com/thrasher-corp/gocryptotrader/common" - "github.com/thrasher-corp/gocryptotrader/common/convert" "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" @@ -31,15 +30,19 @@ var ( // Please supply your APIKeys here for better testing const ( - apiKey = "" - apiSecret = "" - clientID = "" // passphrase you made at API CREATION + apiKey = "" + apiSecret = "" + // clientID = "" // passphrase you made at API CREATION, might not exist any more canManipulateRealOrders = false - testingInSandbox = true + testingInSandbox = false ) -// Donation address -const testAddress = "bc1qk0jareu4jytc0cfrhr5wgshsq8282awpavfahc" +// Constants used within tests +const ( + // Donation address + testAddress = "bc1qk0jareu4jytc0cfrhr5wgshsq8282awpavfahc" + travelRuleDuplicateError = "CoinbasePro unsuccessful HTTP status code: 400 raw response: {\"message\":\"addresses stored must be unique\"}, authenticated request failed" +) func TestMain(m *testing.M) { c.SetDefaults() @@ -60,7 +63,7 @@ func TestMain(m *testing.M) { if apiKey != "" { gdxConfig.API.Credentials.Key = apiKey gdxConfig.API.Credentials.Secret = apiSecret - gdxConfig.API.Credentials.ClientID = clientID + // gdxConfig.API.Credentials.ClientID = clientID gdxConfig.API.AuthenticatedSupport = true gdxConfig.API.AuthenticatedWebsocketSupport = true } @@ -87,6 +90,16 @@ func TestStart(t *testing.T) { testWg.Wait() } +func TestSendAuthenticatedHTTPRequest(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + var resp interface{} + err := c.SendAuthenticatedHTTPRequest(context.Background(), exchange.RestSpot, "", "", nil, &resp, nil) + if err != nil { + t.Error("SendAuthenticatedHTTPRequest() error", err) + } + log.Printf("%+v", resp) +} + func TestGetAllAccounts(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) resp, err := c.GetAllAccounts(context.Background()) @@ -309,32 +322,33 @@ func TestDepositViaCoinbase(t *testing.T) { func TestDepositViaPaymentMethod(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) - profID, err := c.GetAllProfiles(context.Background(), nil) + actBool := true + profID, err := c.GetAllProfiles(context.Background(), &actBool) if err != nil { t.Error("CoinBasePro GetAllProfiles() error", err) } + if len(profID) == 0 { + t.Fatal("CoinBasePro GetAllProfiles() error, expected a non-empty response") + } payID, err := c.GetPayMethods(context.Background()) if err != nil { t.Error("CoinBasePro GetPayMethods() error", err) } - for i := range payID { - if payID[i].Type == "ach_bank_account" || payID[i].Type == "wire" || payID[i].Type == "fedwire" || - payID[i].Type == "fiat_account" { - // The intention here is to use a payment method that isn't SEPA, but I'm not sure how they - // denote that. - resp, err := c.DepositViaPaymentMethod(context.Background(), profID[0].ID, payID[i].ID, "USD", 1) - if err != nil { - t.Error("CoinBasePro DepositViaPaymentMethod() error", err) - } - log.Printf("%+v", resp) + var success bool + i := 0 + for i = range payID { + if payID[i].Type == "ach_bank_account" { + success = true + break } } - - // resp, err := c.DepositViaPaymentMethod(context.Background(), profID[0].ID, idToUse, "USD", 1) - // if err != nil { - // t.Error("CoinBasePro DepositViaPaymentMethod() error", err) - // } - // log.Printf("%+v", resp) + if !success { + t.Skip("Skipping test due to no ACH bank account found") + } + _, err = c.DepositViaPaymentMethod(context.Background(), profID[0].ID, payID[i].ID, payID[i].Currency, 1) + if err != nil { + t.Error("CoinBasePro DepositViaPaymentMethod() error", err) + } } func TestGetPayMethods(t *testing.T) { @@ -360,13 +374,13 @@ func TestGetTransferByID(t *testing.T) { t.Error("CoinBasePro GetAllTransfers() error", err) } if len(resp) == 0 { - t.Log("TestGetTransferByID skipped due to there being zero transfers.") - } else { - _, err = c.GetTransferByID(context.Background(), resp[0].ID) - if err != nil { - t.Error("CoinBasePro GetTransferByID() error", err) - } + t.Skip("TestGetTransferByID skipped due to there being zero transfers.") + } + _, err = c.GetTransferByID(context.Background(), resp[0].ID) + if err != nil { + t.Error("CoinBasePro GetTransferByID() error", err) } + } func TestSendTravelInfoForTransfer(t *testing.T) { @@ -376,26 +390,26 @@ func TestSendTravelInfoForTransfer(t *testing.T) { t.Error("CoinBasePro GetAllTransfers() error", err) } if len(resp) == 0 { + t.Skip("TestSendTravelInfoForTransfer skipped due to there being zero pending transfers.") + } + var tID string + var zeroValue ExchTime + for i := range resp { + if resp[i].CompletedAt == zeroValue && resp[i].CanceledAt == zeroValue && + resp[i].ProcessedAt == zeroValue { + tID = resp[i].ID + break + } + } + if tID == "" { t.Log("TestSendTravelInfoForTransfer skipped due to there being zero pending transfers.") } else { - var tID string - var zeroValue ExchTime - for i := range resp { - if resp[i].CompletedAt == zeroValue && resp[i].CanceledAt == zeroValue && - resp[i].ProcessedAt == zeroValue { - tID = resp[i].ID - break - } - } - if tID == "" { - t.Log("TestSendTravelInfoForTransfer skipped due to there being zero pending transfers.") - } else { - _, err = c.SendTravelInfoForTransfer(context.Background(), tID, "GoCryptoTrader", "AU") - if err != nil { - t.Error("CoinBasePro SendTravelInfoForTransfer() error", err) - } + _, err = c.SendTravelInfoForTransfer(context.Background(), tID, "GoCryptoTrader", "AU") + if err != nil { + t.Error("CoinBasePro SendTravelInfoForTransfer() error", err) } } + } func TestWithdrawViaCoinbase(t *testing.T) { @@ -415,11 +429,20 @@ func TestWithdrawViaCoinbase(t *testing.T) { func TestWithdrawCrypto(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) - _, err := c.WithdrawCrypto(context.Background(), "This", "BTC", testAddress, "", "yet", "bitcoin", 1, + actBool := true + profID, err := c.GetAllProfiles(context.Background(), &actBool) + if err != nil { + t.Error("CoinBasePro GetAllProfiles() error", err) + } + if len(profID) == 0 { + t.Fatal("CoinBasePro GetAllProfiles() error, expected a non-empty response") + } + resp, err := c.WithdrawCrypto(context.Background(), profID[0].ID, "BTC", testAddress, "", "", "bitcoin", 1, false, false, 2) if err != nil { t.Error("CoinBasePro WithdrawCrypto() error", err) } + log.Printf("%+v", resp) } func TestGetWithdrawalFeeEstimate(t *testing.T) { @@ -440,7 +463,30 @@ func TestGetWithdrawalFeeEstimate(t *testing.T) { func TestWithdrawViaPaymentMethod(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) - _, err := c.WithdrawViaPaymentMethod(context.Background(), "This test", "is not", "yet implemented", 1) + actBool := true + profID, err := c.GetAllProfiles(context.Background(), &actBool) + if err != nil { + t.Error("CoinBasePro GetAllProfiles() error", err) + } + if len(profID) == 0 { + t.Fatal("CoinBasePro GetAllProfiles() error, expected a non-empty response") + } + payID, err := c.GetPayMethods(context.Background()) + if err != nil { + t.Error("CoinBasePro GetPayMethods() error", err) + } + var success bool + i := 0 + for i = range payID { + if payID[i].Type == "ach_bank_account" { + success = true + break + } + } + if !success { + t.Skip("Skipping test due to no ACH bank account found") + } + _, err = c.WithdrawViaPaymentMethod(context.Background(), profID[0].ID, payID[i].ID, payID[i].Currency, 1) if err != nil { t.Error("CoinBasePro WithdrawViaPaymentMethod() error", err) } @@ -452,24 +498,28 @@ func TestGetFees(t *testing.T) { if err != nil { t.Error("CoinBasePro GetFees() error", err) } - if resp.TakerFeeRate == 0 { - t.Error("CoinBasePro GetFees() error, expected non-zero value for taker fee rate") - } + assert.NotEmpty(t, resp, "CoinBasePro GetFees() error, expected a non-empty response") } func TestGetFills(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - _, err := c.GetFills(context.Background(), "", "", "", "", "", 0, time.Time{}, time.Time{}) + _, _, err := c.GetFills(context.Background(), "", "", "", "", "", 0, time.Time{}, time.Time{}) if err == nil { t.Error("CoinBasePro GetFills() error, expected error due to empty order and product ID") } - _, err = c.GetFills(context.Background(), "1", "", "", "", "", 0, time.Time{}, time.Time{}) + _, _, err = c.GetFills(context.Background(), "1", "", "", "", "", 0, time.Unix(2, 2), time.Unix(1, 1)) if err == nil { - t.Error("CoinBasePro GetFills() error, expected error due to null time range") + t.Error("CoinBasePro GetFills() error, expected error due to invalid time range") } - _, err = c.GetFills(context.Background(), "", testPair.String(), "", "", "spot", 0, time.Unix(1, 1), time.Now()) + _, _, err = c.GetFills(context.Background(), "a", testPair.String(), "", "", "spot", 2, time.Unix(1, 1), + time.Now()) + if err == nil { + t.Error("CoinBasePro GetFills() error, expected error due to invalid order ID") + } + _, _, err = c.GetFills(context.Background(), "", testPair.String(), "", "", "spot", 2, time.Unix(1, 1), + time.Now()) if err != nil { t.Error("CoinBasePro GetFills() error", err) } @@ -477,9 +527,17 @@ func TestGetFills(t *testing.T) { func TestGetAllOrders(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - status := []string{"open", "pending", "active", "done"} + status := []string{"open", "pending", "rejected", "active", "done", "received", "settled", "all"} - _, err := c.GetAllOrders(context.Background(), "", "", "", "", "", "", "", time.Unix(1, 1), time.Now(), 5, status) + _, _, err := c.GetAllOrders(context.Background(), "", "", "", "", "", "", "", time.Time{}, time.Time{}, 0, status) + if err == nil { + t.Error("CoinBasePro GetAllOrders() error, expected error due to invalid limit") + } + _, _, err = c.GetAllOrders(context.Background(), "", "", "", "", "", "", "", time.Unix(2, 2), time.Unix(1, 1), 5, status) + if err == nil { + t.Error("CoinBasePro GetAllOrders() error, expected error due to null time range") + } + _, _, err = c.GetAllOrders(context.Background(), "", "", "", "", "", "", "", time.Unix(1, 1), time.Now(), 5, status) if err != nil { t.Error("CoinBasePro GetAllOrders() error", err) } @@ -487,7 +545,7 @@ func TestGetAllOrders(t *testing.T) { func TestCancelAllExistingOrders(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) - _, err := c.CancelAllExistingOrders(context.Background(), "This test is not", "yet implemented") + _, err := c.CancelAllExistingOrders(context.Background(), "", "") if err != nil { t.Error("CoinBasePro CancelAllExistingOrders() error", err) } @@ -495,29 +553,73 @@ func TestCancelAllExistingOrders(t *testing.T) { func TestPlaceOrder(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) - - for x := 0; x < 550; x++ { - resp, err := c.PlaceOrder(context.Background(), "this", "", "sell", "BTC-USD", "", "implemented", "", "sandbox", - "testing", 0, 2<<30, 1, 0, false) - if err != nil { - log.Printf("Damn, there was an error: %s", err) - } - log.Printf("The response is: %+v", resp) + _, err := c.PlaceOrder(context.Background(), "", "", "", "", "", "", "", "", "", 0, 0, 0, 0, false) + if err == nil { + t.Error("CoinBasePro PlaceOrder() error, expected error due to price and size") + } + _, err = c.PlaceOrder(context.Background(), "", order.Market.Lower(), "", "", "", "", "", "", "", 0, 0, 0, 0, + false) + if err == nil { + t.Error("CoinBasePro PlaceOrder() error, expected error due to no size or funds for market order") + } + _, err = c.PlaceOrder(context.Background(), "", "", "", "", "", "", "", "", "", 0, 1, 1, 0, false) + if err == nil { + t.Error("CoinBasePro PlaceOrder() error, expected error due to no side provided") + } + _, err = c.PlaceOrder(context.Background(), "meow", "meow", "sell", "meow", "meow", "meow", "meow", "meow", + "meow", 2<<31, 2<<30, 1, 1, false) + if err == nil { + t.Error("CoinBasePro PlaceOrder() error, expected error due to invalid fields") + } + _, err = c.PlaceOrder(context.Background(), "", "", "sell", "BTC-USD", "", "", "", "", "", 2<<31, 2<<30, 1, + 0, false) + if err != nil { + // This can also error out if you're at your limit of orders on this product + t.Error("CoinBasePro PlaceOrder() error", err) } - - // log.Printf("Response: %+v\nError: %+v", resp, err) } func TestGetOrderByID(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - resp, err := c.GetOrderByID(context.Background(), "940d4bf3-933b-4714-a702-155f82c3e739", "spot", false) - - log.Printf("Response: %+v\nError: %+v", resp, err) + ordID, _, err := c.GetAllOrders(context.Background(), "", "", "", "", "", "", "", time.Unix(1, 1), time.Now(), + 5, []string{"all"}) + if err != nil { + t.Error("CoinBasePro GetAllOrders() error", err) + } + if len(ordID) == 0 { + t.Skip("Skipping test due to no orders found") + } + _, err = c.GetOrderByID(context.Background(), "", "", true) + if err == nil { + t.Error("CoinBasePro GetOrderByID() error, expected error due to empty order ID") + } + _, err = c.GetOrderByID(context.Background(), "meow", "", true) + if err == nil { + t.Error("CoinBasePro GetOrderByID() error, expected error due to invalid order ID") + } + _, err = c.GetOrderByID(context.Background(), ordID[0].ID, ordID[0].MarketType, false) + if err != nil { + t.Error("CoinBasePro GetOrderByID() error", err) + } } func TestCancelExistingOrder(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) - _, err := c.CancelExistingOrder(context.Background(), "This test", "is not", "yet implemented", true) + _, err := c.CancelExistingOrder(context.Background(), "", "", "", true) + if err == nil { + t.Error("CoinBasePro CancelExistingOrder() error, expected error due to empty order ID") + } + _, err = c.CancelExistingOrder(context.Background(), "meow", "", "", true) + if err == nil { + t.Error("CoinBasePro GetOrderByID() error, expected error due to invalid order ID") + } + ordID, err := c.PlaceOrder(context.Background(), "", "", "sell", "BTC-USD", "", "", "", "", "", 2<<31, + 2<<30, 1, 0, false) + if err != nil { + // This can also error out if you're at your limit of orders on this product + t.Error("CoinBasePro PlaceOrder() error", err) + } + _, err = c.CancelExistingOrder(context.Background(), ordID.ID, "", "", false) if err != nil { t.Error("CoinBasePro CancelExistingOrder() error", err) } @@ -529,9 +631,7 @@ func TestGetSignedPrices(t *testing.T) { if err != nil { t.Error("CoinBasePro GetSignedPrices() error", err) } - if resp.Timestamp == "" { - t.Error("CoinBasePro GetSignedPrices() error, expected non-empty timestamp") - } + assert.NotEmpty(t, resp, "CoinBasePro GetSignedPrices() error, expected a non-empty response") } func TestGetAllProducts(t *testing.T) { @@ -539,9 +639,7 @@ func TestGetAllProducts(t *testing.T) { if err != nil { t.Error("Coinbase, GetAllProducts() Error:", err) } - if resp[0].ID == "" { - t.Error("Coinbase, GetAllProducts() Error, expected non-empty string") - } + assert.NotEmpty(t, resp, "Coinbase, GetAllProducts() Error, expected a non-empty response") } func TestGetProductByID(t *testing.T) { @@ -553,9 +651,7 @@ func TestGetProductByID(t *testing.T) { if err != nil { t.Error("Coinbase, GetProductByID() Error:", err) } - if resp.ID != "BTC-USD" { - t.Error("Coinbase, GetProductByID() Error, expected BTC-USD") - } + assert.NotEmpty(t, resp, "Coinbase, GetProductByID() Error, expected a non-empty response") } func TestGetOrderbook(t *testing.T) { @@ -567,14 +663,11 @@ func TestGetOrderbook(t *testing.T) { if err == nil { t.Error("Coinbase, GetOrderbook() Error, expected an error due to invalid pair") } - _, err = c.GetOrderbook(context.Background(), testPair.String(), 1) - if err != nil { - t.Error("Coinbase, GetOrderbook() Error", err) - } - _, err = c.GetOrderbook(context.Background(), testPair.String(), 3) + resp, err := c.GetOrderbook(context.Background(), testPair.String(), 1) if err != nil { t.Error("Coinbase, GetOrderbook() Error", err) } + assert.NotEmpty(t, resp, "Coinbase, GetOrderbook() Error, expected a non-empty response") } func TestGetHistoricRates(t *testing.T) { @@ -598,9 +691,7 @@ func TestGetHistoricRates(t *testing.T) { if err != nil { t.Error("Coinbase, GetHistoricRates() Error", err) } - if resp[0].High == 0 { - t.Error("Coinbase, GetHistoricRates() Error, expected non-zero value") - } + assert.NotEmpty(t, resp, "Coinbase, GetHistoricRates() Error, expected a non-empty response") } func TestGetStats(t *testing.T) { @@ -608,10 +699,11 @@ func TestGetStats(t *testing.T) { if err == nil { t.Error("Coinbase, GetStats() Error, expected an error due to nonexistent pair") } - _, err = c.GetStats(context.Background(), testPair.String()) + resp, err := c.GetStats(context.Background(), testPair.String()) if err != nil { t.Error("GetStats() error", err) } + assert.NotEmpty(t, resp, "Coinbase, GetStats() Error, expected a non-empty response") } func TestGetTicker(t *testing.T) { @@ -619,10 +711,11 @@ func TestGetTicker(t *testing.T) { if err == nil { t.Error("Coinbase, GetTicker() Error, expected an error due to nonexistent pair") } - _, err = c.GetTicker(context.Background(), testPair.String()) + resp, err := c.GetTicker(context.Background(), testPair.String()) if err != nil { t.Error("GetTicker() error", err) } + assert.NotEmpty(t, resp, "Coinbase, GetTicker() Error, expected a non-empty response") } func TestGetTrades(t *testing.T) { @@ -630,19 +723,21 @@ func TestGetTrades(t *testing.T) { if err == nil { t.Error("Coinbase, GetTrades() Error, expected an error due to nonexistent pair") } - _, err = c.GetTrades(context.Background(), testPair.String(), "", "", 1) + resp, err := c.GetTrades(context.Background(), testPair.String(), "", "", 1) if err != nil { t.Error("GetTrades() error", err) } + assert.NotEmpty(t, resp, "Coinbase, GetTrades() Error, expected a non-empty response") } func TestGetAllProfiles(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) active := true - _, err := c.GetAllProfiles(context.Background(), &active) + resp, err := c.GetAllProfiles(context.Background(), &active) if err != nil { t.Error("GetAllProfiles() error", err) } + assert.NotEmpty(t, resp, "Coinbase, GetAllProfiles() Error, expected a non-empty response") } func TestCreateAProfile(t *testing.T) { @@ -651,13 +746,19 @@ func TestCreateAProfile(t *testing.T) { if err == nil { t.Error("Coinbase, CreateAProfile() Error, expected an error due to empty name") } - // The names 'default' and 'margin' are reserved, so consider using those for tests - _, err = c.CreateAProfile(context.Background(), "GCT Test Profile") + _, err = c.CreateAProfile(context.Background(), "default") + if err == nil { + t.Error("CreateAProfile() error, expected an error due to reserved name") + } + t.Skip("Skipping test; seems to always return an internal server error when a non-reserved profile name is sent") + resp, err := c.CreateAProfile(context.Background(), "GCTTestProfile") if err != nil { t.Error("CreateAProfile() error", err) } + assert.NotEmpty(t, resp, "Coinbase, CreateAProfile() Error, expected a non-empty response") } +// Cannot d due to there only being one profile, and CreateAProfile not working func TestTransferBetweenProfiles(t *testing.T) { sharedtestvalues.SkipTestIfCannotManipulateOrders(t, c, canManipulateRealOrders) _, err := c.TransferBetweenProfiles(context.Background(), "", "", "", 0) @@ -681,9 +782,7 @@ func TestGetProfileByID(t *testing.T) { if err != nil { t.Error("GetProfileByID() error", err) } - if resp2.ID != resp[0].ID { - t.Error("GetProfileByID() error, expected matching ID's") - } + assert.NotEmpty(t, resp2, "Coinbase, GetProfileByID() Error, expected a non-empty response") } func TestRenameProfile(t *testing.T) { @@ -692,31 +791,55 @@ func TestRenameProfile(t *testing.T) { if err == nil { t.Error("Coinbase, RenameProfile() Error, expected an error due to empty fields") } - _, err = c.RenameProfile(context.Background(), "this test has", "not been implemented") + profID, err := c.GetAllProfiles(context.Background(), nil) + if err != nil { + t.Error("GetAllProfiles() error", err) + } + if len(profID) == 0 { + t.Fatal("CoinBasePro GetAllProfiles() error, expected a non-empty response") + } + _, err = c.RenameProfile(context.Background(), profID[0].ID, "margin") if err == nil { - t.Error("Coinbase, RenameProfile() Error, expected an error due to un-implemented test") + t.Error("RenameProfile() error, expected an error due to reserved name") + } + t.Skip("Skipping test; seems to always return an internal server error when a non-reserved profile name is sent") + resp, err := c.RenameProfile(context.Background(), profID[0].ID, "GCTTestProfile2") + if err != nil { + t.Error("Coinbase, RenameProfile() Error", err) } + assert.NotEmpty(t, resp, "Coinbase, RenameProfile() Error, expected a non-empty response") } +// Cannot be tested due to there only being one profile, and CreateAProfile not working func TestDeleteProfile(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) _, err := c.DeleteProfile(context.Background(), "", "") if err == nil { t.Error("Coinbase, DeleteProfile() Error, expected an error due to empty fields") } - _, err = c.DeleteProfile(context.Background(), "this test has", "not been implemented") + profID, err := c.GetAllProfiles(context.Background(), nil) + if err != nil { + t.Error("GetAllProfiles() error", err) + } + if len(profID) == 0 { + t.Fatal("CoinBasePro GetAllProfiles() error, expected a non-empty response") + } + _, err = c.DeleteProfile(context.Background(), "profID[0].ID", "this test hasn't been implemented") if err == nil { t.Error("Coinbase, DeleteProfile() Error, expected an error due to un-implemented test") } } -func TestGetReport(t *testing.T) { +func TestGetAllReports(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - prof, err := c.GetAllProfiles(context.Background(), nil) + profID, err := c.GetAllProfiles(context.Background(), nil) if err != nil { t.Error("GetAllProfiles() error", err) } - _, err = c.GetAllReports(context.Background(), prof[0].ID, "account", time.Time{}, 1000, false) + if len(profID) == 0 { + t.Fatal("CoinBasePro GetAllProfiles() error, expected a non-empty response") + } + _, err = c.GetAllReports(context.Background(), profID[0].ID, "account", time.Time{}, 1000, false) if err != nil { t.Error("GetAllReports() error", err) } @@ -724,35 +847,58 @@ func TestGetReport(t *testing.T) { func TestCreateReport(t *testing.T) { sharedtestvalues.SkipTestIfCannotManipulateOrders(t, c, canManipulateRealOrders) - prof, err := c.GetAllProfiles(context.Background(), nil) + profID, err := c.GetAllProfiles(context.Background(), nil) if err != nil { t.Error("GetAllProfiles() error", err) } - _, err = c.CreateReport(context.Background(), "this", "test", "is", "not", prof[0].ID, "yet", "implemented", - time.Time{}, time.Time{}, time.Time{}) - if err == nil { - t.Error("Coinbase, CreateReport() Error, expected an error due to un-implemented test") + if len(profID) == 0 { + t.Fatal("CoinBasePro GetAllProfiles() error, expected a non-empty response") + } + accID, err := c.GetAllAccounts(context.Background()) + if err != nil { + t.Error("CoinBasePro GetAllAccounts() error", err) + } + if len(accID) == 0 { + t.Fatal("CoinBasePro GetAllAccounts() error, expected a non-empty response") + } + // _, err = c.CreateReport(context.Background(), "account", "", "pdf", "testemail@thrasher.io", profID[0].ID, + // "", accID[0].ID, time.Time{}, time.Unix(1, 1), time.Now()) + // if err != nil { + // t.Error("Coinbase, CreateReport() error", err) + // } + resp, err := c.CreateReport(context.Background(), "balance", "", "csv", "testemail@thrasher.io", "", + "", "", time.Now(), time.Unix(1, 1), time.Now()) + if err != nil { + t.Error("Coinbase, CreateReport() error", err) } + assert.NotEmpty(t, resp, "Coinbase, CreateReport() Error, expected a non-empty response") } func TestGetReportByID(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + _, err := c.GetReportByID(context.Background(), "") + if err == nil { + t.Error("Coinbase, GetReportByID() Error, expected an error due to empty fields") + } prof, err := c.GetAllProfiles(context.Background(), nil) if err != nil { t.Error("GetAllProfiles() error", err) } + if len(prof) == 0 { + t.Fatal("CoinBasePro GetAllProfiles() error, expected a non-empty response") + } resp, err := c.GetAllReports(context.Background(), prof[0].ID, "account", time.Time{}, 1000, false) if err != nil { t.Error("GetAllReports() error", err) } if len(resp) == 0 { - t.Log("No reports found, skipping test") - } else { - _, err = c.GetReportByID(context.Background(), resp[0].ID) - if err != nil { - t.Error("GetReportByID() error", err) - } + t.Skip("Skipping test due to no reports found") } + resp2, err := c.GetReportByID(context.Background(), resp[0].ID) + if err != nil { + t.Error("GetReportByID() error", err) + } + assert.NotEmpty(t, resp2, "Coinbase, GetReportByID() Error, expected a non-empty response") } func TestGetTravelRules(t *testing.T) { @@ -765,45 +911,78 @@ func TestGetTravelRules(t *testing.T) { func TestCreateTravelRule(t *testing.T) { sharedtestvalues.SkipTestIfCannotManipulateOrders(t, c, canManipulateRealOrders) - _, err := c.CreateTravelRule(context.Background(), "this test", "not yet", "implemented") - if err == nil { - t.Error("Coinbase, CreateTravelRule() Error, expected an error due to unimplemented test") + resp, err := c.CreateTravelRule(context.Background(), "GCT Travel Rule Test", "GoCryptoTrader", "AU") + if err != nil && err.Error() != travelRuleDuplicateError { + t.Error("Coinbase, CreateTravelRule() error", err) } + assert.NotEmpty(t, resp, "Coinbase, CreateTravelRule() Error, expected a non-empty response") } func TestDeleteTravelRule(t *testing.T) { sharedtestvalues.SkipTestIfCannotManipulateOrders(t, c, canManipulateRealOrders) - err := c.DeleteTravelRule(context.Background(), "this test is not yet implemented") + err := c.DeleteTravelRule(context.Background(), "") if err == nil { - t.Error("Coinbase, DeleteTravelRule() Error, expected an error due to unimplemented test") + t.Error("Coinbase, DeleteTravelRule() Error, expected an error due to empty ID") + } + _, err = c.CreateTravelRule(context.Background(), "GCT Travel Rule Test", "GoCryptoTrader", "AU") + if err != nil && err.Error() != travelRuleDuplicateError { + t.Error("Coinbase, CreateTravelRule() error", err) + } + resp, err := c.GetTravelRules(context.Background(), "", "", "", 0) + if err != nil { + t.Error("GetTravelRules() error", err) + } + if len(resp) == 0 { + t.Fatal("GetTravelRules() error, expected a non-empty response") + } + for i := range resp { + if resp[i].Address == "GCT Travel Rule Test" { + err = c.DeleteTravelRule(context.Background(), resp[i].ID) + if err != nil { + t.Error("Coinbase, DeleteTravelRule() error", err) + } + } } } func TestGetExchangeLimits(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - acc, err := c.GetAllAccounts(context.Background()) + accID, err := c.GetAllAccounts(context.Background()) if err != nil { t.Error("GetAllAccounts() error", err) } - _, err = c.GetExchangeLimits(context.Background(), acc[0].ID) + if len(accID) == 0 { + t.Fatal("CoinBasePro GetAllAccounts() error, expected a non-empty response") + } + resp, err := c.GetExchangeLimits(context.Background(), accID[0].ID) if err != nil { t.Error("GetExchangeLimits() error", err) } + assert.NotEmpty(t, resp, "Coinbase, GetExchangeLimits() Error, expected a non-empty response") } func TestUpdateSettlementPreference(t *testing.T) { sharedtestvalues.SkipTestIfCannotManipulateOrders(t, c, canManipulateRealOrders) - _, err := c.UpdateSettlementPreference(context.Background(), "this test", "not implemented") - if err == nil { - t.Error("Coinbase, UpdateSettlementPreference() Error, expected an error due to unimplemented test") + uID, err := c.GetAllProfiles(context.Background(), nil) + if err != nil { + t.Error("GetAllProfiles() error", err) + } + if len(uID) == 0 { + t.Fatal("CoinBasePro GetAllProfiles() error, expected a non-empty response") + } + resp, err := c.UpdateSettlementPreference(context.Background(), uID[0].UserID, "USD") + if err != nil { + t.Error("Coinbase, UpdateSettlementPreference() error", err) } + log.Printf("%+v", resp) } func TestGetAllWrappedAssets(t *testing.T) { - _, err := c.GetAllWrappedAssets(context.Background()) + resp, err := c.GetAllWrappedAssets(context.Background()) if err != nil { t.Error("GetAllWrappedAssets() error", err) } + assert.NotEmpty(t, resp, "Coinbase, GetAllWrappedAssets() Error, expected a non-empty response") } func TestGetAllStakeWraps(t *testing.T) { @@ -824,10 +1003,11 @@ func TestCreateStakeWrap(t *testing.T) { if err == nil { t.Error("Coinbase, CreateStakeWrap() Error, expected an error due to empty fields") } - _, err = c.CreateStakeWrap(context.Background(), "this test", "is not implemented", 1) - if err == nil { - t.Error("Coinbase, CreateStakeWrap() Error, expected an error due to unimplemented test") + resp, err := c.CreateStakeWrap(context.Background(), "ETH", "CBETH", 1) + if err != nil { + t.Error("Coinbase, CreateStakeWrap() error", err) } + log.Printf("%+v", resp) } func TestGetStakeWrapByID(t *testing.T) { @@ -836,13 +1016,14 @@ func TestGetStakeWrapByID(t *testing.T) { t.Error("GetAllStakeWraps() error", err) } if len(resp) == 0 { - t.Log("No stake wraps found, skipping test") - } else { - _, err = c.GetStakeWrapByID(context.Background(), resp[0].ID) - if err != nil { - t.Error("GetStakeWrapByID() error", err) - } + t.Skip("No stake wraps found, skipping test") + } + resp2, err := c.GetStakeWrapByID(context.Background(), resp[0].ID) + if err != nil { + t.Error("GetStakeWrapByID() error", err) } + assert.NotEmpty(t, resp2, "Coinbase, GetStakeWrapByID() Error, expected a non-empty response") + } func TestGetWrappedAssetByID(t *testing.T) { @@ -850,10 +1031,11 @@ func TestGetWrappedAssetByID(t *testing.T) { if err == nil { t.Error("Coinbase, GetWrappedAssetByID() Error, expected an error due to empty fields") } - _, err = c.GetWrappedAssetByID(context.Background(), "CBETH") + resp, err := c.GetWrappedAssetByID(context.Background(), "CBETH") if err != nil { t.Error("GetWrappedAssetByID() error", err) } + assert.NotEmpty(t, resp, "Coinbase, GetWrappedAssetByID() Error, expected a non-empty response") } func TestGetWrappedAssetConversionRate(t *testing.T) { @@ -861,10 +1043,11 @@ func TestGetWrappedAssetConversionRate(t *testing.T) { if err == nil { t.Error("Coinbase, GetWrappedAssetConversionRate() Error, expected an error due to empty fields") } - _, err = c.GetWrappedAssetConversionRate(context.Background(), "CBETH") + resp, err := c.GetWrappedAssetConversionRate(context.Background(), "CBETH") if err != nil { t.Error("GetWrappedAssetConversionRate() error", err) } + assert.NotEmpty(t, resp, "Coinbase, GetWrappedAssetConversionRate() Error, expected a non-empty response") } // func TestGetCurrentServerTime(t *testing.T) { @@ -1711,19 +1894,19 @@ func TestStatusToStandardStatus(t *testing.T) { } } -func TestParseTime(t *testing.T) { - // Rest examples use 2014-11-07T22:19:28.578544Z" and can be safely - // unmarhsalled into time.Time +// func TestParseTime(t *testing.T) { +// // Rest examples use 2014-11-07T22:19:28.578544Z" and can be safely +// // unmarhsalled into time.Time - // All events except for activate use the above, in the below test - // we'll use their API docs example - r := convert.TimeFromUnixTimestampDecimal(1483736448.299000).UTC() - if r.Year() != 2017 || - r.Month().String() != "January" || - r.Day() != 6 { - t.Error("unexpected result") - } -} +// // All events except for activate use the above, in the below test +// // we'll use their API docs example +// r := convert.TimeFromUnixTimestampDecimal(1483736448.299000).UTC() +// if r.Year() != 2017 || +// r.Month().String() != "January" || +// r.Day() != 6 { +// t.Error("unexpected result") +// } +// } // func TestGetRecentTrades(t *testing.T) { // t.Parallel() diff --git a/exchanges/coinbasepro/coinbasepro_types.go b/exchanges/coinbasepro/coinbasepro_types.go index f52db630e60..1831748fa4a 100644 --- a/exchanges/coinbasepro/coinbasepro_types.go +++ b/exchanges/coinbasepro/coinbasepro_types.go @@ -461,20 +461,31 @@ type Report struct { FulfillsNewRequirements bool `json:"fulfills_new_requirements"` Flags interface{} `json:"flags"` Details interface{} `json:"details"` + DefaultProfileID string `json:"default_profile_id"` OauthClient string `json:"oauth_client"` Preferences struct { PreferredMarket string `json:"preferred_market"` MarginTermsCompletedInUTC time.Time `json:"margin_terms_completed_in_utc"` MarginTutorialCompletedInUTC time.Time `json:"margin_tutorial_completed_in_utc"` } `json:"preferences"` - HasDefault bool `json:"has_default"` - StateCode string `json:"state_code"` - CBDataFromCache bool `json:"cb_data_from_cache"` - TwoFactorMethod string `json:"two_factor_method"` - LegalName string `json:"legal_name"` - TermsAccepted time.Time `json:"terms_accepted"` - HasClawbackPaymentPending bool `json:"has_clawback_payment_pending"` - HasRestrictedAssets bool `json:"has_restricted_assets"` + HasDefault bool `json:"has_default"` + OrgID interface{} `json:"org_id"` + IsBrokerage bool `json:"is_brokerage"` + TaxDomain string `json:"tax_domain"` + ProfileLimit uint16 `json:"profile_limit"` + APIKeyLimit uint16 `json:"api_key_limit"` + ConnectionLimit uint16 `json:"connection_limit"` + RateLimit uint16 `json:"rate_limit"` + GlobalConnectionLimit uint16 `json:"global_connection_limit"` + SettlementPreference interface{} `json:"settlement_preference"` + PrimeLendingEntityID interface{} `json:"prime_lending_entity_id"` + StateCode string `json:"state_code"` + CBDataFromCache bool `json:"cb_data_from_cache"` + TwoFactorMethod string `json:"two_factor_method"` + LegalName string `json:"legal_name"` + TermsAccepted time.Time `json:"terms_accepted"` + HasClawbackPaymentPending bool `json:"has_clawback_payment_pending"` + HasRestrictedAssets bool `json:"has_restricted_assets"` } `json:"user"` NewYorkState bool `json:"new_york_state"` DateTime time.Time `json:"date_time"` @@ -915,43 +926,55 @@ type CreateReportResponse struct { // ReportBalanceStruct is used internally when crafting a CreateReport request type ReportBalanceStruct struct { - DateTime string + DateTime string `json:"datetime"` } // ReportFillsTaxStruct is used internally when crafting a CreateReport request type ReportFillsTaxStruct struct { - StartDate string - EndDate string - ProductID string + StartDate string `json:"start_date"` + EndDate string `json:"end_date"` + ProductID string `json:"product_id"` } // ReportAccountStruct is used internally when crafting a CreateReport request type ReportAccountStruct struct { - StartDate string - EndDate string - AccountID string + StartDate string `json:"start_date"` + EndDate string `json:"end_date"` + AccountID string `json:"account_id"` +} + +// MaxRemSubStruct is a sub-type used in CurListSubStruct, which is itself used in ExchangeLimits +type MaxRemSubStruct struct { + Max float64 `json:"max"` + Remaining float64 `json:"remaining"` +} + +// CurListSubStruct is a sub-type used in ExchangeLimits +type CurListSubStruct struct { + USD MaxRemSubStruct `json:"usd"` + EUR MaxRemSubStruct `json:"eur"` + GBP MaxRemSubStruct `json:"gbp"` + BTC MaxRemSubStruct `json:"btc"` + ETH MaxRemSubStruct `json:"eth"` } // ExchangeLimits contains information on payment method transfer limits, returned // by GetExchangeLimits type ExchangeLimits struct { TransferLimits struct { - Buy interface{} `json:"buy"` - Sell interface{} `json:"sell"` - ExchangeWithdraw interface{} `json:"exchange_withdraw"` - Ach []struct { - Max float64 `json:"max,string"` - Remaining float64 `json:"remaining,string"` - PeriodInDays int32 `json:"period_in_days"` - } `json:"ach"` - AchNoBalance interface{} `json:"ach_no_balance"` - CreditDebitCard interface{} `json:"credit_debit_card"` - Secure3DBuy interface{} `json:"secure3d_buy"` - PaypalBuy interface{} `json:"paypal_buy"` - PaypalWithdrawal interface{} `json:"paypal_withdrawal"` - IdealDeposit interface{} `json:"ideal_deposit"` - SofortDeposit interface{} `json:"sofort_deposit"` - InstantAchWithdrawal interface{} `json:"instant_ach_withdrawal"` + Buy CurListSubStruct `json:"buy"` + Sell CurListSubStruct `json:"sell"` + ExchangeWithdraw CurListSubStruct `json:"exchange_withdraw"` + Ach CurListSubStruct `json:"ach"` + InstantBuy CurListSubStruct `json:"instant_buy"` + AchNoBalance CurListSubStruct `json:"ach_no_balance"` + CreditDebitCard CurListSubStruct `json:"credit_debit_card"` + Secure3DBuy CurListSubStruct `json:"secure3d_buy"` + PaypalBuy CurListSubStruct `json:"paypal_buy"` + PaypalWithdrawal CurListSubStruct `json:"paypal_withdrawal"` + IdealDeposit CurListSubStruct `json:"ideal_deposit"` + SofortDeposit CurListSubStruct `json:"sofort_deposit"` + InstantAchWithdrawal CurListSubStruct `json:"instant_ach_withdrawal"` } `json:"transfer_limits"` LimitCurrency string `json:"limit_currency"` } @@ -969,7 +992,7 @@ type WrappedAssetResponse struct { // AllWrappedAssetResponse contains information on all wrapped assets, returned by // GetAllWrappedAssets type AllWrappedAssetResponse struct { - WrappedAssetResponse []struct{} `json:"wrapped_assets"` + WrappedAssetResponse []WrappedAssetResponse `json:"wrapped_assets"` } // StakeWrap contains information on a stake wrap, returned by GetAllStakeWraps and diff --git a/exchanges/coinbasepro/coinbasepro_wrapper.go b/exchanges/coinbasepro/coinbasepro_wrapper.go index a053dff41c2..978c1b2b168 100644 --- a/exchanges/coinbasepro/coinbasepro_wrapper.go +++ b/exchanges/coinbasepro/coinbasepro_wrapper.go @@ -60,7 +60,7 @@ func (c *CoinbasePro) SetDefaults() { c.Verbose = true c.API.CredentialsValidator.RequiresKey = true c.API.CredentialsValidator.RequiresSecret = true - c.API.CredentialsValidator.RequiresClientID = true + // c.API.CredentialsValidator.RequiresClientID = true c.API.CredentialsValidator.RequiresBase64DecodeSecret = true requestFmt := ¤cy.PairFormat{Delimiter: currency.DashDelimiter, Uppercase: true} diff --git a/exchanges/coinbasepro/ratelimit.go b/exchanges/coinbasepro/ratelimit.go index 0761c87ddba..7b43eabb83d 100644 --- a/exchanges/coinbasepro/ratelimit.go +++ b/exchanges/coinbasepro/ratelimit.go @@ -8,10 +8,10 @@ import ( "golang.org/x/time/rate" ) -// Coinbasepro rate limit conts +// Coinbasepro rate limit constants const ( coinbaseproRateInterval = time.Second - coinbaseproAuthRate = 10 + coinbaseproAuthRate = 30 coinbaseproUnauthRate = 10 ) diff --git a/exchanges/request/retry.go b/exchanges/request/retry.go index 9ac974bf936..60fe1149d15 100644 --- a/exchanges/request/retry.go +++ b/exchanges/request/retry.go @@ -21,7 +21,7 @@ func DefaultRetryPolicy(resp *http.Response, err error) (bool, error) { } if resp.StatusCode == http.StatusTooManyRequests { - return true, nil + return false, nil } if resp.Header.Get(headerRetryAfter) != "" { From 88e67132c48e56539839968860e055880c089ae6 Mon Sep 17 00:00:00 2001 From: Samuel Reid <43227667+cranktakular@users.noreply.github.com> Date: Tue, 7 Nov 2023 16:58:18 +1100 Subject: [PATCH 08/79] Coinbase Advanced Trade Starts in Ernest V3 done, onto V2 Coinbase revamp nears completion Coinbase revamp nears completion Test commit should fail Coinbase revamp nears completion --- exchanges/coinbasepro/coinbasepro.go | 1530 +++++++++++++----- exchanges/coinbasepro/coinbasepro_test.go | 1335 +++++++++++---- exchanges/coinbasepro/coinbasepro_types.go | 892 ++++++++-- exchanges/coinbasepro/coinbasepro_wrapper.go | 112 +- exchanges/coinbasepro/ratelimit.go | 27 +- 5 files changed, 2928 insertions(+), 968 deletions(-) diff --git a/exchanges/coinbasepro/coinbasepro.go b/exchanges/coinbasepro/coinbasepro.go index 3c56a957d34..09d88ddb7f7 100644 --- a/exchanges/coinbasepro/coinbasepro.go +++ b/exchanges/coinbasepro/coinbasepro.go @@ -3,13 +3,13 @@ package coinbasepro import ( "bytes" "context" + "encoding/hex" "encoding/json" "errors" "fmt" "net/http" "net/url" "strconv" - "strings" "time" "github.com/thrasher-corp/gocryptotrader/common" @@ -20,46 +20,60 @@ import ( ) const ( - coinbaseproAPIURL = "https://api.coinbase.com/" - coinbaseproSandboxAPIURL = "https://api-public.sandbox.exchange.coinbase.com/" - coinbaseproAPIVersion = "0" - coinbaseproAccounts = "accounts" - coinbaseproHolds = "holds" - coinbaseproLedger = "ledger" - coinbaseproTransfers = "transfers" - coinbaseproAddressBook = "address-book" - coinbaseproCoinbaseAccounts = "coinbase-accounts" - coinbaseproAddress = "addresses" - coinbaseproConversions = "conversions" - coinbaseproCurrencies = "currencies" - coinbaseproDepositCoinbase = "deposits/coinbase-account" - coinbaseproPaymentMethodDeposit = "deposits/payment-method" - coinbaseproPaymentMethod = "payment-methods" - coinbaseproTravelRules = "travel-rules" - coinbaseproWithdrawalCoinbase = "withdrawals/coinbase-account" - coinbaseproWithdrawalCrypto = "withdrawals/crypto" - coinbaseproFeeEstimate = "withdrawals/fee-estimate" - coinbaseproWithdrawalPaymentMethod = "withdrawals/payment-method" - coinbaseproFees = "fees" - coinbaseproFills = "fills" - coinbaseproOrders = "orders" - coinbaseproOracle = "oracle" - coinbaseproProducts = "products" - coinbaseproOrderbook = "book" - coinbaseproHistory = "candles" - coinbaseproStats = "stats" - coinbaseproTicker = "ticker" - coinbaseproTrades = "trades" - coinbaseproProfiles = "profiles" - coinbaseproTransfer = "transfer" - coinbaseproDeactivate = "deactivate" - coinbaseproReports = "reports" - coinbaseproUsers = "users" - coinbaseproExchangeLimits = "exchange-limits" - coinbaseproSettlementPreferences = "settlement-preferences" - coinbaseproWrappedAssets = "wrapped-assets" - coinbaseproStakeWraps = "stake-wrap" - coinbaseproConversionRate = "conversion-rate" + coinbaseAPIURL = "https://api.coinbase.com" + coinbaseproSandboxAPIURL = "https://api-public.sandbox.exchange.coinbase.com/" + coinbaseproAPIVersion = "0" + coinbaseV3 = "/api/v3/brokerage/" + coinbaseAccounts = "accounts" + coinbaseBestBidAsk = "best_bid_ask" + coinbaseProductBook = "product_book" + coinbaseProducts = "products" + coinbaseOrders = "orders" + coinbaseBatchCancel = "batch_cancel" + coinbaseHistorical = "historical" + coinbaseBatch = "batch" + coinbaseEdit = "edit" + coinbaseEditPreview = "edit_preview" + coinbaseFills = "fills" + coinbaseCandles = "candles" + coinbaseTicker = "ticker" + coinbaseTransactionSummary = "transaction_summary" + coinbaseV2 = "/v2/" + coinbaseNotifications = "notifications" + coinbaseUser = "user" + coinbaseUsers = "users" + coinbaseAuth = "auth" + coinbaseAddresses = "addresses" + coinbaseTransactions = "transactions" + coinbaseDeposits = "deposits" + coinbaseCommit = "commit" + coinbasePaymentMethods = "payment-methods" + coinbaseWithdrawals = "withdrawals" + coinbaseCurrencies = "currencies" + coinbaseCrypto = "crypto" + coinbaseExchangeRates = "exchange-rates" + coinbasePrices = "prices" + coinbaseTime = "time" + + Version3 Version = true + Version2 Version = false + FiatDeposit FiatTransferType = false + FiatWithdrawal FiatTransferType = true + pageNone = "" + pageBefore = "before" + pageAfter = "after" + unknownContract = "UNKNOWN_CONTRACT_EXPIRY_TYPE" + granUnknown = "UNKNOWN_GRANULARITY" + granOneMin = "ONE_MINUTE" + granFiveMin = "FIVE_MINUTE" + granFifteenMin = "FIFTEEN_MINUTE" + granThirtyMin = "THIRTY_MINUTE" + granOneHour = "ONE_HOUR" + granTwoHour = "TWO_HOUR" + granSixHour = "SIX_HOUR" + granOneDay = "ONE_DAY" + startDateString = "start_date" + endDateString = "end_date" // coinbaseproTime = "time" // coinbaseproMarginTransfer = "profiles/margin-transfer" @@ -68,37 +82,945 @@ const ( // coinbaseproTrailingVolume = "users/self/trailing-volume" ) -const ( - pageNone = "" - pageBefore = "before" - pageAfter = "after" +var ( + errAccountIDEmpty = errors.New("account id cannot be empty") + errClientOrderIDEmpty = errors.New("client order id cannot be empty") + errProductIDEmpty = errors.New("product id cannot be empty") + errAmountZeroNonMarketBuy = errors.New("base amount cannot be 0 unless a market buy order") + errAmountZeroMarketBuy = errors.New("quote amount cannot be 0 for market buy orders") + errOrderIDEmpty = errors.New("order ids cannot be empty") + errOpenPairWithOtherTypes = errors.New("cannot pair open orders with other order types") + errUserIDEmpty = errors.New("user id cannot be empty") + errSizeAndPriceZero = errors.New("size and price cannot both be 0") + errCurrencyEmpty = errors.New("currency cannot be empty") + errCurrWalletConflict = errors.New("exactly one of walletID and currency must be specified") + errWalletIDEmpty = errors.New("wallet id cannot be empty") + errAddressIDEmpty = errors.New("address id cannot be empty") + errTransactionTypeEmpty = errors.New("transaction type cannot be empty") + errToEmpty = errors.New("to cannot be empty") + errAmountEmpty = errors.New("amount cannot be empty") + errTransactionIDEmpty = errors.New("transaction id cannot be empty") + errPaymentMethodEmpty = errors.New("payment method cannot be empty") + errDepositIDEmpty = errors.New("deposit id cannot be empty") + errInvalidPriceType = errors.New("price type must be spot, buy, or sell") + errInvalidOrderType = errors.New("order type must be market, limit, or stop") ) -// CoinbasePro is the overarching type across the coinbasepro package -type CoinbasePro struct { - exchange.Base +type Version bool +type FiatTransferType bool + +// CoinbasePro is the overarching type across the coinbasepro package +type CoinbasePro struct { + exchange.Base +} + +// 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) { + var resp AllAccountsResponse + + var params Params + params.urlVals = url.Values{} + params.urlVals.Set("limit", strconv.Itoa(int(limit))) + params.urlVals.Set("cursor", cursor) + + pathParams := common.EncodeURLValues("", params.urlVals) + + return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, + coinbaseV3+coinbaseAccounts, pathParams, nil, Version3, &resp, nil) +} + +// GetAccountByID returns information for a single account +func (c *CoinbasePro) GetAccountByID(ctx context.Context, accountID string) (*Account, error) { + if accountID == "" { + return nil, errAccountIDEmpty + } + path := fmt.Sprintf("%s%s/%s", coinbaseV3, coinbaseAccounts, accountID) + resp := OneAccountResponse{} + + return &resp.Account, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, + path, "", nil, Version3, &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]) + } + } + + pathParams := common.EncodeURLValues("", params.urlVals) + + var resp BestBidAsk + + return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, + coinbaseV3+coinbaseBestBidAsk, pathParams, nil, Version3, &resp, nil) +} + +// GetProductBook returns a list of bids/asks for a single product +func (c *CoinbasePro) GetProductBook(ctx context.Context, productID string, limit int32) (ProductBook, error) { + if productID == "" { + return ProductBook{}, errProductIDEmpty + } + 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, Version3, &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 string, productIDs []string) (AllProducts, error) { + var params Params + params.urlVals = url.Values{} + params.urlVals.Set("limit", strconv.Itoa(int(limit))) + params.urlVals.Set("offset", strconv.Itoa(int(offset))) + + if productType != "" { + params.urlVals.Set("product_type", productType) + } + + if contractExpiryType != "" { + params.urlVals.Set("contract_expiry_type", contractExpiryType) + } + + if len(productIDs) > 0 { + for x := range productIDs { + params.urlVals.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, Version3, &products, nil) +} + +// GetProductByID returns information on a single specified currency pair +func (c *CoinbasePro) GetProductByID(ctx context.Context, productID string) (*Product, error) { + if productID == "" { + return nil, errProductIDEmpty + } + + path := fmt.Sprintf("%s%s/%s", coinbaseV3, coinbaseProducts, productID) + + resp := Product{} + + return &resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, + path, "", nil, Version3, &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) { + var resp History + + if productID == "" { + return resp, 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", + 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) + + err := c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, + path, pathParams, nil, Version3, &resp, nil) + + return resp, err +} + +// GetTicker returns snapshot information about the last trades (ticks), best bid/ask, and +// 24h volume. +func (c *CoinbasePro) GetTicker(ctx context.Context, productID string, limit uint16) (*Ticker, error) { + 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)) + + pathParams := common.EncodeURLValues("", params.urlVals) + + resp := Ticker{} + + return &resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, + path, pathParams, nil, Version3, &resp, nil) +} + +// PlaceOrder places either a limit, market, or stop order +func (c *CoinbasePro) PlaceOrder(ctx context.Context, clientOID, productID, side, stopDirection, orderType string, baseAmount, quoteAmount, limitPrice, stopPrice float64, postOnly bool, endTime time.Time) (*PlaceOrderResp, error) { + var resp PlaceOrderResp + + if clientOID == "" { + return &resp, errClientOrderIDEmpty + } + if productID == "" { + return &resp, errProductIDEmpty + } + if baseAmount == 0 && orderType != order.Market.Lower() && side != order.Buy.String() { + return &resp, errAmountZeroNonMarketBuy + } + if quoteAmount == 0 && orderType == order.Market.Lower() && side == order.Buy.String() { + return &resp, errAmountZeroMarketBuy + } + + var orderConfig OrderConfiguration + + switch orderType { + case order.Market.Lower(): + if side == order.Buy.Lower() { + orderConfig.MarketMarketIOC = &MarketMarketIOC{} + orderConfig.MarketMarketIOC.QuoteSize = strconv.FormatFloat(quoteAmount, 'f', -1, 64) + } + if side == order.Sell.Lower() { + orderConfig.MarketMarketIOC = &MarketMarketIOC{} + orderConfig.MarketMarketIOC.BaseSize = strconv.FormatFloat(baseAmount, 'f', -1, 64) + } + case order.Limit.Lower(): + if endTime == (time.Time{}) { + orderConfig.LimitLimitGTC = &LimitLimitGTC{} + orderConfig.LimitLimitGTC.BaseSize = strconv.FormatFloat(baseAmount, 'f', -1, 64) + orderConfig.LimitLimitGTC.LimitPrice = strconv.FormatFloat(limitPrice, 'f', -1, 64) + orderConfig.LimitLimitGTC.PostOnly = postOnly + } else { + orderConfig.LimitLimitGTD = &LimitLimitGTD{} + orderConfig.LimitLimitGTD.BaseSize = strconv.FormatFloat(baseAmount, 'f', -1, 64) + orderConfig.LimitLimitGTD.LimitPrice = strconv.FormatFloat(limitPrice, 'f', -1, 64) + orderConfig.LimitLimitGTD.PostOnly = postOnly + orderConfig.LimitLimitGTD.EndTime = endTime + } + case order.Stop.Lower(): + if endTime == (time.Time{}) { + orderConfig.StopLimitStopLimitGTC = &StopLimitStopLimitGTC{} + orderConfig.StopLimitStopLimitGTC.BaseSize = strconv.FormatFloat(baseAmount, 'f', -1, 64) + orderConfig.StopLimitStopLimitGTC.LimitPrice = strconv.FormatFloat(limitPrice, 'f', -1, + 64) + orderConfig.StopLimitStopLimitGTC.StopPrice = strconv.FormatFloat(stopPrice, 'f', -1, 64) + orderConfig.StopLimitStopLimitGTC.StopDirection = stopDirection + } else { + orderConfig.StopLimitStopLimitGTD = &StopLimitStopLimitGTD{} + orderConfig.StopLimitStopLimitGTD.BaseSize = strconv.FormatFloat(baseAmount, 'f', -1, 64) + orderConfig.StopLimitStopLimitGTD.LimitPrice = strconv.FormatFloat(limitPrice, 'f', -1, + 64) + orderConfig.StopLimitStopLimitGTD.StopPrice = strconv.FormatFloat(stopPrice, 'f', -1, 64) + orderConfig.StopLimitStopLimitGTD.StopDirection = stopDirection + orderConfig.StopLimitStopLimitGTD.EndTime = endTime + } + default: + return &resp, errInvalidOrderType + } + + req := map[string]interface{}{"client_order_id": clientOID, "product_id": productID, + "side": side, "order_configuration": orderConfig} + + return &resp, + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, + coinbaseV3+coinbaseOrders, "", req, Version3, &resp, nil) +} + +// CancelOrders cancels orders by orderID +func (c *CoinbasePro) CancelOrders(ctx context.Context, orderIDs []string) (CancelOrderResp, error) { + var resp CancelOrderResp + if len(orderIDs) == 0 { + return resp, errOrderIDEmpty + } + + path := fmt.Sprintf("%s%s/%s", coinbaseV3, coinbaseOrders, coinbaseBatchCancel) + + req := map[string]interface{}{"order_ids": orderIDs} + + return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, path, "", + nil, Version3, req, 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) (bool, error) { + if orderID == "" { + return false, errOrderIDEmpty + } + if size == 0 && price == 0 { + 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)} + + var resp bool + + return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, path, "", + req, Version3, &resp, nil) +} + +// EditOrderPreview simulates an edit order request, to preview the result. Only limit orders with a +// good-till-cancelled time in force can be edited. +func (c *CoinbasePro) EditOrderPreview(ctx context.Context, orderID string, size, price float64) (*EditOrderPreviewResp, error) { + if orderID == "" { + return nil, errOrderIDEmpty + } + 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)} + + var resp *EditOrderPreviewResp + + return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, path, "", + req, Version3, &resp, nil) +} + +// GetAllOrders lists orders, filtered by their status +func (c *CoinbasePro) GetAllOrders(ctx context.Context, productID, userNativeCurrency, orderType, orderSide, cursor, productType, orderPlacementSource, contractExpiryType string, orderStatus []string, limit int32, startDate, endDate time.Time) (GetAllOrdersResp, error) { + var resp GetAllOrdersResp + + var params Params + params.urlVals = make(url.Values) + err := params.PrepareDateString(startDate, endDate, startDateString, endDateString) + if err != nil { + return resp, 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]) + } + } + + 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) + } + if orderPlacementSource != "" { + params.urlVals.Set("order_placement_source", orderPlacementSource) + } + if productType != "" { + params.urlVals.Set("product_type", productType) + } + if orderSide != "" { + params.urlVals.Set("order_side", orderSide) + } + if contractExpiryType != "" { + params.urlVals.Set("contract_expiry_type", contractExpiryType) + } + if orderType != "" { + params.urlVals.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, Version3, &resp, nil) +} + +// GetFills returns information of recent fills on the specified profile +func (c *CoinbasePro) GetFills(ctx context.Context, orderID, productID, cursor string, limit int64, startDate, endDate time.Time) (FillResponse, error) { + var resp FillResponse + var params Params + params.urlVals = url.Values{} + err := params.PrepareDateString(startDate, endDate, "start_sequence_timestamp", + "end_sequence_timestamp") + if err != nil { + return resp, err + } + + if orderID != "" { + params.urlVals.Set("order_id", orderID) + } + if productID != "" { + params.urlVals.Set("product_id", productID) + } + + params.urlVals.Set("limit", strconv.FormatInt(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, Version3, &resp, nil) +} + +// GetOrderByID returns a single order by order id. +func (c *CoinbasePro) GetOrderByID(ctx context.Context, orderID, userNativeCurrency, clientID string) (*GetOrderResponse, error) { + resp := GetOrderResponse{} + if orderID == "" { + return &resp, errOrderIDEmpty + } + var params Params + params.urlVals = url.Values{} + params.urlVals.Set("client_order_id", clientID) + 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, Version3, &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 resp TransactionSummary + var params Params + params.urlVals = url.Values{} + + err := params.PrepareDateString(startDate, endDate, startDateString, endDateString) + if err != nil { + return resp, err + } + + if contractExpiryType != "" { + params.urlVals.Set("contract_expiry_type", contractExpiryType) + } + if productType != "" { + params.urlVals.Set("product_type", productType) + } + + params.urlVals.Set("user_native_currency", userNativeCurrency) + + pathParams := common.EncodeURLValues("", params.urlVals) + + return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, + coinbaseV3+coinbaseTransactionSummary, pathParams, nil, Version3, &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 + + var params Params + params.urlVals = url.Values{} + params.PreparePagination(pag) + + pathParams := common.EncodeURLValues("", params.urlVals) + + return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, + coinbaseV2+coinbaseNotifications, pathParams, nil, Version2, &resp, nil) +} + +func (c *CoinbasePro) GetUserByID(ctx context.Context, userID string) (*UserResponse, error) { + if userID == "" { + return nil, errUserIDEmpty + } + + path := fmt.Sprintf("%s%s/%s", coinbaseV2, coinbaseUsers, userID) + + var resp *UserResponse + + return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, + path, "", nil, Version2, &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, Version2, &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) + + return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, + path, "", nil, Version2, &resp, nil) +} + +// UpdateUser modifies certain user preferences +func (c *CoinbasePro) UpdateUser(ctx context.Context, name, timeZone, nativeCurrency string) (*UserResponse, error) { + var resp *UserResponse + + req := map[string]interface{}{} + + if name != "" { + req["name"] = name + } + if timeZone != "" { + req["time_zone"] = timeZone + } + if nativeCurrency != "" { + req["native_currency"] = nativeCurrency + } + + return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPut, + coinbaseV2+coinbaseUser, "", req, Version2, &resp, nil) +} + +// CreateWallet creates a new wallet for the specified currency +func (c *CoinbasePro) CreateWallet(ctx context.Context, currency string) (*GenWalletResponse, error) { + if currency == "" { + return nil, errCurrencyEmpty + } + + path := fmt.Sprintf("%s%s/%s", coinbaseV2, coinbaseAccounts, currency) + + var resp *GenWalletResponse + + return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, + path, "", nil, Version2, &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 + + var params Params + params.urlVals = url.Values{} + params.PreparePagination(pag) + + pathParams := common.EncodeURLValues("", params.urlVals) + + return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, + coinbaseV2+coinbaseAccounts, pathParams, nil, Version2, &resp, nil) +} + +// GetWalletByID returns information about a single wallet. In lieu of a wallet ID, +// a currency can be provided to get the primary account for that currency +func (c *CoinbasePro) GetWalletByID(ctx context.Context, walletID, currency string) (*GenWalletResponse, error) { + if (walletID == "" && currency == "") || (walletID != "" && currency != "") { + return nil, errCurrWalletConflict + } + + var path string + + if walletID != "" { + path = fmt.Sprintf("%s%s/%s", coinbaseV2, coinbaseAccounts, walletID) + } + if currency != "" { + path = fmt.Sprintf("%s%s/%s", coinbaseV2, coinbaseAccounts, currency) + } + + var resp *GenWalletResponse + + return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, + path, "", nil, Version2, &resp, nil) +} + +// UpdateWalletName updates the name of a wallet +func (c *CoinbasePro) UpdateWalletName(ctx context.Context, walletID, newName string) (*GenWalletResponse, error) { + if walletID == "" { + return nil, errWalletIDEmpty + } + + path := fmt.Sprintf("%s%s/%s", coinbaseV2, coinbaseAccounts, walletID) + + req := map[string]interface{}{"name": newName} + + var resp *GenWalletResponse + + return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPut, + path, "", req, Version2, &resp, nil) +} + +// DeleteWallet deletes a wallet +func (c *CoinbasePro) DeleteWallet(ctx context.Context, walletID string) error { + if walletID == "" { + return errWalletIDEmpty + } + + path := fmt.Sprintf("%s%s/%s", coinbaseV2, coinbaseAccounts, walletID) + + return c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodDelete, path, "", nil, + Version2, nil, nil) +} + +// CreateAddress generates a crypto address for depositing to the specified wallet +func (c *CoinbasePro) CreateAddress(ctx context.Context, walletID, name string) (*GenAddrResponse, error) { + if walletID == "" { + return nil, errWalletIDEmpty + } + + path := fmt.Sprintf("%s%s/%s/%s", coinbaseV2, coinbaseAccounts, walletID, coinbaseAddresses) + + req := map[string]interface{}{"name": name} + + var resp *GenAddrResponse + + return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, + path, "", req, Version2, &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 + + if walletID == "" { + return resp, errWalletIDEmpty + } + + path := fmt.Sprintf("%s%s/%s/%s", coinbaseV2, coinbaseAccounts, walletID, coinbaseAddresses) + + var params Params + params.urlVals = url.Values{} + params.PreparePagination(pag) + + pathParams := common.EncodeURLValues("", params.urlVals) + + return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, + path, pathParams, nil, Version2, &resp, nil) +} + +// GetAddressByID returns information on a single address associated with the specified wallet +func (c *CoinbasePro) GetAddressByID(ctx context.Context, walletID, addressID string) (*GenAddrResponse, error) { + if walletID == "" { + return nil, errWalletIDEmpty + } + if addressID == "" { + return nil, errAddressIDEmpty + } + + path := fmt.Sprintf("%s%s/%s/%s/%s", coinbaseV2, coinbaseAccounts, walletID, coinbaseAddresses, + addressID) + + var resp *GenAddrResponse + + return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, + path, "", nil, Version2, &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 + + if walletID == "" { + return resp, errWalletIDEmpty + } + if addressID == "" { + return resp, errAddressIDEmpty + } + + path := fmt.Sprintf("%s%s/%s/%s/%s/%s", coinbaseV2, coinbaseAccounts, walletID, + coinbaseAddresses, addressID, coinbaseTransactions) + + var params Params + params.urlVals = url.Values{} + params.PreparePagination(pag) + + pathParams := common.EncodeURLValues("", params.urlVals) + + return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, + path, pathParams, nil, Version2, &resp, nil) +} + +// SendMoney ca send funds to an email or cryptocurrency address (if "traType" is set to "send"), +// or to another one of the user's wallets or vaults (if "traType" is set to "transfer"). Coinbase +// may delay or cancel the transaction at their discretion. The "idem" parameter is an optional +// string for idempotency; a token with a max length of 100 characters, if a previous +// transaction included the same string as a parameter, the new transaction won't be processed, +// and information on the previous transaction will be returned +func (c *CoinbasePro) SendMoney(ctx context.Context, traType, walletID, to, amount, currency, description, idem, financialInstitutionWebsite, destinationTag string, skipNotifications, toFinancialInstitution bool) (*GenTransactionResp, error) { + if traType == "" { + return nil, errTransactionTypeEmpty + } + if walletID == "" { + return nil, errWalletIDEmpty + } + if to == "" { + return nil, errToEmpty + } + if amount == "" { + return nil, errAmountEmpty + } + 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": amount, "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, Version2, &resp, nil) +} + +// ListTransactions returns a list of transactions associated with the specified wallet +func (c *CoinbasePro) ListTransactions(ctx context.Context, walletID string, pag PaginationInp) (ManyTransactionsResp, error) { + var resp ManyTransactionsResp + + if walletID == "" { + return resp, errWalletIDEmpty + } + + path := fmt.Sprintf("%s%s/%s/%s", coinbaseV2, coinbaseAccounts, walletID, coinbaseTransactions) + + var params Params + params.urlVals = url.Values{} + params.PreparePagination(pag) + + pathParams := common.EncodeURLValues("", params.urlVals) + + return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, + path, pathParams, nil, Version2, &resp, nil) +} + +// GetTransactionByID returns information on a single transaction associated with the +// specified wallet +func (c *CoinbasePro) GetTransactionByID(ctx context.Context, walletID, transactionID string) (*GenTransactionResp, error) { + if walletID == "" { + return nil, errWalletIDEmpty + } + if transactionID == "" { + return nil, errTransactionIDEmpty + } + + path := fmt.Sprintf("%s%s/%s/%s/%s", coinbaseV2, coinbaseAccounts, walletID, + coinbaseTransactions, transactionID) + + var resp *GenTransactionResp + + return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, + path, "", nil, Version2, &resp, nil) +} + +// FiatTransfer prepares and optionally processes a transfer of funds between the exchange and a +// fiat payment method. "Deposit" signifies funds going from exchange to bank, "withdraw" +// signifies funds going from bank to exchange +func (c *CoinbasePro) FiatTransfer(ctx context.Context, walletID, currency, paymentMethod string, amount float64, commit bool, transferType FiatTransferType) (*GenDeposWithdrResp, error) { + if walletID == "" { + return nil, errWalletIDEmpty + } + if amount == 0 { + return nil, errAmountEmpty + } + if currency == "" { + return nil, errCurrencyEmpty + } + if paymentMethod == "" { + return nil, errPaymentMethodEmpty + } + + var path string + switch transferType { + case FiatDeposit: + path = fmt.Sprintf("%s%s/%s/%s", coinbaseV2, coinbaseAccounts, walletID, coinbaseDeposits) + case FiatWithdrawal: + path = fmt.Sprintf("%s%s/%s/%s", coinbaseV2, coinbaseAccounts, walletID, coinbaseWithdrawals) + } + + 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, Version2, &resp, nil) +} + +// CommitTransfer processes a deposit/withdrawal that was created with the "commit" parameter set +// to false +func (c *CoinbasePro) CommitTransfer(ctx context.Context, walletID, depositID string, transferType FiatTransferType) (*GenDeposWithdrResp, error) { + if walletID == "" { + return nil, errWalletIDEmpty + } + 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) + case FiatWithdrawal: + path = fmt.Sprintf("%s%s/%s/%s/%s/%s", coinbaseV2, coinbaseAccounts, walletID, + coinbaseWithdrawals, depositID, coinbaseCommit) + } + + var resp *GenDeposWithdrResp + + return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, + path, "", nil, Version2, &resp, nil) } -// GetAllAccounts returns information on all trading accounts associated with the API key -func (c *CoinbasePro) GetAllAccounts(ctx context.Context) ([]AccountResponse, error) { - var resp []AccountResponse +// 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 - return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, coinbaseproAccounts, nil, &resp, nil) + if walletID == "" { + return resp, errWalletIDEmpty + } + + var path string + switch transferType { + case FiatDeposit: + path = fmt.Sprintf("%s%s/%s/%s", coinbaseV2, coinbaseAccounts, walletID, coinbaseDeposits) + case FiatWithdrawal: + path = fmt.Sprintf("%s%s/%s/%s", coinbaseV2, coinbaseAccounts, walletID, coinbaseWithdrawals) + } + + var params Params + params.urlVals = url.Values{} + params.PreparePagination(pag) + + pathParams := common.EncodeURLValues("", params.urlVals) + + return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, + path, pathParams, nil, Version2, &resp, nil) } -// GetAccountByID returns information for a single account -func (c *CoinbasePro) GetAccountByID(ctx context.Context, accountID string) (*AccountResponse, error) { - path := fmt.Sprintf("%s/%s", coinbaseproAccounts, accountID) - resp := AccountResponse{} +// GetFiatTransferByID returns information on a single deposit associated with the specified wallet +func (c *CoinbasePro) GetFiatTransferByID(ctx context.Context, walletID, depositID string, transferType FiatTransferType) (*GenDeposWithdrResp, error) { + if walletID == "" { + return nil, errWalletIDEmpty + } + if depositID == "" { + return nil, errDepositIDEmpty + } - return &resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp, nil) + path := fmt.Sprintf("%s%s/%s/%s/%s", coinbaseV2, coinbaseAccounts, walletID, + coinbaseDeposits, depositID) + + var resp *GenDeposWithdrResp + + return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, + path, "", nil, Version2, &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) + + var params Params + params.urlVals = url.Values{} + params.PreparePagination(pag) + + pathParams := common.EncodeURLValues("", params.urlVals) + + return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, + path, pathParams, nil, Version2, &resp, nil) +} + +// GetPaymentMethodByID returns information on a single payment method associated with the user's +// account +func (c *CoinbasePro) GetPaymentMethodByID(ctx context.Context, paymentMethodID string) (*GenPaymentMethodResp, error) { + if paymentMethodID == "" { + return nil, errPaymentMethodEmpty + } + + path := fmt.Sprintf("%s%s/%s", coinbaseV2, coinbasePaymentMethods, paymentMethodID) + + var resp *GenPaymentMethodResp + + return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, + path, "", nil, Version2, &resp, nil) +} + +// GetFiatCurrencies lists currencies that Coinbase knows about +func (c *CoinbasePro) GetFiatCurrencies(ctx context.Context) (GetFiatCurrenciesResp, error) { + var resp GetFiatCurrenciesResp + + return resp, c.SendHTTPRequest(ctx, exchange.RestSpot, coinbaseV2+coinbaseCurrencies, &resp) +} + +// GetCryptocurrencies lists cryptocurrencies that Coinbase knows about +func (c *CoinbasePro) GetCryptocurrencies(ctx context.Context) (GetCryptocurrenciesResp, error) { + var resp GetCryptocurrenciesResp + + path := fmt.Sprintf("%s%s/%s", coinbaseV2, coinbaseCurrencies, coinbaseCrypto) + + return resp, 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) + + 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 + + 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) + default: + return resp, errInvalidPriceType + } + + return resp, c.SendHTTPRequest(ctx, exchange.RestSpot, path, &resp) +} + +// GetCurrentServerTime returns the API server time +func (c *CoinbasePro) GetCurrentServerTime(ctx context.Context) (ServerTime, error) { + resp := ServerTime{} + return resp, c.SendHTTPRequest(ctx, exchange.RestSpot, coinbaseV2+coinbaseTime, &resp) } +/* + // GetHolds returns information on the holds of an account func (c *CoinbasePro) GetHolds(ctx context.Context, accountID, direction, step string, limit int64) ([]AccountHolds, ReturnedPaginationHeaders, error) { - path := fmt.Sprintf("%s/%s/%s", coinbaseproAccounts, accountID, coinbaseproHolds) + path := fmt.Sprintf("%s/%s/%s", coinbaseAccounts, accountID, coinbaseproHolds) var params Params params.urlVals = url.Values{} @@ -112,7 +1034,7 @@ func (c *CoinbasePro) GetHolds(ctx context.Context, accountID, direction, step s var resp []AccountHolds retH := http.Header{} - err := c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp, &retH) + err := c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, "", nil, Version3, &resp, &retH) rph := ReturnedPaginationHeaders{before: retH.Get("CB-BEFORE"), after: retH.Get("CB-AFTER")} @@ -125,12 +1047,12 @@ func (c *CoinbasePro) GetAccountLedger(ctx context.Context, accountID, direction params.urlVals = url.Values{} var rph ReturnedPaginationHeaders - err := params.PrepareDateString(startDate, endDate) + err := params.PrepareDateString(startDate, endDate, startDateString, endDateString) if err != nil { return nil, rph, err } - path := fmt.Sprintf("%s/%s/%s", coinbaseproAccounts, accountID, coinbaseproLedger) + path := fmt.Sprintf("%s/%s/%s", coinbaseAccounts, accountID, coinbaseproLedger) params.PrepareDSL(direction, step, limit) @@ -143,7 +1065,7 @@ func (c *CoinbasePro) GetAccountLedger(ctx context.Context, accountID, direction var resp []AccountLedgerResponse retH := http.Header{} - err = c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp, &retH) + err = c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, "", nil, Version3, &resp, &retH) rph = ReturnedPaginationHeaders{before: retH.Get("CB-BEFORE"), after: retH.Get("CB-AFTER")} @@ -153,7 +1075,7 @@ func (c *CoinbasePro) GetAccountLedger(ctx context.Context, accountID, direction // GetAccountTransfers returns a history of withdrawal and or deposit // transactions for a single account func (c *CoinbasePro) GetAccountTransfers(ctx context.Context, accountID, direction, step, transferType string, limit int64) ([]TransferResponse, ReturnedPaginationHeaders, error) { - path := fmt.Sprintf("%s/%s/%s", coinbaseproAccounts, accountID, coinbaseproTransfers) + path := fmt.Sprintf("%s/%s/%s", coinbaseAccounts, accountID, coinbaseproTransfers) var params Params params.urlVals = url.Values{} @@ -166,7 +1088,7 @@ func (c *CoinbasePro) GetAccountTransfers(ctx context.Context, accountID, direct var resp []TransferResponse retH := http.Header{} - err := c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp, &retH) + err := c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, "", nil, Version3, &resp, &retH) rph := ReturnedPaginationHeaders{before: retH.Get("CB-BEFORE"), after: retH.Get("CB-AFTER")} @@ -178,7 +1100,7 @@ func (c *CoinbasePro) GetAddressBook(ctx context.Context) ([]GetAddressResponse, var resp []GetAddressResponse return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, coinbaseproAddressBook, nil, &resp, nil) + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, coinbaseproAddressBook, "", nil, Version3, &resp, nil) } // AddAddresses adds new addresses to the address book @@ -192,14 +1114,14 @@ func (c *CoinbasePro) AddAddresses(ctx context.Context, req []AddAddressRequest) var resp []AddAddressResponse return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproAddressBook, params, &resp, nil) + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproAddressBook, "", params, Version3, &resp, nil) } // DeleteAddress deletes an address from the address book func (c *CoinbasePro) DeleteAddress(ctx context.Context, addressID string) error { path := fmt.Sprintf("%s/%s", coinbaseproAddressBook, addressID) - return c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodDelete, path, nil, nil, nil) + return c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodDelete, path, "", nil, Version3, nil, nil) } // GetCoinbaseWallets returns all of the user's available Coinbase wallets @@ -207,22 +1129,7 @@ func (c *CoinbasePro) GetCoinbaseWallets(ctx context.Context) ([]CoinbaseAccount var resp []CoinbaseAccounts return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, coinbaseproCoinbaseAccounts, nil, &resp, nil) -} - -// GenerateCryptoAddress generates a one-time address for deposting crypto -func (c *CoinbasePro) GenerateCryptoAddress(ctx context.Context, accountID, profileID, network string) (*CryptoAddressResponse, error) { - path := fmt.Sprintf("%s/%s/%s", coinbaseproCoinbaseAccounts, accountID, coinbaseproAddress) - - // In this case, accountID has to come from GetCoinbaseWallets, not GetAccounts - - params := map[string]interface{}{"account_id": accountID, "profile_id": profileID, - "network": network} - - resp := CryptoAddressResponse{} - - return &resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, path, params, &resp, nil) + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, coinbaseproCoinbaseAccounts, "", nil, Version3, &resp, nil) } // ConvertCurrency converts between two currencies in the specified profile @@ -233,7 +1140,7 @@ func (c *CoinbasePro) ConvertCurrency(ctx context.Context, profileID, from, to, resp := ConvertResponse{} return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproConversions, params, &resp, nil) + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproConversions, "", params, Version3, &resp, nil) } // GetConversionByID returns the details of a past conversion, given its ID @@ -247,7 +1154,7 @@ func (c *CoinbasePro) GetConversionByID(ctx context.Context, conversionID, profi resp := ConvertResponse{} return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp, nil) + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, "", nil, Version3, &resp, nil) } // GetAllCurrencies returns a list of currencies known by the exchange @@ -278,7 +1185,7 @@ func (c *CoinbasePro) DepositViaCoinbase(ctx context.Context, profileID, currenc resp := DepositWithdrawalInfo{} return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproDepositCoinbase, params, &resp, nil) + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproDepositCoinbase, "", params, Version3, &resp, nil) } // DepositViaPaymentMethod deposits funds from a payment method. SEPA is not allowed @@ -290,7 +1197,7 @@ func (c *CoinbasePro) DepositViaPaymentMethod(ctx context.Context, profileID, pa resp := DepositWithdrawalInfo{} return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproPaymentMethodDeposit, params, &resp, nil) + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproPaymentMethodDeposit, "", params, Version3, &resp, nil) } // GetPayMethods returns a full list of payment methods @@ -298,7 +1205,7 @@ func (c *CoinbasePro) GetPayMethods(ctx context.Context) ([]PaymentMethod, error var resp []PaymentMethod return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, coinbaseproPaymentMethod, nil, &resp, nil) + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, coinbaseproPaymentMethod, "", nil, Version3, &resp, nil) } // GetAllTransfers returns all in-progress and completed transfers in and out of any @@ -314,7 +1221,7 @@ func (c *CoinbasePro) GetAllTransfers(ctx context.Context, profileID, direction, resp := []TransferResponse{} retH := http.Header{} - err := c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp, &retH) + err := c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, "", nil, Version3, &resp, &retH) rph := ReturnedPaginationHeaders{before: retH.Get("CB-BEFORE"), after: retH.Get("CB-AFTER")} @@ -327,7 +1234,7 @@ func (c *CoinbasePro) GetTransferByID(ctx context.Context, transferID string) (* resp := TransferResponse{} return &resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp, nil) + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, "", nil, Version3, &resp, nil) } // SendTravelInfoForTransfer sends travel rule information for a transfer @@ -340,7 +1247,7 @@ func (c *CoinbasePro) SendTravelInfoForTransfer(ctx context.Context, transferID, var resp string return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, path, params, &resp, nil) + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, path, "", params, Version3, &resp, nil) } // WithdrawViaCoinbase withdraws funds to a coinbase account. @@ -352,7 +1259,7 @@ func (c *CoinbasePro) WithdrawViaCoinbase(ctx context.Context, profileID, accoun resp := DepositWithdrawalInfo{} return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproWithdrawalCoinbase, req, &resp, nil) + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproWithdrawalCoinbase, "", req, Version3, &resp, nil) } // WithdrawCrypto withdraws funds to a crypto address @@ -367,7 +1274,7 @@ func (c *CoinbasePro) WithdrawCrypto(ctx context.Context, profileID, currency, c resp := DepositWithdrawalInfo{} return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproWithdrawalCrypto, req, &resp, nil) + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproWithdrawalCrypto, "", req, Version3, &resp, nil) } // GetWithdrawalFeeEstimate has Coinbase estimate the fee for withdrawing in a certain @@ -388,7 +1295,7 @@ func (c *CoinbasePro) GetWithdrawalFeeEstimate(ctx context.Context, currency, cr path := common.EncodeURLValues(coinbaseproFeeEstimate, params.urlVals) return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp, nil) + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, "", nil, Version3, &resp, nil) } // WithdrawViaPaymentMethod withdraws funds to a payment method @@ -400,7 +1307,7 @@ func (c *CoinbasePro) WithdrawViaPaymentMethod(ctx context.Context, profileID, p resp := DepositWithdrawalInfo{} return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproWithdrawalPaymentMethod, req, &resp, nil) + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproWithdrawalPaymentMethod, "", req, Version3, &resp, nil) } // GetFees returns your current maker & taker fee rates, as well as your 30-day @@ -409,173 +1316,7 @@ func (c *CoinbasePro) GetFees(ctx context.Context) (FeeResponse, error) { resp := FeeResponse{} return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, coinbaseproFees, nil, &resp, nil) -} - -// GetFills returns information of recent fills on the specified profile -func (c *CoinbasePro) GetFills(ctx context.Context, orderID, currencyPair, direction, step, marketType string, limit int64, startDate, endDate time.Time) ([]FillResponse, ReturnedPaginationHeaders, error) { - var rph ReturnedPaginationHeaders - - if orderID == "" && currencyPair == "" { - return nil, rph, errors.New("requires either order id or product id") - } - var params Params - params.urlVals = url.Values{} - err := params.PrepareDateString(startDate, endDate) - if err != nil { - return nil, rph, err - } - - if orderID != "" { - params.urlVals.Set("order_id", orderID) - } - if currencyPair != "" { - params.urlVals.Set("product_id", currencyPair) - } - - params.PrepareDSL(direction, step, limit) - params.urlVals.Set("market_type", marketType) - - path := common.EncodeURLValues(coinbaseproFills, params.urlVals) - - var resp []FillResponse - retH := http.Header{} - - err = c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp, &retH) - - rph = ReturnedPaginationHeaders{before: retH.Get("CB-BEFORE"), after: retH.Get("CB-AFTER")} - - return resp, rph, err -} - -// GetAllOrders lists orders, filtered by their status -func (c *CoinbasePro) GetAllOrders(ctx context.Context, profileID, currencyPair, sortedBy, sorting, direction, step, marketType string, startDate, endDate time.Time, limit int64, status []string) ([]GeneralizedOrderResponse, ReturnedPaginationHeaders, error) { - var rph ReturnedPaginationHeaders - if limit < 1 { - return nil, rph, errors.New("limit must be greater than 0") - } - var params Params - params.urlVals = make(url.Values) - err := params.PrepareDateString(startDate, endDate) - if err != nil { - return nil, rph, err - } - - params.PrepareProfIDAndProdID(profileID, currencyPair) - params.urlVals.Set("sorted_by", sortedBy) - params.urlVals.Set("sorting", sorting) - params.PrepareDSL(direction, step, limit) - - for _, individualStatus := range status { - params.urlVals.Add("status", individualStatus) - } - - params.urlVals.Set("market_type", marketType) - - path := common.EncodeURLValues(coinbaseproOrders, params.urlVals) - - var resp []GeneralizedOrderResponse - retH := http.Header{} - - err = c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp, &retH) - - rph = ReturnedPaginationHeaders{before: retH.Get("CB-BEFORE"), after: retH.Get("CB-AFTER")} - - return resp, rph, err -} - -// CancelAllExistingOrders attempts to cancel all open orders. The exchange warns that -// this may need to be called multiple times to properly close all of them -func (c *CoinbasePro) CancelAllExistingOrders(ctx context.Context, profileID, currencyPair string) ([]string, error) { - var params Params - params.urlVals = url.Values{} - - params.PrepareProfIDAndProdID(profileID, currencyPair) - - path := common.EncodeURLValues(coinbaseproOrders, params.urlVals) - - var resp []string - - return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodDelete, path, nil, &resp, nil) -} - -// PlaceOrder places either a limit, market, or stop order -func (c *CoinbasePro) PlaceOrder(ctx context.Context, profileID, orderType, side, currencyPair, stp, stop, timeInForce, cancelAfter, clientOID string, stopPrice, price, size, funds float64, postOnly bool) (*GeneralizedOrderResponse, error) { - var resp GeneralizedOrderResponse - - if (orderType == order.Limit.Lower() || orderType == "" || orderType == "stop") && - (price == 0 || size == 0) { - return &resp, errors.New("price and size must be greater than 0 for limit or stop orders") - } - if orderType == order.Market.Lower() && (size == 0 && funds == 0) { - return &resp, errors.New("size or funds must be greater than 0 for market orders") - } - if side != order.Buy.Lower() && side != order.Sell.Lower() { - return &resp, errors.New("side must be buy or sell") - } - - req := map[string]interface{}{"profile_id": profileID, "type": orderType, "side": side, - "product_id": currencyPair, "stp": stp, "stop": stop, - "size": strconv.FormatFloat(size, 'f', -1, 64), - "time_in_force": timeInForce, "cancel_after": cancelAfter, "post_only": postOnly, - "client_oid": clientOID} - - if stopPrice != 0 { - req["stop_price"] = strconv.FormatFloat(stopPrice, 'f', -1, 64) - } - if funds != 0 { - req["funds"] = strconv.FormatFloat(funds, 'f', -1, 64) - } - if price != 0 { - req["price"] = strconv.FormatFloat(price, 'f', -1, 64) - } - - return &resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproOrders, req, &resp, nil) -} - -// GetOrderByID returns a single order by order id. -func (c *CoinbasePro) GetOrderByID(ctx context.Context, orderID, marketType string, clientID bool) (*GeneralizedOrderResponse, error) { - resp := GeneralizedOrderResponse{} - if orderID == "" { - return &resp, errors.New("order id cannot be empty") - } - if clientID { - orderID = fmt.Sprintf("client:%s", orderID) - } - var param Params - param.urlVals = url.Values{} - - // Spot's seemingly the only supported market type; passing in anything else here will - // cause the request to time out after a minute - if marketType == "spot" { - param.urlVals.Set("market_type", marketType) - } - - path := fmt.Sprintf("%s/%s", coinbaseproOrders, orderID) - path = common.EncodeURLValues(path, param.urlVals) - - return &resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp, nil) -} - -// CancelExistingOrder cancels order by orderID -func (c *CoinbasePro) CancelExistingOrder(ctx context.Context, orderID, profileID, productID string, clientID bool) (string, error) { - var resp string - if orderID == "" { - return resp, errors.New("order id cannot be empty") - } - if clientID { - orderID = fmt.Sprintf("client:%s", orderID) - } - path := fmt.Sprintf("%s/%s", coinbaseproOrders, orderID) - var param Params - param.urlVals = url.Values{} - - param.PrepareProfIDAndProdID(profileID, productID) - - path = common.EncodeURLValues(path, param.urlVals) - - return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodDelete, path, nil, nil, nil) + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, coinbaseproFees, "", nil, Version3, &resp, nil) } // GetSignedPrices returns some cryptographically signed prices ready to be @@ -584,33 +1325,7 @@ func (c *CoinbasePro) GetSignedPrices(ctx context.Context) (SignedPrices, error) resp := SignedPrices{} return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, coinbaseproOracle, nil, &resp, nil) -} - -// GetAllProducts returns information on all currency pairs that are available for trading -func (c *CoinbasePro) GetAllProducts(ctx context.Context, productType string) ([]Product, error) { - var params Params - params.urlVals = url.Values{} - params.urlVals.Set("type", productType) - - path := common.EncodeURLValues(coinbaseproProducts, params.urlVals) - - var products []Product - - return products, c.SendHTTPRequest(ctx, exchange.RestSpot, path, &products) -} - -// GetProductByID returns information on a single specified currency pair -func (c *CoinbasePro) GetProductByID(ctx context.Context, productID string) (*Product, error) { - if productID == "" { - return nil, errors.New("product id cannot be empty") - } - - path := fmt.Sprintf("%s/%s", coinbaseproProducts, productID) - - resp := Product{} - - return &resp, c.SendHTTPRequest(ctx, exchange.RestSpot, path, &resp) + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, coinbaseproOracle, "", nil, Version3, &resp, nil) } // GetOrderbook returns orderbook by currency pair and level @@ -619,7 +1334,7 @@ func (c *CoinbasePro) GetOrderbook(ctx context.Context, symbol string, level int return nil, errors.New("symbol cannot be empty") } - path := fmt.Sprintf("%s/%s/%s", coinbaseproProducts, symbol, coinbaseproOrderbook) + path := fmt.Sprintf("%s/%s/%s", coinbaseProducts, symbol, coinbaseproOrderbook) if level > 0 { var params Params params.urlVals = url.Values{} @@ -653,57 +1368,6 @@ func (c *CoinbasePro) GetOrderbook(ctx context.Context, symbol string, level int return &obF, nil } -// GetHistoricRates returns historic rates for a product. Rates are returned in -// grouped buckets based on requested granularity. Contrary to the documentation, -// requests that return more than 300 data points aren't rejected; just truncated -// to the 300 most recent data points. -func (c *CoinbasePro) GetHistoricRates(ctx context.Context, currencyPair string, granularity int64, startDate, endDate time.Time) ([]History, error) { - if currencyPair == "" { - return nil, errors.New("currency pair cannot be empty") - } - - var params Params - params.urlVals = url.Values{} - - err := params.PrepareDateString(startDate, endDate) - if err != nil { - return nil, err - } - - allowedGranularities := [7]int64{0, 60, 300, 900, 3600, 21600, 86400} - validGran, _ := common.InArray(granularity, allowedGranularities) - if !validGran { - return nil, fmt.Errorf("invalid granularity %v, allowed granularities are: %+v", - granularity, allowedGranularities) - } - if granularity > 0 { - params.urlVals.Set("granularity", strconv.FormatInt(granularity, 10)) - } - - var resp [][6]float64 - - path := common.EncodeURLValues( - fmt.Sprintf("%s/%s/%s", coinbaseproProducts, currencyPair, coinbaseproHistory), - params.urlVals) - if err := c.SendHTTPRequest(ctx, exchange.RestSpot, path, &resp); err != nil { - return nil, err - } - - history := make([]History, len(resp)) - for x := range resp { - history[x] = History{ - Time: time.Unix(int64(resp[x][0]), 0), - Low: resp[x][1], - High: resp[x][2], - Open: resp[x][3], - Close: resp[x][4], - Volume: resp[x][5], - } - } - - return history, nil -} - // GetStats returns 30 day and 24 hour stats for the product. Volume is in base currency // units. open, high, low are in quote currency units. func (c *CoinbasePro) GetStats(ctx context.Context, currencyPair string) (Stats, error) { @@ -713,24 +1377,11 @@ func (c *CoinbasePro) GetStats(ctx context.Context, currencyPair string) (Stats, } path := fmt.Sprintf( - "%s/%s/%s", coinbaseproProducts, currencyPair, coinbaseproStats) + "%s/%s/%s", coinbaseProducts, currencyPair, coinbaseproStats) return stats, c.SendHTTPRequest(ctx, exchange.RestSpot, path, &stats) } -// GetTicker returns snapshot information about the last trade (tick), best bid/ask and -// 24h volume. -func (c *CoinbasePro) GetTicker(ctx context.Context, currencyPair string) (*Ticker, error) { - if currencyPair == "" { - return nil, errors.New("currency pair cannot be empty") - } - path := fmt.Sprintf( - "%s/%s/%s", coinbaseproProducts, currencyPair, coinbaseproTicker) - tick := Ticker{} - - return &tick, c.SendHTTPRequest(ctx, exchange.RestSpot, path, &tick) -} - // GetTrades lists information on the latest trades for a product func (c *CoinbasePro) GetTrades(ctx context.Context, currencyPair, direction, step string, limit int64) ([]Trade, error) { if currencyPair == "" { @@ -738,7 +1389,7 @@ func (c *CoinbasePro) GetTrades(ctx context.Context, currencyPair, direction, st } path := fmt.Sprintf( - "%s/%s/%s", coinbaseproProducts, currencyPair, coinbaseproTrades) + "%s/%s/%s", coinbaseProducts, currencyPair, coinbaseproTrades) var params Params params.urlVals = url.Values{} @@ -765,7 +1416,7 @@ func (c *CoinbasePro) GetAllProfiles(ctx context.Context, active *bool) ([]Profi path := common.EncodeURLValues(coinbaseproProfiles, params.urlVals) return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp, nil) + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, "", nil, Version3, &resp, nil) } // CreateAProfile creates a new profile, failing if no name is provided, @@ -779,7 +1430,7 @@ func (c *CoinbasePro) CreateAProfile(ctx context.Context, name string) (Profile, req := map[string]interface{}{"name": name} return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproProfiles, req, &resp, nil) + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproProfiles, "", req, Version3, &resp, nil) } // TransferBetweenProfiles transfers an amount of currency from one profile to another @@ -795,7 +1446,7 @@ func (c *CoinbasePro) TransferBetweenProfiles(ctx context.Context, from, to, cur path := fmt.Sprintf("%s/%s", coinbaseproProfiles, coinbaseproTransfer) return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, path, req, &resp, nil) + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, path, "", req, Version3, &resp, nil) } // GetProfileByID returns information on a single profile, provided its ID @@ -811,7 +1462,7 @@ func (c *CoinbasePro) GetProfileByID(ctx context.Context, profileID string, acti path = common.EncodeURLValues(path, params.urlVals) return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp, nil) + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, "", nil, Version3, &resp, nil) } // RenameProfile renames a profile, provided its ID @@ -826,7 +1477,7 @@ func (c *CoinbasePro) RenameProfile(ctx context.Context, profileID, newName stri path := fmt.Sprintf("%s/%s", coinbaseproProfiles, profileID) return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPut, path, req, &resp, nil) + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPut, path, "", req, Version3, &resp, nil) } // DeleteProfile deletes a profile and transfers its funds to a specified @@ -842,7 +1493,7 @@ func (c *CoinbasePro) DeleteProfile(ctx context.Context, profileID, transferTo s path := fmt.Sprintf("%s/%s/%s", coinbaseproProfiles, profileID, coinbaseproDeactivate) return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodDelete, path, req, &resp, nil) + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodDelete, path, "", req, Version3, &resp, nil) } // GetAllReports returns a list of all user-generated reports @@ -864,7 +1515,7 @@ func (c *CoinbasePro) GetAllReports(ctx context.Context, profileID string, repor path := common.EncodeURLValues(coinbaseproReports, params.urlVals) return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp, nil) + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, "", nil, Version3, &resp, nil) } // CreateReport creates a new report @@ -900,7 +1551,7 @@ func (c *CoinbasePro) CreateReport(ctx context.Context, reportType, year, format EndDate: endDate.Format(time.RFC3339), ProductID: productID} } return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproReports, req, &resp, nil) + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproReports, "", req, Version3, &resp, nil) } // GetReportByID returns a single report, provided its ID @@ -913,7 +1564,7 @@ func (c *CoinbasePro) GetReportByID(ctx context.Context, reportID string) (Repor path := fmt.Sprintf("%s/%s", coinbaseproReports, reportID) return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp, nil) + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, "", nil, Version3, &resp, nil) } // GetTravelRules returns a list of all travel rule information @@ -927,7 +1578,7 @@ func (c *CoinbasePro) GetTravelRules(ctx context.Context, direction, step, addre path := common.EncodeURLValues(coinbaseproTravelRules, params.urlVals) - return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp, nil) + return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, "", nil, Version3, &resp, nil) } // CreateTravelRule creates a travel rule entry @@ -938,7 +1589,7 @@ func (c *CoinbasePro) CreateTravelRule(ctx context.Context, address, originName, "originator_country": originCountry} return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproTravelRules, req, &resp, nil) + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproTravelRules, "", req, Version3, &resp, nil) } // DeleteTravelRule deletes a travel rule entry @@ -949,7 +1600,7 @@ func (c *CoinbasePro) DeleteTravelRule(ctx context.Context, id string) error { path := fmt.Sprintf("%s/%s", coinbaseproTravelRules, id) - return c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodDelete, path, nil, nil, nil) + return c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodDelete, path, "", nil, Version3, nil, nil) } // GetExchangeLimits returns information on payment method transfer limits, @@ -957,10 +1608,10 @@ func (c *CoinbasePro) DeleteTravelRule(ctx context.Context, id string) error { func (c *CoinbasePro) GetExchangeLimits(ctx context.Context, userID string) (ExchangeLimits, error) { var resp ExchangeLimits - path := fmt.Sprintf("%s/%s/%s", coinbaseproUsers, userID, coinbaseproExchangeLimits) + path := fmt.Sprintf("%s/%s/%s", coinbaseUsers, userID, coinbaseproExchangeLimits) return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp, nil) + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, "", nil, Version3, &resp, nil) } // UpdateSettlementPreference updates whether one wants their funds to @@ -972,12 +1623,12 @@ func (c *CoinbasePro) UpdateSettlementPreference(ctx context.Context, userID, pr req := map[string]interface{}{"settlement_preference": preference} - path := fmt.Sprintf("%s/%s/%s", coinbaseproUsers, userID, coinbaseproSettlementPreferences) + path := fmt.Sprintf("%s/%s/%s", coinbaseUsers, userID, coinbaseproSettlementPreferences) var resp string return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPut, path, req, &resp, nil) + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPut, path, "", req, Version3, &resp, nil) } // GetAllWrappedAssets returns information on all supported wrapped assets @@ -1011,7 +1662,7 @@ func (c *CoinbasePro) GetAllStakeWraps(ctx context.Context, direction, from, to, path = common.EncodeURLValues(path, params.urlVals) return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp, nil) + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, "", nil, Version3, &resp, nil) } // CreateStakeWrap stakes and wraps from one currency to another, under the profile @@ -1028,7 +1679,7 @@ func (c *CoinbasePro) CreateStakeWrap(ctx context.Context, from, to string, amou path := fmt.Sprintf("%s/%s", coinbaseproWrappedAssets, coinbaseproStakeWraps) return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, path, req, &resp, nil) + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, path, "", req, Version3, &resp, nil) } // GetStakeWrapByID returns details of a single stake-wrap @@ -1042,7 +1693,7 @@ func (c *CoinbasePro) GetStakeWrapByID(ctx context.Context, stakeWrapID string) path := fmt.Sprintf("%s/%s/%s", coinbaseproWrappedAssets, coinbaseproStakeWraps, stakeWrapID) return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp, nil) + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, "", nil, Version3, &resp, nil) } // GetWrappedAssetByID returns details of a single wrapped asset @@ -1071,12 +1722,6 @@ func (c *CoinbasePro) GetWrappedAssetConversionRate(ctx context.Context, wrapped return resp, c.SendHTTPRequest(ctx, exchange.RestSpot, path, &resp) } -// // GetCurrentServerTime returns the API server time -// func (c *CoinbasePro) GetCurrentServerTime(ctx context.Context) (ServerTime, error) { -// serverTime := ServerTime{} -// return serverTime, c.SendHTTPRequest(ctx, exchange.RestSpot, coinbaseproTime, &serverTime) -// } - // // MarginTransfer sends funds between a standard/default profile and a margin // // profile. // // A deposit will transfer funds from the default profile into the margin @@ -1137,6 +1782,8 @@ func (c *CoinbasePro) GetWrappedAssetConversionRate(ctx context.Context, wrapped // c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, coinbaseproTrailingVolume, nil, &resp) // } +*/ + // SendHTTPRequest sends an unauthenticated HTTP request func (c *CoinbasePro) SendHTTPRequest(ctx context.Context, ep exchange.URL, path string, result interface{}) error { endpoint, err := c.API.Endpoints.GetURL(ep) @@ -1159,7 +1806,7 @@ func (c *CoinbasePro) SendHTTPRequest(ctx context.Context, ep exchange.URL, path } // SendAuthenticatedHTTPRequest sends an authenticated HTTP request -func (c *CoinbasePro) SendAuthenticatedHTTPRequest(ctx context.Context, ep exchange.URL, method, path string, bodyParams map[string]interface{}, result interface{}, returnHead *http.Header) (err error) { +func (c *CoinbasePro) SendAuthenticatedHTTPRequest(ctx context.Context, ep exchange.URL, method, path, queryParams string, bodyParams map[string]interface{}, version Version, result interface{}, returnHead *http.Header) (err error) { creds, err := c.GetCredentials(ctx) if err != nil { return err @@ -1169,6 +1816,12 @@ func (c *CoinbasePro) SendAuthenticatedHTTPRequest(ctx context.Context, ep excha return err } + // Version 2 wants query params in the path during signing + if version == Version2 { + path = path + queryParams + } + + interim := json.RawMessage{} newRequest := func() (*request.Item, error) { payload := []byte("") if bodyParams != nil { @@ -1179,9 +1832,8 @@ func (c *CoinbasePro) SendAuthenticatedHTTPRequest(ctx context.Context, ep excha } n := strconv.FormatInt(time.Now().Unix(), 10) - message := n + method + "/" + path + string(payload) - fmt.Println(message) + message := n + method + path + string(payload) hmac, err := crypto.GetHMAC(crypto.HashSHA256, []byte(message), @@ -1192,25 +1844,60 @@ func (c *CoinbasePro) SendAuthenticatedHTTPRequest(ctx context.Context, ep excha headers := make(map[string]string) headers["CB-ACCESS-KEY"] = creds.Key - headers["CB-ACCESS-SIGN"] = crypto.Base64Encode(hmac) + headers["CB-ACCESS-SIGN"] = hex.EncodeToString(hmac) headers["CB-ACCESS-TIMESTAMP"] = n - headers["CB-ACCESS-PASSPHRASE"] = creds.ClientID headers["Content-Type"] = "application/json" + headers["CB-VERSION"] = "2023-11-13" + + // Version 3 only wants query params in the path when the request is sent + if version == Version3 { + path = path + queryParams + } return &request.Item{ Method: method, Path: endpoint + path, Headers: headers, Body: bytes.NewBuffer(payload), - Result: result, + Result: &interim, Verbose: c.Verbose, HTTPDebugging: c.HTTPDebugging, HTTPRecording: c.HTTPRecording, HeaderResponse: returnHead, }, nil } - err = c.SendPayload(ctx, request.Unset, newRequest, request.AuthenticatedRequest) - return err + rateLim := V2Rate + if version == Version3 { + 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 + if err != nil { + return err + } + errCap := struct { + ErrorType string `json:"error"` + Message string `json:"message"` + ErrorDetails string `json:"error_details"` + EditFailureReason string `json:"edit_failure_reason"` + PreviewFailureReason string `json:"preview_failure_reason"` + NewOrderFailureReason string `json:"new_order_failure_reason"` + }{} + if err := json.Unmarshal(interim, &errCap); err == nil { + if errCap.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", + errCap.Message, errCap.ErrorType, errCap.ErrorDetails, errCap.EditFailureReason, + errCap.PreviewFailureReason, errCap.NewOrderFailureReason) + return errors.New(errMessage) + } + } + if result == nil { + return nil + } + return json.Unmarshal(interim, result) } // // GetFee returns an estimate of fee based on type of transaction @@ -1288,21 +1975,13 @@ func (c *CoinbasePro) SendAuthenticatedHTTPRequest(ctx context.Context, ep excha // return fee // } -// PrepareDSL adds the direction, step, and limit queries for pagination -func (p *Params) PrepareDSL(direction, step string, limit int64) { - p.urlVals.Set(direction, step) - if limit >= 0 { - p.urlVals.Set("limit", strconv.FormatInt(limit, 10)) - } -} - // PrepareDateString encodes a set of parameters indicating start & end dates -func (p *Params) PrepareDateString(startDate, endDate time.Time) error { +func (p *Params) PrepareDateString(startDate, endDate time.Time, labelStart, labelEnd string) error { err := common.StartEndTimeCheck(startDate, endDate) if err == nil { - p.urlVals.Set("start_date", startDate.Format(time.RFC3339)) - p.urlVals.Set("end_date", endDate.Format(time.RFC3339)) + p.urlVals.Set(labelStart, startDate.Format(time.RFC3339)) + p.urlVals.Set(labelEnd, endDate.Format(time.RFC3339)) } if err != nil { @@ -1314,36 +1993,19 @@ func (p *Params) PrepareDateString(startDate, endDate time.Time) error { return err } -// PrepareProfIDAndProdID encodes a set of parameters indicating profile and product IDs -func (p *Params) PrepareProfIDAndProdID(profileID, currencyPair string) { - p.urlVals.Set("profile_id", profileID) - p.urlVals.Set("product_id", currencyPair) -} - -// PrepareAddAddress constructs an element of a slice to be passed to the -// AddAddresses function -func PrepareAddAddress(currency, address, destination_tag, label, vaspID string, - verifiedSelfHosted bool) (AddAddressRequest, error) { - if address == "" { - return AddAddressRequest{}, errors.New("address cannot be empty") +func (p *Params) PreparePagination(pag PaginationInp) { + if pag.Limit != 0 { + p.urlVals.Set("limit", strconv.Itoa(int(pag.Limit))) } - vIDCheck := []string{"Coinbase", "Anchorage", "Balance", "bitFlyer", "BitGo", - "Bittrex", "BlockFi", "Circle", "Coinhako", "Fidelity", "Gemini", "Huobi", - "Kraken", "Paxos", "PayPal", "Robinhood", "Shakepay", "StandardCustody", - "Tradestation", "Zero Hash", "Bitstamp"} - if vaspID != "" && !common.StringDataCompare(vIDCheck, vaspID) { - return AddAddressRequest{}, - errors.New("vaspID must be one of the following or empty: " + - strings.Join(vIDCheck, ", ")) + if pag.OrderAscend { + p.urlVals.Set("order", "asc") + } + if pag.StartingAfter != "" { + p.urlVals.Set("starting_after", pag.StartingAfter) + } + if pag.EndingBefore != "" { + p.urlVals.Set("ending_before", pag.EndingBefore) } - - req := AddAddressRequest{currency, To{address, destination_tag}, label, - verifiedSelfHosted, vaspID} - - // TODO: It also lets us add an arbitrary amount of strings under this object, - // but doesn't explain what they do. Investigate more later. - - return req, nil } // OrderbookHelper handles the transfer of bids and asks of unclear levels, to a @@ -1390,3 +2052,11 @@ func OrderbookHelper(iOD InterOrderDetail, level int32) ([]GenOrderDetail, error return gOD, nil } + +// PrepareDSL adds the direction, step, and limit queries for pagination +func (p *Params) PrepareDSL(direction, step string, limit int64) { + p.urlVals.Set(direction, step) + if limit >= 0 { + p.urlVals.Set("limit", strconv.FormatInt(limit, 10)) + } +} diff --git a/exchanges/coinbasepro/coinbasepro_test.go b/exchanges/coinbasepro/coinbasepro_test.go index 70af08850dd..31480446644 100644 --- a/exchanges/coinbasepro/coinbasepro_test.go +++ b/exchanges/coinbasepro/coinbasepro_test.go @@ -3,16 +3,16 @@ package coinbasepro import ( "context" "errors" + "fmt" "log" - "net/http" "net/url" "os" "sync" "testing" "time" - "github.com/gorilla/websocket" + "github.com/gofrs/uuid" "github.com/stretchr/testify/assert" "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/config" @@ -20,7 +20,6 @@ import ( exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues" - "github.com/thrasher-corp/gocryptotrader/exchanges/stream" ) var ( @@ -40,8 +39,11 @@ const ( // Constants used within tests const ( // Donation address - testAddress = "bc1qk0jareu4jytc0cfrhr5wgshsq8282awpavfahc" - travelRuleDuplicateError = "CoinbasePro unsuccessful HTTP status code: 400 raw response: {\"message\":\"addresses stored must be unique\"}, authenticated request failed" + testAddress = "bc1qk0jareu4jytc0cfrhr5wgshsq8282awpavfahc" + + errExpectMismatch = "received: '%v' but expected: '%v'" + errExpectedNonEmpty = "expected non-empty response" + skipPayMethodNotFound = "no payment methods found, skipping" ) func TestMain(m *testing.M) { @@ -80,7 +82,7 @@ func TestStart(t *testing.T) { t.Parallel() err := c.Start(context.Background(), nil) if !errors.Is(err, common.ErrNilPointer) { - t.Fatalf("received: '%v' but expected: '%v'", err, common.ErrNilPointer) + t.Fatalf(errExpectMismatch, err, common.ErrNilPointer) } var testWg sync.WaitGroup err = c.Start(context.Background(), &testWg) @@ -90,53 +92,809 @@ func TestStart(t *testing.T) { testWg.Wait() } -func TestSendAuthenticatedHTTPRequest(t *testing.T) { +// func TestSendAuthenticatedHTTPRequest(t *testing.T) { +// sharedtestvalues.SkipTestIfCredentialsUnset(t, c) +// var resp interface{} +// err := c.SendAuthenticatedHTTPRequest(context.Background(), exchange.RestSpot, "", "", "", nil, &resp, nil) +// if err != nil { +// t.Error("SendAuthenticatedHTTPRequest() error", err) +// } +// log.Printf("%+v", resp) +// } + +func TestGetAllAccounts(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + resp, err := c.GetAllAccounts(context.Background(), 50, "") + if err != nil { + t.Error(err) + } + assert.NotEmpty(t, resp, errExpectedNonEmpty) +} + +func TestGetAccountByID(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + _, err := c.GetAccountByID(context.Background(), "") + if !errors.Is(err, errAccountIDEmpty) { + t.Errorf(errExpectMismatch, err, errAccountIDEmpty) + } + longResp, err := c.GetAllAccounts(context.Background(), 49, "") + if err != nil { + t.Error(err) + } + if len(longResp.Accounts) == 0 { + t.Fatal(errExpectedNonEmpty) + } + shortResp, err := c.GetAccountByID(context.Background(), longResp.Accounts[0].UUID) + if err != nil { + t.Error(err) + } + if *shortResp != longResp.Accounts[0] { + t.Error("mismatched responses") + } +} + +func TestGetBestBidAsk(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + testPairs := []string{testPair.String(), "ETH-USD"} + resp, err := c.GetBestBidAsk(context.Background(), testPairs) + if err != nil { + t.Error(err) + } + assert.NotEmpty(t, resp, errExpectedNonEmpty) +} + +func TestGetProductBook(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + _, err := c.GetProductBook(context.Background(), "", 0) + if !errors.Is(err, errProductIDEmpty) { + t.Errorf(errExpectMismatch, err, errProductIDEmpty) + } + resp, err := c.GetProductBook(context.Background(), testPair.String(), 5) + if err != nil { + t.Error(err) + } + assert.NotEmpty(t, resp, errExpectedNonEmpty) +} + +func TestGetAllProducts(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - var resp interface{} - err := c.SendAuthenticatedHTTPRequest(context.Background(), exchange.RestSpot, "", "", nil, &resp, nil) + testPairs := []string{testPair.String(), "ETH-USD"} + resp, err := c.GetAllProducts(context.Background(), 2, 0, "SPOT", unknownContract, testPairs) if err != nil { - t.Error("SendAuthenticatedHTTPRequest() error", err) + t.Error(err) + } + assert.NotEmpty(t, resp, errExpectedNonEmpty) +} + +func TestGetProductByID(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + _, err := c.GetProductByID(context.Background(), "") + if !errors.Is(err, errProductIDEmpty) { + t.Errorf(errExpectMismatch, err, errProductIDEmpty) + } + resp, err := c.GetProductByID(context.Background(), testPair.String()) + if err != nil { + t.Error(err) + } + assert.NotEmpty(t, resp, errExpectedNonEmpty) +} + +func TestGetHistoricRates(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + _, err := c.GetHistoricRates(context.Background(), "", granUnknown, time.Time{}, time.Time{}) + if !errors.Is(err, errProductIDEmpty) { + t.Errorf(errExpectMismatch, err, errProductIDEmpty) + } + _, err = c.GetHistoricRates(context.Background(), testPair.String(), "blorbo", time.Time{}, time.Time{}) + if err == nil { + t.Error("expected error due to invalid granularity") + } + resp, err := c.GetHistoricRates(context.Background(), testPair.String(), granOneMin, + time.Now().Add(-5*time.Minute), time.Now()) + if err != nil { + t.Error(err) + } + assert.NotEmpty(t, resp, errExpectedNonEmpty) +} + +func TestGetTicker(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + _, err := c.GetTicker(context.Background(), "", 1) + if !errors.Is(err, errProductIDEmpty) { + t.Errorf(errExpectMismatch, err, errProductIDEmpty) + } + resp, err := c.GetTicker(context.Background(), testPair.String(), 5) + if err != nil { + t.Error(err) + } + assert.NotEmpty(t, resp, errExpectedNonEmpty) +} + +func TestPlaceOrder(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) + _, err := c.PlaceOrder(context.Background(), "", "", "", "", "", 0, 0, 0, 0, false, time.Time{}) + if !errors.Is(err, errClientOrderIDEmpty) { + t.Errorf(errExpectMismatch, err, errClientOrderIDEmpty) + } + _, err = c.PlaceOrder(context.Background(), "meow", "", "", "", "", 0, 0, 0, 0, false, time.Time{}) + if !errors.Is(err, errProductIDEmpty) { + t.Errorf(errExpectMismatch, err, errProductIDEmpty) + } + _, err = c.PlaceOrder(context.Background(), "meow", testPair.String(), order.Sell.String(), "", "", 0, + 0, 0, 0, false, time.Time{}) + if !errors.Is(err, errAmountZeroNonMarketBuy) { + t.Errorf(errExpectMismatch, err, errAmountZeroNonMarketBuy) + } + _, err = c.PlaceOrder(context.Background(), "meow", testPair.String(), order.Buy.String(), "", + order.Market.Lower(), 1, 0, 0, 0, false, time.Time{}) + if !errors.Is(err, errAmountZeroMarketBuy) { + t.Errorf(errExpectMismatch, err, errAmountZeroMarketBuy) + } + id, _ := uuid.NewV4() + _, err = c.PlaceOrder(context.Background(), id.String(), testPair.String(), order.Sell.String(), "", + order.Limit.Lower(), 0.0000001, 0, 1000000000000, 0, false, time.Now().Add(time.Hour)) + if err != nil { + t.Error(err) + } +} + +func TestCancelOrders(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) + var OrderSlice []string + _, err := c.CancelOrders(context.Background(), OrderSlice) + if !errors.Is(err, errOrderIDEmpty) { + t.Errorf(errExpectMismatch, err, errOrderIDEmpty) + } + ordID, err := c.PlaceOrder(context.Background(), "meow", testPair.String(), order.Sell.String(), "", + order.Limit.Lower(), 0.0000001, 0, 1000000000000, 0, false, time.Time{}) + if err != nil { + t.Error(err) + } + OrderSlice = append(OrderSlice, ordID.OrderID) + _, err = c.CancelOrders(context.Background(), OrderSlice) + if err != nil { + t.Error(err) + } +} + +func TestEditOrder(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) + _, err := c.EditOrder(context.Background(), "", 0, 0) + if !errors.Is(err, errOrderIDEmpty) { + t.Errorf(errExpectMismatch, err, errOrderIDEmpty) + } + _, err = c.EditOrder(context.Background(), "meow", 0, 0) + if !errors.Is(err, errSizeAndPriceZero) { + t.Errorf(errExpectMismatch, err, errSizeAndPriceZero) + } + id, _ := uuid.NewV4() + ordID, err := c.PlaceOrder(context.Background(), id.String(), testPair.String(), order.Sell.String(), "", + order.Limit.Lower(), 0.0000001, 0, 1000000000000, 0, false, time.Time{}) + if err != nil { + t.Error(err) + } + _, err = c.EditOrder(context.Background(), ordID.OrderID, 0, 10000000000000) + if err != nil { + t.Error(err) + } +} + +func TestEditOrderPreview(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) + _, err := c.EditOrderPreview(context.Background(), "", 0, 0) + if !errors.Is(err, errOrderIDEmpty) { + t.Errorf(errExpectMismatch, err, errOrderIDEmpty) + } + _, err = c.EditOrderPreview(context.Background(), "meow", 0, 0) + if !errors.Is(err, errSizeAndPriceZero) { + t.Errorf(errExpectMismatch, err, errSizeAndPriceZero) + } + id, _ := uuid.NewV4() + resp, err := c.EditOrderPreview(context.Background(), id.String(), 0.0000001, 10000000000000) + if err != nil { + t.Error(err) } log.Printf("%+v", resp) } -func TestGetAllAccounts(t *testing.T) { +func TestGetAllOrders(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - resp, err := c.GetAllAccounts(context.Background()) + status := make([]string, 2) + _, err := c.GetAllOrders(context.Background(), "", "", "", "", "", "", "", "", status, 0, time.Unix(2, 2), + time.Unix(1, 1)) + if !errors.Is(err, common.ErrStartAfterEnd) { + t.Errorf(errExpectMismatch, err, common.ErrStartAfterEnd) + } + status[0] = "CANCELLED" + status[1] = "OPEN" + _, err = c.GetAllOrders(context.Background(), "", "", "", "", "", "", "", "", status, 0, time.Time{}, + time.Time{}) + if !errors.Is(err, errOpenPairWithOtherTypes) { + t.Errorf(errExpectMismatch, err, errOpenPairWithOtherTypes) + } + status = make([]string, 0) + _, err = c.GetAllOrders(context.Background(), "", "USD", "LIMIT", "SELL", "", "SPOT", "RETAIL_ADVANCED", + "UNKNOWN_CONTRACT_EXPIRY_TYPE", status, 10, time.Time{}, time.Time{}) if err != nil { - t.Error("CoinBasePro GetAllAccounts() error", err) + t.Error(err) + } +} + +func TestGetFills(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + _, err := c.GetFills(context.Background(), "", "", "", 0, time.Unix(2, 2), time.Unix(1, 1)) + if !errors.Is(err, common.ErrStartAfterEnd) { + t.Errorf(errExpectMismatch, err, common.ErrStartAfterEnd) + } + _, err = c.GetFills(context.Background(), "0", testPair.String(), "", 2, time.Unix(1, 1), time.Now()) + if err != nil { + t.Error(err) + } +} + +func TestGetOrderByID(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + _, err := c.GetOrderByID(context.Background(), "", "", "") + if !errors.Is(err, errOrderIDEmpty) { + t.Errorf(errExpectMismatch, err, errOrderIDEmpty) + } + ordID, err := c.GetAllOrders(context.Background(), "", "", "", "", "", "", "", "", nil, 10, + time.Time{}, time.Time{}) + if err != nil { + t.Error(err) + } + if len(ordID.Orders) == 0 { + t.Skip("no orders found, skipping") + } + _, err = c.GetOrderByID(context.Background(), ordID.Orders[0].OrderID, ordID.Orders[0].ClientOID, "") + if err != nil { + t.Error(err) + } +} + +func TestGetTransactionSummary(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + _, err := c.GetTransactionSummary(context.Background(), time.Unix(2, 2), time.Unix(1, 1), "", "", "") + if !errors.Is(err, common.ErrStartAfterEnd) { + t.Errorf(errExpectMismatch, err, common.ErrStartAfterEnd) + } + _, err = c.GetTransactionSummary(context.Background(), time.Unix(1, 1), time.Now(), "", "SPOT", + "UNKNOWN_CONTRACT_EXPIRY_TYPE") + if err != nil { + t.Error(err) + } +} + +func TestListNotifications(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + _, err := c.ListNotifications(context.Background(), PaginationInp{}) + if err != nil { + t.Error(err) + } +} + +func TestGetUserByID(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + resp, err := c.GetCurrentUser(context.Background()) + if err != nil { + t.Error(err) + } + assert.NotEmpty(t, resp, errExpectedNonEmpty) + resp2, err := c.GetUserByID(context.Background(), resp.Data.ID) + if err != nil { + t.Error(err) + } + assert.NotEmpty(t, resp2, errExpectedNonEmpty) +} + +func TestGetCurrentUser(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + resp, err := c.GetCurrentUser(context.Background()) + if err != nil { + t.Error(err) + } + assert.NotEmpty(t, resp, errExpectedNonEmpty) +} + +func TestGetAuthInfo(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + resp, err := c.GetAuthInfo(context.Background()) + if err != nil { + t.Error(err) + } + assert.NotEmpty(t, resp, errExpectedNonEmpty) +} + +func TestUpdateUser(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) + resp, err := c.UpdateUser(context.Background(), "", "", "AUD") + if err != nil { + t.Error(err) + } + assert.NotEmpty(t, resp, errExpectedNonEmpty) +} + +func TestCreateWallet(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) + resp, err := c.CreateWallet(context.Background(), "BTC") + if err != nil { + t.Error(err) + } + assert.NotEmpty(t, resp, errExpectedNonEmpty) +} + +func TestGetAllWallets(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + pagIn := PaginationInp{Limit: 2} + resp, err := c.GetAllWallets(context.Background(), pagIn) + if err != nil { + t.Error(err) + } + assert.NotEmpty(t, resp, errExpectedNonEmpty) + if resp.Pagination.NextStartingAfter == "" { + t.Skip("fewer than 3 wallets found, skipping pagination test") + } + pagIn.StartingAfter = resp.Pagination.NextStartingAfter + resp, err = c.GetAllWallets(context.Background(), pagIn) + if err != nil { + t.Error(err) + } + assert.NotEmpty(t, resp, errExpectedNonEmpty) +} + +func TestGetWalletByID(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + _, err := c.GetWalletByID(context.Background(), "", "") + if !errors.Is(err, errCurrWalletConflict) { + t.Errorf(errExpectMismatch, err, errCurrWalletConflict) + } + resp, err := c.GetWalletByID(context.Background(), "", "BTC") + if err != nil { + t.Error(err) + } + assert.NotEmpty(t, resp, errExpectedNonEmpty) +} + +func TestUpdateWalletName(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) + _, err := c.UpdateWalletName(context.Background(), "", "") + if !errors.Is(err, errWalletIDEmpty) { + t.Errorf(errExpectMismatch, err, errWalletIDEmpty) + } + wID, err := c.GetAllWallets(context.Background(), PaginationInp{}) + if err != nil { + t.Error(err) + } + if len(wID.Data) == 0 { + t.Fatal(errExpectedNonEmpty) + } + resp, err := c.UpdateWalletName(context.Background(), wID.Data[len(wID.Data)-1].ID, "Wallet Tested by GCT") + if err != nil { + t.Error(err) + } + assert.NotEmpty(t, resp, errExpectedNonEmpty) +} + +func TestDeleteWallet(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) + err := c.DeleteWallet(context.Background(), "") + if !errors.Is(err, errWalletIDEmpty) { + t.Errorf(errExpectMismatch, err, errWalletIDEmpty) + } + wID, err := c.CreateWallet(context.Background(), "LTC") + if err != nil { + t.Error(err) + } + // As of now, it seems like this next step always fails. DeleteWallet only lets you delete non-primary + // non-fiat wallets, but the only non-primary wallet is fiat. Trying to create a secondary wallet for + // any cryptocurrency using CreateWallet simply returns the details of the existing primary wallet. + err = c.DeleteWallet(context.Background(), wID.Data.ID) + if err != nil { + t.Error(err) + } +} + +func TestCreateAddress(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) + _, err := c.CreateAddress(context.Background(), "", "") + if !errors.Is(err, errWalletIDEmpty) { + t.Errorf(errExpectMismatch, err, errWalletIDEmpty) + } + wID, err := c.GetWalletByID(context.Background(), "", "BTC") + if err != nil { + t.Error(err) + } + assert.NotEmpty(t, wID, errExpectedNonEmpty) + resp, err := c.CreateAddress(context.Background(), wID.Data.ID, "") + if err != nil { + t.Error(err) + } + assert.NotEmpty(t, resp, errExpectedNonEmpty) +} + +func TestGetAllAddresses(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + var pag PaginationInp + _, err := c.GetAllAddresses(context.Background(), "", pag) + if !errors.Is(err, errWalletIDEmpty) { + t.Errorf(errExpectMismatch, err, errWalletIDEmpty) + } + wID, err := c.GetWalletByID(context.Background(), "", "BTC") + if err != nil { + t.Error(err) + } + assert.NotEmpty(t, wID, errExpectedNonEmpty) + resp, err := c.GetAllAddresses(context.Background(), wID.Data.ID, pag) + if err != nil { + t.Error(err) + } + assert.NotEmpty(t, resp, errExpectedNonEmpty) +} + +func TestGetAddressByID(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + _, err := c.GetAddressByID(context.Background(), "", "") + if !errors.Is(err, errWalletIDEmpty) { + t.Errorf(errExpectMismatch, err, errWalletIDEmpty) + } + _, err = c.GetAddressByID(context.Background(), "123", "") + if !errors.Is(err, errAddressIDEmpty) { + t.Errorf(errExpectMismatch, err, errAddressIDEmpty) + } + wID, err := c.GetWalletByID(context.Background(), "", "BTC") + if err != nil { + t.Error(err) + } + assert.NotEmpty(t, wID, errExpectedNonEmpty) + addID, err := c.GetAllAddresses(context.Background(), wID.Data.ID, PaginationInp{}) + if err != nil { + t.Error(err) + } + assert.NotEmpty(t, addID, errExpectedNonEmpty) + resp, err := c.GetAddressByID(context.Background(), wID.Data.ID, addID.Data[0].ID) + if err != nil { + t.Error(err) + } + assert.NotEmpty(t, resp, errExpectedNonEmpty) +} + +func TestGetAddressTransactions(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + _, err := c.GetAddressTransactions(context.Background(), "", "", PaginationInp{}) + if !errors.Is(err, errWalletIDEmpty) { + t.Errorf(errExpectMismatch, err, errWalletIDEmpty) + } + _, err = c.GetAddressTransactions(context.Background(), "123", "", PaginationInp{}) + if !errors.Is(err, errAddressIDEmpty) { + t.Errorf(errExpectMismatch, err, errAddressIDEmpty) + } + wID, err := c.GetWalletByID(context.Background(), "", "BTC") + if err != nil { + t.Error(err) + } + assert.NotEmpty(t, wID, errExpectedNonEmpty) + addID, err := c.GetAllAddresses(context.Background(), wID.Data.ID, PaginationInp{}) + if err != nil { + t.Error(err) + } + assert.NotEmpty(t, addID, errExpectedNonEmpty) + _, err = c.GetAddressTransactions(context.Background(), wID.Data.ID, addID.Data[0].ID, PaginationInp{}) + if err != nil { + t.Error(err) + } +} + +func TestSendMoney(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) + _, err := c.SendMoney(context.Background(), "", "", "", "", "", "", "", "", "", false, false) + if !errors.Is(err, errTransactionTypeEmpty) { + t.Errorf(errExpectMismatch, err, errTransactionTypeEmpty) + } + _, err = c.SendMoney(context.Background(), "123", "", "", "", "", "", "", "", "", false, false) + if !errors.Is(err, errWalletIDEmpty) { + t.Errorf(errExpectMismatch, err, errWalletIDEmpty) + } + _, err = c.SendMoney(context.Background(), "123", "123", "", "", "", "", "", "", "", false, false) + if !errors.Is(err, errToEmpty) { + t.Errorf(errExpectMismatch, err, errToEmpty) + } + _, err = c.SendMoney(context.Background(), "123", "123", "123", "", "", "", "", "", "", false, false) + if !errors.Is(err, errAmountEmpty) { + t.Errorf(errExpectMismatch, err, errAmountEmpty) + } + _, err = c.SendMoney(context.Background(), "123", "123", "123", "123", "", "", "", "", "", false, false) + if !errors.Is(err, errCurrencyEmpty) { + t.Errorf(errExpectMismatch, err, errCurrencyEmpty) + } + wID, err := c.GetAllWallets(context.Background(), PaginationInp{}) + if err != nil { + t.Error(err) + } + if len(wID.Data) < 2 { + t.Skip("fewer than 2 wallets found, skipping test") + } + _, err = c.SendMoney(context.Background(), "transfer", wID.Data[0].ID, wID.Data[1].ID, "0.00000001", + "BTC", "GCT Test", "123", "", "", false, false) + if err != nil { + t.Error(err) + } +} + +func TestListTransactions(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + var pag PaginationInp + _, err := c.ListTransactions(context.Background(), "", pag) + if !errors.Is(err, errWalletIDEmpty) { + t.Errorf(errExpectMismatch, err, errWalletIDEmpty) + } + wID, err := c.GetWalletByID(context.Background(), "", "BTC") + if err != nil { + t.Error(err) + } + assert.NotEmpty(t, wID, errExpectedNonEmpty) + _, err = c.ListTransactions(context.Background(), wID.Data.ID, pag) + if err != nil { + t.Error(err) + } +} + +func TestGetTransactionByID(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + _, err := c.GetTransactionByID(context.Background(), "", "") + if !errors.Is(err, errWalletIDEmpty) { + t.Errorf(errExpectMismatch, err, errWalletIDEmpty) + } + _, err = c.GetTransactionByID(context.Background(), "123", "") + if !errors.Is(err, errTransactionIDEmpty) { + t.Errorf(errExpectMismatch, err, errTransactionIDEmpty) + } + wID, err := c.GetWalletByID(context.Background(), "", "BTC") + if err != nil { + t.Error(err) + } + assert.NotEmpty(t, wID, errExpectedNonEmpty) + tID, err := c.ListTransactions(context.Background(), wID.Data.ID, PaginationInp{}) + if err != nil { + t.Error(err) + } + if len(tID.Data) == 0 { + t.Skip("no transactions found, skipping") + } + resp, err := c.GetTransactionByID(context.Background(), wID.Data.ID, tID.Data[0].ID) + if err != nil { + t.Error(err) + } + assert.NotEmpty(t, resp, errExpectedNonEmpty) +} + +func TestFiatTransfer(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) + _, err := c.FiatTransfer(context.Background(), "", "", "", 0, false, FiatDeposit) + if !errors.Is(err, errWalletIDEmpty) { + t.Errorf(errExpectMismatch, err, errWalletIDEmpty) + } + _, err = c.FiatTransfer(context.Background(), "123", "", "", 0, false, FiatDeposit) + if !errors.Is(err, errAmountEmpty) { + t.Errorf(errExpectMismatch, err, errAmountEmpty) + } + _, err = c.FiatTransfer(context.Background(), "123", "", "", 1, false, FiatDeposit) + if !errors.Is(err, errCurrencyEmpty) { + t.Errorf(errExpectMismatch, err, errCurrencyEmpty) + } + _, err = c.FiatTransfer(context.Background(), "123", "123", "", 1, false, FiatDeposit) + if !errors.Is(err, errPaymentMethodEmpty) { + t.Errorf(errExpectMismatch, err, errPaymentMethodEmpty) + } + wID, err := c.GetWalletByID(context.Background(), "", "AUD") + if err != nil { + t.Error(err) + } + assert.NotEmpty(t, wID, errExpectedNonEmpty) + pmID, err := c.GetAllPaymentMethods(context.Background(), PaginationInp{}) + if err != nil { + t.Error(err) + } + if len(pmID.Data) == 0 { + t.Skip(skipPayMethodNotFound) + } + _, err = c.FiatTransfer(context.Background(), wID.Data.ID, "AUD", pmID.Data[0].FiatAccount.ID, 1, false, FiatDeposit) + if err != nil { + t.Error(err) + } + _, err = c.FiatTransfer(context.Background(), wID.Data.ID, "AUD", pmID.Data[0].FiatAccount.ID, 1, false, FiatWithdrawal) + if err != nil { + t.Error(err) + } +} + +func TestCommitTransfer(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) + _, err := c.CommitTransfer(context.Background(), "", "", FiatDeposit) + if !errors.Is(err, errWalletIDEmpty) { + t.Errorf(errExpectMismatch, err, errWalletIDEmpty) + } + _, err = c.CommitTransfer(context.Background(), "123", "", FiatDeposit) + if !errors.Is(err, errDepositIDEmpty) { + t.Errorf(errExpectMismatch, err, errDepositIDEmpty) + } + wID, err := c.GetWalletByID(context.Background(), "", "AUD") + if err != nil { + t.Error(err) + } + assert.NotEmpty(t, wID, errExpectedNonEmpty) + pmID, err := c.GetAllPaymentMethods(context.Background(), PaginationInp{}) + if err != nil { + t.Error(err) + } + if len(pmID.Data) == 0 { + t.Skip(skipPayMethodNotFound) + } + depID, err := c.FiatTransfer(context.Background(), wID.Data.ID, "AUD", pmID.Data[0].FiatAccount.ID, 1, + false, FiatDeposit) + if err != nil { + t.Error(err) + } + _, err = c.CommitTransfer(context.Background(), wID.Data.ID, depID.Data.ID, FiatDeposit) + if err != nil { + t.Error(err) + } + _, err = c.CommitTransfer(context.Background(), wID.Data.ID, depID.Data.ID, FiatWithdrawal) + if err != nil { + t.Error(err) + } +} + +func TestGetAllFiatTransfers(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + var pag PaginationInp + _, err := c.GetAllFiatTransfers(context.Background(), "", pag, FiatDeposit) + if !errors.Is(err, errWalletIDEmpty) { + t.Errorf(errExpectMismatch, err, errWalletIDEmpty) + } + wID, err := c.GetWalletByID(context.Background(), "", "AUD") + if err != nil { + t.Error(err) + } + assert.NotEmpty(t, wID, errExpectedNonEmpty) + _, err = c.GetAllFiatTransfers(context.Background(), wID.Data.ID, pag, FiatDeposit) + if err != nil { + t.Error(err) + } + _, err = c.GetAllFiatTransfers(context.Background(), wID.Data.ID, pag, FiatWithdrawal) + if err != nil { + t.Error(err) + } +} + +func TestGetFiatTransferByID(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + _, err := c.GetFiatTransferByID(context.Background(), "", "", FiatDeposit) + if !errors.Is(err, errWalletIDEmpty) { + t.Errorf(errExpectMismatch, err, errWalletIDEmpty) + } + _, err = c.GetFiatTransferByID(context.Background(), "123", "", FiatDeposit) + if !errors.Is(err, errDepositIDEmpty) { + t.Errorf(errExpectMismatch, err, errDepositIDEmpty) + } + wID, err := c.GetWalletByID(context.Background(), "", "AUD") + if err != nil { + t.Error(err) + } + assert.NotEmpty(t, wID, errExpectedNonEmpty) + dID, err := c.GetAllFiatTransfers(context.Background(), wID.Data.ID, PaginationInp{}, FiatDeposit) + if err != nil { + t.Error(err) + } + if len(dID.Data) == 0 { + t.Skip("no deposits found, skipping") + } + resp, err := c.GetFiatTransferByID(context.Background(), wID.Data.ID, dID.Data[0].ID, FiatDeposit) + if err != nil { + t.Error(err) + } + assert.NotEmpty(t, resp, errExpectedNonEmpty) + resp, err = c.GetFiatTransferByID(context.Background(), wID.Data.ID, dID.Data[0].ID, FiatWithdrawal) + if err != nil { + t.Error(err) + } + assert.NotEmpty(t, resp, errExpectedNonEmpty) +} + +func TestGetAllPaymentMethods(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + resp, err := c.GetAllPaymentMethods(context.Background(), PaginationInp{}) + if err != nil { + t.Error(err) + } + assert.NotEmpty(t, resp, errExpectedNonEmpty) +} + +func TestGetPaymentMethodByID(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + _, err := c.GetPaymentMethodByID(context.Background(), "") + if !errors.Is(err, errPaymentMethodEmpty) { + t.Errorf(errExpectMismatch, err, errPaymentMethodEmpty) + } + pmID, err := c.GetAllPaymentMethods(context.Background(), PaginationInp{}) + if err != nil { + t.Error(err) + } + if len(pmID.Data) == 0 { + t.Skip(skipPayMethodNotFound) + } + resp, err := c.GetPaymentMethodByID(context.Background(), pmID.Data[0].ID) + if err != nil { + t.Error(err) + } + assert.NotEmpty(t, resp, errExpectedNonEmpty) +} + +func TestGetFiatCurrencies(t *testing.T) { + resp, err := c.GetFiatCurrencies(context.Background()) + if err != nil { + t.Error(err) + } + assert.NotEmpty(t, resp, errExpectedNonEmpty) +} + +func TestGetCryptocurrencies(t *testing.T) { + resp, err := c.GetCryptocurrencies(context.Background()) + if err != nil { + t.Error(err) + } + assert.NotEmpty(t, resp, errExpectedNonEmpty) +} + +func TestGetExchangeRates(t *testing.T) { + resp, err := c.GetExchangeRates(context.Background(), "") + if err != nil { + t.Error(err) } - assert.NotEmpty(t, resp, "CoinBasePro GetAllAccounts() error, expected a non-empty response") + assert.NotEmpty(t, resp, errExpectedNonEmpty) } -func TestGetAccountByID(t *testing.T) { - sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - longResp, err := c.GetAllAccounts(context.Background()) +func TestGetPrice(t *testing.T) { + _, err := c.GetPrice(context.Background(), "", "") + if !errors.Is(err, errInvalidPriceType) { + t.Errorf(errExpectMismatch, err, errInvalidPriceType) + } + resp, err := c.GetPrice(context.Background(), testPair.String(), "spot") if err != nil { - t.Error("CoinBasePro GetAllAccounts() error", err) + t.Error(err) } - if len(longResp) == 0 { - t.Fatal("CoinBasePro GetAllAccounts() error, expected a non-empty response") + assert.NotEmpty(t, resp, errExpectedNonEmpty) + resp, err = c.GetPrice(context.Background(), testPair.String(), "buy") + if err != nil { + t.Error(err) } - shortResp, err := c.GetAccountByID(context.Background(), longResp[0].ID) + assert.NotEmpty(t, resp, errExpectedNonEmpty) + resp, err = c.GetPrice(context.Background(), testPair.String(), "sell") if err != nil { - t.Error("CoinBasePro GetAccountByID() error", err) + t.Error(err) } - if *shortResp != longResp[0] { - t.Error("CoinBasePro GetAccountByID() error, mismatched responses") + assert.NotEmpty(t, resp, errExpectedNonEmpty) +} + +func TestGetCurrentServerTime(t *testing.T) { + resp, err := c.GetCurrentServerTime(context.Background()) + if err != nil { + t.Error(err) } + assert.NotEmpty(t, resp, errExpectedNonEmpty) } +/* + func TestGetHolds(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - accID, err := c.GetAllAccounts(context.Background()) + accID, err := c.GetAllAccounts(context.Background(), 49, "") if err != nil { t.Error("CoinBasePro GetAllAccounts() error", err) } - if len(accID) == 0 { + if len(accID.Accounts) == 0 { t.Fatal("CoinBasePro GetAllAccounts() error, expected a non-empty response") } - _, _, err = c.GetHolds(context.Background(), accID[1].ID, pageNone, "2", 2) + _, _, err = c.GetHolds(context.Background(), accID.Accounts[1].UUID, pageNone, "2", 2) if err != nil { t.Error("CoinBasePro GetHolds() error", err) } @@ -144,7 +902,7 @@ func TestGetHolds(t *testing.T) { func TestGetAccountLedger(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - accID, err := c.GetAllAccounts(context.Background()) + accID, err := c.GetAllAccounts(context.Background(), 49, "") if err != nil { t.Error("CoinBasePro GetAllAccounts() error", err) } @@ -152,10 +910,10 @@ func TestGetAccountLedger(t *testing.T) { if err == nil { t.Error("CoinBasePro GetAccountLedger() error, expected an error due to invalid times") } - if len(accID) == 0 { + if len(accID.Accounts) == 0 { t.Fatal("CoinBasePro GetAllAccounts() error, expected a non-empty response") } - _, _, err = c.GetAccountLedger(context.Background(), accID[1].ID, pageNone, "1177507600", "a", + _, _, err = c.GetAccountLedger(context.Background(), accID.Accounts[1].UUID, pageNone, "1177507600", "a", time.Unix(1, 1), time.Now(), 1000) if err != nil { t.Error("CoinBasePro GetAccountLedger() error", err) @@ -164,14 +922,14 @@ func TestGetAccountLedger(t *testing.T) { func TestGetAccountTransfers(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - accID, err := c.GetAllAccounts(context.Background()) + accID, err := c.GetAllAccounts(context.Background(), 49, "") if err != nil { t.Error("CoinBasePro GetAllAccounts() error", err) } - if len(accID) == 0 { + if len(accID.Accounts) == 0 { t.Fatal("CoinBasePro GetAllAccounts() error, expected a non-empty response") } - _, _, err = c.GetAccountTransfers(context.Background(), accID[1].ID, "", "", "", 3) + _, _, err = c.GetAccountTransfers(context.Background(), accID.Accounts[1].UUID, "", "", "", 3) if err != nil { t.Error(err) } @@ -187,40 +945,40 @@ func TestGetAddressBook(t *testing.T) { } func TestAddAddresses(t *testing.T) { - sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) - var req [1]AddAddressRequest - var err error - req[0], err = PrepareAddAddress("BTC", testAddress, "", "implemented", "Coinbase", false) - if err != nil { - t.Error(err) - } - resp, err := c.AddAddresses(context.Background(), req[:]) - if err != nil { - t.Error("CoinBasePro AddAddresses() error", err) - } - assert.NotEmpty(t, resp, "CoinBasePro AddAddresses() error, expected a non-empty response") + // sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) + // var req [1]AddAddressRequest + // var err error + // req[0], err = PrepareAddAddress("BTC", testAddress, "", "implemented", "Coinbase", false) + // if err != nil { + // t.Error(err) + // } + // resp, err := c.AddAddresses(context.Background(), req[:]) + // if err != nil { + // t.Error("CoinBasePro AddAddresses() error", err) + // } + // assert.NotEmpty(t, resp, "CoinBasePro AddAddresses() error, expected a non-empty response") } func TestDeleteAddress(t *testing.T) { - sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) - var req [1]AddAddressRequest - var err error - req[0], err = PrepareAddAddress("BTC", testAddress, "", "implemented", "Coinbase", false) - if err != nil { - t.Error(err) - } - resp, err := c.AddAddresses(context.Background(), req[:]) - if err != nil { - t.Error("CoinBasePro AddAddresses() error", err) - } - if len(resp) == 0 { - t.Fatal("CoinBasePro AddAddresses() error, expected a non-empty response") - } + // sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) + // var req [1]AddAddressRequest + // var err error + // req[0], err = PrepareAddAddress("BTC", testAddress, "", "implemented", "Coinbase", false) + // if err != nil { + // t.Error(err) + // } + // resp, err := c.AddAddresses(context.Background(), req[:]) + // if err != nil { + // t.Error("CoinBasePro AddAddresses() error", err) + // } + // if len(resp) == 0 { + // t.Fatal("CoinBasePro AddAddresses() error, expected a non-empty response") + // } - err = c.DeleteAddress(context.Background(), resp[0].ID) - if err != nil { - t.Error("CoinBasePro DeleteAddress() error", err) - } + // err = c.DeleteAddress(context.Background(), resp[0].ID) + // if err != nil { + // t.Error("CoinBasePro DeleteAddress() error", err) + // } } func TestGetCoinbaseWallets(t *testing.T) { @@ -232,22 +990,6 @@ func TestGetCoinbaseWallets(t *testing.T) { assert.NotEmpty(t, resp, "CoinBasePro GetCoinbaseWallets() error, expected a non-empty response") } -func TestGenerateCryptoAddress(t *testing.T) { - sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - accID, err := c.GetCoinbaseWallets(context.Background()) - if err != nil { - t.Error("CoinBasePro GetAllAccounts() error", err) - } - if len(accID) == 0 { - t.Fatal("CoinBasePro GetAllAccounts() error, expected a non-empty response") - } - resp, err := c.GenerateCryptoAddress(context.Background(), accID[0].ID, "", "") - if err != nil { - t.Error("CoinBasePro GenerateCryptoAddress() error", err) - } - assert.NotEmpty(t, resp, "CoinBasePro GenerateCryptoAddress() error, expected a non-empty response") -} - func TestConvertCurrency(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) actBool := true @@ -501,130 +1243,6 @@ func TestGetFees(t *testing.T) { assert.NotEmpty(t, resp, "CoinBasePro GetFees() error, expected a non-empty response") } -func TestGetFills(t *testing.T) { - sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - _, _, err := c.GetFills(context.Background(), "", "", "", "", "", 0, time.Time{}, time.Time{}) - if err == nil { - t.Error("CoinBasePro GetFills() error, expected error due to empty order and product ID") - } - - _, _, err = c.GetFills(context.Background(), "1", "", "", "", "", 0, time.Unix(2, 2), time.Unix(1, 1)) - if err == nil { - t.Error("CoinBasePro GetFills() error, expected error due to invalid time range") - } - - _, _, err = c.GetFills(context.Background(), "a", testPair.String(), "", "", "spot", 2, time.Unix(1, 1), - time.Now()) - if err == nil { - t.Error("CoinBasePro GetFills() error, expected error due to invalid order ID") - } - _, _, err = c.GetFills(context.Background(), "", testPair.String(), "", "", "spot", 2, time.Unix(1, 1), - time.Now()) - if err != nil { - t.Error("CoinBasePro GetFills() error", err) - } -} - -func TestGetAllOrders(t *testing.T) { - sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - status := []string{"open", "pending", "rejected", "active", "done", "received", "settled", "all"} - - _, _, err := c.GetAllOrders(context.Background(), "", "", "", "", "", "", "", time.Time{}, time.Time{}, 0, status) - if err == nil { - t.Error("CoinBasePro GetAllOrders() error, expected error due to invalid limit") - } - _, _, err = c.GetAllOrders(context.Background(), "", "", "", "", "", "", "", time.Unix(2, 2), time.Unix(1, 1), 5, status) - if err == nil { - t.Error("CoinBasePro GetAllOrders() error, expected error due to null time range") - } - _, _, err = c.GetAllOrders(context.Background(), "", "", "", "", "", "", "", time.Unix(1, 1), time.Now(), 5, status) - if err != nil { - t.Error("CoinBasePro GetAllOrders() error", err) - } -} - -func TestCancelAllExistingOrders(t *testing.T) { - sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) - _, err := c.CancelAllExistingOrders(context.Background(), "", "") - if err != nil { - t.Error("CoinBasePro CancelAllExistingOrders() error", err) - } -} - -func TestPlaceOrder(t *testing.T) { - sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) - _, err := c.PlaceOrder(context.Background(), "", "", "", "", "", "", "", "", "", 0, 0, 0, 0, false) - if err == nil { - t.Error("CoinBasePro PlaceOrder() error, expected error due to price and size") - } - _, err = c.PlaceOrder(context.Background(), "", order.Market.Lower(), "", "", "", "", "", "", "", 0, 0, 0, 0, - false) - if err == nil { - t.Error("CoinBasePro PlaceOrder() error, expected error due to no size or funds for market order") - } - _, err = c.PlaceOrder(context.Background(), "", "", "", "", "", "", "", "", "", 0, 1, 1, 0, false) - if err == nil { - t.Error("CoinBasePro PlaceOrder() error, expected error due to no side provided") - } - _, err = c.PlaceOrder(context.Background(), "meow", "meow", "sell", "meow", "meow", "meow", "meow", "meow", - "meow", 2<<31, 2<<30, 1, 1, false) - if err == nil { - t.Error("CoinBasePro PlaceOrder() error, expected error due to invalid fields") - } - _, err = c.PlaceOrder(context.Background(), "", "", "sell", "BTC-USD", "", "", "", "", "", 2<<31, 2<<30, 1, - 0, false) - if err != nil { - // This can also error out if you're at your limit of orders on this product - t.Error("CoinBasePro PlaceOrder() error", err) - } -} - -func TestGetOrderByID(t *testing.T) { - sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - ordID, _, err := c.GetAllOrders(context.Background(), "", "", "", "", "", "", "", time.Unix(1, 1), time.Now(), - 5, []string{"all"}) - if err != nil { - t.Error("CoinBasePro GetAllOrders() error", err) - } - if len(ordID) == 0 { - t.Skip("Skipping test due to no orders found") - } - _, err = c.GetOrderByID(context.Background(), "", "", true) - if err == nil { - t.Error("CoinBasePro GetOrderByID() error, expected error due to empty order ID") - } - _, err = c.GetOrderByID(context.Background(), "meow", "", true) - if err == nil { - t.Error("CoinBasePro GetOrderByID() error, expected error due to invalid order ID") - } - _, err = c.GetOrderByID(context.Background(), ordID[0].ID, ordID[0].MarketType, false) - if err != nil { - t.Error("CoinBasePro GetOrderByID() error", err) - } -} - -func TestCancelExistingOrder(t *testing.T) { - sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) - _, err := c.CancelExistingOrder(context.Background(), "", "", "", true) - if err == nil { - t.Error("CoinBasePro CancelExistingOrder() error, expected error due to empty order ID") - } - _, err = c.CancelExistingOrder(context.Background(), "meow", "", "", true) - if err == nil { - t.Error("CoinBasePro GetOrderByID() error, expected error due to invalid order ID") - } - ordID, err := c.PlaceOrder(context.Background(), "", "", "sell", "BTC-USD", "", "", "", "", "", 2<<31, - 2<<30, 1, 0, false) - if err != nil { - // This can also error out if you're at your limit of orders on this product - t.Error("CoinBasePro PlaceOrder() error", err) - } - _, err = c.CancelExistingOrder(context.Background(), ordID.ID, "", "", false) - if err != nil { - t.Error("CoinBasePro CancelExistingOrder() error", err) - } -} - func TestGetSignedPrices(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) resp, err := c.GetSignedPrices(context.Background()) @@ -634,26 +1252,6 @@ func TestGetSignedPrices(t *testing.T) { assert.NotEmpty(t, resp, "CoinBasePro GetSignedPrices() error, expected a non-empty response") } -func TestGetAllProducts(t *testing.T) { - resp, err := c.GetAllProducts(context.Background(), "") - if err != nil { - t.Error("Coinbase, GetAllProducts() Error:", err) - } - assert.NotEmpty(t, resp, "Coinbase, GetAllProducts() Error, expected a non-empty response") -} - -func TestGetProductByID(t *testing.T) { - _, err := c.GetProductByID(context.Background(), "") - if err == nil { - t.Error("Coinbase, GetProductByID() Error, expected an error due to nonexistent pair") - } - resp, err := c.GetProductByID(context.Background(), "BTC-USD") - if err != nil { - t.Error("Coinbase, GetProductByID() Error:", err) - } - assert.NotEmpty(t, resp, "Coinbase, GetProductByID() Error, expected a non-empty response") -} - func TestGetOrderbook(t *testing.T) { _, err := c.GetOrderbook(context.Background(), "", 1) if err == nil { @@ -670,30 +1268,6 @@ func TestGetOrderbook(t *testing.T) { assert.NotEmpty(t, resp, "Coinbase, GetOrderbook() Error, expected a non-empty response") } -func TestGetHistoricRates(t *testing.T) { - _, err := c.GetHistoricRates(context.Background(), "", 0, time.Time{}, time.Time{}) - if err == nil { - t.Error("Coinbase, GetHistoricRates() Error, expected an error due to nonexistent pair") - } - _, err = c.GetHistoricRates(context.Background(), testPair.String(), 0, time.Now(), time.Unix(1, 1)) - if err == nil { - t.Error("Coinbase, GetHistoricRates() Error, expected an error due to invalid time") - } - _, err = c.GetHistoricRates(context.Background(), testPair.String(), 2<<60-2<<20, time.Time{}, time.Time{}) - if err == nil { - t.Error("Coinbase, GetHistoricRates() Error, expected an error due to invalid granularity") - } - _, err = c.GetHistoricRates(context.Background(), "Invalid pair.woof", 60, time.Time{}, time.Time{}) - if err == nil { - t.Error("Coinbase, GetHistoricRates() Error, expected an error due to invalid pair") - } - resp, err := c.GetHistoricRates(context.Background(), testPair.String(), 0, time.Unix(1, 1), time.Now()) - if err != nil { - t.Error("Coinbase, GetHistoricRates() Error", err) - } - assert.NotEmpty(t, resp, "Coinbase, GetHistoricRates() Error, expected a non-empty response") -} - func TestGetStats(t *testing.T) { _, err := c.GetStats(context.Background(), "") if err == nil { @@ -706,18 +1280,6 @@ func TestGetStats(t *testing.T) { assert.NotEmpty(t, resp, "Coinbase, GetStats() Error, expected a non-empty response") } -func TestGetTicker(t *testing.T) { - _, err := c.GetTicker(context.Background(), "") - if err == nil { - t.Error("Coinbase, GetTicker() Error, expected an error due to nonexistent pair") - } - resp, err := c.GetTicker(context.Background(), testPair.String()) - if err != nil { - t.Error("GetTicker() error", err) - } - assert.NotEmpty(t, resp, "Coinbase, GetTicker() Error, expected a non-empty response") -} - func TestGetTrades(t *testing.T) { _, err := c.GetTrades(context.Background(), "", "", "", 1) if err == nil { @@ -854,11 +1416,11 @@ func TestCreateReport(t *testing.T) { if len(profID) == 0 { t.Fatal("CoinBasePro GetAllProfiles() error, expected a non-empty response") } - accID, err := c.GetAllAccounts(context.Background()) + accID, err := c.GetAllAccounts(context.Background(), 49, "") if err != nil { t.Error("CoinBasePro GetAllAccounts() error", err) } - if len(accID) == 0 { + if len(accID.Accounts) == 0 { t.Fatal("CoinBasePro GetAllAccounts() error, expected a non-empty response") } // _, err = c.CreateReport(context.Background(), "account", "", "pdf", "testemail@thrasher.io", profID[0].ID, @@ -910,51 +1472,51 @@ func TestGetTravelRules(t *testing.T) { } func TestCreateTravelRule(t *testing.T) { - sharedtestvalues.SkipTestIfCannotManipulateOrders(t, c, canManipulateRealOrders) - resp, err := c.CreateTravelRule(context.Background(), "GCT Travel Rule Test", "GoCryptoTrader", "AU") - if err != nil && err.Error() != travelRuleDuplicateError { - t.Error("Coinbase, CreateTravelRule() error", err) - } - assert.NotEmpty(t, resp, "Coinbase, CreateTravelRule() Error, expected a non-empty response") + // sharedtestvalues.SkipTestIfCannotManipulateOrders(t, c, canManipulateRealOrders) + // resp, err := c.CreateTravelRule(context.Background(), "GCT Travel Rule Test", "GoCryptoTrader", "AU") + // if err != nil && err.Error() != travelRuleDuplicateError { + // t.Error("Coinbase, CreateTravelRule() error", err) + // } + // assert.NotEmpty(t, resp, "Coinbase, CreateTravelRule() Error, expected a non-empty response") } func TestDeleteTravelRule(t *testing.T) { - sharedtestvalues.SkipTestIfCannotManipulateOrders(t, c, canManipulateRealOrders) - err := c.DeleteTravelRule(context.Background(), "") - if err == nil { - t.Error("Coinbase, DeleteTravelRule() Error, expected an error due to empty ID") - } - _, err = c.CreateTravelRule(context.Background(), "GCT Travel Rule Test", "GoCryptoTrader", "AU") - if err != nil && err.Error() != travelRuleDuplicateError { - t.Error("Coinbase, CreateTravelRule() error", err) - } - resp, err := c.GetTravelRules(context.Background(), "", "", "", 0) - if err != nil { - t.Error("GetTravelRules() error", err) - } - if len(resp) == 0 { - t.Fatal("GetTravelRules() error, expected a non-empty response") - } - for i := range resp { - if resp[i].Address == "GCT Travel Rule Test" { - err = c.DeleteTravelRule(context.Background(), resp[i].ID) - if err != nil { - t.Error("Coinbase, DeleteTravelRule() error", err) - } - } - } + // sharedtestvalues.SkipTestIfCannotManipulateOrders(t, c, canManipulateRealOrders) + // err := c.DeleteTravelRule(context.Background(), "") + // if err == nil { + // t.Error("Coinbase, DeleteTravelRule() Error, expected an error due to empty ID") + // } + // _, err = c.CreateTravelRule(context.Background(), "GCT Travel Rule Test", "GoCryptoTrader", "AU") + // if err != nil && err.Error() != travelRuleDuplicateError { + // t.Error("Coinbase, CreateTravelRule() error", err) + // } + // resp, err := c.GetTravelRules(context.Background(), "", "", "", 0) + // if err != nil { + // t.Error("GetTravelRules() error", err) + // } + // if len(resp) == 0 { + // t.Fatal("GetTravelRules() error, expected a non-empty response") + // } + // for i := range resp { + // if resp[i].Address == "GCT Travel Rule Test" { + // err = c.DeleteTravelRule(context.Background(), resp[i].ID) + // if err != nil { + // t.Error("Coinbase, DeleteTravelRule() error", err) + // } + // } + // } } func TestGetExchangeLimits(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - accID, err := c.GetAllAccounts(context.Background()) + accID, err := c.GetAllAccounts(context.Background(), 49, "") if err != nil { t.Error("GetAllAccounts() error", err) } - if len(accID) == 0 { + if len(accID.Accounts) == 0 { t.Fatal("CoinBasePro GetAllAccounts() error, expected a non-empty response") } - resp, err := c.GetExchangeLimits(context.Background(), accID[0].ID) + resp, err := c.GetExchangeLimits(context.Background(), accID.Accounts[0].UUID) if err != nil { t.Error("GetExchangeLimits() error", err) } @@ -1050,6 +1612,8 @@ func TestGetWrappedAssetConversionRate(t *testing.T) { assert.NotEmpty(t, resp, "Coinbase, GetWrappedAssetConversionRate() Error, expected a non-empty response") } +*/ + // func TestGetCurrentServerTime(t *testing.T) { // _, err := c.GetCurrentServerTime(context.Background()) // if err != nil { @@ -1061,7 +1625,7 @@ func TestGetWrappedAssetConversionRate(t *testing.T) { // t.Parallel() // st, err := c.GetServerTime(context.Background(), asset.Spot) // if !errors.Is(err, nil) { -// t.Fatalf("received: '%v' but expected: '%v'", err, nil) +// t.Fatalf(errExpectMismatch, err, nil) // } // if st.IsZero() { @@ -1073,7 +1637,7 @@ func TestGetWrappedAssetConversionRate(t *testing.T) { // t.Parallel() // sharedtestvalues.SkipTestIfCredentialsUnset(t, c) -// _, err := c.GetAllAccounts(context.Background()) +// _, err := c.GetAllAccounts(context.Background(), 49, "") // if err != nil { // t.Error("GetAllAccounts() error", err) // } @@ -1555,6 +2119,8 @@ func TestGetWrappedAssetConversionRate(t *testing.T) { // } // } +/* + // TestWsAuth dials websocket, sends login request. func TestWsAuth(t *testing.T) { if !c.Websocket.IsEnabled() && !c.API.AuthenticatedWebsocketSupport || !sharedtestvalues.AreAPICredentialsSet(c) { @@ -1624,7 +2190,7 @@ func TestWsHeartbeat(t *testing.T) { "type": "heartbeat", "sequence": 90, "last_trade_id": 20, - "product_id": "BTC-USD", + "product_id": BTC-USD, "time": "2014-11-07T08:19:28.464459Z" }`) err := c.wsHandleData(pressXToJSON) @@ -1638,7 +2204,7 @@ func TestWsStatus(t *testing.T) { "type": "status", "products": [ { - "id": "BTC-USD", + "id": BTC-USD, "base_currency": "BTC", "quote_currency": "USD", "base_min_size": "0.001", @@ -1699,7 +2265,7 @@ func TestWsTicker(t *testing.T) { "time": "2017-09-02T17:05:49.250000Z", "product_id": "BTC-USD", "price": "4388.01000000", - "side": "buy", + "side": "buy", "last_size": "0.03000000", "best_bid": "4388", "best_ask": "4388.01" @@ -1795,7 +2361,7 @@ func TestWsOrders(t *testing.T) { "sequence": 10, "price": "200.2", "order_id": "d50ec984-77a8-460a-b958-66f114b0de9b", - "reason": "filled", + "reason": "filled", "side": "sell", "remaining_size": "0" }`) @@ -1925,22 +2491,7 @@ func TestStatusToStandardStatus(t *testing.T) { // } // } -func TestPrepareDSL(t *testing.T) { - t.Parallel() - var expectedResult Params - expectedResult.urlVals = map[string][]string{ - "before": {"1"}, - "limit": {"2"}, - } - var result Params - - result.urlVals = make(url.Values) - - result.PrepareDSL("before", "1", 2) - if fmt.Sprint(expectedResult) != fmt.Sprint(result) { - t.Errorf("CoinBasePro PrepareDSL(), Expected: %v, Received: %v", expectedResult, result) - } -} +*/ func TestPrepareDateString(t *testing.T) { t.Parallel() @@ -1953,61 +2504,44 @@ func TestPrepareDateString(t *testing.T) { result.urlVals = make(url.Values) - err := result.PrepareDateString(time.Unix(1, 1).UTC(), time.Unix(2, 2).UTC()) + labelStart := "start_date" + labelEnd := "end_date" + + err := result.PrepareDateString(time.Unix(1, 1).UTC(), time.Unix(2, 2).UTC(), labelStart, labelEnd) if err != nil { - t.Error("CoinBasePro PrepareDateString() error", err) + t.Error(err) } if fmt.Sprint(expectedResult) != fmt.Sprint(result) { - t.Errorf("CoinBasePro PrepareDateString(), Expected: %v, Received: %v", expectedResult, result) + t.Errorf(errExpectMismatch, expectedResult, result) } var newTime time.Time - err = result.PrepareDateString(newTime, newTime) + err = result.PrepareDateString(newTime, newTime, labelStart, labelEnd) if err != nil { - t.Error("CoinBasePro PrepareDateString() error", err) + t.Error(err) } - err = result.PrepareDateString(time.Unix(2, 2).UTC(), time.Unix(1, 1).UTC()) + err = result.PrepareDateString(time.Unix(2, 2).UTC(), time.Unix(1, 1).UTC(), labelStart, labelEnd) if err == nil { - t.Error("CoinBasePro PrepareDateString() expected StartAfterEnd error") + t.Error("expected startafterend error") } } -func TestPrepareProfIDAndProdID(t *testing.T) { +func TestPreparePagination(t *testing.T) { t.Parallel() var expectedResult Params - expectedResult.urlVals = map[string][]string{ - "profile_id": {"123"}, - "product_id": {"BTC-USD"}, - } + expectedResult.urlVals = map[string][]string{"limit": {"1"}, "order": {"asc"}, "starting_after": {"meow"}, + "ending_before": {"woof"}} + var result Params result.urlVals = make(url.Values) - result.PrepareProfIDAndProdID("123", "BTC-USD") - if fmt.Sprint(expectedResult) != fmt.Sprint(result) { - t.Errorf("CoinBasePro PrepareProfIDAndProdID(), Expected: %v, Received: %v", expectedResult, result) - } -} - -func TestPrepareAddAddress(t *testing.T) { - t.Parallel() + pagIn := PaginationInp{Limit: 1, OrderAscend: true, StartingAfter: "meow", EndingBefore: "woof"} - _, err := PrepareAddAddress("", "", "", "", "", false) - if err == nil { - t.Error("CoinBasePro PrepareAddAddress() Expected error for empty address") - } - _, err = PrepareAddAddress("", "test", "", "", "meow", false) - if err == nil { - t.Error("CoinBasePro PrepareAddAddress() Expected error for invalid vaspID") - } + result.PreparePagination(pagIn) - expectedResult := AddAddressRequest{"test", To{"woof", "meow"}, "whinny", false, "Coinbase"} - result, err := PrepareAddAddress("test", "woof", "meow", "whinny", "Coinbase", false) - if err != nil { - t.Error("CoinBasePro PrepareAddAddress() error", err) - } if fmt.Sprint(expectedResult) != fmt.Sprint(result) { - t.Errorf("CoinBasePro PrepareAddAddress(), Expected: %v, Received: %v", expectedResult, result) + t.Errorf(errExpectMismatch, expectedResult, result) } } @@ -2020,41 +2554,58 @@ func TestOrderbookHelper(t *testing.T) { _, err := OrderbookHelper(req, 2) if err == nil { - t.Error("CoinBasePro OrderbookHelper(), expected unable to type assert price error") + t.Error("expected unable to type assert price error") } req[0][0] = "egg" _, err = OrderbookHelper(req, 2) if err == nil { - t.Error("CoinBasePro OrderbookHelper(), expected invalid ParseFloat error") + t.Error("expected invalid ParseFloat error") } req[0][0] = "1.1" _, err = OrderbookHelper(req, 2) if err == nil { - t.Error("CoinBasePro OrderbookHelper(), expected unable to type assert amount error") + t.Error("expected unable to type assert amount error") } req[0][1] = "meow" _, err = OrderbookHelper(req, 2) if err == nil { - t.Error("CoinBasePro OrderbookHelper(), expected invalid ParseFloat error") + t.Error("expected invalid ParseFloat error") } req[0][1] = "2.2" _, err = OrderbookHelper(req, 2) if err == nil { - t.Error("CoinBasePro OrderbookHelper(), expected unable to type assert number of orders error") + t.Error("expected unable to type assert number of orders error") } req[0][2] = 3.3 _, err = OrderbookHelper(req, 2) if err != nil { - t.Error("CoinBasePro OrderbookHelper() error", err) + t.Error(err) } _, err = OrderbookHelper(req, 3) if err == nil { - t.Error("CoinBasePro OrderbookHelper(), expected unable to type assert order ID error") + t.Error("expected unable to type assert order ID error") } req[0][2] = "woof" _, err = OrderbookHelper(req, 3) if err != nil { - t.Error("CoinBasePro OrderbookHelper() error", err) + t.Error(err) + } +} + +func TestPrepareDSL(t *testing.T) { + t.Parallel() + var expectedResult Params + expectedResult.urlVals = map[string][]string{ + "before": {"1"}, + "limit": {"2"}, + } + var result Params + + result.urlVals = make(url.Values) + + result.PrepareDSL("before", "1", 2) + if fmt.Sprint(expectedResult) != fmt.Sprint(result) { + t.Errorf(errExpectMismatch, expectedResult, result) } } @@ -2110,3 +2661,71 @@ func TestOrderbookHelper(t *testing.T) { // } // log.Print(a) // } + +// // BenchmarkIfPrevar-8 537492 2807 ns/op 155 B/op 2 allocs/op +// // BenchmarkIfPrevar-8 534740 2674 ns/op 155 B/op 2 allocs/op +// func BenchmarkIfPrevar(b *testing.B) { +// var str1 string +// for x := 0; x < b.N; x++ { +// if x%2 == 0 { +// str1 = coinbaseDeposits +// } +// if x%2 == 1 { +// str1 = coinbaseWithdrawals +// } +// str2 := fmt.Sprintf("%s%s/%s/%s", coinbaseV2, coinbaseAccounts, "67e0eaec-07d7-54c4-a72c-2e92826897df", +// str1) +// _ = str2 +// } +// } + +// // BenchmarkIfDirSet-8 459709 2634 ns/op 139 B/op 1 allocs/op +// // BenchmarkIfDirSet-8 494604 2576 ns/op 139 B/op 1 allocs/op +// func BenchmarkIfDirSet(b *testing.B) { +// for x := 0; x < b.N; x++ { +// var str2 string +// if x%2 == 0 { +// str2 = fmt.Sprintf("%s%s/%s/%s", coinbaseV2, coinbaseAccounts, "67e0eaec-07d7-54c4-a72c-2e92826897df", +// coinbaseDeposits) +// } +// if x%2 == 1 { +// str2 = fmt.Sprintf("%s%s/%s/%s", coinbaseV2, coinbaseAccounts, "67e0eaec-07d7-54c4-a72c-2e92826897df", +// coinbaseWithdrawals) +// } +// _ = str2 +// } +// } + +// // BenchmarkSwitchPrevar-8 453200 2623 ns/op 155 B/op 2 allocs/op +// // BenchmarkSwitchPrevar-8 556477 2077 ns/op 156 B/op 3 allocs/op +// func BenchmarkSwitchPrevar(b *testing.B) { +// var str1 string +// for x := 0; x < b.N; x++ { +// switch x % 2 { +// case 0: +// str1 = coinbaseDeposits +// case 1: +// str1 = coinbaseWithdrawals +// } +// str2 := fmt.Sprintf("%s%s/%s/%s", coinbaseV2, coinbaseAccounts, "67e0eaec-07d7-54c4-a72c-2e92826897df", +// str1) +// _ = str2 +// } +// } + +// // BenchmarkSwitchDirSet-8 432816 2371 ns/op 139 B/op 1 allocs/op +// // BenchmarkSwitchDirSet-8 544873 2071 ns/op 140 B/op 2 allocs/op +// func BenchmarkSwitchDirSet(b *testing.B) { +// for x := 0; x < b.N; x++ { +// var str2 string +// switch x % 2 { +// case 0: +// str2 = fmt.Sprintf("%s%s/%s/%s", coinbaseV2, coinbaseAccounts, "67e0eaec-07d7-54c4-a72c-2e92826897df", +// coinbaseDeposits) +// case 1: +// str2 = fmt.Sprintf("%s%s/%s/%s", coinbaseV2, coinbaseAccounts, "67e0eaec-07d7-54c4-a72c-2e92826897df", +// coinbaseWithdrawals) +// } +// _ = str2 +// } +// } diff --git a/exchanges/coinbasepro/coinbasepro_types.go b/exchanges/coinbasepro/coinbasepro_types.go index 1831748fa4a..733c989a4d2 100644 --- a/exchanges/coinbasepro/coinbasepro_types.go +++ b/exchanges/coinbasepro/coinbasepro_types.go @@ -11,37 +11,793 @@ import ( "github.com/thrasher-corp/gocryptotrader/currency" ) +// ValCur is a sub-struct used in the type Account +type ValCur struct { + Value float64 `json:"value,string"` + Currency string `json:"currency"` +} + +// Account holds details for a trading account, returned by GetAccountByID and used as +// a sub-struct in the type AllAccountsResponse +type Account struct { + UUID string `json:"uuid"` + Name string `json:"name"` + Currency string `json:"currency"` + AvailableBalance ValCur `json:"available_balance"` + Default bool `json:"default"` + Active bool `json:"active"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + DeletedAt time.Time `json:"deleted_at"` + Type string `json:"type"` + Ready bool `json:"ready"` + Hold ValCur `json:"hold"` +} + +// AllAccountsResponse holds many Account structs, as well as pagination information, +// returned by GetAllAccounts +type AllAccountsResponse struct { + Accounts []Account `json:"accounts"` + HasNext bool `json:"has_next"` + Cursor string `json:"cursor"` + Size uint8 `json:"size"` +} + +// OneAccountResponse is a temporary struct used for unmarshalling in GetAccountByID +type OneAccountResponse struct { + Account Account `json:"account"` +} + +// PriSiz is a sub-struct used in the type BestBidAsk +type PriSiz struct { + Price float64 `json:"price,string"` + Size float64 `json:"size,string"` +} + +// ProductBook holds bid and ask prices for a particular product, returned by GetProductBook +// and used as a sub-struct in the type BestBidAsk +type ProductBook struct { + ProductID string `json:"product_id"` + Bids []PriSiz `json:"bids"` + Asks []PriSiz `json:"asks"` + Time time.Time `json:"time"` +} + +// BestBidAsk holds the best bid and ask prices for a variety of products, returned by +// GetBestBidAsk +type BestBidAsk struct { + Pricebooks []ProductBook `json:"pricebooks"` +} + +// ProductBook holds bids and asks for a particular product, returned by GetProductBook +type ProductBookResponse struct { + Pricebook ProductBook `json:"pricebook"` +} + // Product holds product information, returned by GetAllProducts and GetProductByID type Product struct { - ID string `json:"id"` - BaseCurrency string `json:"base_currency"` - QuoteCurrency string `json:"quote_currency"` - QuoteIncrement float64 `json:"quote_increment,string"` + ID string `json:"product_id"` + Price float64 `json:"price,string"` + PricePercentageChange24H float64 `json:"price_percentage_change_24h,string"` + Volume24H float64 `json:"volume_24h,string"` + VolumePercentageChange24H float64 `json:"volume_percentage_change_24h,string"` BaseIncrement float64 `json:"base_increment,string"` - DisplayName string `json:"display_name"` - MinimumMarketFunds float64 `json:"min_market_funds,string"` - MarginEnabled bool `json:"margin_enabled"` - PostOnly bool `json:"post_only"` - LimitOnly bool `json:"limit_only"` - CancelOnly bool `json:"cancel_only"` + QuoteIncrement float64 `json:"quote_increment,string"` + QuoteMinSize float64 `json:"quote_min_size,string"` + QuoteMaxSize float64 `json:"quote_max_size,string"` + BaseMinSize float64 `json:"base_min_size,string"` + BaseMaxSize float64 `json:"base_max_size,string"` + BaseName string `json:"base_name"` + QuoteName string `json:"quote_name"` + Watched bool `json:"watched"` + IsDisabled bool `json:"is_disabled"` + New bool `json:"new"` Status string `json:"status"` - StatusMessage string `json:"status_message"` + CancelOnly bool `json:"cancel_only"` + LimitOnly bool `json:"limit_only"` + PostOnly bool `json:"post_only"` TradingDisabled bool `json:"trading_disabled"` - ForeignExchangeStableCoin bool `json:"fx_stablecoin"` - MaxSlippagePercentage float64 `json:"max_slippage_percentage,string"` AuctionMode bool `json:"auction_mode"` - HighBidLimitPercentage string `json:"high_bid_limit_percentage"` + ProductType string `json:"product_type"` + QuoteCurrencyID string `json:"quote_currency_id"` + BaseCurrencyID string `json:"base_currency_id"` + FCMTradingSessionDetails struct { + IsSessionOpen bool `json:"is_session_open"` + OpenTime time.Time `json:"open_time"` + CloseTime time.Time `json:"close_time"` + } `json:"fcm_trading_session_details"` + MidMarketPrice convert.StringToFloat64 `json:"mid_market_price"` + Alias string `json:"alias"` + AliasTo []string `json:"alias_to"` + BaseDisplaySymbol string `json:"base_display_symbol"` + QuoteDisplaySymbol string `json:"quote_display_symbol"` + ViewOnly bool `json:"view_only"` + PriceIncrement float64 `json:"price_increment,string"` + FutureProductDetails struct { + Venue string `json:"venue"` + ContractCode string `json:"contract_code"` + ContractExpiry string `json:"contract_expiry"` + ContractSize float64 `json:"contract_size,string"` + ContractRootUnit string `json:"contract_root_unit"` + GroupDescription string `json:"group_description"` + ContractExpiryTimezone string `json:"contract_expiry_timezone"` + GroupShortDescription string `json:"group_short_description"` + RiskManagedBy string `json:"risk_managed_by"` + ContractExpiryType string `json:"contract_expiry_type"` + PerpetualDetails struct { + OpenInterest float64 `json:"open_interest,string"` + FundingRate float64 `json:"funding_rate,string"` + FundingTime time.Time `json:"funding_time"` + } `json:"perpetual_details"` + ContractDisplayName string `json:"contract_display_name"` + } `json:"future_product_details"` +} + +// AllProducts holds information on a lot of available currency pairs, returned by +// GetAllProducts +type AllProducts struct { + Products []Product `json:"products"` + NumProducts int32 `json:"num_products"` +} + +// UnixTimestamp is a type used to unmarshal unix timestamps returned from +// the exchange +type UnixTimestamp time.Time + +func (t *UnixTimestamp) UnmarshalJSON(b []byte) error { + var timestampStr string + err := json.Unmarshal(b, ×tampStr) + if err != nil { + return err + } + timestamp, err := strconv.ParseInt(timestampStr, 10, 64) + if err != nil { + return err + } + *t = UnixTimestamp(time.Unix(timestamp, 0)) + return nil +} + +func (t UnixTimestamp) String() string { + return time.Time(t).String() +} + +// History holds historic rate information, returned by 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"` } // Ticker holds basic ticker information, returned by GetTicker type Ticker struct { - Ask float64 `json:"ask,string"` - Bid float64 `json:"bid,string"` - Volume float64 `json:"volume,string"` - TradeID int32 `json:"trade_id"` - Price float64 `json:"price,string"` - Size float64 `json:"size,string"` - Time time.Time `json:"time"` + Trades []struct { + TradeID string `json:"trade_id"` + ProductID string `json:"product_id"` + Price float64 `json:"price,string"` + Size float64 `json:"size,string"` + Time time.Time `json:"time"` + Side string `json:"side"` + Bid convert.StringToFloat64 `json:"bid"` + Ask convert.StringToFloat64 `json:"ask"` + } `json:"trades"` + BestBid float64 `json:"best_bid,string"` + BestAsk float64 `json:"best_ask,string"` +} + +// MarketMarketIOC is a sub-struct used in the type OrderConfiguration +type MarketMarketIOC struct { + QuoteSize string `json:"quote_size,omitempty"` + BaseSize string `json:"base_size,omitempty"` +} + +// LimitLimitGTC is a sub-struct used in the type OrderConfiguration +type LimitLimitGTC struct { + BaseSize string `json:"base_size"` + LimitPrice string `json:"limit_price"` + PostOnly bool `json:"post_only"` +} + +// LimitLimitGTD is a sub-struct used in the type OrderConfiguration +type LimitLimitGTD struct { + BaseSize string `json:"base_size"` + LimitPrice string `json:"limit_price"` + EndTime time.Time `json:"end_time"` + PostOnly bool `json:"post_only"` +} + +// StopLimitStopLimitGTC is a sub-struct used in the type OrderConfiguration +type StopLimitStopLimitGTC struct { + BaseSize string `json:"base_size"` + LimitPrice string `json:"limit_price"` + StopPrice string `json:"stop_price"` + StopDirection string `json:"stop_direction"` +} + +// StopLimitStopLimitGTD is a sub-struct used in the type OrderConfiguration +type StopLimitStopLimitGTD struct { + BaseSize string `json:"base_size"` + LimitPrice string `json:"limit_price"` + StopPrice string `json:"stop_price"` + EndTime time.Time `json:"end_time"` + StopDirection string `json:"stop_direction"` +} + +// OrderConfiguration is a struct used in the formation of requests for PlaceOrder +type OrderConfiguration struct { + MarketMarketIOC *MarketMarketIOC `json:"market_market_ioc,omitempty"` + LimitLimitGTC *LimitLimitGTC `json:"limit_limit_gtc,omitempty"` + LimitLimitGTD *LimitLimitGTD `json:"limit_limit_gtd,omitempty"` + StopLimitStopLimitGTC *StopLimitStopLimitGTC `json:"stop_limit_stop_limit_gtc,omitempty"` + StopLimitStopLimitGTD *StopLimitStopLimitGTD `json:"stop_limit_stop_limit_gtd,omitempty"` +} + +// PlaceOrderResp contains information on an order, returned by PlaceOrder +type PlaceOrderResp struct { + Success bool `json:"success"` + FailureReason string `json:"failure_reason"` + OrderID string `json:"order_id"` + SuccessResponse struct { + OrderID string `json:"order_id"` + ProductID string `json:"product_id"` + Side string `json:"side"` + ClientOrderID string `json:"client_oid"` + } `json:"success_response"` + ErrorResponse struct { + Error string `json:"error"` + Message string `json:"message"` + ErrorDetails string `json:"error_details"` + PreviewFailureReason string `json:"preview_failure_reason"` + NewOrderFailureReason string `json:"new_order_failure_reason"` + } `json:"error_response"` + OrderConfiguration OrderConfiguration `json:"order_configuration"` +} + +// CancelOrderResp contains information on attempted order cancellations, returned by +// CancelOrders +type CancelOrderResp struct { + Results []struct { + Success bool `json:"success"` + FailureReason string `json:"failure_reason"` + OrderID string `json:"order_id"` + } `json:"results"` +} + +// EditOrderPreviewResp contains information on the effects of editing an order, +// returned by EditOrderPreview +type EditOrderPreviewResp struct { + Slippage float64 `json:"slippage,string"` + OrderTotal float64 `json:"order_total,string"` + CommissionTotal float64 `json:"commission_total,string"` + QuoteSize float64 `json:"quote_size,string"` + BaseSize float64 `json:"base_size,string"` + BestBid float64 `json:"best_bid,string"` + BestAsk float64 `json:"best_ask,string"` + AverageFilledPrice float64 `json:"average_filled_price,string"` +} + +// GetOrderResponse contains information on an order, returned by GetOrderByID +// and used in GetAllOrdersResp +type GetOrderResponse struct { + OrderID string `json:"order_id"` + ProductID string `json:"product_id"` + UserID string `json:"user_id"` + OrderConfiguration OrderConfiguration `json:"order_configuration"` + Side string `json:"side"` + ClientOID string `json:"client_order_id"` + Status string `json:"status"` + TimeInForce string `json:"time_in_force"` + CreatedTime time.Time `json:"created_time"` + CompletionPercentage float64 `json:"completion_percentage,string"` + FilledSize float64 `json:"filled_size,string"` + AverageFilledPrice float64 `json:"average_filled_price,string"` + Fee float64 `json:"fee,string"` + NumberOfFills int64 `json:"num_fills,string"` + FilledValue float64 `json:"filled_value,string"` + PendingCancel bool `json:"pending_cancel"` + SizeInQuote bool `json:"size_in_quote"` + TotalFees float64 `json:"total_fees,string"` + SizeInclusiveOfFees bool `json:"size_inclusive_of_fees"` + TotalValueAfterFees float64 `json:"total_value_after_fees,string"` + TriggerStatus string `json:"trigger_status"` + OrderType string `json:"order_type"` + RejectReason string `json:"reject_reason"` + Settled bool `json:"settled"` + ProductType string `json:"product_type"` + RejectMessage string `json:"reject_message"` + CancelMessage string `json:"cancel_message"` + OrderPlacementSource string `json:"order_placement_source"` + OutstandingHoldAmount float64 `json:"outstanding_hold_amount,string"` + IsLiquidation bool `json:"is_liquidation"` + LastFillTime time.Time `json:"last_fill_time"` + EditHistory []struct { + Price float64 `json:"price,string"` + Size float64 `json:"size,string"` + ReplaceAcceptTimestamp time.Time `json:"replace_accept_timestamp"` + } `json:"edit_history"` +} + +// FillResponse contains fill information, returned by GetFills +type FillResponse struct { + Fills []struct { + EntryID string `json:"entry_id"` + TradeID string `json:"trade_id"` + OrderID string `json:"order_id"` + TradeTime time.Time `json:"trade_time"` + TradeType string `json:"trade_type"` + Price float64 `json:"price,string"` + Size float64 `json:"size,string"` + Commission float64 `json:"commission,string"` + ProductID string `json:"product_id"` + SequenceTimestamp time.Time `json:"sequence_timestamp"` + LiquidityIndicator string `json:"liquidity_indicator"` + SizeInQuote bool `json:"size_in_quote"` + UserID string `json:"user_id"` + Side string `json:"side"` + } `json:"fills"` + Cursor string `json:"cursor"` +} + +// TransactionSummary contains a summary of transaction fees, volume, and the like. Returned +// by GetTransactionSummary +type TransactionSummary struct { + TotalVolume float64 `json:"total_volume"` + TotalFees float64 `json:"total_fees"` + FeeTier struct { + PricingTier float64 `json:"pricing_tier,string"` + USDFrom float64 `json:"usd_from,string"` + USDTo float64 `json:"usd_to,string"` + TakerFeeRate float64 `json:"taker_fee_rate,string"` + MakerFeeRate float64 `json:"maker_fee_rate,string"` + } + MarginRate struct { + Value float64 `json:"value,string"` + } + GoodsAndServicesTax struct { + Rate float64 `json:"rate,string"` + Type string `json:"type"` + } + AdvancedTradeOnlyVolume float64 `json:"advanced_trade_only_volume"` + AdvancedTradeOnlyFees float64 `json:"advanced_trade_only_fees"` + CoinbaseProVolume float64 `json:"coinbase_pro_volume"` + CoinbaseProFees float64 `json:"coinbase_pro_fees"` +} + +// GetAllOrdersResp contains information on a lot of orders, returned by GetAllOrders +type GetAllOrdersResp struct { + Orders []GetOrderResponse `json:"orders"` + Sequence int64 `json:"sequence,string"` + HasNext bool `json:"has_next"` + Cursor string `json:"cursor"` +} + +// IDResource holds an ID, resource type, and associated data, used in ListNotificationsResponse, +// TransactionData +type IDResource struct { + ID string `json:"id"` + Resource string `json:"resource"` + ResourcePath string `json:"resource_path"` + Email string `json:"email"` +} + +// PaginationResp holds pagination information, used in ListNotificationsResponse, +// GetAllWalletsResponse, +type PaginationResp struct { + EndingBefore string `json:"ending_before"` + StartingAfter string `json:"starting_after"` + PreviousEndingBefore string `json:"previous_ending_before"` + NextStartingAfter string `json:"next_starting_after"` + Limit uint8 `json:"limit"` + Order string `json:"order"` + PreviousURI string `json:"previous_uri"` + NextURI string `json:"next_uri"` +} + +// PaginationInp holds information needed to engage in pagination with Sign in With +// Coinbase. Used in ListNotifications, GetAllWallets, GetAllAddresses, +type PaginationInp struct { + Limit uint8 + OrderAscend bool + StartingAfter string + EndingBefore string +} + +// ListNotificationsResponse holds information on notifications that the user is subscribed +// to. Returned by ListNotifications +type ListNotificationsResponse struct { + Pagination PaginationResp `json:"pagination"` + Data []struct { + ID string `json:"id"` + Type string `json:"type"` + Data struct { + ID string `json:"id"` + Status string `json:"status"` + PaymentMethod IDResource `json:"payment_method"` + Transaction IDResource `json:"transaction"` + Amount struct { + Amount float64 `json:"amount,string"` + Currency string `json:"currency"` + } + Total struct { + Amount float64 `json:"amount,string"` + Currency string `json:"currency"` + } + Subtotal struct { + Amount float64 `json:"amount,string"` + Currency string `json:"currency"` + } + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + Resource string `json:"resource"` + ResourcePath string `json:"resource_path"` + Committed bool `json:"committed"` + Instant bool `json:"instant"` + Fees []struct { + Type string `json:"type"` + Amount struct { + Amount float64 `json:"amount,string"` + Currency string `json:"currency"` + } `json:"amount"` + } `json:"fees"` + PayoutAt time.Time `json:"payout_at"` + } `json:"data"` + User IDResource `json:"user"` + Account IDResource `json:"account"` + DeliveryAttempts int32 `json:"delivery_attempts"` + CreatedAt time.Time `json:"created_at"` + Resource string `json:"resource"` + ResourcePath string `json:"resource_path"` + } `json:"data"` +} + +// UserResponse holds information on a user, returned by GetUseByID and GetCurrentUser +type UserResponse struct { + Data struct { + ID string `json:"id"` + Name string `json:"name"` + Username string `json:"username"` + ProfileLocation string `json:"profile_location"` + ProfileBio string `json:"profile_bio"` + ProfileURL string `json:"profile_url"` + AvatarURL string `json:"avatar_url"` + Resource string `json:"resource"` + ResourcePath string `json:"resource_path"` + LegacyID string `json:"legacy_id"` + TimeZone string `json:"time_zone"` + NativeCurrency string `json:"native_currency"` + BitcoinUnit string `json:"bitcoin_unit"` + State string `json:"state"` + Country struct { + Code string `json:"code"` + Name string `json:"name"` + IsInEurope bool `json:"is_in_europe"` + } `json:"country"` + Nationality struct { + Code string `json:"code"` + Name string `json:"name"` + } `json:"nationality"` + RegionSupportsFiatTransfers bool `json:"region_supports_fiat_transfers"` + RegionSupportsCryptoToCryptoTransfers bool `json:"region_supports_crypto_to_crypto_transfers"` + CreatedAt time.Time `json:"created_at"` + SupportsRewards bool `json:"supports_rewards"` + Tiers struct { + CompletedDescription string `json:"completed_description"` + UpgradeButtonText string `json:"upgrade_button_text"` + Header string `json:"header"` + Body string `json:"body"` + } `json:"tiers"` + ReferralMoney struct { + Amount float64 `json:"amount,string"` + Currency string `json:"currency"` + CurrencySymbol string `json:"currency_symbol"` + ReferralThreshold float64 `json:"referral_threshold,string"` + } `json:"referral_money"` + HasBlockingBuyRestrictions bool `json:"has_blocking_buy_restrictions"` + HasMadeAPurchase bool `json:"has_made_a_purchase"` + HasBuyDepositPaymentMethods bool `json:"has_buy_deposit_payment_methods"` + HasUnverifiedBuyDepositPaymentMethods bool `json:"has_unverified_buy_deposit_payment_methods"` + NeedsKYCRemediation bool `json:"needs_kyc_remediation"` + ShowInstantAchUx bool `json:"show_instant_ach_ux"` + UserType string `json:"user_type"` + Email string `json:"email"` + } `json:"data"` +} + +// AuthResponse holds authentication information, returned by GetAuthInfo +type AuthResponse struct { + Data struct { + Method string `json:"method"` + Scopes []string `json:"scopes"` + OAuthMeta interface{} + } `json:"data"` +} + +// WalletData holds wallet information, returned by in GenWalletResponse and GetAllWalletsResponse +type WalletData struct { + ID string `json:"id"` + Name string `json:"name"` + Primary bool `json:"primary"` + Type string `json:"type"` + Currency struct { + Code string `json:"code"` + Name string `json:"name"` + Color string `json:"color"` + SortIndex int32 `json:"sort_index"` + Exponent int32 `json:"exponent"` + Type string `json:"type"` + AddressRegex string `json:"address_regex"` + AssetID string `json:"asset_id"` + DestinationTagName string `json:"destination_tag_name"` + DestinationTagRegex string `json:"destination_tag_regex"` + Slug string `json:"slug"` + } `json:"currency"` + Balance struct { + Amount float64 `json:"amount,string"` + Currency string `json:"currency"` + } `json:"balance"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + Resource string `json:"resource"` + ResourcePath string `json:"resource_path"` + AllowDeposits bool `json:"allow_deposits"` + AllowWithdrawals bool `json:"allow_withdrawals"` +} + +// GenWalletResponse holds information on a single wallet, returned by CreateWallet, +// GetWalletByID, and UpdateWalletName +type GenWalletResponse struct { + Data WalletData `json:"data"` +} + +// GetAllWalletsResponse holds information on many wallets, returned by GetAllWallets +type GetAllWalletsResponse struct { + Pagination PaginationResp `json:"pagination"` + Data []WalletData `json:"data"` +} + +// AddressData holds address information, used in GenAddrResponse and GetAllAddrResponse +type AddressData struct { + ID string `json:"id"` + Address string `json:"address"` + AddressInfo struct { + Address string `json:"address"` + DestinationTag string `json:"destination_tag"` + } `json:"address_info"` + Name string `json:"name"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + Network string `json:"network"` + URIScheme string `json:"uri_scheme"` + Resource string `json:"resource"` + ResourcePath string `json:"resource_path"` + Warnings []struct { + Type string `json:"type"` + Title string `json:"title"` + Details string `json:"details"` + ImageURL string `json:"image_url"` + Options []struct { + Text string `json:"text"` + Style string `json:"style"` + ID string `json:"id"` + } `json:"options"` + } `json:"warnings"` + QRCodeImageURL string `json:"qr_code_image_url"` + AddressLabel string `json:"address_label"` + DefaultReceive bool `json:"default_receive"` + DestinationTag string `json:"destination_tag"` + DepositURI string `json:"deposit_uri"` + CallbackURL string `json:"callback_url"` + ShareAddressCopy struct { + Line1 string `json:"line1"` + Line2 string `json:"line2"` + } `json:"share_address_copy"` + ReceiveSubtitle string `json:"receive_subtitle"` + InlineWarning struct { + Text string `json:"text"` + Tooltip struct { + Title string `json:"title"` + Subtitle string `json:"subtitle"` + } `json:"tooltip"` + } `json:"inline_warning"` +} + +// GenAddrResponse holds information on a generated address, returned by CreateAddress and +// GetAddressByID. Used in GetAllAddrResponse +type GenAddrResponse struct { + Data AddressData `json:"data"` +} + +// GetAllAddrResponse holds information on many addresses, returned by GetAllAddresses +type GetAllAddrResponse struct { + Pagination PaginationResp `json:"pagination"` + Data []AddressData `json:"data"` +} + +// AmCur is a sub-type used in TransactionData, LimitStruct +type AmCur struct { + Amount float64 `json:"amount,string"` + Currency string `json:"currency"` +} + +// TransactionData is a sub-type that holds information on a transaction. Used in +// ManyTransactionsResp +type TransactionData struct { + ID string `json:"id"` + Type string `json:"type"` + Status string `json:"status"` + Amount AmCur `json:"amount"` + NativeAmount AmCur `json:"native_amount"` + Description string `json:"description"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + Resource string `json:"resource"` + ResourcePath string `json:"resource_path"` + InstantExchange bool `json:"instant_exchange"` + Buy IDResource `json:"buy"` + AdvancedTradeFill struct { + FillPrice float64 `json:"fill_price,string"` + ProductID string `json:"product_id"` + OrderID string `json:"order_id"` + Commission float64 `json:"commission,string"` + OrderSide string `json:"order_side"` + } `json:"advanced_trade_fill"` + Details struct { + Title string `json:"title"` + Subtitle string `json:"subtitle"` + // SubsidebarLabel string `json:"subsidebar_label"` + // Header string `json:"header"` + // Health string `json:"health"` + } `json:"details"` + Network struct { + Status string `json:"status"` + // StatusDescription string `json:"status_description"` + Hash string `json:"hash"` + Name string `json:"name"` + } `json:"network"` + To IDResource `json:"to"` + From IDResource `json:"from"` + Address struct { + } `json:"address"` + Application struct { + } `json:"application"` +} + +// ManyTransactionsResp holds information on many transactions. Returned by +// GetAddressTransactions, ListTransactions +type ManyTransactionsResp struct { + Pagination PaginationResp `json:"pagination"` + Data []TransactionData `json:"data"` +} + +// GenTransactionResp holds information on one transaction. Returned by SendMoney, +type GenTransactionResp struct { + Data TransactionData `json:"data"` +} + +// DeposWithdrData is a sub-type that holds information on a deposit/withdrawal. Used in +// GenDeposWithdrResp and ManyDeposWithdrResp +type DeposWithdrData struct { + ID string `json:"id"` + Status string `json:"status"` + PaymentMethod IDResource `json:"payment_method"` + Transaction IDResource `json:"transaction"` + Amount AmCur `json:"amount"` + Subtotal AmCur `json:"subtotal"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + Resource string `json:"resource"` + ResourcePath string `json:"resource_path"` + Committed bool `json:"committed"` + Fee AmCur `json:"fee"` + PayoutAt time.Time `json:"payout_at"` +} + +// GenDeposWithdrResp holds information on a deposit. Returned by DepositFunds, CommitDeposit, +// and GetDepositByID +type GenDeposWithdrResp struct { + Data DeposWithdrData `json:"data"` +} + +// ManyDeposWithdrResp holds information on many deposits. Returned by GetAllDeposits +type ManyDeposWithdrResp struct { + Pagination PaginationResp `json:"pagination"` + Data []DeposWithdrData `json:"data"` +} + +// PaymentMethodData is a sub-type that holds information on a payment method. Used in +// GenPaymentMethodResp and GetAllPaymentMethodsResp +type PaymentMethodData struct { + ID string `json:"id"` + Type string `json:"type"` + Name string `json:"name"` + Currency string `json:"currency"` + PrimaryBuy bool `json:"primary_buy"` + PrimarySell bool `json:"primary_sell"` + AllowBuy bool `json:"allow_buy"` + AllowSell bool `json:"allow_sell"` + AllowDeposit bool `json:"allow_deposit"` + AllowWithdraw bool `json:"allow_withdraw"` + InstantBuy bool `json:"instant_buy"` + InstantSell bool `json:"instant_sell"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + Resource string `json:"resource"` + ResourcePath string `json:"resource_path"` + Limits struct { + Type string `json:"type"` + Name string `json:"name"` + } `json:"limits"` + FiatAccount IDResource `json:"fiat_account"` + Verified bool `json:"verified"` + MinimumPurchaseAmount AmCur `json:"minimum_purchase_amount"` +} + +// GetAllPaymentMethodsResp holds information on many payment methods. Returned by +// GetAllPaymentMethods +type GetAllPaymentMethodsResp struct { + Pagination PaginationResp `json:"pagination"` + Data []PaymentMethodData `json:"data"` +} + +// GenPaymentMethodResp holds information on a payment method. Returned by +// GetPaymentMethodByID +type GenPaymentMethodResp struct { + Data PaymentMethodData `json:"data"` +} + +// GetFiatCurrenciesResp holds information on fiat currencies. Returned by +// GetFiatCurrencies +type GetFiatCurrenciesResp struct { + Data []struct { + ID string `json:"id"` + Name string `json:"name"` + MinSize float64 `json:"min_size,string"` + } +} + +// GetCryptocurrenciesResp holds information on cryptocurrencies. Returned by +// 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"` + } +} + +// GetExchangeRatesResp holds information on exchange rates. Returned by GetExchangeRates +type GetExchangeRatesResp struct { + Data struct { + Currency string `json:"currency"` + Rates map[string]convert.StringToFloat64 `json:"rates"` + } `json:"data"` +} + +// GetPriceResp holds information on a price. Returned by GetPrice +type GetPriceResp struct { + Data struct { + Amount float64 `json:"amount,string"` + Base string `json:"base"` + Currency string `json:"currency"` + } `json:"data"` +} + +// ServerTime holds current requested server time information +type ServerTime struct { + Data struct { + ISO time.Time `json:"iso"` + Epoch uint64 `json:"epoch"` + } `json:"data"` } // Trade holds executed trade information, returned by GetTrades @@ -53,16 +809,6 @@ type Trade struct { Side string `json:"side"` } -// History holds historic rate information, returned by GetHistoricRates -type History struct { - Time time.Time - Low float64 - High float64 - Open float64 - Close float64 - Volume float64 -} - // Stats holds 30 day and 24 hr data for a currency pair, returned by GetStats type Stats struct { Open float64 `json:"open,string"` @@ -108,26 +854,7 @@ type Currency struct { MaxWithdrawalAmount float64 `json:"max_withdrawal_amount"` NetworkConfirmations int32 `json:"network_confirmations"` ProcessingTimeSeconds int32 `json:"processing_time_seconds"` - } -} - -// ServerTime holds current requested server time information -// type ServerTime struct { -// ISO time.Time `json:"iso"` -// Epoch float64 `json:"epoch"` -// } - -// AccountResponse holds details for a trading account, returned by GetAllAccounts -// and GetAccountByID -type AccountResponse struct { - ID string `json:"id"` - Currency string `json:"currency"` - Balance float64 `json:"balance,string"` - Hold float64 `json:"hold,string"` - Available float64 `json:"available,string"` - ProfileID string `json:"profile_id"` - TradingEnabled bool `json:"trading_enabled"` - PendingDeposit string `json:"pending_deposit"` + } `json:"supported_networks"` } // AccountLedgerResponse holds account history information, returned by GetAccountLedger @@ -156,37 +883,6 @@ type AccountHolds struct { Reference string `json:"ref"` } -// GeneralizedOrderResponse contains information on an order, returned by GetAllOrders, -// PlaceOrder, and GetOrderByID -type GeneralizedOrderResponse struct { - ID string `json:"id"` - Price float64 `json:"price,string"` - Size float64 `json:"size,string"` - ProductID string `json:"product_id"` - ProfileID string `json:"profile_id"` - Side string `json:"side"` - Funds float64 `json:"funds,string"` - SpecifiedFunds float64 `json:"specified_funds,string"` - Type string `json:"type"` - TimeInForce string `json:"time_in_force"` - ExpireTime time.Time `json:"expire_time"` - PostOnly bool `json:"post_only"` - CreatedAt time.Time `json:"created_at"` - DoneAt time.Time `json:"done_at"` - DoneReason string `json:"done_reason"` - RejectReason string `json:"reject_reason"` - FillFees float64 `json:"fill_fees,string"` - FilledSize float64 `json:"filled_size,string"` - ExecutedValue float64 `json:"executed_value,string"` - Status string `json:"status"` - Settled bool `json:"settled"` - Stop string `json:"stop"` - StopPrice float64 `json:"stop_price,string"` - FundingAmount float64 `json:"funding_amount,string"` - ClientOID string `json:"client_oid"` - MarketType string `json:"market_type"` -} - // Funding holds funding data // type Funding struct { // ID string `json:"id"` @@ -266,12 +962,6 @@ type GeneralizedOrderResponse struct { // DefaultAmount float64 `json:"default_amount,string"` // } -// AmCur is a sub-type used in LimitStruct -type AmCur struct { - Amount float64 `json:"amount,string"` - Currency string `json:"currency"` -} - // LimitStruct is a sub-type used in PaymentMethod type LimitStruct struct { PeriodInDays int `json:"period_in_days"` @@ -553,25 +1243,6 @@ type OrderbookFinalResponse struct { Time time.Time `json:"time"` } -// FillResponse contains fill information, returned by GetFills -type FillResponse struct { - TradeID int32 `json:"trade_id"` - ProductID string `json:"product_id"` - OrderID string `json:"order_id"` - UserID string `json:"user_id"` - ProfileID string `json:"profile_id"` - Liquidity string `json:"liquidity"` - Price float64 `json:"price,string"` - Size float64 `json:"size,string"` - Fee float64 `json:"fee,string"` - CreatedAt time.Time `json:"created_at"` - Side string `json:"side"` - Settled bool `json:"settled"` - USDVolume float64 `json:"usd_volume,string"` - MarketType string `json:"market_type"` - FundingCurrency string `json:"funding_currency"` -} - // WebsocketSubscribe takes in subscription information type WebsocketSubscribe struct { Type string `json:"type"` @@ -1017,10 +1688,3 @@ type StakeWrap struct { type WrappedAssetConversionRate struct { Amount float64 `json:"amount,string"` } - -// ReturnedPaginationHeaders contains the pagination headers returned by the exchange, -// returned by GetHolds, GetAccountLedger, -type ReturnedPaginationHeaders struct { - before string - after string -} diff --git a/exchanges/coinbasepro/coinbasepro_wrapper.go b/exchanges/coinbasepro/coinbasepro_wrapper.go index 978c1b2b168..06a83dab5ae 100644 --- a/exchanges/coinbasepro/coinbasepro_wrapper.go +++ b/exchanges/coinbasepro/coinbasepro_wrapper.go @@ -61,7 +61,7 @@ func (c *CoinbasePro) SetDefaults() { c.API.CredentialsValidator.RequiresKey = true c.API.CredentialsValidator.RequiresSecret = true // c.API.CredentialsValidator.RequiresClientID = true - c.API.CredentialsValidator.RequiresBase64DecodeSecret = true + c.API.CredentialsValidator.RequiresBase64DecodeSecret = false requestFmt := ¤cy.PairFormat{Delimiter: currency.DashDelimiter, Uppercase: true} configFmt := ¤cy.PairFormat{Delimiter: currency.DashDelimiter, Uppercase: true} @@ -139,7 +139,7 @@ func (c *CoinbasePro) SetDefaults() { } c.API.Endpoints = c.NewEndpoints() err = c.API.Endpoints.SetDefaultEndpoints(map[exchange.URL]string{ - exchange.RestSpot: coinbaseproAPIURL, + exchange.RestSpot: coinbaseAPIURL, exchange.RestSandbox: coinbaseproSandboxAPIURL, exchange.WebsocketSpot: coinbaseproWebsocketURL, }) @@ -380,37 +380,37 @@ func (c *CoinbasePro) UpdateTickers(_ context.Context, _ asset.Item) error { // UpdateTicker updates and returns the ticker for a currency pair func (c *CoinbasePro) UpdateTicker(ctx context.Context, p currency.Pair, a asset.Item) (*ticker.Price, error) { - fPair, err := c.FormatExchangeCurrency(p, a) - if err != nil { - return nil, err - } + // fPair, err := c.FormatExchangeCurrency(p, a) + // if err != nil { + // return nil, err + // } - tick, err := c.GetTicker(ctx, fPair.String()) - if err != nil { - return nil, err - } - stats, err := c.GetStats(ctx, fPair.String()) - if err != nil { - return nil, err - } + // tick, err := c.GetTicker(ctx, fPair.String()) + // if err != nil { + // return nil, err + // } + // stats, err := c.GetStats(ctx, fPair.String()) + // if err != nil { + // return nil, err + // } - tickerPrice := &ticker.Price{ - Last: stats.Last, - High: stats.High, - Low: stats.Low, - Bid: tick.Bid, - Ask: tick.Ask, - Volume: tick.Volume, - Open: stats.Open, - Pair: p, - LastUpdated: tick.Time, - ExchangeName: c.Name, - AssetType: a} - - err = ticker.ProcessTicker(tickerPrice) - if err != nil { - return tickerPrice, err - } + // tickerPrice := &ticker.Price{ + // Last: stats.Last, + // High: stats.High, + // Low: stats.Low, + // Bid: tick.Bid, + // Ask: tick.Ask, + // Volume: tick.Volume, + // Open: stats.Open, + // Pair: p, + // LastUpdated: tick.Time, + // ExchangeName: c.Name, + // AssetType: a} + + // err = ticker.ProcessTicker(tickerPrice) + // if err != nil { + // return tickerPrice, err + // } return ticker.GetTicker(c.Name, p, a) } @@ -770,7 +770,7 @@ func (c *CoinbasePro) GetActiveOrders(ctx context.Context, req *order.MultiOrder if err != nil { return nil, err } - var respOrders []GeneralizedOrderResponse + var respOrders []GetOrderResponse // var fPair currency.Pair // for i := range req.Pairs { // // fPair, err = c.FormatExchangeCurrency(req.Pairs[i], asset.Spot) @@ -778,7 +778,7 @@ func (c *CoinbasePro) GetActiveOrders(ctx context.Context, req *order.MultiOrder // return nil, err // } - // var resp []GeneralizedOrderResponse + // var resp []GetOrderResponse // // resp, err = c.GetOrders(ctx, // // []string{"open", "pending", "active"}, // // fPair.String()) @@ -807,16 +807,16 @@ func (c *CoinbasePro) GetActiveOrders(ctx context.Context, req *order.MultiOrder return nil, err } var orderType order.Type - orderType, err = order.StringToOrderType(respOrders[i].Type) + // orderType, err = order.StringToOrderType(respOrders[i].Type) if err != nil { log.Errorf(log.ExchangeSys, "%s %v", c.Name, err) } orders[i] = order.Detail{ - OrderID: respOrders[i].ID, - Amount: respOrders[i].Size, + // OrderID: respOrders[i].ID, + // Amount: respOrders[i].Size, ExecutedAmount: respOrders[i].FilledSize, Type: orderType, - Date: respOrders[i].CreatedAt, + Date: respOrders[i].CreatedTime, Side: side, Pair: curr, Exchange: c.Name, @@ -832,10 +832,10 @@ func (c *CoinbasePro) GetOrderHistory(ctx context.Context, req *order.MultiOrder if err != nil { return nil, err } - var respOrders []GeneralizedOrderResponse + var respOrders []GetOrderResponse // if len(req.Pairs) > 0 { // var fPair currency.Pair - // var resp []GeneralizedOrderResponse + // var resp []GetOrderResponse // for i := range req.Pairs { // fPair, err = c.FormatExchangeCurrency(req.Pairs[i], asset.Spot) // if err != nil { @@ -878,27 +878,27 @@ func (c *CoinbasePro) GetOrderHistory(ctx context.Context, req *order.MultiOrder log.Errorf(log.ExchangeSys, "%s %v", c.Name, err) } var orderType order.Type - orderType, err = order.StringToOrderType(respOrders[i].Type) + // orderType, err = order.StringToOrderType(respOrders[i].Type) if err != nil { log.Errorf(log.ExchangeSys, "%s %v", c.Name, err) } detail := order.Detail{ - OrderID: respOrders[i].ID, - Amount: respOrders[i].Size, - ExecutedAmount: respOrders[i].FilledSize, - RemainingAmount: respOrders[i].Size - respOrders[i].FilledSize, - Cost: respOrders[i].ExecutedValue, - CostAsset: curr.Quote, - Type: orderType, - Date: respOrders[i].CreatedAt, - CloseTime: respOrders[i].DoneAt, - Fee: respOrders[i].FillFees, - FeeAsset: curr.Quote, - Side: side, - Status: orderStatus, - Pair: curr, - Price: respOrders[i].Price, - Exchange: c.Name, + OrderID: respOrders[i].OrderID, + // Amount: respOrders[i].Size, + ExecutedAmount: respOrders[i].FilledSize, + // RemainingAmount: respOrders[i].Size - respOrders[i].FilledSize, + // Cost: respOrders[i].ExecutedValue, + CostAsset: curr.Quote, + Type: orderType, + Date: respOrders[i].CreatedTime, + // CloseTime: respOrders[i].DoneAt, + // Fee: respOrders[i].FillFees, + FeeAsset: curr.Quote, + Side: side, + Status: orderStatus, + Pair: curr, + // Price: respOrders[i].Price, + Exchange: c.Name, } detail.InferCostsAndTimes() orders[i] = detail diff --git a/exchanges/coinbasepro/ratelimit.go b/exchanges/coinbasepro/ratelimit.go index 7b43eabb83d..61b37aad687 100644 --- a/exchanges/coinbasepro/ratelimit.go +++ b/exchanges/coinbasepro/ratelimit.go @@ -10,29 +10,36 @@ import ( // Coinbasepro rate limit constants const ( - coinbaseproRateInterval = time.Second - coinbaseproAuthRate = 30 - coinbaseproUnauthRate = 10 + coinbaseV3Interval = time.Second + coinbaseV3Rate = 30 + + coinbaseV2Interval = time.Hour + coinbaseV2Rate = 10000 +) + +const ( + V2Rate request.EndpointLimit = iota + V3Rate ) // RateLimit implements the request.Limiter interface type RateLimit struct { - Auth *rate.Limiter - UnAuth *rate.Limiter + RateLimV3 *rate.Limiter + RateLimV2 *rate.Limiter } // Limit limits outbound calls func (r *RateLimit) Limit(ctx context.Context, f request.EndpointLimit) error { - if f == request.Auth { - return r.Auth.Wait(ctx) + if f == V3Rate { + return r.RateLimV3.Wait(ctx) } - return r.UnAuth.Wait(ctx) + return r.RateLimV2.Wait(ctx) } // SetRateLimit returns the rate limit for the exchange func SetRateLimit() *RateLimit { return &RateLimit{ - Auth: request.NewRateLimit(coinbaseproRateInterval, coinbaseproAuthRate), - UnAuth: request.NewRateLimit(coinbaseproRateInterval, coinbaseproUnauthRate), + RateLimV3: request.NewRateLimit(coinbaseV3Interval, coinbaseV3Rate), + RateLimV2: request.NewRateLimit(coinbaseV2Interval, coinbaseV2Rate), } } From aa2efefbbcc8b4939490a9e5dc2e89e34be50a69 Mon Sep 17 00:00:00 2001 From: Samuel Reid <43227667+cranktakular@users.noreply.github.com> Date: Tue, 28 Nov 2023 15:52:43 +1100 Subject: [PATCH 09/79] Coinbase revamp stage wrapper --- config/config.go | 6 +- config/config_test.go | 4 +- currency/pair.go | 2 +- exchanges/coinbasepro/coinbasepro.go | 89 ++++++++----- exchanges/coinbasepro/coinbasepro_test.go | 76 +++++++++-- exchanges/coinbasepro/coinbasepro_types.go | 128 +++++++++---------- exchanges/coinbasepro/coinbasepro_wrapper.go | 125 ++++++------------ exchanges/exchange.go | 2 +- 8 files changed, 231 insertions(+), 201 deletions(-) diff --git a/config/config.go b/config/config.go index 4b7b5827dcd..1781a8e491a 100644 --- a/config/config.go +++ b/config/config.go @@ -29,8 +29,8 @@ import ( ) var ( - // errExchangeConfigIsNil defines an error when the config is nil - errExchangeConfigIsNil = errors.New("exchange config is nil") + // ErrExchangeConfigIsNil defines an error when the config is nil + ErrExchangeConfigIsNil = errors.New("exchange config is nil") errPairsManagerIsNil = errors.New("currency pairs manager is nil") ) @@ -1893,7 +1893,7 @@ func (c *Config) GetDataPath(elem ...string) string { // Validate checks if exchange config is valid func (c *Exchange) Validate() error { if c == nil { - return errExchangeConfigIsNil + return ErrExchangeConfigIsNil } if c.ConnectionMonitorDelay <= 0 { diff --git a/config/config_test.go b/config/config_test.go index 451dd0b2a29..e2830d1134d 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -2290,8 +2290,8 @@ func TestMigrateConfig(t *testing.T) { func TestExchangeConfigValidate(t *testing.T) { err := (*Exchange)(nil).Validate() - if !errors.Is(err, errExchangeConfigIsNil) { - t.Fatalf("received: '%v' but expected: '%v'", err, errExchangeConfigIsNil) + if !errors.Is(err, ErrExchangeConfigIsNil) { + t.Fatalf("received: '%v' but expected: '%v'", err, ErrExchangeConfigIsNil) } err = (&Exchange{}).Validate() diff --git a/currency/pair.go b/currency/pair.go index 975eeb9704f..c4984b6e136 100644 --- a/currency/pair.go +++ b/currency/pair.go @@ -18,7 +18,7 @@ func NewBTCUSD() Pair { return NewPair(BTC, USD) } -// NewPairDelimiter splits the desired currency string at delimiter, the returns +// NewPairDelimiter splits the desired currency string at delimiter, then returns // a Pair struct func NewPairDelimiter(currencyPair, delimiter string) (Pair, error) { if !strings.Contains(currencyPair, delimiter) { diff --git a/exchanges/coinbasepro/coinbasepro.go b/exchanges/coinbasepro/coinbasepro.go index 09d88ddb7f7..500437bb0d7 100644 --- a/exchanges/coinbasepro/coinbasepro.go +++ b/exchanges/coinbasepro/coinbasepro.go @@ -120,7 +120,7 @@ func (c *CoinbasePro) GetAllAccounts(ctx context.Context, limit uint8, cursor st var params Params params.urlVals = url.Values{} - params.urlVals.Set("limit", strconv.Itoa(int(limit))) + params.urlVals.Set("limit", strconv.FormatInt(int64(limit), 10)) params.urlVals.Set("cursor", cursor) pathParams := common.EncodeURLValues("", params.urlVals) @@ -161,7 +161,7 @@ func (c *CoinbasePro) GetBestBidAsk(ctx context.Context, products []string) (Bes } // GetProductBook returns a list of bids/asks for a single product -func (c *CoinbasePro) GetProductBook(ctx context.Context, productID string, limit int32) (ProductBook, error) { +func (c *CoinbasePro) GetProductBook(ctx context.Context, productID string, limit uint16) (ProductBook, error) { if productID == "" { return ProductBook{}, errProductIDEmpty } @@ -182,8 +182,8 @@ func (c *CoinbasePro) GetProductBook(ctx context.Context, productID string, limi func (c *CoinbasePro) GetAllProducts(ctx context.Context, limit, offset int32, productType, contractExpiryType string, productIDs []string) (AllProducts, error) { var params Params params.urlVals = url.Values{} - params.urlVals.Set("limit", strconv.Itoa(int(limit))) - params.urlVals.Set("offset", strconv.Itoa(int(offset))) + params.urlVals.Set("limit", strconv.FormatInt(int64(limit), 10)) + params.urlVals.Set("offset", strconv.FormatInt(int64(offset), 10)) if productType != "" { params.urlVals.Set("product_type", productType) @@ -271,7 +271,7 @@ func (c *CoinbasePro) GetTicker(ctx context.Context, productID string, limit uin pathParams := common.EncodeURLValues("", params.urlVals) - resp := Ticker{} + var resp Ticker return &resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, pathParams, nil, Version3, &resp, nil) @@ -279,31 +279,30 @@ func (c *CoinbasePro) GetTicker(ctx context.Context, productID string, limit uin // PlaceOrder places either a limit, market, or stop order func (c *CoinbasePro) PlaceOrder(ctx context.Context, clientOID, productID, side, stopDirection, orderType string, baseAmount, quoteAmount, limitPrice, stopPrice float64, postOnly bool, endTime time.Time) (*PlaceOrderResp, error) { - var resp PlaceOrderResp - if clientOID == "" { - return &resp, errClientOrderIDEmpty + return nil, errClientOrderIDEmpty } if productID == "" { - return &resp, errProductIDEmpty + return nil, errProductIDEmpty } if baseAmount == 0 && orderType != order.Market.Lower() && side != order.Buy.String() { - return &resp, errAmountZeroNonMarketBuy + return nil, errAmountZeroNonMarketBuy } if quoteAmount == 0 && orderType == order.Market.Lower() && side == order.Buy.String() { - return &resp, errAmountZeroMarketBuy + return nil, errAmountZeroMarketBuy } + var resp PlaceOrderResp + var orderConfig OrderConfiguration switch orderType { case order.Market.Lower(): + orderConfig.MarketMarketIOC = &MarketMarketIOC{} if side == order.Buy.Lower() { - orderConfig.MarketMarketIOC = &MarketMarketIOC{} orderConfig.MarketMarketIOC.QuoteSize = strconv.FormatFloat(quoteAmount, 'f', -1, 64) } if side == order.Sell.Lower() { - orderConfig.MarketMarketIOC = &MarketMarketIOC{} orderConfig.MarketMarketIOC.BaseSize = strconv.FormatFloat(baseAmount, 'f', -1, 64) } case order.Limit.Lower(): @@ -455,7 +454,7 @@ func (c *CoinbasePro) GetAllOrders(ctx context.Context, productID, userNativeCur } // GetFills returns information of recent fills on the specified profile -func (c *CoinbasePro) GetFills(ctx context.Context, orderID, productID, cursor string, limit int64, startDate, endDate time.Time) (FillResponse, error) { +func (c *CoinbasePro) GetFills(ctx context.Context, orderID, productID, cursor string, limit uint16, startDate, endDate time.Time) (FillResponse, error) { var resp FillResponse var params Params params.urlVals = url.Values{} @@ -472,7 +471,7 @@ func (c *CoinbasePro) GetFills(ctx context.Context, orderID, productID, cursor s params.urlVals.Set("product_id", productID) } - params.urlVals.Set("limit", strconv.FormatInt(limit, 10)) + params.urlVals.Set("limit", strconv.FormatInt(int64(limit), 10)) params.urlVals.Set("cursor", cursor) pathParams := common.EncodeURLValues("", params.urlVals) @@ -484,10 +483,10 @@ func (c *CoinbasePro) GetFills(ctx context.Context, orderID, productID, cursor s // GetOrderByID returns a single order by order id. func (c *CoinbasePro) GetOrderByID(ctx context.Context, orderID, userNativeCurrency, clientID string) (*GetOrderResponse, error) { - resp := GetOrderResponse{} if orderID == "" { - return &resp, errOrderIDEmpty + return nil, errOrderIDEmpty } + var resp GetOrderResponse var params Params params.urlVals = url.Values{} params.urlVals.Set("client_order_id", clientID) @@ -501,14 +500,13 @@ func (c *CoinbasePro) GetOrderByID(ctx context.Context, orderID, userNativeCurre // 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 resp TransactionSummary +func (c *CoinbasePro) GetTransactionSummary(ctx context.Context, startDate, endDate time.Time, userNativeCurrency, productType, contractExpiryType string) (*TransactionSummary, error) { var params Params params.urlVals = url.Values{} err := params.PrepareDateString(startDate, endDate, startDateString, endDateString) if err != nil { - return resp, err + return nil, err } if contractExpiryType != "" { @@ -522,7 +520,9 @@ func (c *CoinbasePro) GetTransactionSummary(ctx context.Context, startDate, endD pathParams := common.EncodeURLValues("", params.urlVals) - return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, + var resp TransactionSummary + + return &resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, coinbaseV3+coinbaseTransactionSummary, pathParams, nil, Version3, &resp, nil) } @@ -747,12 +747,12 @@ func (c *CoinbasePro) GetAddressTransactions(ctx context.Context, walletID, addr path, pathParams, nil, Version2, &resp, nil) } -// SendMoney ca send funds to an email or cryptocurrency address (if "traType" is set to "send"), +// SendMoney can send funds to an email or cryptocurrency address (if "traType" is set to "send"), // or to another one of the user's wallets or vaults (if "traType" is set to "transfer"). Coinbase -// may delay or cancel the transaction at their discretion. The "idem" parameter is an optional -// string for idempotency; a token with a max length of 100 characters, if a previous -// transaction included the same string as a parameter, the new transaction won't be processed, -// and information on the previous transaction will be returned +// may delay or cancel the transaction at their discretion. The "idem" parameter is an optional +// string for idempotency; a token with a max length of 100 characters, if a previous +// transaction included the same token as a parameter, the new transaction won't be processed, +// and information on the previous transaction will be returned instead func (c *CoinbasePro) SendMoney(ctx context.Context, traType, walletID, to, amount, currency, description, idem, financialInstitutionWebsite, destinationTag string, skipNotifications, toFinancialInstitution bool) (*GenTransactionResp, error) { if traType == "" { return nil, errTransactionTypeEmpty @@ -1878,7 +1878,7 @@ func (c *CoinbasePro) SendAuthenticatedHTTPRequest(ctx context.Context, ep excha if err != nil { return err } - errCap := struct { + singleErrCap := struct { ErrorType string `json:"error"` Message string `json:"message"` ErrorDetails string `json:"error_details"` @@ -1886,14 +1886,39 @@ func (c *CoinbasePro) SendAuthenticatedHTTPRequest(ctx context.Context, ep excha PreviewFailureReason string `json:"preview_failure_reason"` NewOrderFailureReason string `json:"new_order_failure_reason"` }{} - if err := json.Unmarshal(interim, &errCap); err == nil { - if errCap.Message != "" { + 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", - errCap.Message, errCap.ErrorType, errCap.ErrorDetails, errCap.EditFailureReason, - errCap.PreviewFailureReason, errCap.NewOrderFailureReason) + singleErrCap.Message, singleErrCap.ErrorType, singleErrCap.ErrorDetails, singleErrCap.EditFailureReason, + singleErrCap.PreviewFailureReason, singleErrCap.NewOrderFailureReason) return errors.New(errMessage) } } + manyErrCap := struct { + Errors []struct { + Success bool `json:"success"` + FailureReason string `json:"failure_reason"` + OrderID string `json:"order_id"` + EditFailureReason string `json:"edit_failure_reason"` + PreviewFailureReason string `json:"preview_failure_reason"` + } + }{} + if err := json.Unmarshal(interim, &manyErrCap); 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) + } + } + } if result == nil { return nil } @@ -1995,7 +2020,7 @@ func (p *Params) PrepareDateString(startDate, endDate time.Time, labelStart, lab func (p *Params) PreparePagination(pag PaginationInp) { if pag.Limit != 0 { - p.urlVals.Set("limit", strconv.Itoa(int(pag.Limit))) + p.urlVals.Set("limit", strconv.FormatInt(int64(pag.Limit), 10)) } if pag.OrderAscend { p.urlVals.Set("order", "asc") diff --git a/exchanges/coinbasepro/coinbasepro_test.go b/exchanges/coinbasepro/coinbasepro_test.go index 31480446644..6d70c36c38d 100644 --- a/exchanges/coinbasepro/coinbasepro_test.go +++ b/exchanges/coinbasepro/coinbasepro_test.go @@ -18,8 +18,10 @@ import ( "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" + "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues" + gctlog "github.com/thrasher-corp/gocryptotrader/log" ) var ( @@ -44,6 +46,7 @@ const ( errExpectMismatch = "received: '%v' but expected: '%v'" errExpectedNonEmpty = "expected non-empty response" skipPayMethodNotFound = "no payment methods found, skipping" + errx7f = "setting proxy address error parse \"\\x7f\": net/url: invalid control character in URL" ) func TestMain(m *testing.M) { @@ -56,11 +59,11 @@ func TestMain(m *testing.M) { cfg := config.GetConfig() err := cfg.LoadConfig("../../testdata/configtest.json", true) if err != nil { - log.Fatal("coinbasepro load config error", err) + log.Fatal("load config error", err) } gdxConfig, err := cfg.GetExchangeConfig("CoinbasePro") if err != nil { - log.Fatal("coinbasepro Setup() init error") + log.Fatal("init error") } if apiKey != "" { gdxConfig.API.Credentials.Key = apiKey @@ -69,12 +72,17 @@ func TestMain(m *testing.M) { gdxConfig.API.AuthenticatedSupport = true gdxConfig.API.AuthenticatedWebsocketSupport = true } + gdxConfig.API.AuthenticatedSupport = true c.Websocket = sharedtestvalues.NewTestWebsocket() err = c.Setup(gdxConfig) if err != nil { log.Fatal("CoinbasePro setup error", err) } + c.GetBase().API.AuthenticatedSupport = true + c.GetBase().API.AuthenticatedWebsocketSupport = true c.Verbose = true + err = gctlog.SetGlobalLogConfig(gctlog.GenDefaultSettings()) + fmt.Println(err) os.Exit(m.Run()) } @@ -156,14 +164,21 @@ func TestGetProductBook(t *testing.T) { assert.NotEmpty(t, resp, errExpectedNonEmpty) } +func TestSillyTestTest(t *testing.T) { + assTypes := c.CurrencyPairs.GetAssetTypes(false) + log.Printf("%+v", assTypes) +} + func TestGetAllProducts(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - testPairs := []string{testPair.String(), "ETH-USD"} - resp, err := c.GetAllProducts(context.Background(), 2, 0, "SPOT", unknownContract, testPairs) + // testPairs := []string{testPair.String(), "ETH-USD"} + var testPairs []string + resp, err := c.GetAllProducts(context.Background(), 30000, 0, "", "", testPairs) if err != nil { t.Error(err) } assert.NotEmpty(t, resp, errExpectedNonEmpty) + log.Printf("%+v\n%+v", resp.NumProducts, len(resp.Products)) } func TestGetProductByID(t *testing.T) { @@ -290,11 +305,10 @@ func TestEditOrderPreview(t *testing.T) { t.Errorf(errExpectMismatch, err, errSizeAndPriceZero) } id, _ := uuid.NewV4() - resp, err := c.EditOrderPreview(context.Background(), id.String(), 0.0000001, 10000000000000) + _, err = c.EditOrderPreview(context.Background(), id.String(), 0.0000001, 10000000000000) if err != nil { t.Error(err) } - log.Printf("%+v", resp) } func TestGetAllOrders(t *testing.T) { @@ -326,7 +340,7 @@ func TestGetFills(t *testing.T) { if !errors.Is(err, common.ErrStartAfterEnd) { t.Errorf(errExpectMismatch, err, common.ErrStartAfterEnd) } - _, err = c.GetFills(context.Background(), "0", testPair.String(), "", 2, time.Unix(1, 1), time.Now()) + _, err = c.GetFills(context.Background(), "", "", "", 5, time.Unix(1, 1), time.Now()) if err != nil { t.Error(err) } @@ -2512,7 +2526,7 @@ func TestPrepareDateString(t *testing.T) { t.Error(err) } if fmt.Sprint(expectedResult) != fmt.Sprint(result) { - t.Errorf(errExpectMismatch, expectedResult, result) + t.Errorf(errExpectMismatch, result, expectedResult) } var newTime time.Time @@ -2541,7 +2555,7 @@ func TestPreparePagination(t *testing.T) { result.PreparePagination(pagIn) if fmt.Sprint(expectedResult) != fmt.Sprint(result) { - t.Errorf(errExpectMismatch, expectedResult, result) + t.Errorf(errExpectMismatch, result, expectedResult) } } @@ -2605,7 +2619,49 @@ func TestPrepareDSL(t *testing.T) { result.PrepareDSL("before", "1", 2) if fmt.Sprint(expectedResult) != fmt.Sprint(result) { - t.Errorf(errExpectMismatch, expectedResult, result) + t.Errorf(errExpectMismatch, result, expectedResult) + } +} + +func TestGetDefaultConfig(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + _, err := c.GetDefaultConfig(context.Background()) + if err != nil { + t.Error(err) + } +} + +func TestSetup(t *testing.T) { + err := c.Setup(nil) + if !errors.Is(err, config.ErrExchangeConfigIsNil) { + t.Errorf(errExpectMismatch, err, config.ErrExchangeConfigIsNil) + } + cfg, err := c.GetStandardConfig() + if err != nil { + t.Error(err) + } + cfg.Enabled = false + _ = c.Setup(cfg) + cfg.Enabled = true + cfg.ProxyAddress = string(rune(0x7f)) + err = c.Setup(cfg) + if err.Error() != errx7f { + t.Errorf(errExpectMismatch, err, errx7f) + } +} + +func TestFetchTradablePairs(t *testing.T) { + _, err := c.FetchTradablePairs(context.Background(), asset.Spot) + if err != nil { + t.Error(err) + } + _, err = c.FetchTradablePairs(context.Background(), asset.Futures) + if err != nil { + t.Error(err) + } + _, err = c.FetchTradablePairs(context.Background(), asset.Empty) + if !errors.Is(err, asset.ErrNotSupported) { + t.Errorf(errExpectMismatch, err, asset.ErrNotSupported) } } diff --git a/exchanges/coinbasepro/coinbasepro_types.go b/exchanges/coinbasepro/coinbasepro_types.go index 733c989a4d2..5ffab69b4cb 100644 --- a/exchanges/coinbasepro/coinbasepro_types.go +++ b/exchanges/coinbasepro/coinbasepro_types.go @@ -76,31 +76,31 @@ type ProductBookResponse struct { // Product holds product information, returned by GetAllProducts and GetProductByID type Product struct { - ID string `json:"product_id"` - Price float64 `json:"price,string"` - PricePercentageChange24H float64 `json:"price_percentage_change_24h,string"` - Volume24H float64 `json:"volume_24h,string"` - VolumePercentageChange24H float64 `json:"volume_percentage_change_24h,string"` - BaseIncrement float64 `json:"base_increment,string"` - QuoteIncrement float64 `json:"quote_increment,string"` - QuoteMinSize float64 `json:"quote_min_size,string"` - QuoteMaxSize float64 `json:"quote_max_size,string"` - BaseMinSize float64 `json:"base_min_size,string"` - BaseMaxSize float64 `json:"base_max_size,string"` - BaseName string `json:"base_name"` - QuoteName string `json:"quote_name"` - Watched bool `json:"watched"` - IsDisabled bool `json:"is_disabled"` - New bool `json:"new"` - Status string `json:"status"` - CancelOnly bool `json:"cancel_only"` - LimitOnly bool `json:"limit_only"` - PostOnly bool `json:"post_only"` - TradingDisabled bool `json:"trading_disabled"` - AuctionMode bool `json:"auction_mode"` - ProductType string `json:"product_type"` - QuoteCurrencyID string `json:"quote_currency_id"` - BaseCurrencyID string `json:"base_currency_id"` + ID string `json:"product_id"` + Price convert.StringToFloat64 `json:"price"` + PricePercentageChange24H convert.StringToFloat64 `json:"price_percentage_change_24h"` + Volume24H convert.StringToFloat64 `json:"volume_24h"` + VolumePercentageChange24H convert.StringToFloat64 `json:"volume_percentage_change_24h"` + BaseIncrement convert.StringToFloat64 `json:"base_increment"` + QuoteIncrement convert.StringToFloat64 `json:"quote_increment"` + QuoteMinSize convert.StringToFloat64 `json:"quote_min_size"` + QuoteMaxSize convert.StringToFloat64 `json:"quote_max_size"` + BaseMinSize convert.StringToFloat64 `json:"base_min_size"` + BaseMaxSize convert.StringToFloat64 `json:"base_max_size"` + BaseName string `json:"base_name"` + QuoteName string `json:"quote_name"` + Watched bool `json:"watched"` + IsDisabled bool `json:"is_disabled"` + New bool `json:"new"` + Status string `json:"status"` + CancelOnly bool `json:"cancel_only"` + LimitOnly bool `json:"limit_only"` + PostOnly bool `json:"post_only"` + TradingDisabled bool `json:"trading_disabled"` + AuctionMode bool `json:"auction_mode"` + ProductType string `json:"product_type"` + QuoteCurrencyID string `json:"quote_currency_id"` + BaseCurrencyID string `json:"base_currency_id"` FCMTradingSessionDetails struct { IsSessionOpen bool `json:"is_session_open"` OpenTime time.Time `json:"open_time"` @@ -112,22 +112,22 @@ type Product struct { BaseDisplaySymbol string `json:"base_display_symbol"` QuoteDisplaySymbol string `json:"quote_display_symbol"` ViewOnly bool `json:"view_only"` - PriceIncrement float64 `json:"price_increment,string"` + PriceIncrement convert.StringToFloat64 `json:"price_increment"` FutureProductDetails struct { - Venue string `json:"venue"` - ContractCode string `json:"contract_code"` - ContractExpiry string `json:"contract_expiry"` - ContractSize float64 `json:"contract_size,string"` - ContractRootUnit string `json:"contract_root_unit"` - GroupDescription string `json:"group_description"` - ContractExpiryTimezone string `json:"contract_expiry_timezone"` - GroupShortDescription string `json:"group_short_description"` - RiskManagedBy string `json:"risk_managed_by"` - ContractExpiryType string `json:"contract_expiry_type"` + Venue string `json:"venue"` + ContractCode string `json:"contract_code"` + ContractExpiry string `json:"contract_expiry"` + ContractSize convert.StringToFloat64 `json:"contract_size"` + ContractRootUnit string `json:"contract_root_unit"` + GroupDescription string `json:"group_description"` + ContractExpiryTimezone string `json:"contract_expiry_timezone"` + GroupShortDescription string `json:"group_short_description"` + RiskManagedBy string `json:"risk_managed_by"` + ContractExpiryType string `json:"contract_expiry_type"` PerpetualDetails struct { - OpenInterest float64 `json:"open_interest,string"` - FundingRate float64 `json:"funding_rate,string"` - FundingTime time.Time `json:"funding_time"` + OpenInterest convert.StringToFloat64 `json:"open_interest"` + FundingRate convert.StringToFloat64 `json:"funding_rate"` + FundingTime time.Time `json:"funding_time"` } `json:"perpetual_details"` ContractDisplayName string `json:"contract_display_name"` } `json:"future_product_details"` @@ -248,13 +248,6 @@ type PlaceOrderResp struct { Side string `json:"side"` ClientOrderID string `json:"client_oid"` } `json:"success_response"` - ErrorResponse struct { - Error string `json:"error"` - Message string `json:"message"` - ErrorDetails string `json:"error_details"` - PreviewFailureReason string `json:"preview_failure_reason"` - NewOrderFailureReason string `json:"new_order_failure_reason"` - } `json:"error_response"` OrderConfiguration OrderConfiguration `json:"order_configuration"` } @@ -416,42 +409,38 @@ type ListNotificationsResponse struct { Type string `json:"type"` Data struct { ID string `json:"id"` + Address string `json:"address"` + Name string `json:"name"` Status string `json:"status"` PaymentMethod IDResource `json:"payment_method"` Transaction IDResource `json:"transaction"` - Amount struct { - Amount float64 `json:"amount,string"` - Currency string `json:"currency"` - } - Total struct { - Amount float64 `json:"amount,string"` - Currency string `json:"currency"` - } - Subtotal struct { - Amount float64 `json:"amount,string"` - Currency string `json:"currency"` - } - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` - Resource string `json:"resource"` - ResourcePath string `json:"resource_path"` - Committed bool `json:"committed"` - Instant bool `json:"instant"` - Fees []struct { + Amount AmCur `json:"amount"` + Total AmCur `json:"total"` + Subtotal AmCur `json:"subtotal"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + Resource string `json:"resource"` + ResourcePath string `json:"resource_path"` + Committed bool `json:"committed"` + Instant bool `json:"instant"` + Fee AmCur `json:"fee"` + Fees []struct { Type string `json:"type"` - Amount struct { - Amount float64 `json:"amount,string"` - Currency string `json:"currency"` - } `json:"amount"` + Amount AmCur `json:"amount"` } `json:"fees"` PayoutAt time.Time `json:"payout_at"` } `json:"data"` + AdditionalData struct { + Hash string `json:"hash"` + Amount AmCur `json:"amount"` + } `json:"additional_data"` User IDResource `json:"user"` Account IDResource `json:"account"` DeliveryAttempts int32 `json:"delivery_attempts"` CreatedAt time.Time `json:"created_at"` Resource string `json:"resource"` ResourcePath string `json:"resource_path"` + Transaction IDResource `json:"transaction"` } `json:"data"` } @@ -505,6 +494,7 @@ type UserResponse struct { ShowInstantAchUx bool `json:"show_instant_ach_ux"` UserType string `json:"user_type"` Email string `json:"email"` + SendsDisabled bool `json:"sends_disabled"` } `json:"data"` } diff --git a/exchanges/coinbasepro/coinbasepro_wrapper.go b/exchanges/coinbasepro/coinbasepro_wrapper.go index 06a83dab5ae..1899dc78726 100644 --- a/exchanges/coinbasepro/coinbasepro_wrapper.go +++ b/exchanges/coinbasepro/coinbasepro_wrapper.go @@ -43,7 +43,7 @@ func (c *CoinbasePro) GetDefaultConfig(ctx context.Context) (*config.Exchange, e return nil, err } - if c.Features.Supports.RESTCapabilities.AutoPairUpdates { + if c.Features.Supports.RESTCapabilities.AutoPairUpdates && c.Base.API.AuthenticatedSupport { err = c.UpdateTradablePairs(ctx, true) if err != nil { return nil, err @@ -65,7 +65,7 @@ func (c *CoinbasePro) SetDefaults() { requestFmt := ¤cy.PairFormat{Delimiter: currency.DashDelimiter, Uppercase: true} configFmt := ¤cy.PairFormat{Delimiter: currency.DashDelimiter, Uppercase: true} - err := c.SetGlobalPairsManager(requestFmt, configFmt, asset.Spot) + err := c.SetGlobalPairsManager(requestFmt, configFmt, asset.Spot, asset.Futures) if err != nil { log.Errorln(log.ExchangeSys, err) } @@ -220,102 +220,61 @@ func (c *CoinbasePro) Run(ctx context.Context) { c.PrintEnabledPairs() } - forceUpdate := false - if !c.BypassConfigFormatUpgrades { - format, err := c.GetPairFormat(asset.Spot, false) - if err != nil { - log.Errorf(log.ExchangeSys, - "%s failed to update currencies. Err: %s\n", - c.Name, - err) - return - } - enabled, err := c.CurrencyPairs.GetPairs(asset.Spot, true) - if err != nil { - log.Errorf(log.ExchangeSys, - "%s failed to update currencies. Err: %s\n", - c.Name, - err) - return - } - - avail, err := c.CurrencyPairs.GetPairs(asset.Spot, false) - if err != nil { - log.Errorf(log.ExchangeSys, - "%s failed to update currencies. Err: %s\n", - c.Name, - err) - return - } - - if !common.StringDataContains(enabled.Strings(), format.Delimiter) || - !common.StringDataContains(avail.Strings(), format.Delimiter) { - var p currency.Pairs - p, err = currency.NewPairsFromStrings([]string{currency.BTC.String() + - format.Delimiter + - currency.USD.String()}) - if err != nil { - log.Errorf(log.ExchangeSys, - "%s failed to update currencies. Err: %s\n", - c.Name, - err) - } else { - forceUpdate = true - log.Warnf(log.ExchangeSys, exchange.ResetConfigPairsWarningMessage, c.Name, asset.Spot, p) - - err = c.UpdatePairs(p, asset.Spot, true, true) - if err != nil { - log.Errorf(log.ExchangeSys, - "%s failed to update currencies. Err: %s\n", - c.Name, - err) - } - } - } - } - - if !c.GetEnabledFeatures().AutoPairUpdates && !forceUpdate { + if !c.GetEnabledFeatures().AutoPairUpdates { return } - err := c.UpdateTradablePairs(ctx, forceUpdate) + err := c.UpdateTradablePairs(ctx, false) if err != nil { log.Errorf(log.ExchangeSys, "%s failed to update tradable pairs. Err: %s", c.Name, err) } } // FetchTradablePairs returns a list of the exchanges tradable pairs -func (c *CoinbasePro) FetchTradablePairs(ctx context.Context, _ asset.Item) (currency.Pairs, error) { - // products, err := c.GetProducts(ctx) - // if err != nil { - // return nil, err - // } +func (c *CoinbasePro) FetchTradablePairs(ctx context.Context, a asset.Item) (currency.Pairs, error) { + var products AllProducts + var err error + switch a { + case asset.Spot: + products, err = c.GetAllProducts(ctx, 2<<30-1, 0, "SPOT", "", nil) + case asset.Futures: + products, err = c.GetAllProducts(ctx, 2<<30-1, 0, "FUTURE", "", nil) + default: + err = asset.ErrNotSupported + } - // pairs := make([]currency.Pair, 0, len(products)) - // for x := range products { - // if products[x].TradingDisabled { - // continue - // } - // var pair currency.Pair - // pair, err = currency.NewPairDelimiter(products[x].ID, currency.DashDelimiter) - // if err != nil { - // return nil, err - // } - // pairs = append(pairs, pair) - // } - return nil, errors.New("function not properly implemented") + if err != nil { + return nil, err + } + + pairs := make([]currency.Pair, 0, len(products.Products)) + for x := range products.Products { + if products.Products[x].TradingDisabled { + continue + } + var pair currency.Pair + pair, err = currency.NewPairDelimiter(products.Products[x].ID, currency.DashDelimiter) + if err != nil { + return nil, err + } + pairs = append(pairs, pair) + } + return pairs, nil } // UpdateTradablePairs updates the exchanges available pairs and stores // them in the exchanges config func (c *CoinbasePro) UpdateTradablePairs(ctx context.Context, forceUpdate bool) error { - pairs, err := c.FetchTradablePairs(ctx, asset.Spot) - if err != nil { - return err - } - err = c.UpdatePairs(pairs, asset.Spot, false, forceUpdate) - if err != nil { - return err + assets := c.GetAssetTypes(true) + for i := range assets { + pairs, err := c.FetchTradablePairs(ctx, assets[i]) + if err != nil { + return err + } + err = c.UpdatePairs(pairs, assets[i], false, forceUpdate) + if err != nil { + return err + } } return c.EnsureOnePairEnabled() } diff --git a/exchanges/exchange.go b/exchanges/exchange.go index 98f7f6bac4f..680b29792b4 100644 --- a/exchanges/exchange.go +++ b/exchanges/exchange.go @@ -177,7 +177,7 @@ func (b *Base) GetLastPairsUpdateTime() int64 { return b.CurrencyPairs.LastUpdated } -// GetAssetTypes returns the either the enabled or available asset types for an +// GetAssetTypes returns either the enabled or available asset types for an // individual exchange func (b *Base) GetAssetTypes(enabled bool) asset.Items { return b.CurrencyPairs.GetAssetTypes(enabled) From 4593828f68e34db1e73ee0ac10cb8897c26cff2b Mon Sep 17 00:00:00 2001 From: Samuel Reid <43227667+cranktakular@users.noreply.github.com> Date: Tue, 28 Nov 2023 16:45:21 +1100 Subject: [PATCH 10/79] Coinbase wrapper coherence continues --- exchanges/coinbasepro/coinbasepro_test.go | 41 +++++++++-- exchanges/coinbasepro/coinbasepro_wrapper.go | 73 ++++++++++++-------- 2 files changed, 80 insertions(+), 34 deletions(-) diff --git a/exchanges/coinbasepro/coinbasepro_test.go b/exchanges/coinbasepro/coinbasepro_test.go index 6d70c36c38d..e17bb24fd52 100644 --- a/exchanges/coinbasepro/coinbasepro_test.go +++ b/exchanges/coinbasepro/coinbasepro_test.go @@ -2650,8 +2650,21 @@ func TestSetup(t *testing.T) { } } +func TestWrapperStart(t *testing.T) { + wg := sync.WaitGroup{} + err := c.Start(context.Background(), &wg) + if err != nil { + t.Error(err) + } +} + func TestFetchTradablePairs(t *testing.T) { - _, err := c.FetchTradablePairs(context.Background(), asset.Spot) + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + _, err := c.FetchTradablePairs(context.Background(), asset.Empty) + if !errors.Is(err, asset.ErrNotSupported) { + t.Errorf(errExpectMismatch, err, asset.ErrNotSupported) + } + _, err = c.FetchTradablePairs(context.Background(), asset.Spot) if err != nil { t.Error(err) } @@ -2659,9 +2672,29 @@ func TestFetchTradablePairs(t *testing.T) { if err != nil { t.Error(err) } - _, err = c.FetchTradablePairs(context.Background(), asset.Empty) - if !errors.Is(err, asset.ErrNotSupported) { - t.Errorf(errExpectMismatch, err, asset.ErrNotSupported) +} + +func TestUpdateTradablePairs(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + err := c.UpdateTradablePairs(context.Background(), false) + if err != nil { + t.Error(err) + } +} + +func TestUpdateAccountInfo(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + _, err := c.UpdateAccountInfo(context.Background(), asset.Spot) + if err != nil { + t.Error(err) + } +} + +func TestFetchAccountInfo(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + _, err := c.FetchAccountInfo(context.Background(), asset.Spot) + if err != nil { + t.Error(err) } } diff --git a/exchanges/coinbasepro/coinbasepro_wrapper.go b/exchanges/coinbasepro/coinbasepro_wrapper.go index 03b10a5f298..ba11926b859 100644 --- a/exchanges/coinbasepro/coinbasepro_wrapper.go +++ b/exchanges/coinbasepro/coinbasepro_wrapper.go @@ -283,39 +283,52 @@ func (c *CoinbasePro) UpdateTradablePairs(ctx context.Context, forceUpdate bool) // UpdateAccountInfo retrieves balances for all enabled currencies for the // coinbasepro exchange func (c *CoinbasePro) UpdateAccountInfo(ctx context.Context, assetType asset.Item) (account.Holdings, error) { - var response account.Holdings + var ( + response account.Holdings + accountBalance []Account + done bool + err error + cursor string + ) response.Exchange = c.Name - // accountBalance, err := c.GetAccounts(ctx) - // if err != nil { - // return response, err - // } - - // accountCurrencies := make(map[string][]account.Balance) - // for i := range accountBalance { - // profileID := accountBalance[i].ProfileID - // currencies := accountCurrencies[profileID] - // accountCurrencies[profileID] = append(currencies, account.Balance{ - // Currency: currency.NewCode(accountBalance[i].Currency), - // Total: accountBalance[i].Balance, - // Hold: accountBalance[i].Hold, - // Free: accountBalance[i].Available, - // AvailableWithoutBorrow: accountBalance[i].Available - accountBalance[i].FundedAmount, - // Borrowed: accountBalance[i].FundedAmount, - // }) - // } - // if response.Accounts, err = account.CollectBalances(accountCurrencies, assetType); err != nil { - // return account.Holdings{}, err - // } + for !done { + accountResp, err := c.GetAllAccounts(ctx, 250, cursor) + if err != nil { + return response, err + } + accountBalance = append(accountBalance, accountResp.Accounts...) + done = !accountResp.HasNext + cursor = accountResp.Cursor + } + + accountCurrencies := make(map[string][]account.Balance) + for i := range accountBalance { + profileID := accountBalance[i].UUID + currencies := accountCurrencies[profileID] + accountCurrencies[profileID] = append(currencies, account.Balance{ + Currency: currency.NewCode(accountBalance[i].Currency), + Total: accountBalance[i].AvailableBalance.Value, + Hold: accountBalance[i].Hold.Value, + 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 - // } - // err = account.Process(&response, creds) - // if err != nil { - // return account.Holdings{}, err - // } + creds, err := c.GetCredentials(ctx) + if err != nil { + return account.Holdings{}, err + } + err = account.Process(&response, creds) + if err != nil { + return account.Holdings{}, err + } return response, nil } From a8cae402a873941bccc428d5a341fe561c46583a Mon Sep 17 00:00:00 2001 From: Samuel Reid <43227667+cranktakular@users.noreply.github.com> Date: Tue, 5 Dec 2023 16:54:12 +1100 Subject: [PATCH 11/79] Coinbase wrapper continues writhing --- exchanges/coinbasepro/coinbasepro.go | 175 +++++++--- exchanges/coinbasepro/coinbasepro_test.go | 258 ++++++++++++++- exchanges/coinbasepro/coinbasepro_types.go | 192 ++++++----- exchanges/coinbasepro/coinbasepro_wrapper.go | 328 ++++++++++++++----- 4 files changed, 754 insertions(+), 199 deletions(-) diff --git a/exchanges/coinbasepro/coinbasepro.go b/exchanges/coinbasepro/coinbasepro.go index 500437bb0d7..124f6e8bbbb 100644 --- a/exchanges/coinbasepro/coinbasepro.go +++ b/exchanges/coinbasepro/coinbasepro.go @@ -10,6 +10,7 @@ import ( "net/http" "net/url" "strconv" + "strings" "time" "github.com/thrasher-corp/gocryptotrader/common" @@ -38,6 +39,9 @@ const ( coinbaseCandles = "candles" coinbaseTicker = "ticker" coinbaseTransactionSummary = "transaction_summary" + coinbaseConvert = "convert" + coinbaseQuote = "quote" + coinbaseTrade = "trade" coinbaseV2 = "/v2/" coinbaseNotifications = "notifications" coinbaseUser = "user" @@ -104,16 +108,9 @@ var ( errDepositIDEmpty = errors.New("deposit id cannot be empty") errInvalidPriceType = errors.New("price type must be spot, buy, or sell") errInvalidOrderType = errors.New("order type must be market, limit, or stop") + errNoMatchingWallets = errors.New("no matching wallets returned") ) -type Version bool -type FiatTransferType bool - -// CoinbasePro is the overarching type across the coinbasepro package -type CoinbasePro struct { - exchange.Base -} - // 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) { var resp AllAccountsResponse @@ -256,8 +253,8 @@ func (c *CoinbasePro) GetHistoricRates(ctx context.Context, productID, granulari return resp, err } -// GetTicker returns snapshot information about the last trades (ticks), best bid/ask, and -// 24h volume. +// GetTicker returns snapshot information about the last trades (ticks) and best bid/ask. +// Contrary to documentation, this does not tell you the 24h volume func (c *CoinbasePro) GetTicker(ctx context.Context, productID string, limit uint16) (*Ticker, error) { if productID == "" { return nil, errProductIDEmpty @@ -526,6 +523,65 @@ func (c *CoinbasePro) GetTransactionSummary(ctx context.Context, startDate, endD coinbaseV3+coinbaseTransactionSummary, pathParams, nil, Version3, &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 + if from == "" || to == "" { + return resp, errAccountIDEmpty + } + if amount == 0 { + return resp, 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} + + return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, path, + "", req, Version3, &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 + if tradeID == "" { + return resp, errTransactionIDEmpty + } + if from == "" || to == "" { + return resp, errAccountIDEmpty + } + + path := fmt.Sprintf("%s%s/%s/%s", coinbaseV3, coinbaseConvert, coinbaseTrade, tradeID) + + req := map[string]interface{}{"from_account": from, "to_account": to} + + return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, path, + "", req, Version3, &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 + if tradeID == "" { + return resp, errTransactionIDEmpty + } + if from == "" || to == "" { + return resp, errAccountIDEmpty + } + + path := fmt.Sprintf("%s%s/%s/%s", coinbaseV3, coinbaseConvert, coinbaseTrade, tradeID) + + req := map[string]interface{}{"from_account": from, "to_account": to} + + return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, + "", req, Version3, &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 @@ -784,8 +840,8 @@ func (c *CoinbasePro) SendMoney(ctx context.Context, traType, walletID, to, amou path, "", req, Version2, &resp, nil) } -// ListTransactions returns a list of transactions associated with the specified wallet -func (c *CoinbasePro) ListTransactions(ctx context.Context, walletID string, pag PaginationInp) (ManyTransactionsResp, error) { +// 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 if walletID == "" { @@ -906,8 +962,18 @@ func (c *CoinbasePro) GetAllFiatTransfers(ctx context.Context, walletID string, pathParams := common.EncodeURLValues("", params.urlVals) - return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, + err := c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, pathParams, nil, Version2, &resp, nil) + + if err != nil { + return resp, err + } + + for i := range resp.Data { + resp.Data[i].TransferType = transferType + } + + return resp, nil } // GetFiatTransferByID returns information on a single deposit associated with the specified wallet @@ -1132,31 +1198,6 @@ func (c *CoinbasePro) GetCoinbaseWallets(ctx context.Context) ([]CoinbaseAccount c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, coinbaseproCoinbaseAccounts, "", nil, Version3, &resp, nil) } -// ConvertCurrency converts between two currencies in the specified profile -func (c *CoinbasePro) ConvertCurrency(ctx context.Context, profileID, from, to, nonce string, amount float64) (ConvertResponse, error) { - params := map[string]interface{}{"profile_id": profileID, "from": from, "to": to, - "amount": strconv.FormatFloat(amount, 'f', -1, 64), "nonce": nonce} - - resp := ConvertResponse{} - - return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproConversions, "", params, Version3, &resp, nil) -} - -// GetConversionByID returns the details of a past conversion, given its ID -func (c *CoinbasePro) GetConversionByID(ctx context.Context, conversionID, profileID string) (ConvertResponse, error) { - path := fmt.Sprintf("%s/%s", coinbaseproConversions, conversionID) - var params Params - params.urlVals = url.Values{} - params.urlVals.Set("profile_id", profileID) - path = common.EncodeURLValues(path, params.urlVals) - - resp := ConvertResponse{} - - return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, "", nil, Version3, &resp, nil) -} - // GetAllCurrencies returns a list of currencies known by the exchange // Warning: Currencies won't necessarily be available for trading func (c *CoinbasePro) GetAllCurrencies(ctx context.Context) ([]Currency, error) { @@ -2085,3 +2126,61 @@ func (p *Params) PrepareDSL(direction, step string, limit int64) { p.urlVals.Set("limit", strconv.FormatInt(limit, 10)) } } + +func (f FiatTransferType) String() string { + if f { + return "withdrawal" + } + return "deposit" +} + +func (t *UnixTimestamp) UnmarshalJSON(b []byte) error { + var timestampStr string + err := json.Unmarshal(b, ×tampStr) + if err != nil { + return err + } + timestamp, err := strconv.ParseInt(timestampStr, 10, 64) + if err != nil { + return err + } + *t = UnixTimestamp(time.Unix(timestamp, 0)) + return nil +} + +func (t UnixTimestamp) String() string { + return time.Time(t).String() +} + +func (t *ExchTime) UnmarshalJSON(b []byte) error { + s := strings.Trim(string(b), `"`) + if s == " " || s == "null" { + return nil + } + tt, err := time.Parse("2006-01-02 15:04:05.999999-07", s) + if err != nil { + return err + } + *t = ExchTime(tt) + return nil +} + +func (t ExchTime) String() string { + return time.Time(t).String() +} + +func (pm *PriceMap) UnmarshalJSON(data []byte) error { + var m map[string]string + if err := json.Unmarshal(data, &m); err != nil { + return err + } + *pm = make(PriceMap) + for k, v := range m { + f, err := strconv.ParseFloat(v, 64) + if err != nil { + return err + } + (*pm)[k] = f + } + return nil +} diff --git a/exchanges/coinbasepro/coinbasepro_test.go b/exchanges/coinbasepro/coinbasepro_test.go index e17bb24fd52..17bebd2a13a 100644 --- a/exchanges/coinbasepro/coinbasepro_test.go +++ b/exchanges/coinbasepro/coinbasepro_test.go @@ -46,6 +46,7 @@ const ( errExpectMismatch = "received: '%v' but expected: '%v'" errExpectedNonEmpty = "expected non-empty response" skipPayMethodNotFound = "no payment methods found, skipping" + skipInsufSuitableAccs = "insufficient suitable accounts found, skipping" errx7f = "setting proxy address error parse \"\\x7f\": net/url: invalid control character in URL" ) @@ -157,18 +158,13 @@ func TestGetProductBook(t *testing.T) { if !errors.Is(err, errProductIDEmpty) { t.Errorf(errExpectMismatch, err, errProductIDEmpty) } - resp, err := c.GetProductBook(context.Background(), testPair.String(), 5) + resp, err := c.GetProductBook(context.Background(), testPair.String(), 2) if err != nil { t.Error(err) } assert.NotEmpty(t, resp, errExpectedNonEmpty) } -func TestSillyTestTest(t *testing.T) { - assTypes := c.CurrencyPairs.GetAssetTypes(false) - log.Printf("%+v", assTypes) -} - func TestGetAllProducts(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) // testPairs := []string{testPair.String(), "ETH-USD"} @@ -178,7 +174,7 @@ func TestGetAllProducts(t *testing.T) { t.Error(err) } assert.NotEmpty(t, resp, errExpectedNonEmpty) - log.Printf("%+v\n%+v", resp.NumProducts, len(resp.Products)) + // log.Printf("%+v\n%+v", resp.NumProducts, len(resp.Products)) } func TestGetProductByID(t *testing.T) { @@ -379,6 +375,137 @@ func TestGetTransactionSummary(t *testing.T) { } } +func TestCreateConvertQuote(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) + _, err := c.CreateConvertQuote(context.Background(), "", "", "", "", 0) + if !errors.Is(err, errAccountIDEmpty) { + t.Errorf(errExpectMismatch, err, errAccountIDEmpty) + } + _, err = c.CreateConvertQuote(context.Background(), "meow", "123", "", "", 0) + if !errors.Is(err, errAmountEmpty) { + t.Errorf(errExpectMismatch, err, errAmountEmpty) + } + accIDs, err := c.GetAllAccounts(context.Background(), 250, "") + if err != nil { + t.Error(err) + } + if len(accIDs.Accounts) == 0 { + t.Fatal(errExpectedNonEmpty) + } + var ( + fromAccID string + toAccID string + ) + for x := range accIDs.Accounts { + if accIDs.Accounts[x].Currency == "USDC" { + fromAccID = accIDs.Accounts[x].UUID + } + if accIDs.Accounts[x].Currency == "USD" { + toAccID = accIDs.Accounts[x].UUID + } + if fromAccID != "" && toAccID != "" { + break + } + } + if fromAccID == "" || toAccID == "" { + t.Skip(skipInsufSuitableAccs) + } + _, err = c.CreateConvertQuote(context.Background(), fromAccID, toAccID, "", "", 0.01) + if err != nil { + t.Error(err) + } +} + +func TestCommitConvertTrade(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) + _, err := c.CommitConvertTrade(context.Background(), "", "", "") + if !errors.Is(err, errTransactionIDEmpty) { + t.Errorf(errExpectMismatch, err, errTransactionIDEmpty) + } + _, err = c.CommitConvertTrade(context.Background(), "meow", "", "") + if !errors.Is(err, errAccountIDEmpty) { + t.Errorf(errExpectMismatch, err, errAccountIDEmpty) + } + accIDs, err := c.GetAllAccounts(context.Background(), 250, "") + if err != nil { + t.Error(err) + } + if len(accIDs.Accounts) == 0 { + t.Fatal(errExpectedNonEmpty) + } + var ( + fromAccID string + toAccID string + ) + for x := range accIDs.Accounts { + if accIDs.Accounts[x].Currency == "USDC" { + fromAccID = accIDs.Accounts[x].UUID + } + if accIDs.Accounts[x].Currency == "USD" { + toAccID = accIDs.Accounts[x].UUID + } + if fromAccID != "" && toAccID != "" { + break + } + } + if fromAccID == "" || toAccID == "" { + t.Skip(skipInsufSuitableAccs) + } + resp, err := c.CreateConvertQuote(context.Background(), fromAccID, toAccID, "", "", 0.01) + if err != nil { + t.Error(err) + } + _, err = c.CommitConvertTrade(context.Background(), resp.Trade.ID, fromAccID, toAccID) + if err != nil { + t.Error(err) + } +} + +func TestGetConvertTradeByID(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) + _, err := c.GetConvertTradeByID(context.Background(), "", "", "") + if !errors.Is(err, errTransactionIDEmpty) { + t.Errorf(errExpectMismatch, err, errTransactionIDEmpty) + } + _, err = c.GetConvertTradeByID(context.Background(), "meow", "", "") + if !errors.Is(err, errAccountIDEmpty) { + t.Errorf(errExpectMismatch, err, errAccountIDEmpty) + } + accIDs, err := c.GetAllAccounts(context.Background(), 250, "") + if err != nil { + t.Error(err) + } + if len(accIDs.Accounts) == 0 { + t.Fatal(errExpectedNonEmpty) + } + var ( + fromAccID string + toAccID string + ) + for x := range accIDs.Accounts { + if accIDs.Accounts[x].Currency == "USDC" { + fromAccID = accIDs.Accounts[x].UUID + } + if accIDs.Accounts[x].Currency == "USD" { + toAccID = accIDs.Accounts[x].UUID + } + if fromAccID != "" && toAccID != "" { + break + } + } + if fromAccID == "" || toAccID == "" { + t.Skip(skipInsufSuitableAccs) + } + resp, err := c.CreateConvertQuote(context.Background(), fromAccID, toAccID, "", "", 0.01) + if err != nil { + t.Error(err) + } + _, err = c.GetConvertTradeByID(context.Background(), resp.Trade.ID, fromAccID, toAccID) + if err != nil { + t.Error(err) + } +} + func TestListNotifications(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) _, err := c.ListNotifications(context.Background(), PaginationInp{}) @@ -634,10 +761,10 @@ func TestSendMoney(t *testing.T) { } } -func TestListTransactions(t *testing.T) { +func TestGetAllTransactions(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) var pag PaginationInp - _, err := c.ListTransactions(context.Background(), "", pag) + _, err := c.GetAllTransactions(context.Background(), "", pag) if !errors.Is(err, errWalletIDEmpty) { t.Errorf(errExpectMismatch, err, errWalletIDEmpty) } @@ -646,7 +773,7 @@ func TestListTransactions(t *testing.T) { t.Error(err) } assert.NotEmpty(t, wID, errExpectedNonEmpty) - _, err = c.ListTransactions(context.Background(), wID.Data.ID, pag) + _, err = c.GetAllTransactions(context.Background(), wID.Data.ID, pag) if err != nil { t.Error(err) } @@ -667,7 +794,7 @@ func TestGetTransactionByID(t *testing.T) { t.Error(err) } assert.NotEmpty(t, wID, errExpectedNonEmpty) - tID, err := c.ListTransactions(context.Background(), wID.Data.ID, PaginationInp{}) + tID, err := c.GetAllTransactions(context.Background(), wID.Data.ID, PaginationInp{}) if err != nil { t.Error(err) } @@ -2698,6 +2825,115 @@ func TestFetchAccountInfo(t *testing.T) { } } +func TestUpdateTickersS(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + err := c.UpdateTickers(context.Background(), asset.Empty) + if !errors.Is(err, asset.ErrNotSupported) { + t.Errorf(errExpectMismatch, err, asset.ErrNotSupported) + } + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + err = c.UpdateTickers(context.Background(), asset.Futures) + if err != nil { + t.Error(err) + } + err = c.UpdateTickers(context.Background(), asset.Spot) + if err != nil { + t.Error(err) + } +} + +func TestUpdateTicker(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + _, err := c.UpdateTicker(context.Background(), currency.Pair{}, asset.Empty) + if !errors.Is(err, currency.ErrCurrencyPairEmpty) { + t.Errorf(errExpectMismatch, err, currency.ErrCurrencyPairEmpty) + } + _, err = c.UpdateTicker(context.Background(), testPair, asset.Spot) + if err != nil { + t.Error(err) + } +} + +func TestFetchTicker(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + _, err := c.FetchTicker(context.Background(), testPair, asset.Spot) + if err != nil { + t.Error(err) + } +} + +func TestFetchOrderbook(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + _, err := c.FetchOrderbook(context.Background(), testPair, asset.Spot) + if err != nil { + t.Error(err) + } +} + +func TestUpdateOrderbook(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + _, err := c.UpdateOrderbook(context.Background(), currency.Pair{}, asset.Empty) + if !errors.Is(err, currency.ErrCurrencyPairEmpty) { + t.Errorf(errExpectMismatch, err, currency.ErrCurrencyPairEmpty) + } + _, err = c.UpdateOrderbook(context.Background(), currency.NewPairWithDelimiter("meow", "woof", "-"), asset.Spot) + if err != nil { + t.Error(err) + } + _, err = c.UpdateOrderbook(context.Background(), testPair, asset.Spot) + if err != nil { + t.Error(err) + } +} + +func TestProcessFundingData(t *testing.T) { + accHist := make([]DeposWithdrData, 1) + genAmcur := AmCur{Amount: 1, Currency: "DOGE"} + accHist[0] = DeposWithdrData{Status: "meow", ID: "woof", PayoutAt: time.Unix(1, 1).UTC(), Amount: genAmcur, + Fee: genAmcur, TransferType: FiatWithdrawal} + genAmcur.Amount = 2 + cryptHist := make([]TransactionData, 2) + cryptHist[0] = TransactionData{Status: "moo", ID: "oink", CreatedAt: time.Unix(2, 2).UTC(), Amount: genAmcur, + Type: "receive"} + cryptHist[0].Network.Name = "neigh" + cryptHist[0].To.ID = "The Barnyard" + cryptHist[1].Type = "send" + + expectedResult := make([]exchange.FundingHistory, 3) + expectedResult[0] = exchange.FundingHistory{ExchangeName: "CoinbasePro", Status: "meow", TransferID: "woof", + Timestamp: time.Unix(1, 1).UTC(), Currency: "DOGE", Amount: 1, Fee: 1, TransferType: "withdrawal"} + expectedResult[1] = exchange.FundingHistory{ExchangeName: "CoinbasePro", Status: "moo", TransferID: "oink", + Timestamp: time.Unix(2, 2).UTC(), Currency: "DOGE", Amount: 2, Fee: 0, TransferType: "deposit", + CryptoFromAddress: "The Barnyard", CryptoChain: "neigh"} + expectedResult[2] = exchange.FundingHistory{ExchangeName: "CoinbasePro", TransferType: "withdrawal"} + + resp := c.ProcessFundingData(accHist, cryptHist) + + if fmt.Sprint(expectedResult) != fmt.Sprint(resp) { + t.Errorf(errExpectMismatch, resp, expectedResult) + } +} + +func TestGetAccountFundingHistory(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + _, err := c.GetAccountFundingHistory(context.Background()) + if err != nil { + t.Error(err) + } +} + +func TestGetWithdrawalsHistory(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + _, err := c.GetWithdrawalsHistory(context.Background(), currency.NewCode("meow"), asset.Spot) + if !errors.Is(err, errNoMatchingWallets) { + t.Errorf(errExpectMismatch, err, errNoMatchingWallets) + } + _, err = c.GetWithdrawalsHistory(context.Background(), currency.BTC, asset.Spot) + if err != nil { + t.Error(err) + } +} + // 8837708 143.0 ns/op 24 B/op 5 allocs/op // func BenchmarkXxx(b *testing.B) { // for x := 0; x < b.N; x++ { diff --git a/exchanges/coinbasepro/coinbasepro_types.go b/exchanges/coinbasepro/coinbasepro_types.go index 5ffab69b4cb..a9ff7438c27 100644 --- a/exchanges/coinbasepro/coinbasepro_types.go +++ b/exchanges/coinbasepro/coinbasepro_types.go @@ -1,16 +1,27 @@ package coinbasepro import ( - "encoding/json" "net/url" - "strconv" - "strings" "time" "github.com/thrasher-corp/gocryptotrader/common/convert" "github.com/thrasher-corp/gocryptotrader/currency" + exchange "github.com/thrasher-corp/gocryptotrader/exchanges" ) +// CoinbasePro is the overarching type across the coinbasepro package +type CoinbasePro struct { + exchange.Base +} + +// Version is used for the niche cases where the Version of the API must be specified and passed +// around for proper functionality +type Version bool + +// FiatTransferType is used so that we don't need to duplicate the four fiat transfer-related +// endpoints under version 2 of the API +type FiatTransferType bool + // ValCur is a sub-struct used in the type Account type ValCur struct { Value float64 `json:"value,string"` @@ -144,24 +155,6 @@ type AllProducts struct { // the exchange type UnixTimestamp time.Time -func (t *UnixTimestamp) UnmarshalJSON(b []byte) error { - var timestampStr string - err := json.Unmarshal(b, ×tampStr) - if err != nil { - return err - } - timestamp, err := strconv.ParseInt(timestampStr, 10, 64) - if err != nil { - return err - } - *t = UnixTimestamp(time.Unix(timestamp, 0)) - return nil -} - -func (t UnixTimestamp) String() string { - return time.Time(t).String() -} - // History holds historic rate information, returned by GetHistoricRates type History struct { Candles []struct { @@ -186,8 +179,8 @@ type Ticker struct { Bid convert.StringToFloat64 `json:"bid"` Ask convert.StringToFloat64 `json:"ask"` } `json:"trades"` - BestBid float64 `json:"best_bid,string"` - BestAsk float64 `json:"best_ask,string"` + BestBid convert.StringToFloat64 `json:"best_bid"` + BestAsk convert.StringToFloat64 `json:"best_ask"` } // MarketMarketIOC is a sub-struct used in the type OrderConfiguration @@ -369,6 +362,106 @@ type GetAllOrdersResp struct { Cursor string `json:"cursor"` } +// FeeStruct is a sub-struct storing information on fees, used in ConvertResponse +type FeeStruct struct { + Title string `json:"title"` + Description string `json:"description"` + Amount ValCur `json:"amount"` + Label string `json:"label"` + Disclosure struct { + Title string `json:"title"` + Description string `json:"description"` + Link struct { + Text string `json:"text"` + URL string `json:"url"` + } `json:"link"` + } `json:"disclosure"` +} + +// AccountStruct is a sub-struct storing information on accounts, used in ConvertResponse +type AccountStruct struct { + Type string `json:"type"` + Network string `json:"network"` + LedgerAccount struct { + AccountID string `json:"account_id"` + Currency string `json:"currency"` + Owner struct { + ID string `json:"id"` + UUID string `json:"uuid"` + UserUUID string `json:"user_uuid"` + Type string `json:"type"` + } `json:"owner"` + } `json:"ledger_account"` +} + +// AmScale is a sub-struct storing information on amounts and scales, used in ConvertResponse +type AmScale struct { + Amount ValCur `json:"amount"` + Scale int32 `json:"scale"` +} + +// ConvertResponse contains information on a convert trade, returned by CreateConvertQuote, +// CommitConvertTrade, and GetConvertTrade +type ConvertResponse struct { + Trade struct { + ID string `json:"id"` + Status string `json:"status"` + UserEnteredAmount ValCur `json:"user_entered_amount"` + Amount ValCur `json:"amount"` + Subtotal ValCur `json:"subtotal"` + Total ValCur `json:"total"` + Fees []FeeStruct `json:"fees"` + TotalFee FeeStruct `json:"total_fee"` + Source AccountStruct `json:"source"` + Target AccountStruct `json:"target"` + UnitPrice struct { + TargetToFiat AmScale `json:"target_to_fiat"` + TargetToSource AmScale `json:"target_to_source"` + SourceToFiat AmScale `json:"source_to_fiat"` + } `json:"unit_price"` + UserWarnings []struct { + ID string `json:"id"` + Link struct { + Text string `json:"text"` + URL string `json:"url"` + } `json:"link"` + Context struct { + Details []string `json:"details"` + Title string `json:"title"` + LinkText string `json:"link_text"` + } `json:"context"` + Code string `json:"code"` + Message string `json:"message"` + } `json:"user_warnings"` + UserReference string `json:"user_reference"` + SourceCurrency string `json:"source_currency"` + TargetCurrency string `json:"target_currency"` + CancellationReason struct { + Message string `json:"message"` + Code string `json:"code"` + ErrorCode string `json:"error_code"` + ErrorCTA string `json:"error_cta"` + } `json:"cancellation_reason"` + SourceID string `json:"source_id"` + TargetID string `json:"target_id"` + ExchangeRate ValCur `json:"exchange_rate"` + TaxDetails []struct { + Name string `json:"name"` + Amount ValCur `json:"amount"` + } `json:"tax_details"` + TradeIncentiveInfo struct { + AppliedIncentive bool `json:"applied_incentive"` + UserIncentiveID string `json:"user_incentive_id"` + CodeVal string `json:"code_val"` + EndsAt time.Time `json:"ends_at"` + FeeWithoutIncentive ValCur `json:"fee_without_incentive"` + Redeemed bool `json:"redeemed"` + } `json:"trade_incentive_info"` + TotalFeeWithoutTax FeeStruct `json:"total_fee_without_tax"` + FiatDenotedTotal ValCur `json:"fiat_denoted_total"` + } `json:"trade"` +} + // IDResource holds an ID, resource type, and associated data, used in ListNotificationsResponse, // TransactionData type IDResource struct { @@ -639,15 +732,11 @@ type TransactionData struct { Details struct { Title string `json:"title"` Subtitle string `json:"subtitle"` - // SubsidebarLabel string `json:"subsidebar_label"` - // Header string `json:"header"` - // Health string `json:"health"` } `json:"details"` Network struct { Status string `json:"status"` - // StatusDescription string `json:"status_description"` - Hash string `json:"hash"` - Name string `json:"name"` + Hash string `json:"hash"` + Name string `json:"name"` } `json:"network"` To IDResource `json:"to"` From IDResource `json:"from"` @@ -685,6 +774,7 @@ type DeposWithdrData struct { Committed bool `json:"committed"` Fee AmCur `json:"fee"` PayoutAt time.Time `json:"payout_at"` + TransferType FiatTransferType } // GenDeposWithdrResp holds information on a deposit. Returned by DepositFunds, CommitDeposit, @@ -1401,23 +1491,6 @@ type TransferResponse struct { type ExchTime time.Time -func (t *ExchTime) UnmarshalJSON(b []byte) error { - s := strings.Trim(string(b), `"`) - if s == " " || s == "null" { - return nil - } - tt, err := time.Parse("2006-01-02 15:04:05.999999-07", s) - if err != nil { - return err - } - *t = ExchTime(tt) - return nil -} - -func (t ExchTime) String() string { - return time.Time(t).String() -} - // TravelRule contains information on a travel rule, returned by GetTravelRules // and CreateTravelRule type TravelRule struct { @@ -1511,17 +1584,6 @@ type CryptoAddressResponse struct { CallbackURL string `json:"callback_url"` } -// ConvertResponse contains information about a completed currency conversion, returned -// by ConvertCurrency and GetConversionByID -type ConvertResponse struct { - ID string `json:"id"` - Amount string `json:"amount"` - FromAccountID string `json:"from_account_id"` - ToAccountID string `json:"to_account_id"` - From string `json:"from"` - To string `json:"to"` -} - // WithdrawalFeeEstimate is the exchange's estimate of the fee for a withdrawal, returned // by GetWithdrawalFeeEstimate type WithdrawalFeeEstimate struct { @@ -1540,22 +1602,6 @@ type FeeResponse struct { // PriceMap is used to properly unmarshal the response from GetSignedPrices type PriceMap map[string]float64 -func (pm *PriceMap) UnmarshalJSON(data []byte) error { - var m map[string]string - if err := json.Unmarshal(data, &m); err != nil { - return err - } - *pm = make(PriceMap) - for k, v := range m { - f, err := strconv.ParseFloat(v, 64) - if err != nil { - return err - } - (*pm)[k] = f - } - return nil -} - // SignedPrices contains cryptographically signed prices, alongside other information // necessary for them to be posted on-chain using Compound's Open Oracle smart contract. // Returned by GetSignedPrices diff --git a/exchanges/coinbasepro/coinbasepro_wrapper.go b/exchanges/coinbasepro/coinbasepro_wrapper.go index ba11926b859..a31c151917e 100644 --- a/exchanges/coinbasepro/coinbasepro_wrapper.go +++ b/exchanges/coinbasepro/coinbasepro_wrapper.go @@ -340,50 +340,86 @@ func (c *CoinbasePro) FetchAccountInfo(ctx context.Context, assetType asset.Item return account.Holdings{}, err } acc, err := account.GetHoldings(c.Name, creds, assetType) + fmt.Printf("Error: %v\n", err) if err != nil { return c.UpdateAccountInfo(ctx, assetType) } return acc, nil } -// UpdateTickers updates the ticker for all currency pairs of a given asset type -func (c *CoinbasePro) UpdateTickers(_ context.Context, _ asset.Item) error { - return common.ErrFunctionNotSupported +// UpdateTickers updates all currency pairs of a given asset type +func (c *CoinbasePro) UpdateTickers(ctx context.Context, assetType asset.Item) error { + var aTString string + switch assetType { + case asset.Futures: + aTString = "FUTURE" + case asset.Spot: + aTString = "SPOT" + default: + return fmt.Errorf("%w %v", asset.ErrNotSupported, assetType) + } + + products, err := c.GetAllProducts(ctx, 2<<30-1, 0, aTString, "", nil) + if err != nil { + return err + } + for x := range products.Products { + tick, err := c.GetTicker(ctx, products.Products[x].ID, 1) + if err != nil { + return err + } + pair, err := currency.NewPairDelimiter(products.Products[x].ID, currency.DashDelimiter) + if err != nil { + return err + } + var last float64 + if len(tick.Trades) != 0 { + last = tick.Trades[0].Price + } + err = ticker.ProcessTicker(&ticker.Price{ + Last: last, + Bid: tick.BestBid.Float64(), + Ask: tick.BestAsk.Float64(), + Pair: pair, + ExchangeName: c.Name, + AssetType: assetType, + }) + if err != nil { + return err + } + } + return nil } // UpdateTicker updates and returns the ticker for a currency pair func (c *CoinbasePro) UpdateTicker(ctx context.Context, p currency.Pair, a asset.Item) (*ticker.Price, error) { - // fPair, err := c.FormatExchangeCurrency(p, a) - // if err != nil { - // return nil, err - // } + fPair, err := c.FormatExchangeCurrency(p, a) + if err != nil { + return nil, err + } - // tick, err := c.GetTicker(ctx, fPair.String()) - // if err != nil { - // return nil, err - // } - // stats, err := c.GetStats(ctx, fPair.String()) - // if err != nil { - // return nil, err - // } + tick, err := c.GetTicker(ctx, fPair.String(), 1) + if err != nil { + return nil, err + } - // tickerPrice := &ticker.Price{ - // Last: stats.Last, - // High: stats.High, - // Low: stats.Low, - // Bid: tick.Bid, - // Ask: tick.Ask, - // Volume: tick.Volume, - // Open: stats.Open, - // Pair: p, - // LastUpdated: tick.Time, - // ExchangeName: c.Name, - // AssetType: a} - - // err = ticker.ProcessTicker(tickerPrice) - // if err != nil { - // return tickerPrice, err - // } + var last float64 + if len(tick.Trades) != 0 { + last = tick.Trades[0].Price + } + + tickerPrice := &ticker.Price{ + Last: last, + Bid: tick.BestBid.Float64(), + Ask: tick.BestAsk.Float64(), + Pair: p, + ExchangeName: c.Name, + AssetType: a} + + err = ticker.ProcessTicker(tickerPrice) + if err != nil { + return tickerPrice, err + } return ticker.GetTicker(c.Name, p, a) } @@ -414,66 +450,204 @@ func (c *CoinbasePro) UpdateOrderbook(ctx context.Context, p currency.Pair, asse if err := c.CurrencyPairs.IsAssetEnabled(assetType); err != nil { return nil, err } - // book := &orderbook.Base{ - // Exchange: c.Name, - // Pair: p, - // Asset: assetType, - // VerifyOrderbook: c.CanVerifyOrderbook, - // } - // fPair, err := c.FormatExchangeCurrency(p, assetType) - // if err != nil { - // return book, err - // } - - // orderbookNew, err := c.GetOrderbook(ctx, fPair.String(), 2) - // if err != nil { - // return book, err - // } + book := &orderbook.Base{ + Exchange: c.Name, + Pair: p, + Asset: assetType, + VerifyOrderbook: c.CanVerifyOrderbook, + } + fPair, err := c.FormatExchangeCurrency(p, assetType) + if err != nil { + return book, err + } - // obNew, ok := orderbookNew.(OrderbookL1L2) - // if !ok { - // return book, common.GetTypeAssertError("OrderbookL1L2", orderbookNew) - // } + orderbookNew, err := c.GetProductBook(ctx, fPair.String(), 1000) + if err != nil { + return book, err + } - // book.Bids = make(orderbook.Items, len(obNew.Bids)) - // for x := range obNew.Bids { - // book.Bids[x] = orderbook.Item{ - // Amount: obNew.Bids[x].Amount, - // Price: obNew.Bids[x].Price, - // } - // } + book.Bids = make(orderbook.Items, len(orderbookNew.Bids)) + for x := range orderbookNew.Bids { + book.Bids[x] = orderbook.Item{ + Amount: orderbookNew.Bids[x].Size, + Price: orderbookNew.Bids[x].Price, + } + } - // book.Asks = make(orderbook.Items, len(obNew.Asks)) - // for x := range obNew.Asks { - // book.Asks[x] = orderbook.Item{ - // Amount: obNew.Asks[x].Amount, - // Price: obNew.Asks[x].Price, - // } - // } - // err = book.Process() - // if err != nil { - // return book, err - // } + book.Asks = make(orderbook.Items, len(orderbookNew.Asks)) + for x := range orderbookNew.Asks { + book.Asks[x] = orderbook.Item{ + Amount: orderbookNew.Asks[x].Size, + Price: orderbookNew.Asks[x].Price, + } + } + err = book.Process() + if err != nil { + return book, err + } return orderbook.Get(c.Name, p, assetType) } +// ProcessFundingData is a helper function for GetAccountFundingHistory and GetWithdrawalsHistory +func (c *CoinbasePro) ProcessFundingData(accHistory []DeposWithdrData, cryptoHistory []TransactionData) []exchange.FundingHistory { + fundingData := make([]exchange.FundingHistory, len(accHistory)+len(cryptoHistory)) + for i := range accHistory { + fundingData[i] = exchange.FundingHistory{ + ExchangeName: c.Name, + Status: accHistory[i].Status, + TransferID: accHistory[i].ID, + Timestamp: accHistory[i].PayoutAt, + Currency: accHistory[i].Amount.Currency, + Amount: accHistory[i].Amount.Amount, + Fee: accHistory[i].Fee.Amount, + TransferType: accHistory[i].TransferType.String(), + } + } + + for i := range cryptoHistory { + fundingData[i+len(accHistory)] = exchange.FundingHistory{ + ExchangeName: c.Name, + Status: cryptoHistory[i].Status, + TransferID: cryptoHistory[i].ID, + Description: cryptoHistory[i].Details.Title + cryptoHistory[i].Details.Subtitle, + Timestamp: cryptoHistory[i].CreatedAt, + Currency: cryptoHistory[i].Amount.Currency, + Amount: cryptoHistory[i].Amount.Amount, + CryptoChain: cryptoHistory[i].Network.Name, + } + if cryptoHistory[i].Type == "receive" { + fundingData[i+len(accHistory)].TransferType = "deposit" + fundingData[i+len(accHistory)].CryptoFromAddress = cryptoHistory[i].To.ID + } + if cryptoHistory[i].Type == "send" { + fundingData[i+len(accHistory)].TransferType = "withdrawal" + fundingData[i+len(accHistory)].CryptoToAddress = cryptoHistory[i].From.ID + } + } + return fundingData +} + // GetAccountFundingHistory returns funding history, deposits and // withdrawals -func (c *CoinbasePro) GetAccountFundingHistory(_ context.Context) ([]exchange.FundingHistory, error) { - return nil, common.ErrFunctionNotSupported +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, errors.New("no wallets returned") + } + + var accHistory []DeposWithdrData + + for i := range wallIDs.Data { + tempAccHist, err := c.GetAllFiatTransfers(ctx, wallIDs.Data[i].ID, PaginationInp{}, FiatDeposit) + if err != nil { + return nil, err + } + accHistory = append(accHistory, tempAccHist.Data...) + tempAccHist, err = c.GetAllFiatTransfers(ctx, wallIDs.Data[i].ID, PaginationInp{}, FiatWithdrawal) + if err != nil { + return nil, err + } + 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 { + return nil, err + } + for j := range tempCryptoHist.Data { + if tempCryptoHist.Data[j].Type == "receive" || tempCryptoHist.Data[j].Type == "send" { + cryptoHistory = append(cryptoHistory, tempCryptoHist.Data[j]) + } + } + } + + fundingData := c.ProcessFundingData(accHistory, cryptoHistory) + + return fundingData, nil } // GetWithdrawalsHistory returns previous withdrawals data -func (c *CoinbasePro) GetWithdrawalsHistory(_ context.Context, _ currency.Code, _ asset.Item) ([]exchange.WithdrawalHistory, error) { - // while fetching withdrawal history is possible, the API response lacks any useful information - // like the currency withdrawn and thus is unsupported. If that position changes, use GetTransfers(...) - return nil, common.ErrFunctionNotSupported +func (c *CoinbasePro) GetWithdrawalsHistory(ctx context.Context, cur currency.Code, a asset.Item) ([]exchange.WithdrawalHistory, error) { + tempWallIDs, err := c.GetAllWallets(ctx, PaginationInp{}) + + if err != nil { + return nil, err + } + if len(tempWallIDs.Data) == 0 { + return nil, errors.New("no wallets returned") + } + + 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 { + return nil, err + } + accHistory = append(accHistory, tempAccHist.Data...) + } + + var cryptoHistory []TransactionData + + for i := range wallIDs { + tempCryptoHist, err := c.GetAllTransactions(ctx, wallIDs[i], PaginationInp{}) + if err != nil { + return nil, err + } + for j := range tempCryptoHist.Data { + if tempCryptoHist.Data[j].Type == "send" { + cryptoHistory = append(cryptoHistory, tempCryptoHist.Data[j]) + } + } + } + + tempFundingData := c.ProcessFundingData(accHistory, cryptoHistory) + + fundingData := make([]exchange.WithdrawalHistory, len(tempFundingData)) + + for i := range tempFundingData { + fundingData[i] = exchange.WithdrawalHistory{ + Status: tempFundingData[i].Status, + TransferID: tempFundingData[i].TransferID, + Description: tempFundingData[i].Description, + Timestamp: tempFundingData[i].Timestamp, + Currency: tempFundingData[i].Currency, + Amount: tempFundingData[i].Amount, + Fee: tempFundingData[i].Fee, + TransferType: tempFundingData[i].TransferType, + CryptoToAddress: tempFundingData[i].CryptoToAddress, + CryptoTxID: tempFundingData[i].CryptoTxID, + CryptoChain: tempFundingData[i].CryptoChain, + BankTo: tempFundingData[i].BankTo, + } + } + + return fundingData, nil } // GetRecentTrades returns the most recent trades for a currency and asset func (c *CoinbasePro) GetRecentTrades(ctx context.Context, p currency.Pair, assetType asset.Item) ([]trade.Data, error) { - var err error - p, err = c.FormatExchangeCurrency(p, assetType) + + p, err := c.FormatExchangeCurrency(p, assetType) if err != nil { return nil, err } From 0564e45b4c3b5e4a1c8fcce9002791d3af4dd0ac Mon Sep 17 00:00:00 2001 From: Samuel Reid <43227667+cranktakular@users.noreply.github.com> Date: Mon, 11 Dec 2023 17:22:17 +1100 Subject: [PATCH 12/79] Coinbase wrapper & codebase cleanup --- common/common.go | 5 +- engine/datahistory_manager.go | 4 +- engine/datahistory_manager_test.go | 8 +- engine/rpcserver.go | 7 +- exchanges/account/account.go | 7 +- exchanges/account/account_test.go | 17 +- exchanges/coinbasepro/coinbasepro.go | 55 +-- exchanges/coinbasepro/coinbasepro_test.go | 195 ++++++++++- exchanges/coinbasepro/coinbasepro_types.go | 10 +- exchanges/coinbasepro/coinbasepro_wrapper.go | 349 +++++++++++++++---- exchanges/order/order_test.go | 2 +- exchanges/order/order_types.go | 34 ++ exchanges/order/orders.go | 5 +- exchanges/orderbook/orderbook.go | 5 +- exchanges/orderbook/orderbook_test.go | 5 +- exchanges/orderbook/orderbook_types.go | 1 - exchanges/ticker/ticker.go | 3 +- exchanges/ticker/ticker_types.go | 7 +- portfolio/withdraw/validate.go | 3 +- portfolio/withdraw/validate_test.go | 3 +- portfolio/withdraw/withdraw_types.go | 2 - 21 files changed, 581 insertions(+), 146 deletions(-) diff --git a/common/common.go b/common/common.go index 93e66a5f40a..4edd8ffe511 100644 --- a/common/common.go +++ b/common/common.go @@ -55,7 +55,10 @@ var ( ErrNotYetImplemented = errors.New("not yet implemented") // ErrFunctionNotSupported defines a standardised error for an unsupported // wrapper function by an API - ErrFunctionNotSupported = errors.New("unsupported wrapper function") + ErrFunctionNotSupported = errors.New("unsupported wrapper function") + // ErrExchangeNameUnset defines a common error across the code base for + // when the exchange name is unset + ErrExchangeNameUnset = errors.New("exchange name unset") errInvalidCryptoCurrency = errors.New("invalid crypto currency") // ErrDateUnset is an error for start end check calculations ErrDateUnset = errors.New("date unset") diff --git a/engine/datahistory_manager.go b/engine/datahistory_manager.go index 0e5e8ca9ab2..1b26f6974d1 100644 --- a/engine/datahistory_manager.go +++ b/engine/datahistory_manager.go @@ -1195,11 +1195,11 @@ func (m *DataHistoryManager) validateJob(job *DataHistoryJob) error { exchangeName := job.Exchange if job.DataType == dataHistoryCandleValidationSecondarySourceType { if job.SecondaryExchangeSource == "" { - return fmt.Errorf("job %s %w, secondary exchange name required to lookup existing results", job.Nickname, errExchangeNameUnset) + return fmt.Errorf("job %s %w, secondary exchange name required to lookup existing results", job.Nickname, common.ErrExchangeNameUnset) } exchangeName = job.SecondaryExchangeSource if job.Exchange == "" { - return fmt.Errorf("job %s %w, exchange name required to lookup existing results", job.Nickname, errExchangeNameUnset) + return fmt.Errorf("job %s %w, exchange name required to lookup existing results", job.Nickname, common.ErrExchangeNameUnset) } } exch, err := m.exchangeManager.GetExchangeByName(exchangeName) diff --git a/engine/datahistory_manager_test.go b/engine/datahistory_manager_test.go index 927494874ba..7a0a4ce8b46 100644 --- a/engine/datahistory_manager_test.go +++ b/engine/datahistory_manager_test.go @@ -529,14 +529,14 @@ func TestValidateJob(t *testing.T) { dhj.DataType = dataHistoryCandleValidationSecondarySourceType err = m.validateJob(dhj) - if !errors.Is(err, errExchangeNameUnset) { - t.Errorf("error '%v', expected '%v'", err, errExchangeNameUnset) + if !errors.Is(err, common.ErrExchangeNameUnset) { + t.Errorf("error '%v', expected '%v'", err, common.ErrExchangeNameUnset) } dhj.SecondaryExchangeSource = "lol" dhj.Exchange = "" err = m.validateJob(dhj) - if !errors.Is(err, errExchangeNameUnset) { - t.Errorf("error '%v', expected '%v'", err, errExchangeNameUnset) + if !errors.Is(err, common.ErrExchangeNameUnset) { + t.Errorf("error '%v', expected '%v'", err, common.ErrExchangeNameUnset) } } diff --git a/engine/rpcserver.go b/engine/rpcserver.go index f2824c9eac2..0aaa25c53f5 100644 --- a/engine/rpcserver.go +++ b/engine/rpcserver.go @@ -63,7 +63,6 @@ var ( errExchangeNotEnabled = errors.New("exchange is not enabled") errExchangeBaseNotFound = errors.New("cannot get exchange base") errInvalidArguments = errors.New("invalid arguments received") - errExchangeNameUnset = errors.New("exchange name unset") errCurrencyPairUnset = errors.New("currency pair unset") errInvalidTimes = errors.New("invalid start and end times") errAssetTypeUnset = errors.New("asset type unset") @@ -2181,7 +2180,7 @@ func (s *RPCServer) GetOrderbookStream(r *gctrpc.GetOrderbookStreamRequest, stre // GetExchangeOrderbookStream streams all orderbooks associated with an exchange func (s *RPCServer) GetExchangeOrderbookStream(r *gctrpc.GetExchangeOrderbookStreamRequest, stream gctrpc.GoCryptoTraderService_GetExchangeOrderbookStreamServer) error { if r.Exchange == "" { - return errExchangeNameUnset + return common.ErrExchangeNameUnset } if _, err := s.GetExchangeByName(r.Exchange); err != nil { @@ -2249,7 +2248,7 @@ func (s *RPCServer) GetExchangeOrderbookStream(r *gctrpc.GetExchangeOrderbookStr // GetTickerStream streams the requested updated ticker func (s *RPCServer) GetTickerStream(r *gctrpc.GetTickerStreamRequest, stream gctrpc.GoCryptoTraderService_GetTickerStreamServer) error { if r.Exchange == "" { - return errExchangeNameUnset + return common.ErrExchangeNameUnset } if _, err := s.GetExchangeByName(r.Exchange); err != nil { @@ -2320,7 +2319,7 @@ func (s *RPCServer) GetTickerStream(r *gctrpc.GetTickerStreamRequest, stream gct // GetExchangeTickerStream streams all tickers associated with an exchange func (s *RPCServer) GetExchangeTickerStream(r *gctrpc.GetExchangeTickerStreamRequest, stream gctrpc.GoCryptoTraderService_GetExchangeTickerStreamServer) error { if r.Exchange == "" { - return errExchangeNameUnset + return common.ErrExchangeNameUnset } if _, err := s.GetExchangeByName(r.Exchange); err != nil { diff --git a/exchanges/account/account.go b/exchanges/account/account.go index 043635d92ac..186618f5453 100644 --- a/exchanges/account/account.go +++ b/exchanges/account/account.go @@ -20,7 +20,6 @@ func init() { var ( errHoldingsIsNil = errors.New("holdings cannot be nil") - errExchangeNameUnset = errors.New("exchange name unset") errExchangeHoldingsNotFound = errors.New("exchange holdings not found") errAssetHoldingsNotFound = errors.New("asset holdings not found") errExchangeAccountsNotFound = errors.New("exchange accounts not found") @@ -76,7 +75,7 @@ func Process(h *Holdings, c *Credentials) error { // TODO: Add jurisdiction and differentiation between APIKEY holdings. func GetHoldings(exch string, creds *Credentials, assetType asset.Item) (Holdings, error) { if exch == "" { - return Holdings{}, errExchangeNameUnset + return Holdings{}, common.ErrExchangeNameUnset } if creds.IsEmpty() { @@ -152,7 +151,7 @@ func GetHoldings(exch string, creds *Credentials, assetType asset.Item) (Holding // GetBalance returns the internal balance for that asset item. func GetBalance(exch, subAccount string, creds *Credentials, ai asset.Item, c currency.Code) (*ProtectedBalance, error) { if exch == "" { - return nil, fmt.Errorf("cannot get balance: %w", errExchangeNameUnset) + return nil, fmt.Errorf("cannot get balance: %w", common.ErrExchangeNameUnset) } if !ai.IsValid() { @@ -201,7 +200,7 @@ func (s *Service) Update(incoming *Holdings, creds *Credentials) error { } if incoming.Exchange == "" { - return fmt.Errorf("cannot update holdings: %w", errExchangeNameUnset) + return fmt.Errorf("cannot update holdings: %w", common.ErrExchangeNameUnset) } if creds.IsEmpty() { diff --git a/exchanges/account/account_test.go b/exchanges/account/account_test.go index a3b80f8165b..eb416ba7e75 100644 --- a/exchanges/account/account_test.go +++ b/exchanges/account/account_test.go @@ -6,6 +6,7 @@ import ( "testing" "time" + "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/common/key" "github.com/thrasher-corp/gocryptotrader/currency" "github.com/thrasher-corp/gocryptotrader/dispatch" @@ -72,8 +73,8 @@ func TestGetHoldings(t *testing.T) { } err = Process(&Holdings{}, nil) - if !errors.Is(err, errExchangeNameUnset) { - t.Fatalf("received: '%v' but expected: '%v'", err, errExchangeNameUnset) + if !errors.Is(err, common.ErrExchangeNameUnset) { + t.Fatalf("received: '%v' but expected: '%v'", err, common.ErrExchangeNameUnset) } holdings := Holdings{ @@ -145,8 +146,8 @@ func TestGetHoldings(t *testing.T) { } _, err = GetHoldings("", nil, asset.Spot) - if !errors.Is(err, errExchangeNameUnset) { - t.Fatalf("received: '%v' but expected: '%v'", err, errExchangeNameUnset) + if !errors.Is(err, common.ErrExchangeNameUnset) { + t.Fatalf("received: '%v' but expected: '%v'", err, common.ErrExchangeNameUnset) } _, err = GetHoldings("bla", nil, asset.Spot) @@ -245,8 +246,8 @@ func TestGetHoldings(t *testing.T) { func TestGetBalance(t *testing.T) { _, err := GetBalance("", "", nil, asset.Empty, currency.Code{}) - if !errors.Is(err, errExchangeNameUnset) { - t.Fatalf("received: '%v' but expected: '%v'", err, errExchangeNameUnset) + if !errors.Is(err, common.ErrExchangeNameUnset) { + t.Fatalf("received: '%v' but expected: '%v'", err, common.ErrExchangeNameUnset) } _, err = GetBalance("bruh", "", nil, asset.Empty, currency.Code{}) @@ -408,8 +409,8 @@ func TestUpdate(t *testing.T) { } err = s.Update(&Holdings{}, nil) - if !errors.Is(err, errExchangeNameUnset) { - t.Fatalf("received: '%v' but expected: '%v'", err, errExchangeNameUnset) + if !errors.Is(err, common.ErrExchangeNameUnset) { + t.Fatalf("received: '%v' but expected: '%v'", err, common.ErrExchangeNameUnset) } err = s.Update(&Holdings{ diff --git a/exchanges/coinbasepro/coinbasepro.go b/exchanges/coinbasepro/coinbasepro.go index 124f6e8bbbb..9e9d2e7c437 100644 --- a/exchanges/coinbasepro/coinbasepro.go +++ b/exchanges/coinbasepro/coinbasepro.go @@ -90,8 +90,7 @@ var ( errAccountIDEmpty = errors.New("account id cannot be empty") errClientOrderIDEmpty = errors.New("client order id cannot be empty") errProductIDEmpty = errors.New("product id cannot be empty") - errAmountZeroNonMarketBuy = errors.New("base amount cannot be 0 unless a market buy order") - errAmountZeroMarketBuy = errors.New("quote amount cannot be 0 for market buy orders") + errAmountZero = errors.New("amount cannot be 0") errOrderIDEmpty = errors.New("order ids cannot be empty") errOpenPairWithOtherTypes = errors.New("cannot pair open orders with other order types") errUserIDEmpty = errors.New("user id cannot be empty") @@ -109,6 +108,8 @@ var ( errInvalidPriceType = errors.New("price type must be spot, buy, or sell") errInvalidOrderType = errors.New("order type must be market, limit, or stop") errNoMatchingWallets = errors.New("no matching wallets returned") + errOrderModFailNoErr = errors.New("order modification failed but no error returned") + errNoMatchingOrders = errors.New("no matching orders returned") ) // GetAllAccounts returns information on all trading accounts associated with the API key @@ -275,18 +276,15 @@ func (c *CoinbasePro) GetTicker(ctx context.Context, productID string, limit uin } // PlaceOrder places either a limit, market, or stop order -func (c *CoinbasePro) PlaceOrder(ctx context.Context, clientOID, productID, side, stopDirection, orderType string, baseAmount, quoteAmount, limitPrice, stopPrice float64, postOnly bool, endTime time.Time) (*PlaceOrderResp, error) { +func (c *CoinbasePro) PlaceOrder(ctx context.Context, clientOID, productID, side, stopDirection, orderType string, amount, limitPrice, stopPrice float64, postOnly bool, endTime time.Time) (*PlaceOrderResp, error) { if clientOID == "" { return nil, errClientOrderIDEmpty } if productID == "" { return nil, errProductIDEmpty } - if baseAmount == 0 && orderType != order.Market.Lower() && side != order.Buy.String() { - return nil, errAmountZeroNonMarketBuy - } - if quoteAmount == 0 && orderType == order.Market.Lower() && side == order.Buy.String() { - return nil, errAmountZeroMarketBuy + if amount == 0 { + return nil, errAmountZero } var resp PlaceOrderResp @@ -294,38 +292,38 @@ func (c *CoinbasePro) PlaceOrder(ctx context.Context, clientOID, productID, side var orderConfig OrderConfiguration switch orderType { - case order.Market.Lower(): + case order.Market.String(), order.ImmediateOrCancel.String(): orderConfig.MarketMarketIOC = &MarketMarketIOC{} - if side == order.Buy.Lower() { - orderConfig.MarketMarketIOC.QuoteSize = strconv.FormatFloat(quoteAmount, 'f', -1, 64) + if side == order.Buy.String() { + orderConfig.MarketMarketIOC.QuoteSize = strconv.FormatFloat(amount, 'f', -1, 64) } - if side == order.Sell.Lower() { - orderConfig.MarketMarketIOC.BaseSize = strconv.FormatFloat(baseAmount, 'f', -1, 64) + if side == order.Sell.String() { + orderConfig.MarketMarketIOC.BaseSize = strconv.FormatFloat(amount, 'f', -1, 64) } - case order.Limit.Lower(): + case order.Limit.String(): if endTime == (time.Time{}) { orderConfig.LimitLimitGTC = &LimitLimitGTC{} - orderConfig.LimitLimitGTC.BaseSize = strconv.FormatFloat(baseAmount, 'f', -1, 64) + orderConfig.LimitLimitGTC.BaseSize = strconv.FormatFloat(amount, 'f', -1, 64) orderConfig.LimitLimitGTC.LimitPrice = strconv.FormatFloat(limitPrice, 'f', -1, 64) orderConfig.LimitLimitGTC.PostOnly = postOnly } else { orderConfig.LimitLimitGTD = &LimitLimitGTD{} - orderConfig.LimitLimitGTD.BaseSize = strconv.FormatFloat(baseAmount, 'f', -1, 64) + orderConfig.LimitLimitGTD.BaseSize = strconv.FormatFloat(amount, 'f', -1, 64) orderConfig.LimitLimitGTD.LimitPrice = strconv.FormatFloat(limitPrice, 'f', -1, 64) orderConfig.LimitLimitGTD.PostOnly = postOnly orderConfig.LimitLimitGTD.EndTime = endTime } - case order.Stop.Lower(): + case order.StopLimit.String(): if endTime == (time.Time{}) { orderConfig.StopLimitStopLimitGTC = &StopLimitStopLimitGTC{} - orderConfig.StopLimitStopLimitGTC.BaseSize = strconv.FormatFloat(baseAmount, 'f', -1, 64) + orderConfig.StopLimitStopLimitGTC.BaseSize = strconv.FormatFloat(amount, 'f', -1, 64) orderConfig.StopLimitStopLimitGTC.LimitPrice = strconv.FormatFloat(limitPrice, 'f', -1, 64) orderConfig.StopLimitStopLimitGTC.StopPrice = strconv.FormatFloat(stopPrice, 'f', -1, 64) orderConfig.StopLimitStopLimitGTC.StopDirection = stopDirection } else { orderConfig.StopLimitStopLimitGTD = &StopLimitStopLimitGTD{} - orderConfig.StopLimitStopLimitGTD.BaseSize = strconv.FormatFloat(baseAmount, 'f', -1, 64) + orderConfig.StopLimitStopLimitGTD.BaseSize = strconv.FormatFloat(amount, 'f', -1, 64) orderConfig.StopLimitStopLimitGTD.LimitPrice = strconv.FormatFloat(limitPrice, 'f', -1, 64) orderConfig.StopLimitStopLimitGTD.StopPrice = strconv.FormatFloat(stopPrice, 'f', -1, 64) @@ -356,7 +354,7 @@ func (c *CoinbasePro) CancelOrders(ctx context.Context, orderIDs []string) (Canc req := map[string]interface{}{"order_ids": orderIDs} return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, path, "", - nil, Version3, req, nil) + req, Version3, &resp, nil) } // EditOrder edits an order to a new size or price. Only limit orders with a good-till-cancelled time @@ -582,6 +580,14 @@ func (c *CoinbasePro) GetConvertTradeByID(ctx context.Context, tradeID, from, to "", req, Version3, &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 + + return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, + coinbaseV3+coinbaseTime, "", nil, Version3, &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 @@ -1076,9 +1082,9 @@ func (c *CoinbasePro) GetPrice(ctx context.Context, currencyPair, priceType stri return resp, c.SendHTTPRequest(ctx, exchange.RestSpot, path, &resp) } -// GetCurrentServerTime returns the API server time -func (c *CoinbasePro) GetCurrentServerTime(ctx context.Context) (ServerTime, error) { - resp := ServerTime{} +// GetV2Time returns the current server time, calling V2 of the API +func (c *CoinbasePro) GetV2Time(ctx context.Context) (ServerTimeV2, error) { + resp := ServerTimeV2{} return resp, c.SendHTTPRequest(ctx, exchange.RestSpot, coinbaseV2+coinbaseTime, &resp) } @@ -1944,7 +1950,8 @@ func (c *CoinbasePro) SendAuthenticatedHTTPRequest(ctx context.Context, ep excha PreviewFailureReason string `json:"preview_failure_reason"` } }{} - if err := json.Unmarshal(interim, &manyErrCap); err == nil { + err = json.Unmarshal(interim, &manyErrCap) + if err == nil { if len(manyErrCap.Errors) > 0 { errMessage := "" for i := range manyErrCap.Errors { diff --git a/exchanges/coinbasepro/coinbasepro_test.go b/exchanges/coinbasepro/coinbasepro_test.go index 17bebd2a13a..992f17b0eb6 100644 --- a/exchanges/coinbasepro/coinbasepro_test.go +++ b/exchanges/coinbasepro/coinbasepro_test.go @@ -3,6 +3,7 @@ package coinbasepro import ( "context" "errors" + "strconv" "fmt" "log" @@ -45,6 +46,8 @@ const ( errExpectMismatch = "received: '%v' but expected: '%v'" errExpectedNonEmpty = "expected non-empty response" + errOrder0CancelFail = "order 0 failed to cancel" + errIDNotSet = "ID not set" skipPayMethodNotFound = "no payment methods found, skipping" skipInsufSuitableAccs = "insufficient suitable accounts found, skipping" errx7f = "setting proxy address error parse \"\\x7f\": net/url: invalid control character in URL" @@ -223,27 +226,22 @@ func TestGetTicker(t *testing.T) { func TestPlaceOrder(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) - _, err := c.PlaceOrder(context.Background(), "", "", "", "", "", 0, 0, 0, 0, false, time.Time{}) + _, err := c.PlaceOrder(context.Background(), "", "", "", "", "", 0, 0, 0, false, time.Time{}) if !errors.Is(err, errClientOrderIDEmpty) { t.Errorf(errExpectMismatch, err, errClientOrderIDEmpty) } - _, err = c.PlaceOrder(context.Background(), "meow", "", "", "", "", 0, 0, 0, 0, false, time.Time{}) + _, err = c.PlaceOrder(context.Background(), "meow", "", "", "", "", 0, 0, 0, false, time.Time{}) if !errors.Is(err, errProductIDEmpty) { t.Errorf(errExpectMismatch, err, errProductIDEmpty) } _, err = c.PlaceOrder(context.Background(), "meow", testPair.String(), order.Sell.String(), "", "", 0, - 0, 0, 0, false, time.Time{}) - if !errors.Is(err, errAmountZeroNonMarketBuy) { - t.Errorf(errExpectMismatch, err, errAmountZeroNonMarketBuy) - } - _, err = c.PlaceOrder(context.Background(), "meow", testPair.String(), order.Buy.String(), "", - order.Market.Lower(), 1, 0, 0, 0, false, time.Time{}) - if !errors.Is(err, errAmountZeroMarketBuy) { - t.Errorf(errExpectMismatch, err, errAmountZeroMarketBuy) + 0, 0, false, time.Time{}) + if !errors.Is(err, errAmountZero) { + t.Errorf(errExpectMismatch, err, errAmountZero) } id, _ := uuid.NewV4() _, err = c.PlaceOrder(context.Background(), id.String(), testPair.String(), order.Sell.String(), "", - order.Limit.Lower(), 0.0000001, 0, 1000000000000, 0, false, time.Now().Add(time.Hour)) + order.Limit.String(), 0.0000001, 1000000000000, 0, false, time.Now().Add(time.Hour)) if err != nil { t.Error(err) } @@ -257,7 +255,7 @@ func TestCancelOrders(t *testing.T) { t.Errorf(errExpectMismatch, err, errOrderIDEmpty) } ordID, err := c.PlaceOrder(context.Background(), "meow", testPair.String(), order.Sell.String(), "", - order.Limit.Lower(), 0.0000001, 0, 1000000000000, 0, false, time.Time{}) + order.Limit.Lower(), 0.0000001, 1000000000000, 0, false, time.Time{}) if err != nil { t.Error(err) } @@ -280,7 +278,7 @@ func TestEditOrder(t *testing.T) { } id, _ := uuid.NewV4() ordID, err := c.PlaceOrder(context.Background(), id.String(), testPair.String(), order.Sell.String(), "", - order.Limit.Lower(), 0.0000001, 0, 1000000000000, 0, false, time.Time{}) + order.Limit.Lower(), 0.0000001, 1000000000000, 0, false, time.Time{}) if err != nil { t.Error(err) } @@ -506,6 +504,16 @@ func TestGetConvertTradeByID(t *testing.T) { } } +func TestGetV3Time(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + resp, err := c.GetV3Time(context.Background()) + if err != nil { + t.Error(err) + } + assert.NotEmpty(t, resp, errExpectedNonEmpty) + log.Printf("%+v", resp) +} + func TestListNotifications(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) _, err := c.ListNotifications(context.Background(), PaginationInp{}) @@ -1016,8 +1024,8 @@ func TestGetPrice(t *testing.T) { assert.NotEmpty(t, resp, errExpectedNonEmpty) } -func TestGetCurrentServerTime(t *testing.T) { - resp, err := c.GetCurrentServerTime(context.Background()) +func TestGetV2Time(t *testing.T) { + resp, err := c.GetV2Time(context.Background()) if err != nil { t.Error(err) } @@ -2934,6 +2942,163 @@ func TestGetWithdrawalsHistory(t *testing.T) { } } +func TestGetRecentTrades(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + _, err := c.GetRecentTrades(context.Background(), testPair, asset.Spot) + if err != nil { + t.Error(err) + } +} + +func TestGetHistoricTrades(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + _, err := c.GetHistoricTrades(context.Background(), testPair, asset.Spot, time.Time{}, time.Now()) + if err != nil { + t.Error(err) + } +} + +func TestSubmitOrder(t *testing.T) { + _, err := c.SubmitOrder(context.Background(), nil) + if !errors.Is(err, common.ErrNilPointer) { + t.Errorf(errExpectMismatch, err, common.ErrNilPointer) + } + var ord order.Submit + _, err = c.SubmitOrder(context.Background(), &ord) + if !errors.Is(err, common.ErrExchangeNameUnset) { + t.Errorf(errExpectMismatch, err, common.ErrExchangeNameUnset) + } + sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) + ord.Exchange = c.Name + ord.Pair = testPair + ord.AssetType = asset.Spot + ord.Side = order.Sell + ord.Type = order.StopLimit + ord.StopDirection = order.StopUp + ord.Amount = 0.0000001 + ord.Price = 1000000000000 + ord.RetrieveFees = true + ord.ClientOrderID = strconv.FormatInt(time.Now().UnixMilli(), 18) + "GCTSubmitOrderTest" + _, err = c.SubmitOrder(context.Background(), &ord) + if err != nil { + t.Error(err) + } + ord.StopDirection = order.StopDown + ord.Side = order.Buy + _, err = c.SubmitOrder(context.Background(), &ord) + if err != nil { + t.Error(err) + } + ord.Type = order.Market + ord.QuoteAmount = 0.0000001 + _, err = c.SubmitOrder(context.Background(), &ord) + if err != nil { + t.Error(err) + } +} + +func TestModifyOrder(t *testing.T) { + _, err := c.ModifyOrder(context.Background(), nil) + if !errors.Is(err, common.ErrNilPointer) { + t.Errorf(errExpectMismatch, err, common.ErrNilPointer) + } + var ord order.Modify + _, err = c.ModifyOrder(context.Background(), &ord) + if !errors.Is(err, order.ErrPairIsEmpty) { + t.Errorf(errExpectMismatch, err, order.ErrPairIsEmpty) + } + sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) + resp, err := c.PlaceOrder(context.Background(), strconv.FormatInt(time.Now().UnixMilli(), 18)+"GCTModifyOrderTest", + testPair.String(), order.Sell.String(), "", order.Limit.String(), 0.0000001, 1000000000000, 0, false, time.Time{}) + if err != nil { + t.Fatal(err) + } + ord.OrderID = resp.OrderID + ord.Price = 1000000000001 + _, err = c.ModifyOrder(context.Background(), &ord) + if err != nil { + t.Error(err) + } +} + +func TestCancelOrder(t *testing.T) { + err := c.CancelOrder(context.Background(), nil) + if !errors.Is(err, common.ErrNilPointer) { + t.Errorf(errExpectMismatch, err, common.ErrNilPointer) + } + var can order.Cancel + err = c.CancelOrder(context.Background(), &can) + if err.Error() != errIDNotSet { + t.Errorf(errExpectMismatch, err, errIDNotSet) + } + sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) + can.OrderID = "0" + c.Verbose = true + err = c.CancelOrder(context.Background(), &can) + if err.Error() != errOrder0CancelFail { + t.Errorf(errExpectMismatch, err, errOrder0CancelFail) + } + resp, err := c.PlaceOrder(context.Background(), strconv.FormatInt(time.Now().UnixMilli(), 18)+"GCTCancelOrderTest", + testPair.String(), order.Sell.String(), "", order.Limit.String(), 0.0000001, 1000000000000, 0, false, time.Time{}) + if err != nil { + t.Fatal(err) + } + can.OrderID = resp.OrderID + err = c.CancelOrder(context.Background(), &can) + if err != nil { + t.Error(err) + } +} + +func TestCancelBatchOrders(t *testing.T) { + _, err := c.CancelBatchOrders(context.Background(), nil) + if !errors.Is(err, errOrderIDEmpty) { + t.Errorf(errExpectMismatch, err, errOrderIDEmpty) + } + can := make([]order.Cancel, 1) + _, err = c.CancelBatchOrders(context.Background(), can) + if err.Error() != errIDNotSet { + t.Errorf(errExpectMismatch, err, errIDNotSet) + } + sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) + resp, err := c.PlaceOrder(context.Background(), + strconv.FormatInt(time.Now().UnixMilli(), 18)+"GCTCancelBatchOrdersTest", testPair.String(), + order.Sell.String(), "", order.Limit.String(), 0.0000001, 1000000000000, 0, false, time.Time{}) + if err != nil { + t.Fatal(err) + } + can[0].OrderID = resp.OrderID + _, err = c.CancelBatchOrders(context.Background(), can) + if err != nil { + t.Error(err) + } +} + +func TestCancelAllOrders(t *testing.T) { + _, err := c.CancelAllOrders(context.Background(), nil) + if !errors.Is(err, common.ErrNilPointer) { + t.Errorf(errExpectMismatch, err, common.ErrNilPointer) + } + var can order.Cancel + _, err = c.CancelAllOrders(context.Background(), &can) + if !errors.Is(err, order.ErrPairIsEmpty) { + t.Errorf(errExpectMismatch, err, order.ErrPairIsEmpty) + } + sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) + _, err = c.PlaceOrder(context.Background(), + strconv.FormatInt(time.Now().UnixMilli(), 18)+"GCTCancelAllOrdersTest", testPair.String(), + order.Sell.String(), "", order.Limit.String(), 0.0000001, 1000000000000, 0, false, time.Time{}) + if err != nil { + t.Fatal(err) + } + can.Pair = testPair + can.AssetType = asset.Spot + _, err = c.CancelAllOrders(context.Background(), &can) + if err != nil { + t.Error(err) + } +} + // 8837708 143.0 ns/op 24 B/op 5 allocs/op // func BenchmarkXxx(b *testing.B) { // for x := 0; x < b.N; x++ { diff --git a/exchanges/coinbasepro/coinbasepro_types.go b/exchanges/coinbasepro/coinbasepro_types.go index a9ff7438c27..1d5b2d11d49 100644 --- a/exchanges/coinbasepro/coinbasepro_types.go +++ b/exchanges/coinbasepro/coinbasepro_types.go @@ -462,6 +462,12 @@ type ConvertResponse struct { } `json:"trade"` } +type ServerTimeV3 struct { + Iso time.Time `json:"iso"` + EpochSeconds int64 `json:"epochSeconds,string"` + EpochMilliseconds int64 `json:"epochMillis,string"` +} + // IDResource holds an ID, resource type, and associated data, used in ListNotificationsResponse, // TransactionData type IDResource struct { @@ -872,8 +878,8 @@ type GetPriceResp struct { } `json:"data"` } -// ServerTime holds current requested server time information -type ServerTime struct { +// ServerTimeV2 holds current requested server time information, returned from V2 of the API +type ServerTimeV2 struct { Data struct { ISO time.Time `json:"iso"` Epoch uint64 `json:"epoch"` diff --git a/exchanges/coinbasepro/coinbasepro_wrapper.go b/exchanges/coinbasepro/coinbasepro_wrapper.go index a31c151917e..5e40a8c7309 100644 --- a/exchanges/coinbasepro/coinbasepro_wrapper.go +++ b/exchanges/coinbasepro/coinbasepro_wrapper.go @@ -9,6 +9,7 @@ import ( "sync" "time" + "github.com/gofrs/uuid" "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/currency" @@ -426,6 +427,10 @@ func (c *CoinbasePro) UpdateTicker(ctx context.Context, p currency.Pair, a asset // FetchTicker returns the ticker for a currency pair func (c *CoinbasePro) FetchTicker(ctx context.Context, p currency.Pair, assetType asset.Item) (*ticker.Price, error) { + p, err := c.FormatExchangeCurrency(p, assetType) + if err != nil { + return nil, err + } tickerNew, err := ticker.GetTicker(c.Name, p, assetType) if err != nil { return c.UpdateTicker(ctx, p, assetType) @@ -435,6 +440,10 @@ func (c *CoinbasePro) FetchTicker(ctx context.Context, p currency.Pair, assetTyp // FetchOrderbook returns orderbook base on the currency pair func (c *CoinbasePro) FetchOrderbook(ctx context.Context, p currency.Pair, assetType asset.Item) (*orderbook.Base, error) { + p, err := c.FormatExchangeCurrency(p, assetType) + if err != nil { + return nil, err + } ob, err := orderbook.Get(c.Name, p, assetType) if err != nil { return c.UpdateOrderbook(ctx, p, assetType) @@ -444,6 +453,10 @@ func (c *CoinbasePro) FetchOrderbook(ctx context.Context, p currency.Pair, asset // UpdateOrderbook updates and returns the orderbook for a currency pair func (c *CoinbasePro) UpdateOrderbook(ctx context.Context, p currency.Pair, assetType asset.Item) (*orderbook.Base, error) { + p, err := c.FormatExchangeCurrency(p, assetType) + if err != nil { + return nil, err + } if p.IsEmpty() { return nil, currency.ErrCurrencyPairEmpty } @@ -646,32 +659,118 @@ func (c *CoinbasePro) GetWithdrawalsHistory(ctx context.Context, cur currency.Co // GetRecentTrades returns the most recent trades for a currency and asset func (c *CoinbasePro) GetRecentTrades(ctx context.Context, p currency.Pair, assetType asset.Item) ([]trade.Data, error) { + return c.GetHistoricTrades(ctx, p, assetType, time.Time{}, time.Now()) +} +// GetHistoricTrades returns historic trade data within the timeframe provided +func (c *CoinbasePro) GetHistoricTrades(ctx context.Context, p currency.Pair, assetType asset.Item, startDate, endDate time.Time) ([]trade.Data, error) { p, err := c.FormatExchangeCurrency(p, assetType) if err != nil { return nil, err } - var tradeData []Trade - // tradeData, err = c.GetTrades(ctx, p.String()) + + statuses := []string{"FILLED", "CANCELLED", "EXPIRED", "FAILED"} + + ord, err := c.GetAllOrders(ctx, p.String(), "", "", "", "", "", "", "", statuses, 2<<30-1, startDate, endDate) + if err != nil { return nil, err } - resp := make([]trade.Data, len(tradeData)) - for i := range tradeData { + + resp := make([]trade.Data, len(ord.Orders)) + + for i := range ord.Orders { var side order.Side - side, err = order.StringToOrderSide(tradeData[i].Side) + side, err = order.StringToOrderSide(ord.Orders[i].Side) + if err != nil { + return nil, err + } + id, err := uuid.FromString(ord.Orders[i].OrderID) if err != nil { return nil, err } + var price float64 + var amount float64 + if ord.Orders[i].OrderConfiguration.MarketMarketIOC != nil { + if ord.Orders[i].OrderConfiguration.MarketMarketIOC.QuoteSize != "" { + amount, err = strconv.ParseFloat(ord.Orders[i].OrderConfiguration.MarketMarketIOC.QuoteSize, 64) + if err != nil { + return nil, err + } + } + if ord.Orders[i].OrderConfiguration.MarketMarketIOC.BaseSize != "" { + amount, err = strconv.ParseFloat(ord.Orders[i].OrderConfiguration.MarketMarketIOC.BaseSize, 64) + if err != nil { + return nil, err + } + } + } + if ord.Orders[i].OrderConfiguration.LimitLimitGTC != nil { + if ord.Orders[i].OrderConfiguration.LimitLimitGTC.LimitPrice != "" { + price, err = strconv.ParseFloat(ord.Orders[i].OrderConfiguration.LimitLimitGTC.LimitPrice, 64) + if err != nil { + return nil, err + } + } + if ord.Orders[i].OrderConfiguration.LimitLimitGTC.BaseSize != "" { + amount, err = strconv.ParseFloat(ord.Orders[i].OrderConfiguration.LimitLimitGTC.BaseSize, 64) + if err != nil { + return nil, err + } + } + } + if ord.Orders[i].OrderConfiguration.LimitLimitGTD != nil { + if ord.Orders[i].OrderConfiguration.LimitLimitGTD.LimitPrice != "" { + price, err = strconv.ParseFloat(ord.Orders[i].OrderConfiguration.LimitLimitGTD.LimitPrice, 64) + if err != nil { + return nil, err + } + } + if ord.Orders[i].OrderConfiguration.LimitLimitGTD.BaseSize != "" { + amount, err = strconv.ParseFloat(ord.Orders[i].OrderConfiguration.LimitLimitGTD.BaseSize, 64) + if err != nil { + return nil, err + } + } + } + if ord.Orders[i].OrderConfiguration.StopLimitStopLimitGTC != nil { + if ord.Orders[i].OrderConfiguration.StopLimitStopLimitGTC.LimitPrice != "" { + price, err = strconv.ParseFloat(ord.Orders[i].OrderConfiguration.StopLimitStopLimitGTC.LimitPrice, 64) + if err != nil { + return nil, err + } + } + if ord.Orders[i].OrderConfiguration.StopLimitStopLimitGTC.BaseSize != "" { + amount, err = strconv.ParseFloat(ord.Orders[i].OrderConfiguration.StopLimitStopLimitGTC.BaseSize, 64) + if err != nil { + return nil, err + } + } + } + if ord.Orders[i].OrderConfiguration.StopLimitStopLimitGTD != nil { + if ord.Orders[i].OrderConfiguration.StopLimitStopLimitGTD.LimitPrice != "" { + price, err = strconv.ParseFloat(ord.Orders[i].OrderConfiguration.StopLimitStopLimitGTD.LimitPrice, 64) + if err != nil { + return nil, err + } + } + if ord.Orders[i].OrderConfiguration.StopLimitStopLimitGTD.BaseSize != "" { + amount, err = strconv.ParseFloat(ord.Orders[i].OrderConfiguration.StopLimitStopLimitGTD.BaseSize, 64) + if err != nil { + return nil, err + } + } + } + resp[i] = trade.Data{ + ID: id, Exchange: c.Name, - TID: strconv.FormatInt(tradeData[i].TradeID, 10), CurrencyPair: p, AssetType: assetType, Side: side, - Price: tradeData[i].Price, - Amount: tradeData[i].Size, - Timestamp: tradeData[i].Time, + Price: price, + Amount: amount, + Timestamp: ord.Orders[i].CreatedTime, } } @@ -684,89 +783,211 @@ func (c *CoinbasePro) GetRecentTrades(ctx context.Context, p currency.Pair, asse return resp, nil } -// GetHistoricTrades returns historic trade data within the timeframe provided -func (c *CoinbasePro) GetHistoricTrades(_ context.Context, _ currency.Pair, _ asset.Item, _, _ time.Time) ([]trade.Data, error) { - return nil, common.ErrFunctionNotSupported -} - // SubmitOrder submits a new order func (c *CoinbasePro) SubmitOrder(ctx context.Context, s *order.Submit) (*order.SubmitResponse, error) { - if err := s.Validate(); err != nil { + if s == nil { + return nil, common.ErrNilPointer + } + err := s.Validate() + if err != nil { return nil, err } - // fPair, err := c.FormatExchangeCurrency(s.Pair, asset.Spot) - // if err != nil { - // return nil, err - // } + fPair, err := c.FormatExchangeCurrency(s.Pair, s.AssetType) + if err != nil { + return nil, err + } - var orderID string - switch s.Type { - case order.Market: - // orderID, err = c.PlaceMarketOrder(ctx, - // "", - // s.Amount, - // s.QuoteAmount, - // s.Side.Lower(), - // fPair.String(), - // "") - case order.Limit: - // timeInForce := CoinbaseRequestParamsTimeGTC - // if s.ImmediateOrCancel { - // timeInForce = CoinbaseRequestParamsTimeIOC - // } - // orderID, err = c.PlaceLimitOrder(ctx, - // "", - // s.Price, - // s.Amount, - // s.Side.Lower(), - // timeInForce, - // "", - // fPair.String(), - // "", - // false) - default: - // err = fmt.Errorf("%w %v", order.ErrUnsupportedOrderType, s.Type) + var stopDir string + + if s.Type == order.StopLimit { + switch s.StopDirection { + case order.StopUp: + stopDir = "STOP_DIRECTION_STOP_UP" + case order.StopDown: + stopDir = "STOP_DIRECTION_STOP_DOWN" + } } - // if err != nil { - // return nil, err - // } - return s.DeriveSubmitResponse(orderID) + + 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(), + amount, s.Price, s.TriggerPrice, 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) + if err != nil { + return nil, err + } + subResp.Fee = feeResp.TotalFees + } + return subResp, nil } // ModifyOrder will allow of changing orderbook placement and limit to // market conversion -func (c *CoinbasePro) ModifyOrder(_ context.Context, _ *order.Modify) (*order.ModifyResponse, error) { - return nil, common.ErrFunctionNotSupported +func (c *CoinbasePro) ModifyOrder(ctx context.Context, m *order.Modify) (*order.ModifyResponse, error) { + if m == nil { + return nil, common.ErrNilPointer + } + err := m.Validate() + if err != nil { + return nil, err + } + success, err := c.EditOrder(ctx, m.OrderID, m.Amount, m.Price) + if err != nil { + return nil, err + } + if !success { + return nil, errOrderModFailNoErr + } + + return m.DeriveModifyResponse() } // CancelOrder cancels an order by its corresponding ID number func (c *CoinbasePro) CancelOrder(ctx context.Context, o *order.Cancel) error { - if err := o.Validate(o.StandardCancel()); err != nil { + if o == nil { + return common.ErrNilPointer + } + err := o.Validate(o.StandardCancel()) + if err != nil { return err } - return errors.New("function not properly implemented") + canSlice := []order.Cancel{*o} + resp, err := c.CancelBatchOrders(ctx, canSlice) + if err != nil { + return err + } + if resp.Status[o.OrderID] != order.Cancelled.String() { + return fmt.Errorf("order %s failed to cancel", o.OrderID) + } + return err } // CancelBatchOrders cancels an orders by their corresponding ID numbers -func (c *CoinbasePro) CancelBatchOrders(_ context.Context, _ []order.Cancel) (*order.CancelBatchResponse, error) { - return nil, common.ErrFunctionNotSupported +func (c *CoinbasePro) CancelBatchOrders(ctx context.Context, o []order.Cancel) (*order.CancelBatchResponse, error) { + if len(o) == 0 { + return nil, errOrderIDEmpty + } + var status order.CancelBatchResponse + status.Status = make(map[string]string) + ordIDSlice := make([]string, len(o)) + for i := range o { + err := o[i].Validate(o[i].StandardCancel()) + if err != nil { + return nil, err + } + ordIDSlice[i] = o[i].OrderID + status.Status[o[i].OrderID] = "Failed to cancel" + } + resp, err := c.CancelOrders(ctx, ordIDSlice) + if err != nil { + return nil, err + } + for i := range resp.Results { + if resp.Results[i].Success { + status.Status[resp.Results[i].OrderID] = order.Cancelled.String() + } + } + return &status, nil } // CancelAllOrders cancels all orders associated with a currency pair -func (c *CoinbasePro) CancelAllOrders(ctx context.Context, _ *order.Cancel) (order.CancelAllResponse, error) { - // // CancellAllExisting orders returns a list of successful cancellations, we're only interested in failures - // _, err := c.CancelAllExistingOrders(ctx, "") - err := errors.New("function not properly implemented") - return order.CancelAllResponse{}, err +func (c *CoinbasePro) CancelAllOrders(ctx context.Context, can *order.Cancel) (order.CancelAllResponse, error) { + var resp order.CancelAllResponse + if can == nil { + return resp, common.ErrNilPointer + } + err := can.Validate(can.PairAssetRequired()) + if err != nil { + return resp, err + } + var ordIDs []GetOrderResponse + var cursor string + ordStatus := []string{"OPEN"} + hasNext := true + for hasNext { + interResp, err := c.GetAllOrders(ctx, can.Pair.String(), "", "", "", cursor, "", "", "", ordStatus, 1000, + time.Time{}, time.Time{}) + if err != nil { + return resp, err + } + ordIDs = append(ordIDs, interResp.Orders...) + hasNext = interResp.HasNext + cursor = interResp.Cursor + } + if len(ordStatus) == 0 { + return resp, errNoMatchingOrders + } + var orders []order.Cancel + for i := range ordIDs { + orders = append(orders, order.Cancel{OrderID: ordIDs[i].OrderID}) + } + + batchResp, err := c.CancelBatchOrders(ctx, orders) + if err != nil { + return resp, err + } + + resp.Status = batchResp.Status + resp.Count = int64(len(orders)) + + return resp, nil } // GetOrderInfo returns order information based on order ID -func (c *CoinbasePro) GetOrderInfo(ctx context.Context, orderID string, _ currency.Pair, _ asset.Item) (*order.Detail, error) { - // genOrderDetail, err := c.GetOrder(ctx, orderID) +func (c *CoinbasePro) GetOrderInfo(ctx context.Context, orderID string, pair currency.Pair, asset asset.Item) (*order.Detail, error) { + // genOrderDetail, err := c.GetOrderByID(ctx, orderID, "", "") // if err != nil { - // return nil, fmt.Errorf("error retrieving order %s : %w", orderID, err) + // return nil, err + // } + + // var amount float64 + // if genOrderDetail.OrderConfiguration.MarketMarketIOC != nil { + // if genOrderDetail.OrderConfiguration.MarketMarketIOC.QuoteSize != "" { + // amount, err = strconv.ParseFloat(genOrderDetail.OrderConfiguration.MarketMarketIOC.QuoteSize, 64) + // if err != nil { + // return nil, err + // } + // } + // if genOrderDetail.OrderConfiguration.MarketMarketIOC.BaseSize != "" { + // amount, err = strconv.ParseFloat(genOrderDetail.OrderConfiguration.MarketMarketIOC.BaseSize, 64) + // if err != nil { + // return nil, err + // } + // } // } + // var price float64 + // var postOnly bool + // if genOrderDetail.OrderConfiguration.LimitLimitGTC != nil { + + // postOnly = genOrderDetail.OrderConfiguration.LimitLimitGTC.PostOnly + // } + // if genOrderDetail.OrderConfiguration.LimitLimitGTD != nil { + // postOnly = genOrderDetail.OrderConfiguration.LimitLimitGTD.PostOnly + // } + + // response := order.Detail{ + // ImmediateOrCancel: genOrderDetail.OrderConfiguration.MarketMarketIOC != nil, + // PostOnly: postOnly, + // Price: + // } + // orderStatus, err := order.StringToOrderStatus(genOrderDetail.Status) // if err != nil { // return nil, fmt.Errorf("error parsing order status: %w", err) diff --git a/exchanges/order/order_test.go b/exchanges/order/order_test.go index 544ae9c23d7..2cd3589da70 100644 --- a/exchanges/order/order_test.go +++ b/exchanges/order/order_test.go @@ -33,7 +33,7 @@ func TestSubmit_Validate(t *testing.T) { Submit: nil, }, // nil struct { - ExpectedErr: errExchangeNameUnset, + ExpectedErr: common.ErrExchangeNameUnset, Submit: &Submit{}, }, // empty exchange { diff --git a/exchanges/order/order_types.go b/exchanges/order/order_types.go index b6136b5b1e1..3db18ddfb6e 100644 --- a/exchanges/order/order_types.go +++ b/exchanges/order/order_types.go @@ -86,6 +86,11 @@ type Submit struct { Hidden bool // TradeMode specifies the trading mode for margin and non-margin orders: see okcoin_wrapper.go TradeMode string + + // EndTime is the moment which a good til date order is valid until + EndTime time.Time + + StopDirection StopDirection } // SubmitResponse is what is returned after submitting an order to an exchange @@ -345,6 +350,25 @@ const ( ConditionalStop // One-way stop order ) +var AllOrderTypes = Limit | + Market | + PostOnly | + ImmediateOrCancel | + Stop | + StopLimit | + StopMarket | + TakeProfit | + TakeProfitMarket | + TrailingStop | + FillOrKill | + IOS | + AnyType | + Liquidation | + Trigger | + OptimalLimitIOC | + OCO | + ConditionalStop + // Side enforces a standard for order sides across the code base type Side uint32 @@ -398,3 +422,13 @@ type ClassificationError struct { // forcing required filter operations when calling method Filter() on // MultiOrderRequest. type FilteredOrders []Detail + +// StopDirection is the direction from which the stop order will trigger; Up will have the order trigger +// when the last trade price goes above the TriggerPrice; Down will have the order trigger when the +// last trade price goes below the TriggerPrice +type StopDirection bool + +const ( + StopUp StopDirection = true + StopDown StopDirection = false +) diff --git a/exchanges/order/orders.go b/exchanges/order/orders.go index 98400f66028..2bb6c0e3c8c 100644 --- a/exchanges/order/orders.go +++ b/exchanges/order/orders.go @@ -40,7 +40,6 @@ var ( errTimeInForceConflict = errors.New("multiple time in force options applied") errUnrecognisedOrderType = errors.New("unrecognised order type") errUnrecognisedOrderStatus = errors.New("unrecognised order status") - errExchangeNameUnset = errors.New("exchange name unset") errOrderSubmitIsNil = errors.New("order submit is nil") errOrderSubmitResponseIsNil = errors.New("order submit response is nil") errOrderDetailIsNil = errors.New("order detail is nil") @@ -59,7 +58,7 @@ func (s *Submit) Validate(opt ...validate.Checker) error { } if s.Exchange == "" { - return errExchangeNameUnset + return common.ErrExchangeNameUnset } if s.Pair.IsEmpty() { @@ -78,7 +77,7 @@ func (s *Submit) Validate(opt ...validate.Checker) error { return fmt.Errorf("%w %v", ErrSideIsInvalid, s.Side) } - if s.Type != Market && s.Type != Limit { + if AllOrderTypes&s.Type != s.Type || s.Type == UnknownType { return ErrTypeIsInvalid } diff --git a/exchanges/orderbook/orderbook.go b/exchanges/orderbook/orderbook.go index d5a8ca5eee3..9c3668eb102 100644 --- a/exchanges/orderbook/orderbook.go +++ b/exchanges/orderbook/orderbook.go @@ -6,6 +6,7 @@ import ( "strings" "time" + "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/common/key" "github.com/thrasher-corp/gocryptotrader/currency" "github.com/thrasher-corp/gocryptotrader/dispatch" @@ -83,7 +84,7 @@ func (s *Service) Update(b *Base) error { // then returns a ptr to that Depth item func (s *Service) DeployDepth(exchange string, p currency.Pair, a asset.Item) (*Depth, error) { if exchange == "" { - return nil, errExchangeNameUnset + return nil, common.ErrExchangeNameUnset } if p.IsEmpty() { return nil, errPairNotSet @@ -300,7 +301,7 @@ func checkAlignment(depth Items, fundingRate, priceDuplication, isIDAligned, req // list func (b *Base) Process() error { if b.Exchange == "" { - return errExchangeNameUnset + return common.ErrExchangeNameUnset } if b.Pair.IsEmpty() { diff --git a/exchanges/orderbook/orderbook_test.go b/exchanges/orderbook/orderbook_test.go index 5c8b0debb78..ef6e94f9a18 100644 --- a/exchanges/orderbook/orderbook_test.go +++ b/exchanges/orderbook/orderbook_test.go @@ -10,6 +10,7 @@ import ( "testing" "time" + "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/currency" "github.com/thrasher-corp/gocryptotrader/dispatch" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" @@ -328,8 +329,8 @@ func TestDeployDepth(t *testing.T) { t.Fatal(err) } _, err = DeployDepth("", c, asset.Spot) - if !errors.Is(err, errExchangeNameUnset) { - t.Fatalf("expecting %s error but received %v", errExchangeNameUnset, err) + if !errors.Is(err, common.ErrExchangeNameUnset) { + t.Fatalf("expecting %s error but received %v", common.ErrExchangeNameUnset, err) } _, err = DeployDepth("test", currency.EMPTYPAIR, asset.Spot) if !errors.Is(err, errPairNotSet) { diff --git a/exchanges/orderbook/orderbook_types.go b/exchanges/orderbook/orderbook_types.go index 07b2a467036..bc893068776 100644 --- a/exchanges/orderbook/orderbook_types.go +++ b/exchanges/orderbook/orderbook_types.go @@ -21,7 +21,6 @@ const ( // Vars for the orderbook package var ( - errExchangeNameUnset = errors.New("orderbook exchange name not set") errPairNotSet = errors.New("orderbook currency pair not set") errAssetTypeNotSet = errors.New("orderbook asset type not set") errCannotFindOrderbook = errors.New("cannot find orderbook(s)") diff --git a/exchanges/ticker/ticker.go b/exchanges/ticker/ticker.go index c09791602ba..3cdbd8e9d9d 100644 --- a/exchanges/ticker/ticker.go +++ b/exchanges/ticker/ticker.go @@ -7,6 +7,7 @@ import ( "time" "github.com/gofrs/uuid" + "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/common/key" "github.com/thrasher-corp/gocryptotrader/currency" "github.com/thrasher-corp/gocryptotrader/dispatch" @@ -116,7 +117,7 @@ func ProcessTicker(p *Price) error { } if p.ExchangeName == "" { - return fmt.Errorf(ErrExchangeNameUnset) + return common.ErrExchangeNameUnset } if p.Pair.IsEmpty() { diff --git a/exchanges/ticker/ticker_types.go b/exchanges/ticker/ticker_types.go index 6525a394ef6..678d511b5d1 100644 --- a/exchanges/ticker/ticker_types.go +++ b/exchanges/ticker/ticker_types.go @@ -13,10 +13,9 @@ import ( // const values for the ticker package const ( - ErrExchangeNameUnset = "ticker exchange name not set" - errPairNotSet = "ticker currency pair not set" - errAssetTypeNotSet = "ticker asset type not set" - errTickerPriceIsNil = "ticker price is nil" + errPairNotSet = "ticker currency pair not set" + errAssetTypeNotSet = "ticker asset type not set" + errTickerPriceIsNil = "ticker price is nil" ) // Vars for the ticker package diff --git a/portfolio/withdraw/validate.go b/portfolio/withdraw/validate.go index 297cfe8ba22..45d5b434aa7 100644 --- a/portfolio/withdraw/validate.go +++ b/portfolio/withdraw/validate.go @@ -4,6 +4,7 @@ import ( "errors" "strings" + "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/currency" "github.com/thrasher-corp/gocryptotrader/exchanges/validate" ) @@ -15,7 +16,7 @@ func (r *Request) Validate(opt ...validate.Checker) (err error) { } if r.Exchange == "" { - return ErrExchangeNameUnset + return common.ErrExchangeNameUnset } var allErrors []string diff --git a/portfolio/withdraw/validate_test.go b/portfolio/withdraw/validate_test.go index 12d98edd96d..fac52c2a73e 100644 --- a/portfolio/withdraw/validate_test.go +++ b/portfolio/withdraw/validate_test.go @@ -6,6 +6,7 @@ import ( "os" "testing" + "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/core" "github.com/thrasher-corp/gocryptotrader/currency" "github.com/thrasher-corp/gocryptotrader/exchanges/validate" @@ -151,7 +152,7 @@ func TestExchangeNameUnset(t *testing.T) { r := Request{} err := r.Validate() if err != nil { - if err != ErrExchangeNameUnset { + if err != common.ErrExchangeNameUnset { t.Fatal(err) } } diff --git a/portfolio/withdraw/withdraw_types.go b/portfolio/withdraw/withdraw_types.go index 142a412b60b..70551a7a484 100644 --- a/portfolio/withdraw/withdraw_types.go +++ b/portfolio/withdraw/withdraw_types.go @@ -40,8 +40,6 @@ const ( var ( // ErrRequestCannotBeNil message to return when a request is nil ErrRequestCannotBeNil = errors.New("request cannot be nil") - // ErrExchangeNameUnset message to return when an exchange name is unset - ErrExchangeNameUnset = errors.New("exchange name unset") // ErrInvalidRequest message to return when a request type is invalid ErrInvalidRequest = errors.New("invalid request type") // ErrStrAddressNotWhiteListed occurs when a withdrawal attempts to withdraw from a non-whitelisted address From 4f5ccb5c4efefb55209fe1aab4b0a25bc825f4c2 Mon Sep 17 00:00:00 2001 From: Samuel Reid <43227667+cranktakular@users.noreply.github.com> Date: Tue, 19 Dec 2023 18:28:35 +1100 Subject: [PATCH 13/79] Coinbase updates & wrap progress --- exchanges/asset/asset.go | 5 + exchanges/coinbasepro/coinbasepro.go | 121 ++++- exchanges/coinbasepro/coinbasepro_test.go | 206 ++++++- exchanges/coinbasepro/coinbasepro_types.go | 105 ++++ exchanges/coinbasepro/coinbasepro_wrapper.go | 531 ++++++++++++------- 5 files changed, 751 insertions(+), 217 deletions(-) diff --git a/exchanges/asset/asset.go b/exchanges/asset/asset.go index 43442f74272..8fc5119cf34 100644 --- a/exchanges/asset/asset.go +++ b/exchanges/asset/asset.go @@ -113,6 +113,11 @@ func (a Item) String() string { } } +// Upper returns the item's upper case string +func (a Item) Upper() string { + return strings.ToUpper(a.String()) +} + // Strings converts an asset type array to a string array func (a Items) Strings() []string { assets := make([]string, len(a)) diff --git a/exchanges/coinbasepro/coinbasepro.go b/exchanges/coinbasepro/coinbasepro.go index 9e9d2e7c437..73fd5d3df1f 100644 --- a/exchanges/coinbasepro/coinbasepro.go +++ b/exchanges/coinbasepro/coinbasepro.go @@ -38,6 +38,8 @@ const ( coinbaseFills = "fills" coinbaseCandles = "candles" coinbaseTicker = "ticker" + coinbasePortfolios = "portfolios" + coinbaseMoveFunds = "move_funds" coinbaseTransactionSummary = "transaction_summary" coinbaseConvert = "convert" coinbaseQuote = "quote" @@ -78,19 +80,12 @@ const ( granOneDay = "ONE_DAY" startDateString = "start_date" endDateString = "end_date" - - // coinbaseproTime = "time" - // coinbaseproMarginTransfer = "profiles/margin-transfer" - // coinbaseproPosition = "position" - // coinbaseproPositionClose = "position/close" - // coinbaseproTrailingVolume = "users/self/trailing-volume" ) var ( errAccountIDEmpty = errors.New("account id cannot be empty") errClientOrderIDEmpty = errors.New("client order id cannot be empty") errProductIDEmpty = errors.New("product id cannot be empty") - errAmountZero = errors.New("amount cannot be 0") errOrderIDEmpty = errors.New("order ids cannot be empty") errOpenPairWithOtherTypes = errors.New("cannot pair open orders with other order types") errUserIDEmpty = errors.New("user id cannot be empty") @@ -110,6 +105,9 @@ var ( errNoMatchingWallets = errors.New("no matching wallets returned") errOrderModFailNoErr = errors.New("order modification failed but no error returned") errNoMatchingOrders = errors.New("no matching orders returned") + errPointerNil = errors.New("relevant pointer is nil") + errNameEmpty = errors.New("name cannot be empty") + errPortfolioIDEmpty = errors.New("portfolio id cannot be empty") ) // GetAllAccounts returns information on all trading accounts associated with the API key @@ -284,7 +282,7 @@ func (c *CoinbasePro) PlaceOrder(ctx context.Context, clientOID, productID, side return nil, errProductIDEmpty } if amount == 0 { - return nil, errAmountZero + return nil, errAmountEmpty } var resp PlaceOrderResp @@ -493,6 +491,106 @@ func (c *CoinbasePro) GetOrderByID(ctx context.Context, orderID, userNativeCurre return &resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, pathParams, nil, Version3, &resp, nil) } +// GetAllPortfolios returns a list of portfolios associated with the user +func (c *CoinbasePro) GetAllPortfolios(ctx context.Context, portfolioType string) (AllPortfolioResponse, error) { + var resp AllPortfolioResponse + + var params Params + params.urlVals = url.Values{} + + if portfolioType != "" { + params.urlVals.Set("portfolio_type", portfolioType) + } + + pathParams := common.EncodeURLValues("", params.urlVals) + + return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, + coinbaseV3+coinbasePortfolios, pathParams, nil, Version3, &resp, nil) +} + +// CreatePortfolio creates a new portfolio +func (c *CoinbasePro) CreatePortfolio(ctx context.Context, name string) (SimplePortfolioResponse, error) { + var resp SimplePortfolioResponse + + if name == "" { + return resp, errNameEmpty + } + + req := map[string]interface{}{"name": name} + + return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, + coinbaseV3+coinbasePortfolios, "", req, Version3, &resp, nil) +} + +// MovePortfolioFunds transfers funds between portfolios +func (c *CoinbasePro) MovePortfolioFunds(ctx context.Context, from, to, currency string, amount float64) (MovePortfolioFundsResponse, error) { + var resp MovePortfolioFundsResponse + + if from == "" || to == "" { + return resp, errPortfolioIDEmpty + } + if currency == "" { + return resp, errCurrencyEmpty + } + if amount == 0 { + return resp, 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) + + return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, + path, "", req, Version3, &resp, nil) +} + +// GetPortfolioByID provides detailed information on a single portfolio +func (c *CoinbasePro) GetPortfolioByID(ctx context.Context, portfolioID string) (*DetailedPortfolioResponse, error) { + if portfolioID == "" { + return nil, errPortfolioIDEmpty + } + + path := fmt.Sprintf("%s%s/%s", coinbaseV3, coinbasePortfolios, portfolioID) + + var resp DetailedPortfolioResponse + + return &resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, + path, "", nil, Version3, &resp, nil) +} + +// DeletePortfolio deletes a portfolio +func (c *CoinbasePro) DeletePortfolio(ctx context.Context, portfolioID string) error { + if portfolioID == "" { + return errPortfolioIDEmpty + } + + path := fmt.Sprintf("%s%s/%s", coinbaseV3, coinbasePortfolios, portfolioID) + + return c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodDelete, path, "", nil, + Version3, nil, nil) +} + +// EditPortfolio edits the name of a portfolio +func (c *CoinbasePro) EditPortfolio(ctx context.Context, portfolioID, name string) (SimplePortfolioResponse, error) { + var resp SimplePortfolioResponse + + if portfolioID == "" { + return resp, errPortfolioIDEmpty + } + if name == "" { + return resp, errNameEmpty + } + + req := map[string]interface{}{"name": name} + + path := fmt.Sprintf("%s%s/%s", coinbaseV3, coinbasePortfolios, portfolioID) + + return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPut, + path, "", req, Version3, &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) { @@ -815,7 +913,7 @@ func (c *CoinbasePro) GetAddressTransactions(ctx context.Context, walletID, addr // string for idempotency; a token with a max length of 100 characters, if a previous // transaction included the same token as a parameter, the new transaction won't be processed, // and information on the previous transaction will be returned instead -func (c *CoinbasePro) SendMoney(ctx context.Context, traType, walletID, to, amount, currency, description, idem, financialInstitutionWebsite, destinationTag string, skipNotifications, toFinancialInstitution bool) (*GenTransactionResp, error) { +func (c *CoinbasePro) SendMoney(ctx context.Context, traType, walletID, to, currency, description, idem, financialInstitutionWebsite, destinationTag string, amount float64, skipNotifications, toFinancialInstitution bool) (*GenTransactionResp, error) { if traType == "" { return nil, errTransactionTypeEmpty } @@ -825,7 +923,7 @@ func (c *CoinbasePro) SendMoney(ctx context.Context, traType, walletID, to, amou if to == "" { return nil, errToEmpty } - if amount == "" { + if amount == 0 { return nil, errAmountEmpty } if currency == "" { @@ -834,7 +932,8 @@ func (c *CoinbasePro) SendMoney(ctx context.Context, traType, walletID, to, amou path := fmt.Sprintf("%s%s/%s/%s", coinbaseV2, coinbaseAccounts, walletID, coinbaseTransactions) - req := map[string]interface{}{"type": traType, "to": to, "amount": amount, "currency": currency, + 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, diff --git a/exchanges/coinbasepro/coinbasepro_test.go b/exchanges/coinbasepro/coinbasepro_test.go index 992f17b0eb6..1d7d733020b 100644 --- a/exchanges/coinbasepro/coinbasepro_test.go +++ b/exchanges/coinbasepro/coinbasepro_test.go @@ -23,6 +23,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues" gctlog "github.com/thrasher-corp/gocryptotrader/log" + "github.com/thrasher-corp/gocryptotrader/portfolio/withdraw" ) var ( @@ -44,13 +45,15 @@ const ( // Donation address testAddress = "bc1qk0jareu4jytc0cfrhr5wgshsq8282awpavfahc" - errExpectMismatch = "received: '%v' but expected: '%v'" - errExpectedNonEmpty = "expected non-empty response" - errOrder0CancelFail = "order 0 failed to cancel" - errIDNotSet = "ID not set" - skipPayMethodNotFound = "no payment methods found, skipping" - skipInsufSuitableAccs = "insufficient suitable accounts found, skipping" - errx7f = "setting proxy address error parse \"\\x7f\": net/url: invalid control character in URL" + errExpectMismatch = "received: '%v' but expected: '%v'" + errExpectedNonEmpty = "expected non-empty response" + errOrder0CancelFail = "order 0 failed to cancel" + errIDNotSet = "ID not set" + skipPayMethodNotFound = "no payment methods found, skipping" + skipInsufSuitableAccs = "insufficient suitable accounts found, skipping" + errx7f = "setting proxy address error parse \"\\x7f\": net/url: invalid control character in URL" + errPortfolioNameDuplicate = `CoinbasePro unsuccessful HTTP status code: 409 raw response: {"error":"CONFLICT","error_details":"[PORTFOLIO_ERROR_CODE_ALREADY_EXISTS] the requested portfolio name already exists","message":"[PORTFOLIO_ERROR_CODE_ALREADY_EXISTS] the requested portfolio name already exists"}, authenticated request failed` + errPortTransferInsufFunds = `CoinbasePro unsuccessful HTTP status code: 429 raw response: {"error":"unknown","error_details":"[PORTFOLIO_ERROR_CODE_INSUFFICIENT_FUNDS] insufficient funds in source account","message":"[PORTFOLIO_ERROR_CODE_INSUFFICIENT_FUNDS] insufficient funds in source account"}, authenticated request failed` ) func TestMain(m *testing.M) { @@ -236,8 +239,8 @@ func TestPlaceOrder(t *testing.T) { } _, err = c.PlaceOrder(context.Background(), "meow", testPair.String(), order.Sell.String(), "", "", 0, 0, 0, false, time.Time{}) - if !errors.Is(err, errAmountZero) { - t.Errorf(errExpectMismatch, err, errAmountZero) + if !errors.Is(err, errAmountEmpty) { + t.Errorf(errExpectMismatch, err, errAmountEmpty) } id, _ := uuid.NewV4() _, err = c.PlaceOrder(context.Background(), id.String(), testPair.String(), order.Sell.String(), "", @@ -360,13 +363,138 @@ func TestGetOrderByID(t *testing.T) { } } +func TestGetAllPortfolios(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + resp, err := c.GetAllPortfolios(context.Background(), "") + if err != nil { + t.Error(err) + } + assert.NotEmpty(t, resp, errExpectedNonEmpty) +} + +func TestCreatePortfolio(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) + _, err := c.CreatePortfolio(context.Background(), "GCT Test Portfolio") + if err != nil && err.Error() != errPortfolioNameDuplicate { + t.Error(err) + } +} + +func TestMovePortfolioFunds(t *testing.T) { + _, err := c.MovePortfolioFunds(context.Background(), "", "", "", 0) + if !errors.Is(err, errPortfolioIDEmpty) { + t.Errorf(errExpectMismatch, err, errPortfolioIDEmpty) + } + _, err = c.MovePortfolioFunds(context.Background(), "meowPort", "woofPort", "", 0) + if !errors.Is(err, errCurrencyEmpty) { + t.Errorf(errExpectMismatch, err, errCurrencyEmpty) + } + _, err = c.MovePortfolioFunds(context.Background(), "meowPort", "woofPort", "BTC", 0) + if !errors.Is(err, errAmountEmpty) { + t.Errorf(errExpectMismatch, err, errAmountEmpty) + } + sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) + portID, err := c.GetAllPortfolios(context.Background(), "") + if err != nil { + t.Error(err) + } + if len(portID.Portfolios) < 2 { + t.Skip("fewer than 2 portfolios found, skipping") + } + _, err = c.MovePortfolioFunds(context.Background(), portID.Portfolios[0].UUID, portID.Portfolios[1].UUID, "BTC", + 0.00000001) + if err != nil && err.Error() != errPortTransferInsufFunds { + t.Error(err) + } +} + +func TestGetPortfolioByID(t *testing.T) { + _, err := c.GetPortfolioByID(context.Background(), "") + if !errors.Is(err, errPortfolioIDEmpty) { + t.Errorf(errExpectMismatch, err, errPortfolioIDEmpty) + } + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + resp, err := c.GetAllPortfolios(context.Background(), "") + if err != nil { + t.Error(err) + } + if len(resp.Portfolios) == 0 { + t.Fatal(errExpectedNonEmpty) + } + _, err = c.GetPortfolioByID(context.Background(), resp.Portfolios[0].UUID) + if err != nil { + t.Error(err) + } +} + +func PortfolioTestHelper(t *testing.T, targetName string) string { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) + createResp, err := c.CreatePortfolio(context.Background(), targetName) + var targetID string + if err != nil { + if err.Error() != errPortfolioNameDuplicate { + t.Error(err) + } + getResp, err := c.GetAllPortfolios(context.Background(), "") + if err != nil { + t.Error(err) + } + if len(getResp.Portfolios) == 0 { + t.Fatal(errExpectedNonEmpty) + } + for i := range getResp.Portfolios { + if getResp.Portfolios[i].Name == targetName { + targetID = getResp.Portfolios[i].UUID + break + } + } + } else { + targetID = createResp.Portfolio.UUID + } + return targetID +} + +func TestDeletePortfolio(t *testing.T) { + err := c.DeletePortfolio(context.Background(), "") + if !errors.Is(err, errPortfolioIDEmpty) { + t.Errorf(errExpectMismatch, err, errPortfolioIDEmpty) + } + sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) + + pID := PortfolioTestHelper(t, "GCT Test Portfolio To-Delete") + + err = c.DeletePortfolio(context.Background(), pID) + if err != nil { + t.Error(err) + } +} + +func TestEditPortfolio(t *testing.T) { + _, err := c.EditPortfolio(context.Background(), "", "") + if !errors.Is(err, errPortfolioIDEmpty) { + t.Errorf(errExpectMismatch, err, errPortfolioIDEmpty) + } + _, err = c.EditPortfolio(context.Background(), "meow", "") + if !errors.Is(err, errNameEmpty) { + t.Errorf(errExpectMismatch, err, errNameEmpty) + } + sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) + + pID := PortfolioTestHelper(t, "GCT Test Portfolio To-Edit") + + _, err = c.EditPortfolio(context.Background(), pID, "GCT Test Portfolio Edited") + if err != nil && err.Error() != errPortfolioNameDuplicate { + t.Error(err) + } +} + func TestGetTransactionSummary(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) _, err := c.GetTransactionSummary(context.Background(), time.Unix(2, 2), time.Unix(1, 1), "", "", "") if !errors.Is(err, common.ErrStartAfterEnd) { t.Errorf(errExpectMismatch, err, common.ErrStartAfterEnd) } - _, err = c.GetTransactionSummary(context.Background(), time.Unix(1, 1), time.Now(), "", "SPOT", + _, err = c.GetTransactionSummary(context.Background(), time.Unix(1, 1), time.Now(), "", asset.Spot.Upper(), "UNKNOWN_CONTRACT_EXPIRY_TYPE") if err != nil { t.Error(err) @@ -735,23 +863,23 @@ func TestGetAddressTransactions(t *testing.T) { func TestSendMoney(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) - _, err := c.SendMoney(context.Background(), "", "", "", "", "", "", "", "", "", false, false) + _, err := c.SendMoney(context.Background(), "", "", "", "", "", "", "", "", 0, false, false) if !errors.Is(err, errTransactionTypeEmpty) { t.Errorf(errExpectMismatch, err, errTransactionTypeEmpty) } - _, err = c.SendMoney(context.Background(), "123", "", "", "", "", "", "", "", "", false, false) + _, err = c.SendMoney(context.Background(), "123", "", "", "", "", "", "", "", 0, false, false) if !errors.Is(err, errWalletIDEmpty) { t.Errorf(errExpectMismatch, err, errWalletIDEmpty) } - _, err = c.SendMoney(context.Background(), "123", "123", "", "", "", "", "", "", "", false, false) + _, err = c.SendMoney(context.Background(), "123", "123", "", "", "", "", "", "", 0, false, false) if !errors.Is(err, errToEmpty) { t.Errorf(errExpectMismatch, err, errToEmpty) } - _, err = c.SendMoney(context.Background(), "123", "123", "123", "", "", "", "", "", "", false, false) + _, err = c.SendMoney(context.Background(), "123", "123", "123", "", "", "", "", "", 0, false, false) if !errors.Is(err, errAmountEmpty) { t.Errorf(errExpectMismatch, err, errAmountEmpty) } - _, err = c.SendMoney(context.Background(), "123", "123", "123", "123", "", "", "", "", "", false, false) + _, err = c.SendMoney(context.Background(), "123", "123", "123", "", "", "", "", "", 1, false, false) if !errors.Is(err, errCurrencyEmpty) { t.Errorf(errExpectMismatch, err, errCurrencyEmpty) } @@ -762,8 +890,8 @@ func TestSendMoney(t *testing.T) { if len(wID.Data) < 2 { t.Skip("fewer than 2 wallets found, skipping test") } - _, err = c.SendMoney(context.Background(), "transfer", wID.Data[0].ID, wID.Data[1].ID, "0.00000001", - "BTC", "GCT Test", "123", "", "", false, false) + _, err = c.SendMoney(context.Background(), "transfer", wID.Data[0].ID, wID.Data[1].ID, + "BTC", "GCT Test", "123", "", "", 0.00000001, false, false) if err != nil { t.Error(err) } @@ -1007,7 +1135,7 @@ func TestGetPrice(t *testing.T) { if !errors.Is(err, errInvalidPriceType) { t.Errorf(errExpectMismatch, err, errInvalidPriceType) } - resp, err := c.GetPrice(context.Background(), testPair.String(), "spot") + resp, err := c.GetPrice(context.Background(), testPair.String(), asset.Spot.Upper()) if err != nil { t.Error(err) } @@ -3099,6 +3227,48 @@ func TestCancelAllOrders(t *testing.T) { } } +func TestGetOrderInfo(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) + ordID, err := c.GetAllOrders(context.Background(), testPair.String(), "", "", "", "", + asset.Spot.Upper(), "", "", nil, 2, time.Time{}, time.Now()) + if err != nil { + t.Fatal(err) + } + if len(ordID.Orders) == 0 { + t.Fatal(errExpectedNonEmpty) + } + _, err = c.GetOrderInfo(context.Background(), ordID.Orders[0].OrderID, testPair, asset.Spot) + if err != nil { + t.Error(err) + } +} + +func TestGetDepositAddress(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) + _, err := c.GetDepositAddress(context.Background(), currency.BTC, "", "") + if err != nil { + t.Error(err) + } +} + +func TestWithdrawCryptocurrencyFunds(t *testing.T) { + req := withdraw.Request{} + _, err := c.WithdrawCryptocurrencyFunds(context.Background(), &req) + if !errors.Is(err, common.ErrExchangeNameUnset) { + t.Errorf(errExpectMismatch, err, common.ErrExchangeNameUnset) + } + sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) + req.Exchange = c.Name + req.Currency = currency.BTC + req.Amount = 0.00000001 + req.Type = withdraw.Crypto + req.Crypto.Address = testAddress + _, err = c.WithdrawCryptocurrencyFunds(context.Background(), &req) + if err != nil { + t.Error(err) + } +} + // 8837708 143.0 ns/op 24 B/op 5 allocs/op // func BenchmarkXxx(b *testing.B) { // for x := 0; x < b.N; x++ { diff --git a/exchanges/coinbasepro/coinbasepro_types.go b/exchanges/coinbasepro/coinbasepro_types.go index 1d5b2d11d49..b79874369d7 100644 --- a/exchanges/coinbasepro/coinbasepro_types.go +++ b/exchanges/coinbasepro/coinbasepro_types.go @@ -329,6 +329,111 @@ type FillResponse struct { Cursor string `json:"cursor"` } +// SimplePortfolioData is a sub-struct used in the types AllPortfolioResponse, and +// SimplePortfolioResponse +type SimplePortfolioData struct { + Name string `json:"name"` + UUID string `json:"uuid"` + Type string `json:"type"` + Deleted bool `json:"deleted"` +} + +// AllPortfolioResponse contains a brief overview of the user's portfolios, returned by +// GetAllPortfolios +type AllPortfolioResponse struct { + Portfolios []SimplePortfolioData `json:"portfolios"` +} + +// SimplePortfolioResponse contains a small amount of information on a single portfolio. +// Returned by CreatePortfolio and EditPortfolio +type SimplePortfolioResponse struct { + Portfolio SimplePortfolioData `json:"portfolio"` +} + +// MovePortfolioFundsResponse contains the UUIDs of the portfolios involved. Returned by +// MovePortfolioFunds +type MovePortfolioFundsResponse struct { + SourcePortfolioUUID string `json:"source_portfolio_uuid"` + TargetPortfolioUUID string `json:"target_portfolio_uuid"` +} + +// FundsData is used internally when preparing a request in MovePortfolioFunds +type FundsData struct { + Value string `json:"value"` + Currency string `json:"currency"` +} + +// NativeAndRaw is a sub-struct used in the type DetailedPortfolioResponse +type NativeAndRaw struct { + UserNativeCurrency ValCur `json:"userNativeCurrency"` + RawCurrency ValCur `json:"rawCurrency"` +} + +// DetailedPortfolioResponse contains a great deal of information on a single portfolio. +// Returned by GetPortfolioByID +type DetailedPortfolioResponse struct { + Breakdown struct { + Portfolio SimplePortfolioData `json:"portfolio"` + PortfolioBalances struct { + TotalBalance ValCur `json:"total_balance"` + TotalFuturesBalance ValCur `json:"total_futures_balance"` + TotalCashEquivalentBalance ValCur `json:"total_cash_equivalent_balance"` + TotalCryptoBalance ValCur `json:"total_crypto_balance"` + FuturesUnrealizedPNL ValCur `json:"futures_unrealized_pnl"` + PerpUnrealizedPNL ValCur `json:"perp_unrealized_pnl"` + } `json:"portfolio_balances"` + SpotPositions []struct { + Asset string `json:"asset"` + AccountUUID string `json:"account_uuid"` + TotalBalanceFiat float64 `json:"total_balance_fiat"` + TotalBalanceCrypto float64 `json:"total_balance_crypto"` + AvailableToTreadeFiat float64 `json:"available_to_trade_fiat"` + Allocation float64 `json:"allocation"` + OneDayChange float64 `json:"one_day_change"` + CostBasis ValCur `json:"cost_basis"` + AssetImgURL string `json:"asset_img_url"` + IsCash bool `json:"is_cash"` + } `json:"spot_positions"` + PerpPositions []struct { + ProductID string `json:"product_id"` + ProductUUID string `json:"product_uuid"` + Symbol string `json:"symbol"` + AssetImageURL string `json:"asset_image_url"` + VWAP NativeAndRaw `json:"vwap"` + PositionSide string `json:"position_side"` + NetSize float64 `json:"net_size,string"` + BuyOrderSize float64 `json:"buy_order_size,string"` + SellOrderSize float64 `json:"sell_order_size,string"` + IMContribution float64 `json:"im_contribution,string"` + UnrealizedPNL NativeAndRaw `json:"unrealized_pnl"` + MarkPrice NativeAndRaw `json:"mark_price"` + LiquidationPrice NativeAndRaw `json:"liquidation_price"` + Leverage float64 `json:"leverage,string"` + IMNotional NativeAndRaw `json:"im_notional"` + MMNotional NativeAndRaw `json:"mm_notional"` + PositionNotional NativeAndRaw `json:"position_notional"` + MarginType string `json:"margin_type"` + LiquidationBuffer float64 `json:"liquidation_buffer,string"` + LiquidationPercentage float64 `json:"liquidation_percentage,string"` + } `json:"perp_positions"` + FuturesPositions []struct { + ProductID string `json:"product_id"` + ContractSize float64 `json:"contract_size,string"` + Side string `json:"side"` + Amount float64 `json:"amount,string"` + AvgEntryPrice float64 `json:"avg_entry_price,string"` + CurrentPrice float64 `json:"current_price,string"` + UnrealizedPNL float64 `json:"unrealized_pnl,string"` + Expiry time.Time `json:"expiry"` + UnderlyingAsset string `json:"underlying_asset"` + AssetImgURL string `json:"asset_img_url"` + ProductName string `json:"product_name"` + Venue string `json:"venue"` + NotionalValue float64 `json:"notional_value,string"` + } `json:"futures_positions"` + } `json:"breakdown"` +} + // TransactionSummary contains a summary of transaction fees, volume, and the like. Returned // by GetTransactionSummary type TransactionSummary struct { diff --git a/exchanges/coinbasepro/coinbasepro_wrapper.go b/exchanges/coinbasepro/coinbasepro_wrapper.go index 5e40a8c7309..fd0ff4613e0 100644 --- a/exchanges/coinbasepro/coinbasepro_wrapper.go +++ b/exchanges/coinbasepro/coinbasepro_wrapper.go @@ -2,6 +2,7 @@ package coinbasepro import ( "context" + "encoding/hex" "errors" "fmt" "sort" @@ -11,6 +12,7 @@ import ( "github.com/gofrs/uuid" "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" @@ -238,9 +240,9 @@ func (c *CoinbasePro) FetchTradablePairs(ctx context.Context, a asset.Item) (cur var err error switch a { case asset.Spot: - products, err = c.GetAllProducts(ctx, 2<<30-1, 0, "SPOT", "", nil) + products, err = c.GetAllProducts(ctx, 2<<30-1, 0, asset.Spot.Upper(), "", nil) case asset.Futures: - products, err = c.GetAllProducts(ctx, 2<<30-1, 0, "FUTURE", "", nil) + products, err = c.GetAllProducts(ctx, 2<<30-1, 0, asset.Futures.Upper(), "", nil) default: err = asset.ErrNotSupported } @@ -350,17 +352,7 @@ 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 { - var aTString string - switch assetType { - case asset.Futures: - aTString = "FUTURE" - case asset.Spot: - aTString = "SPOT" - default: - return fmt.Errorf("%w %v", asset.ErrNotSupported, assetType) - } - - products, err := c.GetAllProducts(ctx, 2<<30-1, 0, aTString, "", nil) + products, err := c.GetAllProducts(ctx, 2<<30-1, 0, assetType.Upper(), "", nil) if err != nil { return err } @@ -587,7 +579,7 @@ func (c *CoinbasePro) GetAccountFundingHistory(ctx context.Context) ([]exchange. } // GetWithdrawalsHistory returns previous withdrawals data -func (c *CoinbasePro) GetWithdrawalsHistory(ctx context.Context, cur currency.Code, a asset.Item) ([]exchange.WithdrawalHistory, error) { +func (c *CoinbasePro) GetWithdrawalsHistory(ctx context.Context, cur currency.Code, _ asset.Item) ([]exchange.WithdrawalHistory, error) { tempWallIDs, err := c.GetAllWallets(ctx, PaginationInp{}) if err != nil { @@ -671,7 +663,8 @@ func (c *CoinbasePro) GetHistoricTrades(ctx context.Context, p currency.Pair, as statuses := []string{"FILLED", "CANCELLED", "EXPIRED", "FAILED"} - ord, err := c.GetAllOrders(ctx, p.String(), "", "", "", "", "", "", "", statuses, 2<<30-1, startDate, endDate) + ord, err := c.GetAllOrders(ctx, p.String(), "", "", "", "", assetType.Upper(), "", "", statuses, 2<<30-1, + startDate, endDate) if err != nil { return nil, err @@ -692,74 +685,59 @@ func (c *CoinbasePro) GetHistoricTrades(ctx context.Context, p currency.Pair, as var price float64 var amount float64 if ord.Orders[i].OrderConfiguration.MarketMarketIOC != nil { - if ord.Orders[i].OrderConfiguration.MarketMarketIOC.QuoteSize != "" { - amount, err = strconv.ParseFloat(ord.Orders[i].OrderConfiguration.MarketMarketIOC.QuoteSize, 64) - if err != nil { - return nil, err - } + err = stringToFloatPtr(&amount, ord.Orders[i].OrderConfiguration.MarketMarketIOC.QuoteSize) + if err != nil { + return nil, err } - if ord.Orders[i].OrderConfiguration.MarketMarketIOC.BaseSize != "" { - amount, err = strconv.ParseFloat(ord.Orders[i].OrderConfiguration.MarketMarketIOC.BaseSize, 64) - if err != nil { - return nil, err - } + err = stringToFloatPtr(&amount, ord.Orders[i].OrderConfiguration.MarketMarketIOC.BaseSize) + if err != nil { + return nil, err } } + if ord.Orders[i].OrderConfiguration.LimitLimitGTC != nil { - if ord.Orders[i].OrderConfiguration.LimitLimitGTC.LimitPrice != "" { - price, err = strconv.ParseFloat(ord.Orders[i].OrderConfiguration.LimitLimitGTC.LimitPrice, 64) - if err != nil { - return nil, err - } + err = stringToFloatPtr(&price, ord.Orders[i].OrderConfiguration.LimitLimitGTC.LimitPrice) + if err != nil { + return nil, err } - if ord.Orders[i].OrderConfiguration.LimitLimitGTC.BaseSize != "" { - amount, err = strconv.ParseFloat(ord.Orders[i].OrderConfiguration.LimitLimitGTC.BaseSize, 64) - if err != nil { - return nil, err - } + err = stringToFloatPtr(&amount, ord.Orders[i].OrderConfiguration.LimitLimitGTC.BaseSize) + if err != nil { + return nil, err } + } if ord.Orders[i].OrderConfiguration.LimitLimitGTD != nil { - if ord.Orders[i].OrderConfiguration.LimitLimitGTD.LimitPrice != "" { - price, err = strconv.ParseFloat(ord.Orders[i].OrderConfiguration.LimitLimitGTD.LimitPrice, 64) - if err != nil { - return nil, err - } + err = stringToFloatPtr(&price, ord.Orders[i].OrderConfiguration.LimitLimitGTD.LimitPrice) + if err != nil { + return nil, err } - if ord.Orders[i].OrderConfiguration.LimitLimitGTD.BaseSize != "" { - amount, err = strconv.ParseFloat(ord.Orders[i].OrderConfiguration.LimitLimitGTD.BaseSize, 64) - if err != nil { - return nil, err - } + err = stringToFloatPtr(&amount, ord.Orders[i].OrderConfiguration.LimitLimitGTD.BaseSize) + if err != nil { + return nil, err } + } if ord.Orders[i].OrderConfiguration.StopLimitStopLimitGTC != nil { - if ord.Orders[i].OrderConfiguration.StopLimitStopLimitGTC.LimitPrice != "" { - price, err = strconv.ParseFloat(ord.Orders[i].OrderConfiguration.StopLimitStopLimitGTC.LimitPrice, 64) - if err != nil { - return nil, err - } + err = stringToFloatPtr(&price, ord.Orders[i].OrderConfiguration.StopLimitStopLimitGTC.LimitPrice) + if err != nil { + return nil, err } - if ord.Orders[i].OrderConfiguration.StopLimitStopLimitGTC.BaseSize != "" { - amount, err = strconv.ParseFloat(ord.Orders[i].OrderConfiguration.StopLimitStopLimitGTC.BaseSize, 64) - if err != nil { - return nil, err - } + err = stringToFloatPtr(&amount, ord.Orders[i].OrderConfiguration.StopLimitStopLimitGTC.BaseSize) + if err != nil { + return nil, err } + } if ord.Orders[i].OrderConfiguration.StopLimitStopLimitGTD != nil { - if ord.Orders[i].OrderConfiguration.StopLimitStopLimitGTD.LimitPrice != "" { - price, err = strconv.ParseFloat(ord.Orders[i].OrderConfiguration.StopLimitStopLimitGTD.LimitPrice, 64) - if err != nil { - return nil, err - } + err = stringToFloatPtr(&price, ord.Orders[i].OrderConfiguration.StopLimitStopLimitGTD.LimitPrice) + if err != nil { + return nil, err } - if ord.Orders[i].OrderConfiguration.StopLimitStopLimitGTD.BaseSize != "" { - amount, err = strconv.ParseFloat(ord.Orders[i].OrderConfiguration.StopLimitStopLimitGTD.BaseSize, 64) - if err != nil { - return nil, err - } + err = stringToFloatPtr(&amount, ord.Orders[i].OrderConfiguration.StopLimitStopLimitGTD.BaseSize) + if err != nil { + return nil, err } + } resp[i] = trade.Data{ @@ -881,29 +859,12 @@ func (c *CoinbasePro) CancelOrder(ctx context.Context, o *order.Cancel) error { // CancelBatchOrders cancels an orders by their corresponding ID numbers func (c *CoinbasePro) CancelBatchOrders(ctx context.Context, o []order.Cancel) (*order.CancelBatchResponse, error) { - if len(o) == 0 { - return nil, errOrderIDEmpty - } var status order.CancelBatchResponse - status.Status = make(map[string]string) - ordIDSlice := make([]string, len(o)) - for i := range o { - err := o[i].Validate(o[i].StandardCancel()) - if err != nil { - return nil, err - } - ordIDSlice[i] = o[i].OrderID - status.Status[o[i].OrderID] = "Failed to cancel" - } - resp, err := c.CancelOrders(ctx, ordIDSlice) + var err error + status.Status, _, err = c.cancelOrdersReturnMapAndCount(ctx, o) if err != nil { return nil, err } - for i := range resp.Results { - if resp.Results[i].Success { - status.Status[resp.Results[i].OrderID] = order.Cancelled.String() - } - } return &status, nil } @@ -939,113 +900,225 @@ func (c *CoinbasePro) CancelAllOrders(ctx context.Context, can *order.Cancel) (o orders = append(orders, order.Cancel{OrderID: ordIDs[i].OrderID}) } - batchResp, err := c.CancelBatchOrders(ctx, orders) + batchResp, count, err := c.cancelOrdersReturnMapAndCount(ctx, orders) if err != nil { return resp, err } - resp.Status = batchResp.Status - resp.Count = int64(len(orders)) + resp.Status = batchResp + resp.Count = count return resp, nil } // GetOrderInfo returns order information based on order ID -func (c *CoinbasePro) GetOrderInfo(ctx context.Context, orderID string, pair currency.Pair, asset asset.Item) (*order.Detail, error) { - // genOrderDetail, err := c.GetOrderByID(ctx, orderID, "", "") - // if err != nil { - // return nil, err - // } - - // var amount float64 - // if genOrderDetail.OrderConfiguration.MarketMarketIOC != nil { - // if genOrderDetail.OrderConfiguration.MarketMarketIOC.QuoteSize != "" { - // amount, err = strconv.ParseFloat(genOrderDetail.OrderConfiguration.MarketMarketIOC.QuoteSize, 64) - // if err != nil { - // return nil, err - // } - // } - // if genOrderDetail.OrderConfiguration.MarketMarketIOC.BaseSize != "" { - // amount, err = strconv.ParseFloat(genOrderDetail.OrderConfiguration.MarketMarketIOC.BaseSize, 64) - // if err != nil { - // return nil, err - // } - // } - // } - // var price float64 - // var postOnly bool - // if genOrderDetail.OrderConfiguration.LimitLimitGTC != nil { - - // postOnly = genOrderDetail.OrderConfiguration.LimitLimitGTC.PostOnly - // } - // if genOrderDetail.OrderConfiguration.LimitLimitGTD != nil { - // postOnly = genOrderDetail.OrderConfiguration.LimitLimitGTD.PostOnly - // } - - // response := order.Detail{ - // ImmediateOrCancel: genOrderDetail.OrderConfiguration.MarketMarketIOC != nil, - // PostOnly: postOnly, - // Price: - // } +func (c *CoinbasePro) GetOrderInfo(ctx context.Context, orderID string, pair currency.Pair, assetItem asset.Item) (*order.Detail, error) { + genOrderDetail, err := c.GetOrderByID(ctx, orderID, "", "") + if err != nil { + return nil, err + } - // orderStatus, err := order.StringToOrderStatus(genOrderDetail.Status) - // if err != nil { - // return nil, fmt.Errorf("error parsing order status: %w", err) - // } - // orderType, err := order.StringToOrderType(genOrderDetail.Type) - // if err != nil { - // return nil, fmt.Errorf("error parsing order type: %w", err) - // } - // orderSide, err := order.StringToOrderSide(genOrderDetail.Side) - // if err != nil { - // return nil, fmt.Errorf("error parsing order side: %w", err) - // } - // pair, err := currency.NewPairDelimiter(genOrderDetail.ProductID, "-") - // if err != nil { - // return nil, fmt.Errorf("error parsing order pair: %w", err) - // } + var amount float64 + var quoteAmount float64 + var orderType order.Type + if genOrderDetail.OrderConfiguration.MarketMarketIOC != nil { + err = stringToFloatPtr("eAmount, genOrderDetail.OrderConfiguration.MarketMarketIOC.QuoteSize) + if err != nil { + return nil, err + } + err = stringToFloatPtr(&amount, genOrderDetail.OrderConfiguration.MarketMarketIOC.BaseSize) + if err != nil { + return nil, err + } + orderType = order.Market + } + var price float64 + var postOnly bool + if genOrderDetail.OrderConfiguration.LimitLimitGTC != nil { + err = stringToFloatPtr(&amount, genOrderDetail.OrderConfiguration.LimitLimitGTC.BaseSize) + if err != nil { + return nil, err + } + err = stringToFloatPtr(&price, genOrderDetail.OrderConfiguration.LimitLimitGTC.LimitPrice) + if err != nil { + return nil, err + } + postOnly = genOrderDetail.OrderConfiguration.LimitLimitGTC.PostOnly + orderType = order.Limit + } + if genOrderDetail.OrderConfiguration.LimitLimitGTD != nil { + err = stringToFloatPtr(&amount, genOrderDetail.OrderConfiguration.LimitLimitGTD.BaseSize) + if err != nil { + return nil, err + } + err = stringToFloatPtr(&price, genOrderDetail.OrderConfiguration.LimitLimitGTD.LimitPrice) + if err != nil { + return nil, err + } + postOnly = genOrderDetail.OrderConfiguration.LimitLimitGTD.PostOnly + orderType = order.Limit + } + var triggerPrice float64 + if genOrderDetail.OrderConfiguration.StopLimitStopLimitGTC != nil { + err = stringToFloatPtr(&amount, genOrderDetail.OrderConfiguration.StopLimitStopLimitGTC.BaseSize) + if err != nil { + return nil, err + } + err = stringToFloatPtr(&price, genOrderDetail.OrderConfiguration.StopLimitStopLimitGTC.LimitPrice) + if err != nil { + return nil, err + } + err = stringToFloatPtr(&triggerPrice, genOrderDetail.OrderConfiguration.StopLimitStopLimitGTC.StopPrice) + if err != nil { + return nil, err + } + orderType = order.StopLimit + } + if genOrderDetail.OrderConfiguration.StopLimitStopLimitGTD != nil { + err = stringToFloatPtr(&amount, genOrderDetail.OrderConfiguration.StopLimitStopLimitGTD.BaseSize) + if err != nil { + return nil, err + } + err = stringToFloatPtr(&price, genOrderDetail.OrderConfiguration.StopLimitStopLimitGTD.LimitPrice) + if err != nil { + return nil, err + } + err = stringToFloatPtr(&triggerPrice, genOrderDetail.OrderConfiguration.StopLimitStopLimitGTD.StopPrice) + if err != nil { + return nil, err + } + orderType = order.StopLimit + } + var remainingAmount float64 + if !genOrderDetail.SizeInQuote { + remainingAmount = amount - genOrderDetail.FilledSize + } + var orderSide order.Side + switch genOrderDetail.Side { + case order.Buy.String(): + orderSide = order.Buy + case order.Sell.String(): + orderSide = order.Sell + } + var orderStatus order.Status + switch genOrderDetail.Status { + case order.Open.String(): + orderStatus = order.Open + case order.Filled.String(): + orderStatus = order.Filled + case order.Cancelled.String(): + orderStatus = order.Cancelled + case order.Expired.String(): + orderStatus = order.Expired + case "FAILED": + orderStatus = order.Rejected + case "UNKNOWN_ORDER_STATUS": + orderStatus = order.UnknownStatus + } + var closeTime time.Time + if genOrderDetail.Settled { + closeTime = genOrderDetail.LastFillTime + } + var lastUpdateTime time.Time + if len(genOrderDetail.EditHistory) > 0 { + lastUpdateTime = genOrderDetail.EditHistory[len(genOrderDetail.EditHistory)-1].ReplaceAcceptTimestamp + } + + response := order.Detail{ + ImmediateOrCancel: genOrderDetail.OrderConfiguration.MarketMarketIOC != nil, + PostOnly: postOnly, + Price: price, + Amount: amount, + TriggerPrice: triggerPrice, + AverageExecutedPrice: genOrderDetail.AverageFilledPrice, + QuoteAmount: quoteAmount, + ExecutedAmount: genOrderDetail.FilledSize, + RemainingAmount: remainingAmount, + Cost: genOrderDetail.TotalValueAfterFees, + Fee: genOrderDetail.TotalFees, + Exchange: c.GetName(), + OrderID: genOrderDetail.OrderID, + ClientOrderID: genOrderDetail.ClientOID, + ClientID: genOrderDetail.UserID, + Type: orderType, + Side: orderSide, + Status: orderStatus, + AssetType: assetItem, + Date: genOrderDetail.CreatedTime, + CloseTime: closeTime, + LastUpdated: lastUpdateTime, + Pair: pair, + } + + fmt.Printf("%+v\n", response) + + fillData, err := c.GetFills(ctx, orderID, "", "", 2<<15-1, time.Time{}, time.Now()) + if err != nil { + return nil, err + } + cursor := fillData.Cursor + for cursor != "" { + tempFillData, err := c.GetFills(ctx, orderID, "", cursor, 2<<15-1, time.Time{}, time.Now()) + if err != nil { + return nil, err + } + fillData.Fills = append(fillData.Fills, tempFillData.Fills...) + cursor = tempFillData.Cursor + } + response.Trades = make([]order.TradeHistory, len(fillData.Fills)) + switch orderSide { + case order.Buy: + orderSide = order.Sell + case order.Sell: + orderSide = order.Buy + } + for i := range fillData.Fills { + response.Trades[i] = order.TradeHistory{ + Price: fillData.Fills[i].Price, + Amount: fillData.Fills[i].Size, + Fee: fillData.Fills[i].Commission, + Exchange: c.GetName(), + TID: fillData.Fills[i].TradeID, + Side: orderSide, + Timestamp: fillData.Fills[i].TradeTime, + Total: fillData.Fills[i].Price * fillData.Fills[i].Size, + } + } - // response := order.Detail{ - // Exchange: c.GetName(), - // OrderID: genOrderDetail.ID, - // Pair: pair, - // Side: orderSide, - // Type: orderType, - // Date: genOrderDetail.DoneAt, - // Status: orderStatus, - // Price: genOrderDetail.Price, - // Amount: genOrderDetail.Size, - // ExecutedAmount: genOrderDetail.FilledSize, - // RemainingAmount: genOrderDetail.Size - genOrderDetail.FilledSize, - // Fee: genOrderDetail.FillFees, - // } - // fillResponse, err := c.GetFills(ctx, orderID, genOrderDetail.ProductID) - // if err != nil { - // return nil, fmt.Errorf("error retrieving the order fills: %w", err) - // } - // for i := range fillResponse { - // var fillSide order.Side - // fillSide, err = order.StringToOrderSide(fillResponse[i].Side) - // if err != nil { - // return nil, fmt.Errorf("error parsing order Side: %w", err) - // } - // response.Trades = append(response.Trades, order.TradeHistory{ - // Timestamp: fillResponse[i].CreatedAt, - // TID: strconv.FormatInt(fillResponse[i].TradeID, 10), - // Price: fillResponse[i].Price, - // Amount: fillResponse[i].Size, - // Exchange: c.GetName(), - // Type: orderType, - // Side: fillSide, - // Fee: fillResponse[i].Fee, - // }) - // } - return nil, errors.New("function not properly implemented") + return &response, nil } // GetDepositAddress returns a deposit address for a specified currency -func (c *CoinbasePro) GetDepositAddress(_ context.Context, _ currency.Code, _, _ string) (*deposit.Address, error) { - return nil, common.ErrFunctionNotSupported +func (c *CoinbasePro) GetDepositAddress(ctx context.Context, cryptocurrency currency.Code, _, chain string) (*deposit.Address, error) { + allWalResp, err := c.GetAllWallets(ctx, PaginationInp{}) + if err != nil { + 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 + } + } + } + if targetWalletID == "" { + createWalResp, err := c.CreateWallet(ctx, cryptocurrency.String()) + if err != nil { + return nil, err + } + targetWalletID = createWalResp.Data.ID + } + resp, err := c.CreateAddress(ctx, targetWalletID, "") + if err != nil { + return nil, err + } + return &deposit.Address{ + Address: resp.Data.Address, + Tag: resp.Data.Name, + Chain: resp.Data.Network, + }, nil } // WithdrawCryptocurrencyFunds returns a withdrawal ID when a withdrawal is @@ -1054,17 +1127,55 @@ func (c *CoinbasePro) WithdrawCryptocurrencyFunds(ctx context.Context, withdrawR if err := withdrawRequest.Validate(); err != nil { return nil, err } - // resp, err := c.WithdrawCrypto(ctx, - // withdrawRequest.Amount, - // withdrawRequest.Currency.String(), - // withdrawRequest.Crypto.Address) - // if err != nil { - // return nil, err - // } - // return &withdraw.ExchangeResponse{ - // ID: resp.ID, - // }, err - return nil, common.ErrFunctionNotSupported + gawResp, err := c.GetAllWallets(ctx, PaginationInp{}) + if err != nil { + return nil, err + } + if len(gawResp.Data) == 0 { + return nil, errNoMatchingWallets + } + var wID string + for i := range gawResp.Data { + if gawResp.Data[i].Currency.Code == withdrawRequest.Currency.String() { + wID = gawResp.Data[i].ID + break + } + } + + message := fmt.Sprintf("%+v", withdrawRequest) + + creds, err := c.GetCredentials(ctx) + if err != nil { + return nil, err + } + + 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] + + resp, err := c.SendMoney(ctx, "send", wID, 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 } // WithdrawFiatFunds returns a withdrawal ID when a withdrawal is @@ -1371,3 +1482,47 @@ func (c *CoinbasePro) GetFuturesContractDetails(context.Context, asset.Item) ([] func (c *CoinbasePro) UpdateOrderExecutionLimits(_ context.Context, _ asset.Item) error { return common.ErrNotYetImplemented } + +// cancelOrdersReturnMapAndCount is a helper function for CancelBatchOrders and CancelAllOrders, +// calling the appropriate Coinbase endpoint, and returning information useful to both +func (c *CoinbasePro) cancelOrdersReturnMapAndCount(ctx context.Context, o []order.Cancel) (map[string]string, int64, error) { + if len(o) == 0 { + return nil, 0, errOrderIDEmpty + } + status := make(map[string]string) + ordIDSlice := make([]string, len(o)) + for i := range o { + err := o[i].Validate(o[i].StandardCancel()) + if err != nil { + return nil, 0, err + } + ordIDSlice[i] = o[i].OrderID + status[o[i].OrderID] = "Failed to cancel" + } + resp, err := c.CancelOrders(ctx, ordIDSlice) + if err != nil { + return nil, 0, err + } + var counter int64 + for i := range resp.Results { + if resp.Results[i].Success { + status[resp.Results[i].OrderID] = order.Cancelled.String() + counter++ + } + } + return status, counter, nil +} + +// stringToFloatPtr essentially calls ParseFloat, but leaves the float alone instead of erroring out +// if the string is empty. +func stringToFloatPtr(outgoing *float64, incoming string) error { + if outgoing == nil { + return errPointerNil + } + var err error + if incoming != "" { + *outgoing, err = strconv.ParseFloat(incoming, 64) + return err + } + return nil +} From 9a6c7e6ee9ed3fb3c462f9fc050900f227735081 Mon Sep 17 00:00:00 2001 From: Samuel Reid <43227667+cranktakular@users.noreply.github.com> Date: Wed, 27 Dec 2023 17:38:24 +1100 Subject: [PATCH 14/79] More Coinbase wrapper progress --- currency/code_types.go | 4 + exchanges/coinbasepro/coinbasepro.go | 112 ++-- exchanges/coinbasepro/coinbasepro_test.go | 264 ++++++++- exchanges/coinbasepro/coinbasepro_types.go | 28 +- exchanges/coinbasepro/coinbasepro_wrapper.go | 566 ++++++++++--------- portfolio/withdraw/withdraw_types.go | 3 + 6 files changed, 611 insertions(+), 366 deletions(-) diff --git a/currency/code_types.go b/currency/code_types.go index db3d167996b..0f8f12165fe 100644 --- a/currency/code_types.go +++ b/currency/code_types.go @@ -3021,6 +3021,10 @@ var ( FI = NewCode("FI") USDM = NewCode("USDM") USDTM = NewCode("USDTM") + CBETH = NewCode("CBETH") + PYUSD = NewCode("PYUSD") + EUROC = NewCode("EUROC") + LSETH = NewCode("LSETH") stables = Currencies{ USDT, diff --git a/exchanges/coinbasepro/coinbasepro.go b/exchanges/coinbasepro/coinbasepro.go index 73fd5d3df1f..f8443bd1f05 100644 --- a/exchanges/coinbasepro/coinbasepro.go +++ b/exchanges/coinbasepro/coinbasepro.go @@ -15,6 +15,8 @@ import ( "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/common/crypto" + "github.com/thrasher-corp/gocryptotrader/common/key" + "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/request" @@ -80,6 +82,8 @@ const ( granOneDay = "ONE_DAY" startDateString = "start_date" endDateString = "end_date" + + errPayMethodNotFound = "payment method '%v' not found" ) var ( @@ -108,6 +112,7 @@ var ( errPointerNil = errors.New("relevant pointer is nil") errNameEmpty = errors.New("name cannot be empty") errPortfolioIDEmpty = errors.New("portfolio id cannot be empty") + errFeeTypeNotSupported = errors.New("fee type not supported") ) // GetAllAccounts returns information on all trading accounts associated with the API key @@ -2072,56 +2077,63 @@ func (c *CoinbasePro) SendAuthenticatedHTTPRequest(ctx context.Context, ep excha return json.Unmarshal(interim, result) } -// // GetFee returns an estimate of fee based on type of transaction -// func (c *CoinbasePro) GetFee(ctx context.Context, feeBuilder *exchange.FeeBuilder) (float64, error) { -// var fee float64 -// switch feeBuilder.FeeType { -// case exchange.CryptocurrencyTradeFee: -// fees, err := c.GetFees(ctx) -// if err != nil { -// fee = fees.TakerFeeRate -// } else { -// fee = 0.006 -// } -// case exchange.InternationalBankWithdrawalFee: -// fee = getInternationalBankWithdrawalFee(feeBuilder.FiatCurrency) -// case exchange.InternationalBankDepositFee: -// fee = getInternationalBankDepositFee(feeBuilder.FiatCurrency) -// case exchange.OfflineTradeFee: -// fee = getOfflineTradeFee(feeBuilder.PurchasePrice, feeBuilder.Amount) -// } - -// if fee < 0 { -// fee = 0 -// } - -// return fee, nil -// } - -// // getOfflineTradeFee calculates the worst case-scenario trading fee -// func getOfflineTradeFee(price, amount float64) float64 { -// return 0.0025 * price * amount -// } - -// func (c *CoinbasePro) calculateTradingFee(trailingVolume []Volume, base, quote currency.Code, delimiter string, purchasePrice, amount float64, isMaker bool) float64 { -// var fee float64 -// for _, i := range trailingVolume { -// if strings.EqualFold(i.ProductID, base.String()+delimiter+quote.String()) { -// switch { -// case isMaker: -// fee = 0 -// case i.Volume <= 10000000: -// fee = 0.003 -// case i.Volume > 10000000 && i.Volume <= 100000000: -// fee = 0.002 -// case i.Volume > 100000000: -// fee = 0.001 -// } -// break -// } -// } -// return fee * amount * purchasePrice -// } +// GetFee returns an estimate of fee based on type of transaction +func (c *CoinbasePro) GetFee(ctx context.Context, feeBuilder *exchange.FeeBuilder) (float64, error) { + if feeBuilder == nil { + return 0, fmt.Errorf("%T %w", feeBuilder, common.ErrNilPointer) + } + var fee float64 + switch { + case !c.IsStablePair(feeBuilder.Pair) && feeBuilder.FeeType == exchange.CryptocurrencyTradeFee: + fees, err := c.GetTransactionSummary(ctx, time.Now().Add(-time.Hour*24*30), time.Now(), "", "", "") + fmt.Printf("Fees struct: %v\n", fees) + if err != nil { + return 0, err + } + if feeBuilder.IsMaker { + fee = fees.FeeTier.MakerFeeRate + } else { + fee = fees.FeeTier.TakerFeeRate + } + case feeBuilder.IsMaker && c.IsStablePair(feeBuilder.Pair) && + (feeBuilder.FeeType == exchange.CryptocurrencyTradeFee || feeBuilder.FeeType == exchange.OfflineTradeFee): + fee = 0 + case !feeBuilder.IsMaker && c.IsStablePair(feeBuilder.Pair) && + (feeBuilder.FeeType == exchange.CryptocurrencyTradeFee || feeBuilder.FeeType == exchange.OfflineTradeFee): + fee = 0.00001 + case feeBuilder.IsMaker && !c.IsStablePair(feeBuilder.Pair) && feeBuilder.FeeType == exchange.OfflineTradeFee: + fee = 0.006 + case !feeBuilder.IsMaker && !c.IsStablePair(feeBuilder.Pair) && feeBuilder.FeeType == exchange.OfflineTradeFee: + fee = 0.008 + default: + return 0, errFeeTypeNotSupported + } + return fee * feeBuilder.Amount * feeBuilder.PurchasePrice, nil +} + +var stableMap = map[key.PairAsset]bool{ + {Base: currency.USDT.Item, Quote: currency.USD.Item}: true, + {Base: currency.USDT.Item, Quote: currency.EUR.Item}: true, + {Base: currency.USDC.Item, Quote: currency.EUR.Item}: true, + {Base: currency.USDC.Item, Quote: currency.GBP.Item}: true, + {Base: currency.USDT.Item, Quote: currency.GBP.Item}: true, + {Base: currency.USDT.Item, Quote: currency.USDC.Item}: true, + {Base: currency.DAI.Item, Quote: currency.USD.Item}: true, + {Base: currency.CBETH.Item, Quote: currency.ETH.Item}: true, + {Base: currency.PYUSD.Item, Quote: currency.USD.Item}: true, + {Base: currency.EUROC.Item, Quote: currency.USD.Item}: true, + {Base: currency.GUSD.Item, Quote: currency.USD.Item}: true, + {Base: currency.EUROC.Item, Quote: currency.EUR.Item}: true, + {Base: currency.WBTC.Item, Quote: currency.BTC.Item}: true, + {Base: currency.LSETH.Item, Quote: currency.ETH.Item}: true, + {Base: currency.GYEN.Item, Quote: currency.USD.Item}: true, + {Base: currency.PAX.Item, Quote: currency.USD.Item}: true, +} + +// IsStablePair returns true if the currency pair is considered a "stable pair" by Coinbase +func (c *CoinbasePro) IsStablePair(pair currency.Pair) bool { + return stableMap[key.PairAsset{Base: pair.Base.Item, Quote: pair.Quote.Item}] +} // func getInternationalBankWithdrawalFee(c currency.Code) float64 { // var fee float64 diff --git a/exchanges/coinbasepro/coinbasepro_test.go b/exchanges/coinbasepro/coinbasepro_test.go index 1d7d733020b..65f4acc5a68 100644 --- a/exchanges/coinbasepro/coinbasepro_test.go +++ b/exchanges/coinbasepro/coinbasepro_test.go @@ -51,9 +51,14 @@ const ( errIDNotSet = "ID not set" skipPayMethodNotFound = "no payment methods found, skipping" skipInsufSuitableAccs = "insufficient suitable accounts found, skipping" + skipInsufficientFunds = "insufficient funds for test, skipping" errx7f = "setting proxy address error parse \"\\x7f\": net/url: invalid control character in URL" errPortfolioNameDuplicate = `CoinbasePro unsuccessful HTTP status code: 409 raw response: {"error":"CONFLICT","error_details":"[PORTFOLIO_ERROR_CODE_ALREADY_EXISTS] the requested portfolio name already exists","message":"[PORTFOLIO_ERROR_CODE_ALREADY_EXISTS] the requested portfolio name already exists"}, authenticated request failed` errPortTransferInsufFunds = `CoinbasePro unsuccessful HTTP status code: 429 raw response: {"error":"unknown","error_details":"[PORTFOLIO_ERROR_CODE_INSUFFICIENT_FUNDS] insufficient funds in source account","message":"[PORTFOLIO_ERROR_CODE_INSUFFICIENT_FUNDS] insufficient funds in source account"}, authenticated request failed` + errFeeBuilderNil = "*exchange.FeeBuilder nil pointer" + errUnsupportedAssetType = " unsupported asset type" + + testAmount = 0.00000001 ) func TestMain(m *testing.M) { @@ -402,7 +407,7 @@ func TestMovePortfolioFunds(t *testing.T) { t.Skip("fewer than 2 portfolios found, skipping") } _, err = c.MovePortfolioFunds(context.Background(), portID.Portfolios[0].UUID, portID.Portfolios[1].UUID, "BTC", - 0.00000001) + testAmount) if err != nil && err.Error() != errPortTransferInsufFunds { t.Error(err) } @@ -890,8 +895,27 @@ func TestSendMoney(t *testing.T) { if len(wID.Data) < 2 { t.Skip("fewer than 2 wallets found, skipping test") } + var ( + fromID string + toID string + ) + for i := range wID.Data { + if wID.Data[i].Currency.Name == "BTC" { + if wID.Data[i].Balance.Amount > 0 { + fromID = wID.Data[i].ID + } else { + toID = wID.Data[i].ID + } + } + if fromID != "" && toID != "" { + break + } + } + if fromID == "" || toID == "" { + t.Skip("insufficient funds or BTC wallets, skipping") + } _, err = c.SendMoney(context.Background(), "transfer", wID.Data[0].ID, wID.Data[1].ID, - "BTC", "GCT Test", "123", "", "", 0.00000001, false, false) + "BTC", "GCT Test", "123", "", "", testAmount, false, false) if err != nil { t.Error(err) } @@ -2486,7 +2510,7 @@ func TestWsStatus(t *testing.T) { "quote_currency": "USD", "base_min_size": "0.001", "base_max_size": "70", - "base_increment": "0.00000001", + "base_increment": "testAmount", "quote_increment": "0.01", "display_name": "BTC/USD", "status": "online", @@ -2520,10 +2544,10 @@ func TestWsStatus(t *testing.T) { { "id": "BTC", "name": "Bitcoin", - "min_size": "0.00000001", + "min_size": "testAmount", "status": "online", "status_message": null, - "max_precision": "0.00000001", + "max_precision": "testAmount", "convertible_to": [] } ] @@ -2770,6 +2794,72 @@ func TestStatusToStandardStatus(t *testing.T) { */ +func TestGetFee(t *testing.T) { + _, err := c.GetFee(context.Background(), nil) + if err.Error() != errFeeBuilderNil { + t.Errorf(errExpectMismatch, errFeeBuilderNil, err) + } + feeBuilder := exchange.FeeBuilder{ + FeeType: exchange.OfflineTradeFee, + Amount: 1, + PurchasePrice: 1, + } + resp, err := c.GetFee(context.Background(), &feeBuilder) + if err != nil { + t.Error(err) + } + if resp != 0.008 { + t.Errorf(errExpectMismatch, resp, 0.008) + } + feeBuilder.IsMaker = true + resp, err = c.GetFee(context.Background(), &feeBuilder) + if err != nil { + t.Error(err) + } + if resp != 0.006 { + t.Errorf(errExpectMismatch, resp, 0.006) + } + feeBuilder.Pair = currency.NewPair(currency.USDT, currency.USD) + resp, err = c.GetFee(context.Background(), &feeBuilder) + if err != nil { + t.Error(err) + } + if resp != 0 { + t.Errorf(errExpectMismatch, resp, 0) + } + feeBuilder.IsMaker = false + resp, err = c.GetFee(context.Background(), &feeBuilder) + if err != nil { + t.Error(err) + } + if resp != 0.00001 { + t.Errorf(errExpectMismatch, resp, 0.00001) + } + feeBuilder.FeeType = exchange.CryptocurrencyDepositFee + _, err = c.GetFee(context.Background(), &feeBuilder) + if err != errFeeTypeNotSupported { + t.Errorf(errExpectMismatch, errFeeTypeNotSupported, err) + } + feeBuilder.Pair = currency.Pair{} + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + feeBuilder.FeeType = exchange.CryptocurrencyTradeFee + resp, err = c.GetFee(context.Background(), &feeBuilder) + if err != nil { + t.Error(err) + } + if !(resp <= 0.008 && resp >= 0.0005) { + t.Errorf("expected fee range of 0.0005 and 0.008, received %v", resp) + } + feeBuilder.IsMaker = true + resp, err = c.GetFee(context.Background(), &feeBuilder) + if err != nil { + t.Error(err) + } + if !(resp <= 0.006 && resp >= 0) { + t.Errorf("expected fee range of 0 and 0.006, received %v", resp) + } +} + func TestPrepareDateString(t *testing.T) { t.Parallel() var expectedResult Params @@ -3070,22 +3160,6 @@ func TestGetWithdrawalsHistory(t *testing.T) { } } -func TestGetRecentTrades(t *testing.T) { - sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - _, err := c.GetRecentTrades(context.Background(), testPair, asset.Spot) - if err != nil { - t.Error(err) - } -} - -func TestGetHistoricTrades(t *testing.T) { - sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - _, err := c.GetHistoricTrades(context.Background(), testPair, asset.Spot, time.Time{}, time.Now()) - if err != nil { - t.Error(err) - } -} - func TestSubmitOrder(t *testing.T) { _, err := c.SubmitOrder(context.Background(), nil) if !errors.Is(err, common.ErrNilPointer) { @@ -3257,18 +3331,145 @@ func TestWithdrawCryptocurrencyFunds(t *testing.T) { if !errors.Is(err, common.ErrExchangeNameUnset) { t.Errorf(errExpectMismatch, err, common.ErrExchangeNameUnset) } - sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) req.Exchange = c.Name req.Currency = currency.BTC - req.Amount = 0.00000001 + req.Amount = testAmount req.Type = withdraw.Crypto req.Crypto.Address = testAddress _, err = c.WithdrawCryptocurrencyFunds(context.Background(), &req) + if !errors.Is(err, errWalletIDEmpty) { + t.Errorf(errExpectMismatch, err, errWalletIDEmpty) + } + sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) + wallets, err := c.GetAllWallets(context.Background(), PaginationInp{}) + if err != nil { + t.Error(err) + } + if len(wallets.Data) == 0 { + t.Fatal(errExpectedNonEmpty) + } + for i := range wallets.Data { + if wallets.Data[i].Currency.Name == currency.BTC.String() && wallets.Data[i].Balance.Amount > testAmount { + req.WalletID = wallets.Data[i].ID + break + } + } + if req.WalletID == "" { + t.Skip(skipInsufficientFunds) + } + _, err = c.WithdrawCryptocurrencyFunds(context.Background(), &req) + if err != nil { + t.Error(err) + } +} + +func TestWithdrawFiatFunds(t *testing.T) { + req := withdraw.Request{} + _, err := c.WithdrawFiatFunds(context.Background(), &req) + if !errors.Is(err, common.ErrExchangeNameUnset) { + t.Errorf(errExpectMismatch, err, common.ErrExchangeNameUnset) + } + req.Exchange = c.Name + req.Currency = currency.AUD + req.Amount = 1 + req.Type = withdraw.Fiat + req.Fiat.Bank.Enabled = true + req.Fiat.Bank.SupportedExchanges = "CoinbasePro" + req.Fiat.Bank.SupportedCurrencies = "AUD" + req.Fiat.Bank.AccountNumber = "123" + req.Fiat.Bank.SWIFTCode = "456" + req.Fiat.Bank.BSBNumber = "789" + _, err = c.WithdrawFiatFunds(context.Background(), &req) + if !errors.Is(err, errWalletIDEmpty) { + t.Errorf(errExpectMismatch, err, errWalletIDEmpty) + } + req.WalletID = "meow" + req.Fiat.Bank.BankName = "GCT's Fake and Not Real Test Bank Meow Meow" + expectedError := fmt.Sprintf(errPayMethodNotFound, req.Fiat.Bank.BankName) + _, err = c.WithdrawFiatFunds(context.Background(), &req) + if err.Error() != expectedError { + t.Errorf(errExpectMismatch, err, expectedError) + } + sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) + wallets, err := c.GetAllWallets(context.Background(), PaginationInp{}) + if err != nil { + t.Error(err) + } + if len(wallets.Data) == 0 { + t.Fatal(errExpectedNonEmpty) + } + for i := range wallets.Data { + if wallets.Data[i].Currency.Name == currency.AUD.String() && wallets.Data[i].Balance.Amount > testAmount { + req.WalletID = wallets.Data[i].ID + break + } + } + if req.WalletID == "" { + t.Skip(skipInsufficientFunds) + } + req.Fiat.Bank.BankName = "AUD Wallet" + _, err = c.WithdrawFiatFunds(context.Background(), &req) if err != nil { t.Error(err) } } +func TestWithdrawFiatFundsToInternationalBank(t *testing.T) { + req := withdraw.Request{} + _, err := c.WithdrawFiatFundsToInternationalBank(context.Background(), &req) + if !errors.Is(err, common.ErrExchangeNameUnset) { + t.Errorf(errExpectMismatch, err, common.ErrExchangeNameUnset) + } +} + +func TestGetFeeByType(t *testing.T) { + _, err := c.GetFeeByType(context.Background(), nil) + if err.Error() != errFeeBuilderNil { + t.Errorf(errExpectMismatch, err, errFeeBuilderNil) + } + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + var feeBuilder exchange.FeeBuilder + feeBuilder.FeeType = exchange.OfflineTradeFee + feeBuilder.Amount = 1 + feeBuilder.PurchasePrice = 1 + resp, err := c.GetFeeByType(context.Background(), &feeBuilder) + if err != nil { + t.Error(err) + } + if resp != 0.008 { + t.Errorf(errExpectMismatch, resp, 0.008) + } +} + +func TestGetActiveOrders(t *testing.T) { + _, err := c.GetActiveOrders(context.Background(), nil) + if !errors.Is(err, common.ErrNilPointer) { + t.Errorf(errExpectMismatch, err, common.ErrNilPointer) + } + var req order.MultiOrderRequest + _, err = c.GetActiveOrders(context.Background(), &req) + if err.Error() != errUnsupportedAssetType { + t.Errorf(errExpectMismatch, err, errUnsupportedAssetType) + } + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + req.AssetType = asset.Spot + req.Side = order.AnySide + req.Type = order.AnyType + _, err = c.GetActiveOrders(context.Background(), &req) + if err != nil { + t.Error(err) + } + req.Pairs = req.Pairs.Add(currency.NewPair(currency.BTC, currency.USD)) + _, err = c.GetActiveOrders(context.Background(), &req) + if err != nil { + t.Error(err) + } +} + +func TestGetOrderHistory(t *testing.T) { + +} + // 8837708 143.0 ns/op 24 B/op 5 allocs/op // func BenchmarkXxx(b *testing.B) { // for x := 0; x < b.N; x++ { @@ -3389,3 +3590,20 @@ func TestWithdrawCryptocurrencyFunds(t *testing.T) { // _ = str2 // } // } + +// func AssignStructData(a AmCur) { +// req := map[string]interface{}{"amount": a.Amount, "currency": a.Currency} +// _ = req +// } + +// BenchmarkAssignStructData-8 18488131 65.81 ns/op 0 B/op 0 allocs/op +// BenchmarkAssignStructData-8 18336296 67.94 ns/op 0 B/op 0 allocs/op +// BenchmarkAssignStructData-8 5245032 230.0 ns/op 0 B/op 0 allocs/op +// BenchmarkAssignStructData-8 6194977 193.4 ns/op 0 B/op 0 allocs/op +// func BenchmarkAssignStructData(b *testing.B) { +// for x := 0; x < b.N; x++ { +// AssignStructData(AmCur{}) +// AssignStructData(AmCur{}) +// AssignStructData(AmCur{}) +// } +// } diff --git a/exchanges/coinbasepro/coinbasepro_types.go b/exchanges/coinbasepro/coinbasepro_types.go index b79874369d7..84f187cd20d 100644 --- a/exchanges/coinbasepro/coinbasepro_types.go +++ b/exchanges/coinbasepro/coinbasepro_types.go @@ -440,23 +440,27 @@ type TransactionSummary struct { TotalVolume float64 `json:"total_volume"` TotalFees float64 `json:"total_fees"` FeeTier struct { - PricingTier float64 `json:"pricing_tier,string"` - USDFrom float64 `json:"usd_from,string"` - USDTo float64 `json:"usd_to,string"` - TakerFeeRate float64 `json:"taker_fee_rate,string"` - MakerFeeRate float64 `json:"maker_fee_rate,string"` - } + PricingTier string `json:"pricing_tier"` + USDFrom float64 `json:"usd_from,string"` + USDTo float64 `json:"usd_to,string"` + TakerFeeRate float64 `json:"taker_fee_rate,string"` + MakerFeeRate float64 `json:"maker_fee_rate,string"` + AOPFrom convert.StringToFloat64 `json:"aop_from"` + AOPTo convert.StringToFloat64 `json:"aop_to"` + } `json:"fee_tier"` MarginRate struct { Value float64 `json:"value,string"` - } + } `json:"margin_rate"` GoodsAndServicesTax struct { Rate float64 `json:"rate,string"` Type string `json:"type"` - } - AdvancedTradeOnlyVolume float64 `json:"advanced_trade_only_volume"` - AdvancedTradeOnlyFees float64 `json:"advanced_trade_only_fees"` - CoinbaseProVolume float64 `json:"coinbase_pro_volume"` - CoinbaseProFees float64 `json:"coinbase_pro_fees"` + } `json:"goods_and_services_tax"` + AdvancedTradeOnlyVolume float64 `json:"advanced_trade_only_volume"` + AdvancedTradeOnlyFees float64 `json:"advanced_trade_only_fees"` + CoinbaseProVolume float64 `json:"coinbase_pro_volume"` + CoinbaseProFees float64 `json:"coinbase_pro_fees"` + TotalBalance convert.StringToFloat64 `json:"total_balance"` + HasPromoFee bool `json:"has_promo_fee"` } // GetAllOrdersResp contains information on a lot of orders, returned by GetAllOrders diff --git a/exchanges/coinbasepro/coinbasepro_wrapper.go b/exchanges/coinbasepro/coinbasepro_wrapper.go index fd0ff4613e0..79ca74a2347 100644 --- a/exchanges/coinbasepro/coinbasepro_wrapper.go +++ b/exchanges/coinbasepro/coinbasepro_wrapper.go @@ -5,12 +5,10 @@ import ( "encoding/hex" "errors" "fmt" - "sort" "strconv" "sync" "time" - "github.com/gofrs/uuid" "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/common/crypto" "github.com/thrasher-corp/gocryptotrader/config" @@ -651,114 +649,12 @@ func (c *CoinbasePro) GetWithdrawalsHistory(ctx context.Context, cur currency.Co // GetRecentTrades returns the most recent trades for a currency and asset func (c *CoinbasePro) GetRecentTrades(ctx context.Context, p currency.Pair, assetType asset.Item) ([]trade.Data, error) { - return c.GetHistoricTrades(ctx, p, assetType, time.Time{}, time.Now()) + return nil, common.ErrFunctionNotSupported } // GetHistoricTrades returns historic trade data within the timeframe provided func (c *CoinbasePro) GetHistoricTrades(ctx context.Context, p currency.Pair, assetType asset.Item, startDate, endDate time.Time) ([]trade.Data, error) { - p, err := c.FormatExchangeCurrency(p, assetType) - if err != nil { - return nil, err - } - - statuses := []string{"FILLED", "CANCELLED", "EXPIRED", "FAILED"} - - ord, err := c.GetAllOrders(ctx, p.String(), "", "", "", "", assetType.Upper(), "", "", statuses, 2<<30-1, - startDate, endDate) - - if err != nil { - return nil, err - } - - resp := make([]trade.Data, len(ord.Orders)) - - for i := range ord.Orders { - var side order.Side - side, err = order.StringToOrderSide(ord.Orders[i].Side) - if err != nil { - return nil, err - } - id, err := uuid.FromString(ord.Orders[i].OrderID) - if err != nil { - return nil, err - } - var price float64 - var amount float64 - if ord.Orders[i].OrderConfiguration.MarketMarketIOC != nil { - err = stringToFloatPtr(&amount, ord.Orders[i].OrderConfiguration.MarketMarketIOC.QuoteSize) - if err != nil { - return nil, err - } - err = stringToFloatPtr(&amount, ord.Orders[i].OrderConfiguration.MarketMarketIOC.BaseSize) - if err != nil { - return nil, err - } - } - - if ord.Orders[i].OrderConfiguration.LimitLimitGTC != nil { - err = stringToFloatPtr(&price, ord.Orders[i].OrderConfiguration.LimitLimitGTC.LimitPrice) - if err != nil { - return nil, err - } - err = stringToFloatPtr(&amount, ord.Orders[i].OrderConfiguration.LimitLimitGTC.BaseSize) - if err != nil { - return nil, err - } - - } - if ord.Orders[i].OrderConfiguration.LimitLimitGTD != nil { - err = stringToFloatPtr(&price, ord.Orders[i].OrderConfiguration.LimitLimitGTD.LimitPrice) - if err != nil { - return nil, err - } - err = stringToFloatPtr(&amount, ord.Orders[i].OrderConfiguration.LimitLimitGTD.BaseSize) - if err != nil { - return nil, err - } - - } - if ord.Orders[i].OrderConfiguration.StopLimitStopLimitGTC != nil { - err = stringToFloatPtr(&price, ord.Orders[i].OrderConfiguration.StopLimitStopLimitGTC.LimitPrice) - if err != nil { - return nil, err - } - err = stringToFloatPtr(&amount, ord.Orders[i].OrderConfiguration.StopLimitStopLimitGTC.BaseSize) - if err != nil { - return nil, err - } - - } - if ord.Orders[i].OrderConfiguration.StopLimitStopLimitGTD != nil { - err = stringToFloatPtr(&price, ord.Orders[i].OrderConfiguration.StopLimitStopLimitGTD.LimitPrice) - if err != nil { - return nil, err - } - err = stringToFloatPtr(&amount, ord.Orders[i].OrderConfiguration.StopLimitStopLimitGTD.BaseSize) - if err != nil { - return nil, err - } - - } - - resp[i] = trade.Data{ - ID: id, - Exchange: c.Name, - CurrencyPair: p, - AssetType: assetType, - Side: side, - Price: price, - Amount: amount, - Timestamp: ord.Orders[i].CreatedTime, - } - } - - err = c.AddTradesToBuffer(resp...) - if err != nil { - return nil, err - } - - sort.Sort(trade.ByDate(resp)) - return resp, nil + return nil, common.ErrFunctionNotSupported } // SubmitOrder submits a new order @@ -868,6 +764,22 @@ func (c *CoinbasePro) CancelBatchOrders(ctx context.Context, o []order.Cancel) ( return &status, nil } +func (c *CoinbasePro) iterativeGetAllOrders(ctx context.Context, productID, userNativeCurrency, orderType, orderSide, cursor, productType, orderPlacementSource, contractExpiryType string, orderStatus []string, limit int32, startDate, endDate time.Time) ([]GetOrderResponse, error) { + var hasNext bool + var resp []GetOrderResponse + for hasNext { + interResp, err := c.GetAllOrders(ctx, productID, userNativeCurrency, orderType, orderSide, cursor, + productType, orderPlacementSource, contractExpiryType, orderStatus, limit, startDate, endDate) + if err != nil { + return nil, err + } + resp = append(resp, interResp.Orders...) + hasNext = interResp.HasNext + cursor = interResp.Cursor + } + return resp, nil +} + // CancelAllOrders cancels all orders associated with a currency pair func (c *CoinbasePro) CancelAllOrders(ctx context.Context, can *order.Cancel) (order.CancelAllResponse, error) { var resp order.CancelAllResponse @@ -878,20 +790,15 @@ func (c *CoinbasePro) CancelAllOrders(ctx context.Context, can *order.Cancel) (o if err != nil { return resp, err } - var ordIDs []GetOrderResponse var cursor string ordStatus := []string{"OPEN"} - hasNext := true - for hasNext { - interResp, err := c.GetAllOrders(ctx, can.Pair.String(), "", "", "", cursor, "", "", "", ordStatus, 1000, - time.Time{}, time.Time{}) - if err != nil { - return resp, err - } - ordIDs = append(ordIDs, interResp.Orders...) - hasNext = interResp.HasNext - cursor = interResp.Cursor + + ordIDs, err := c.iterativeGetAllOrders(ctx, can.Pair.String(), "", "", "", cursor, "", "", "", ordStatus, 1000, + time.Time{}, time.Time{}) + if err != nil { + return resp, err } + if len(ordStatus) == 0 { return resp, errNoMatchingOrders } @@ -911,16 +818,11 @@ func (c *CoinbasePro) CancelAllOrders(ctx context.Context, can *order.Cancel) (o return resp, nil } -// GetOrderInfo returns order information based on order ID -func (c *CoinbasePro) GetOrderInfo(ctx context.Context, orderID string, pair currency.Pair, assetItem asset.Item) (*order.Detail, error) { - genOrderDetail, err := c.GetOrderByID(ctx, orderID, "", "") - if err != nil { - return nil, err - } - +func (c *CoinbasePro) getOrderRespToOrderDetail(genOrderDetail *GetOrderResponse, pair currency.Pair, assetItem asset.Item) (*order.Detail, error) { var amount float64 var quoteAmount float64 var orderType order.Type + var err error if genOrderDetail.OrderConfiguration.MarketMarketIOC != nil { err = stringToFloatPtr("eAmount, genOrderDetail.OrderConfiguration.MarketMarketIOC.QuoteSize) if err != nil { @@ -1049,8 +951,20 @@ func (c *CoinbasePro) GetOrderInfo(ctx context.Context, orderID string, pair cur LastUpdated: lastUpdateTime, Pair: pair, } + return &response, nil +} - fmt.Printf("%+v\n", response) +// GetOrderInfo returns order information based on order ID +func (c *CoinbasePro) GetOrderInfo(ctx context.Context, orderID string, pair currency.Pair, assetItem asset.Item) (*order.Detail, error) { + genOrderDetail, err := c.GetOrderByID(ctx, orderID, "", "") + 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, "", "", 2<<15-1, time.Time{}, time.Now()) if err != nil { @@ -1066,7 +980,8 @@ func (c *CoinbasePro) GetOrderInfo(ctx context.Context, orderID string, pair cur cursor = tempFillData.Cursor } response.Trades = make([]order.TradeHistory, len(fillData.Fills)) - switch orderSide { + var orderSide order.Side + switch response.Side { case order.Buy: orderSide = order.Sell case order.Sell: @@ -1085,7 +1000,7 @@ func (c *CoinbasePro) GetOrderInfo(ctx context.Context, orderID string, pair cur } } - return &response, nil + return response, nil } // GetDepositAddress returns a deposit address for a specified currency @@ -1127,28 +1042,17 @@ func (c *CoinbasePro) WithdrawCryptocurrencyFunds(ctx context.Context, withdrawR if err := withdrawRequest.Validate(); err != nil { return nil, err } - gawResp, err := c.GetAllWallets(ctx, PaginationInp{}) - if err != nil { - return nil, err - } - if len(gawResp.Data) == 0 { - return nil, errNoMatchingWallets + if withdrawRequest.WalletID == "" { + return nil, errWalletIDEmpty } - var wID string - for i := range gawResp.Data { - if gawResp.Data[i].Currency.Code == withdrawRequest.Currency.String() { - wID = gawResp.Data[i].ID - break - } - } - - message := fmt.Sprintf("%+v", withdrawRequest) 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)) @@ -1167,9 +1071,9 @@ func (c *CoinbasePro) WithdrawCryptocurrencyFunds(ctx context.Context, withdrawR message = message[:tocut] - resp, err := c.SendMoney(ctx, "send", wID, withdrawRequest.Crypto.Address, withdrawRequest.Currency.String(), - withdrawRequest.Description, message, "", withdrawRequest.Crypto.AddressTag, withdrawRequest.Amount, - false, false) + 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 @@ -1184,122 +1088,95 @@ func (c *CoinbasePro) WithdrawFiatFunds(ctx context.Context, withdrawRequest *wi if err := withdrawRequest.Validate(); err != nil { return nil, err } - // paymentMethods, err := c.GetPayMethods(ctx) - // if err != nil { - // return nil, err - // } + if withdrawRequest.WalletID == "" { + return nil, errWalletIDEmpty + } - selectedWithdrawalMethod := PaymentMethod{} - // for i := range paymentMethods { - // if withdrawRequest.Fiat.Bank.BankName == paymentMethods[i].Name { - // selectedWithdrawalMethod = paymentMethods[i] - // break - // } - // } - if selectedWithdrawalMethod.ID == "" { - return nil, fmt.Errorf("could not find payment method '%v'. Check the name via the website and try again", withdrawRequest.Fiat.Bank.BankName) + paymentMethods, err := c.GetAllPaymentMethods(ctx, PaginationInp{}) + if err != nil { + return nil, err } - // resp, err := c.WithdrawViaPaymentMethod(ctx, - // withdrawRequest.Amount, - // withdrawRequest.Currency.String(), - // selectedWithdrawalMethod.ID) - // if err != nil { - // return nil, err - // } + selectedWithdrawalMethod := PaymentMethodData{} + for i := range paymentMethods.Data { + if withdrawRequest.Fiat.Bank.BankName == paymentMethods.Data[i].Name { + selectedWithdrawalMethod = paymentMethods.Data[i] + break + } + } + if selectedWithdrawalMethod.ID == "" { + return nil, fmt.Errorf(errPayMethodNotFound, withdrawRequest.Fiat.Bank.BankName) + } - // return &withdraw.ExchangeResponse{ - // Status: resp.ID, - // }, nil - return nil, common.ErrFunctionNotSupported -} + resp, err := c.FiatTransfer(ctx, withdrawRequest.WalletID, withdrawRequest.Currency.String(), + selectedWithdrawalMethod.ID, withdrawRequest.Amount, true, FiatWithdrawal) -// WithdrawFiatFundsToInternationalBank returns a withdrawal ID when a -// withdrawal is submitted -func (c *CoinbasePro) WithdrawFiatFundsToInternationalBank(ctx context.Context, withdrawRequest *withdraw.Request) (*withdraw.ExchangeResponse, error) { - if err := withdrawRequest.Validate(); err != nil { - return nil, err - } - v, err := c.WithdrawFiatFunds(ctx, withdrawRequest) if err != nil { return nil, err } + return &withdraw.ExchangeResponse{ - ID: v.ID, - Status: v.Status, + Name: selectedWithdrawalMethod.Name, + ID: resp.Data.ID, + Status: resp.Data.Status, }, nil } +// WithdrawFiatFundsToInternationalBank returns a withdrawal ID when a +// withdrawal is submitted +func (c *CoinbasePro) WithdrawFiatFundsToInternationalBank(ctx context.Context, withdrawRequest *withdraw.Request) (*withdraw.ExchangeResponse, error) { + return c.WithdrawFiatFunds(ctx, withdrawRequest) +} + // GetFeeByType returns an estimate of fee based on type of transaction func (c *CoinbasePro) GetFeeByType(ctx context.Context, feeBuilder *exchange.FeeBuilder) (float64, error) { - // if feeBuilder == nil { - // return 0, fmt.Errorf("%T %w", feeBuilder, common.ErrNilPointer) - // } - // if !c.AreCredentialsValid(ctx) && // Todo check connection status - // feeBuilder.FeeType == exchange.CryptocurrencyTradeFee { - // feeBuilder.FeeType = exchange.OfflineTradeFee - // } - // return c.GetFee(ctx, feeBuilder) - return 99999, errors.New(common.ErrFunctionNotSupported.Error()) + if feeBuilder == nil { + return 0, fmt.Errorf("%T %w", feeBuilder, common.ErrNilPointer) + } + if !c.AreCredentialsValid(ctx) && // Todo check connection status + feeBuilder.FeeType == exchange.CryptocurrencyTradeFee { + feeBuilder.FeeType = exchange.OfflineTradeFee + } + return c.GetFee(ctx, feeBuilder) } // GetActiveOrders retrieves any orders that are active/open func (c *CoinbasePro) GetActiveOrders(ctx context.Context, req *order.MultiOrderRequest) (order.FilteredOrders, error) { + if req == nil { + return nil, common.ErrNilPointer + } err := req.Validate() if err != nil { return nil, err } var respOrders []GetOrderResponse - // var fPair currency.Pair - // for i := range req.Pairs { - // // fPair, err = c.FormatExchangeCurrency(req.Pairs[i], asset.Spot) - // if err != nil { - // return nil, err - // } - - // var resp []GetOrderResponse - // // resp, err = c.GetOrders(ctx, - // // []string{"open", "pending", "active"}, - // // fPair.String()) - // if err != nil { - // return nil, err - // } - // respOrders = append(respOrders, resp...) - // } - - format, err := c.GetPairFormat(asset.Spot, false) - if err != nil { - return nil, err + var cursor string + ordStatus := []string{"OPEN"} + pairIDs := req.Pairs.Strings() + if len(pairIDs) == 0 { + respOrders, err = c.iterativeGetAllOrders(ctx, "", "", req.Type.String(), req.Side.String(), cursor, + req.AssetType.Upper(), "", "", ordStatus, 1000, req.StartTime, req.EndTime) + if err != nil { + return nil, err + } + } else { + for i := range pairIDs { + interResp, err := c.iterativeGetAllOrders(ctx, pairIDs[i], "", req.Type.String(), req.Side.String(), + cursor, req.AssetType.Upper(), "", "", ordStatus, 1000, req.StartTime, req.EndTime) + if err != nil { + return nil, err + } + respOrders = append(respOrders, interResp...) + } } orders := make([]order.Detail, len(respOrders)) for i := range respOrders { - var curr currency.Pair - curr, err = currency.NewPairDelimiter(respOrders[i].ProductID, - format.Delimiter) - if err != nil { - return nil, err - } - var side order.Side - side, err = order.StringToOrderSide(respOrders[i].Side) + orderRec, err := c.getOrderRespToOrderDetail(&respOrders[i], req.Pairs[i], asset.Spot) if err != nil { return nil, err } - var orderType order.Type - // orderType, err = order.StringToOrderType(respOrders[i].Type) - if err != nil { - log.Errorf(log.ExchangeSys, "%s %v", c.Name, err) - } - orders[i] = order.Detail{ - // OrderID: respOrders[i].ID, - // Amount: respOrders[i].Size, - ExecutedAmount: respOrders[i].FilledSize, - Type: orderType, - Date: respOrders[i].CreatedTime, - Side: side, - Pair: curr, - Exchange: c.Name, - } + orders[i] = *orderRec } return req.Filter(c.Name, orders), nil } @@ -1311,7 +1188,134 @@ func (c *CoinbasePro) GetOrderHistory(ctx context.Context, req *order.MultiOrder if err != nil { return nil, err } - var respOrders []GetOrderResponse + + var p []string + + if len(req.Pairs) == 0 { + p = make([]string, 1) + } else { + p = make([]string, len(req.Pairs)) + for i := range req.Pairs { + req.Pairs[i], err = c.FormatExchangeCurrency(req.Pairs[i], req.AssetType) + if err != nil { + return nil, err + } + 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) + if err != nil { + return nil, err + } + ord = append(ord, interOrd...) + interOrd, err = c.iterativeGetAllOrders(ctx, p[i], "", req.Type.String(), req.Side.String(), "", + req.AssetType.Upper(), "", "", openStatus, 2<<30-1, req.StartTime, req.EndTime) + if err != nil { + return nil, err + } + 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 { + return nil, err + } + orders[i] = *singleOrder + } + + // for i := range ord { + // var side order.Side + // side, err = order.StringToOrderSide(ord[i].Side) + // if err != nil { + // return nil, err + // } + // id, err := uuid.FromString(ord[i].OrderID) + // if err != nil { + // return nil, err + // } + // var price float64 + // var amount float64 + // if ord[i].OrderConfiguration.MarketMarketIOC != nil { + // err = stringToFloatPtr(&amount, ord[i].OrderConfiguration.MarketMarketIOC.QuoteSize) + // if err != nil { + // return nil, err + // } + // err = stringToFloatPtr(&amount, ord[i].OrderConfiguration.MarketMarketIOC.BaseSize) + // if err != nil { + // return nil, err + // } + // } + + // if ord[i].OrderConfiguration.LimitLimitGTC != nil { + // err = stringToFloatPtr(&price, ord[i].OrderConfiguration.LimitLimitGTC.LimitPrice) + // if err != nil { + // return nil, err + // } + // err = stringToFloatPtr(&amount, ord[i].OrderConfiguration.LimitLimitGTC.BaseSize) + // if err != nil { + // return nil, err + // } + + // } + // if ord[i].OrderConfiguration.LimitLimitGTD != nil { + // err = stringToFloatPtr(&price, ord[i].OrderConfiguration.LimitLimitGTD.LimitPrice) + // if err != nil { + // return nil, err + // } + // err = stringToFloatPtr(&amount, ord[i].OrderConfiguration.LimitLimitGTD.BaseSize) + // if err != nil { + // return nil, err + // } + + // } + // if ord[i].OrderConfiguration.StopLimitStopLimitGTC != nil { + // err = stringToFloatPtr(&price, ord[i].OrderConfiguration.StopLimitStopLimitGTC.LimitPrice) + // if err != nil { + // return nil, err + // } + // err = stringToFloatPtr(&amount, ord[i].OrderConfiguration.StopLimitStopLimitGTC.BaseSize) + // if err != nil { + // return nil, err + // } + + // } + // if ord[i].OrderConfiguration.StopLimitStopLimitGTD != nil { + // err = stringToFloatPtr(&price, ord[i].OrderConfiguration.StopLimitStopLimitGTD.LimitPrice) + // if err != nil { + // return nil, err + // } + // err = stringToFloatPtr(&amount, ord[i].OrderConfiguration.StopLimitStopLimitGTD.BaseSize) + // if err != nil { + // return nil, err + // } + + // } + + // orders[i] = order.Detail{ + // ID: id, + // Exchange: c.Name, + // CurrencyPair: p, + // AssetType: assetType, + // Side: side, + // Price: price, + // Amount: amount, + // Timestamp: ord[i].CreatedTime, + // } + // } + + return req.Filter(c.Name, orders), nil + + // var respOrders []GetOrderResponse // if len(req.Pairs) > 0 { // var fPair currency.Pair // var resp []GetOrderResponse @@ -1333,56 +1337,56 @@ func (c *CoinbasePro) GetOrderHistory(ctx context.Context, req *order.MultiOrder // } // } - format, err := c.GetPairFormat(asset.Spot, false) - if err != nil { - return nil, err - } + // format, err := c.GetPairFormat(asset.Spot, false) + // if err != nil { + // return nil, err + // } - orders := make([]order.Detail, len(respOrders)) - for i := range respOrders { - var curr currency.Pair - curr, err = currency.NewPairDelimiter(respOrders[i].ProductID, - format.Delimiter) - if err != nil { - return nil, err - } - var side order.Side - side, err = order.StringToOrderSide(respOrders[i].Side) - if err != nil { - return nil, err - } - var orderStatus order.Status - orderStatus, err = order.StringToOrderStatus(respOrders[i].Status) - if err != nil { - log.Errorf(log.ExchangeSys, "%s %v", c.Name, err) - } - var orderType order.Type - // orderType, err = order.StringToOrderType(respOrders[i].Type) - if err != nil { - log.Errorf(log.ExchangeSys, "%s %v", c.Name, err) - } - detail := order.Detail{ - OrderID: respOrders[i].OrderID, - // Amount: respOrders[i].Size, - ExecutedAmount: respOrders[i].FilledSize, - // RemainingAmount: respOrders[i].Size - respOrders[i].FilledSize, - // Cost: respOrders[i].ExecutedValue, - CostAsset: curr.Quote, - Type: orderType, - Date: respOrders[i].CreatedTime, - // CloseTime: respOrders[i].DoneAt, - // Fee: respOrders[i].FillFees, - FeeAsset: curr.Quote, - Side: side, - Status: orderStatus, - Pair: curr, - // Price: respOrders[i].Price, - Exchange: c.Name, - } - detail.InferCostsAndTimes() - orders[i] = detail - } - return req.Filter(c.Name, orders), nil + // orders := make([]order.Detail, len(respOrders)) + // for i := range respOrders { + // var curr currency.Pair + // curr, err = currency.NewPairDelimiter(respOrders[i].ProductID, + // format.Delimiter) + // if err != nil { + // return nil, err + // } + // var side order.Side + // side, err = order.StringToOrderSide(respOrders[i].Side) + // if err != nil { + // return nil, err + // } + // var orderStatus order.Status + // orderStatus, err = order.StringToOrderStatus(respOrders[i].Status) + // if err != nil { + // log.Errorf(log.ExchangeSys, "%s %v", c.Name, err) + // } + // var orderType order.Type + // // orderType, err = order.StringToOrderType(respOrders[i].Type) + // if err != nil { + // log.Errorf(log.ExchangeSys, "%s %v", c.Name, err) + // } + // detail := order.Detail{ + // OrderID: respOrders[i].OrderID, + // // Amount: respOrders[i].Size, + // ExecutedAmount: respOrders[i].FilledSize, + // // RemainingAmount: respOrders[i].Size - respOrders[i].FilledSize, + // // Cost: respOrders[i].ExecutedValue, + // CostAsset: curr.Quote, + // Type: orderType, + // Date: respOrders[i].CreatedTime, + // // CloseTime: respOrders[i].DoneAt, + // // Fee: respOrders[i].FillFees, + // FeeAsset: curr.Quote, + // Side: side, + // Status: orderStatus, + // Pair: curr, + // // Price: respOrders[i].Price, + // Exchange: c.Name, + // } + // detail.InferCostsAndTimes() + // orders[i] = detail + // } + // return req.Filter(c.Name, orders), nil } // GetHistoricCandles returns a set of candle between two time periods for a diff --git a/portfolio/withdraw/withdraw_types.go b/portfolio/withdraw/withdraw_types.go index 70551a7a484..1ff8cce099a 100644 --- a/portfolio/withdraw/withdraw_types.go +++ b/portfolio/withdraw/withdraw_types.go @@ -92,6 +92,9 @@ type Request struct { // Used exclusively in Binance.US ClientOrderID string `json:"clientID"` + // Currently used exclusively in Coinbase + WalletID string `json:"walletID"` + // Used exclusively in Okcoin to classify internal represented by '3' or on chain represented by '4' InternalTransfer bool From 2896b3e7c5ab6a56fa3930e8d0dd66144ac2c487 Mon Sep 17 00:00:00 2001 From: Samuel Reid <43227667+cranktakular@users.noreply.github.com> Date: Tue, 2 Jan 2024 17:08:35 +1100 Subject: [PATCH 15/79] Wrapper is wrapped, kinda --- exchanges/coinbasepro/coinbasepro.go | 25 +- exchanges/coinbasepro/coinbasepro_test.go | 327 ++++++++++--- exchanges/coinbasepro/coinbasepro_types.go | 2 +- exchanges/coinbasepro/coinbasepro_wrapper.go | 480 +++++++++---------- 4 files changed, 508 insertions(+), 326 deletions(-) diff --git a/exchanges/coinbasepro/coinbasepro.go b/exchanges/coinbasepro/coinbasepro.go index f8443bd1f05..0b4f72b5bba 100644 --- a/exchanges/coinbasepro/coinbasepro.go +++ b/exchanges/coinbasepro/coinbasepro.go @@ -83,7 +83,8 @@ const ( startDateString = "start_date" endDateString = "end_date" - errPayMethodNotFound = "payment method '%v' not found" + errPayMethodNotFound = "payment method '%v' not found" + errIntervalNotSupported = "interval not supported" ) var ( @@ -180,7 +181,7 @@ func (c *CoinbasePro) GetProductBook(ctx context.Context, productID string, limi } // GetAllProducts returns information on all currency pairs that are available for trading -func (c *CoinbasePro) GetAllProducts(ctx context.Context, limit, offset int32, productType, contractExpiryType string, productIDs []string) (AllProducts, error) { +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)) @@ -259,7 +260,7 @@ func (c *CoinbasePro) GetHistoricRates(ctx context.Context, productID, granulari // GetTicker returns snapshot information about the last trades (ticks) and best bid/ask. // Contrary to documentation, this does not tell you the 24h volume -func (c *CoinbasePro) GetTicker(ctx context.Context, productID string, limit uint16) (*Ticker, error) { +func (c *CoinbasePro) GetTicker(ctx context.Context, productID string, limit uint16, startDate, endDate time.Time) (*Ticker, error) { if productID == "" { return nil, errProductIDEmpty } @@ -269,6 +270,8 @@ func (c *CoinbasePro) GetTicker(ctx context.Context, productID string, limit uin 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) @@ -279,7 +282,7 @@ func (c *CoinbasePro) GetTicker(ctx context.Context, productID string, limit uin } // PlaceOrder places either a limit, market, or stop order -func (c *CoinbasePro) PlaceOrder(ctx context.Context, clientOID, productID, side, stopDirection, orderType string, amount, limitPrice, stopPrice float64, postOnly bool, endTime time.Time) (*PlaceOrderResp, error) { +func (c *CoinbasePro) PlaceOrder(ctx context.Context, clientOID, productID, side, stopDirection, orderType, stpID, marginType, rpID string, amount, limitPrice, stopPrice, leverage float64, postOnly bool, endTime time.Time) (*PlaceOrderResp, error) { if clientOID == "" { return nil, errClientOrderIDEmpty } @@ -338,7 +341,12 @@ func (c *CoinbasePro) PlaceOrder(ctx context.Context, clientOID, productID, side } req := map[string]interface{}{"client_order_id": clientOID, "product_id": productID, - "side": side, "order_configuration": orderConfig} + "side": side, "order_configuration": orderConfig, "self_trade_prevention_id": stpID, + "leverage": strconv.FormatFloat(leverage, 'f', -1, 64), "retail_portfolio_id": rpID} + + if marginType != "" { + req["margin_type"] = marginType + } return &resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, @@ -403,7 +411,7 @@ func (c *CoinbasePro) EditOrderPreview(ctx context.Context, orderID string, size } // GetAllOrders lists orders, filtered by their status -func (c *CoinbasePro) GetAllOrders(ctx context.Context, productID, userNativeCurrency, orderType, orderSide, cursor, productType, orderPlacementSource, contractExpiryType string, orderStatus []string, limit int32, startDate, endDate time.Time) (GetAllOrdersResp, error) { +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 var params Params @@ -2086,7 +2094,6 @@ func (c *CoinbasePro) GetFee(ctx context.Context, feeBuilder *exchange.FeeBuilde switch { case !c.IsStablePair(feeBuilder.Pair) && feeBuilder.FeeType == exchange.CryptocurrencyTradeFee: fees, err := c.GetTransactionSummary(ctx, time.Now().Add(-time.Hour*24*30), time.Now(), "", "", "") - fmt.Printf("Fees struct: %v\n", fees) if err != nil { return 0, err } @@ -2270,6 +2277,10 @@ func (t UnixTimestamp) String() string { return time.Time(t).String() } +func (t UnixTimestamp) Time() time.Time { + return time.Time(t) +} + func (t *ExchTime) UnmarshalJSON(b []byte) error { s := strings.Trim(string(b), `"`) if s == " " || s == "null" { diff --git a/exchanges/coinbasepro/coinbasepro_test.go b/exchanges/coinbasepro/coinbasepro_test.go index 65f4acc5a68..bd2900b9852 100644 --- a/exchanges/coinbasepro/coinbasepro_test.go +++ b/exchanges/coinbasepro/coinbasepro_test.go @@ -20,6 +20,9 @@ import ( "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" + "github.com/thrasher-corp/gocryptotrader/exchanges/fundingrate" + "github.com/thrasher-corp/gocryptotrader/exchanges/futures" + "github.com/thrasher-corp/gocryptotrader/exchanges/kline" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues" gctlog "github.com/thrasher-corp/gocryptotrader/log" @@ -29,6 +32,7 @@ import ( var ( c = &CoinbasePro{} testPair = currency.NewPairWithDelimiter(currency.BTC.String(), currency.USD.String(), "-") + testCur = currency.BTC ) // Please supply your APIKeys here for better testing @@ -57,10 +61,49 @@ const ( errPortTransferInsufFunds = `CoinbasePro unsuccessful HTTP status code: 429 raw response: {"error":"unknown","error_details":"[PORTFOLIO_ERROR_CODE_INSUFFICIENT_FUNDS] insufficient funds in source account","message":"[PORTFOLIO_ERROR_CODE_INSUFFICIENT_FUNDS] insufficient funds in source account"}, authenticated request failed` errFeeBuilderNil = "*exchange.FeeBuilder nil pointer" errUnsupportedAssetType = " unsupported asset type" + errUpsideUnsupported = "unsupported asset type upsideprofitcontract" testAmount = 0.00000001 ) +func TestGetDefaultConfig(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + _, err := c.GetDefaultConfig(context.Background()) + if err != nil { + t.Error(err) + } +} + +func TestWrapperStart(t *testing.T) { + wg := sync.WaitGroup{} + err := c.Start(context.Background(), &wg) + if err != nil { + t.Error(err) + } +} + +func TestSetup(t *testing.T) { + err := c.Setup(nil) + if !errors.Is(err, config.ErrExchangeConfigIsNil) { + t.Errorf(errExpectMismatch, err, config.ErrExchangeConfigIsNil) + } + cfg, err := c.GetStandardConfig() + if err != nil { + t.Error(err) + } + cfg.API.AuthenticatedSupport = true + cfg.API.Credentials.Key = apiKey + cfg.API.Credentials.Secret = apiSecret + cfg.Enabled = false + _ = c.Setup(cfg) + cfg.Enabled = true + cfg.ProxyAddress = string(rune(0x7f)) + err = c.Setup(cfg) + if err.Error() != errx7f { + t.Errorf(errExpectMismatch, err, errx7f) + } +} + func TestMain(m *testing.M) { c.SetDefaults() if testingInSandbox { @@ -180,7 +223,8 @@ func TestGetAllProducts(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) // testPairs := []string{testPair.String(), "ETH-USD"} var testPairs []string - resp, err := c.GetAllProducts(context.Background(), 30000, 0, "", "", testPairs) + resp, err := c.GetAllProducts(context.Background(), 30000, 0, "FUTURE", "", "", + testPairs) if err != nil { t.Error(err) } @@ -221,11 +265,11 @@ func TestGetHistoricRates(t *testing.T) { func TestGetTicker(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - _, err := c.GetTicker(context.Background(), "", 1) + _, err := c.GetTicker(context.Background(), "", 1, time.Time{}, time.Time{}) if !errors.Is(err, errProductIDEmpty) { t.Errorf(errExpectMismatch, err, errProductIDEmpty) } - resp, err := c.GetTicker(context.Background(), testPair.String(), 5) + resp, err := c.GetTicker(context.Background(), testPair.String(), 5, time.Time{}, time.Time{}) if err != nil { t.Error(err) } @@ -233,37 +277,69 @@ func TestGetTicker(t *testing.T) { } func TestPlaceOrder(t *testing.T) { - sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) - _, err := c.PlaceOrder(context.Background(), "", "", "", "", "", 0, 0, 0, false, time.Time{}) + _, err := c.PlaceOrder(context.Background(), "", "", "", "", "", "", "", "", 0, 0, 0, 0, false, time.Time{}) if !errors.Is(err, errClientOrderIDEmpty) { t.Errorf(errExpectMismatch, err, errClientOrderIDEmpty) } - _, err = c.PlaceOrder(context.Background(), "meow", "", "", "", "", 0, 0, 0, false, time.Time{}) + _, err = c.PlaceOrder(context.Background(), "meow", "", "", "", "", "", "", "", 0, 0, 0, 0, false, time.Time{}) if !errors.Is(err, errProductIDEmpty) { t.Errorf(errExpectMismatch, err, errProductIDEmpty) } - _, err = c.PlaceOrder(context.Background(), "meow", testPair.String(), order.Sell.String(), "", "", 0, - 0, 0, false, time.Time{}) + _, err = c.PlaceOrder(context.Background(), "meow", testPair.String(), order.Sell.String(), "", "", "", "", "", 0, + 0, 0, 0, false, time.Time{}) if !errors.Is(err, errAmountEmpty) { t.Errorf(errExpectMismatch, err, errAmountEmpty) } + sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) + accounts, err := c.GetAllAccounts(context.Background(), 250, "") + if err != nil { + t.Error(err) + } + if len(accounts.Accounts) == 0 { + t.Fatal(errExpectedNonEmpty) + } + var hasValidFunds bool + for i := range accounts.Accounts { + if accounts.Accounts[i].Currency == testCur.String() && accounts.Accounts[i].AvailableBalance.Value > 0.00001 { + hasValidFunds = true + } + } + if !hasValidFunds { + t.Skip(skipInsufficientFunds) + } id, _ := uuid.NewV4() _, err = c.PlaceOrder(context.Background(), id.String(), testPair.String(), order.Sell.String(), "", - order.Limit.String(), 0.0000001, 1000000000000, 0, false, time.Now().Add(time.Hour)) + order.Limit.String(), "", "CROSS", "", 0.0000001, 1000000000000, 0, 9999, false, time.Now().Add(time.Hour)) if err != nil { t.Error(err) } } func TestCancelOrders(t *testing.T) { - sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) var OrderSlice []string _, err := c.CancelOrders(context.Background(), OrderSlice) if !errors.Is(err, errOrderIDEmpty) { t.Errorf(errExpectMismatch, err, errOrderIDEmpty) } + sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) + accounts, err := c.GetAllAccounts(context.Background(), 250, "") + if err != nil { + t.Error(err) + } + if len(accounts.Accounts) == 0 { + t.Fatal(errExpectedNonEmpty) + } + var hasValidFunds bool + for i := range accounts.Accounts { + if accounts.Accounts[i].Currency == testCur.String() && accounts.Accounts[i].AvailableBalance.Value > 0.00001 { + hasValidFunds = true + } + } + if !hasValidFunds { + t.Skip(skipInsufficientFunds) + } ordID, err := c.PlaceOrder(context.Background(), "meow", testPair.String(), order.Sell.String(), "", - order.Limit.Lower(), 0.0000001, 1000000000000, 0, false, time.Time{}) + order.Limit.String(), "", "", "", 0.0000001, 1000000000000, 0, 9999, false, time.Time{}) if err != nil { t.Error(err) } @@ -275,7 +351,6 @@ func TestCancelOrders(t *testing.T) { } func TestEditOrder(t *testing.T) { - sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) _, err := c.EditOrder(context.Background(), "", 0, 0) if !errors.Is(err, errOrderIDEmpty) { t.Errorf(errExpectMismatch, err, errOrderIDEmpty) @@ -284,20 +359,36 @@ func TestEditOrder(t *testing.T) { if !errors.Is(err, errSizeAndPriceZero) { t.Errorf(errExpectMismatch, err, errSizeAndPriceZero) } + sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) + accounts, err := c.GetAllAccounts(context.Background(), 250, "") + if err != nil { + t.Error(err) + } + if len(accounts.Accounts) == 0 { + t.Fatal(errExpectedNonEmpty) + } + var hasValidFunds bool + for i := range accounts.Accounts { + if accounts.Accounts[i].Currency == testCur.String() && accounts.Accounts[i].AvailableBalance.Value > 0.00001 { + hasValidFunds = true + } + } + if !hasValidFunds { + t.Skip(skipInsufficientFunds) + } id, _ := uuid.NewV4() ordID, err := c.PlaceOrder(context.Background(), id.String(), testPair.String(), order.Sell.String(), "", - order.Limit.Lower(), 0.0000001, 1000000000000, 0, false, time.Time{}) + order.Limit.String(), "", "", "", 0.0000001, 1000000000000, 0, 9999, false, time.Time{}) if err != nil { t.Error(err) } - _, err = c.EditOrder(context.Background(), ordID.OrderID, 0, 10000000000000) + _, err = c.EditOrder(context.Background(), ordID.OrderID, 0.0000001, 10000000000000) if err != nil { t.Error(err) } } func TestEditOrderPreview(t *testing.T) { - sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) _, err := c.EditOrderPreview(context.Background(), "", 0, 0) if !errors.Is(err, errOrderIDEmpty) { t.Errorf(errExpectMismatch, err, errOrderIDEmpty) @@ -306,8 +397,30 @@ func TestEditOrderPreview(t *testing.T) { if !errors.Is(err, errSizeAndPriceZero) { t.Errorf(errExpectMismatch, err, errSizeAndPriceZero) } + sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) + accounts, err := c.GetAllAccounts(context.Background(), 250, "") + if err != nil { + t.Error(err) + } + if len(accounts.Accounts) == 0 { + t.Fatal(errExpectedNonEmpty) + } + var hasValidFunds bool + for i := range accounts.Accounts { + if accounts.Accounts[i].Currency == testCur.String() && accounts.Accounts[i].AvailableBalance.Value > 0.00001 { + hasValidFunds = true + } + } + if !hasValidFunds { + t.Skip(skipInsufficientFunds) + } id, _ := uuid.NewV4() - _, err = c.EditOrderPreview(context.Background(), id.String(), 0.0000001, 10000000000000) + ordID, err := c.PlaceOrder(context.Background(), id.String(), testPair.String(), order.Sell.String(), "", + order.Limit.String(), "", "", "", 0.0000001, 1000000000000, 0, 9999, false, time.Time{}) + if err != nil { + t.Error(err) + } + _, err = c.EditOrderPreview(context.Background(), ordID.OrderID, 0.0000001, 10000000000000) if err != nil { t.Error(err) } @@ -315,22 +428,23 @@ func TestEditOrderPreview(t *testing.T) { func TestGetAllOrders(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + assets := []string{"USD"} status := make([]string, 2) - _, err := c.GetAllOrders(context.Background(), "", "", "", "", "", "", "", "", status, 0, time.Unix(2, 2), - time.Unix(1, 1)) + _, err := c.GetAllOrders(context.Background(), "", "", "", "", "", "", "", "", "", status, assets, 0, + time.Unix(2, 2), time.Unix(1, 1)) if !errors.Is(err, common.ErrStartAfterEnd) { t.Errorf(errExpectMismatch, err, common.ErrStartAfterEnd) } status[0] = "CANCELLED" status[1] = "OPEN" - _, err = c.GetAllOrders(context.Background(), "", "", "", "", "", "", "", "", status, 0, time.Time{}, + _, err = c.GetAllOrders(context.Background(), "", "", "", "", "", "", "", "", "", status, assets, 0, time.Time{}, time.Time{}) if !errors.Is(err, errOpenPairWithOtherTypes) { t.Errorf(errExpectMismatch, err, errOpenPairWithOtherTypes) } status = make([]string, 0) _, err = c.GetAllOrders(context.Background(), "", "USD", "LIMIT", "SELL", "", "SPOT", "RETAIL_ADVANCED", - "UNKNOWN_CONTRACT_EXPIRY_TYPE", status, 10, time.Time{}, time.Time{}) + "UNKNOWN_CONTRACT_EXPIRY_TYPE", "2", status, assets, 10, time.Time{}, time.Time{}) if err != nil { t.Error(err) } @@ -354,7 +468,7 @@ func TestGetOrderByID(t *testing.T) { if !errors.Is(err, errOrderIDEmpty) { t.Errorf(errExpectMismatch, err, errOrderIDEmpty) } - ordID, err := c.GetAllOrders(context.Background(), "", "", "", "", "", "", "", "", nil, 10, + ordID, err := c.GetAllOrders(context.Background(), "", "", "", "", "", "", "", "", "", nil, nil, 10, time.Time{}, time.Time{}) if err != nil { t.Error(err) @@ -644,7 +758,6 @@ func TestGetV3Time(t *testing.T) { t.Error(err) } assert.NotEmpty(t, resp, errExpectedNonEmpty) - log.Printf("%+v", resp) } func TestListNotifications(t *testing.T) { @@ -661,7 +774,9 @@ func TestGetUserByID(t *testing.T) { if err != nil { t.Error(err) } - assert.NotEmpty(t, resp, errExpectedNonEmpty) + if resp == nil { + t.Fatal(errExpectedNonEmpty) + } resp2, err := c.GetUserByID(context.Background(), resp.Data.ID) if err != nil { t.Error(err) @@ -1033,7 +1148,7 @@ func TestCommitTransfer(t *testing.T) { depID, err := c.FiatTransfer(context.Background(), wID.Data.ID, "AUD", pmID.Data[0].FiatAccount.ID, 1, false, FiatDeposit) if err != nil { - t.Error(err) + t.Fatal(err) } _, err = c.CommitTransfer(context.Background(), wID.Data.ID, depID.Data.ID, FiatDeposit) if err != nil { @@ -2976,41 +3091,6 @@ func TestPrepareDSL(t *testing.T) { } } -func TestGetDefaultConfig(t *testing.T) { - sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - _, err := c.GetDefaultConfig(context.Background()) - if err != nil { - t.Error(err) - } -} - -func TestSetup(t *testing.T) { - err := c.Setup(nil) - if !errors.Is(err, config.ErrExchangeConfigIsNil) { - t.Errorf(errExpectMismatch, err, config.ErrExchangeConfigIsNil) - } - cfg, err := c.GetStandardConfig() - if err != nil { - t.Error(err) - } - cfg.Enabled = false - _ = c.Setup(cfg) - cfg.Enabled = true - cfg.ProxyAddress = string(rune(0x7f)) - err = c.Setup(cfg) - if err.Error() != errx7f { - t.Errorf(errExpectMismatch, err, errx7f) - } -} - -func TestWrapperStart(t *testing.T) { - wg := sync.WaitGroup{} - err := c.Start(context.Background(), &wg) - if err != nil { - t.Error(err) - } -} - func TestFetchTradablePairs(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) _, err := c.FetchTradablePairs(context.Background(), asset.Empty) @@ -3051,14 +3131,9 @@ func TestFetchAccountInfo(t *testing.T) { } } -func TestUpdateTickersS(t *testing.T) { +func TestUpdateTickers(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - err := c.UpdateTickers(context.Background(), asset.Empty) - if !errors.Is(err, asset.ErrNotSupported) { - t.Errorf(errExpectMismatch, err, asset.ErrNotSupported) - } - sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - err = c.UpdateTickers(context.Background(), asset.Futures) + err := c.UpdateTickers(context.Background(), asset.Futures) if err != nil { t.Error(err) } @@ -3211,7 +3286,8 @@ func TestModifyOrder(t *testing.T) { } sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) resp, err := c.PlaceOrder(context.Background(), strconv.FormatInt(time.Now().UnixMilli(), 18)+"GCTModifyOrderTest", - testPair.String(), order.Sell.String(), "", order.Limit.String(), 0.0000001, 1000000000000, 0, false, time.Time{}) + testPair.String(), order.Sell.String(), "", order.Limit.String(), "", "", "", 0.0000001, 1000000000000, 0, 9999, + false, time.Time{}) if err != nil { t.Fatal(err) } @@ -3241,7 +3317,8 @@ func TestCancelOrder(t *testing.T) { t.Errorf(errExpectMismatch, err, errOrder0CancelFail) } resp, err := c.PlaceOrder(context.Background(), strconv.FormatInt(time.Now().UnixMilli(), 18)+"GCTCancelOrderTest", - testPair.String(), order.Sell.String(), "", order.Limit.String(), 0.0000001, 1000000000000, 0, false, time.Time{}) + testPair.String(), order.Sell.String(), "", order.Limit.String(), "", "", "", 0.0000001, 1000000000000, 0, 9999, + false, time.Time{}) if err != nil { t.Fatal(err) } @@ -3265,7 +3342,7 @@ func TestCancelBatchOrders(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) resp, err := c.PlaceOrder(context.Background(), strconv.FormatInt(time.Now().UnixMilli(), 18)+"GCTCancelBatchOrdersTest", testPair.String(), - order.Sell.String(), "", order.Limit.String(), 0.0000001, 1000000000000, 0, false, time.Time{}) + order.Sell.String(), "", order.Limit.String(), "", "", "", 0.0000001, 1000000000000, 0, 9999, false, time.Time{}) if err != nil { t.Fatal(err) } @@ -3289,7 +3366,7 @@ func TestCancelAllOrders(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) _, err = c.PlaceOrder(context.Background(), strconv.FormatInt(time.Now().UnixMilli(), 18)+"GCTCancelAllOrdersTest", testPair.String(), - order.Sell.String(), "", order.Limit.String(), 0.0000001, 1000000000000, 0, false, time.Time{}) + order.Sell.String(), "", order.Limit.String(), "", "", "", 0.0000001, 1000000000000, 0, 9999, false, time.Time{}) if err != nil { t.Fatal(err) } @@ -3304,7 +3381,7 @@ func TestCancelAllOrders(t *testing.T) { func TestGetOrderInfo(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) ordID, err := c.GetAllOrders(context.Background(), testPair.String(), "", "", "", "", - asset.Spot.Upper(), "", "", nil, 2, time.Time{}, time.Now()) + asset.Spot.Upper(), "", "", "", nil, nil, 2, time.Time{}, time.Now()) if err != nil { t.Fatal(err) } @@ -3467,7 +3544,115 @@ func TestGetActiveOrders(t *testing.T) { } func TestGetOrderHistory(t *testing.T) { + _, err := c.GetOrderHistory(context.Background(), nil) + if !errors.Is(err, order.ErrGetOrdersRequestIsNil) { + t.Errorf(errExpectMismatch, err, order.ErrGetOrdersRequestIsNil) + } + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + var req order.MultiOrderRequest + req.AssetType = asset.Spot + req.Side = order.AnySide + req.Type = order.AnyType + _, err = c.GetOrderHistory(context.Background(), &req) + if err != nil { + t.Error(err) + } + req.Pairs = req.Pairs.Add(testPair) + _, err = c.GetOrderHistory(context.Background(), &req) + if err != nil { + t.Error(err) + } + +} + +func TestGetHistoricCandles(t *testing.T) { + _, err := c.GetHistoricCandles(context.Background(), currency.Pair{}, asset.Empty, kline.OneYear, time.Time{}, + time.Time{}) + if !errors.Is(err, currency.ErrCurrencyPairEmpty) { + t.Errorf(errExpectMismatch, err, currency.ErrCurrencyPairEmpty) + } + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + _, err = c.GetHistoricCandles(context.Background(), testPair, asset.Spot, kline.ThreeHour, + time.Now().Add(-time.Hour*30), time.Now()) + if err != nil { + t.Error(err) + } +} +func TestGetHistoricCandlesExtended(t *testing.T) { + _, err := c.GetHistoricCandlesExtended(context.Background(), currency.Pair{}, asset.Empty, kline.OneYear, + time.Time{}, time.Time{}) + if !errors.Is(err, currency.ErrCurrencyPairEmpty) { + t.Errorf(errExpectMismatch, err, currency.ErrCurrencyPairEmpty) + } + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + resp, err := c.GetHistoricCandlesExtended(context.Background(), testPair, asset.Spot, kline.OneMin, + time.Now().Add(-time.Hour*9), time.Now()) + if err != nil { + t.Error(err) + } + assert.NotEmpty(t, resp, errExpectedNonEmpty) +} + +func TestValidateAPICredentials(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + err := c.ValidateAPICredentials(context.Background(), asset.Spot) + if err != nil { + t.Error(err) + } +} + +func TestGetServerTime(t *testing.T) { + _, err := c.GetServerTime(context.Background(), 0) + if err != nil { + t.Error(err) + } +} + +func TestGetLatestFundingRates(t *testing.T) { + _, err := c.GetLatestFundingRates(context.Background(), nil) + if !errors.Is(err, common.ErrNilPointer) { + t.Errorf(errExpectMismatch, err, common.ErrNilPointer) + } + req := fundingrate.LatestRateRequest{Asset: asset.UpsideProfitContract} + _, err = c.GetLatestFundingRates(context.Background(), &req) + if err.Error() != errUpsideUnsupported { + t.Errorf(errExpectMismatch, err, errUpsideUnsupported) + } + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + req.Asset = asset.Futures + _, err = c.GetLatestFundingRates(context.Background(), &req) + if err != nil { + t.Error(err) + } +} + +func TestGetFuturesContractDetails(t *testing.T) { + _, err := c.GetFuturesContractDetails(context.Background(), asset.Empty) + if !errors.Is(err, futures.ErrNotFuturesAsset) { + t.Errorf(errExpectMismatch, err, futures.ErrNotFuturesAsset) + } + _, err = c.GetFuturesContractDetails(context.Background(), asset.UpsideProfitContract) + if err.Error() != errUpsideUnsupported { + t.Errorf(errExpectMismatch, err, errUpsideUnsupported) + } + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + _, err = c.GetFuturesContractDetails(context.Background(), asset.Futures) + if err != nil { + t.Error(err) + } +} + +func TestUpdateOrderExecutionLimits(t *testing.T) { + err := c.UpdateOrderExecutionLimits(context.Background(), asset.UpsideProfitContract) + if err.Error() != errUpsideUnsupported { + t.Errorf(errExpectMismatch, err, errUpsideUnsupported) + } + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + err = c.UpdateOrderExecutionLimits(context.Background(), asset.Futures) + if err != nil { + t.Error(err) + } } // 8837708 143.0 ns/op 24 B/op 5 allocs/op diff --git a/exchanges/coinbasepro/coinbasepro_types.go b/exchanges/coinbasepro/coinbasepro_types.go index 84f187cd20d..db3b8bcf165 100644 --- a/exchanges/coinbasepro/coinbasepro_types.go +++ b/exchanges/coinbasepro/coinbasepro_types.go @@ -127,7 +127,7 @@ type Product struct { FutureProductDetails struct { Venue string `json:"venue"` ContractCode string `json:"contract_code"` - ContractExpiry string `json:"contract_expiry"` + ContractExpiry time.Time `json:"contract_expiry"` ContractSize convert.StringToFloat64 `json:"contract_size"` ContractRootUnit string `json:"contract_root_unit"` GroupDescription string `json:"group_description"` diff --git a/exchanges/coinbasepro/coinbasepro_wrapper.go b/exchanges/coinbasepro/coinbasepro_wrapper.go index 79ca74a2347..2e7de766768 100644 --- a/exchanges/coinbasepro/coinbasepro_wrapper.go +++ b/exchanges/coinbasepro/coinbasepro_wrapper.go @@ -9,6 +9,7 @@ import ( "sync" "time" + "github.com/shopspring/decimal" "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/common/crypto" "github.com/thrasher-corp/gocryptotrader/config" @@ -124,7 +125,9 @@ func (c *CoinbasePro) SetDefaults() { kline.IntervalCapacity{Interval: kline.OneMin}, kline.IntervalCapacity{Interval: kline.FiveMin}, kline.IntervalCapacity{Interval: kline.FifteenMin}, + kline.IntervalCapacity{Interval: kline.ThirtyMin}, kline.IntervalCapacity{Interval: kline.OneHour}, + kline.IntervalCapacity{Interval: kline.TwoHour}, kline.IntervalCapacity{Interval: kline.SixHour}, kline.IntervalCapacity{Interval: kline.OneDay}, ), @@ -232,15 +235,30 @@ func (c *CoinbasePro) Run(ctx context.Context) { } } +// fetchFutures is a helper function that calls the List Products endpoint twice, to get both +// expiring futures and perpetual futures +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 + } + products2, err := c.GetAllProducts(ctx, 2<<30-1, 0, "FUTURE", "PERPETUAL", "", nil) + if err != nil { + return AllProducts{}, err + } + products.Products = append(products.Products, products2.Products...) + return products, nil +} + // 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 err error switch a { case asset.Spot: - products, err = c.GetAllProducts(ctx, 2<<30-1, 0, asset.Spot.Upper(), "", nil) + products, err = c.GetAllProducts(ctx, 2<<30-1, 0, asset.Spot.Upper(), "", "", nil) case asset.Futures: - products, err = c.GetAllProducts(ctx, 2<<30-1, 0, asset.Futures.Upper(), "", nil) + products, err = c.fetchFutures(ctx) default: err = asset.ErrNotSupported } @@ -350,16 +368,14 @@ 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, err := c.GetAllProducts(ctx, 2<<30-1, 0, assetType.Upper(), "", nil) - if err != nil { - return err - } - for x := range products.Products { - tick, err := c.GetTicker(ctx, products.Products[x].ID, 1) + products, _ := c.GetEnabledPairs(assetType) + + for x := range products { + tick, err := c.GetTicker(ctx, products[x].String(), 1, time.Time{}, time.Time{}) if err != nil { return err } - pair, err := currency.NewPairDelimiter(products.Products[x].ID, currency.DashDelimiter) + pair, err := currency.NewPairDelimiter(products[x].String(), currency.DashDelimiter) if err != nil { return err } @@ -389,7 +405,7 @@ func (c *CoinbasePro) UpdateTicker(ctx context.Context, p currency.Pair, a asset return nil, err } - tick, err := c.GetTicker(ctx, fPair.String(), 1) + tick, err := c.GetTicker(ctx, fPair.String(), 1, time.Time{}, time.Time{}) if err != nil { return nil, err } @@ -689,8 +705,8 @@ func (c *CoinbasePro) SubmitOrder(ctx context.Context, s *order.Submit) (*order. amount = s.QuoteAmount } - resp, err := c.PlaceOrder(ctx, s.ClientOrderID, fPair.String(), s.Side.String(), stopDir, s.Type.String(), - amount, s.Price, s.TriggerPrice, s.PostOnly, s.EndTime) + 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 @@ -764,12 +780,13 @@ func (c *CoinbasePro) CancelBatchOrders(ctx context.Context, o []order.Cancel) ( return &status, nil } -func (c *CoinbasePro) iterativeGetAllOrders(ctx context.Context, productID, userNativeCurrency, orderType, orderSide, cursor, productType, orderPlacementSource, contractExpiryType string, orderStatus []string, limit int32, startDate, endDate time.Time) ([]GetOrderResponse, error) { +func (c *CoinbasePro) iterativeGetAllOrders(ctx context.Context, productID, orderType, orderSide, productType string, orderStatus []string, limit int32, startDate, endDate time.Time) ([]GetOrderResponse, error) { var hasNext bool var resp []GetOrderResponse + var cursor string for hasNext { - interResp, err := c.GetAllOrders(ctx, productID, userNativeCurrency, orderType, orderSide, cursor, - productType, orderPlacementSource, contractExpiryType, orderStatus, limit, startDate, endDate) + interResp, err := c.GetAllOrders(ctx, productID, "", orderType, orderSide, cursor, productType, "", "", "", + orderStatus, nil, limit, startDate, endDate) if err != nil { return nil, err } @@ -790,10 +807,9 @@ func (c *CoinbasePro) CancelAllOrders(ctx context.Context, can *order.Cancel) (o if err != nil { return resp, err } - var cursor string ordStatus := []string{"OPEN"} - ordIDs, err := c.iterativeGetAllOrders(ctx, can.Pair.String(), "", "", "", cursor, "", "", "", ordStatus, 1000, + ordIDs, err := c.iterativeGetAllOrders(ctx, can.Pair.String(), "", "", "", ordStatus, 1000, time.Time{}, time.Time{}) if err != nil { return resp, err @@ -1150,19 +1166,18 @@ func (c *CoinbasePro) GetActiveOrders(ctx context.Context, req *order.MultiOrder return nil, err } var respOrders []GetOrderResponse - var cursor string ordStatus := []string{"OPEN"} pairIDs := req.Pairs.Strings() if len(pairIDs) == 0 { - respOrders, err = c.iterativeGetAllOrders(ctx, "", "", req.Type.String(), req.Side.String(), cursor, - req.AssetType.Upper(), "", "", ordStatus, 1000, req.StartTime, req.EndTime) + respOrders, err = c.iterativeGetAllOrders(ctx, "", req.Type.String(), req.Side.String(), + req.AssetType.Upper(), ordStatus, 1000, req.StartTime, req.EndTime) if err != nil { return nil, err } } else { for i := range pairIDs { - interResp, err := c.iterativeGetAllOrders(ctx, pairIDs[i], "", req.Type.String(), req.Side.String(), - cursor, req.AssetType.Upper(), "", "", ordStatus, 1000, req.StartTime, req.EndTime) + interResp, err := c.iterativeGetAllOrders(ctx, pairIDs[i], req.Type.String(), req.Side.String(), + req.AssetType.Upper(), ordStatus, 1000, req.StartTime, req.EndTime) if err != nil { return nil, err } @@ -1209,14 +1224,14 @@ func (c *CoinbasePro) GetOrderHistory(ctx context.Context, req *order.MultiOrder 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) + interOrd, err := c.iterativeGetAllOrders(ctx, p[i], req.Type.String(), req.Side.String(), + req.AssetType.Upper(), closedStatuses, 2<<30-1, req.StartTime, req.EndTime) if err != nil { return nil, err } ord = append(ord, interOrd...) - interOrd, err = c.iterativeGetAllOrders(ctx, p[i], "", req.Type.String(), req.Side.String(), "", - req.AssetType.Upper(), "", "", openStatus, 2<<30-1, req.StartTime, req.EndTime) + interOrd, err = c.iterativeGetAllOrders(ctx, p[i], req.Type.String(), req.Side.String(), + req.AssetType.Upper(), openStatus, 2<<30-1, req.StartTime, req.EndTime) if err != nil { return nil, err } @@ -1233,226 +1248,89 @@ func (c *CoinbasePro) GetOrderHistory(ctx context.Context, req *order.MultiOrder orders[i] = *singleOrder } - // for i := range ord { - // var side order.Side - // side, err = order.StringToOrderSide(ord[i].Side) - // if err != nil { - // return nil, err - // } - // id, err := uuid.FromString(ord[i].OrderID) - // if err != nil { - // return nil, err - // } - // var price float64 - // var amount float64 - // if ord[i].OrderConfiguration.MarketMarketIOC != nil { - // err = stringToFloatPtr(&amount, ord[i].OrderConfiguration.MarketMarketIOC.QuoteSize) - // if err != nil { - // return nil, err - // } - // err = stringToFloatPtr(&amount, ord[i].OrderConfiguration.MarketMarketIOC.BaseSize) - // if err != nil { - // return nil, err - // } - // } - - // if ord[i].OrderConfiguration.LimitLimitGTC != nil { - // err = stringToFloatPtr(&price, ord[i].OrderConfiguration.LimitLimitGTC.LimitPrice) - // if err != nil { - // return nil, err - // } - // err = stringToFloatPtr(&amount, ord[i].OrderConfiguration.LimitLimitGTC.BaseSize) - // if err != nil { - // return nil, err - // } - - // } - // if ord[i].OrderConfiguration.LimitLimitGTD != nil { - // err = stringToFloatPtr(&price, ord[i].OrderConfiguration.LimitLimitGTD.LimitPrice) - // if err != nil { - // return nil, err - // } - // err = stringToFloatPtr(&amount, ord[i].OrderConfiguration.LimitLimitGTD.BaseSize) - // if err != nil { - // return nil, err - // } - - // } - // if ord[i].OrderConfiguration.StopLimitStopLimitGTC != nil { - // err = stringToFloatPtr(&price, ord[i].OrderConfiguration.StopLimitStopLimitGTC.LimitPrice) - // if err != nil { - // return nil, err - // } - // err = stringToFloatPtr(&amount, ord[i].OrderConfiguration.StopLimitStopLimitGTC.BaseSize) - // if err != nil { - // return nil, err - // } - - // } - // if ord[i].OrderConfiguration.StopLimitStopLimitGTD != nil { - // err = stringToFloatPtr(&price, ord[i].OrderConfiguration.StopLimitStopLimitGTD.LimitPrice) - // if err != nil { - // return nil, err - // } - // err = stringToFloatPtr(&amount, ord[i].OrderConfiguration.StopLimitStopLimitGTD.BaseSize) - // if err != nil { - // return nil, err - // } - - // } - - // orders[i] = order.Detail{ - // ID: id, - // Exchange: c.Name, - // CurrencyPair: p, - // AssetType: assetType, - // Side: side, - // Price: price, - // Amount: amount, - // Timestamp: ord[i].CreatedTime, - // } - // } - return req.Filter(c.Name, orders), nil +} - // var respOrders []GetOrderResponse - // if len(req.Pairs) > 0 { - // var fPair currency.Pair - // var resp []GetOrderResponse - // for i := range req.Pairs { - // fPair, err = c.FormatExchangeCurrency(req.Pairs[i], asset.Spot) - // if err != nil { - // return nil, err - // } - // resp, err = c.GetOrders(ctx, []string{"done"}, fPair.String()) - // if err != nil { - // return nil, err - // } - // respOrders = append(respOrders, resp...) - // } - // } else { - // respOrders, err = c.GetOrders(ctx, []string{"done"}, "") - // if err != nil { - // return nil, err - // } - // } - - // format, err := c.GetPairFormat(asset.Spot, false) - // if err != nil { - // return nil, err - // } - - // orders := make([]order.Detail, len(respOrders)) - // for i := range respOrders { - // var curr currency.Pair - // curr, err = currency.NewPairDelimiter(respOrders[i].ProductID, - // format.Delimiter) - // if err != nil { - // return nil, err - // } - // var side order.Side - // side, err = order.StringToOrderSide(respOrders[i].Side) - // if err != nil { - // return nil, err - // } - // var orderStatus order.Status - // orderStatus, err = order.StringToOrderStatus(respOrders[i].Status) - // if err != nil { - // log.Errorf(log.ExchangeSys, "%s %v", c.Name, err) - // } - // var orderType order.Type - // // orderType, err = order.StringToOrderType(respOrders[i].Type) - // if err != nil { - // log.Errorf(log.ExchangeSys, "%s %v", c.Name, err) - // } - // detail := order.Detail{ - // OrderID: respOrders[i].OrderID, - // // Amount: respOrders[i].Size, - // ExecutedAmount: respOrders[i].FilledSize, - // // RemainingAmount: respOrders[i].Size - respOrders[i].FilledSize, - // // Cost: respOrders[i].ExecutedValue, - // CostAsset: curr.Quote, - // Type: orderType, - // Date: respOrders[i].CreatedTime, - // // CloseTime: respOrders[i].DoneAt, - // // Fee: respOrders[i].FillFees, - // FeeAsset: curr.Quote, - // Side: side, - // Status: orderStatus, - // Pair: curr, - // // Price: respOrders[i].Price, - // Exchange: c.Name, - // } - // detail.InferCostsAndTimes() - // orders[i] = detail - // } - // return req.Filter(c.Name, orders), nil +func formatExchangeKlineInterval(interval kline.Interval) string { + switch interval { + case kline.OneMin: + return granOneMin + case kline.FiveMin: + return granFiveMin + case kline.FifteenMin: + return granFifteenMin + case kline.ThirtyMin: + return granThirtyMin + case kline.OneHour: + return granOneHour + case kline.TwoHour: + return granTwoHour + case kline.SixHour: + return granSixHour + case kline.OneDay: + return granOneDay + } + return errIntervalNotSupported } // GetHistoricCandles returns a set of candle between two time periods for a // designated time period func (c *CoinbasePro) GetHistoricCandles(ctx context.Context, pair currency.Pair, a asset.Item, interval kline.Interval, start, end time.Time) (*kline.Item, error) { - // req, err := c.GetKlineRequest(pair, a, interval, start, end, false) - // if err != nil { - // return nil, err - // } - - // history, err := c.GetHistoricRates(ctx, - // req.RequestFormatted.String(), - // start.Format(time.RFC3339), - // end.Format(time.RFC3339), - // int64(req.ExchangeInterval.Duration().Seconds())) - // if err != nil { - // return nil, err - // } - - // timeSeries := make([]kline.Candle, len(history)) - // for x := range history { - // timeSeries[x] = kline.Candle{ - // Time: history[x].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) - return nil, common.ErrFunctionNotSupported + req, err := c.GetKlineRequest(pair, a, interval, start, end, false) + 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[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, + } + } + return req.ProcessResponse(timeSeries) } // GetHistoricCandlesExtended returns candles between a time period for a set time interval func (c *CoinbasePro) GetHistoricCandlesExtended(ctx context.Context, pair currency.Pair, a asset.Item, interval kline.Interval, start, end time.Time) (*kline.Item, error) { - // req, err := c.GetKlineExtendedRequest(pair, a, interval, start, end) - // if err != nil { - // return nil, err - // } - - // timeSeries := make([]kline.Candle, 0, req.Size()) - // for x := range req.RangeHolder.Ranges { - // var history []History - // history, err = c.GetHistoricRates(ctx, - // req.RequestFormatted.String(), - // req.RangeHolder.Ranges[x].Start.Time.Format(time.RFC3339), - // req.RangeHolder.Ranges[x].End.Time.Format(time.RFC3339), - // int64(req.ExchangeInterval.Duration().Seconds())) - // if err != nil { - // return nil, err - // } - - // for i := range history { - // timeSeries = append(timeSeries, kline.Candle{ - // Time: history[i].Time, - // Low: history[i].Low, - // High: history[i].High, - // Open: history[i].Open, - // Close: history[i].Close, - // Volume: history[i].Volume, - // }) - // } - // } - // return req.ProcessResponse(timeSeries) - return nil, common.ErrFunctionNotSupported + req, err := c.GetKlineExtendedRequest(pair, a, interval, start, end) + if err != nil { + return nil, err + } + + timeSeries := make([]kline.Candle, 0, req.Size()) + for x := range req.RangeHolder.Ranges { + var history History + history, err := c.GetHistoricRates(ctx, req.RequestFormatted.String(), + formatExchangeKlineInterval(req.ExchangeInterval), + req.RangeHolder.Ranges[x].Start.Time.Add(-time.Nanosecond), + req.RangeHolder.Ranges[x].End.Time.Add(-time.Nanosecond)) + if err != nil { + return nil, err + } + + for i := range history.Candles { + 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, + }) + } + } + return req.ProcessResponse(timeSeries) } // ValidateAPICredentials validates current credentials used for wrapper @@ -1464,27 +1342,135 @@ func (c *CoinbasePro) ValidateAPICredentials(ctx context.Context, assetType asse // GetServerTime returns the current exchange server time. func (c *CoinbasePro) GetServerTime(ctx context.Context, _ asset.Item) (time.Time, error) { - // st, err := c.GetCurrentServerTime(ctx) - // if err != nil { - // return time.Time{}, err - // } - // return st.ISO, nil - return time.Time{}, errors.New(common.ErrFunctionNotSupported.Error()) + st, err := c.GetV2Time(ctx) + if err != nil { + return time.Time{}, err + } + return st.Data.ISO, nil } // GetLatestFundingRates returns the latest funding rates data -func (c *CoinbasePro) GetLatestFundingRates(context.Context, *fundingrate.LatestRateRequest) ([]fundingrate.LatestRateResponse, error) { - return nil, common.ErrFunctionNotSupported +func (c *CoinbasePro) GetLatestFundingRates(ctx context.Context, r *fundingrate.LatestRateRequest) ([]fundingrate.LatestRateResponse, error) { + if r == nil { + return nil, common.ErrNilPointer + } + 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 + } + + var funding []fundingrate.LatestRateResponse + + 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 = append(funding, fundingrate.LatestRateResponse{ + Exchange: c.Name, + Asset: r.Asset, + Pair: pair, + LatestRate: funRate, + TimeChecked: time.Now(), + }) + } + + return funding, nil } // GetFuturesContractDetails returns all contracts from the exchange by asset type -func (c *CoinbasePro) GetFuturesContractDetails(context.Context, asset.Item) ([]futures.Contract, error) { - return nil, common.ErrFunctionNotSupported +func (c *CoinbasePro) GetFuturesContractDetails(ctx context.Context, item asset.Item) ([]futures.Contract, error) { + if !item.IsFutures() { + return nil, futures.ErrNotFuturesAsset + } + if !c.SupportsAsset(item) { + return nil, fmt.Errorf("%w %v", asset.ErrNotSupported, item) + } + + products, err := c.fetchFutures(ctx) + if err != nil { + return nil, err + } + + var contracts []futures.Contract + + 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()), + } + contracts = append(contracts, futures.Contract{ + Exchange: c.Name, + Name: pair, + Asset: item, + EndDate: products.Products[i].FutureProductDetails.ContractExpiry, + IsActive: !products.Products[i].IsDisabled, + Status: products.Products[i].Status, + Type: futures.LongDated, + SettlementCurrencies: []currency.Code{currency.NewCode(products.Products[i].QuoteCurrencyID)}, + Multiplier: products.Products[i].BaseIncrement.Float64(), + LatestRate: funRate, + }) + } + + return contracts, nil } // UpdateOrderExecutionLimits updates order execution limits -func (c *CoinbasePro) UpdateOrderExecutionLimits(_ context.Context, _ asset.Item) error { - return common.ErrNotYetImplemented +func (c *CoinbasePro) UpdateOrderExecutionLimits(ctx context.Context, a asset.Item) error { + + var data AllProducts + var err error + switch a { + case asset.Spot: + data, err = c.GetAllProducts(ctx, 2<<30-1, 0, "SPOT", "", "", nil) + case asset.Futures: + data, err = c.fetchFutures(ctx) + default: + err = fmt.Errorf("%w %s", asset.ErrNotSupported, a) + } + if err != nil { + return err + } + + var limits []order.MinMaxLevel + + for i := range data.Products { + pair, err := currency.NewPairFromString(data.Products[i].ID) + if err != nil { + return err + } + + limits = append(limits, order.MinMaxLevel{ + Pair: pair, + Asset: a, + MinPrice: data.Products[i].QuoteMinSize.Float64(), + MaxPrice: data.Products[i].QuoteMaxSize.Float64(), + PriceStepIncrementSize: data.Products[i].PriceIncrement.Float64(), + MinimumBaseAmount: data.Products[i].BaseMinSize.Float64(), + MaximumBaseAmount: data.Products[i].BaseMaxSize.Float64(), + MinimumQuoteAmount: data.Products[i].QuoteMinSize.Float64(), + MaximumQuoteAmount: data.Products[i].QuoteMaxSize.Float64(), + AmountStepIncrementSize: data.Products[i].BaseIncrement.Float64(), + QuoteStepIncrementSize: data.Products[i].QuoteIncrement.Float64(), + MaxTotalOrders: 1000, + }) + } + + return c.LoadLimits(limits) } // cancelOrdersReturnMapAndCount is a helper function for CancelBatchOrders and CancelAllOrders, From ffb43d89d38ae120bdf501e82912a882b41c8c23 Mon Sep 17 00:00:00 2001 From: Samuel Reid <43227667+cranktakular@users.noreply.github.com> Date: Tue, 9 Jan 2024 17:39:56 +1100 Subject: [PATCH 16/79] Test & type checking --- exchanges/coinbasepro/coinbasepro.go | 244 ++++-- exchanges/coinbasepro/coinbasepro_test.go | 867 +++++++++++-------- exchanges/coinbasepro/coinbasepro_types.go | 273 +++--- exchanges/coinbasepro/coinbasepro_wrapper.go | 8 +- 4 files changed, 832 insertions(+), 560 deletions(-) diff --git a/exchanges/coinbasepro/coinbasepro.go b/exchanges/coinbasepro/coinbasepro.go index 0b4f72b5bba..8eb11b7a53b 100644 --- a/exchanges/coinbasepro/coinbasepro.go +++ b/exchanges/coinbasepro/coinbasepro.go @@ -42,6 +42,11 @@ const ( coinbaseTicker = "ticker" coinbasePortfolios = "portfolios" coinbaseMoveFunds = "move_funds" + coinbaseCFM = "cfm" + coinbaseBalanceSummary = "balance_summary" + coinbasePositions = "positions" + coinbaseSweeps = "sweeps" + coinbaseSchedule = "schedule" coinbaseTransactionSummary = "transaction_summary" coinbaseConvert = "convert" coinbaseQuote = "quote" @@ -293,8 +298,6 @@ func (c *CoinbasePro) PlaceOrder(ctx context.Context, clientOID, productID, side return nil, errAmountEmpty } - var resp PlaceOrderResp - var orderConfig OrderConfiguration switch orderType { @@ -337,16 +340,21 @@ func (c *CoinbasePro) PlaceOrder(ctx context.Context, clientOID, productID, side orderConfig.StopLimitStopLimitGTD.EndTime = endTime } default: - return &resp, errInvalidOrderType + return nil, errInvalidOrderType } 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} - if marginType != "" { + if marginType == "ISOLATED" || marginType == "CROSS" { req["margin_type"] = marginType } + if marginType == "MULTI" { + req["margin_type"] = "CROSS" + } + + var resp PlaceOrderResp return &resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, @@ -370,12 +378,14 @@ func (c *CoinbasePro) CancelOrders(ctx context.Context, orderIDs []string) (Canc // 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) (bool, error) { +func (c *CoinbasePro) EditOrder(ctx context.Context, orderID string, size, price float64) (SuccessBool, error) { + var resp SuccessBool + if orderID == "" { - return false, errOrderIDEmpty + return resp, errOrderIDEmpty } if size == 0 && price == 0 { - return false, errSizeAndPriceZero + return resp, errSizeAndPriceZero } path := fmt.Sprintf("%s%s/%s", coinbaseV3, coinbaseOrders, coinbaseEdit) @@ -383,8 +393,6 @@ func (c *CoinbasePro) EditOrder(ctx context.Context, orderID string, size, price req := map[string]interface{}{"order_id": orderID, "size": strconv.FormatFloat(size, 'f', -1, 64), "price": strconv.FormatFloat(price, 'f', -1, 64)} - var resp bool - return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, path, "", req, Version3, &resp, nil) } @@ -416,7 +424,7 @@ func (c *CoinbasePro) GetAllOrders(ctx context.Context, productID, userNativeCur var params Params params.urlVals = make(url.Values) - err := params.PrepareDateString(startDate, endDate, startDateString, endDateString) + err := params.prepareDateString(startDate, endDate, startDateString, endDateString) if err != nil { return resp, err } @@ -428,6 +436,11 @@ func (c *CoinbasePro) GetAllOrders(ctx context.Context, productID, userNativeCur params.urlVals.Add("order_status", orderStatus[x]) } } + if len(assetFilters) != 0 { + for x := range assetFilters { + params.urlVals.Add("asset_filters", assetFilters[x]) + } + } params.urlVals.Set("product_id", productID) params.urlVals.Set("limit", strconv.FormatInt(int64(limit), 10)) @@ -460,11 +473,11 @@ func (c *CoinbasePro) GetAllOrders(ctx context.Context, productID, userNativeCur } // GetFills returns information of recent fills on the specified profile -func (c *CoinbasePro) GetFills(ctx context.Context, orderID, productID, cursor string, limit uint16, startDate, endDate time.Time) (FillResponse, error) { +func (c *CoinbasePro) GetFills(ctx context.Context, orderID, productID, cursor string, startDate, endDate time.Time, limit uint16) (FillResponse, error) { var resp FillResponse var params Params params.urlVals = url.Values{} - err := params.PrepareDateString(startDate, endDate, "start_sequence_timestamp", + err := params.prepareDateString(startDate, endDate, "start_sequence_timestamp", "end_sequence_timestamp") if err != nil { return resp, err @@ -488,7 +501,7 @@ func (c *CoinbasePro) GetFills(ctx context.Context, orderID, productID, cursor s } // GetOrderByID returns a single order by order id. -func (c *CoinbasePro) GetOrderByID(ctx context.Context, orderID, userNativeCurrency, clientID string) (*GetOrderResponse, error) { +func (c *CoinbasePro) GetOrderByID(ctx context.Context, orderID, clientID, userNativeCurrency string) (*GetOrderResponse, error) { if orderID == "" { return nil, errOrderIDEmpty } @@ -536,7 +549,7 @@ func (c *CoinbasePro) CreatePortfolio(ctx context.Context, name string) (SimpleP } // MovePortfolioFunds transfers funds between portfolios -func (c *CoinbasePro) MovePortfolioFunds(ctx context.Context, from, to, currency string, amount float64) (MovePortfolioFundsResponse, error) { +func (c *CoinbasePro) MovePortfolioFunds(ctx context.Context, currency, from, to string, amount float64) (MovePortfolioFundsResponse, error) { var resp MovePortfolioFundsResponse if from == "" || to == "" { @@ -604,13 +617,88 @@ func (c *CoinbasePro) EditPortfolio(ctx context.Context, portfolioID, name strin path, "", req, Version3, &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) + + return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, + path, "", nil, Version3, &resp, nil) +} + +// GetAllFuturesPositions returns a list of all open positions in CFM futures products +func (c *CoinbasePro) GetAllFuturesPositions(ctx context.Context) (AllFuturesPositions, error) { + var resp AllFuturesPositions + + path := fmt.Sprintf("%s%s/%s", coinbaseV3, coinbaseCFM, coinbasePositions) + + return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, + path, "", nil, Version3, &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 + + if productID == "" { + return resp, errProductIDEmpty + } + + path := fmt.Sprintf("%s%s/%s/%s", coinbaseV3, coinbaseCFM, coinbasePositions, productID) + + return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, + path, "", nil, Version3, &resp, nil) +} + +// ScheduleFuturesSweep schedules a sweep of funds from a CFTC-regulated futures account to a +// Coinbase USD Spot wallet. Request submitted before 5 pm ET are processed the following +// 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) + + 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, Version3, &resp, nil) +} + +// ListFuturesSweeps returns information on pending and/or processing requests to sweep funds +func (c *CoinbasePro) ListFuturesSweeps(ctx context.Context) (ListFuturesSweepsResponse, error) { + var resp ListFuturesSweepsResponse + + path := fmt.Sprintf("%s%s/%s", coinbaseV3, coinbaseCFM, coinbaseSweeps) + + return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, + path, "", nil, Version3, &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) + + var resp SuccessBool + + return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodDelete, + path, "", nil, Version3, &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{} - err := params.PrepareDateString(startDate, endDate, startDateString, endDateString) + err := params.prepareDateString(startDate, endDate, startDateString, endDateString) if err != nil { return nil, err } @@ -705,7 +793,7 @@ func (c *CoinbasePro) ListNotifications(ctx context.Context, pag PaginationInp) var params Params params.urlVals = url.Values{} - params.PreparePagination(pag) + params.preparePagination(pag) pathParams := common.EncodeURLValues("", params.urlVals) @@ -784,7 +872,7 @@ func (c *CoinbasePro) GetAllWallets(ctx context.Context, pag PaginationInp) (Get var params Params params.urlVals = url.Values{} - params.PreparePagination(pag) + params.preparePagination(pag) pathParams := common.EncodeURLValues("", params.urlVals) @@ -870,7 +958,7 @@ func (c *CoinbasePro) GetAllAddresses(ctx context.Context, walletID string, pag var params Params params.urlVals = url.Values{} - params.PreparePagination(pag) + params.preparePagination(pag) pathParams := common.EncodeURLValues("", params.urlVals) @@ -912,7 +1000,7 @@ func (c *CoinbasePro) GetAddressTransactions(ctx context.Context, walletID, addr var params Params params.urlVals = url.Values{} - params.PreparePagination(pag) + params.preparePagination(pag) pathParams := common.EncodeURLValues("", params.urlVals) @@ -970,7 +1058,7 @@ func (c *CoinbasePro) GetAllTransactions(ctx context.Context, walletID string, p var params Params params.urlVals = url.Values{} - params.PreparePagination(pag) + params.preparePagination(pag) pathParams := common.EncodeURLValues("", params.urlVals) @@ -1076,7 +1164,7 @@ func (c *CoinbasePro) GetAllFiatTransfers(ctx context.Context, walletID string, var params Params params.urlVals = url.Values{} - params.PreparePagination(pag) + params.preparePagination(pag) pathParams := common.EncodeURLValues("", params.urlVals) @@ -1094,7 +1182,7 @@ func (c *CoinbasePro) GetAllFiatTransfers(ctx context.Context, walletID string, return resp, nil } -// GetFiatTransferByID returns information on a single deposit associated with the specified wallet +// GetFiatTransferByID returns information on a single deposit/withdrawal associated with the specified wallet func (c *CoinbasePro) GetFiatTransferByID(ctx context.Context, walletID, depositID string, transferType FiatTransferType) (*GenDeposWithdrResp, error) { if walletID == "" { return nil, errWalletIDEmpty @@ -1120,7 +1208,7 @@ func (c *CoinbasePro) GetAllPaymentMethods(ctx context.Context, pag PaginationIn var params Params params.urlVals = url.Values{} - params.PreparePagination(pag) + params.preparePagination(pag) pathParams := common.EncodeURLValues("", params.urlVals) @@ -2092,7 +2180,7 @@ func (c *CoinbasePro) GetFee(ctx context.Context, feeBuilder *exchange.FeeBuilde } var fee float64 switch { - case !c.IsStablePair(feeBuilder.Pair) && feeBuilder.FeeType == exchange.CryptocurrencyTradeFee: + case !isStablePair(feeBuilder.Pair) && feeBuilder.FeeType == exchange.CryptocurrencyTradeFee: fees, err := c.GetTransactionSummary(ctx, time.Now().Add(-time.Hour*24*30), time.Now(), "", "", "") if err != nil { return 0, err @@ -2102,15 +2190,15 @@ func (c *CoinbasePro) GetFee(ctx context.Context, feeBuilder *exchange.FeeBuilde } else { fee = fees.FeeTier.TakerFeeRate } - case feeBuilder.IsMaker && c.IsStablePair(feeBuilder.Pair) && + case feeBuilder.IsMaker && isStablePair(feeBuilder.Pair) && (feeBuilder.FeeType == exchange.CryptocurrencyTradeFee || feeBuilder.FeeType == exchange.OfflineTradeFee): fee = 0 - case !feeBuilder.IsMaker && c.IsStablePair(feeBuilder.Pair) && + case !feeBuilder.IsMaker && isStablePair(feeBuilder.Pair) && (feeBuilder.FeeType == exchange.CryptocurrencyTradeFee || feeBuilder.FeeType == exchange.OfflineTradeFee): fee = 0.00001 - case feeBuilder.IsMaker && !c.IsStablePair(feeBuilder.Pair) && feeBuilder.FeeType == exchange.OfflineTradeFee: + case feeBuilder.IsMaker && !isStablePair(feeBuilder.Pair) && feeBuilder.FeeType == exchange.OfflineTradeFee: fee = 0.006 - case !feeBuilder.IsMaker && !c.IsStablePair(feeBuilder.Pair) && feeBuilder.FeeType == exchange.OfflineTradeFee: + case !feeBuilder.IsMaker && !isStablePair(feeBuilder.Pair) && feeBuilder.FeeType == exchange.OfflineTradeFee: fee = 0.008 default: return 0, errFeeTypeNotSupported @@ -2138,7 +2226,7 @@ var stableMap = map[key.PairAsset]bool{ } // IsStablePair returns true if the currency pair is considered a "stable pair" by Coinbase -func (c *CoinbasePro) IsStablePair(pair currency.Pair) bool { +func isStablePair(pair currency.Pair) bool { return stableMap[key.PairAsset{Base: pair.Base.Item, Quote: pair.Quote.Item}] } @@ -2167,7 +2255,7 @@ func (c *CoinbasePro) 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 { +func (p *Params) prepareDateString(startDate, endDate time.Time, labelStart, labelEnd string) error { err := common.StartEndTimeCheck(startDate, endDate) if err == nil { @@ -2184,7 +2272,7 @@ func (p *Params) PrepareDateString(startDate, endDate time.Time, labelStart, lab return err } -func (p *Params) PreparePagination(pag PaginationInp) { +func (p *Params) preparePagination(pag PaginationInp) { if pag.Limit != 0 { p.urlVals.Set("limit", strconv.FormatInt(int64(pag.Limit), 10)) } @@ -2201,56 +2289,56 @@ func (p *Params) PreparePagination(pag PaginationInp) { // OrderbookHelper handles the transfer of bids and asks of unclear levels, to a // generalised format -func OrderbookHelper(iOD InterOrderDetail, level int32) ([]GenOrderDetail, error) { - gOD := make([]GenOrderDetail, len(iOD)) - - for i := range iOD { - priceConv, ok := iOD[i][0].(string) - if !ok { - return nil, errors.New("unable to type assert price") - } - price, err := strconv.ParseFloat(priceConv, 64) - if err != nil { - return nil, err - } - gOD[i].Price = price - - amountConv, ok := iOD[i][1].(string) - if !ok { - return nil, errors.New("unable to type assert amount") - } - amount, err := strconv.ParseFloat(amountConv, 64) - if err != nil { - return nil, err - } - gOD[i].Amount = amount +// func orderbookHelper(iOD InterOrderDetail, level int32) ([]GenOrderDetail, error) { +// gOD := make([]GenOrderDetail, len(iOD)) + +// for i := range iOD { +// priceConv, ok := iOD[i][0].(string) +// if !ok { +// return nil, errors.New("unable to type assert price") +// } +// price, err := strconv.ParseFloat(priceConv, 64) +// if err != nil { +// return nil, err +// } +// gOD[i].Price = price + +// amountConv, ok := iOD[i][1].(string) +// if !ok { +// return nil, errors.New("unable to type assert amount") +// } +// amount, err := strconv.ParseFloat(amountConv, 64) +// if err != nil { +// return nil, err +// } +// gOD[i].Amount = amount + +// if level == 3 { +// orderID, ok := iOD[i][2].(string) +// if !ok { +// return nil, errors.New("unable to type assert order ID") +// } +// gOD[i].OrderID = orderID +// } else { +// numOrders, ok := iOD[i][2].(float64) +// if !ok { +// return nil, errors.New("unable to type assert number of orders") +// } +// gOD[i].NumOrders = numOrders +// } - if level == 3 { - orderID, ok := iOD[i][2].(string) - if !ok { - return nil, errors.New("unable to type assert order ID") - } - gOD[i].OrderID = orderID - } else { - numOrders, ok := iOD[i][2].(float64) - if !ok { - return nil, errors.New("unable to type assert number of orders") - } - gOD[i].NumOrders = numOrders - } - - } - return gOD, nil +// } +// return gOD, nil -} +// } -// PrepareDSL adds the direction, step, and limit queries for pagination -func (p *Params) PrepareDSL(direction, step string, limit int64) { - p.urlVals.Set(direction, step) - if limit >= 0 { - p.urlVals.Set("limit", strconv.FormatInt(limit, 10)) - } -} +// prepareDSL adds the direction, step, and limit queries for pagination +// func (p *Params) prepareDSL(direction, step string, limit int64) { +// p.urlVals.Set(direction, step) +// if limit >= 0 { +// p.urlVals.Set("limit", strconv.FormatInt(limit, 10)) +// } +// } func (f FiatTransferType) String() string { if f { diff --git a/exchanges/coinbasepro/coinbasepro_test.go b/exchanges/coinbasepro/coinbasepro_test.go index bd2900b9852..56e5c0d75b1 100644 --- a/exchanges/coinbasepro/coinbasepro_test.go +++ b/exchanges/coinbasepro/coinbasepro_test.go @@ -30,9 +30,10 @@ import ( ) var ( - c = &CoinbasePro{} - testPair = currency.NewPairWithDelimiter(currency.BTC.String(), currency.USD.String(), "-") - testCur = currency.BTC + c = &CoinbasePro{} + testCrypto = currency.BTC + testFiat = currency.USD + testPair = currency.NewPairWithDelimiter(testCrypto.String(), testFiat.String(), "-") ) // Please supply your APIKeys here for better testing @@ -49,21 +50,34 @@ const ( // Donation address testAddress = "bc1qk0jareu4jytc0cfrhr5wgshsq8282awpavfahc" - errExpectMismatch = "received: '%v' but expected: '%v'" - errExpectedNonEmpty = "expected non-empty response" - errOrder0CancelFail = "order 0 failed to cancel" - errIDNotSet = "ID not set" - skipPayMethodNotFound = "no payment methods found, skipping" - skipInsufSuitableAccs = "insufficient suitable accounts found, skipping" - skipInsufficientFunds = "insufficient funds for test, skipping" - errx7f = "setting proxy address error parse \"\\x7f\": net/url: invalid control character in URL" - errPortfolioNameDuplicate = `CoinbasePro unsuccessful HTTP status code: 409 raw response: {"error":"CONFLICT","error_details":"[PORTFOLIO_ERROR_CODE_ALREADY_EXISTS] the requested portfolio name already exists","message":"[PORTFOLIO_ERROR_CODE_ALREADY_EXISTS] the requested portfolio name already exists"}, authenticated request failed` - errPortTransferInsufFunds = `CoinbasePro unsuccessful HTTP status code: 429 raw response: {"error":"unknown","error_details":"[PORTFOLIO_ERROR_CODE_INSUFFICIENT_FUNDS] insufficient funds in source account","message":"[PORTFOLIO_ERROR_CODE_INSUFFICIENT_FUNDS] insufficient funds in source account"}, authenticated request failed` - errFeeBuilderNil = "*exchange.FeeBuilder nil pointer" - errUnsupportedAssetType = " unsupported asset type" - errUpsideUnsupported = "unsupported asset type upsideprofitcontract" - - testAmount = 0.00000001 + skipPayMethodNotFound = "no payment methods found, skipping" + skipInsufSuitableAccs = "insufficient suitable accounts for test, skipping" + skipInsufficientFunds = "insufficient funds for test, skipping" + skipInsufficientOrders = "insufficient orders for test, skipping" + skipInsufficientPortfolios = "insufficient portfolios for test, skipping" + skipSweepPending = "pending sweep found, skipping" + skipInsufficientWallets = "insufficient wallets for test, skipping" + skipInsufficientFundsOrWallets = "insufficient funds or wallets for test, skipping" + skipInsufficientTransactions = "insufficient transactions for test, skipping" + + errExpectMismatch = "received: '%v' but expected: '%v'" + errExpectedNonEmpty = "expected non-empty response" + errOrder0CancelFail = "order 0 failed to cancel" + errIDNotSet = "ID not set" + errx7f = "setting proxy address error parse \"\\x7f\": net/url: invalid control character in URL" + errPortfolioNameDuplicate = `CoinbasePro unsuccessful HTTP status code: 409 raw response: {"error":"CONFLICT","error_details":"[PORTFOLIO_ERROR_CODE_ALREADY_EXISTS] the requested portfolio name already exists","message":"[PORTFOLIO_ERROR_CODE_ALREADY_EXISTS] the requested portfolio name already exists"}, authenticated request failed` + errPortTransferInsufFunds = `CoinbasePro unsuccessful HTTP status code: 429 raw response: {"error":"unknown","error_details":"[PORTFOLIO_ERROR_CODE_INSUFFICIENT_FUNDS] insufficient funds in source account","message":"[PORTFOLIO_ERROR_CODE_INSUFFICIENT_FUNDS] insufficient funds in source account"}, authenticated request failed` + errInvalidProductID = `CoinbasePro unsuccessful HTTP status code: 400 raw response: {"error":"INVALID_ARGUMENT","error_details":"valid product_id is required","message":"valid product_id is required"}, authenticated request failed` + errFeeBuilderNil = "*exchange.FeeBuilder nil pointer" + errUnsupportedAssetType = " unsupported asset type" + errUpsideUnsupported = "unsupported asset type upsideprofitcontract" + errBlorboGranularity = "invalid granularity blorbo, allowed granularities are: [ONE_MINUTE FIVE_MINUTE FIFTEEN_MINUTE THIRTY_MINUTE ONE_HOUR TWO_HOUR SIX_HOUR ONE_DAY]" + errNoEndpointPathEdgeCase3 = "no endpoint path found for the given key: EdgeCase3URL" + errJsonUnsupportedChan = "json: unsupported type: chan struct {}, authenticated request failed" + errExpectedFeeRange = "expected fee range of %v and %v, received %v" + + testAmount = 1e-08 + testPrice = 1e+09 ) func TestGetDefaultConfig(t *testing.T) { @@ -74,14 +88,6 @@ func TestGetDefaultConfig(t *testing.T) { } } -func TestWrapperStart(t *testing.T) { - wg := sync.WaitGroup{} - err := c.Start(context.Background(), &wg) - if err != nil { - t.Error(err) - } -} - func TestSetup(t *testing.T) { err := c.Setup(nil) if !errors.Is(err, config.ErrExchangeConfigIsNil) { @@ -104,6 +110,14 @@ func TestSetup(t *testing.T) { } } +func TestWrapperStart(t *testing.T) { + wg := sync.WaitGroup{} + err := c.Start(context.Background(), &wg) + if err != nil { + t.Error(err) + } +} + func TestMain(m *testing.M) { c.SetDefaults() if testingInSandbox { @@ -127,14 +141,15 @@ func TestMain(m *testing.M) { gdxConfig.API.AuthenticatedSupport = true gdxConfig.API.AuthenticatedWebsocketSupport = true } - gdxConfig.API.AuthenticatedSupport = true c.Websocket = sharedtestvalues.NewTestWebsocket() err = c.Setup(gdxConfig) if err != nil { log.Fatal("CoinbasePro setup error", err) } - c.GetBase().API.AuthenticatedSupport = true - c.GetBase().API.AuthenticatedWebsocketSupport = true + if apiKey != "" { + c.GetBase().API.AuthenticatedSupport = true + c.GetBase().API.AuthenticatedWebsocketSupport = true + } c.Verbose = true err = gctlog.SetGlobalLogConfig(gctlog.GenDefaultSettings()) fmt.Println(err) @@ -175,11 +190,11 @@ func TestGetAllAccounts(t *testing.T) { } func TestGetAccountByID(t *testing.T) { - sharedtestvalues.SkipTestIfCredentialsUnset(t, c) _, err := c.GetAccountByID(context.Background(), "") if !errors.Is(err, errAccountIDEmpty) { t.Errorf(errExpectMismatch, err, errAccountIDEmpty) } + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) longResp, err := c.GetAllAccounts(context.Background(), 49, "") if err != nil { t.Error(err) @@ -192,7 +207,7 @@ func TestGetAccountByID(t *testing.T) { t.Error(err) } if *shortResp != longResp.Accounts[0] { - t.Error("mismatched responses") + t.Errorf(errExpectMismatch, shortResp, longResp.Accounts[0]) } } @@ -207,11 +222,11 @@ func TestGetBestBidAsk(t *testing.T) { } func TestGetProductBook(t *testing.T) { - sharedtestvalues.SkipTestIfCredentialsUnset(t, c) _, err := c.GetProductBook(context.Background(), "", 0) if !errors.Is(err, errProductIDEmpty) { t.Errorf(errExpectMismatch, err, errProductIDEmpty) } + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) resp, err := c.GetProductBook(context.Background(), testPair.String(), 2) if err != nil { t.Error(err) @@ -221,9 +236,9 @@ func TestGetProductBook(t *testing.T) { func TestGetAllProducts(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - // testPairs := []string{testPair.String(), "ETH-USD"} - var testPairs []string - resp, err := c.GetAllProducts(context.Background(), 30000, 0, "FUTURE", "", "", + testPairs := []string{testPair.String(), "ETH-USD"} + // var testPairs []string + resp, err := c.GetAllProducts(context.Background(), 30000, 0, "SPOT", "PERPETUAL", "", testPairs) if err != nil { t.Error(err) @@ -233,11 +248,11 @@ func TestGetAllProducts(t *testing.T) { } func TestGetProductByID(t *testing.T) { - sharedtestvalues.SkipTestIfCredentialsUnset(t, c) _, err := c.GetProductByID(context.Background(), "") if !errors.Is(err, errProductIDEmpty) { t.Errorf(errExpectMismatch, err, errProductIDEmpty) } + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) resp, err := c.GetProductByID(context.Background(), testPair.String()) if err != nil { t.Error(err) @@ -246,15 +261,15 @@ func TestGetProductByID(t *testing.T) { } func TestGetHistoricRates(t *testing.T) { - sharedtestvalues.SkipTestIfCredentialsUnset(t, c) _, err := c.GetHistoricRates(context.Background(), "", granUnknown, time.Time{}, time.Time{}) if !errors.Is(err, errProductIDEmpty) { t.Errorf(errExpectMismatch, err, errProductIDEmpty) } _, err = c.GetHistoricRates(context.Background(), testPair.String(), "blorbo", time.Time{}, time.Time{}) - if err == nil { - t.Error("expected error due to invalid granularity") + if err.Error() != errBlorboGranularity { + t.Errorf(errExpectMismatch, err, errBlorboGranularity) } + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) resp, err := c.GetHistoricRates(context.Background(), testPair.String(), granOneMin, time.Now().Add(-5*time.Minute), time.Now()) if err != nil { @@ -264,11 +279,11 @@ func TestGetHistoricRates(t *testing.T) { } func TestGetTicker(t *testing.T) { - sharedtestvalues.SkipTestIfCredentialsUnset(t, c) _, err := c.GetTicker(context.Background(), "", 1, time.Time{}, time.Time{}) if !errors.Is(err, errProductIDEmpty) { t.Errorf(errExpectMismatch, err, errProductIDEmpty) } + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) resp, err := c.GetTicker(context.Background(), testPair.String(), 5, time.Time{}, time.Time{}) if err != nil { t.Error(err) @@ -291,28 +306,27 @@ func TestPlaceOrder(t *testing.T) { t.Errorf(errExpectMismatch, err, errAmountEmpty) } sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) - accounts, err := c.GetAllAccounts(context.Background(), 250, "") + skipTestIfLowOnFunds(t) + id, err := uuid.NewV4() if err != nil { t.Error(err) } - if len(accounts.Accounts) == 0 { - t.Fatal(errExpectedNonEmpty) - } - var hasValidFunds bool - for i := range accounts.Accounts { - if accounts.Accounts[i].Currency == testCur.String() && accounts.Accounts[i].AvailableBalance.Value > 0.00001 { - hasValidFunds = true - } + resp, err := c.PlaceOrder(context.Background(), id.String(), testPair.String(), order.Sell.String(), "", + order.Limit.String(), "", "CROSS", "", testAmount, testPrice, 0, 9999, false, time.Now().Add(time.Hour)) + if err != nil { + t.Error(err) } - if !hasValidFunds { - t.Skip(skipInsufficientFunds) + assert.NotEmpty(t, resp, errExpectedNonEmpty) + id, err = uuid.NewV4() + if err != nil { + t.Error(err) } - id, _ := uuid.NewV4() - _, err = c.PlaceOrder(context.Background(), id.String(), testPair.String(), order.Sell.String(), "", - order.Limit.String(), "", "CROSS", "", 0.0000001, 1000000000000, 0, 9999, false, time.Now().Add(time.Hour)) + resp, err = c.PlaceOrder(context.Background(), id.String(), testPair.String(), order.Sell.String(), "", + order.Limit.String(), "", "MULTI", "", testAmount, testPrice, 0, 9999, false, time.Now().Add(time.Hour)) if err != nil { t.Error(err) } + assert.NotEmpty(t, resp, errExpectedNonEmpty) } func TestCancelOrders(t *testing.T) { @@ -322,32 +336,18 @@ func TestCancelOrders(t *testing.T) { t.Errorf(errExpectMismatch, err, errOrderIDEmpty) } sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) - accounts, err := c.GetAllAccounts(context.Background(), 250, "") - if err != nil { - t.Error(err) - } - if len(accounts.Accounts) == 0 { - t.Fatal(errExpectedNonEmpty) - } - var hasValidFunds bool - for i := range accounts.Accounts { - if accounts.Accounts[i].Currency == testCur.String() && accounts.Accounts[i].AvailableBalance.Value > 0.00001 { - hasValidFunds = true - } - } - if !hasValidFunds { - t.Skip(skipInsufficientFunds) - } + skipTestIfLowOnFunds(t) ordID, err := c.PlaceOrder(context.Background(), "meow", testPair.String(), order.Sell.String(), "", - order.Limit.String(), "", "", "", 0.0000001, 1000000000000, 0, 9999, false, time.Time{}) + order.Limit.String(), "", "", "", testPrice, testAmount, 0, 9999, false, time.Time{}) if err != nil { t.Error(err) } OrderSlice = append(OrderSlice, ordID.OrderID) - _, err = c.CancelOrders(context.Background(), OrderSlice) + resp, err := c.CancelOrders(context.Background(), OrderSlice) if err != nil { t.Error(err) } + assert.NotEmpty(t, resp, errExpectedNonEmpty) } func TestEditOrder(t *testing.T) { @@ -360,32 +360,21 @@ func TestEditOrder(t *testing.T) { t.Errorf(errExpectMismatch, err, errSizeAndPriceZero) } sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) - accounts, err := c.GetAllAccounts(context.Background(), 250, "") + skipTestIfLowOnFunds(t) + id, err := uuid.NewV4() if err != nil { t.Error(err) } - if len(accounts.Accounts) == 0 { - t.Fatal(errExpectedNonEmpty) - } - var hasValidFunds bool - for i := range accounts.Accounts { - if accounts.Accounts[i].Currency == testCur.String() && accounts.Accounts[i].AvailableBalance.Value > 0.00001 { - hasValidFunds = true - } - } - if !hasValidFunds { - t.Skip(skipInsufficientFunds) - } - id, _ := uuid.NewV4() ordID, err := c.PlaceOrder(context.Background(), id.String(), testPair.String(), order.Sell.String(), "", - order.Limit.String(), "", "", "", 0.0000001, 1000000000000, 0, 9999, false, time.Time{}) + order.Limit.String(), "", "", "", testAmount, testPrice, 0, 9999, false, time.Time{}) if err != nil { t.Error(err) } - _, err = c.EditOrder(context.Background(), ordID.OrderID, 0.0000001, 10000000000000) + resp, err := c.EditOrder(context.Background(), ordID.OrderID, testAmount, testPrice*10) if err != nil { t.Error(err) } + assert.NotEmpty(t, resp, errExpectedNonEmpty) } func TestEditOrderPreview(t *testing.T) { @@ -398,36 +387,24 @@ func TestEditOrderPreview(t *testing.T) { t.Errorf(errExpectMismatch, err, errSizeAndPriceZero) } sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) - accounts, err := c.GetAllAccounts(context.Background(), 250, "") + skipTestIfLowOnFunds(t) + id, err := uuid.NewV4() if err != nil { t.Error(err) } - if len(accounts.Accounts) == 0 { - t.Fatal(errExpectedNonEmpty) - } - var hasValidFunds bool - for i := range accounts.Accounts { - if accounts.Accounts[i].Currency == testCur.String() && accounts.Accounts[i].AvailableBalance.Value > 0.00001 { - hasValidFunds = true - } - } - if !hasValidFunds { - t.Skip(skipInsufficientFunds) - } - id, _ := uuid.NewV4() ordID, err := c.PlaceOrder(context.Background(), id.String(), testPair.String(), order.Sell.String(), "", - order.Limit.String(), "", "", "", 0.0000001, 1000000000000, 0, 9999, false, time.Time{}) + order.Limit.String(), "", "", "", testAmount, testPrice, 0, 9999, false, time.Time{}) if err != nil { t.Error(err) } - _, err = c.EditOrderPreview(context.Background(), ordID.OrderID, 0.0000001, 10000000000000) + resp, err := c.EditOrderPreview(context.Background(), ordID.OrderID, testAmount, testPrice*10) if err != nil { t.Error(err) } + assert.NotEmpty(t, resp, errExpectedNonEmpty) } func TestGetAllOrders(t *testing.T) { - sharedtestvalues.SkipTestIfCredentialsUnset(t, c) assets := []string{"USD"} status := make([]string, 2) _, err := c.GetAllOrders(context.Background(), "", "", "", "", "", "", "", "", "", status, assets, 0, @@ -442,7 +419,10 @@ func TestGetAllOrders(t *testing.T) { if !errors.Is(err, errOpenPairWithOtherTypes) { t.Errorf(errExpectMismatch, err, errOpenPairWithOtherTypes) } + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) status = make([]string, 0) + assets = make([]string, 1) + assets[0] = testCrypto.String() _, err = c.GetAllOrders(context.Background(), "", "USD", "LIMIT", "SELL", "", "SPOT", "RETAIL_ADVANCED", "UNKNOWN_CONTRACT_EXPIRY_TYPE", "2", status, assets, 10, time.Time{}, time.Time{}) if err != nil { @@ -451,30 +431,43 @@ func TestGetAllOrders(t *testing.T) { } func TestGetFills(t *testing.T) { - sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - _, err := c.GetFills(context.Background(), "", "", "", 0, time.Unix(2, 2), time.Unix(1, 1)) + _, err := c.GetFills(context.Background(), "", "", "", time.Unix(2, 2), time.Unix(1, 1), 0) if !errors.Is(err, common.ErrStartAfterEnd) { t.Errorf(errExpectMismatch, err, common.ErrStartAfterEnd) } - _, err = c.GetFills(context.Background(), "", "", "", 5, time.Unix(1, 1), time.Now()) + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + _, err = c.GetFills(context.Background(), "", testPair.String(), "", time.Unix(1, 1), time.Now(), 5) + if err != nil { + t.Error(err) + } + status := []string{"OPEN"} + ordID, err := c.GetAllOrders(context.Background(), "", "", "", "", "", "", "", "", "", status, nil, 3, time.Time{}, + time.Time{}) + if err != nil { + t.Error(err) + } + if len(ordID.Orders) == 0 { + t.Skip(skipInsufficientOrders) + } + _, err = c.GetFills(context.Background(), ordID.Orders[0].OrderID, "", "", time.Time{}, time.Time{}, 5) if err != nil { t.Error(err) } } func TestGetOrderByID(t *testing.T) { - sharedtestvalues.SkipTestIfCredentialsUnset(t, c) _, err := c.GetOrderByID(context.Background(), "", "", "") if !errors.Is(err, errOrderIDEmpty) { t.Errorf(errExpectMismatch, err, errOrderIDEmpty) } + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) ordID, err := c.GetAllOrders(context.Background(), "", "", "", "", "", "", "", "", "", nil, nil, 10, time.Time{}, time.Time{}) if err != nil { t.Error(err) } if len(ordID.Orders) == 0 { - t.Skip("no orders found, skipping") + t.Skip(skipInsufficientOrders) } _, err = c.GetOrderByID(context.Background(), ordID.Orders[0].OrderID, ordID.Orders[0].ClientOID, "") if err != nil { @@ -484,7 +477,7 @@ func TestGetOrderByID(t *testing.T) { func TestGetAllPortfolios(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - resp, err := c.GetAllPortfolios(context.Background(), "") + resp, err := c.GetAllPortfolios(context.Background(), "DEFAULT") if err != nil { t.Error(err) } @@ -492,8 +485,12 @@ func TestGetAllPortfolios(t *testing.T) { } func TestCreatePortfolio(t *testing.T) { + _, err := c.CreatePortfolio(context.Background(), "") + if !errors.Is(err, errNameEmpty) { + t.Errorf(errExpectMismatch, err, errNameEmpty) + } sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) - _, err := c.CreatePortfolio(context.Background(), "GCT Test Portfolio") + _, err = c.CreatePortfolio(context.Background(), "GCT Test Portfolio") if err != nil && err.Error() != errPortfolioNameDuplicate { t.Error(err) } @@ -504,11 +501,11 @@ func TestMovePortfolioFunds(t *testing.T) { if !errors.Is(err, errPortfolioIDEmpty) { t.Errorf(errExpectMismatch, err, errPortfolioIDEmpty) } - _, err = c.MovePortfolioFunds(context.Background(), "meowPort", "woofPort", "", 0) + _, err = c.MovePortfolioFunds(context.Background(), "", "meowPort", "woofPort", 0) if !errors.Is(err, errCurrencyEmpty) { t.Errorf(errExpectMismatch, err, errCurrencyEmpty) } - _, err = c.MovePortfolioFunds(context.Background(), "meowPort", "woofPort", "BTC", 0) + _, err = c.MovePortfolioFunds(context.Background(), testCrypto.String(), "meowPort", "woofPort", 0) if !errors.Is(err, errAmountEmpty) { t.Errorf(errExpectMismatch, err, errAmountEmpty) } @@ -518,9 +515,9 @@ func TestMovePortfolioFunds(t *testing.T) { t.Error(err) } if len(portID.Portfolios) < 2 { - t.Skip("fewer than 2 portfolios found, skipping") + t.Skip(skipInsufficientPortfolios) } - _, err = c.MovePortfolioFunds(context.Background(), portID.Portfolios[0].UUID, portID.Portfolios[1].UUID, "BTC", + _, err = c.MovePortfolioFunds(context.Background(), testCrypto.String(), portID.Portfolios[0].UUID, portID.Portfolios[1].UUID, testAmount) if err != nil && err.Error() != errPortTransferInsufFunds { t.Error(err) @@ -533,44 +530,20 @@ func TestGetPortfolioByID(t *testing.T) { t.Errorf(errExpectMismatch, err, errPortfolioIDEmpty) } sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - resp, err := c.GetAllPortfolios(context.Background(), "") + portID, err := c.GetAllPortfolios(context.Background(), "") if err != nil { t.Error(err) } - if len(resp.Portfolios) == 0 { + if len(portID.Portfolios) == 0 { t.Fatal(errExpectedNonEmpty) } - _, err = c.GetPortfolioByID(context.Background(), resp.Portfolios[0].UUID) + resp, err := c.GetPortfolioByID(context.Background(), portID.Portfolios[0].UUID) if err != nil { t.Error(err) } -} - -func PortfolioTestHelper(t *testing.T, targetName string) string { - sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) - createResp, err := c.CreatePortfolio(context.Background(), targetName) - var targetID string - if err != nil { - if err.Error() != errPortfolioNameDuplicate { - t.Error(err) - } - getResp, err := c.GetAllPortfolios(context.Background(), "") - if err != nil { - t.Error(err) - } - if len(getResp.Portfolios) == 0 { - t.Fatal(errExpectedNonEmpty) - } - for i := range getResp.Portfolios { - if getResp.Portfolios[i].Name == targetName { - targetID = getResp.Portfolios[i].UUID - break - } - } - } else { - targetID = createResp.Portfolio.UUID + if resp.Breakdown.Portfolio != portID.Portfolios[0] { + t.Errorf(errExpectMismatch, resp.Breakdown.Portfolio, portID.Portfolios[0]) } - return targetID } func TestDeletePortfolio(t *testing.T) { @@ -580,7 +553,7 @@ func TestDeletePortfolio(t *testing.T) { } sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) - pID := PortfolioTestHelper(t, "GCT Test Portfolio To-Delete") + pID := portfolioTestHelper(t, "GCT Test Portfolio To-Delete") err = c.DeletePortfolio(context.Background(), pID) if err != nil { @@ -599,7 +572,7 @@ func TestEditPortfolio(t *testing.T) { } sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) - pID := PortfolioTestHelper(t, "GCT Test Portfolio To-Edit") + pID := portfolioTestHelper(t, "GCT Test Portfolio To-Edit") _, err = c.EditPortfolio(context.Background(), pID, "GCT Test Portfolio Edited") if err != nil && err.Error() != errPortfolioNameDuplicate { @@ -607,21 +580,118 @@ func TestEditPortfolio(t *testing.T) { } } -func TestGetTransactionSummary(t *testing.T) { +func TestGetFuturesBalanceSummary(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + _, err := c.GetFuturesBalanceSummary(context.Background()) + if err != nil { + t.Error(err) + } +} + +func TestGetAllFuturesPositions(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + _, err := c.GetAllFuturesPositions(context.Background()) + if err != nil { + t.Error(err) + } +} + +func TestGetFuturesPositionByID(t *testing.T) { + _, err := c.GetFuturesPositionByID(context.Background(), "") + if !errors.Is(err, errProductIDEmpty) { + t.Errorf(errExpectMismatch, err, errProductIDEmpty) + } + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + _, err = c.GetFuturesPositionByID(context.Background(), "meow") + if err != nil { + t.Error(err) + } +} + +func TestScheduleFuturesSweep(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) + curSweeps, err := c.ListFuturesSweeps(context.Background()) + if err != nil { + t.Error(err) + } + preCancel := false + if len(curSweeps.Sweeps) > 0 { + for i := range curSweeps.Sweeps { + if curSweeps.Sweeps[i].Status == "PENDING" { + if curSweeps.Sweeps[i].RequestedAmount.Value == 0.001337 { + preCancel = true + } else { + t.Skip(skipSweepPending) + } + } + } + } + if preCancel { + _, err = c.CancelPendingFuturesSweep(context.Background()) + if err != nil { + t.Error(err) + } + } + _, err = c.ScheduleFuturesSweep(context.Background(), 0.001337) + if err != nil { + t.Error(err) + } +} + +func TestListFuturesSweeps(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + _, err := c.ListFuturesSweeps(context.Background()) + if err != nil { + t.Error(err) + } +} + +func TestCancelPendingFuturesSweep(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) + curSweeps, err := c.ListFuturesSweeps(context.Background()) + if err != nil { + t.Error(err) + } + partialSkip := false + if len(curSweeps.Sweeps) > 0 { + for i := range curSweeps.Sweeps { + if curSweeps.Sweeps[i].Status == "PENDING" { + if curSweeps.Sweeps[i].RequestedAmount.Value == 0.001337 { + partialSkip = true + } else { + t.Skip(skipSweepPending) + } + } + } + } + if !partialSkip { + _, err = c.ScheduleFuturesSweep(context.Background(), 0.001337) + if err != nil { + t.Error(err) + } + + } + _, err = c.CancelPendingFuturesSweep(context.Background()) + if err != nil { + t.Error(err) + } +} + +func TestGetTransactionSummary(t *testing.T) { _, err := c.GetTransactionSummary(context.Background(), time.Unix(2, 2), time.Unix(1, 1), "", "", "") if !errors.Is(err, common.ErrStartAfterEnd) { t.Errorf(errExpectMismatch, err, common.ErrStartAfterEnd) } - _, err = c.GetTransactionSummary(context.Background(), time.Unix(1, 1), time.Now(), "", asset.Spot.Upper(), + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + resp, err := c.GetTransactionSummary(context.Background(), time.Unix(1, 1), time.Now(), "", asset.Spot.Upper(), "UNKNOWN_CONTRACT_EXPIRY_TYPE") if err != nil { t.Error(err) } + assert.NotEmpty(t, resp, errExpectedNonEmpty) } func TestCreateConvertQuote(t *testing.T) { - sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) _, err := c.CreateConvertQuote(context.Background(), "", "", "", "", 0) if !errors.Is(err, errAccountIDEmpty) { t.Errorf(errExpectMismatch, err, errAccountIDEmpty) @@ -630,39 +700,16 @@ func TestCreateConvertQuote(t *testing.T) { if !errors.Is(err, errAmountEmpty) { t.Errorf(errExpectMismatch, err, errAmountEmpty) } - accIDs, err := c.GetAllAccounts(context.Background(), 250, "") - if err != nil { - t.Error(err) - } - if len(accIDs.Accounts) == 0 { - t.Fatal(errExpectedNonEmpty) - } - var ( - fromAccID string - toAccID string - ) - for x := range accIDs.Accounts { - if accIDs.Accounts[x].Currency == "USDC" { - fromAccID = accIDs.Accounts[x].UUID - } - if accIDs.Accounts[x].Currency == "USD" { - toAccID = accIDs.Accounts[x].UUID - } - if fromAccID != "" && toAccID != "" { - break - } - } - if fromAccID == "" || toAccID == "" { - t.Skip(skipInsufSuitableAccs) - } - _, err = c.CreateConvertQuote(context.Background(), fromAccID, toAccID, "", "", 0.01) + sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) + fromAccID, toAccID := convertTestHelper(t) + resp, err := c.CreateConvertQuote(context.Background(), fromAccID, toAccID, "", "", 0.01) if err != nil { t.Error(err) } + assert.NotEmpty(t, resp, errExpectedNonEmpty) } func TestCommitConvertTrade(t *testing.T) { - sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) _, err := c.CommitConvertTrade(context.Background(), "", "", "") if !errors.Is(err, errTransactionIDEmpty) { t.Errorf(errExpectMismatch, err, errTransactionIDEmpty) @@ -671,43 +718,20 @@ func TestCommitConvertTrade(t *testing.T) { if !errors.Is(err, errAccountIDEmpty) { t.Errorf(errExpectMismatch, err, errAccountIDEmpty) } - accIDs, err := c.GetAllAccounts(context.Background(), 250, "") - if err != nil { - t.Error(err) - } - if len(accIDs.Accounts) == 0 { - t.Fatal(errExpectedNonEmpty) - } - var ( - fromAccID string - toAccID string - ) - for x := range accIDs.Accounts { - if accIDs.Accounts[x].Currency == "USDC" { - fromAccID = accIDs.Accounts[x].UUID - } - if accIDs.Accounts[x].Currency == "USD" { - toAccID = accIDs.Accounts[x].UUID - } - if fromAccID != "" && toAccID != "" { - break - } - } - if fromAccID == "" || toAccID == "" { - t.Skip(skipInsufSuitableAccs) - } + sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) + fromAccID, toAccID := convertTestHelper(t) resp, err := c.CreateConvertQuote(context.Background(), fromAccID, toAccID, "", "", 0.01) if err != nil { t.Error(err) } - _, err = c.CommitConvertTrade(context.Background(), resp.Trade.ID, fromAccID, toAccID) + resp, err = c.CommitConvertTrade(context.Background(), resp.Trade.ID, fromAccID, toAccID) if err != nil { t.Error(err) } + assert.NotEmpty(t, resp, errExpectedNonEmpty) } func TestGetConvertTradeByID(t *testing.T) { - sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) _, err := c.GetConvertTradeByID(context.Background(), "", "", "") if !errors.Is(err, errTransactionIDEmpty) { t.Errorf(errExpectMismatch, err, errTransactionIDEmpty) @@ -716,39 +740,17 @@ func TestGetConvertTradeByID(t *testing.T) { if !errors.Is(err, errAccountIDEmpty) { t.Errorf(errExpectMismatch, err, errAccountIDEmpty) } - accIDs, err := c.GetAllAccounts(context.Background(), 250, "") - if err != nil { - t.Error(err) - } - if len(accIDs.Accounts) == 0 { - t.Fatal(errExpectedNonEmpty) - } - var ( - fromAccID string - toAccID string - ) - for x := range accIDs.Accounts { - if accIDs.Accounts[x].Currency == "USDC" { - fromAccID = accIDs.Accounts[x].UUID - } - if accIDs.Accounts[x].Currency == "USD" { - toAccID = accIDs.Accounts[x].UUID - } - if fromAccID != "" && toAccID != "" { - break - } - } - if fromAccID == "" || toAccID == "" { - t.Skip(skipInsufSuitableAccs) - } + sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) + fromAccID, toAccID := convertTestHelper(t) resp, err := c.CreateConvertQuote(context.Background(), fromAccID, toAccID, "", "", 0.01) if err != nil { t.Error(err) } - _, err = c.GetConvertTradeByID(context.Background(), resp.Trade.ID, fromAccID, toAccID) + resp, err = c.GetConvertTradeByID(context.Background(), resp.Trade.ID, fromAccID, toAccID) if err != nil { t.Error(err) } + assert.NotEmpty(t, resp, errExpectedNonEmpty) } func TestGetV3Time(t *testing.T) { @@ -769,6 +771,10 @@ func TestListNotifications(t *testing.T) { } func TestGetUserByID(t *testing.T) { + _, err := c.GetUserByID(context.Background(), "") + if !errors.Is(err, errUserIDEmpty) { + t.Errorf(errExpectMismatch, err, errUserIDEmpty) + } sharedtestvalues.SkipTestIfCredentialsUnset(t, c) resp, err := c.GetCurrentUser(context.Background()) if err != nil { @@ -777,11 +783,11 @@ func TestGetUserByID(t *testing.T) { if resp == nil { t.Fatal(errExpectedNonEmpty) } - resp2, err := c.GetUserByID(context.Background(), resp.Data.ID) + resp, err = c.GetUserByID(context.Background(), resp.Data.ID) if err != nil { t.Error(err) } - assert.NotEmpty(t, resp2, errExpectedNonEmpty) + assert.NotEmpty(t, resp, errExpectedNonEmpty) } func TestGetCurrentUser(t *testing.T) { @@ -804,7 +810,15 @@ func TestGetAuthInfo(t *testing.T) { func TestUpdateUser(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) - resp, err := c.UpdateUser(context.Background(), "", "", "AUD") + oldData, err := c.GetCurrentUser(context.Background()) + if err != nil { + t.Fatal(err) + } + _, err = c.UpdateUser(context.Background(), "Name changed as per GCT testing", "Sydney", testFiat.String()) + if err != nil { + t.Error(err) + } + resp, err := c.UpdateUser(context.Background(), oldData.Data.Name, oldData.Data.TimeZone, oldData.Data.NativeCurrency) if err != nil { t.Error(err) } @@ -812,8 +826,12 @@ func TestUpdateUser(t *testing.T) { } func TestCreateWallet(t *testing.T) { + _, err := c.CreateWallet(context.Background(), "") + if !errors.Is(err, errCurrencyEmpty) { + t.Errorf(errExpectMismatch, err, errCurrencyEmpty) + } sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) - resp, err := c.CreateWallet(context.Background(), "BTC") + resp, err := c.CreateWallet(context.Background(), testCrypto.String()) if err != nil { t.Error(err) } @@ -829,7 +847,7 @@ func TestGetAllWallets(t *testing.T) { } assert.NotEmpty(t, resp, errExpectedNonEmpty) if resp.Pagination.NextStartingAfter == "" { - t.Skip("fewer than 3 wallets found, skipping pagination test") + t.Skip(skipInsufficientWallets) } pagIn.StartingAfter = resp.Pagination.NextStartingAfter resp, err = c.GetAllWallets(context.Background(), pagIn) @@ -840,12 +858,17 @@ func TestGetAllWallets(t *testing.T) { } func TestGetWalletByID(t *testing.T) { - sharedtestvalues.SkipTestIfCredentialsUnset(t, c) _, err := c.GetWalletByID(context.Background(), "", "") if !errors.Is(err, errCurrWalletConflict) { t.Errorf(errExpectMismatch, err, errCurrWalletConflict) } - resp, err := c.GetWalletByID(context.Background(), "", "BTC") + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + resp, err := c.GetWalletByID(context.Background(), "", testCrypto.String()) + if err != nil { + t.Error(err) + } + assert.NotEmpty(t, resp, errExpectedNonEmpty) + resp, err = c.GetWalletByID(context.Background(), resp.Data.ID, "") if err != nil { t.Error(err) } @@ -853,11 +876,11 @@ func TestGetWalletByID(t *testing.T) { } func TestUpdateWalletName(t *testing.T) { - sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) _, err := c.UpdateWalletName(context.Background(), "", "") if !errors.Is(err, errWalletIDEmpty) { t.Errorf(errExpectMismatch, err, errWalletIDEmpty) } + sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) wID, err := c.GetAllWallets(context.Background(), PaginationInp{}) if err != nil { t.Error(err) @@ -873,18 +896,19 @@ func TestUpdateWalletName(t *testing.T) { } func TestDeleteWallet(t *testing.T) { - sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) err := c.DeleteWallet(context.Background(), "") if !errors.Is(err, errWalletIDEmpty) { t.Errorf(errExpectMismatch, err, errWalletIDEmpty) } - wID, err := c.CreateWallet(context.Background(), "LTC") + sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) + wID, err := c.CreateWallet(context.Background(), testCrypto.String()) if err != nil { t.Error(err) } // As of now, it seems like this next step always fails. DeleteWallet only lets you delete non-primary // non-fiat wallets, but the only non-primary wallet is fiat. Trying to create a secondary wallet for // any cryptocurrency using CreateWallet simply returns the details of the existing primary wallet. + t.Skip("endpoint bugged on their end, skipping") err = c.DeleteWallet(context.Background(), wID.Data.ID) if err != nil { t.Error(err) @@ -892,12 +916,12 @@ func TestDeleteWallet(t *testing.T) { } func TestCreateAddress(t *testing.T) { - sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) _, err := c.CreateAddress(context.Background(), "", "") if !errors.Is(err, errWalletIDEmpty) { t.Errorf(errExpectMismatch, err, errWalletIDEmpty) } - wID, err := c.GetWalletByID(context.Background(), "", "BTC") + sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) + wID, err := c.GetWalletByID(context.Background(), "", testCrypto.String()) if err != nil { t.Error(err) } @@ -910,13 +934,13 @@ func TestCreateAddress(t *testing.T) { } func TestGetAllAddresses(t *testing.T) { - sharedtestvalues.SkipTestIfCredentialsUnset(t, c) var pag PaginationInp _, err := c.GetAllAddresses(context.Background(), "", pag) if !errors.Is(err, errWalletIDEmpty) { t.Errorf(errExpectMismatch, err, errWalletIDEmpty) } - wID, err := c.GetWalletByID(context.Background(), "", "BTC") + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + wID, err := c.GetWalletByID(context.Background(), "", testCrypto.String()) if err != nil { t.Error(err) } @@ -929,7 +953,6 @@ func TestGetAllAddresses(t *testing.T) { } func TestGetAddressByID(t *testing.T) { - sharedtestvalues.SkipTestIfCredentialsUnset(t, c) _, err := c.GetAddressByID(context.Background(), "", "") if !errors.Is(err, errWalletIDEmpty) { t.Errorf(errExpectMismatch, err, errWalletIDEmpty) @@ -938,7 +961,8 @@ func TestGetAddressByID(t *testing.T) { if !errors.Is(err, errAddressIDEmpty) { t.Errorf(errExpectMismatch, err, errAddressIDEmpty) } - wID, err := c.GetWalletByID(context.Background(), "", "BTC") + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + wID, err := c.GetWalletByID(context.Background(), "", testCrypto.String()) if err != nil { t.Error(err) } @@ -956,7 +980,6 @@ func TestGetAddressByID(t *testing.T) { } func TestGetAddressTransactions(t *testing.T) { - sharedtestvalues.SkipTestIfCredentialsUnset(t, c) _, err := c.GetAddressTransactions(context.Background(), "", "", PaginationInp{}) if !errors.Is(err, errWalletIDEmpty) { t.Errorf(errExpectMismatch, err, errWalletIDEmpty) @@ -965,7 +988,8 @@ func TestGetAddressTransactions(t *testing.T) { if !errors.Is(err, errAddressIDEmpty) { t.Errorf(errExpectMismatch, err, errAddressIDEmpty) } - wID, err := c.GetWalletByID(context.Background(), "", "BTC") + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + wID, err := c.GetWalletByID(context.Background(), "", testCrypto.String()) if err != nil { t.Error(err) } @@ -982,7 +1006,6 @@ func TestGetAddressTransactions(t *testing.T) { } func TestSendMoney(t *testing.T) { - sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) _, err := c.SendMoney(context.Background(), "", "", "", "", "", "", "", "", 0, false, false) if !errors.Is(err, errTransactionTypeEmpty) { t.Errorf(errExpectMismatch, err, errTransactionTypeEmpty) @@ -1003,20 +1026,21 @@ func TestSendMoney(t *testing.T) { if !errors.Is(err, errCurrencyEmpty) { t.Errorf(errExpectMismatch, err, errCurrencyEmpty) } + sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) wID, err := c.GetAllWallets(context.Background(), PaginationInp{}) if err != nil { t.Error(err) } if len(wID.Data) < 2 { - t.Skip("fewer than 2 wallets found, skipping test") + t.Skip(skipInsufficientWallets) } var ( fromID string toID string ) for i := range wID.Data { - if wID.Data[i].Currency.Name == "BTC" { - if wID.Data[i].Balance.Amount > 0 { + if wID.Data[i].Currency.Name == testCrypto.String() { + if wID.Data[i].Balance.Amount > testAmount { fromID = wID.Data[i].ID } else { toID = wID.Data[i].ID @@ -1027,23 +1051,23 @@ func TestSendMoney(t *testing.T) { } } if fromID == "" || toID == "" { - t.Skip("insufficient funds or BTC wallets, skipping") + t.Skip(skipInsufficientFundsOrWallets) } _, err = c.SendMoney(context.Background(), "transfer", wID.Data[0].ID, wID.Data[1].ID, - "BTC", "GCT Test", "123", "", "", testAmount, false, false) + testCrypto.String(), "GCT Test", "123", "", "", testAmount, false, false) if err != nil { t.Error(err) } } func TestGetAllTransactions(t *testing.T) { - sharedtestvalues.SkipTestIfCredentialsUnset(t, c) var pag PaginationInp _, err := c.GetAllTransactions(context.Background(), "", pag) if !errors.Is(err, errWalletIDEmpty) { t.Errorf(errExpectMismatch, err, errWalletIDEmpty) } - wID, err := c.GetWalletByID(context.Background(), "", "BTC") + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + wID, err := c.GetWalletByID(context.Background(), "", testCrypto.String()) if err != nil { t.Error(err) } @@ -1055,7 +1079,6 @@ func TestGetAllTransactions(t *testing.T) { } func TestGetTransactionByID(t *testing.T) { - sharedtestvalues.SkipTestIfCredentialsUnset(t, c) _, err := c.GetTransactionByID(context.Background(), "", "") if !errors.Is(err, errWalletIDEmpty) { t.Errorf(errExpectMismatch, err, errWalletIDEmpty) @@ -1064,7 +1087,8 @@ func TestGetTransactionByID(t *testing.T) { if !errors.Is(err, errTransactionIDEmpty) { t.Errorf(errExpectMismatch, err, errTransactionIDEmpty) } - wID, err := c.GetWalletByID(context.Background(), "", "BTC") + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + wID, err := c.GetWalletByID(context.Background(), "", testCrypto.String()) if err != nil { t.Error(err) } @@ -1074,7 +1098,7 @@ func TestGetTransactionByID(t *testing.T) { t.Error(err) } if len(tID.Data) == 0 { - t.Skip("no transactions found, skipping") + t.Skip(skipInsufficientTransactions) } resp, err := c.GetTransactionByID(context.Background(), wID.Data.ID, tID.Data[0].ID) if err != nil { @@ -1084,7 +1108,6 @@ func TestGetTransactionByID(t *testing.T) { } func TestFiatTransfer(t *testing.T) { - sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) _, err := c.FiatTransfer(context.Background(), "", "", "", 0, false, FiatDeposit) if !errors.Is(err, errWalletIDEmpty) { t.Errorf(errExpectMismatch, err, errWalletIDEmpty) @@ -1101,30 +1124,24 @@ func TestFiatTransfer(t *testing.T) { if !errors.Is(err, errPaymentMethodEmpty) { t.Errorf(errExpectMismatch, err, errPaymentMethodEmpty) } - wID, err := c.GetWalletByID(context.Background(), "", "AUD") - if err != nil { - t.Error(err) - } - assert.NotEmpty(t, wID, errExpectedNonEmpty) - pmID, err := c.GetAllPaymentMethods(context.Background(), PaginationInp{}) + sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) + wallets, err := c.GetAllWallets(context.Background(), PaginationInp{}) if err != nil { t.Error(err) } - if len(pmID.Data) == 0 { - t.Skip(skipPayMethodNotFound) - } - _, err = c.FiatTransfer(context.Background(), wID.Data.ID, "AUD", pmID.Data[0].FiatAccount.ID, 1, false, FiatDeposit) + assert.NotEmpty(t, wallets, errExpectedNonEmpty) + wID, pmID := transferTestHelper(t, wallets) + _, err = c.FiatTransfer(context.Background(), wID, testFiat.String(), pmID, testAmount, false, FiatDeposit) if err != nil { t.Error(err) } - _, err = c.FiatTransfer(context.Background(), wID.Data.ID, "AUD", pmID.Data[0].FiatAccount.ID, 1, false, FiatWithdrawal) + _, err = c.FiatTransfer(context.Background(), wID, testFiat.String(), pmID, testAmount, false, FiatWithdrawal) if err != nil { t.Error(err) } } func TestCommitTransfer(t *testing.T) { - sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) _, err := c.CommitTransfer(context.Background(), "", "", FiatDeposit) if !errors.Is(err, errWalletIDEmpty) { t.Errorf(errExpectMismatch, err, errWalletIDEmpty) @@ -1133,43 +1150,43 @@ func TestCommitTransfer(t *testing.T) { if !errors.Is(err, errDepositIDEmpty) { t.Errorf(errExpectMismatch, err, errDepositIDEmpty) } - wID, err := c.GetWalletByID(context.Background(), "", "AUD") - if err != nil { - t.Error(err) - } - assert.NotEmpty(t, wID, errExpectedNonEmpty) - pmID, err := c.GetAllPaymentMethods(context.Background(), PaginationInp{}) + sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) + wallets, err := c.GetAllWallets(context.Background(), PaginationInp{}) if err != nil { t.Error(err) } - if len(pmID.Data) == 0 { - t.Skip(skipPayMethodNotFound) - } - depID, err := c.FiatTransfer(context.Background(), wID.Data.ID, "AUD", pmID.Data[0].FiatAccount.ID, 1, + assert.NotEmpty(t, wallets, errExpectedNonEmpty) + wID, pmID := transferTestHelper(t, wallets) + depID, err := c.FiatTransfer(context.Background(), wID, testFiat.String(), pmID, testAmount, false, FiatDeposit) if err != nil { t.Fatal(err) } - _, err = c.CommitTransfer(context.Background(), wID.Data.ID, depID.Data.ID, FiatDeposit) + _, err = c.CommitTransfer(context.Background(), wID, depID.Data.ID, FiatDeposit) if err != nil { t.Error(err) } - _, err = c.CommitTransfer(context.Background(), wID.Data.ID, depID.Data.ID, FiatWithdrawal) + depID, err = c.FiatTransfer(context.Background(), wID, testFiat.String(), pmID, testAmount, + false, FiatWithdrawal) + if err != nil { + t.Fatal(err) + } + _, err = c.CommitTransfer(context.Background(), wID, depID.Data.ID, FiatWithdrawal) if err != nil { t.Error(err) } } func TestGetAllFiatTransfers(t *testing.T) { - sharedtestvalues.SkipTestIfCredentialsUnset(t, c) var pag PaginationInp _, err := c.GetAllFiatTransfers(context.Background(), "", pag, FiatDeposit) if !errors.Is(err, errWalletIDEmpty) { t.Errorf(errExpectMismatch, err, errWalletIDEmpty) } + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) wID, err := c.GetWalletByID(context.Background(), "", "AUD") if err != nil { - t.Error(err) + t.Fatal(err) } assert.NotEmpty(t, wID, errExpectedNonEmpty) _, err = c.GetAllFiatTransfers(context.Background(), wID.Data.ID, pag, FiatDeposit) @@ -1183,7 +1200,6 @@ func TestGetAllFiatTransfers(t *testing.T) { } func TestGetFiatTransferByID(t *testing.T) { - sharedtestvalues.SkipTestIfCredentialsUnset(t, c) _, err := c.GetFiatTransferByID(context.Background(), "", "", FiatDeposit) if !errors.Is(err, errWalletIDEmpty) { t.Errorf(errExpectMismatch, err, errWalletIDEmpty) @@ -1192,9 +1208,10 @@ func TestGetFiatTransferByID(t *testing.T) { if !errors.Is(err, errDepositIDEmpty) { t.Errorf(errExpectMismatch, err, errDepositIDEmpty) } + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) wID, err := c.GetWalletByID(context.Background(), "", "AUD") if err != nil { - t.Error(err) + t.Fatal(err) } assert.NotEmpty(t, wID, errExpectedNonEmpty) dID, err := c.GetAllFiatTransfers(context.Background(), wID.Data.ID, PaginationInp{}, FiatDeposit) @@ -1202,7 +1219,7 @@ func TestGetFiatTransferByID(t *testing.T) { t.Error(err) } if len(dID.Data) == 0 { - t.Skip("no deposits found, skipping") + t.Skip(skipInsufficientTransactions) } resp, err := c.GetFiatTransferByID(context.Background(), wID.Data.ID, dID.Data[0].ID, FiatDeposit) if err != nil { @@ -1226,11 +1243,11 @@ func TestGetAllPaymentMethods(t *testing.T) { } func TestGetPaymentMethodByID(t *testing.T) { - sharedtestvalues.SkipTestIfCredentialsUnset(t, c) _, err := c.GetPaymentMethodByID(context.Background(), "") if !errors.Is(err, errPaymentMethodEmpty) { t.Errorf(errExpectMismatch, err, errPaymentMethodEmpty) } + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) pmID, err := c.GetAllPaymentMethods(context.Background(), PaginationInp{}) if err != nil { t.Error(err) @@ -1274,7 +1291,7 @@ func TestGetPrice(t *testing.T) { if !errors.Is(err, errInvalidPriceType) { t.Errorf(errExpectMismatch, err, errInvalidPriceType) } - resp, err := c.GetPrice(context.Background(), testPair.String(), asset.Spot.Upper()) + resp, err := c.GetPrice(context.Background(), testPair.String(), asset.Spot.String()) if err != nil { t.Error(err) } @@ -2909,6 +2926,31 @@ func TestStatusToStandardStatus(t *testing.T) { */ +func TestSendHTTPRequest(t *testing.T) { + err := c.SendHTTPRequest(context.Background(), exchange.EdgeCase3, "", nil) + if err.Error() != errNoEndpointPathEdgeCase3 { + t.Errorf(errExpectMismatch, err, errNoEndpointPathEdgeCase3) + } +} + +func TestSendAuthenticatedHTTPRequest(t *testing.T) { + fc := &CoinbasePro{} + err := fc.SendAuthenticatedHTTPRequest(context.Background(), exchange.EdgeCase3, "", "", "", nil, false, nil, nil) + if !errors.Is(err, exchange.ErrCredentialsAreEmpty) { + t.Errorf(errExpectMismatch, err, exchange.ErrCredentialsAreEmpty) + } + err = c.SendAuthenticatedHTTPRequest(context.Background(), exchange.EdgeCase3, "", "", "", 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) + if err.Error() != errJsonUnsupportedChan { + t.Errorf(errExpectMismatch, err, errJsonUnsupportedChan) + } +} + func TestGetFee(t *testing.T) { _, err := c.GetFee(context.Background(), nil) if err.Error() != errFeeBuilderNil { @@ -2963,7 +3005,7 @@ func TestGetFee(t *testing.T) { t.Error(err) } if !(resp <= 0.008 && resp >= 0.0005) { - t.Errorf("expected fee range of 0.0005 and 0.008, received %v", resp) + t.Errorf(errExpectedFeeRange, 0.0005, 0.008, resp) } feeBuilder.IsMaker = true resp, err = c.GetFee(context.Background(), &feeBuilder) @@ -2971,7 +3013,7 @@ func TestGetFee(t *testing.T) { t.Error(err) } if !(resp <= 0.006 && resp >= 0) { - t.Errorf("expected fee range of 0 and 0.006, received %v", resp) + t.Errorf(errExpectedFeeRange, 0, 0.006, resp) } } @@ -2989,7 +3031,7 @@ func TestPrepareDateString(t *testing.T) { labelStart := "start_date" labelEnd := "end_date" - err := result.PrepareDateString(time.Unix(1, 1).UTC(), time.Unix(2, 2).UTC(), labelStart, labelEnd) + err := result.prepareDateString(time.Unix(1, 1).UTC(), time.Unix(2, 2).UTC(), labelStart, labelEnd) if err != nil { t.Error(err) } @@ -2998,14 +3040,14 @@ func TestPrepareDateString(t *testing.T) { } var newTime time.Time - err = result.PrepareDateString(newTime, newTime, labelStart, labelEnd) + err = result.prepareDateString(newTime, newTime, labelStart, labelEnd) if err != nil { t.Error(err) } - err = result.PrepareDateString(time.Unix(2, 2).UTC(), time.Unix(1, 1).UTC(), labelStart, labelEnd) - if err == nil { - t.Error("expected startafterend error") + err = result.prepareDateString(time.Unix(2, 2).UTC(), time.Unix(1, 1).UTC(), labelStart, labelEnd) + if !errors.Is(err, common.ErrStartAfterEnd) { + t.Errorf(errExpectMismatch, err, common.ErrStartAfterEnd) } } @@ -3020,76 +3062,76 @@ func TestPreparePagination(t *testing.T) { pagIn := PaginationInp{Limit: 1, OrderAscend: true, StartingAfter: "meow", EndingBefore: "woof"} - result.PreparePagination(pagIn) + result.preparePagination(pagIn) if fmt.Sprint(expectedResult) != fmt.Sprint(result) { t.Errorf(errExpectMismatch, result, expectedResult) } } -func TestOrderbookHelper(t *testing.T) { - t.Parallel() - req := make(InterOrderDetail, 1) - req[0][0] = true - req[0][1] = false - req[0][2] = true +// func TestOrderbookHelper(t *testing.T) { +// t.Parallel() +// req := make(InterOrderDetail, 1) +// req[0][0] = true +// req[0][1] = false +// req[0][2] = true - _, err := OrderbookHelper(req, 2) - if err == nil { - t.Error("expected unable to type assert price error") - } - req[0][0] = "egg" - _, err = OrderbookHelper(req, 2) - if err == nil { - t.Error("expected invalid ParseFloat error") - } - req[0][0] = "1.1" - _, err = OrderbookHelper(req, 2) - if err == nil { - t.Error("expected unable to type assert amount error") - } - req[0][1] = "meow" - _, err = OrderbookHelper(req, 2) - if err == nil { - t.Error("expected invalid ParseFloat error") - } - req[0][1] = "2.2" - _, err = OrderbookHelper(req, 2) - if err == nil { - t.Error("expected unable to type assert number of orders error") - } - req[0][2] = 3.3 - _, err = OrderbookHelper(req, 2) - if err != nil { - t.Error(err) - } - _, err = OrderbookHelper(req, 3) - if err == nil { - t.Error("expected unable to type assert order ID error") - } - req[0][2] = "woof" - _, err = OrderbookHelper(req, 3) - if err != nil { - t.Error(err) - } -} +// _, err := OrderbookHelper(req, 2) +// if err == nil { +// t.Error("expected unable to type assert price error") +// } +// req[0][0] = "egg" +// _, err = OrderbookHelper(req, 2) +// if err == nil { +// t.Error("expected invalid ParseFloat error") +// } +// req[0][0] = "1.1" +// _, err = OrderbookHelper(req, 2) +// if err == nil { +// t.Error("expected unable to type assert amount error") +// } +// req[0][1] = "meow" +// _, err = OrderbookHelper(req, 2) +// if err == nil { +// t.Error("expected invalid ParseFloat error") +// } +// req[0][1] = "2.2" +// _, err = OrderbookHelper(req, 2) +// if err == nil { +// t.Error("expected unable to type assert number of orders error") +// } +// req[0][2] = 3.3 +// _, err = OrderbookHelper(req, 2) +// if err != nil { +// t.Error(err) +// } +// _, err = OrderbookHelper(req, 3) +// if err == nil { +// t.Error("expected unable to type assert order ID error") +// } +// req[0][2] = "woof" +// _, err = OrderbookHelper(req, 3) +// if err != nil { +// t.Error(err) +// } +// } -func TestPrepareDSL(t *testing.T) { - t.Parallel() - var expectedResult Params - expectedResult.urlVals = map[string][]string{ - "before": {"1"}, - "limit": {"2"}, - } - var result Params +// func TestPrepareDSL(t *testing.T) { +// t.Parallel() +// var expectedResult Params +// expectedResult.urlVals = map[string][]string{ +// "before": {"1"}, +// "limit": {"2"}, +// } +// var result Params - result.urlVals = make(url.Values) +// result.urlVals = make(url.Values) - result.PrepareDSL("before", "1", 2) - if fmt.Sprint(expectedResult) != fmt.Sprint(result) { - t.Errorf(errExpectMismatch, result, expectedResult) - } -} +// result.PrepareDSL("before", "1", 2) +// if fmt.Sprint(expectedResult) != fmt.Sprint(result) { +// t.Errorf(errExpectMismatch, result, expectedResult) +// } +// } func TestFetchTradablePairs(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) @@ -3178,8 +3220,8 @@ func TestUpdateOrderbook(t *testing.T) { t.Errorf(errExpectMismatch, err, currency.ErrCurrencyPairEmpty) } _, err = c.UpdateOrderbook(context.Background(), currency.NewPairWithDelimiter("meow", "woof", "-"), asset.Spot) - if err != nil { - t.Error(err) + if err.Error() != errInvalidProductID { + t.Errorf(errExpectMismatch, err, errInvalidProductID) } _, err = c.UpdateOrderbook(context.Background(), testPair, asset.Spot) if err != nil { @@ -3246,6 +3288,7 @@ func TestSubmitOrder(t *testing.T) { t.Errorf(errExpectMismatch, err, common.ErrExchangeNameUnset) } sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) + skipTestIfLowOnFunds(t) ord.Exchange = c.Name ord.Pair = testPair ord.AssetType = asset.Spot @@ -3285,6 +3328,7 @@ func TestModifyOrder(t *testing.T) { t.Errorf(errExpectMismatch, err, order.ErrPairIsEmpty) } sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) + skipTestIfLowOnFunds(t) resp, err := c.PlaceOrder(context.Background(), strconv.FormatInt(time.Now().UnixMilli(), 18)+"GCTModifyOrderTest", testPair.String(), order.Sell.String(), "", order.Limit.String(), "", "", "", 0.0000001, 1000000000000, 0, 9999, false, time.Time{}) @@ -3316,6 +3360,7 @@ func TestCancelOrder(t *testing.T) { if err.Error() != errOrder0CancelFail { t.Errorf(errExpectMismatch, err, errOrder0CancelFail) } + skipTestIfLowOnFunds(t) resp, err := c.PlaceOrder(context.Background(), strconv.FormatInt(time.Now().UnixMilli(), 18)+"GCTCancelOrderTest", testPair.String(), order.Sell.String(), "", order.Limit.String(), "", "", "", 0.0000001, 1000000000000, 0, 9999, false, time.Time{}) @@ -3340,6 +3385,7 @@ func TestCancelBatchOrders(t *testing.T) { t.Errorf(errExpectMismatch, err, errIDNotSet) } sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) + skipTestIfLowOnFunds(t) resp, err := c.PlaceOrder(context.Background(), strconv.FormatInt(time.Now().UnixMilli(), 18)+"GCTCancelBatchOrdersTest", testPair.String(), order.Sell.String(), "", order.Limit.String(), "", "", "", 0.0000001, 1000000000000, 0, 9999, false, time.Time{}) @@ -3364,6 +3410,7 @@ func TestCancelAllOrders(t *testing.T) { t.Errorf(errExpectMismatch, err, order.ErrPairIsEmpty) } sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) + skipTestIfLowOnFunds(t) _, err = c.PlaceOrder(context.Background(), strconv.FormatInt(time.Now().UnixMilli(), 18)+"GCTCancelAllOrdersTest", testPair.String(), order.Sell.String(), "", order.Limit.String(), "", "", "", 0.0000001, 1000000000000, 0, 9999, false, time.Time{}) @@ -3386,7 +3433,7 @@ func TestGetOrderInfo(t *testing.T) { t.Fatal(err) } if len(ordID.Orders) == 0 { - t.Fatal(errExpectedNonEmpty) + t.Skip(skipInsufficientOrders) } _, err = c.GetOrderInfo(context.Background(), ordID.Orders[0].OrderID, testPair, asset.Spot) if err != nil { @@ -3475,6 +3522,7 @@ func TestWithdrawFiatFunds(t *testing.T) { if len(wallets.Data) == 0 { t.Fatal(errExpectedNonEmpty) } + req.WalletID = "" for i := range wallets.Data { if wallets.Data[i].Currency.Name == currency.AUD.String() && wallets.Data[i].Balance.Amount > testAmount { req.WalletID = wallets.Data[i].ID @@ -3655,6 +3703,103 @@ func TestUpdateOrderExecutionLimits(t *testing.T) { } } +func skipTestIfLowOnFunds(t *testing.T) { + accounts, err := c.GetAllAccounts(context.Background(), 250, "") + if err != nil { + t.Error(err) + } + if len(accounts.Accounts) == 0 { + t.Fatal(errExpectedNonEmpty) + } + var hasValidFunds bool + for i := range accounts.Accounts { + if accounts.Accounts[i].Currency == testCrypto.String() && accounts.Accounts[i].AvailableBalance.Value > testAmount*100 { + hasValidFunds = true + } + } + if !hasValidFunds { + t.Skip(skipInsufficientFunds) + } +} + +func portfolioTestHelper(t *testing.T, targetName string) string { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) + createResp, err := c.CreatePortfolio(context.Background(), targetName) + var targetID string + if err != nil { + if err.Error() != errPortfolioNameDuplicate { + t.Error(err) + } + getResp, err := c.GetAllPortfolios(context.Background(), "") + if err != nil { + t.Error(err) + } + if len(getResp.Portfolios) == 0 { + t.Fatal(errExpectedNonEmpty) + } + for i := range getResp.Portfolios { + if getResp.Portfolios[i].Name == targetName { + targetID = getResp.Portfolios[i].UUID + break + } + } + } else { + targetID = createResp.Portfolio.UUID + } + return targetID +} + +func convertTestHelper(t *testing.T) (string, string) { + accIDs, err := c.GetAllAccounts(context.Background(), 250, "") + if err != nil { + t.Error(err) + } + if len(accIDs.Accounts) == 0 { + t.Fatal(errExpectedNonEmpty) + } + var ( + fromAccID string + toAccID string + ) + for x := range accIDs.Accounts { + if accIDs.Accounts[x].Currency == "USDC" { + fromAccID = accIDs.Accounts[x].UUID + } + if accIDs.Accounts[x].Currency == "USD" { + toAccID = accIDs.Accounts[x].UUID + } + if fromAccID != "" && toAccID != "" { + break + } + } + if fromAccID == "" || toAccID == "" { + t.Skip(skipInsufSuitableAccs) + } + return fromAccID, toAccID +} + +func transferTestHelper(t *testing.T, wallets GetAllWalletsResponse) (string, string) { + var hasValidFunds bool + var wID string + for i := range wallets.Data { + if wallets.Data[i].Currency.Code == testFiat.String() && wallets.Data[i].Balance.Amount > 10 { + hasValidFunds = true + wID = wallets.Data[i].ID + } + } + if !hasValidFunds { + t.Skip(skipInsufficientFunds) + } + pmID, err := c.GetAllPaymentMethods(context.Background(), PaginationInp{}) + if err != nil { + t.Error(err) + } + if len(pmID.Data) == 0 { + t.Skip(skipPayMethodNotFound) + } + return wID, pmID.Data[0].FiatAccount.ID +} + // 8837708 143.0 ns/op 24 B/op 5 allocs/op // func BenchmarkXxx(b *testing.B) { // for x := 0; x < b.N; x++ { diff --git a/exchanges/coinbasepro/coinbasepro_types.go b/exchanges/coinbasepro/coinbasepro_types.go index db3b8bcf165..a56f9879787 100644 --- a/exchanges/coinbasepro/coinbasepro_types.go +++ b/exchanges/coinbasepro/coinbasepro_types.go @@ -124,6 +124,7 @@ type Product struct { QuoteDisplaySymbol string `json:"quote_display_symbol"` ViewOnly bool `json:"view_only"` PriceIncrement convert.StringToFloat64 `json:"price_increment"` + DisplayName string `json:"display_name"` FutureProductDetails struct { Venue string `json:"venue"` ContractCode string `json:"contract_code"` @@ -139,6 +140,7 @@ type Product struct { OpenInterest convert.StringToFloat64 `json:"open_interest"` FundingRate convert.StringToFloat64 `json:"funding_rate"` FundingTime time.Time `json:"funding_time"` + MaxLeverage convert.StringToFloat64 `json:"max_leverage"` } `json:"perpetual_details"` ContractDisplayName string `json:"contract_display_name"` } `json:"future_product_details"` @@ -434,6 +436,64 @@ type DetailedPortfolioResponse struct { } `json:"breakdown"` } +// FuturesBalanceSummary contains information on futures balances, returned by +// GetFuturesBalanceSummary +type FuturesBalanceSummary struct { + BalanceSummary struct { + FuturesBuyingPower ValCur `json:"futures_buying_power"` + TotalUSDBalance ValCur `json:"total_usd_balance"` + CBIUSDBalance ValCur `json:"cbi_usd_balance"` + CFMUSDBalance ValCur `json:"cfm_usd_balance"` + TotalOpenOrdersHoldAmount ValCur `json:"total_open_orders_hold_amount"` + UnrealizedPNL ValCur `json:"unrealized_pnl"` + DailyRealizedPNL ValCur `json:"daily_realized_pnl"` + InitialMargin ValCur `json:"initial_margin"` + AvailableMargin ValCur `json:"available_margin"` + LiquidationThreshold ValCur `json:"liquidation_threshold"` + LiquidationBufferAmount ValCur `json:"liquidation_buffer_amount"` + LiquidationBufferPercentage float64 `json:"liquidation_buffer_percentage,string"` + } `json:"balance_summary"` +} + +// FuturesPosition contains information on a single futures position, returned by +// GetFuturesPositionByID and used as a sub-struct in the type AllFuturesPositions +type FuturesPosition struct { + // This may belong in a struct of its own called "position", requring a bit + // more abstraction, but for the moment I'll assume it doesn't + ProductID string `json:"product_id"` + ExpirationTime time.Time `json:"expiration_time"` + Side string `json:"side"` + NumberOfContracts float64 `json:"number_of_contracts,string"` + CurrentPrice float64 `json:"current_price,string"` + AverageEntryPrice float64 `json:"avg_entry_price,string"` + UnrealizedPNL float64 `json:"unrealized_pnl,string"` + DailyRealizedPNL float64 `json:"daily_realized_pnl,string"` +} + +// AllFuturesPositions contains information on all futures positions, returned 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 +type SuccessBool struct { + Success bool `json:"success"` +} + +// ListFuturesSweepsResponse contains information on pending and processing sweep +// requests. Returned 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"` +} + // TransactionSummary contains a summary of transaction fees, volume, and the like. Returned // by GetTransactionSummary type TransactionSummary struct { @@ -471,6 +531,13 @@ type GetAllOrdersResp struct { Cursor string `json:"cursor"` } +// LinkStruct is a sub-struct storing information on links, used in FeeStruct and +// ConvertResponse +type LinkStruct struct { + Text string `json:"text"` + URL string `json:"url"` +} + // FeeStruct is a sub-struct storing information on fees, used in ConvertResponse type FeeStruct struct { Title string `json:"title"` @@ -478,12 +545,9 @@ type FeeStruct struct { Amount ValCur `json:"amount"` Label string `json:"label"` Disclosure struct { - Title string `json:"title"` - Description string `json:"description"` - Link struct { - Text string `json:"text"` - URL string `json:"url"` - } `json:"link"` + Title string `json:"title"` + Description string `json:"description"` + Link LinkStruct `json:"link"` } `json:"disclosure"` } @@ -529,11 +593,8 @@ type ConvertResponse struct { SourceToFiat AmScale `json:"source_to_fiat"` } `json:"unit_price"` UserWarnings []struct { - ID string `json:"id"` - Link struct { - Text string `json:"text"` - URL string `json:"url"` - } `json:"link"` + ID string `json:"id"` + Link LinkStruct `json:"link"` Context struct { Details []string `json:"details"` Title string `json:"title"` @@ -652,7 +713,13 @@ type ListNotificationsResponse struct { } `json:"data"` } -// UserResponse holds information on a user, returned by GetUseByID and GetCurrentUser +// CodeName holds a code and a name, used in UserResponse and CoinbaseAccounts +type CodeName struct { + Code string `json:"code"` + Name string `json:"name"` +} + +// UserResponse holds information on a user, returned by GetUserByID and GetCurrentUser type UserResponse struct { Data struct { ID string `json:"id"` @@ -674,10 +741,7 @@ type UserResponse struct { Name string `json:"name"` IsInEurope bool `json:"is_in_europe"` } `json:"country"` - Nationality struct { - Code string `json:"code"` - Name string `json:"name"` - } `json:"nationality"` + Nationality CodeName `json:"nationality"` RegionSupportsFiatTransfers bool `json:"region_supports_fiat_transfers"` RegionSupportsCryptoToCryptoTransfers bool `json:"region_supports_crypto_to_crypto_transfers"` CreatedAt time.Time `json:"created_at"` @@ -722,22 +786,20 @@ type WalletData struct { Primary bool `json:"primary"` Type string `json:"type"` Currency struct { - Code string `json:"code"` - Name string `json:"name"` - Color string `json:"color"` - SortIndex int32 `json:"sort_index"` - Exponent int32 `json:"exponent"` - Type string `json:"type"` - AddressRegex string `json:"address_regex"` - AssetID string `json:"asset_id"` - DestinationTagName string `json:"destination_tag_name"` - DestinationTagRegex string `json:"destination_tag_regex"` - Slug string `json:"slug"` + Code string `json:"code"` + Name string `json:"name"` + Color string `json:"color"` + SortIndex int32 `json:"sort_index"` + Exponent int32 `json:"exponent"` + Type string `json:"type"` + AddressRegex string `json:"address_regex"` + AssetID string `json:"asset_id"` + DestinationTagName string `json:"destination_tag_name"` + DestinationTagRegex string `json:"destination_tag_regex"` + Slug string `json:"slug"` + Rewards interface{} `json:"rewards"` } `json:"currency"` - Balance struct { - Amount float64 `json:"amount,string"` - Currency string `json:"currency"` - } `json:"balance"` + Balance AmCur `json:"balance"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` Resource string `json:"resource"` @@ -758,21 +820,30 @@ type GetAllWalletsResponse struct { Data []WalletData `json:"data"` } +// AddressInfo holds an address and a destination tag, used in AddressData and +type AddressInfo struct { + Address string `json:"address"` + DestinationTag string `json:"destination_tag"` +} + +// TitleSubtitle holds a title and a subtitle, used in AddressData and TransactionData +type TitleSubtitle struct { + Title string `json:"title"` + Subtitle string `json:"subtitle"` +} + // AddressData holds address information, used in GenAddrResponse and GetAllAddrResponse type AddressData struct { - ID string `json:"id"` - Address string `json:"address"` - AddressInfo struct { - Address string `json:"address"` - DestinationTag string `json:"destination_tag"` - } `json:"address_info"` - Name string `json:"name"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` - Network string `json:"network"` - URIScheme string `json:"uri_scheme"` - Resource string `json:"resource"` - ResourcePath string `json:"resource_path"` + ID string `json:"id"` + Address string `json:"address"` + AddressInfo AddressInfo `json:"address_info"` + Name string `json:"name"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + Network string `json:"network"` + URIScheme string `json:"uri_scheme"` + Resource string `json:"resource"` + ResourcePath string `json:"resource_path"` Warnings []struct { Type string `json:"type"` Title string `json:"title"` @@ -796,11 +867,8 @@ type AddressData struct { } `json:"share_address_copy"` ReceiveSubtitle string `json:"receive_subtitle"` InlineWarning struct { - Text string `json:"text"` - Tooltip struct { - Title string `json:"title"` - Subtitle string `json:"subtitle"` - } `json:"tooltip"` + Text string `json:"text"` + Tooltip TitleSubtitle `json:"tooltip"` } `json:"inline_warning"` } @@ -844,10 +912,7 @@ type TransactionData struct { Commission float64 `json:"commission,string"` OrderSide string `json:"order_side"` } `json:"advanced_trade_fill"` - Details struct { - Title string `json:"title"` - Subtitle string `json:"subtitle"` - } `json:"details"` + Details TitleSubtitle `json:"details"` Network struct { Status string `json:"status"` Hash string `json:"hash"` @@ -1186,20 +1251,12 @@ type PaymentMethod struct { InstantBuy []LimitStruct `json:"instant_buy"` Sell []LimitStruct `json:"sell"` } `json:"limits"` - AllowBuy bool `json:"allow_buy"` - AllowSell bool `json:"allow_sell"` - AllowDeposit bool `json:"allow_deposit"` - AllowWithdraw bool `json:"allow_withdraw"` - FiatAccount struct { - ID string `json:"id"` - Resource string `json:"resource"` - ResourcePath string `json:"resource_path"` - } `json:"fiat_account"` - CryptoAccount struct { - ID string `json:"id"` - Resource string `json:"resource"` - ResourcePath string `json:"resource_path"` - } `json:"crypto_account"` + AllowBuy bool `json:"allow_buy"` + AllowSell bool `json:"allow_sell"` + AllowDeposit bool `json:"allow_deposit"` + AllowWithdraw bool `json:"allow_withdraw"` + FiatAccount IDResource `json:"fiat_account"` + CryptoAccount IDResource `json:"crypto_account"` AvailableBalance struct { Amount float64 `json:"amount"` Currency string `json:"currency"` @@ -1222,10 +1279,7 @@ type PaymentMethod struct { BankName string `json:"bank_name"` BranchName string `json:"branch_name"` IconURL string `json:"icon_url"` - Balance struct { - Amount float64 `json:"amount,string"` - Currency string `json:"currency"` - } `json:"balance"` + Balance AmCur `json:"balance"` } `json:"picker_data"` HoldBusinessDays int64 `json:"hold_business_days"` HoldDays int64 `json:"hold_days"` @@ -1267,29 +1321,23 @@ type CoinbaseAccounts struct { AvailableOnConsumer bool `json:"available_on_consumer"` Ready bool `json:"ready"` WireDepositInformation struct { - AccountNumber string `json:"account_number"` - RoutingNumber string `json:"routing_number"` - BankName string `json:"bank_name"` - BankAddress string `json:"bank_address"` - BankCountry struct { - Name string `json:"name"` - Code string `json:"code"` - } `json:"bank_country"` - AccountName string `json:"account_name"` - AccountAddress string `json:"account_address"` - Reference string `json:"reference"` + AccountNumber string `json:"account_number"` + RoutingNumber string `json:"routing_number"` + BankName string `json:"bank_name"` + BankAddress string `json:"bank_address"` + BankCountry CodeName `json:"bank_country"` + AccountName string `json:"account_name"` + AccountAddress string `json:"account_address"` + Reference string `json:"reference"` } `json:"wire_deposit_information"` SwiftDepositInformation struct { - AccountNumber string `json:"account_number"` - BankName string `json:"bank_name"` - BankAddress string `json:"bank_address"` - BankCountry struct { - Name string `json:"name"` - Code string `json:"code"` - } `json:"bank_country"` - AccountName string `json:"account_name"` - AccountAddress string `json:"account_address"` - Reference string `json:"reference"` + AccountNumber string `json:"account_number"` + BankName string `json:"bank_name"` + BankAddress string `json:"bank_address"` + BankCountry CodeName `json:"bank_country"` + AccountName string `json:"account_name"` + AccountAddress string `json:"account_address"` + Reference string `json:"reference"` } `json:"swift_deposit_information"` SepaDepositInformation struct { Iban string `json:"iban"` @@ -1634,19 +1682,13 @@ type GetAddressResponse struct { VASPID string `json:"vasp_id"` } -// To is part of the struct expected by the exchange for the AddAddresses function -type To struct { - Address string `json:"address"` - DestinationTag string `json:"destination_tag"` -} - // AddAddressRequest is the struct expected by the exchange for the AddAddresses function type AddAddressRequest struct { - Currency string `json:"currency"` - To `json:"to"` - Label string `json:"label"` - VerifiedSelfHosted bool `json:"is_verified_self_hosted_wallet"` - VaspID string `json:"vasp_id"` + Currency string `json:"currency"` + AddressInfo AddressInfo `json:"to"` + Label string `json:"label"` + VerifiedSelfHosted bool `json:"is_verified_self_hosted_wallet"` + VaspID string `json:"vasp_id"` } // AddAddressResponse contains information on the addresses just added, returned by @@ -1674,20 +1716,17 @@ type AddAddressResponse struct { // CryptoAddressResponse contains information on the one-time address generated for // depositing crypto, returned by GenerateCryptoAddress type CryptoAddressResponse struct { - ID string `json:"id"` - Address string `json:"address"` - AddressInfo struct { - Address string `json:"address"` - DestinationTag string `json:"destination_tag"` - } `json:"address_info"` - Name string `json:"name"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` - Network string `json:"network"` - URIScheme string `json:"uri_scheme"` - Resource string `json:"resource"` - ResourcePath string `json:"resource_path"` - ExchangeDepositAddress bool `json:"exchange_deposit_address"` + ID string `json:"id"` + Address string `json:"address"` + AddressInfo AddressInfo `json:"address_info"` + Name string `json:"name"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + Network string `json:"network"` + URIScheme string `json:"uri_scheme"` + Resource string `json:"resource"` + ResourcePath string `json:"resource_path"` + ExchangeDepositAddress bool `json:"exchange_deposit_address"` Warnings []struct { Title string `json:"title"` Details string `json:"details"` diff --git a/exchanges/coinbasepro/coinbasepro_wrapper.go b/exchanges/coinbasepro/coinbasepro_wrapper.go index 2e7de766768..6b24d315b9d 100644 --- a/exchanges/coinbasepro/coinbasepro_wrapper.go +++ b/exchanges/coinbasepro/coinbasepro_wrapper.go @@ -719,7 +719,7 @@ func (c *CoinbasePro) SubmitOrder(ctx context.Context, s *order.Submit) (*order. if s.RetrieveFees { time.Sleep(s.RetrieveFeeDelay) - feeResp, err := c.GetOrderByID(ctx, resp.OrderID, "", s.ClientOrderID) + feeResp, err := c.GetOrderByID(ctx, resp.OrderID, s.ClientOrderID, "") if err != nil { return nil, err } @@ -742,7 +742,7 @@ func (c *CoinbasePro) ModifyOrder(ctx context.Context, m *order.Modify) (*order. if err != nil { return nil, err } - if !success { + if !success.Success { return nil, errOrderModFailNoErr } @@ -982,13 +982,13 @@ func (c *CoinbasePro) GetOrderInfo(ctx context.Context, orderID string, pair cur return nil, err } - fillData, err := c.GetFills(ctx, orderID, "", "", 2<<15-1, time.Time{}, time.Now()) + fillData, err := c.GetFills(ctx, orderID, "", "", time.Time{}, time.Now(), 2<<15-1) if err != nil { return nil, err } cursor := fillData.Cursor for cursor != "" { - tempFillData, err := c.GetFills(ctx, orderID, "", cursor, 2<<15-1, time.Time{}, time.Now()) + tempFillData, err := c.GetFills(ctx, orderID, "", cursor, time.Time{}, time.Now(), 2<<15-1) if err != nil { return nil, err } From 7dd45897576490057abbc7fa6e8acb25b9cdabb0 Mon Sep 17 00:00:00 2001 From: Samuel Reid <43227667+cranktakular@users.noreply.github.com> Date: Tue, 16 Jan 2024 12:57:44 +1100 Subject: [PATCH 17/79] Coinbase REST revamp finished --- exchanges/coinbasepro/coinbasepro.go | 981 +------- exchanges/coinbasepro/coinbasepro_test.go | 2291 +++--------------- exchanges/coinbasepro/coinbasepro_types.go | 760 +----- exchanges/coinbasepro/coinbasepro_wrapper.go | 514 ++-- exchanges/coinbasepro/ratelimit.go | 18 +- exchanges/protocol/features.go | 1 + 6 files changed, 662 insertions(+), 3903 deletions(-) diff --git a/exchanges/coinbasepro/coinbasepro.go b/exchanges/coinbasepro/coinbasepro.go index 8eb11b7a53b..5b88891a350 100644 --- a/exchanges/coinbasepro/coinbasepro.go +++ b/exchanges/coinbasepro/coinbasepro.go @@ -10,7 +10,6 @@ import ( "net/http" "net/url" "strconv" - "strings" "time" "github.com/thrasher-corp/gocryptotrader/common" @@ -68,8 +67,6 @@ const ( coinbasePrices = "prices" coinbaseTime = "time" - Version3 Version = true - Version2 Version = false FiatDeposit FiatTransferType = false FiatWithdrawal FiatTransferType = true pageNone = "" @@ -119,6 +116,7 @@ var ( errNameEmpty = errors.New("name cannot be empty") errPortfolioIDEmpty = errors.New("portfolio id cannot be empty") errFeeTypeNotSupported = errors.New("fee type not supported") + errUnknownEndpointLimit = errors.New("unknown endpoint limit") ) // GetAllAccounts returns information on all trading accounts associated with the API key @@ -133,7 +131,7 @@ func (c *CoinbasePro) GetAllAccounts(ctx context.Context, limit uint8, cursor st pathParams := common.EncodeURLValues("", params.urlVals) return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - coinbaseV3+coinbaseAccounts, pathParams, nil, Version3, &resp, nil) + coinbaseV3+coinbaseAccounts, pathParams, nil, true, &resp, nil) } // GetAccountByID returns information for a single account @@ -145,7 +143,7 @@ func (c *CoinbasePro) GetAccountByID(ctx context.Context, accountID string) (*Ac resp := OneAccountResponse{} return &resp.Account, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - path, "", nil, Version3, &resp, nil) + path, "", nil, true, &resp, nil) } // GetBestBidAsk returns the best bid/ask for all products. Can be filtered to certain products @@ -164,7 +162,7 @@ func (c *CoinbasePro) GetBestBidAsk(ctx context.Context, products []string) (Bes var resp BestBidAsk return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - coinbaseV3+coinbaseBestBidAsk, pathParams, nil, Version3, &resp, nil) + coinbaseV3+coinbaseBestBidAsk, pathParams, nil, true, &resp, nil) } // GetProductBook returns a list of bids/asks for a single product @@ -182,7 +180,7 @@ func (c *CoinbasePro) GetProductBook(ctx context.Context, productID string, limi var resp ProductBookResponse return resp.Pricebook, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - coinbaseV3+coinbaseProductBook, pathParams, nil, Version3, &resp, nil) + coinbaseV3+coinbaseProductBook, pathParams, nil, true, &resp, nil) } // GetAllProducts returns information on all currency pairs that are available for trading @@ -211,7 +209,7 @@ func (c *CoinbasePro) GetAllProducts(ctx context.Context, limit, offset int32, p var products AllProducts return products, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - coinbaseV3+coinbaseProducts, pathParams, nil, Version3, &products, nil) + coinbaseV3+coinbaseProducts, pathParams, nil, true, &products, nil) } // GetProductByID returns information on a single specified currency pair @@ -225,7 +223,7 @@ func (c *CoinbasePro) GetProductByID(ctx context.Context, productID string) (*Pr resp := Product{} return &resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - path, "", nil, Version3, &resp, nil) + path, "", nil, true, &resp, nil) } // GetHistoricRates returns historic rates for a product. Rates are returned in @@ -258,7 +256,7 @@ func (c *CoinbasePro) GetHistoricRates(ctx context.Context, productID, granulari path := fmt.Sprintf("%s%s/%s/%s", coinbaseV3, coinbaseProducts, productID, coinbaseCandles) err := c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - path, pathParams, nil, Version3, &resp, nil) + path, pathParams, nil, true, &resp, nil) return resp, err } @@ -283,7 +281,7 @@ func (c *CoinbasePro) GetTicker(ctx context.Context, productID string, limit uin var resp Ticker return &resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - path, pathParams, nil, Version3, &resp, nil) + path, pathParams, nil, true, &resp, nil) } // PlaceOrder places either a limit, market, or stop order @@ -358,7 +356,7 @@ func (c *CoinbasePro) PlaceOrder(ctx context.Context, clientOID, productID, side return &resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, - coinbaseV3+coinbaseOrders, "", req, Version3, &resp, nil) + coinbaseV3+coinbaseOrders, "", req, true, &resp, nil) } // CancelOrders cancels orders by orderID @@ -373,7 +371,7 @@ func (c *CoinbasePro) CancelOrders(ctx context.Context, orderIDs []string) (Canc req := map[string]interface{}{"order_ids": orderIDs} return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, path, "", - req, Version3, &resp, nil) + req, true, &resp, nil) } // EditOrder edits an order to a new size or price. Only limit orders with a good-till-cancelled time @@ -394,7 +392,7 @@ func (c *CoinbasePro) EditOrder(ctx context.Context, orderID string, size, price "price": strconv.FormatFloat(price, 'f', -1, 64)} return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, path, "", - req, Version3, &resp, nil) + req, true, &resp, nil) } // EditOrderPreview simulates an edit order request, to preview the result. Only limit orders with a @@ -415,7 +413,7 @@ func (c *CoinbasePro) EditOrderPreview(ctx context.Context, orderID string, size var resp *EditOrderPreviewResp return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, path, "", - req, Version3, &resp, nil) + req, true, &resp, nil) } // GetAllOrders lists orders, filtered by their status @@ -469,7 +467,7 @@ func (c *CoinbasePro) GetAllOrders(ctx context.Context, productID, userNativeCur path := fmt.Sprintf("%s%s/%s/%s", coinbaseV3, coinbaseOrders, coinbaseHistorical, coinbaseBatch) return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, - pathParams, nil, Version3, &resp, nil) + pathParams, nil, true, &resp, nil) } // GetFills returns information of recent fills on the specified profile @@ -497,7 +495,7 @@ func (c *CoinbasePro) GetFills(ctx context.Context, orderID, productID, cursor s path := fmt.Sprintf("%s%s/%s/%s", coinbaseV3, coinbaseOrders, coinbaseHistorical, coinbaseFills) return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, - pathParams, nil, Version3, &resp, nil) + pathParams, nil, true, &resp, nil) } // GetOrderByID returns a single order by order id. @@ -514,7 +512,7 @@ func (c *CoinbasePro) GetOrderByID(ctx context.Context, orderID, clientID, userN 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, Version3, &resp, nil) + return &resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, pathParams, nil, true, &resp, nil) } // GetAllPortfolios returns a list of portfolios associated with the user @@ -531,7 +529,7 @@ func (c *CoinbasePro) GetAllPortfolios(ctx context.Context, portfolioType string pathParams := common.EncodeURLValues("", params.urlVals) return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - coinbaseV3+coinbasePortfolios, pathParams, nil, Version3, &resp, nil) + coinbaseV3+coinbasePortfolios, pathParams, nil, true, &resp, nil) } // CreatePortfolio creates a new portfolio @@ -545,7 +543,7 @@ func (c *CoinbasePro) CreatePortfolio(ctx context.Context, name string) (SimpleP req := map[string]interface{}{"name": name} return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, - coinbaseV3+coinbasePortfolios, "", req, Version3, &resp, nil) + coinbaseV3+coinbasePortfolios, "", req, true, &resp, nil) } // MovePortfolioFunds transfers funds between portfolios @@ -569,7 +567,7 @@ func (c *CoinbasePro) MovePortfolioFunds(ctx context.Context, currency, from, to path := fmt.Sprintf("%s%s/%s", coinbaseV3, coinbasePortfolios, coinbaseMoveFunds) return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, - path, "", req, Version3, &resp, nil) + path, "", req, true, &resp, nil) } // GetPortfolioByID provides detailed information on a single portfolio @@ -583,7 +581,7 @@ func (c *CoinbasePro) GetPortfolioByID(ctx context.Context, portfolioID string) var resp DetailedPortfolioResponse return &resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - path, "", nil, Version3, &resp, nil) + path, "", nil, true, &resp, nil) } // DeletePortfolio deletes a portfolio @@ -595,7 +593,7 @@ func (c *CoinbasePro) DeletePortfolio(ctx context.Context, portfolioID string) e path := fmt.Sprintf("%s%s/%s", coinbaseV3, coinbasePortfolios, portfolioID) return c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodDelete, path, "", nil, - Version3, nil, nil) + true, nil, nil) } // EditPortfolio edits the name of a portfolio @@ -614,7 +612,7 @@ func (c *CoinbasePro) EditPortfolio(ctx context.Context, portfolioID, name strin path := fmt.Sprintf("%s%s/%s", coinbaseV3, coinbasePortfolios, portfolioID) return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPut, - path, "", req, Version3, &resp, nil) + path, "", req, true, &resp, nil) } // GetFuturesBalanceSummary returns information on balances related to Coinbase Financial Markets @@ -625,7 +623,7 @@ func (c *CoinbasePro) GetFuturesBalanceSummary(ctx context.Context) (FuturesBala path := fmt.Sprintf("%s%s/%s", coinbaseV3, coinbaseCFM, coinbaseBalanceSummary) return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - path, "", nil, Version3, &resp, nil) + path, "", nil, true, &resp, nil) } // GetAllFuturesPositions returns a list of all open positions in CFM futures products @@ -635,7 +633,7 @@ func (c *CoinbasePro) GetAllFuturesPositions(ctx context.Context) (AllFuturesPos path := fmt.Sprintf("%s%s/%s", coinbaseV3, coinbaseCFM, coinbasePositions) return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - path, "", nil, Version3, &resp, nil) + path, "", nil, true, &resp, nil) } // GetFuturesPositionByID returns information on a single open position in CFM futures products @@ -649,7 +647,7 @@ func (c *CoinbasePro) GetFuturesPositionByID(ctx context.Context, productID stri path := fmt.Sprintf("%s%s/%s/%s", coinbaseV3, coinbaseCFM, coinbasePositions, productID) return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - path, "", nil, Version3, &resp, nil) + path, "", nil, true, &resp, nil) } // ScheduleFuturesSweep schedules a sweep of funds from a CFTC-regulated futures account to a @@ -669,7 +667,7 @@ func (c *CoinbasePro) ScheduleFuturesSweep(ctx context.Context, amount float64) var resp SuccessBool return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, - path, "", req, Version3, &resp, nil) + path, "", req, true, &resp, nil) } // ListFuturesSweeps returns information on pending and/or processing requests to sweep funds @@ -679,7 +677,7 @@ func (c *CoinbasePro) ListFuturesSweeps(ctx context.Context) (ListFuturesSweepsR path := fmt.Sprintf("%s%s/%s", coinbaseV3, coinbaseCFM, coinbaseSweeps) return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - path, "", nil, Version3, &resp, nil) + path, "", nil, true, &resp, nil) } // CancelPendingFuturesSweep cancels a pending sweep request @@ -689,7 +687,7 @@ func (c *CoinbasePro) CancelPendingFuturesSweep(ctx context.Context) (SuccessBoo var resp SuccessBool return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodDelete, - path, "", nil, Version3, &resp, nil) + path, "", nil, true, &resp, nil) } // GetTransactionSummary returns a summary of transactions with fee tiers, total volume, @@ -717,7 +715,7 @@ func (c *CoinbasePro) GetTransactionSummary(ctx context.Context, startDate, endD var resp TransactionSummary return &resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - coinbaseV3+coinbaseTransactionSummary, pathParams, nil, Version3, &resp, nil) + coinbaseV3+coinbaseTransactionSummary, pathParams, nil, true, &resp, nil) } // CreateConvertQuote creates a quote for a conversion between two currencies. The trade_id returned @@ -739,7 +737,7 @@ func (c *CoinbasePro) CreateConvertQuote(ctx context.Context, from, to, userInce "amount": strconv.FormatFloat(amount, 'f', -1, 64), "trade_incentive_metadata": tIM} return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, path, - "", req, Version3, &resp, nil) + "", req, true, &resp, nil) } // CommitConvertTrade commits a conversion between two currencies, using the trade_id returned @@ -758,7 +756,7 @@ func (c *CoinbasePro) CommitConvertTrade(ctx context.Context, tradeID, from, to req := map[string]interface{}{"from_account": from, "to_account": to} return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, path, - "", req, Version3, &resp, nil) + "", req, true, &resp, nil) } // GetConvertTradeByID returns information on a conversion between two currencies @@ -776,7 +774,7 @@ func (c *CoinbasePro) GetConvertTradeByID(ctx context.Context, tradeID, from, to req := map[string]interface{}{"from_account": from, "to_account": to} return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, - "", req, Version3, &resp, nil) + "", req, true, &resp, nil) } // GetV3Time returns the current server time, calling V3 of the API @@ -784,7 +782,7 @@ func (c *CoinbasePro) GetV3Time(ctx context.Context) (ServerTimeV3, error) { var resp ServerTimeV3 return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - coinbaseV3+coinbaseTime, "", nil, Version3, &resp, nil) + coinbaseV3+coinbaseTime, "", nil, true, &resp, nil) } // ListNotifications lists the notifications the user is subscribed to @@ -798,7 +796,7 @@ func (c *CoinbasePro) ListNotifications(ctx context.Context, pag PaginationInp) pathParams := common.EncodeURLValues("", params.urlVals) return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - coinbaseV2+coinbaseNotifications, pathParams, nil, Version2, &resp, nil) + coinbaseV2+coinbaseNotifications, pathParams, nil, false, &resp, nil) } func (c *CoinbasePro) GetUserByID(ctx context.Context, userID string) (*UserResponse, error) { @@ -811,7 +809,7 @@ func (c *CoinbasePro) GetUserByID(ctx context.Context, userID string) (*UserResp var resp *UserResponse return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - path, "", nil, Version2, &resp, nil) + path, "", nil, false, &resp, nil) } // GetCurrentUser returns information about the user associated with the API key @@ -819,7 +817,7 @@ func (c *CoinbasePro) GetCurrentUser(ctx context.Context) (*UserResponse, error) var resp *UserResponse return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - coinbaseV2+coinbaseUser, "", nil, Version2, &resp, nil) + coinbaseV2+coinbaseUser, "", nil, false, &resp, nil) } // GetAuthInfo returns information about the scopes granted to the API key @@ -829,7 +827,7 @@ func (c *CoinbasePro) GetAuthInfo(ctx context.Context) (AuthResponse, error) { path := fmt.Sprintf("%s%s/%s", coinbaseV2, coinbaseUser, coinbaseAuth) return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - path, "", nil, Version2, &resp, nil) + path, "", nil, false, &resp, nil) } // UpdateUser modifies certain user preferences @@ -849,7 +847,7 @@ func (c *CoinbasePro) UpdateUser(ctx context.Context, name, timeZone, nativeCurr } return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPut, - coinbaseV2+coinbaseUser, "", req, Version2, &resp, nil) + coinbaseV2+coinbaseUser, "", req, false, &resp, nil) } // CreateWallet creates a new wallet for the specified currency @@ -863,7 +861,7 @@ func (c *CoinbasePro) CreateWallet(ctx context.Context, currency string) (*GenWa var resp *GenWalletResponse return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - path, "", nil, Version2, &resp, nil) + path, "", nil, false, &resp, nil) } // GetAllWallets lists all accounts associated with the API key @@ -877,7 +875,7 @@ func (c *CoinbasePro) GetAllWallets(ctx context.Context, pag PaginationInp) (Get pathParams := common.EncodeURLValues("", params.urlVals) return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - coinbaseV2+coinbaseAccounts, pathParams, nil, Version2, &resp, nil) + coinbaseV2+coinbaseAccounts, pathParams, nil, false, &resp, nil) } // GetWalletByID returns information about a single wallet. In lieu of a wallet ID, @@ -899,7 +897,7 @@ func (c *CoinbasePro) GetWalletByID(ctx context.Context, walletID, currency stri var resp *GenWalletResponse return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - path, "", nil, Version2, &resp, nil) + path, "", nil, false, &resp, nil) } // UpdateWalletName updates the name of a wallet @@ -915,7 +913,7 @@ func (c *CoinbasePro) UpdateWalletName(ctx context.Context, walletID, newName st var resp *GenWalletResponse return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPut, - path, "", req, Version2, &resp, nil) + path, "", req, false, &resp, nil) } // DeleteWallet deletes a wallet @@ -927,7 +925,7 @@ func (c *CoinbasePro) DeleteWallet(ctx context.Context, walletID string) error { path := fmt.Sprintf("%s%s/%s", coinbaseV2, coinbaseAccounts, walletID) return c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodDelete, path, "", nil, - Version2, nil, nil) + false, nil, nil) } // CreateAddress generates a crypto address for depositing to the specified wallet @@ -943,7 +941,7 @@ func (c *CoinbasePro) CreateAddress(ctx context.Context, walletID, name string) var resp *GenAddrResponse return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, - path, "", req, Version2, &resp, nil) + path, "", req, false, &resp, nil) } // GetAllAddresses returns information on all addresses associated with a wallet @@ -963,7 +961,7 @@ func (c *CoinbasePro) GetAllAddresses(ctx context.Context, walletID string, pag pathParams := common.EncodeURLValues("", params.urlVals) return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - path, pathParams, nil, Version2, &resp, nil) + path, pathParams, nil, false, &resp, nil) } // GetAddressByID returns information on a single address associated with the specified wallet @@ -981,7 +979,7 @@ func (c *CoinbasePro) GetAddressByID(ctx context.Context, walletID, addressID st var resp *GenAddrResponse return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - path, "", nil, Version2, &resp, nil) + path, "", nil, false, &resp, nil) } // GetAddressTransactions returns a list of transactions associated with the specified address @@ -1005,7 +1003,7 @@ func (c *CoinbasePro) GetAddressTransactions(ctx context.Context, walletID, addr pathParams := common.EncodeURLValues("", params.urlVals) return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - path, pathParams, nil, Version2, &resp, nil) + path, pathParams, nil, false, &resp, nil) } // SendMoney can send funds to an email or cryptocurrency address (if "traType" is set to "send"), @@ -1043,7 +1041,7 @@ func (c *CoinbasePro) SendMoney(ctx context.Context, traType, walletID, to, curr var resp *GenTransactionResp return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, - path, "", req, Version2, &resp, nil) + path, "", req, false, &resp, nil) } // GetAllTransactions returns a list of transactions associated with the specified wallet @@ -1063,7 +1061,7 @@ func (c *CoinbasePro) GetAllTransactions(ctx context.Context, walletID string, p pathParams := common.EncodeURLValues("", params.urlVals) return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - path, pathParams, nil, Version2, &resp, nil) + path, pathParams, nil, false, &resp, nil) } // GetTransactionByID returns information on a single transaction associated with the @@ -1082,7 +1080,7 @@ func (c *CoinbasePro) GetTransactionByID(ctx context.Context, walletID, transact var resp *GenTransactionResp return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - path, "", nil, Version2, &resp, nil) + path, "", nil, false, &resp, nil) } // FiatTransfer prepares and optionally processes a transfer of funds between the exchange and a @@ -1116,7 +1114,7 @@ func (c *CoinbasePro) FiatTransfer(ctx context.Context, walletID, currency, paym var resp *GenDeposWithdrResp return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, - path, "", req, Version2, &resp, nil) + path, "", req, false, &resp, nil) } // CommitTransfer processes a deposit/withdrawal that was created with the "commit" parameter set @@ -1142,7 +1140,7 @@ func (c *CoinbasePro) CommitTransfer(ctx context.Context, walletID, depositID st var resp *GenDeposWithdrResp return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, - path, "", nil, Version2, &resp, nil) + path, "", nil, false, &resp, nil) } // GetAllFiatTransfers returns a list of transfers either to or from fiat payment methods and @@ -1169,7 +1167,7 @@ func (c *CoinbasePro) GetAllFiatTransfers(ctx context.Context, walletID string, pathParams := common.EncodeURLValues("", params.urlVals) err := c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - path, pathParams, nil, Version2, &resp, nil) + path, pathParams, nil, false, &resp, nil) if err != nil { return resp, err @@ -1197,7 +1195,7 @@ func (c *CoinbasePro) GetFiatTransferByID(ctx context.Context, walletID, deposit var resp *GenDeposWithdrResp return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - path, "", nil, Version2, &resp, nil) + path, "", nil, false, &resp, nil) } // GetAllPaymentMethods returns a list of all payment methods associated with the user's account @@ -1213,7 +1211,7 @@ func (c *CoinbasePro) GetAllPaymentMethods(ctx context.Context, pag PaginationIn pathParams := common.EncodeURLValues("", params.urlVals) return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - path, pathParams, nil, Version2, &resp, nil) + path, pathParams, nil, false, &resp, nil) } // GetPaymentMethodByID returns information on a single payment method associated with the user's @@ -1228,7 +1226,7 @@ func (c *CoinbasePro) GetPaymentMethodByID(ctx context.Context, paymentMethodID var resp *GenPaymentMethodResp return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - path, "", nil, Version2, &resp, nil) + path, "", nil, false, &resp, nil) } // GetFiatCurrencies lists currencies that Coinbase knows about @@ -1288,749 +1286,6 @@ func (c *CoinbasePro) GetV2Time(ctx context.Context) (ServerTimeV2, error) { return resp, c.SendHTTPRequest(ctx, exchange.RestSpot, coinbaseV2+coinbaseTime, &resp) } -/* - -// GetHolds returns information on the holds of an account -func (c *CoinbasePro) GetHolds(ctx context.Context, accountID, direction, step string, limit int64) ([]AccountHolds, ReturnedPaginationHeaders, error) { - path := fmt.Sprintf("%s/%s/%s", coinbaseAccounts, accountID, coinbaseproHolds) - - var params Params - params.urlVals = url.Values{} - // Warning: This endpoint doesn't seem to properly support pagination, the headers - // indicating the cursor position are never actually present. Still, it's handled - // as if it works, in case it gets fixed. - params.PrepareDSL(direction, step, limit) - - path = common.EncodeURLValues(path, params.urlVals) - - var resp []AccountHolds - retH := http.Header{} - - err := c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, "", nil, Version3, &resp, &retH) - - rph := ReturnedPaginationHeaders{before: retH.Get("CB-BEFORE"), after: retH.Get("CB-AFTER")} - - return resp, rph, err -} - -// GetAccountLedger returns a list of ledger activity -func (c *CoinbasePro) GetAccountLedger(ctx context.Context, accountID, direction, step, pID string, startDate, endDate time.Time, limit int64) ([]AccountLedgerResponse, ReturnedPaginationHeaders, error) { - var params Params - params.urlVals = url.Values{} - var rph ReturnedPaginationHeaders - - err := params.PrepareDateString(startDate, endDate, startDateString, endDateString) - if err != nil { - return nil, rph, err - } - - path := fmt.Sprintf("%s/%s/%s", coinbaseAccounts, accountID, coinbaseproLedger) - - params.PrepareDSL(direction, step, limit) - - if pID != "" { - params.urlVals.Set("profile_id", pID) - } - - path = common.EncodeURLValues(path, params.urlVals) - - var resp []AccountLedgerResponse - retH := http.Header{} - - err = c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, "", nil, Version3, &resp, &retH) - - rph = ReturnedPaginationHeaders{before: retH.Get("CB-BEFORE"), after: retH.Get("CB-AFTER")} - - return resp, rph, err -} - -// GetAccountTransfers returns a history of withdrawal and or deposit -// transactions for a single account -func (c *CoinbasePro) GetAccountTransfers(ctx context.Context, accountID, direction, step, transferType string, limit int64) ([]TransferResponse, ReturnedPaginationHeaders, error) { - path := fmt.Sprintf("%s/%s/%s", coinbaseAccounts, accountID, coinbaseproTransfers) - - var params Params - params.urlVals = url.Values{} - - params.PrepareDSL(direction, step, limit) - params.urlVals.Set("type", transferType) - - path = common.EncodeURLValues(path, params.urlVals) - - var resp []TransferResponse - retH := http.Header{} - - err := c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, "", nil, Version3, &resp, &retH) - - rph := ReturnedPaginationHeaders{before: retH.Get("CB-BEFORE"), after: retH.Get("CB-AFTER")} - - return resp, rph, err -} - -// GetAddressBook returns all addresses stored in the address book -func (c *CoinbasePro) GetAddressBook(ctx context.Context) ([]GetAddressResponse, error) { - var resp []GetAddressResponse - - return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, coinbaseproAddressBook, "", nil, Version3, &resp, nil) -} - -// AddAddresses adds new addresses to the address book -func (c *CoinbasePro) AddAddresses(ctx context.Context, req []AddAddressRequest) ([]AddAddressResponse, error) { - params := make(map[string]interface{}) - params["addresses"] = req - // The documentation also prompts us to add in an arbitrary amount of strings - // into the parameters, without specifying what they're for. Adding some seemed - // to do nothing - - var resp []AddAddressResponse - - return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproAddressBook, "", params, Version3, &resp, nil) -} - -// DeleteAddress deletes an address from the address book -func (c *CoinbasePro) DeleteAddress(ctx context.Context, addressID string) error { - path := fmt.Sprintf("%s/%s", coinbaseproAddressBook, addressID) - - return c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodDelete, path, "", nil, Version3, nil, nil) -} - -// GetCoinbaseWallets returns all of the user's available Coinbase wallets -func (c *CoinbasePro) GetCoinbaseWallets(ctx context.Context) ([]CoinbaseAccounts, error) { - var resp []CoinbaseAccounts - - return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, coinbaseproCoinbaseAccounts, "", nil, Version3, &resp, nil) -} - -// GetAllCurrencies returns a list of currencies known by the exchange -// Warning: Currencies won't necessarily be available for trading -func (c *CoinbasePro) GetAllCurrencies(ctx context.Context) ([]Currency, error) { - var currencies []Currency - - return currencies, - c.SendHTTPRequest(ctx, exchange.RestSpot, coinbaseproCurrencies, ¤cies) -} - -// GetCurrencyByID returns info on a single currency given its ID in ISO 4217, or -// in a custom code for currencies which lack an ISO 4217 code -func (c *CoinbasePro) GetCurrencyByID(ctx context.Context, currencyID string) (*Currency, error) { - path := fmt.Sprintf("%s/%s", coinbaseproCurrencies, currencyID) - - resp := Currency{} - - return &resp, c.SendHTTPRequest(ctx, exchange.RestSpot, path, &resp) -} - -// DepositViaCoinbase deposits funds from a Coinbase account -func (c *CoinbasePro) DepositViaCoinbase(ctx context.Context, profileID, currency, coinbaseAccountID string, amount float64) (DepositWithdrawalInfo, error) { - params := map[string]interface{}{"profile_id": profileID, - "amount": strconv.FormatFloat(amount, 'f', -1, 64), - "coinbase_account_id": coinbaseAccountID, "currency": currency} - - resp := DepositWithdrawalInfo{} - - return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproDepositCoinbase, "", params, Version3, &resp, nil) -} - -// DepositViaPaymentMethod deposits funds from a payment method. SEPA is not allowed -func (c *CoinbasePro) DepositViaPaymentMethod(ctx context.Context, profileID, paymentID, currency string, amount float64) (DepositWithdrawalInfo, error) { - params := map[string]interface{}{"profile_id": profileID, - "amount": strconv.FormatFloat(amount, 'f', -1, 64), - "payment_method_id": paymentID, "currency": currency} - - resp := DepositWithdrawalInfo{} - - return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproPaymentMethodDeposit, "", params, Version3, &resp, nil) -} - -// GetPayMethods returns a full list of payment methods -func (c *CoinbasePro) GetPayMethods(ctx context.Context) ([]PaymentMethod, error) { - var resp []PaymentMethod - - return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, coinbaseproPaymentMethod, "", nil, Version3, &resp, nil) -} - -// GetAllTransfers returns all in-progress and completed transfers in and out of any -// of the user's accounts -func (c *CoinbasePro) GetAllTransfers(ctx context.Context, profileID, direction, step, transferType string, limit int64) ([]TransferResponse, ReturnedPaginationHeaders, error) { - var params Params - params.urlVals = url.Values{} - params.urlVals.Set("profile_id", profileID) - params.PrepareDSL(direction, step, limit) - params.urlVals.Set("type", transferType) - path := common.EncodeURLValues(coinbaseproTransfers, params.urlVals) - - resp := []TransferResponse{} - retH := http.Header{} - - err := c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, "", nil, Version3, &resp, &retH) - - rph := ReturnedPaginationHeaders{before: retH.Get("CB-BEFORE"), after: retH.Get("CB-AFTER")} - - return resp, rph, err -} - -// GetTransferByID returns information on a single transfer when provided with its ID -func (c *CoinbasePro) GetTransferByID(ctx context.Context, transferID string) (*TransferResponse, error) { - path := fmt.Sprintf("%s/%s", coinbaseproTransfers, transferID) - resp := TransferResponse{} - - return &resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, "", nil, Version3, &resp, nil) -} - -// SendTravelInfoForTransfer sends travel rule information for a transfer -func (c *CoinbasePro) SendTravelInfoForTransfer(ctx context.Context, transferID, originName, originCountry string) (string, error) { - path := fmt.Sprintf("%s/%s/%s", coinbaseproTransfers, transferID, - coinbaseproTravelRules) - params := map[string]interface{}{"transfer_id": transferID, - "originator_name": originName, "originator_country": originCountry} - - var resp string - - return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, path, "", params, Version3, &resp, nil) -} - -// WithdrawViaCoinbase withdraws funds to a coinbase account. -func (c *CoinbasePro) WithdrawViaCoinbase(ctx context.Context, profileID, accountID, currency string, amount float64) (DepositWithdrawalInfo, error) { - req := map[string]interface{}{"profile_id": profileID, - "amount": strconv.FormatFloat(amount, 'f', -1, 64), - "coinbase_account_id": accountID, "currency": currency} - - resp := DepositWithdrawalInfo{} - - return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproWithdrawalCoinbase, "", req, Version3, &resp, nil) -} - -// WithdrawCrypto withdraws funds to a crypto address -func (c *CoinbasePro) WithdrawCrypto(ctx context.Context, profileID, currency, cryptoAddress, destinationTag, twoFactorCode, network string, amount float64, noDestinationTag, addNetworkFee bool, nonce int32) (DepositWithdrawalInfo, error) { - req := map[string]interface{}{"profile_id": profileID, - "amount": strconv.FormatFloat(amount, 'f', -1, 64), - "currency": currency, "crypto_address": cryptoAddress, - "destination_tag": destinationTag, "no_destination_tag": noDestinationTag, - "two_factor_code": twoFactorCode, "nonce": nonce, "network": network, - "add_network_fee": addNetworkFee} - - resp := DepositWithdrawalInfo{} - - return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproWithdrawalCrypto, "", req, Version3, &resp, nil) -} - -// GetWithdrawalFeeEstimate has Coinbase estimate the fee for withdrawing in a certain -// network to a certain address -func (c *CoinbasePro) GetWithdrawalFeeEstimate(ctx context.Context, currency, cryptoAddress, network string) (WithdrawalFeeEstimate, error) { - resp := WithdrawalFeeEstimate{} - if currency == "" { - return resp, errors.New("currency cannot be empty") - } - if cryptoAddress == "" { - return resp, errors.New("cryptoAddress cannot be empty") - } - var params Params - params.urlVals = url.Values{} - params.urlVals.Set("currency", currency) - params.urlVals.Set("crypto_address", cryptoAddress) - params.urlVals.Set("network", network) - path := common.EncodeURLValues(coinbaseproFeeEstimate, params.urlVals) - - return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, "", nil, Version3, &resp, nil) -} - -// WithdrawViaPaymentMethod withdraws funds to a payment method -func (c *CoinbasePro) WithdrawViaPaymentMethod(ctx context.Context, profileID, paymentID, currency string, amount float64) (DepositWithdrawalInfo, error) { - req := map[string]interface{}{"profile_id": profileID, - "amount": strconv.FormatFloat(amount, 'f', -1, 64), - "payment_method_id": paymentID, "currency": currency} - - resp := DepositWithdrawalInfo{} - - return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproWithdrawalPaymentMethod, "", req, Version3, &resp, nil) -} - -// GetFees returns your current maker & taker fee rates, as well as your 30-day -// trailing volume. Quoted rates are subject to change. -func (c *CoinbasePro) GetFees(ctx context.Context) (FeeResponse, error) { - resp := FeeResponse{} - - return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, coinbaseproFees, "", nil, Version3, &resp, nil) -} - -// GetSignedPrices returns some cryptographically signed prices ready to be -// posted on-chain using Compound's Open Oracle smart contract -func (c *CoinbasePro) GetSignedPrices(ctx context.Context) (SignedPrices, error) { - resp := SignedPrices{} - - return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, coinbaseproOracle, "", nil, Version3, &resp, nil) -} - -// GetOrderbook returns orderbook by currency pair and level -func (c *CoinbasePro) GetOrderbook(ctx context.Context, symbol string, level int32) (*OrderbookFinalResponse, error) { - if symbol == "" { - return nil, errors.New("symbol cannot be empty") - } - - path := fmt.Sprintf("%s/%s/%s", coinbaseProducts, symbol, coinbaseproOrderbook) - if level > 0 { - var params Params - params.urlVals = url.Values{} - params.urlVals.Set("level", strconv.Itoa(int(level))) - - path = common.EncodeURLValues(path, params.urlVals) - } - - data := OrderbookIntermediaryResponse{} - err := c.SendHTTPRequest(ctx, exchange.RestSpot, path, &data) - if err != nil { - return nil, err - } - - obF := OrderbookFinalResponse{ - Sequence: data.Sequence, - AuctionMode: data.AuctionMode, - Auction: data.Auction, - Time: data.Time, - } - - obF.Bids, err = OrderbookHelper(data.Bids, level) - if err != nil { - return nil, err - } - obF.Asks, err = OrderbookHelper(data.Asks, level) - if err != nil { - return nil, err - } - - return &obF, nil -} - -// GetStats returns 30 day and 24 hour stats for the product. Volume is in base currency -// units. open, high, low are in quote currency units. -func (c *CoinbasePro) GetStats(ctx context.Context, currencyPair string) (Stats, error) { - stats := Stats{} - if currencyPair == "" { - return stats, errors.New("currency pair cannot be empty") - } - - path := fmt.Sprintf( - "%s/%s/%s", coinbaseProducts, currencyPair, coinbaseproStats) - - return stats, c.SendHTTPRequest(ctx, exchange.RestSpot, path, &stats) -} - -// GetTrades lists information on the latest trades for a product -func (c *CoinbasePro) GetTrades(ctx context.Context, currencyPair, direction, step string, limit int64) ([]Trade, error) { - if currencyPair == "" { - return nil, errors.New("currency pair cannot be empty") - } - - path := fmt.Sprintf( - "%s/%s/%s", coinbaseProducts, currencyPair, coinbaseproTrades) - - var params Params - params.urlVals = url.Values{} - params.PrepareDSL(direction, step, limit) - - path = common.EncodeURLValues(path, params.urlVals) - - var trades []Trade - - return trades, c.SendHTTPRequest(ctx, exchange.RestSpot, path, &trades) -} - -// GetAllProfiles returns information on all of the current user's profiles -func (c *CoinbasePro) GetAllProfiles(ctx context.Context, active *bool) ([]Profile, error) { - var params Params - params.urlVals = url.Values{} - - if active != nil { - params.urlVals.Set("active", strconv.FormatBool(*active)) - } - - var resp []Profile - - path := common.EncodeURLValues(coinbaseproProfiles, params.urlVals) - - return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, "", nil, Version3, &resp, nil) -} - -// CreateAProfile creates a new profile, failing if no name is provided, -// or if the user already has the max number of profiles -func (c *CoinbasePro) CreateAProfile(ctx context.Context, name string) (Profile, error) { - var resp Profile - if name == "" { - return resp, errors.New("name cannot be empty") - } - - req := map[string]interface{}{"name": name} - - return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproProfiles, "", req, Version3, &resp, nil) -} - -// TransferBetweenProfiles transfers an amount of currency from one profile to another -func (c *CoinbasePro) TransferBetweenProfiles(ctx context.Context, from, to, currency string, amount float64) (string, error) { - var resp string - if from == "" || to == "" || currency == "" { - return resp, errors.New("from, to, and currency must all not be empty") - } - - req := map[string]interface{}{"from": from, "to": to, "currency": currency, - "amount": strconv.FormatFloat(amount, 'f', -1, 64)} - - path := fmt.Sprintf("%s/%s", coinbaseproProfiles, coinbaseproTransfer) - - return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, path, "", req, Version3, &resp, nil) -} - -// GetProfileByID returns information on a single profile, provided its ID -func (c *CoinbasePro) GetProfileByID(ctx context.Context, profileID string, active *bool) (Profile, error) { - var params Params - params.urlVals = url.Values{} - if active != nil { - params.urlVals.Set("active", strconv.FormatBool(*active)) - } - - var resp Profile - path := fmt.Sprintf("%s/%s", coinbaseproProfiles, profileID) - path = common.EncodeURLValues(path, params.urlVals) - - return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, "", nil, Version3, &resp, nil) -} - -// RenameProfile renames a profile, provided its ID -func (c *CoinbasePro) RenameProfile(ctx context.Context, profileID, newName string) (Profile, error) { - var resp Profile - if newName == "" { - return resp, errors.New("new name cannot be empty") - } - - req := map[string]interface{}{"profile_id": profileID, "name": newName} - - path := fmt.Sprintf("%s/%s", coinbaseproProfiles, profileID) - - return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPut, path, "", req, Version3, &resp, nil) -} - -// DeleteProfile deletes a profile and transfers its funds to a specified -// profile. Fails if there are any open orders on the profile facing deletion -func (c *CoinbasePro) DeleteProfile(ctx context.Context, profileID, transferTo string) (string, error) { - var resp string - if profileID == "" || transferTo == "" { - return resp, errors.New("neither profileID nor transferTo can be empty") - } - - req := map[string]interface{}{"profile_id": profileID, "to": transferTo} - - path := fmt.Sprintf("%s/%s/%s", coinbaseproProfiles, profileID, coinbaseproDeactivate) - - return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodDelete, path, "", req, Version3, &resp, nil) -} - -// GetAllReports returns a list of all user-generated reports -func (c *CoinbasePro) GetAllReports(ctx context.Context, profileID string, reportType string, after time.Time, limit int64, ignoreExpired bool) ([]Report, error) { - var resp []Report - - var params Params - params.urlVals = url.Values{} - - params.urlVals.Set("profile_id", profileID) - params.urlVals.Set("after", after.Format(time.RFC3339)) - if limit != 0 { - params.urlVals.Set("limit", strconv.FormatInt(limit, 10)) - } - - params.urlVals.Set("type", reportType) - params.urlVals.Set("ignore_expired", strconv.FormatBool(ignoreExpired)) - - path := common.EncodeURLValues(coinbaseproReports, params.urlVals) - - return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, "", nil, Version3, &resp, nil) -} - -// CreateReport creates a new report -func (c *CoinbasePro) CreateReport(ctx context.Context, reportType, year, format, email, profileID, productID, accountID string, balanceDate, startDate, endDate time.Time) (CreateReportResponse, error) { - var resp CreateReportResponse - - if reportType == "" { - return resp, errors.New("report type cannot be empty") - } - if reportType == "1099k-transaction-history" && year == "" { - return resp, errors.New("year cannot be empty for 1099k-transaction-history reports") - } - if reportType != "balance" { - err := common.StartEndTimeCheck(startDate, endDate) - if err != nil { - return resp, err - } - } - - req := map[string]interface{}{"type": reportType, "year": year, "format": format, - "email": email, "profile_id": profileID} - - if reportType == "account" { - req["account"] = ReportAccountStruct{StartDate: startDate.Format(time.RFC3339), - EndDate: endDate.Format(time.RFC3339), AccountID: accountID} - } - if reportType == "balance" { - req["balance"] = ReportBalanceStruct{DateTime: balanceDate.Format(time.RFC3339)} - } - if reportType == "fills" || reportType == "otc-fills" || reportType == "rfq-fills" || - reportType == "tax-invoice" { - req[reportType] = ReportFillsTaxStruct{StartDate: startDate.Format(time.RFC3339), - EndDate: endDate.Format(time.RFC3339), ProductID: productID} - } - return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproReports, "", req, Version3, &resp, nil) -} - -// GetReportByID returns a single report, provided its ID -func (c *CoinbasePro) GetReportByID(ctx context.Context, reportID string) (Report, error) { - var resp Report - if reportID == "" { - return resp, errors.New("report id cannot be empty") - } - - path := fmt.Sprintf("%s/%s", coinbaseproReports, reportID) - - return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, "", nil, Version3, &resp, nil) -} - -// GetTravelRules returns a list of all travel rule information -func (c *CoinbasePro) GetTravelRules(ctx context.Context, direction, step, address string, limit int64) ([]TravelRule, error) { - var resp []TravelRule - var params Params - params.urlVals = url.Values{} - - params.PrepareDSL(direction, step, limit) - params.urlVals.Set("address", address) - - path := common.EncodeURLValues(coinbaseproTravelRules, params.urlVals) - - return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, "", nil, Version3, &resp, nil) -} - -// CreateTravelRule creates a travel rule entry -func (c *CoinbasePro) CreateTravelRule(ctx context.Context, address, originName, originCountry string) (TravelRule, error) { - var resp TravelRule - - req := map[string]interface{}{"address": address, "originator_name": originName, - "originator_country": originCountry} - - return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproTravelRules, "", req, Version3, &resp, nil) -} - -// DeleteTravelRule deletes a travel rule entry -func (c *CoinbasePro) DeleteTravelRule(ctx context.Context, id string) error { - if id == "" { - return errors.New("id cannot be empty") - } - - path := fmt.Sprintf("%s/%s", coinbaseproTravelRules, id) - - return c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodDelete, path, "", nil, Version3, nil, nil) -} - -// GetExchangeLimits returns information on payment method transfer limits, -// as well as buy/sell limits per currency -func (c *CoinbasePro) GetExchangeLimits(ctx context.Context, userID string) (ExchangeLimits, error) { - var resp ExchangeLimits - - path := fmt.Sprintf("%s/%s/%s", coinbaseUsers, userID, coinbaseproExchangeLimits) - - return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, "", nil, Version3, &resp, nil) -} - -// UpdateSettlementPreference updates whether one wants their funds to -// automatically convert to USD, USDC, or to remain in the currency received -func (c *CoinbasePro) UpdateSettlementPreference(ctx context.Context, userID, preference string) (string, error) { - if userID == "" || preference == "" { - return "", errors.New("neither userID nor preference can be empty") - } - - req := map[string]interface{}{"settlement_preference": preference} - - path := fmt.Sprintf("%s/%s/%s", coinbaseUsers, userID, coinbaseproSettlementPreferences) - - var resp string - - return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPut, path, "", req, Version3, &resp, nil) -} - -// GetAllWrappedAssets returns information on all supported wrapped assets -func (c *CoinbasePro) GetAllWrappedAssets(ctx context.Context) (AllWrappedAssetResponse, error) { - var resp AllWrappedAssetResponse - - return resp, - c.SendHTTPRequest(ctx, exchange.RestSpot, coinbaseproWrappedAssets, &resp) -} - -// GetAllStakeWraps returns details of all stake-wraps under the profile associated -// with the API key -func (c *CoinbasePro) GetAllStakeWraps(ctx context.Context, direction, from, to, status string, timestamp time.Time, limit int64) ([]StakeWrap, error) { - var resp []StakeWrap - - var params Params - params.urlVals = url.Values{} - - if !timestamp.IsZero() && !timestamp.Equal(time.Unix(0, 0)) { - params.PrepareDSL(direction, timestamp.Format(time.RFC3339), limit) - } else { - params.urlVals.Set("limit", strconv.FormatInt(limit, 10)) - } - - params.urlVals.Set("from", from) - params.urlVals.Set("to", to) - params.urlVals.Set("status", status) - - path := fmt.Sprintf("%s/%s", coinbaseproWrappedAssets, coinbaseproStakeWraps) - - path = common.EncodeURLValues(path, params.urlVals) - - return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, "", nil, Version3, &resp, nil) -} - -// CreateStakeWrap stakes and wraps from one currency to another, under the profile -// associated with the API key -func (c *CoinbasePro) CreateStakeWrap(ctx context.Context, from, to string, amount float64) (StakeWrap, error) { - if from == "" || to == "" || amount == 0 { - return StakeWrap{}, errors.New("none of from, to, or amount can be empty or zero") - } - var resp StakeWrap - - req := map[string]interface{}{"from": from, "to": to, - "amount": strconv.FormatFloat(amount, 'f', -1, 64)} - - path := fmt.Sprintf("%s/%s", coinbaseproWrappedAssets, coinbaseproStakeWraps) - - return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, path, "", req, Version3, &resp, nil) -} - -// GetStakeWrapByID returns details of a single stake-wrap -func (c *CoinbasePro) GetStakeWrapByID(ctx context.Context, stakeWrapID string) (StakeWrap, error) { - var resp StakeWrap - - if stakeWrapID == "" { - return resp, errors.New("stake wrap id cannot be empty") - } - - path := fmt.Sprintf("%s/%s/%s", coinbaseproWrappedAssets, coinbaseproStakeWraps, stakeWrapID) - - return resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, "", nil, Version3, &resp, nil) -} - -// GetWrappedAssetByID returns details of a single wrapped asset -func (c *CoinbasePro) GetWrappedAssetByID(ctx context.Context, wrappedAssetID string) (WrappedAssetResponse, error) { - var resp WrappedAssetResponse - - if wrappedAssetID == "" { - return resp, errors.New("wrapped asset id cannot be empty") - } - - path := fmt.Sprintf("%s/%s", coinbaseproWrappedAssets, wrappedAssetID) - - return resp, c.SendHTTPRequest(ctx, exchange.RestSpot, path, &resp) -} - -// GetWrappedAssetConversionRate returns the conversion rate for a wrapped asset -func (c *CoinbasePro) GetWrappedAssetConversionRate(ctx context.Context, wrappedAssetID string) (WrappedAssetConversionRate, error) { - var resp WrappedAssetConversionRate - - if wrappedAssetID == "" { - return resp, errors.New("wrapped asset id cannot be empty") - } - - path := fmt.Sprintf("%s/%s/%s", coinbaseproWrappedAssets, wrappedAssetID, coinbaseproConversionRate) - - return resp, c.SendHTTPRequest(ctx, exchange.RestSpot, path, &resp) -} - -// // MarginTransfer sends funds between a standard/default profile and a margin -// // profile. -// // A deposit will transfer funds from the default profile into the margin -// // profile. A withdraw will transfer funds from the margin profile to the -// // default profile. Withdraws will fail if they would set your margin ratio -// // below the initial margin ratio requirement. -// // -// // amount - the amount to transfer between the default and margin profile -// // transferType - either "deposit" or "withdraw" -// // profileID - The id of the margin profile to deposit or withdraw from -// // currency - currency to transfer, currently on "BTC" or "USD" -// func (c *CoinbasePro) MarginTransfer(ctx context.Context, amount float64, transferType, profileID, currency string) (MarginTransfer, error) { -// resp := MarginTransfer{} -// req := make(map[string]interface{}) -// req["type"] = transferType -// req["amount"] = strconv.FormatFloat(amount, 'f', -1, 64) -// req["currency"] = currency -// req["margin_profile_id"] = profileID - -// return resp, -// c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproMarginTransfer, req, &resp) -// } - -// // GetPosition returns an overview of account profile. -// func (c *CoinbasePro) GetPosition(ctx context.Context) (AccountOverview, error) { -// resp := AccountOverview{} - -// return resp, -// c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, coinbaseproPosition, nil, &resp) -// } - -// // ClosePosition closes a position and allowing you to repay position as well -// // repayOnly - allows the position to be repaid -// func (c *CoinbasePro) ClosePosition(ctx context.Context, repayOnly bool) (AccountOverview, error) { -// resp := AccountOverview{} -// req := make(map[string]interface{}) -// req["repay_only"] = repayOnly - -// return resp, -// c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseproPositionClose, req, &resp) -// } - -// // GetReportStatus once a report request has been accepted for processing, the -// // status is available by polling the report resource endpoint. -// func (c *CoinbasePro) GetReportStatus(ctx context.Context, reportID string) (Report, error) { -// resp := Report{} -// path := fmt.Sprintf("%s/%s", coinbaseproReports, reportID) - -// return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, &resp) -// } - -// // GetTrailingVolume this request will return your 30-day trailing volume for -// // all products. -// func (c *CoinbasePro) GetTrailingVolume(ctx context.Context) ([]Volume, error) { -// var resp []Volume - -// return resp, -// c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, coinbaseproTrailingVolume, nil, &resp) -// } - -*/ - // SendHTTPRequest sends an unauthenticated HTTP request func (c *CoinbasePro) SendHTTPRequest(ctx context.Context, ep exchange.URL, path string, result interface{}) error { endpoint, err := c.API.Endpoints.GetURL(ep) @@ -2053,7 +1308,7 @@ func (c *CoinbasePro) SendHTTPRequest(ctx context.Context, ep exchange.URL, path } // SendAuthenticatedHTTPRequest sends an authenticated HTTP request -func (c *CoinbasePro) SendAuthenticatedHTTPRequest(ctx context.Context, ep exchange.URL, method, path, queryParams string, bodyParams map[string]interface{}, version Version, result interface{}, returnHead *http.Header) (err error) { +func (c *CoinbasePro) SendAuthenticatedHTTPRequest(ctx context.Context, ep exchange.URL, method, path, queryParams string, bodyParams map[string]interface{}, istrue bool, result interface{}, returnHead *http.Header) (err error) { creds, err := c.GetCredentials(ctx) if err != nil { return err @@ -2064,7 +1319,7 @@ func (c *CoinbasePro) SendAuthenticatedHTTPRequest(ctx context.Context, ep excha } // Version 2 wants query params in the path during signing - if version == Version2 { + if !istrue { path = path + queryParams } @@ -2097,7 +1352,7 @@ func (c *CoinbasePro) SendAuthenticatedHTTPRequest(ctx context.Context, ep excha headers["CB-VERSION"] = "2023-11-13" // Version 3 only wants query params in the path when the request is sent - if version == Version3 { + if istrue { path = path + queryParams } @@ -2114,7 +1369,7 @@ func (c *CoinbasePro) SendAuthenticatedHTTPRequest(ctx context.Context, ep excha }, nil } rateLim := V2Rate - if version == Version3 { + if istrue { rateLim = V3Rate } @@ -2230,30 +1485,6 @@ func isStablePair(pair currency.Pair) bool { return stableMap[key.PairAsset{Base: pair.Base.Item, Quote: pair.Quote.Item}] } -// func getInternationalBankWithdrawalFee(c currency.Code) float64 { -// var fee float64 - -// if c.Equal(currency.USD) { -// fee = 25 -// } else if c.Equal(currency.EUR) { -// fee = 0.15 -// } - -// return fee -// } - -// func getInternationalBankDepositFee(c currency.Code) float64 { -// var fee float64 - -// if c.Equal(currency.USD) { -// fee = 10 -// } else if c.Equal(currency.EUR) { -// fee = 0.15 -// } - -// return fee -// } - // 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) @@ -2287,59 +1518,6 @@ func (p *Params) preparePagination(pag PaginationInp) { } } -// OrderbookHelper handles the transfer of bids and asks of unclear levels, to a -// generalised format -// func orderbookHelper(iOD InterOrderDetail, level int32) ([]GenOrderDetail, error) { -// gOD := make([]GenOrderDetail, len(iOD)) - -// for i := range iOD { -// priceConv, ok := iOD[i][0].(string) -// if !ok { -// return nil, errors.New("unable to type assert price") -// } -// price, err := strconv.ParseFloat(priceConv, 64) -// if err != nil { -// return nil, err -// } -// gOD[i].Price = price - -// amountConv, ok := iOD[i][1].(string) -// if !ok { -// return nil, errors.New("unable to type assert amount") -// } -// amount, err := strconv.ParseFloat(amountConv, 64) -// if err != nil { -// return nil, err -// } -// gOD[i].Amount = amount - -// if level == 3 { -// orderID, ok := iOD[i][2].(string) -// if !ok { -// return nil, errors.New("unable to type assert order ID") -// } -// gOD[i].OrderID = orderID -// } else { -// numOrders, ok := iOD[i][2].(float64) -// if !ok { -// return nil, errors.New("unable to type assert number of orders") -// } -// gOD[i].NumOrders = numOrders -// } - -// } -// return gOD, nil - -// } - -// prepareDSL adds the direction, step, and limit queries for pagination -// func (p *Params) prepareDSL(direction, step string, limit int64) { -// p.urlVals.Set(direction, step) -// if limit >= 0 { -// p.urlVals.Set("limit", strconv.FormatInt(limit, 10)) -// } -// } - func (f FiatTransferType) String() string { if f { return "withdrawal" @@ -2357,47 +1535,14 @@ func (t *UnixTimestamp) UnmarshalJSON(b []byte) error { if err != nil { return err } - *t = UnixTimestamp(time.Unix(timestamp, 0)) + *t = UnixTimestamp(time.Unix(timestamp, 0).UTC()) return nil } -func (t UnixTimestamp) String() string { - return time.Time(t).String() +func (t *UnixTimestamp) String() string { + return t.Time().String() } func (t UnixTimestamp) Time() time.Time { return time.Time(t) } - -func (t *ExchTime) UnmarshalJSON(b []byte) error { - s := strings.Trim(string(b), `"`) - if s == " " || s == "null" { - return nil - } - tt, err := time.Parse("2006-01-02 15:04:05.999999-07", s) - if err != nil { - return err - } - *t = ExchTime(tt) - return nil -} - -func (t ExchTime) String() string { - return time.Time(t).String() -} - -func (pm *PriceMap) UnmarshalJSON(data []byte) error { - var m map[string]string - if err := json.Unmarshal(data, &m); err != nil { - return err - } - *pm = make(PriceMap) - for k, v := range m { - f, err := strconv.ParseFloat(v, 64) - if err != nil { - return err - } - (*pm)[k] = f - } - return nil -} diff --git a/exchanges/coinbasepro/coinbasepro_test.go b/exchanges/coinbasepro/coinbasepro_test.go index 56e5c0d75b1..1078d40c568 100644 --- a/exchanges/coinbasepro/coinbasepro_test.go +++ b/exchanges/coinbasepro/coinbasepro_test.go @@ -55,7 +55,6 @@ const ( skipInsufficientFunds = "insufficient funds for test, skipping" skipInsufficientOrders = "insufficient orders for test, skipping" skipInsufficientPortfolios = "insufficient portfolios for test, skipping" - skipSweepPending = "pending sweep found, skipping" skipInsufficientWallets = "insufficient wallets for test, skipping" skipInsufficientFundsOrWallets = "insufficient funds or wallets for test, skipping" skipInsufficientTransactions = "insufficient transactions for test, skipping" @@ -75,6 +74,10 @@ const ( errNoEndpointPathEdgeCase3 = "no endpoint path found for the given key: EdgeCase3URL" errJsonUnsupportedChan = "json: unsupported type: chan struct {}, authenticated request failed" errExpectedFeeRange = "expected fee range of %v and %v, received %v" + errJsonNumberIntoString = "json: cannot unmarshal number into Go value of type string" + errParseIntValueOutOfRange = `strconv.ParseInt: parsing "922337203685477580700": value out of range` + + expectedTimestamp = "1970-01-01 00:20:34 +0000 UTC" testAmount = 1e-08 testPrice = 1e+09 @@ -618,11 +621,8 @@ func TestScheduleFuturesSweep(t *testing.T) { if len(curSweeps.Sweeps) > 0 { for i := range curSweeps.Sweeps { if curSweeps.Sweeps[i].Status == "PENDING" { - if curSweeps.Sweeps[i].RequestedAmount.Value == 0.001337 { - preCancel = true - } else { - t.Skip(skipSweepPending) - } + preCancel = true + } } } @@ -656,11 +656,8 @@ func TestCancelPendingFuturesSweep(t *testing.T) { if len(curSweeps.Sweeps) > 0 { for i := range curSweeps.Sweeps { if curSweeps.Sweeps[i].Status == "PENDING" { - if curSweeps.Sweeps[i].RequestedAmount.Value == 0.001337 { - partialSkip = true - } else { - t.Skip(skipSweepPending) - } + partialSkip = true + } } } @@ -1316,1964 +1313,290 @@ func TestGetV2Time(t *testing.T) { assert.NotEmpty(t, resp, errExpectedNonEmpty) } -/* +func TestSendHTTPRequest(t *testing.T) { + err := c.SendHTTPRequest(context.Background(), exchange.EdgeCase3, "", nil) + if err.Error() != errNoEndpointPathEdgeCase3 { + t.Errorf(errExpectMismatch, err, errNoEndpointPathEdgeCase3) + } +} -func TestGetHolds(t *testing.T) { - sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - accID, err := c.GetAllAccounts(context.Background(), 49, "") - if err != nil { - t.Error("CoinBasePro GetAllAccounts() error", err) +func TestSendAuthenticatedHTTPRequest(t *testing.T) { + fc := &CoinbasePro{} + err := fc.SendAuthenticatedHTTPRequest(context.Background(), exchange.EdgeCase3, "", "", "", nil, false, nil, nil) + if !errors.Is(err, exchange.ErrCredentialsAreEmpty) { + t.Errorf(errExpectMismatch, err, exchange.ErrCredentialsAreEmpty) } - if len(accID.Accounts) == 0 { - t.Fatal("CoinBasePro GetAllAccounts() error, expected a non-empty response") + err = c.SendAuthenticatedHTTPRequest(context.Background(), exchange.EdgeCase3, "", "", "", nil, false, nil, nil) + if err.Error() != errNoEndpointPathEdgeCase3 { + t.Errorf(errExpectMismatch, err, errNoEndpointPathEdgeCase3) } - _, _, err = c.GetHolds(context.Background(), accID.Accounts[1].UUID, pageNone, "2", 2) - if err != nil { - t.Error("CoinBasePro GetHolds() error", err) + ch := make(chan struct{}) + body := map[string]interface{}{"Unmarshalable": ch} + err = c.SendAuthenticatedHTTPRequest(context.Background(), exchange.RestSpot, "", "", "", body, false, nil, nil) + if err.Error() != errJsonUnsupportedChan { + t.Errorf(errExpectMismatch, err, errJsonUnsupportedChan) } } -func TestGetAccountLedger(t *testing.T) { - sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - accID, err := c.GetAllAccounts(context.Background(), 49, "") - if err != nil { - t.Error("CoinBasePro GetAllAccounts() error", err) - } - _, _, err = c.GetAccountLedger(context.Background(), "", pageNone, "", "", time.Unix(2, 2), time.Unix(1, 1), 0) - if err == nil { - t.Error("CoinBasePro GetAccountLedger() error, expected an error due to invalid times") +func TestGetFee(t *testing.T) { + _, err := c.GetFee(context.Background(), nil) + if err.Error() != errFeeBuilderNil { + t.Errorf(errExpectMismatch, errFeeBuilderNil, err) } - if len(accID.Accounts) == 0 { - t.Fatal("CoinBasePro GetAllAccounts() error, expected a non-empty response") + feeBuilder := exchange.FeeBuilder{ + FeeType: exchange.OfflineTradeFee, + Amount: 1, + PurchasePrice: 1, } - _, _, err = c.GetAccountLedger(context.Background(), accID.Accounts[1].UUID, pageNone, "1177507600", "a", - time.Unix(1, 1), time.Now(), 1000) + resp, err := c.GetFee(context.Background(), &feeBuilder) if err != nil { - t.Error("CoinBasePro GetAccountLedger() error", err) + t.Error(err) } -} - -func TestGetAccountTransfers(t *testing.T) { - sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - accID, err := c.GetAllAccounts(context.Background(), 49, "") + if resp != 0.008 { + t.Errorf(errExpectMismatch, resp, 0.008) + } + feeBuilder.IsMaker = true + resp, err = c.GetFee(context.Background(), &feeBuilder) if err != nil { - t.Error("CoinBasePro GetAllAccounts() error", err) + t.Error(err) } - if len(accID.Accounts) == 0 { - t.Fatal("CoinBasePro GetAllAccounts() error, expected a non-empty response") + if resp != 0.006 { + t.Errorf(errExpectMismatch, resp, 0.006) } - _, _, err = c.GetAccountTransfers(context.Background(), accID.Accounts[1].UUID, "", "", "", 3) + feeBuilder.Pair = currency.NewPair(currency.USDT, currency.USD) + resp, err = c.GetFee(context.Background(), &feeBuilder) if err != nil { t.Error(err) } -} - -func TestGetAddressBook(t *testing.T) { - sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - resp, err := c.GetAddressBook(context.Background()) - if err != nil { - t.Error("CoinBasePro GetAddressBook() error", err) - } - assert.NotEmpty(t, resp, "CoinBasePro GetAddressBook() error, expected a non-empty response") -} - -func TestAddAddresses(t *testing.T) { - // sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) - // var req [1]AddAddressRequest - // var err error - // req[0], err = PrepareAddAddress("BTC", testAddress, "", "implemented", "Coinbase", false) - // if err != nil { - // t.Error(err) - // } - // resp, err := c.AddAddresses(context.Background(), req[:]) - // if err != nil { - // t.Error("CoinBasePro AddAddresses() error", err) - // } - // assert.NotEmpty(t, resp, "CoinBasePro AddAddresses() error, expected a non-empty response") -} - -func TestDeleteAddress(t *testing.T) { - // sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) - // var req [1]AddAddressRequest - // var err error - // req[0], err = PrepareAddAddress("BTC", testAddress, "", "implemented", "Coinbase", false) - // if err != nil { - // t.Error(err) - // } - // resp, err := c.AddAddresses(context.Background(), req[:]) - // if err != nil { - // t.Error("CoinBasePro AddAddresses() error", err) - // } - // if len(resp) == 0 { - // t.Fatal("CoinBasePro AddAddresses() error, expected a non-empty response") - // } - - // err = c.DeleteAddress(context.Background(), resp[0].ID) - // if err != nil { - // t.Error("CoinBasePro DeleteAddress() error", err) - // } -} - -func TestGetCoinbaseWallets(t *testing.T) { - sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - resp, err := c.GetCoinbaseWallets(context.Background()) - if err != nil { - t.Error("CoinBasePro GetCoinbaseAccounts() error", err) + if resp != 0 { + t.Errorf(errExpectMismatch, resp, 0) } - assert.NotEmpty(t, resp, "CoinBasePro GetCoinbaseWallets() error, expected a non-empty response") -} - -func TestConvertCurrency(t *testing.T) { - sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) - actBool := true - profID, err := c.GetAllProfiles(context.Background(), &actBool) + feeBuilder.IsMaker = false + resp, err = c.GetFee(context.Background(), &feeBuilder) if err != nil { - t.Error("CoinBasePro GetAllProfiles() error", err) + t.Error(err) } - if len(profID) == 0 { - t.Fatal("CoinBasePro GetAllProfiles() error, expected a non-empty response") + if resp != 0.00001 { + t.Errorf(errExpectMismatch, resp, 0.00001) } - resp, err := c.ConvertCurrency(context.Background(), profID[0].ID, "USD", "USDC", "", 1) - if err != nil { - t.Error("CoinBasePro ConvertCurrency() error", err) + feeBuilder.FeeType = exchange.CryptocurrencyDepositFee + _, err = c.GetFee(context.Background(), &feeBuilder) + if err != errFeeTypeNotSupported { + t.Errorf(errExpectMismatch, errFeeTypeNotSupported, err) } - assert.NotEmpty(t, resp, "CoinBasePro ConvertCurrency() error, expected a non-empty response") -} - -func TestGetConversionByID(t *testing.T) { - sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) - actBool := true - profID, err := c.GetAllProfiles(context.Background(), &actBool) + feeBuilder.Pair = currency.Pair{} + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + feeBuilder.FeeType = exchange.CryptocurrencyTradeFee + resp, err = c.GetFee(context.Background(), &feeBuilder) if err != nil { - t.Error("CoinBasePro GetAllProfiles() error", err) + t.Error(err) } - if len(profID) == 0 { - t.Fatal("CoinBasePro GetAllProfiles() error, expected a non-empty response") + if !(resp <= 0.008 && resp >= 0.0005) { + t.Errorf(errExpectedFeeRange, 0.0005, 0.008, resp) } - resp, err := c.ConvertCurrency(context.Background(), profID[0].ID, "USD", "USDC", "", 1) + feeBuilder.IsMaker = true + resp, err = c.GetFee(context.Background(), &feeBuilder) if err != nil { - t.Error("CoinBasePro ConvertCurrency() error", err) + t.Error(err) } - resp2, err := c.GetConversionByID(context.Background(), resp.ID, profID[0].ID) - if err != nil { - t.Error("CoinBasePro GetConversionByID() error", err) + if !(resp <= 0.006 && resp >= 0) { + t.Errorf(errExpectedFeeRange, 0, 0.006, resp) } - assert.NotEmpty(t, resp2, "CoinBasePro GetConversionByID() error, expected a non-empty response") } -func TestGetAllCurrencies(t *testing.T) { - _, err := c.GetAllCurrencies(context.Background()) - if err != nil { - t.Error("GetAllCurrencies() error", err) +func TestPrepareDateString(t *testing.T) { + t.Parallel() + var expectedResult Params + expectedResult.urlVals = map[string][]string{ + "start_date": {"1970-01-01T00:00:01Z"}, + "end_date": {"1970-01-01T00:00:02Z"}, } -} + var result Params -func TestGetCurrencyByID(t *testing.T) { - resp, err := c.GetCurrencyByID(context.Background(), "BTC") + result.urlVals = make(url.Values) + + labelStart := "start_date" + labelEnd := "end_date" + + err := result.prepareDateString(time.Unix(1, 1).UTC(), time.Unix(2, 2).UTC(), labelStart, labelEnd) if err != nil { - t.Error("GetCurrencyByID() error", err) + t.Error(err) } - if resp.Name != "Bitcoin" { - t.Errorf("GetCurrencyByID() error, incorrect name returned, expected 'Bitcoin', got '%s'", - resp.Name) + if fmt.Sprint(expectedResult) != fmt.Sprint(result) { + t.Errorf(errExpectMismatch, result, expectedResult) } -} -func TestDepositViaCoinbase(t *testing.T) { - sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) - accID, err := c.GetCoinbaseWallets(context.Background()) + var newTime time.Time + err = result.prepareDateString(newTime, newTime, labelStart, labelEnd) if err != nil { - t.Error("CoinBasePro GetCoinbaseWallets() error", err) - } - if len(accID) == 0 { - t.Fatal("CoinBasePro GetCoinbaseWallets() error, expected a non-empty response") + t.Error(err) } - resp, err := c.DepositViaCoinbase(context.Background(), "", "BTC", accID[1].ID, 1) - if err != nil { - t.Error("CoinBasePro DepositViaCoinbase() error", err) + + err = result.prepareDateString(time.Unix(2, 2).UTC(), time.Unix(1, 1).UTC(), labelStart, labelEnd) + if !errors.Is(err, common.ErrStartAfterEnd) { + t.Errorf(errExpectMismatch, err, common.ErrStartAfterEnd) } - log.Printf("%+v", resp) } -func TestDepositViaPaymentMethod(t *testing.T) { - sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) - actBool := true - profID, err := c.GetAllProfiles(context.Background(), &actBool) - if err != nil { - t.Error("CoinBasePro GetAllProfiles() error", err) +func TestPreparePagination(t *testing.T) { + t.Parallel() + var expectedResult Params + expectedResult.urlVals = map[string][]string{"limit": {"1"}, "order": {"asc"}, "starting_after": {"meow"}, + "ending_before": {"woof"}} + + var result Params + result.urlVals = make(url.Values) + + pagIn := PaginationInp{Limit: 1, OrderAscend: true, StartingAfter: "meow", EndingBefore: "woof"} + + result.preparePagination(pagIn) + + if fmt.Sprint(expectedResult) != fmt.Sprint(result) { + t.Errorf(errExpectMismatch, result, expectedResult) } - if len(profID) == 0 { - t.Fatal("CoinBasePro GetAllProfiles() error, expected a non-empty response") +} + +func TestFetchTradablePairs(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + _, err := c.FetchTradablePairs(context.Background(), asset.Empty) + if !errors.Is(err, asset.ErrNotSupported) { + t.Errorf(errExpectMismatch, err, asset.ErrNotSupported) } - payID, err := c.GetPayMethods(context.Background()) + _, err = c.FetchTradablePairs(context.Background(), asset.Spot) if err != nil { - t.Error("CoinBasePro GetPayMethods() error", err) - } - var success bool - i := 0 - for i = range payID { - if payID[i].Type == "ach_bank_account" { - success = true - break - } - } - if !success { - t.Skip("Skipping test due to no ACH bank account found") + t.Error(err) } - _, err = c.DepositViaPaymentMethod(context.Background(), profID[0].ID, payID[i].ID, payID[i].Currency, 1) + _, err = c.FetchTradablePairs(context.Background(), asset.Futures) if err != nil { - t.Error("CoinBasePro DepositViaPaymentMethod() error", err) + t.Error(err) } } -func TestGetPayMethods(t *testing.T) { +func TestUpdateTradablePairs(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - _, err := c.GetPayMethods(context.Background()) + err := c.UpdateTradablePairs(context.Background(), false) if err != nil { - t.Error("CoinBasePro GetPayMethods() error", err) + t.Error(err) } } -func TestGetAllTransfers(t *testing.T) { +func TestUpdateAccountInfo(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - _, _, err := c.GetAllTransfers(context.Background(), "", "", "", "", 3) + _, err := c.UpdateAccountInfo(context.Background(), asset.Spot) if err != nil { - t.Error("CoinBasePro GetAllTransfers() error", err) + t.Error(err) } } -func TestGetTransferByID(t *testing.T) { +func TestFetchAccountInfo(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - resp, _, err := c.GetAllTransfers(context.Background(), "", "", "", "", 3) - if err != nil { - t.Error("CoinBasePro GetAllTransfers() error", err) - } - if len(resp) == 0 { - t.Skip("TestGetTransferByID skipped due to there being zero transfers.") - } - _, err = c.GetTransferByID(context.Background(), resp[0].ID) + _, err := c.FetchAccountInfo(context.Background(), asset.Spot) if err != nil { - t.Error("CoinBasePro GetTransferByID() error", err) + t.Error(err) } - } -func TestSendTravelInfoForTransfer(t *testing.T) { - sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) - resp, _, err := c.GetAllTransfers(context.Background(), "", "", "", "", 1000) +func TestUpdateTickers(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + err := c.UpdateTickers(context.Background(), asset.Futures) if err != nil { - t.Error("CoinBasePro GetAllTransfers() error", err) - } - if len(resp) == 0 { - t.Skip("TestSendTravelInfoForTransfer skipped due to there being zero pending transfers.") - } - var tID string - var zeroValue ExchTime - for i := range resp { - if resp[i].CompletedAt == zeroValue && resp[i].CanceledAt == zeroValue && - resp[i].ProcessedAt == zeroValue { - tID = resp[i].ID - break - } + t.Error(err) } - if tID == "" { - t.Log("TestSendTravelInfoForTransfer skipped due to there being zero pending transfers.") - } else { - _, err = c.SendTravelInfoForTransfer(context.Background(), tID, "GoCryptoTrader", "AU") - if err != nil { - t.Error("CoinBasePro SendTravelInfoForTransfer() error", err) - } + err = c.UpdateTickers(context.Background(), asset.Spot) + if err != nil { + t.Error(err) } - } -func TestWithdrawViaCoinbase(t *testing.T) { - sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) - accID, err := c.GetCoinbaseWallets(context.Background()) - if err != nil { - t.Error("CoinBasePro GetCoinbaseWallets() error", err) - } - if len(accID) == 0 { - t.Fatal("CoinBasePro GetCoinbaseWallets() error, expected a non-empty response") +func TestUpdateTicker(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + _, err := c.UpdateTicker(context.Background(), currency.Pair{}, asset.Empty) + if !errors.Is(err, currency.ErrCurrencyPairEmpty) { + t.Errorf(errExpectMismatch, err, currency.ErrCurrencyPairEmpty) } - _, err = c.WithdrawViaCoinbase(context.Background(), "", accID[1].ID, "BTC", 1) + _, err = c.UpdateTicker(context.Background(), testPair, asset.Spot) if err != nil { - t.Error("CoinBasePro WithdrawViaCoinbase() error", err) + t.Error(err) } } -func TestWithdrawCrypto(t *testing.T) { - sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) - actBool := true - profID, err := c.GetAllProfiles(context.Background(), &actBool) - if err != nil { - t.Error("CoinBasePro GetAllProfiles() error", err) - } - if len(profID) == 0 { - t.Fatal("CoinBasePro GetAllProfiles() error, expected a non-empty response") - } - resp, err := c.WithdrawCrypto(context.Background(), profID[0].ID, "BTC", testAddress, "", "", "bitcoin", 1, - false, false, 2) +func TestFetchTicker(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + _, err := c.FetchTicker(context.Background(), testPair, asset.Spot) if err != nil { - t.Error("CoinBasePro WithdrawCrypto() error", err) + t.Error(err) } - log.Printf("%+v", resp) } -func TestGetWithdrawalFeeEstimate(t *testing.T) { +func TestFetchOrderbook(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - _, err := c.GetWithdrawalFeeEstimate(context.Background(), "", "", "") - if err == nil { - t.Error("CoinBasePro GetWithdrawalFeeEstimate() error, expected error due to empty field") - } - _, err = c.GetWithdrawalFeeEstimate(context.Background(), "BTC", "", "") - if err == nil { - t.Error("CoinBasePro GetWithdrawalFeeEstimate() error, expected error due to empty field") - } - _, err = c.GetWithdrawalFeeEstimate(context.Background(), "BTC", testAddress, "bitcoin") + _, err := c.FetchOrderbook(context.Background(), testPair, asset.Spot) if err != nil { - t.Error("CoinBasePro GetWithdrawalFeeEstimate() error", err) + t.Error(err) } } -func TestWithdrawViaPaymentMethod(t *testing.T) { - sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) - actBool := true - profID, err := c.GetAllProfiles(context.Background(), &actBool) - if err != nil { - t.Error("CoinBasePro GetAllProfiles() error", err) - } - if len(profID) == 0 { - t.Fatal("CoinBasePro GetAllProfiles() error, expected a non-empty response") - } - payID, err := c.GetPayMethods(context.Background()) - if err != nil { - t.Error("CoinBasePro GetPayMethods() error", err) - } - var success bool - i := 0 - for i = range payID { - if payID[i].Type == "ach_bank_account" { - success = true - break - } +func TestUpdateOrderbook(t *testing.T) { + _, err := c.UpdateOrderbook(context.Background(), currency.Pair{}, asset.Empty) + if !errors.Is(err, currency.ErrCurrencyPairEmpty) { + t.Errorf(errExpectMismatch, err, currency.ErrCurrencyPairEmpty) } - if !success { - t.Skip("Skipping test due to no ACH bank account found") + _, err = c.UpdateOrderbook(context.Background(), currency.NewPairWithDelimiter("meow", "woof", "-"), asset.Spot) + if err.Error() != errInvalidProductID { + t.Errorf(errExpectMismatch, err, errInvalidProductID) } - _, err = c.WithdrawViaPaymentMethod(context.Background(), profID[0].ID, payID[i].ID, payID[i].Currency, 1) + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + _, err = c.UpdateOrderbook(context.Background(), testPair, asset.Spot) if err != nil { - t.Error("CoinBasePro WithdrawViaPaymentMethod() error", err) + t.Error(err) } } -func TestGetFees(t *testing.T) { - sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - resp, err := c.GetFees(context.Background()) - if err != nil { - t.Error("CoinBasePro GetFees() error", err) +func TestProcessFundingData(t *testing.T) { + accHist := make([]DeposWithdrData, 1) + genAmcur := AmCur{Amount: 1, Currency: "DOGE"} + accHist[0] = DeposWithdrData{Status: "meow", ID: "woof", PayoutAt: time.Unix(1, 1).UTC(), Amount: genAmcur, + Fee: genAmcur, TransferType: FiatWithdrawal} + genAmcur.Amount = 2 + cryptHist := make([]TransactionData, 2) + cryptHist[0] = TransactionData{Status: "moo", ID: "oink", CreatedAt: time.Unix(2, 2).UTC(), Amount: genAmcur, + Type: "receive"} + cryptHist[0].Network.Name = "neigh" + cryptHist[0].To.ID = "The Barnyard" + cryptHist[1].Type = "send" + + expectedResult := make([]exchange.FundingHistory, 3) + expectedResult[0] = exchange.FundingHistory{ExchangeName: "CoinbasePro", Status: "meow", TransferID: "woof", + Timestamp: time.Unix(1, 1).UTC(), Currency: "DOGE", Amount: 1, Fee: 1, TransferType: "withdrawal"} + expectedResult[1] = exchange.FundingHistory{ExchangeName: "CoinbasePro", Status: "moo", TransferID: "oink", + Timestamp: time.Unix(2, 2).UTC(), Currency: "DOGE", Amount: 2, Fee: 0, TransferType: "deposit", + CryptoFromAddress: "The Barnyard", CryptoChain: "neigh"} + expectedResult[2] = exchange.FundingHistory{ExchangeName: "CoinbasePro", TransferType: "withdrawal"} + + resp := c.processFundingData(accHist, cryptHist) + + if fmt.Sprint(expectedResult) != fmt.Sprint(resp) { + t.Errorf(errExpectMismatch, resp, expectedResult) } - assert.NotEmpty(t, resp, "CoinBasePro GetFees() error, expected a non-empty response") } -func TestGetSignedPrices(t *testing.T) { +func TestGetAccountFundingHistory(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - resp, err := c.GetSignedPrices(context.Background()) + _, err := c.GetAccountFundingHistory(context.Background()) if err != nil { - t.Error("CoinBasePro GetSignedPrices() error", err) + t.Error(err) } - assert.NotEmpty(t, resp, "CoinBasePro GetSignedPrices() error, expected a non-empty response") } -func TestGetOrderbook(t *testing.T) { - _, err := c.GetOrderbook(context.Background(), "", 1) - if err == nil { - t.Error("Coinbase, GetOrderbook() Error, expected an error due to nonexistent pair") - } - _, err = c.GetOrderbook(context.Background(), "There's no way this doesn't cause an error", 1) - if err == nil { - t.Error("Coinbase, GetOrderbook() Error, expected an error due to invalid pair") +func TestGetWithdrawalsHistory(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + _, err := c.GetWithdrawalsHistory(context.Background(), currency.NewCode("meow"), asset.Spot) + if !errors.Is(err, errNoMatchingWallets) { + t.Errorf(errExpectMismatch, err, errNoMatchingWallets) } - resp, err := c.GetOrderbook(context.Background(), testPair.String(), 1) + _, err = c.GetWithdrawalsHistory(context.Background(), currency.BTC, asset.Spot) if err != nil { - t.Error("Coinbase, GetOrderbook() Error", err) - } - assert.NotEmpty(t, resp, "Coinbase, GetOrderbook() Error, expected a non-empty response") -} - -func TestGetStats(t *testing.T) { - _, err := c.GetStats(context.Background(), "") - if err == nil { - t.Error("Coinbase, GetStats() Error, expected an error due to nonexistent pair") - } - resp, err := c.GetStats(context.Background(), testPair.String()) - if err != nil { - t.Error("GetStats() error", err) - } - assert.NotEmpty(t, resp, "Coinbase, GetStats() Error, expected a non-empty response") -} - -func TestGetTrades(t *testing.T) { - _, err := c.GetTrades(context.Background(), "", "", "", 1) - if err == nil { - t.Error("Coinbase, GetTrades() Error, expected an error due to nonexistent pair") - } - resp, err := c.GetTrades(context.Background(), testPair.String(), "", "", 1) - if err != nil { - t.Error("GetTrades() error", err) - } - assert.NotEmpty(t, resp, "Coinbase, GetTrades() Error, expected a non-empty response") -} - -func TestGetAllProfiles(t *testing.T) { - sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - active := true - resp, err := c.GetAllProfiles(context.Background(), &active) - if err != nil { - t.Error("GetAllProfiles() error", err) - } - assert.NotEmpty(t, resp, "Coinbase, GetAllProfiles() Error, expected a non-empty response") -} - -func TestCreateAProfile(t *testing.T) { - sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) - _, err := c.CreateAProfile(context.Background(), "") - if err == nil { - t.Error("Coinbase, CreateAProfile() Error, expected an error due to empty name") - } - _, err = c.CreateAProfile(context.Background(), "default") - if err == nil { - t.Error("CreateAProfile() error, expected an error due to reserved name") - } - t.Skip("Skipping test; seems to always return an internal server error when a non-reserved profile name is sent") - resp, err := c.CreateAProfile(context.Background(), "GCTTestProfile") - if err != nil { - t.Error("CreateAProfile() error", err) - } - assert.NotEmpty(t, resp, "Coinbase, CreateAProfile() Error, expected a non-empty response") -} - -// Cannot d due to there only being one profile, and CreateAProfile not working -func TestTransferBetweenProfiles(t *testing.T) { - sharedtestvalues.SkipTestIfCannotManipulateOrders(t, c, canManipulateRealOrders) - _, err := c.TransferBetweenProfiles(context.Background(), "", "", "", 0) - if err == nil { - t.Error("Coinbase, TransferBetweenProfiles() Error, expected an error due to empty fields") - } - _, err = c.TransferBetweenProfiles(context.Background(), "this test", "has not", "been implemented", 0) - if err == nil { - t.Error("Coinbase, TransferBetweenProfiles() Error, expected an error due to un-implemented test") - } -} - -func TestGetProfileByID(t *testing.T) { - sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - resp, err := c.GetAllProfiles(context.Background(), nil) - if err != nil { - t.Error("GetAllProfiles() error", err) - } - active := true - resp2, err := c.GetProfileByID(context.Background(), resp[0].ID, &active) - if err != nil { - t.Error("GetProfileByID() error", err) - } - assert.NotEmpty(t, resp2, "Coinbase, GetProfileByID() Error, expected a non-empty response") -} - -func TestRenameProfile(t *testing.T) { - sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) - _, err := c.RenameProfile(context.Background(), "", "") - if err == nil { - t.Error("Coinbase, RenameProfile() Error, expected an error due to empty fields") - } - profID, err := c.GetAllProfiles(context.Background(), nil) - if err != nil { - t.Error("GetAllProfiles() error", err) - } - if len(profID) == 0 { - t.Fatal("CoinBasePro GetAllProfiles() error, expected a non-empty response") - } - _, err = c.RenameProfile(context.Background(), profID[0].ID, "margin") - if err == nil { - t.Error("RenameProfile() error, expected an error due to reserved name") - } - t.Skip("Skipping test; seems to always return an internal server error when a non-reserved profile name is sent") - resp, err := c.RenameProfile(context.Background(), profID[0].ID, "GCTTestProfile2") - if err != nil { - t.Error("Coinbase, RenameProfile() Error", err) - } - assert.NotEmpty(t, resp, "Coinbase, RenameProfile() Error, expected a non-empty response") -} - -// Cannot be tested due to there only being one profile, and CreateAProfile not working -func TestDeleteProfile(t *testing.T) { - sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) - _, err := c.DeleteProfile(context.Background(), "", "") - if err == nil { - t.Error("Coinbase, DeleteProfile() Error, expected an error due to empty fields") - } - profID, err := c.GetAllProfiles(context.Background(), nil) - if err != nil { - t.Error("GetAllProfiles() error", err) - } - if len(profID) == 0 { - t.Fatal("CoinBasePro GetAllProfiles() error, expected a non-empty response") - } - _, err = c.DeleteProfile(context.Background(), "profID[0].ID", "this test hasn't been implemented") - if err == nil { - t.Error("Coinbase, DeleteProfile() Error, expected an error due to un-implemented test") - } -} - -func TestGetAllReports(t *testing.T) { - sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - profID, err := c.GetAllProfiles(context.Background(), nil) - if err != nil { - t.Error("GetAllProfiles() error", err) - } - if len(profID) == 0 { - t.Fatal("CoinBasePro GetAllProfiles() error, expected a non-empty response") - } - _, err = c.GetAllReports(context.Background(), profID[0].ID, "account", time.Time{}, 1000, false) - if err != nil { - t.Error("GetAllReports() error", err) - } -} - -func TestCreateReport(t *testing.T) { - sharedtestvalues.SkipTestIfCannotManipulateOrders(t, c, canManipulateRealOrders) - profID, err := c.GetAllProfiles(context.Background(), nil) - if err != nil { - t.Error("GetAllProfiles() error", err) - } - if len(profID) == 0 { - t.Fatal("CoinBasePro GetAllProfiles() error, expected a non-empty response") - } - accID, err := c.GetAllAccounts(context.Background(), 49, "") - if err != nil { - t.Error("CoinBasePro GetAllAccounts() error", err) - } - if len(accID.Accounts) == 0 { - t.Fatal("CoinBasePro GetAllAccounts() error, expected a non-empty response") - } - // _, err = c.CreateReport(context.Background(), "account", "", "pdf", "testemail@thrasher.io", profID[0].ID, - // "", accID[0].ID, time.Time{}, time.Unix(1, 1), time.Now()) - // if err != nil { - // t.Error("Coinbase, CreateReport() error", err) - // } - resp, err := c.CreateReport(context.Background(), "balance", "", "csv", "testemail@thrasher.io", "", - "", "", time.Now(), time.Unix(1, 1), time.Now()) - if err != nil { - t.Error("Coinbase, CreateReport() error", err) - } - assert.NotEmpty(t, resp, "Coinbase, CreateReport() Error, expected a non-empty response") -} - -func TestGetReportByID(t *testing.T) { - sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - _, err := c.GetReportByID(context.Background(), "") - if err == nil { - t.Error("Coinbase, GetReportByID() Error, expected an error due to empty fields") - } - prof, err := c.GetAllProfiles(context.Background(), nil) - if err != nil { - t.Error("GetAllProfiles() error", err) - } - if len(prof) == 0 { - t.Fatal("CoinBasePro GetAllProfiles() error, expected a non-empty response") - } - resp, err := c.GetAllReports(context.Background(), prof[0].ID, "account", time.Time{}, 1000, false) - if err != nil { - t.Error("GetAllReports() error", err) - } - if len(resp) == 0 { - t.Skip("Skipping test due to no reports found") - } - resp2, err := c.GetReportByID(context.Background(), resp[0].ID) - if err != nil { - t.Error("GetReportByID() error", err) - } - assert.NotEmpty(t, resp2, "Coinbase, GetReportByID() Error, expected a non-empty response") -} - -func TestGetTravelRules(t *testing.T) { - sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - _, err := c.GetTravelRules(context.Background(), "", "", "", 0) - if err != nil { - t.Error("GetTravelRules() error", err) - } -} - -func TestCreateTravelRule(t *testing.T) { - // sharedtestvalues.SkipTestIfCannotManipulateOrders(t, c, canManipulateRealOrders) - // resp, err := c.CreateTravelRule(context.Background(), "GCT Travel Rule Test", "GoCryptoTrader", "AU") - // if err != nil && err.Error() != travelRuleDuplicateError { - // t.Error("Coinbase, CreateTravelRule() error", err) - // } - // assert.NotEmpty(t, resp, "Coinbase, CreateTravelRule() Error, expected a non-empty response") -} - -func TestDeleteTravelRule(t *testing.T) { - // sharedtestvalues.SkipTestIfCannotManipulateOrders(t, c, canManipulateRealOrders) - // err := c.DeleteTravelRule(context.Background(), "") - // if err == nil { - // t.Error("Coinbase, DeleteTravelRule() Error, expected an error due to empty ID") - // } - // _, err = c.CreateTravelRule(context.Background(), "GCT Travel Rule Test", "GoCryptoTrader", "AU") - // if err != nil && err.Error() != travelRuleDuplicateError { - // t.Error("Coinbase, CreateTravelRule() error", err) - // } - // resp, err := c.GetTravelRules(context.Background(), "", "", "", 0) - // if err != nil { - // t.Error("GetTravelRules() error", err) - // } - // if len(resp) == 0 { - // t.Fatal("GetTravelRules() error, expected a non-empty response") - // } - // for i := range resp { - // if resp[i].Address == "GCT Travel Rule Test" { - // err = c.DeleteTravelRule(context.Background(), resp[i].ID) - // if err != nil { - // t.Error("Coinbase, DeleteTravelRule() error", err) - // } - // } - // } -} - -func TestGetExchangeLimits(t *testing.T) { - sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - accID, err := c.GetAllAccounts(context.Background(), 49, "") - if err != nil { - t.Error("GetAllAccounts() error", err) - } - if len(accID.Accounts) == 0 { - t.Fatal("CoinBasePro GetAllAccounts() error, expected a non-empty response") - } - resp, err := c.GetExchangeLimits(context.Background(), accID.Accounts[0].UUID) - if err != nil { - t.Error("GetExchangeLimits() error", err) - } - assert.NotEmpty(t, resp, "Coinbase, GetExchangeLimits() Error, expected a non-empty response") -} - -func TestUpdateSettlementPreference(t *testing.T) { - sharedtestvalues.SkipTestIfCannotManipulateOrders(t, c, canManipulateRealOrders) - uID, err := c.GetAllProfiles(context.Background(), nil) - if err != nil { - t.Error("GetAllProfiles() error", err) - } - if len(uID) == 0 { - t.Fatal("CoinBasePro GetAllProfiles() error, expected a non-empty response") - } - resp, err := c.UpdateSettlementPreference(context.Background(), uID[0].UserID, "USD") - if err != nil { - t.Error("Coinbase, UpdateSettlementPreference() error", err) - } - log.Printf("%+v", resp) -} - -func TestGetAllWrappedAssets(t *testing.T) { - resp, err := c.GetAllWrappedAssets(context.Background()) - if err != nil { - t.Error("GetAllWrappedAssets() error", err) - } - assert.NotEmpty(t, resp, "Coinbase, GetAllWrappedAssets() Error, expected a non-empty response") -} - -func TestGetAllStakeWraps(t *testing.T) { - sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - _, err := c.GetAllStakeWraps(context.Background(), "", "ETH", "CBETH", "", time.Time{}, 1) - if err != nil { - t.Error("GetAllStakeWraps() error", err) - } - _, err = c.GetAllStakeWraps(context.Background(), "after", "ETH", "CBETH", "", time.Unix(1, 1), 1000) - if err != nil { - t.Error("GetAllStakeWraps() error", err) - } -} - -func TestCreateStakeWrap(t *testing.T) { - sharedtestvalues.SkipTestIfCannotManipulateOrders(t, c, canManipulateRealOrders) - _, err := c.CreateStakeWrap(context.Background(), "", "", 0) - if err == nil { - t.Error("Coinbase, CreateStakeWrap() Error, expected an error due to empty fields") - } - resp, err := c.CreateStakeWrap(context.Background(), "ETH", "CBETH", 1) - if err != nil { - t.Error("Coinbase, CreateStakeWrap() error", err) - } - log.Printf("%+v", resp) -} - -func TestGetStakeWrapByID(t *testing.T) { - resp, err := c.GetAllStakeWraps(context.Background(), "", "ETH", "CBETH", "", time.Time{}, 1) - if err != nil { - t.Error("GetAllStakeWraps() error", err) - } - if len(resp) == 0 { - t.Skip("No stake wraps found, skipping test") - } - resp2, err := c.GetStakeWrapByID(context.Background(), resp[0].ID) - if err != nil { - t.Error("GetStakeWrapByID() error", err) - } - assert.NotEmpty(t, resp2, "Coinbase, GetStakeWrapByID() Error, expected a non-empty response") - -} - -func TestGetWrappedAssetByID(t *testing.T) { - _, err := c.GetWrappedAssetByID(context.Background(), "") - if err == nil { - t.Error("Coinbase, GetWrappedAssetByID() Error, expected an error due to empty fields") - } - resp, err := c.GetWrappedAssetByID(context.Background(), "CBETH") - if err != nil { - t.Error("GetWrappedAssetByID() error", err) - } - assert.NotEmpty(t, resp, "Coinbase, GetWrappedAssetByID() Error, expected a non-empty response") -} - -func TestGetWrappedAssetConversionRate(t *testing.T) { - _, err := c.GetWrappedAssetConversionRate(context.Background(), "") - if err == nil { - t.Error("Coinbase, GetWrappedAssetConversionRate() Error, expected an error due to empty fields") - } - resp, err := c.GetWrappedAssetConversionRate(context.Background(), "CBETH") - if err != nil { - t.Error("GetWrappedAssetConversionRate() error", err) - } - assert.NotEmpty(t, resp, "Coinbase, GetWrappedAssetConversionRate() Error, expected a non-empty response") -} - -*/ - -// func TestGetCurrentServerTime(t *testing.T) { -// _, err := c.GetCurrentServerTime(context.Background()) -// if err != nil { -// t.Error("GetServerTime() error", err) -// } -// } - -// func TestWrapperGetServerTime(t *testing.T) { -// t.Parallel() -// st, err := c.GetServerTime(context.Background(), asset.Spot) -// if !errors.Is(err, nil) { -// t.Fatalf(errExpectMismatch, err, nil) -// } - -// if st.IsZero() { -// t.Fatal("expected a time") -// } -// } - -// func TestAuthRequests(t *testing.T) { -// t.Parallel() -// sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - -// _, err := c.GetAllAccounts(context.Background(), 49, "") -// if err != nil { -// t.Error("GetAllAccounts() error", err) -// } -// accountResponse, err := c.GetAccountByID(context.Background(), -// "13371337-1337-1337-1337-133713371337") -// if accountResponse.ID != "" { -// t.Error("Expecting no data returned") -// } -// if err == nil { -// t.Error("Expecting error") -// } -// if err == nil { -// t.Error("Expecting error") -// } -// // getHoldsResponse, err := c.GetHolds(context.Background(), -// // "13371337-1337-1337-1337-133713371337") -// // if len(getHoldsResponse) > 0 { -// // t.Error("Expecting no data returned") -// // } -// if err == nil { -// t.Error("Expecting error") -// } -// marginTransferResponse, err := c.MarginTransfer(context.Background(), -// 1, "withdraw", "13371337-1337-1337-1337-133713371337", "BTC") -// if marginTransferResponse.ID != "" { -// t.Error("Expecting no data returned") -// } -// if err == nil { -// t.Error("Expecting error") -// } -// _, err = c.GetPosition(context.Background()) -// if err == nil { -// t.Error("Expecting error") -// } -// _, err = c.ClosePosition(context.Background(), false) -// if err == nil { -// t.Error("Expecting error") -// } -// } - -// func setFeeBuilder() *exchange.FeeBuilder { -// return &exchange.FeeBuilder{ -// Amount: 1, -// FeeType: exchange.CryptocurrencyTradeFee, -// Pair: testPair, -// PurchasePrice: 1, -// } -// } - -// // TestGetFeeByTypeOfflineTradeFee logic test -// func TestGetFeeByTypeOfflineTradeFee(t *testing.T) { -// var feeBuilder = setFeeBuilder() -// _, err := c.GetFeeByType(context.Background(), feeBuilder) -// if err != nil { -// t.Fatal(err) -// } -// if !sharedtestvalues.AreAPICredentialsSet(c) { -// if feeBuilder.FeeType != exchange.OfflineTradeFee { -// t.Errorf("Expected %v, received %v", exchange.OfflineTradeFee, feeBuilder.FeeType) -// } -// } else { -// if feeBuilder.FeeType != exchange.CryptocurrencyTradeFee { -// t.Errorf("Expected %v, received %v", exchange.CryptocurrencyTradeFee, feeBuilder.FeeType) -// } -// } -// } - -// func TestGetFee(t *testing.T) { -// var feeBuilder = setFeeBuilder() - -// if sharedtestvalues.AreAPICredentialsSet(c) { -// // CryptocurrencyTradeFee Basic -// if _, err := c.GetFee(context.Background(), feeBuilder); err != nil { -// t.Error(err) -// } - -// // CryptocurrencyTradeFee High quantity -// feeBuilder = setFeeBuilder() -// feeBuilder.Amount = 1000 -// feeBuilder.PurchasePrice = 1000 -// if _, err := c.GetFee(context.Background(), feeBuilder); err != nil { -// t.Error(err) -// } - -// // CryptocurrencyTradeFee IsMaker -// feeBuilder = setFeeBuilder() -// feeBuilder.IsMaker = true -// if _, err := c.GetFee(context.Background(), feeBuilder); err != nil { -// t.Error(err) -// } - -// // CryptocurrencyTradeFee Negative purchase price -// feeBuilder = setFeeBuilder() -// feeBuilder.PurchasePrice = -1000 -// if _, err := c.GetFee(context.Background(), feeBuilder); err != nil { -// t.Error(err) -// } -// } - -// // CryptocurrencyWithdrawalFee Basic -// feeBuilder = setFeeBuilder() -// feeBuilder.FeeType = exchange.CryptocurrencyWithdrawalFee -// if _, err := c.GetFee(context.Background(), feeBuilder); err != nil { -// t.Error(err) -// } - -// // CryptocurrencyDepositFee Basic -// feeBuilder = setFeeBuilder() -// feeBuilder.FeeType = exchange.CryptocurrencyDepositFee -// if _, err := c.GetFee(context.Background(), feeBuilder); err != nil { -// t.Error(err) -// } - -// // InternationalBankDepositFee Basic -// feeBuilder = setFeeBuilder() -// feeBuilder.FeeType = exchange.InternationalBankDepositFee -// feeBuilder.FiatCurrency = currency.EUR -// if _, err := c.GetFee(context.Background(), feeBuilder); err != nil { -// t.Error(err) -// } - -// // InternationalBankWithdrawalFee Basic -// feeBuilder = setFeeBuilder() -// feeBuilder.FeeType = exchange.InternationalBankWithdrawalFee -// feeBuilder.FiatCurrency = currency.USD -// if _, err := c.GetFee(context.Background(), feeBuilder); err != nil { -// t.Error(err) -// } -// } - -// func TestCalculateTradingFee(t *testing.T) { -// t.Parallel() -// // uppercase -// var volume = []Volume{ -// { -// ProductID: "BTC_USD", -// Volume: 100, -// }, -// } - -// if resp := c.calculateTradingFee(volume, currency.BTC, currency.USD, "_", 1, 1, false); resp != float64(0.003) { -// t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0.003), resp) -// } - -// // lowercase -// volume = []Volume{ -// { -// ProductID: "btc_usd", -// Volume: 100, -// }, -// } - -// if resp := c.calculateTradingFee(volume, currency.BTC, currency.USD, "_", 1, 1, false); resp != float64(0.003) { -// t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0.003), resp) -// } - -// // mixedCase -// volume = []Volume{ -// { -// ProductID: "btc_USD", -// Volume: 100, -// }, -// } - -// if resp := c.calculateTradingFee(volume, currency.BTC, currency.USD, "_", 1, 1, false); resp != float64(0.003) { -// t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0.003), resp) -// } - -// // medium volume -// volume = []Volume{ -// { -// ProductID: "btc_USD", -// Volume: 10000001, -// }, -// } - -// if resp := c.calculateTradingFee(volume, currency.BTC, currency.USD, "_", 1, 1, false); resp != float64(0.002) { -// t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0.002), resp) -// } - -// // high volume -// volume = []Volume{ -// { -// ProductID: "btc_USD", -// Volume: 100000010000, -// }, -// } - -// if resp := c.calculateTradingFee(volume, currency.BTC, currency.USD, "_", 1, 1, false); resp != float64(0.001) { -// t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0.001), resp) -// } - -// // no match -// volume = []Volume{ -// { -// ProductID: "btc_beeteesee", -// Volume: 100000010000, -// }, -// } - -// if resp := c.calculateTradingFee(volume, currency.BTC, currency.USD, "_", 1, 1, false); resp != float64(0) { -// t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) -// } - -// // taker -// volume = []Volume{ -// { -// ProductID: "btc_USD", -// Volume: 100000010000, -// }, -// } - -// if resp := c.calculateTradingFee(volume, currency.BTC, currency.USD, "_", 1, 1, true); resp != float64(0) { -// t.Errorf("GetFee() error. Expected: %f, Received: %f", float64(0), resp) -// } -// } - -// func TestFormatWithdrawPermissions(t *testing.T) { -// expectedResult := exchange.AutoWithdrawCryptoWithAPIPermissionText + " & " + exchange.AutoWithdrawFiatWithAPIPermissionText -// withdrawPermissions := c.FormatWithdrawPermissions() -// if withdrawPermissions != expectedResult { -// t.Errorf("Expected: %s, Received: %s", expectedResult, withdrawPermissions) -// } -// } - -// func TestGetActiveOrders(t *testing.T) { -// var getOrdersRequest = order.MultiOrderRequest{ -// Type: order.AnyType, -// AssetType: asset.Spot, -// Pairs: []currency.Pair{testPair}, -// Side: order.AnySide, -// } - -// _, err := c.GetActiveOrders(context.Background(), &getOrdersRequest) -// if sharedtestvalues.AreAPICredentialsSet(c) && err != nil { -// t.Errorf("Could not get open orders: %s", err) -// } else if !sharedtestvalues.AreAPICredentialsSet(c) && err == nil { -// t.Error("Expecting an error when no keys are set") -// } -// } - -// func TestGetOrderHistory(t *testing.T) { -// var getOrdersRequest = order.MultiOrderRequest{ -// Type: order.AnyType, -// AssetType: asset.Spot, -// Pairs: []currency.Pair{testPair}, -// Side: order.AnySide, -// } - -// _, err := c.GetOrderHistory(context.Background(), &getOrdersRequest) -// if sharedtestvalues.AreAPICredentialsSet(c) && err != nil { -// t.Errorf("Could not get order history: %s", err) -// } else if !sharedtestvalues.AreAPICredentialsSet(c) && err == nil { -// t.Error("Expecting an error when no keys are set") -// } - -// getOrdersRequest.Pairs = []currency.Pair{} -// _, err = c.GetOrderHistory(context.Background(), &getOrdersRequest) -// if sharedtestvalues.AreAPICredentialsSet(c) && err != nil { -// t.Errorf("Could not get order history: %s", err) -// } else if !sharedtestvalues.AreAPICredentialsSet(c) && err == nil { -// t.Error("Expecting an error when no keys are set") -// } - -// getOrdersRequest.Pairs = nil -// _, err = c.GetOrderHistory(context.Background(), &getOrdersRequest) -// if sharedtestvalues.AreAPICredentialsSet(c) && err != nil { -// t.Errorf("Could not get order history: %s", err) -// } else if !sharedtestvalues.AreAPICredentialsSet(c) && err == nil { -// t.Error("Expecting an error when no keys are set") -// } -// } - -// // Any tests below this line have the ability to impact your orders on the exchange. Enable canManipulateRealOrders to run them -// // ---------------------------------------------------------------------------------------------------------------------------- - -// func TestSubmitOrder(t *testing.T) { -// t.Parallel() -// sharedtestvalues.SkipTestIfCannotManipulateOrders(t, c, canManipulateRealOrders) - -// // limit order -// var orderSubmission = &order.Submit{ -// Exchange: c.Name, -// Pair: currency.Pair{ -// Delimiter: "-", -// Base: currency.BTC, -// Quote: currency.USD, -// }, -// Side: order.Buy, -// Type: order.Limit, -// Price: 1, -// Amount: 0.001, -// ClientID: "meowOrder", -// AssetType: asset.Spot, -// } -// response, err := c.SubmitOrder(context.Background(), orderSubmission) -// if sharedtestvalues.AreAPICredentialsSet(c) && (err != nil || response.Status != order.New) { -// t.Errorf("Order failed to be placed: %v", err) -// } else if !sharedtestvalues.AreAPICredentialsSet(c) && err == nil { -// t.Error("Expecting an error when no keys are set") -// } - -// // market order from amount -// orderSubmission = &order.Submit{ -// Exchange: c.Name, -// Pair: currency.Pair{ -// Delimiter: "-", -// Base: currency.BTC, -// Quote: currency.USD, -// }, -// Side: order.Buy, -// Type: order.Market, -// Amount: 0.001, -// ClientID: "meowOrder", -// AssetType: asset.Spot, -// } -// response, err = c.SubmitOrder(context.Background(), orderSubmission) -// if sharedtestvalues.AreAPICredentialsSet(c) && (err != nil || response.Status != order.New) { -// t.Errorf("Order failed to be placed: %v", err) -// } else if !sharedtestvalues.AreAPICredentialsSet(c) && err == nil { -// t.Error("Expecting an error when no keys are set") -// } - -// // market order from quote amount -// orderSubmission = &order.Submit{ -// Exchange: c.Name, -// Pair: currency.Pair{ -// Delimiter: "-", -// Base: currency.BTC, -// Quote: currency.USD, -// }, -// Side: order.Buy, -// Type: order.Market, -// QuoteAmount: 1, -// ClientID: "meowOrder", -// AssetType: asset.Spot, -// } -// response, err = c.SubmitOrder(context.Background(), orderSubmission) -// if sharedtestvalues.AreAPICredentialsSet(c) && (err != nil || response.Status != order.New) { -// t.Errorf("Order failed to be placed: %v", err) -// } else if !sharedtestvalues.AreAPICredentialsSet(c) && err == nil { -// t.Error("Expecting an error when no keys are set") -// } -// } - -// func TestCancelExchangeOrder(t *testing.T) { -// t.Parallel() -// sharedtestvalues.SkipTestIfCannotManipulateOrders(t, c, canManipulateRealOrders) - -// var orderCancellation = &order.Cancel{ -// OrderID: "1", -// WalletAddress: core.BitcoinDonationAddress, -// AccountID: "1", -// Pair: testPair, -// AssetType: asset.Spot, -// } - -// err := c.CancelOrder(context.Background(), orderCancellation) -// if !sharedtestvalues.AreAPICredentialsSet(c) && err == nil { -// t.Error("Expecting an error when no keys are set") -// } -// if sharedtestvalues.AreAPICredentialsSet(c) && err != nil { -// t.Errorf("Could not cancel orders: %v", err) -// } -// } - -// func TestCancelAllExchangeOrders(t *testing.T) { -// t.Parallel() -// sharedtestvalues.SkipTestIfCannotManipulateOrders(t, c, canManipulateRealOrders) - -// var orderCancellation = &order.Cancel{ -// OrderID: "1", -// WalletAddress: core.BitcoinDonationAddress, -// AccountID: "1", -// Pair: testPair, -// AssetType: asset.Spot, -// } - -// resp, err := c.CancelAllOrders(context.Background(), orderCancellation) - -// if !sharedtestvalues.AreAPICredentialsSet(c) && err == nil { -// t.Error("Expecting an error when no keys are set") -// } -// if sharedtestvalues.AreAPICredentialsSet(c) && err != nil { -// t.Errorf("Could not cancel orders: %v", err) -// } - -// if len(resp.Status) > 0 { -// t.Errorf("%v orders failed to cancel", len(resp.Status)) -// } -// } - -// func TestModifyOrder(t *testing.T) { -// t.Parallel() -// sharedtestvalues.SkipTestIfCannotManipulateOrders(t, c, canManipulateRealOrders) - -// _, err := c.ModifyOrder(context.Background(), -// &order.Modify{AssetType: asset.Spot}) -// if err == nil { -// t.Error("ModifyOrder() Expected error") -// } -// } - -// func TestWithdraw(t *testing.T) { -// t.Parallel() -// sharedtestvalues.SkipTestIfCannotManipulateOrders(t, c, canManipulateRealOrders) - -// withdrawCryptoRequest := withdraw.Request{ -// Exchange: c.Name, -// Amount: -1, -// Currency: currency.BTC, -// Description: "WITHDRAW IT ALL", -// Crypto: withdraw.CryptoRequest{ -// Address: core.BitcoinDonationAddress, -// }, -// } - -// _, err := c.WithdrawCryptocurrencyFunds(context.Background(), -// &withdrawCryptoRequest) -// if !sharedtestvalues.AreAPICredentialsSet(c) && err == nil { -// t.Error("Expecting an error when no keys are set") -// } -// if sharedtestvalues.AreAPICredentialsSet(c) && err != nil { -// t.Errorf("Withdraw failed to be placed: %v", err) -// } -// } - -// func TestWithdrawFiat(t *testing.T) { -// t.Parallel() -// sharedtestvalues.SkipTestIfCannotManipulateOrders(t, c, canManipulateRealOrders) - -// var withdrawFiatRequest = withdraw.Request{ -// Amount: 100, -// Currency: currency.USD, -// Fiat: withdraw.FiatRequest{ -// Bank: banking.Account{ -// BankName: "Federal Reserve Bank", -// }, -// }, -// } - -// _, err := c.WithdrawFiatFunds(context.Background(), &withdrawFiatRequest) -// if !sharedtestvalues.AreAPICredentialsSet(c) && err == nil { -// t.Error("Expecting an error when no keys are set") -// } -// if sharedtestvalues.AreAPICredentialsSet(c) && err != nil { -// t.Errorf("Withdraw failed to be placed: %v", err) -// } -// } - -// func TestWithdrawInternationalBank(t *testing.T) { -// t.Parallel() -// sharedtestvalues.SkipTestIfCannotManipulateOrders(t, c, canManipulateRealOrders) - -// var withdrawFiatRequest = withdraw.Request{ -// Amount: 100, -// Currency: currency.USD, -// Fiat: withdraw.FiatRequest{ -// Bank: banking.Account{ -// BankName: "Federal Reserve Bank", -// }, -// }, -// } - -// _, err := c.WithdrawFiatFundsToInternationalBank(context.Background(), -// &withdrawFiatRequest) -// if !sharedtestvalues.AreAPICredentialsSet(c) && err == nil { -// t.Error("Expecting an error when no keys are set") -// } -// if sharedtestvalues.AreAPICredentialsSet(c) && err != nil { -// t.Errorf("Withdraw failed to be placed: %v", err) -// } -// } - -// func TestGetDepositAddress(t *testing.T) { -// _, err := c.GetDepositAddress(context.Background(), currency.BTC, "", "") -// if err == nil { -// t.Error("GetDepositAddress() error", err) -// } -// } - -/* - -// TestWsAuth dials websocket, sends login request. -func TestWsAuth(t *testing.T) { - if !c.Websocket.IsEnabled() && !c.API.AuthenticatedWebsocketSupport || !sharedtestvalues.AreAPICredentialsSet(c) { - t.Skip(stream.WebsocketNotEnabled) - } - var dialer websocket.Dialer - err := c.Websocket.Conn.Dial(&dialer, http.Header{}) - if err != nil { - t.Fatal(err) - } - go c.wsReadData() - - err = c.Subscribe([]stream.ChannelSubscription{ - { - Channel: "user", - Currency: testPair, - }, - }) - if err != nil { - t.Error(err) - } - timer := time.NewTimer(sharedtestvalues.WebsocketResponseDefaultTimeout) - select { - case badResponse := <-c.Websocket.DataHandler: - t.Error(badResponse) - case <-timer.C: - } - timer.Stop() -} - -func TestWsSubscribe(t *testing.T) { - pressXToJSON := []byte(`{ - "type": "subscriptions", - "channels": [ - { - "name": "level2", - "product_ids": [ - "ETH-USD", - "ETH-EUR" - ] - }, - { - "name": "heartbeat", - "product_ids": [ - "ETH-USD", - "ETH-EUR" - ] - }, - { - "name": "ticker", - "product_ids": [ - "ETH-USD", - "ETH-EUR", - "ETH-BTC" - ] - } - ] - }`) - err := c.wsHandleData(pressXToJSON) - if err != nil { - t.Error(err) - } -} - -func TestWsHeartbeat(t *testing.T) { - pressXToJSON := []byte(`{ - "type": "heartbeat", - "sequence": 90, - "last_trade_id": 20, - "product_id": BTC-USD, - "time": "2014-11-07T08:19:28.464459Z" - }`) - err := c.wsHandleData(pressXToJSON) - if err != nil { - t.Error(err) - } -} - -func TestWsStatus(t *testing.T) { - pressXToJSON := []byte(`{ - "type": "status", - "products": [ - { - "id": BTC-USD, - "base_currency": "BTC", - "quote_currency": "USD", - "base_min_size": "0.001", - "base_max_size": "70", - "base_increment": "testAmount", - "quote_increment": "0.01", - "display_name": "BTC/USD", - "status": "online", - "status_message": null, - "min_market_funds": "10", - "max_market_funds": "1000000", - "post_only": false, - "limit_only": false, - "cancel_only": false - } - ], - "currencies": [ - { - "id": "USD", - "name": "United States Dollar", - "min_size": "0.01000000", - "status": "online", - "status_message": null, - "max_precision": "0.01", - "convertible_to": ["USDC"], "details": {} - }, - { - "id": "USDC", - "name": "USD Coin", - "min_size": "0.00000100", - "status": "online", - "status_message": null, - "max_precision": "0.000001", - "convertible_to": ["USD"], "details": {} - }, - { - "id": "BTC", - "name": "Bitcoin", - "min_size": "testAmount", - "status": "online", - "status_message": null, - "max_precision": "testAmount", - "convertible_to": [] - } - ] -}`) - err := c.wsHandleData(pressXToJSON) - if err != nil { - t.Error(err) - } -} - -func TestWsTicker(t *testing.T) { - pressXToJSON := []byte(`{ - "type": "ticker", - "trade_id": 20153558, - "sequence": 3262786978, - "time": "2017-09-02T17:05:49.250000Z", - "product_id": "BTC-USD", - "price": "4388.01000000", - "side": "buy", - "last_size": "0.03000000", - "best_bid": "4388", - "best_ask": "4388.01" -}`) - err := c.wsHandleData(pressXToJSON) - if err != nil { - t.Error(err) - } -} - -func TestWsOrderbook(t *testing.T) { - pressXToJSON := []byte(`{ - "type": "snapshot", - "product_id": "BTC-USD", - "bids": [["10101.10", "0.45054140"]], - "asks": [["10102.55", "0.57753524"]], - "time":"2023-08-15T06:46:55.376250Z" -}`) - err := c.wsHandleData(pressXToJSON) - if err != nil { - t.Error(err) - } - - pressXToJSON = []byte(`{ - "type": "l2update", - "product_id": "BTC-USD", - "time": "2023-08-15T06:46:57.933713Z", - "changes": [ - [ - "buy", - "10101.80000000", - "0.162567" - ] - ] -}`) - err = c.wsHandleData(pressXToJSON) - if err != nil { - t.Error(err) - } -} - -func TestWsOrders(t *testing.T) { - pressXToJSON := []byte(`{ - "type": "received", - "time": "2014-11-07T08:19:27.028459Z", - "product_id": "BTC-USD", - "sequence": 10, - "order_id": "d50ec984-77a8-460a-b958-66f114b0de9b", - "size": "1.34", - "price": "502.1", - "side": "buy", - "order_type": "limit" -}`) - err := c.wsHandleData(pressXToJSON) - if err != nil { - t.Error(err) - } - - pressXToJSON = []byte(`{ - "type": "received", - "time": "2014-11-09T08:19:27.028459Z", - "product_id": "BTC-USD", - "sequence": 12, - "order_id": "dddec984-77a8-460a-b958-66f114b0de9b", - "funds": "3000.234", - "side": "buy", - "order_type": "market" -}`) - err = c.wsHandleData(pressXToJSON) - if err != nil { - t.Error(err) - } - - pressXToJSON = []byte(`{ - "type": "open", - "time": "2014-11-07T08:19:27.028459Z", - "product_id": "BTC-USD", - "sequence": 10, - "order_id": "d50ec984-77a8-460a-b958-66f114b0de9b", - "price": "200.2", - "remaining_size": "1.00", - "side": "sell" -}`) - err = c.wsHandleData(pressXToJSON) - if err != nil { - t.Error(err) - } - - pressXToJSON = []byte(`{ - "type": "done", - "time": "2014-11-07T08:19:27.028459Z", - "product_id": "BTC-USD", - "sequence": 10, - "price": "200.2", - "order_id": "d50ec984-77a8-460a-b958-66f114b0de9b", - "reason": "filled", - "side": "sell", - "remaining_size": "0" -}`) - err = c.wsHandleData(pressXToJSON) - if err != nil { - t.Error(err) - } - - pressXToJSON = []byte(`{ - "type": "match", - "trade_id": 10, - "sequence": 50, - "maker_order_id": "ac928c66-ca53-498f-9c13-a110027a60e8", - "taker_order_id": "132fb6ae-456b-4654-b4e0-d681ac05cea1", - "time": "2014-11-07T08:19:27.028459Z", - "product_id": "BTC-USD", - "size": "5.23512", - "price": "400.23", - "side": "sell" -}`) - err = c.wsHandleData(pressXToJSON) - if err != nil { - t.Error(err) - } - - pressXToJSON = []byte(`{ - "type": "change", - "time": "2014-11-07T08:19:27.028459Z", - "sequence": 80, - "order_id": "ac928c66-ca53-498f-9c13-a110027a60e8", - "product_id": "BTC-USD", - "new_size": "5.23512", - "old_size": "12.234412", - "price": "400.23", - "side": "sell" -}`) - err = c.wsHandleData(pressXToJSON) - if err != nil { - t.Error(err) - } - pressXToJSON = []byte(`{ - "type": "change", - "time": "2014-11-07T08:19:27.028459Z", - "sequence": 80, - "order_id": "ac928c66-ca53-498f-9c13-a110027a60e8", - "product_id": "BTC-USD", - "new_funds": "5.23512", - "old_funds": "12.234412", - "price": "400.23", - "side": "sell" -}`) - err = c.wsHandleData(pressXToJSON) - if err != nil { - t.Error(err) - } - pressXToJSON = []byte(`{ - "type": "activate", - "product_id": "BTC-USD", - "timestamp": "1483736448.299000", - "user_id": "12", - "profile_id": "30000727-d308-cf50-7b1c-c06deb1934fc", - "order_id": "7b52009b-64fd-0a2a-49e6-d8a939753077", - "stop_type": "entry", - "side": "buy", - "stop_price": "80", - "size": "2", - "funds": "50", - "taker_fee_rate": "0.0025", - "private": true -}`) - err = c.wsHandleData(pressXToJSON) - if err != nil { - t.Error(err) - } -} - -func TestStatusToStandardStatus(t *testing.T) { - type TestCases struct { - Case string - Result order.Status - } - testCases := []TestCases{ - {Case: "received", Result: order.New}, - {Case: "open", Result: order.Active}, - {Case: "done", Result: order.Filled}, - {Case: "match", Result: order.PartiallyFilled}, - {Case: "change", Result: order.Active}, - {Case: "activate", Result: order.Active}, - {Case: "LOL", Result: order.UnknownStatus}, - } - for i := range testCases { - result, _ := statusToStandardStatus(testCases[i].Case) - if result != testCases[i].Result { - t.Errorf("Expected: %v, received: %v", testCases[i].Result, result) - } - } -} - -// func TestParseTime(t *testing.T) { -// // Rest examples use 2014-11-07T22:19:28.578544Z" and can be safely -// // unmarhsalled into time.Time - -// // All events except for activate use the above, in the below test -// // we'll use their API docs example -// r := convert.TimeFromUnixTimestampDecimal(1483736448.299000).UTC() -// if r.Year() != 2017 || -// r.Month().String() != "January" || -// r.Day() != 6 { -// t.Error("unexpected result") -// } -// } - -// func TestGetRecentTrades(t *testing.T) { -// t.Parallel() -// _, err := c.GetRecentTrades(context.Background(), testPair, asset.Spot) -// if err != nil { -// t.Error(err) -// } -// } - -// func TestGetHistoricTrades(t *testing.T) { -// t.Parallel() -// _, err := c.GetHistoricTrades(context.Background(), -// testPair, asset.Spot, time.Now().Add(-time.Minute*15), time.Now()) -// if err != nil && err != common.ErrFunctionNotSupported { -// t.Error(err) -// } -// } - -*/ - -func TestSendHTTPRequest(t *testing.T) { - err := c.SendHTTPRequest(context.Background(), exchange.EdgeCase3, "", nil) - if err.Error() != errNoEndpointPathEdgeCase3 { - t.Errorf(errExpectMismatch, err, errNoEndpointPathEdgeCase3) - } -} - -func TestSendAuthenticatedHTTPRequest(t *testing.T) { - fc := &CoinbasePro{} - err := fc.SendAuthenticatedHTTPRequest(context.Background(), exchange.EdgeCase3, "", "", "", nil, false, nil, nil) - if !errors.Is(err, exchange.ErrCredentialsAreEmpty) { - t.Errorf(errExpectMismatch, err, exchange.ErrCredentialsAreEmpty) - } - err = c.SendAuthenticatedHTTPRequest(context.Background(), exchange.EdgeCase3, "", "", "", 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) - if err.Error() != errJsonUnsupportedChan { - t.Errorf(errExpectMismatch, err, errJsonUnsupportedChan) - } -} - -func TestGetFee(t *testing.T) { - _, err := c.GetFee(context.Background(), nil) - if err.Error() != errFeeBuilderNil { - t.Errorf(errExpectMismatch, errFeeBuilderNil, err) - } - feeBuilder := exchange.FeeBuilder{ - FeeType: exchange.OfflineTradeFee, - Amount: 1, - PurchasePrice: 1, - } - resp, err := c.GetFee(context.Background(), &feeBuilder) - if err != nil { - t.Error(err) - } - if resp != 0.008 { - t.Errorf(errExpectMismatch, resp, 0.008) - } - feeBuilder.IsMaker = true - resp, err = c.GetFee(context.Background(), &feeBuilder) - if err != nil { - t.Error(err) - } - if resp != 0.006 { - t.Errorf(errExpectMismatch, resp, 0.006) - } - feeBuilder.Pair = currency.NewPair(currency.USDT, currency.USD) - resp, err = c.GetFee(context.Background(), &feeBuilder) - if err != nil { - t.Error(err) - } - if resp != 0 { - t.Errorf(errExpectMismatch, resp, 0) - } - feeBuilder.IsMaker = false - resp, err = c.GetFee(context.Background(), &feeBuilder) - if err != nil { - t.Error(err) - } - if resp != 0.00001 { - t.Errorf(errExpectMismatch, resp, 0.00001) - } - feeBuilder.FeeType = exchange.CryptocurrencyDepositFee - _, err = c.GetFee(context.Background(), &feeBuilder) - if err != errFeeTypeNotSupported { - t.Errorf(errExpectMismatch, errFeeTypeNotSupported, err) - } - feeBuilder.Pair = currency.Pair{} - sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - feeBuilder.FeeType = exchange.CryptocurrencyTradeFee - resp, err = c.GetFee(context.Background(), &feeBuilder) - if err != nil { - t.Error(err) - } - if !(resp <= 0.008 && resp >= 0.0005) { - t.Errorf(errExpectedFeeRange, 0.0005, 0.008, resp) - } - feeBuilder.IsMaker = true - resp, err = c.GetFee(context.Background(), &feeBuilder) - if err != nil { - t.Error(err) - } - if !(resp <= 0.006 && resp >= 0) { - t.Errorf(errExpectedFeeRange, 0, 0.006, resp) - } -} - -func TestPrepareDateString(t *testing.T) { - t.Parallel() - var expectedResult Params - expectedResult.urlVals = map[string][]string{ - "start_date": {"1970-01-01T00:00:01Z"}, - "end_date": {"1970-01-01T00:00:02Z"}, - } - var result Params - - result.urlVals = make(url.Values) - - labelStart := "start_date" - labelEnd := "end_date" - - err := result.prepareDateString(time.Unix(1, 1).UTC(), time.Unix(2, 2).UTC(), labelStart, labelEnd) - if err != nil { - t.Error(err) - } - if fmt.Sprint(expectedResult) != fmt.Sprint(result) { - t.Errorf(errExpectMismatch, result, expectedResult) - } - - var newTime time.Time - err = result.prepareDateString(newTime, newTime, labelStart, labelEnd) - if err != nil { - t.Error(err) - } - - err = result.prepareDateString(time.Unix(2, 2).UTC(), time.Unix(1, 1).UTC(), labelStart, labelEnd) - if !errors.Is(err, common.ErrStartAfterEnd) { - t.Errorf(errExpectMismatch, err, common.ErrStartAfterEnd) - } -} - -func TestPreparePagination(t *testing.T) { - t.Parallel() - var expectedResult Params - expectedResult.urlVals = map[string][]string{"limit": {"1"}, "order": {"asc"}, "starting_after": {"meow"}, - "ending_before": {"woof"}} - - var result Params - result.urlVals = make(url.Values) - - pagIn := PaginationInp{Limit: 1, OrderAscend: true, StartingAfter: "meow", EndingBefore: "woof"} - - result.preparePagination(pagIn) - - if fmt.Sprint(expectedResult) != fmt.Sprint(result) { - t.Errorf(errExpectMismatch, result, expectedResult) - } -} - -// func TestOrderbookHelper(t *testing.T) { -// t.Parallel() -// req := make(InterOrderDetail, 1) -// req[0][0] = true -// req[0][1] = false -// req[0][2] = true - -// _, err := OrderbookHelper(req, 2) -// if err == nil { -// t.Error("expected unable to type assert price error") -// } -// req[0][0] = "egg" -// _, err = OrderbookHelper(req, 2) -// if err == nil { -// t.Error("expected invalid ParseFloat error") -// } -// req[0][0] = "1.1" -// _, err = OrderbookHelper(req, 2) -// if err == nil { -// t.Error("expected unable to type assert amount error") -// } -// req[0][1] = "meow" -// _, err = OrderbookHelper(req, 2) -// if err == nil { -// t.Error("expected invalid ParseFloat error") -// } -// req[0][1] = "2.2" -// _, err = OrderbookHelper(req, 2) -// if err == nil { -// t.Error("expected unable to type assert number of orders error") -// } -// req[0][2] = 3.3 -// _, err = OrderbookHelper(req, 2) -// if err != nil { -// t.Error(err) -// } -// _, err = OrderbookHelper(req, 3) -// if err == nil { -// t.Error("expected unable to type assert order ID error") -// } -// req[0][2] = "woof" -// _, err = OrderbookHelper(req, 3) -// if err != nil { -// t.Error(err) -// } -// } - -// func TestPrepareDSL(t *testing.T) { -// t.Parallel() -// var expectedResult Params -// expectedResult.urlVals = map[string][]string{ -// "before": {"1"}, -// "limit": {"2"}, -// } -// var result Params - -// result.urlVals = make(url.Values) - -// result.PrepareDSL("before", "1", 2) -// if fmt.Sprint(expectedResult) != fmt.Sprint(result) { -// t.Errorf(errExpectMismatch, result, expectedResult) -// } -// } - -func TestFetchTradablePairs(t *testing.T) { - sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - _, err := c.FetchTradablePairs(context.Background(), asset.Empty) - if !errors.Is(err, asset.ErrNotSupported) { - t.Errorf(errExpectMismatch, err, asset.ErrNotSupported) - } - _, err = c.FetchTradablePairs(context.Background(), asset.Spot) - if err != nil { - t.Error(err) - } - _, err = c.FetchTradablePairs(context.Background(), asset.Futures) - if err != nil { - t.Error(err) - } -} - -func TestUpdateTradablePairs(t *testing.T) { - sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - err := c.UpdateTradablePairs(context.Background(), false) - if err != nil { - t.Error(err) - } -} - -func TestUpdateAccountInfo(t *testing.T) { - sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - _, err := c.UpdateAccountInfo(context.Background(), asset.Spot) - if err != nil { - t.Error(err) - } -} - -func TestFetchAccountInfo(t *testing.T) { - sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - _, err := c.FetchAccountInfo(context.Background(), asset.Spot) - if err != nil { - t.Error(err) - } -} - -func TestUpdateTickers(t *testing.T) { - sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - err := c.UpdateTickers(context.Background(), asset.Futures) - if err != nil { - t.Error(err) - } - err = c.UpdateTickers(context.Background(), asset.Spot) - if err != nil { - t.Error(err) - } -} - -func TestUpdateTicker(t *testing.T) { - sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - _, err := c.UpdateTicker(context.Background(), currency.Pair{}, asset.Empty) - if !errors.Is(err, currency.ErrCurrencyPairEmpty) { - t.Errorf(errExpectMismatch, err, currency.ErrCurrencyPairEmpty) - } - _, err = c.UpdateTicker(context.Background(), testPair, asset.Spot) - if err != nil { - t.Error(err) - } -} - -func TestFetchTicker(t *testing.T) { - sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - _, err := c.FetchTicker(context.Background(), testPair, asset.Spot) - if err != nil { - t.Error(err) - } -} - -func TestFetchOrderbook(t *testing.T) { - sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - _, err := c.FetchOrderbook(context.Background(), testPair, asset.Spot) - if err != nil { - t.Error(err) - } -} - -func TestUpdateOrderbook(t *testing.T) { - sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - _, err := c.UpdateOrderbook(context.Background(), currency.Pair{}, asset.Empty) - if !errors.Is(err, currency.ErrCurrencyPairEmpty) { - t.Errorf(errExpectMismatch, err, currency.ErrCurrencyPairEmpty) - } - _, err = c.UpdateOrderbook(context.Background(), currency.NewPairWithDelimiter("meow", "woof", "-"), asset.Spot) - if err.Error() != errInvalidProductID { - t.Errorf(errExpectMismatch, err, errInvalidProductID) - } - _, err = c.UpdateOrderbook(context.Background(), testPair, asset.Spot) - if err != nil { - t.Error(err) - } -} - -func TestProcessFundingData(t *testing.T) { - accHist := make([]DeposWithdrData, 1) - genAmcur := AmCur{Amount: 1, Currency: "DOGE"} - accHist[0] = DeposWithdrData{Status: "meow", ID: "woof", PayoutAt: time.Unix(1, 1).UTC(), Amount: genAmcur, - Fee: genAmcur, TransferType: FiatWithdrawal} - genAmcur.Amount = 2 - cryptHist := make([]TransactionData, 2) - cryptHist[0] = TransactionData{Status: "moo", ID: "oink", CreatedAt: time.Unix(2, 2).UTC(), Amount: genAmcur, - Type: "receive"} - cryptHist[0].Network.Name = "neigh" - cryptHist[0].To.ID = "The Barnyard" - cryptHist[1].Type = "send" - - expectedResult := make([]exchange.FundingHistory, 3) - expectedResult[0] = exchange.FundingHistory{ExchangeName: "CoinbasePro", Status: "meow", TransferID: "woof", - Timestamp: time.Unix(1, 1).UTC(), Currency: "DOGE", Amount: 1, Fee: 1, TransferType: "withdrawal"} - expectedResult[1] = exchange.FundingHistory{ExchangeName: "CoinbasePro", Status: "moo", TransferID: "oink", - Timestamp: time.Unix(2, 2).UTC(), Currency: "DOGE", Amount: 2, Fee: 0, TransferType: "deposit", - CryptoFromAddress: "The Barnyard", CryptoChain: "neigh"} - expectedResult[2] = exchange.FundingHistory{ExchangeName: "CoinbasePro", TransferType: "withdrawal"} - - resp := c.ProcessFundingData(accHist, cryptHist) - - if fmt.Sprint(expectedResult) != fmt.Sprint(resp) { - t.Errorf(errExpectMismatch, resp, expectedResult) - } -} - -func TestGetAccountFundingHistory(t *testing.T) { - sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - _, err := c.GetAccountFundingHistory(context.Background()) - if err != nil { - t.Error(err) - } -} - -func TestGetWithdrawalsHistory(t *testing.T) { - sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - _, err := c.GetWithdrawalsHistory(context.Background(), currency.NewCode("meow"), asset.Spot) - if !errors.Is(err, errNoMatchingWallets) { - t.Errorf(errExpectMismatch, err, errNoMatchingWallets) - } - _, err = c.GetWithdrawalsHistory(context.Background(), currency.BTC, asset.Spot) - if err != nil { - t.Error(err) + t.Error(err) } } @@ -3703,6 +2026,94 @@ func TestUpdateOrderExecutionLimits(t *testing.T) { } } +func TestFiatTransferTypeString(t *testing.T) { + t.Parallel() + var f FiatTransferType + if f.String() != "deposit" { + t.Errorf(errExpectMismatch, f.String(), "deposit") + } + f = FiatWithdrawal + if f.String() != "withdrawal" { + t.Errorf(errExpectMismatch, f.String(), "withdrawal") + } +} + +func TestUnixTimestampUnmarshalJSON(t *testing.T) { + t.Parallel() + var u UnixTimestamp + err := u.UnmarshalJSON([]byte("0")) + if err.Error() != errJsonNumberIntoString { + t.Errorf(errExpectMismatch, err, errJsonNumberIntoString) + } + err = u.UnmarshalJSON([]byte("\"922337203685477580700\"")) + if err.Error() != errParseIntValueOutOfRange { + t.Errorf(errExpectMismatch, err, errParseIntValueOutOfRange) + } + err = u.UnmarshalJSON([]byte("\"1234\"")) + if err != nil { + t.Error(err) + } +} + +func TestUnixTimestampString(t *testing.T) { + t.Parallel() + var u UnixTimestamp + u.UnmarshalJSON([]byte("\"1234\"")) + s := u.String() + if s != expectedTimestamp { + t.Errorf(errExpectMismatch, s, expectedTimestamp) + } +} + +func TestFormatExchangeKlineInterval(t *testing.T) { + t.Parallel() + resp := formatExchangeKlineInterval(kline.FiveMin) + if resp != granFiveMin { + t.Errorf(errExpectMismatch, resp, granFiveMin) + } + resp = formatExchangeKlineInterval(kline.FifteenMin) + if resp != granFifteenMin { + t.Errorf(errExpectMismatch, resp, granFifteenMin) + } + resp = formatExchangeKlineInterval(kline.ThirtyMin) + if resp != granThirtyMin { + t.Errorf(errExpectMismatch, resp, granThirtyMin) + } + resp = formatExchangeKlineInterval(kline.TwoHour) + if resp != granTwoHour { + t.Errorf(errExpectMismatch, resp, granTwoHour) + } + resp = formatExchangeKlineInterval(kline.SixHour) + if resp != granSixHour { + t.Errorf(errExpectMismatch, resp, granSixHour) + } + resp = formatExchangeKlineInterval(kline.OneDay) + if resp != granOneDay { + t.Errorf(errExpectMismatch, resp, granOneDay) + } + resp = formatExchangeKlineInterval(kline.OneWeek) + if resp != errIntervalNotSupported { + t.Errorf(errExpectMismatch, resp, errIntervalNotSupported) + } +} + +func TestStringToFloatPtr(t *testing.T) { + t.Parallel() + err := stringToFloatPtr(nil, "") + if err != errPointerNil { + t.Errorf(errExpectMismatch, err, errPointerNil) + } + var fl float64 + err = stringToFloatPtr(&fl, "") + if err != nil { + t.Error(err) + } + err = stringToFloatPtr(&fl, "1.1") + if err != nil { + t.Error(err) + } +} + func skipTestIfLowOnFunds(t *testing.T) { accounts, err := c.GetAllAccounts(context.Background(), 250, "") if err != nil { @@ -3799,141 +2210,3 @@ func transferTestHelper(t *testing.T, wallets GetAllWalletsResponse) (string, st } return wID, pmID.Data[0].FiatAccount.ID } - -// 8837708 143.0 ns/op 24 B/op 5 allocs/op -// func BenchmarkXxx(b *testing.B) { -// for x := 0; x < b.N; x++ { -// _ = strconv.FormatInt(60, 10) -// _ = strconv.FormatInt(300, 10) -// _ = strconv.FormatInt(900, 10) -// _ = strconv.FormatInt(3600, 10) -// _ = strconv.FormatInt(21600, 10) -// _ = strconv.FormatInt(86400, 10) -// } -// } - -// 8350056 154.3 ns/op 24 B/op 5 allocs/op -// func BenchmarkXxx2(b *testing.B) { -// for x := 0; x < b.N; x++ { -// _ = strconv.Itoa(60) -// _ = strconv.Itoa(300) -// _ = strconv.Itoa(900) -// _ = strconv.Itoa(3600) -// _ = strconv.Itoa(21600) -// _ = strconv.Itoa(86400) -// } -// } - -// const reportType = "rfq-fills" - -// // Benchmark3Ifs-8 1000000000 0.2556 ns/op 0 B/op 0 allocs/op -// // 1000000000 0.2879 ns/op 0 B/op 0 allocs/op -// // Benchmark3Ifs-8 1000000000 0.2945 ns/op 0 B/op 0 allocs/op -// func Benchmark3Ifs(b *testing.B) { -// a := 0 -// for x := 0; x < b.N; x++ { -// if reportType == "fills" || reportType == "otc-fills" || reportType == "rfq-fills" { -// a++ -// } -// } -// log.Print(a) -// } - -// // BenchmarkNeedle-8 322462062 3.670 ns/op 0 B/op 0 allocs/op -// // BenchmarkNeedle-8 295766910 4.467 ns/op 0 B/op 0 allocs/op -// // BenchmarkNeedle-8 137813607 9.496 ns/op 0 B/op 0 allocs/op -// func BenchmarkNeedle(b *testing.B) { -// a := 0 -// for x := 0; x < b.N; x++ { -// rTCheck := []string{"fills", "otc-fills", "rfq-fills"} -// if common.StringDataCompare(rTCheck, reportType) { -// a++ -// } -// } -// log.Print(a) -// } - -// // BenchmarkIfPrevar-8 537492 2807 ns/op 155 B/op 2 allocs/op -// // BenchmarkIfPrevar-8 534740 2674 ns/op 155 B/op 2 allocs/op -// func BenchmarkIfPrevar(b *testing.B) { -// var str1 string -// for x := 0; x < b.N; x++ { -// if x%2 == 0 { -// str1 = coinbaseDeposits -// } -// if x%2 == 1 { -// str1 = coinbaseWithdrawals -// } -// str2 := fmt.Sprintf("%s%s/%s/%s", coinbaseV2, coinbaseAccounts, "67e0eaec-07d7-54c4-a72c-2e92826897df", -// str1) -// _ = str2 -// } -// } - -// // BenchmarkIfDirSet-8 459709 2634 ns/op 139 B/op 1 allocs/op -// // BenchmarkIfDirSet-8 494604 2576 ns/op 139 B/op 1 allocs/op -// func BenchmarkIfDirSet(b *testing.B) { -// for x := 0; x < b.N; x++ { -// var str2 string -// if x%2 == 0 { -// str2 = fmt.Sprintf("%s%s/%s/%s", coinbaseV2, coinbaseAccounts, "67e0eaec-07d7-54c4-a72c-2e92826897df", -// coinbaseDeposits) -// } -// if x%2 == 1 { -// str2 = fmt.Sprintf("%s%s/%s/%s", coinbaseV2, coinbaseAccounts, "67e0eaec-07d7-54c4-a72c-2e92826897df", -// coinbaseWithdrawals) -// } -// _ = str2 -// } -// } - -// // BenchmarkSwitchPrevar-8 453200 2623 ns/op 155 B/op 2 allocs/op -// // BenchmarkSwitchPrevar-8 556477 2077 ns/op 156 B/op 3 allocs/op -// func BenchmarkSwitchPrevar(b *testing.B) { -// var str1 string -// for x := 0; x < b.N; x++ { -// switch x % 2 { -// case 0: -// str1 = coinbaseDeposits -// case 1: -// str1 = coinbaseWithdrawals -// } -// str2 := fmt.Sprintf("%s%s/%s/%s", coinbaseV2, coinbaseAccounts, "67e0eaec-07d7-54c4-a72c-2e92826897df", -// str1) -// _ = str2 -// } -// } - -// // BenchmarkSwitchDirSet-8 432816 2371 ns/op 139 B/op 1 allocs/op -// // BenchmarkSwitchDirSet-8 544873 2071 ns/op 140 B/op 2 allocs/op -// func BenchmarkSwitchDirSet(b *testing.B) { -// for x := 0; x < b.N; x++ { -// var str2 string -// switch x % 2 { -// case 0: -// str2 = fmt.Sprintf("%s%s/%s/%s", coinbaseV2, coinbaseAccounts, "67e0eaec-07d7-54c4-a72c-2e92826897df", -// coinbaseDeposits) -// case 1: -// str2 = fmt.Sprintf("%s%s/%s/%s", coinbaseV2, coinbaseAccounts, "67e0eaec-07d7-54c4-a72c-2e92826897df", -// coinbaseWithdrawals) -// } -// _ = str2 -// } -// } - -// func AssignStructData(a AmCur) { -// req := map[string]interface{}{"amount": a.Amount, "currency": a.Currency} -// _ = req -// } - -// BenchmarkAssignStructData-8 18488131 65.81 ns/op 0 B/op 0 allocs/op -// BenchmarkAssignStructData-8 18336296 67.94 ns/op 0 B/op 0 allocs/op -// BenchmarkAssignStructData-8 5245032 230.0 ns/op 0 B/op 0 allocs/op -// BenchmarkAssignStructData-8 6194977 193.4 ns/op 0 B/op 0 allocs/op -// func BenchmarkAssignStructData(b *testing.B) { -// for x := 0; x < b.N; x++ { -// AssignStructData(AmCur{}) -// AssignStructData(AmCur{}) -// AssignStructData(AmCur{}) -// } -// } diff --git a/exchanges/coinbasepro/coinbasepro_types.go b/exchanges/coinbasepro/coinbasepro_types.go index a56f9879787..65490fb6fb6 100644 --- a/exchanges/coinbasepro/coinbasepro_types.go +++ b/exchanges/coinbasepro/coinbasepro_types.go @@ -22,14 +22,15 @@ type Version bool // endpoints under version 2 of the API type FiatTransferType bool -// ValCur is a sub-struct used in the type Account +// ValCur is a sub-struct used in the types Account, NativeAndRaw, DetailedPortfolioResponse, +// FuturesBalanceSummary, ListFuturesSweepsResponse, FeeStruct, AmScale, and ConvertResponse type ValCur struct { Value float64 `json:"value,string"` Currency string `json:"currency"` } // Account holds details for a trading account, returned by GetAccountByID and used as -// a sub-struct in the type AllAccountsResponse +// a sub-struct in the types AllAccountsResponse and OneAccountResponse type Account struct { UUID string `json:"uuid"` Name string `json:"name"` @@ -59,14 +60,14 @@ type OneAccountResponse struct { Account Account `json:"account"` } -// PriSiz is a sub-struct used in the type BestBidAsk +// PriSiz is a sub-struct used in the type ProductBook type PriSiz struct { Price float64 `json:"price,string"` Size float64 `json:"size,string"` } // ProductBook holds bid and ask prices for a particular product, returned by GetProductBook -// and used as a sub-struct in the type BestBidAsk +// and used as a sub-struct in the types BestBidAsk and ProductBookResponse type ProductBook struct { ProductID string `json:"product_id"` Bids []PriSiz `json:"bids"` @@ -80,12 +81,13 @@ type BestBidAsk struct { Pricebooks []ProductBook `json:"pricebooks"` } -// ProductBook holds bids and asks for a particular product, returned by GetProductBook +// ProductBookResponse is a temporary struct used for unmarshalling in GetProductBook type ProductBookResponse struct { Pricebook ProductBook `json:"pricebook"` } -// Product holds product information, returned by GetAllProducts and GetProductByID +// Product holds product information, returned by GetProductByID, and used as a sub-struct +// in the type AllProducts type Product struct { ID string `json:"product_id"` Price convert.StringToFloat64 `json:"price"` @@ -154,7 +156,7 @@ type AllProducts struct { } // UnixTimestamp is a type used to unmarshal unix timestamps returned from -// the exchange +// the exchange, used in the type History type UnixTimestamp time.Time // History holds historic rate information, returned by GetHistoricRates @@ -223,7 +225,8 @@ type StopLimitStopLimitGTD struct { StopDirection string `json:"stop_direction"` } -// OrderConfiguration is a struct used in the formation of requests for PlaceOrder +// OrderConfiguration is a struct used in the formation of requests for PlaceOrder, and is +// a sub-struct used in the types PlaceOrderResp and GetOrderResponse type OrderConfiguration struct { MarketMarketIOC *MarketMarketIOC `json:"market_market_ioc,omitempty"` LimitLimitGTC *LimitLimitGTC `json:"limit_limit_gtc,omitempty"` @@ -270,7 +273,7 @@ type EditOrderPreviewResp struct { } // GetOrderResponse contains information on an order, returned by GetOrderByID -// and used in GetAllOrdersResp +// and IterativeGetAllOrders, and used in GetAllOrdersResp type GetOrderResponse struct { OrderID string `json:"order_id"` ProductID string `json:"product_id"` @@ -331,8 +334,8 @@ type FillResponse struct { Cursor string `json:"cursor"` } -// SimplePortfolioData is a sub-struct used in the types AllPortfolioResponse, and -// SimplePortfolioResponse +// SimplePortfolioData is a sub-struct used in the types AllPortfolioResponse, +// SimplePortfolioResponse, and DetailedPortfolioResponse type SimplePortfolioData struct { Name string `json:"name"` UUID string `json:"uuid"` @@ -574,7 +577,7 @@ type AmScale struct { } // ConvertResponse contains information on a convert trade, returned by CreateConvertQuote, -// CommitConvertTrade, and GetConvertTrade +// CommitConvertTrade, and GetConvertTradeByID type ConvertResponse struct { Trade struct { ID string `json:"id"` @@ -639,7 +642,7 @@ type ServerTimeV3 struct { } // IDResource holds an ID, resource type, and associated data, used in ListNotificationsResponse, -// TransactionData +// TransactionData, DeposWithdrData, PaymentMethodData, and PaymentMethod type IDResource struct { ID string `json:"id"` Resource string `json:"resource"` @@ -648,7 +651,8 @@ type IDResource struct { } // PaginationResp holds pagination information, used in ListNotificationsResponse, -// GetAllWalletsResponse, +// GetAllWalletsResponse, GetAllAddrResponse, ManyTransactionsResp, ManyDeposWithdrResp, +// and GetAllPaymentMethodsResp type PaginationResp struct { EndingBefore string `json:"ending_before"` StartingAfter string `json:"starting_after"` @@ -661,7 +665,8 @@ type PaginationResp struct { } // PaginationInp holds information needed to engage in pagination with Sign in With -// Coinbase. Used in ListNotifications, GetAllWallets, GetAllAddresses, +// Coinbase. Used in ListNotifications, GetAllWallets, GetAllAddresses, GetAddressTransactions, +// GetAllTransactions, GetAllFiatTransfers, GetAllPaymentMethods, and preparePagination type PaginationInp struct { Limit uint8 OrderAscend bool @@ -669,6 +674,13 @@ type PaginationInp struct { EndingBefore string } +// AmCur is a sub-struct used in ListNotificationsResponse, WalletData, TransactionData, +// DeposWithdrData, PaymentMethodData, LimitStruct, and PaymentMethod +type AmCur struct { + Amount float64 `json:"amount,string"` + Currency string `json:"currency"` +} + // ListNotificationsResponse holds information on notifications that the user is subscribed // to. Returned by ListNotifications type ListNotificationsResponse struct { @@ -713,13 +725,13 @@ type ListNotificationsResponse struct { } `json:"data"` } -// CodeName holds a code and a name, used in UserResponse and CoinbaseAccounts +// CodeName is a sub-struct holding a code and a name, used in UserResponse and CoinbaseAccounts type CodeName struct { Code string `json:"code"` Name string `json:"name"` } -// UserResponse holds information on a user, returned by GetUserByID and GetCurrentUser +// UserResponse holds information on a user, returned by GetUserByID, GetCurrentUser, and UpdateUser type UserResponse struct { Data struct { ID string `json:"id"` @@ -779,7 +791,7 @@ type AuthResponse struct { } `json:"data"` } -// WalletData holds wallet information, returned by in GenWalletResponse and GetAllWalletsResponse +// WalletData is a sub-struct holding wallet information, used in GenWalletResponse and GetAllWalletsResponse type WalletData struct { ID string `json:"id"` Name string `json:"name"` @@ -820,7 +832,8 @@ type GetAllWalletsResponse struct { Data []WalletData `json:"data"` } -// AddressInfo holds an address and a destination tag, used in AddressData and +// AddressInfo holds an address and a destination tag, used in AddressData, AddAddressRequest, +// AddAddressResponse, and CryptoAddressResponse type AddressInfo struct { Address string `json:"address"` DestinationTag string `json:"destination_tag"` @@ -873,7 +886,7 @@ type AddressData struct { } // GenAddrResponse holds information on a generated address, returned by CreateAddress and -// GetAddressByID. Used in GetAllAddrResponse +// GetAddressByID type GenAddrResponse struct { Data AddressData `json:"data"` } @@ -884,14 +897,8 @@ type GetAllAddrResponse struct { Data []AddressData `json:"data"` } -// AmCur is a sub-type used in TransactionData, LimitStruct -type AmCur struct { - Amount float64 `json:"amount,string"` - Currency string `json:"currency"` -} - // TransactionData is a sub-type that holds information on a transaction. Used in -// ManyTransactionsResp +// ManyTransactionsResp and GenTransactionResp type TransactionData struct { ID string `json:"id"` Type string `json:"type"` @@ -927,13 +934,13 @@ type TransactionData struct { } // ManyTransactionsResp holds information on many transactions. Returned by -// GetAddressTransactions, ListTransactions +// GetAddressTransactions and GetAllTransactions type ManyTransactionsResp struct { Pagination PaginationResp `json:"pagination"` Data []TransactionData `json:"data"` } -// GenTransactionResp holds information on one transaction. Returned by SendMoney, +// GenTransactionResp holds information on one transaction. Returned by SendMoney and GetTransactionByID type GenTransactionResp struct { Data TransactionData `json:"data"` } @@ -957,20 +964,20 @@ type DeposWithdrData struct { TransferType FiatTransferType } -// GenDeposWithdrResp holds information on a deposit. Returned by DepositFunds, CommitDeposit, -// and GetDepositByID +// GenDeposWithdrResp holds information on a deposit. Returned by FiatTransfer, CommitTransfer, and +// GetFiatTransferByID type GenDeposWithdrResp struct { Data DeposWithdrData `json:"data"` } -// ManyDeposWithdrResp holds information on many deposits. Returned by GetAllDeposits +// ManyDeposWithdrResp holds information on many deposits. Returned by GetAllFiatTransfers type ManyDeposWithdrResp struct { Pagination PaginationResp `json:"pagination"` Data []DeposWithdrData `json:"data"` } // PaymentMethodData is a sub-type that holds information on a payment method. Used in -// GenPaymentMethodResp and GetAllPaymentMethodsResp +// GetAllPaymentMethodsResp and GenPaymentMethodResp type PaymentMethodData struct { ID string `json:"id"` Type string `json:"type"` @@ -1052,7 +1059,7 @@ type GetPriceResp struct { } `json:"data"` } -// ServerTimeV2 holds current requested server time information, returned from V2 of the API +// ServerTimeV2 holds current requested server time information, returned by GetV2Time type ServerTimeV2 struct { Data struct { ISO time.Time `json:"iso"` @@ -1060,432 +1067,6 @@ type ServerTimeV2 struct { } `json:"data"` } -// Trade holds executed trade information, returned by GetTrades -type Trade struct { - TradeID int64 `json:"trade_id"` - Price float64 `json:"price,string"` - Size float64 `json:"size,string"` - Time time.Time `json:"time"` - Side string `json:"side"` -} - -// Stats holds 30 day and 24 hr data for a currency pair, returned by GetStats -type Stats struct { - Open float64 `json:"open,string"` - High float64 `json:"high,string"` - Low float64 `json:"low,string"` - Last float64 `json:"last,string"` - Volume float64 `json:"volume,string"` - Volume30Day float64 `json:"volume_30day,string"` -} - -// Currency holds information on a currency, returned by GetAllCurrencies and GetCurrencyByID -type Currency struct { - ID string `json:"id"` - Name string `json:"name"` - MinSize float64 `json:"min_size,string"` - Status string `json:"status"` - Message string `json:"message"` - MaxPrecision float64 `json:"max_precision,string"` - ConvertibleTo []string `json:"convertible_to"` - Details struct { - Type string `json:"type"` - Symbol string `json:"symbol"` - NetworkConfirmations int32 `json:"network_confirmations"` - SortOrder int32 `json:"sort_order"` - CryptoAddressLink string `json:"crypto_address_link"` - CryptoTransactionLink string `json:"crypto_transaction_link"` - PushPaymentMethods []string `json:"push_payment_methods"` - GroupTypes []string `json:"group_types"` - DisplayName string `json:"display_name"` - ProcessingTimeSeconds float64 `json:"processing_time_seconds"` - MinWithdrawalAmount float64 `json:"min_withdrawal_amount"` - MaxWithdrawalAmount float64 `json:"max_withdrawal_amount"` - } `json:"details"` - DefaultNetwork string `json:"default_network"` - SupportedNetworks []struct { - ID string `json:"id"` - Name string `json:"name"` - Status string `json:"status"` - ContactAddress string `json:"contact_address"` - CryptoAddressLink string `json:"crypto_address_link"` - CryptoTransactionLink string `json:"crypto_transaction_link"` - MinWithdrawalAmount float64 `json:"min_withdrawal_amount"` - MaxWithdrawalAmount float64 `json:"max_withdrawal_amount"` - NetworkConfirmations int32 `json:"network_confirmations"` - ProcessingTimeSeconds int32 `json:"processing_time_seconds"` - } `json:"supported_networks"` -} - -// AccountLedgerResponse holds account history information, returned by GetAccountLedger -type AccountLedgerResponse struct { - ID string `json:"id"` - Amount float64 `json:"amount,string"` - CreatedAt time.Time `json:"created_at"` - Balance float64 `json:"balance,string"` - Type string `json:"type"` - Details struct { - OrderID string `json:"order_id"` - ProductID string `json:"product_id"` - TradeID string `json:"trade_id"` - TransferID string `json:"transfer_id"` - TransferType string `json:"transfer_type"` - } `json:"details"` -} - -// AccountHolds contains information on a hold, returned by GetHolds -type AccountHolds struct { - ID string `json:"id"` - CreatedAt time.Time `json:"created_at"` - Amount float64 `json:"amount,string"` - UpdatedAt time.Time `json:"updated_at"` - Type string `json:"type"` - Reference string `json:"ref"` -} - -// Funding holds funding data -// type Funding struct { -// ID string `json:"id"` -// OrderID string `json:"order_id"` -// ProfileID string `json:"profile_id"` -// Amount float64 `json:"amount,string"` -// Status string `json:"status"` -// CreatedAt time.Time `json:"created_at"` -// Currency string `json:"currency"` -// RepaidAmount float64 `json:"repaid_amount"` -// DefaultAmount float64 `json:"default_amount,string"` -// RepaidDefault bool `json:"repaid_default"` -// } - -// MarginTransfer holds margin transfer details -// type MarginTransfer struct { -// CreatedAt time.Time `json:"created_at"` -// ID string `json:"id"` -// UserID string `json:"user_id"` -// ProfileID string `json:"profile_id"` -// MarginProfileID string `json:"margin_profile_id"` -// Type string `json:"type"` -// Amount float64 `json:"amount,string"` -// Currency string `json:"currency"` -// AccountID string `json:"account_id"` -// MarginAccountID string `json:"margin_account_id"` -// MarginProductID string `json:"margin_product_id"` -// Status string `json:"status"` -// Nonce int `json:"nonce"` -// } - -// AccountOverview holds account information returned from position -// type AccountOverview struct { -// Status string `json:"status"` -// Funding struct { -// MaxFundingValue float64 `json:"max_funding_value,string"` -// FundingValue float64 `json:"funding_value,string"` -// OldestOutstanding struct { -// ID string `json:"id"` -// OrderID string `json:"order_id"` -// CreatedAt time.Time `json:"created_at"` -// Currency string `json:"currency"` -// AccountID string `json:"account_id"` -// Amount float64 `json:"amount,string"` -// } `json:"oldest_outstanding"` -// } `json:"funding"` -// Accounts struct { -// LTC Account `json:"LTC"` -// ETH Account `json:"ETH"` -// USD Account `json:"USD"` -// BTC Account `json:"BTC"` -// } `json:"accounts"` -// MarginCall struct { -// Active bool `json:"active"` -// Price float64 `json:"price,string"` -// Side string `json:"side"` -// Size float64 `json:"size,string"` -// Funds float64 `json:"funds,string"` -// } `json:"margin_call"` -// UserID string `json:"user_id"` -// ProfileID string `json:"profile_id"` -// Position struct { -// Type string `json:"type"` -// Size float64 `json:"size,string"` -// Complement float64 `json:"complement,string"` -// MaxSize float64 `json:"max_size,string"` -// } `json:"position"` -// ProductID string `json:"product_id"` -// } - -// Account is a sub-type for account overview -// type Account struct { -// ID string `json:"id"` -// Balance float64 `json:"balance,string"` -// Hold float64 `json:"hold,string"` -// FundedAmount float64 `json:"funded_amount,string"` -// DefaultAmount float64 `json:"default_amount,string"` -// } - -// LimitStruct is a sub-type used in PaymentMethod -type LimitStruct struct { - PeriodInDays int `json:"period_in_days"` - Total AmCur `json:"total"` - Remaining AmCur `json:"remaining"` -} - -// PaymentMethod holds payment method information, returned by GetPayMethods -type PaymentMethod struct { - ID string `json:"id"` - Type string `json:"type"` - Name string `json:"name"` - Currency string `json:"currency"` - PrimaryBuy bool `json:"primary_buy"` - PrimarySell bool `json:"primary_sell"` - InstantBuy bool `json:"instant_buy"` - InstantSell bool `json:"instant_sell"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` - Resource string `json:"resource"` - ResourcePath string `json:"resource_path"` - Verified bool `json:"verified"` - Limits struct { - Type string `json:"type"` - Name string `json:"name"` - Buy []LimitStruct `json:"buy"` - InstantBuy []LimitStruct `json:"instant_buy"` - Sell []LimitStruct `json:"sell"` - } `json:"limits"` - AllowBuy bool `json:"allow_buy"` - AllowSell bool `json:"allow_sell"` - AllowDeposit bool `json:"allow_deposit"` - AllowWithdraw bool `json:"allow_withdraw"` - FiatAccount IDResource `json:"fiat_account"` - CryptoAccount IDResource `json:"crypto_account"` - AvailableBalance struct { - Amount float64 `json:"amount"` - Currency string `json:"currency"` - Scale string `json:"scale"` - } `json:"available_balance"` - PickerData struct { - Symbol string `json:"symbol"` - CustomerName string `json:"customer_name"` - AccountName string `json:"account_name"` - AccountNumber string `json:"account_number"` - AccountType string `json:"account_type"` - InstitutionCode string `json:"institution_code"` - InstitutionName string `json:"institution_name"` - Iban string `json:"iban"` - Swift string `json:"swift"` - PaypalEmail string `json:"paypal_email"` - PaypalOwner string `json:"paypal_owner"` - RoutingNumber string `json:"routing_number"` - InstitutionIdentifier string `json:"institution_identifier"` - BankName string `json:"bank_name"` - BranchName string `json:"branch_name"` - IconURL string `json:"icon_url"` - Balance AmCur `json:"balance"` - } `json:"picker_data"` - HoldBusinessDays int64 `json:"hold_business_days"` - HoldDays int64 `json:"hold_days"` - VerificationMethod string `json:"verification_method"` - CDVStatus string `json:"cdv_status"` -} - -// LimitInfo is a sub-type for payment method -// type LimitInfo struct { -// PeriodInDays int `json:"period_in_days"` -// Total struct { -// Amount float64 `json:"amount,string"` -// Currency string `json:"currency"` -// } `json:"total"` -// } - -// DepositWithdrawalInfo holds information provided when depositing or -// withdrawing from payment methods. Returned by DepositViaCoinbase, -// DepositViaPaymentMethod, WithdrawViaCoinbase, WithdrawCrypto, and -// WithdrawViaPaymentMethod -type DepositWithdrawalInfo struct { - ID string `json:"id"` - Amount float64 `json:"amount,string"` - Currency string `json:"currency"` - PayoutAt time.Time `json:"payout_at"` - Fee float64 `json:"fee,string"` - Subtotal float64 `json:"subtotal,string"` -} - -// CoinbaseAccounts holds coinbase account information, returned by GetCoinbaseWallets -type CoinbaseAccounts struct { - ID string `json:"id"` - Name string `json:"name"` - Balance float64 `json:"balance,string"` - Currency string `json:"currency"` - Type string `json:"type"` - Primary bool `json:"primary"` - Active bool `json:"active"` - AvailableOnConsumer bool `json:"available_on_consumer"` - Ready bool `json:"ready"` - WireDepositInformation struct { - AccountNumber string `json:"account_number"` - RoutingNumber string `json:"routing_number"` - BankName string `json:"bank_name"` - BankAddress string `json:"bank_address"` - BankCountry CodeName `json:"bank_country"` - AccountName string `json:"account_name"` - AccountAddress string `json:"account_address"` - Reference string `json:"reference"` - } `json:"wire_deposit_information"` - SwiftDepositInformation struct { - AccountNumber string `json:"account_number"` - BankName string `json:"bank_name"` - BankAddress string `json:"bank_address"` - BankCountry CodeName `json:"bank_country"` - AccountName string `json:"account_name"` - AccountAddress string `json:"account_address"` - Reference string `json:"reference"` - } `json:"swift_deposit_information"` - SepaDepositInformation struct { - Iban string `json:"iban"` - Swift string `json:"swift"` - BankName string `json:"bank_name"` - BankAddress string `json:"bank_address"` - BankCountryName string `json:"bank_country_name"` - AccountName string `json:"account_name"` - AccountAddress string `json:"account_address"` - Reference string `json:"reference"` - } `json:"sepa_deposit_information"` - UkDepositInformation struct { - SortCode string `json:"sort_code"` - AccountNumber string `json:"account_number"` - BankName string `json:"bank_name"` - AccountName string `json:"account_name"` - Reference string `json:"reference"` - } `json:"uk_deposit_information"` - DestinationTagName string `json:"destination_tag_name"` - DestinationTagRegex string `json:"destination_tag_regex"` - HoldBalance float64 `json:"hold_balance,string"` - HoldCurrency string `json:"hold_currency"` -} - -// Report holds information on user-generated reports, returned by GetAllReports -// and GetReportByID -type Report struct { - ID string `json:"id"` - Type string `json:"type"` - CreatedAt time.Time `json:"created_at"` - CompletedAt time.Time `json:"completed_at"` - ExpiresAt time.Time `json:"expires_at"` - Status string `json:"status"` - UserID string `json:"user_id"` - FileURL string `json:"file_url"` - Params struct { - StartDate time.Time `json:"start_date"` - EndDate time.Time `json:"end_date"` - Format string `json:"format"` - ProductID string `json:"product_id"` - AccountID string `json:"account_id"` - ProfileID string `json:"profile_id"` - Email string `json:"email"` - User struct { - ID string `json:"id"` - CreatedAt time.Time `json:"created_at"` - ActiveAt time.Time `json:"active_at"` - Name string `json:"name"` - Email string `json:"email"` - Roles []interface{} `json:"roles"` - IsBanned bool `json:"is_banned"` - Permissions interface{} `json:"permissions"` - UserType string `json:"user_type"` - FulfillsNewRequirements bool `json:"fulfills_new_requirements"` - Flags interface{} `json:"flags"` - Details interface{} `json:"details"` - DefaultProfileID string `json:"default_profile_id"` - OauthClient string `json:"oauth_client"` - Preferences struct { - PreferredMarket string `json:"preferred_market"` - MarginTermsCompletedInUTC time.Time `json:"margin_terms_completed_in_utc"` - MarginTutorialCompletedInUTC time.Time `json:"margin_tutorial_completed_in_utc"` - } `json:"preferences"` - HasDefault bool `json:"has_default"` - OrgID interface{} `json:"org_id"` - IsBrokerage bool `json:"is_brokerage"` - TaxDomain string `json:"tax_domain"` - ProfileLimit uint16 `json:"profile_limit"` - APIKeyLimit uint16 `json:"api_key_limit"` - ConnectionLimit uint16 `json:"connection_limit"` - RateLimit uint16 `json:"rate_limit"` - GlobalConnectionLimit uint16 `json:"global_connection_limit"` - SettlementPreference interface{} `json:"settlement_preference"` - PrimeLendingEntityID interface{} `json:"prime_lending_entity_id"` - StateCode string `json:"state_code"` - CBDataFromCache bool `json:"cb_data_from_cache"` - TwoFactorMethod string `json:"two_factor_method"` - LegalName string `json:"legal_name"` - TermsAccepted time.Time `json:"terms_accepted"` - HasClawbackPaymentPending bool `json:"has_clawback_payment_pending"` - HasRestrictedAssets bool `json:"has_restricted_assets"` - } `json:"user"` - NewYorkState bool `json:"new_york_state"` - DateTime time.Time `json:"date_time"` - GroupByProfile bool `json:"group_by_profile"` - } `json:"params"` - FileCount uint64 `json:"file_count"` -} - -// Volume type contains trailing volume information -// type Volume struct { -// ProductID string `json:"product_id"` -// ExchangeVolume float64 `json:"exchange_volume,string"` -// Volume float64 `json:"volume,string"` -// RecordedAt string `json:"recorded_at"` -// } - -// InterOrderDetail is used to make intermediary orderbook handling easier -type InterOrderDetail [][3]interface{} - -// OrderbookIntermediaryResponse is used while processing the orderbook -type OrderbookIntermediaryResponse struct { - Bids InterOrderDetail `json:"bids"` - Asks InterOrderDetail `json:"asks"` - Sequence float64 `json:"sequence"` - AuctionMode bool `json:"auction_mode"` - Auction struct { - OpenPrice float64 `json:"open_price,string"` - OpenSize float64 `json:"open_size,string"` - BestBidPrice float64 `json:"best_bid_price,string"` - BestBidSize float64 `json:"best_bid_size,string"` - BestAskPrice float64 `json:"best_ask_price,string"` - BestAskSize float64 `json:"best_ask_size,string"` - AuctionState string `json:"auction_state"` - CanOpen string `json:"can_open"` - Time time.Time `json:"time"` - } - Time time.Time `json:"time"` -} - -// GenOrderDetail is a subtype used for the final state of the orderbook -type GenOrderDetail struct { - Price float64 - Amount float64 - NumOrders float64 - OrderID string -} - -// OrderbookResponse is the final state of the orderbook, returned by GetOrderbook -type OrderbookFinalResponse struct { - Bids []GenOrderDetail `json:"bids"` - Asks []GenOrderDetail `json:"asks"` - Sequence float64 `json:"sequence"` - AuctionMode bool `json:"auction_mode"` - Auction struct { - OpenPrice float64 `json:"open_price,string"` - OpenSize float64 `json:"open_size,string"` - BestBidPrice float64 `json:"best_bid_price,string"` - BestBidSize float64 `json:"best_bid_size,string"` - BestAskPrice float64 `json:"best_ask_price,string"` - BestAskSize float64 `json:"best_ask_size,string"` - AuctionState string `json:"auction_state"` - CanOpen string `json:"can_open"` - Time time.Time `json:"time"` - } - Time time.Time `json:"time"` -} - // WebsocketSubscribe takes in subscription information type WebsocketSubscribe struct { Type string `json:"type"` @@ -1615,266 +1196,7 @@ type wsStatus struct { Type string `json:"type"` } -// RequestParamsTimeForceType Time in force -// type RequestParamsTimeForceType string - -// var ( -// // CoinbaseRequestParamsTimeGTC GTC -// CoinbaseRequestParamsTimeGTC = RequestParamsTimeForceType("GTC") - -// // CoinbaseRequestParamsTimeIOC IOC -// CoinbaseRequestParamsTimeIOC = RequestParamsTimeForceType("IOC") -// ) - -// TransferResponse contains information on a transfer, returned by GetAccountTransfers, -// GetAllTransfers, and GetTransferByID -type TransferResponse struct { - ID string `json:"id"` - Type string `json:"type"` - CreatedAt ExchTime `json:"created_at"` - CompletedAt ExchTime `json:"completed_at"` - CanceledAt ExchTime `json:"canceled_at"` - ProcessedAt ExchTime `json:"processed_at"` - AccountID string `json:"account_id"` - UserID string `json:"user_id"` - Amount float64 `json:"amount,string"` - Details struct { - CoinbasePayoutAt time.Time `json:"coinbase_payout_at"` - CoinbaseAccountID string `json:"coinbase_account_id"` - CoinbaseTransactionID string `json:"coinbase_transaction_id"` - CoinbaseDepositID string `json:"coinbase_deposit_id"` - CoinbasePaymentMethodID string `json:"coinbase_payment_method_id"` - CoinbasePaymentMethodType string `json:"coinbase_payment_method_type"` - } `json:"details"` - UserNonce int64 `json:"user_nonce"` - ProfileID string `json:"profile_id"` - Currency string `json:"currency"` - Idem string `json:"idem"` -} - -type ExchTime time.Time - -// TravelRule contains information on a travel rule, returned by GetTravelRules -// and CreateTravelRule -type TravelRule struct { - ID string `json:"id"` - CreatedAt time.Time `json:"created_at"` - Address string `json:"address"` - OriginName string `json:"originator_name"` - OriginCountry string `json:"originator_country"` -} - // Params is used within functions to make the setting of parameters easier type Params struct { urlVals url.Values } - -// GetAddressResponse contains information on addresses, returned by GetAddressBook -type GetAddressResponse struct { - ID string `json:"id"` - Address string `json:"address"` - DestinationTag string `json:"destination_tag"` - Currency string `json:"currency"` - Label string `json:"label"` - AddressBookAddedAt time.Time `json:"address_book_added_at"` - LastUsed time.Time `json:"last_used"` - VerifiedSelfHosted bool `json:"is_verified_self_hosted_wallet"` - VASPID string `json:"vasp_id"` -} - -// AddAddressRequest is the struct expected by the exchange for the AddAddresses function -type AddAddressRequest struct { - Currency string `json:"currency"` - AddressInfo AddressInfo `json:"to"` - Label string `json:"label"` - VerifiedSelfHosted bool `json:"is_verified_self_hosted_wallet"` - VaspID string `json:"vasp_id"` -} - -// AddAddressResponse contains information on the addresses just added, returned by -// AddAddresses -type AddAddressResponse struct { - ID string `json:"id"` - Address string `json:"address"` - AddressInfo struct { - Address string `json:"address"` - DisplayAddress string `json:"display_address"` - DestinationTag string `json:"destination_tag"` - } `json:"address_info"` - Currency string `json:"currency"` - Label string `json:"label"` - DisplayAddress string `json:"display_address"` - Trusted bool `json:"trusted"` - AddressBooked bool `json:"address_booked"` - AddressBookAddedAt time.Time `json:"address_book_added_at"` - LastUsed time.Time `json:"last_used"` - AddressBookEntryPendingUntil time.Time `json:"address_book_entry_pending_until"` - VerifiedSelfHosted bool `json:"is_verified_self_hosted_wallet"` - VaspID string `json:"vasp_id"` -} - -// CryptoAddressResponse contains information on the one-time address generated for -// depositing crypto, returned by GenerateCryptoAddress -type CryptoAddressResponse struct { - ID string `json:"id"` - Address string `json:"address"` - AddressInfo AddressInfo `json:"address_info"` - Name string `json:"name"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` - Network string `json:"network"` - URIScheme string `json:"uri_scheme"` - Resource string `json:"resource"` - ResourcePath string `json:"resource_path"` - ExchangeDepositAddress bool `json:"exchange_deposit_address"` - Warnings []struct { - Title string `json:"title"` - Details string `json:"details"` - ImageURL string `json:"image_url"` - } `json:"warnings"` - LegacyAddress string `json:"legacy_address"` - DestinationTag string `json:"destination_tag"` - DepositURI string `json:"deposit_uri"` - CallbackURL string `json:"callback_url"` -} - -// WithdrawalFeeEstimate is the exchange's estimate of the fee for a withdrawal, returned -// by GetWithdrawalFeeEstimate -type WithdrawalFeeEstimate struct { - Fee float64 `json:"fee"` - FeeBeforeSubsidy float64 `json:"fee_before_subsidy"` -} - -// FeeResponse contains current taker and maker fee rates, as well as 30-day trailing -// volume. Returned by GetFees -type FeeResponse struct { - TakerFeeRate float64 `json:"taker_fee_rate,string"` - MakerFeeRate float64 `json:"maker_fee_rate,string"` - USDVolume float64 `json:"usd_volume,string"` -} - -// PriceMap is used to properly unmarshal the response from GetSignedPrices -type PriceMap map[string]float64 - -// SignedPrices contains cryptographically signed prices, alongside other information -// necessary for them to be posted on-chain using Compound's Open Oracle smart contract. -// Returned by GetSignedPrices -type SignedPrices struct { - Timestamp string `json:"timestamp"` - Messages []string `json:"messages"` - Signatures []string `json:"signatures"` - Prices PriceMap `json:"prices"` -} - -// Profile contains information on a profile. Returned by GetAllProfiles, CreateAProfile, -// GetProfileByID, and RenameProfile -type Profile struct { - ID string `json:"id"` - UserID string `json:"user_id"` - Name string `json:"name"` - Active bool `json:"active"` - IsDefault bool `json:"is_default"` - CreatedAt time.Time `json:"created_at"` -} - -// CreateReportResponse contains information on a newly-created report, returned by -// CreateReport -type CreateReportResponse struct { - ID string `json:"id"` - Type string `json:"type"` - Status string `json:"status"` -} - -// ReportBalanceStruct is used internally when crafting a CreateReport request -type ReportBalanceStruct struct { - DateTime string `json:"datetime"` -} - -// ReportFillsTaxStruct is used internally when crafting a CreateReport request -type ReportFillsTaxStruct struct { - StartDate string `json:"start_date"` - EndDate string `json:"end_date"` - ProductID string `json:"product_id"` -} - -// ReportAccountStruct is used internally when crafting a CreateReport request -type ReportAccountStruct struct { - StartDate string `json:"start_date"` - EndDate string `json:"end_date"` - AccountID string `json:"account_id"` -} - -// MaxRemSubStruct is a sub-type used in CurListSubStruct, which is itself used in ExchangeLimits -type MaxRemSubStruct struct { - Max float64 `json:"max"` - Remaining float64 `json:"remaining"` -} - -// CurListSubStruct is a sub-type used in ExchangeLimits -type CurListSubStruct struct { - USD MaxRemSubStruct `json:"usd"` - EUR MaxRemSubStruct `json:"eur"` - GBP MaxRemSubStruct `json:"gbp"` - BTC MaxRemSubStruct `json:"btc"` - ETH MaxRemSubStruct `json:"eth"` -} - -// ExchangeLimits contains information on payment method transfer limits, returned -// by GetExchangeLimits -type ExchangeLimits struct { - TransferLimits struct { - Buy CurListSubStruct `json:"buy"` - Sell CurListSubStruct `json:"sell"` - ExchangeWithdraw CurListSubStruct `json:"exchange_withdraw"` - Ach CurListSubStruct `json:"ach"` - InstantBuy CurListSubStruct `json:"instant_buy"` - AchNoBalance CurListSubStruct `json:"ach_no_balance"` - CreditDebitCard CurListSubStruct `json:"credit_debit_card"` - Secure3DBuy CurListSubStruct `json:"secure3d_buy"` - PaypalBuy CurListSubStruct `json:"paypal_buy"` - PaypalWithdrawal CurListSubStruct `json:"paypal_withdrawal"` - IdealDeposit CurListSubStruct `json:"ideal_deposit"` - SofortDeposit CurListSubStruct `json:"sofort_deposit"` - InstantAchWithdrawal CurListSubStruct `json:"instant_ach_withdrawal"` - } `json:"transfer_limits"` - LimitCurrency string `json:"limit_currency"` -} - -// WrappedAssetResponse contains information on a wrapped asset, returned by -// GetWrappedAssetByID -type WrappedAssetResponse struct { - ID string `json:"id"` - CirculatingSupply float64 `json:"circulating_supply,string"` - TotalSupply float64 `json:"total_supply,string"` - ConversionRate float64 `json:"conversion_rate,string"` - APY convert.StringToFloat64 `json:"apy"` -} - -// AllWrappedAssetResponse contains information on all wrapped assets, returned by -// GetAllWrappedAssets -type AllWrappedAssetResponse struct { - WrappedAssetResponse []WrappedAssetResponse `json:"wrapped_assets"` -} - -// StakeWrap contains information on a stake wrap, returned by GetAllStakeWraps and -// GetStakeWrapByID -type StakeWrap struct { - ID string `json:"id"` - FromAmount float64 `json:"from_amount,string"` - ToAmount float64 `json:"to_amount,string"` - FromAccountID string `json:"from_account_id"` - ToAccountID string `json:"to_account_id"` - FromCurrency string `json:"from_currency"` - ToCurrency string `json:"to_currency"` - Status string `json:"status"` - ConversionRate float64 `json:"conversion_rate,string"` - CreatedAt time.Time `json:"created_at"` - CompletedAt time.Time `json:"completed_at"` - CanceledAt time.Time `json:"canceled_at"` -} - -// WrappedAssetConversionRate contains the conversion rate for a wrapped asset, returned -// by GetWrappedAssetConversionRate -type WrappedAssetConversionRate struct { - Amount float64 `json:"amount,string"` -} diff --git a/exchanges/coinbasepro/coinbasepro_wrapper.go b/exchanges/coinbasepro/coinbasepro_wrapper.go index 6b24d315b9d..4759dfd5f9d 100644 --- a/exchanges/coinbasepro/coinbasepro_wrapper.go +++ b/exchanges/coinbasepro/coinbasepro_wrapper.go @@ -78,28 +78,28 @@ func (c *CoinbasePro) SetDefaults() { REST: true, Websocket: true, RESTCapabilities: protocol.Features{ - TickerFetching: true, - KlineFetching: true, - TradeFetching: true, - OrderbookFetching: true, - AutoPairUpdates: true, - AccountInfo: true, - GetOrder: true, - GetOrders: true, - CancelOrders: true, - CancelOrder: true, - SubmitOrder: true, - DepositHistory: true, - WithdrawalHistory: true, - UserTradeHistory: true, - CryptoDeposit: true, - CryptoWithdrawal: true, - FiatDeposit: true, - FiatWithdraw: true, - TradeFee: true, - FiatDepositFee: true, - FiatWithdrawalFee: true, - CandleHistory: true, + AutoPairUpdates: true, + AccountBalance: true, + CryptoDeposit: true, + CryptoWithdrawal: true, + FiatWithdraw: true, + GetOrder: true, + GetOrders: true, + CancelOrders: true, + CancelOrder: true, + SubmitOrder: true, + ModifyOrder: true, + DepositHistory: true, + WithdrawalHistory: true, + FiatWithdrawalFee: true, + CryptoWithdrawalFee: true, + TickerFetching: true, + KlineFetching: true, + OrderbookFetching: true, + AccountInfo: true, + FiatDeposit: true, + FundingRateFetching: true, + HasAssetTypeAccountSegregation: true, }, WebsocketCapabilities: protocol.Features{ TickerFetching: true, @@ -235,21 +235,6 @@ func (c *CoinbasePro) Run(ctx context.Context) { } } -// fetchFutures is a helper function that calls the List Products endpoint twice, to get both -// expiring futures and perpetual futures -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 - } - products2, err := c.GetAllProducts(ctx, 2<<30-1, 0, "FUTURE", "PERPETUAL", "", nil) - if err != nil { - return AllProducts{}, err - } - products.Products = append(products.Products, products2.Products...) - return products, nil -} - // 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 @@ -507,45 +492,6 @@ func (c *CoinbasePro) UpdateOrderbook(ctx context.Context, p currency.Pair, asse return orderbook.Get(c.Name, p, assetType) } -// ProcessFundingData is a helper function for GetAccountFundingHistory and GetWithdrawalsHistory -func (c *CoinbasePro) ProcessFundingData(accHistory []DeposWithdrData, cryptoHistory []TransactionData) []exchange.FundingHistory { - fundingData := make([]exchange.FundingHistory, len(accHistory)+len(cryptoHistory)) - for i := range accHistory { - fundingData[i] = exchange.FundingHistory{ - ExchangeName: c.Name, - Status: accHistory[i].Status, - TransferID: accHistory[i].ID, - Timestamp: accHistory[i].PayoutAt, - Currency: accHistory[i].Amount.Currency, - Amount: accHistory[i].Amount.Amount, - Fee: accHistory[i].Fee.Amount, - TransferType: accHistory[i].TransferType.String(), - } - } - - for i := range cryptoHistory { - fundingData[i+len(accHistory)] = exchange.FundingHistory{ - ExchangeName: c.Name, - Status: cryptoHistory[i].Status, - TransferID: cryptoHistory[i].ID, - Description: cryptoHistory[i].Details.Title + cryptoHistory[i].Details.Subtitle, - Timestamp: cryptoHistory[i].CreatedAt, - Currency: cryptoHistory[i].Amount.Currency, - Amount: cryptoHistory[i].Amount.Amount, - CryptoChain: cryptoHistory[i].Network.Name, - } - if cryptoHistory[i].Type == "receive" { - fundingData[i+len(accHistory)].TransferType = "deposit" - fundingData[i+len(accHistory)].CryptoFromAddress = cryptoHistory[i].To.ID - } - if cryptoHistory[i].Type == "send" { - fundingData[i+len(accHistory)].TransferType = "withdrawal" - fundingData[i+len(accHistory)].CryptoToAddress = cryptoHistory[i].From.ID - } - } - return fundingData -} - // GetAccountFundingHistory returns funding history, deposits and // withdrawals func (c *CoinbasePro) GetAccountFundingHistory(ctx context.Context) ([]exchange.FundingHistory, error) { @@ -587,7 +533,7 @@ func (c *CoinbasePro) GetAccountFundingHistory(ctx context.Context) ([]exchange. } } - fundingData := c.ProcessFundingData(accHistory, cryptoHistory) + fundingData := c.processFundingData(accHistory, cryptoHistory) return fundingData, nil } @@ -639,7 +585,7 @@ func (c *CoinbasePro) GetWithdrawalsHistory(ctx context.Context, cur currency.Co } } - tempFundingData := c.ProcessFundingData(accHistory, cryptoHistory) + tempFundingData := c.processFundingData(accHistory, cryptoHistory) fundingData := make([]exchange.WithdrawalHistory, len(tempFundingData)) @@ -769,7 +715,7 @@ func (c *CoinbasePro) CancelOrder(ctx context.Context, o *order.Cancel) error { return err } -// CancelBatchOrders cancels an orders by their corresponding ID numbers +// CancelBatchOrders cancels orders by their corresponding ID numbers func (c *CoinbasePro) CancelBatchOrders(ctx context.Context, o []order.Cancel) (*order.CancelBatchResponse, error) { var status order.CancelBatchResponse var err error @@ -780,23 +726,6 @@ func (c *CoinbasePro) CancelBatchOrders(ctx context.Context, o []order.Cancel) ( return &status, nil } -func (c *CoinbasePro) iterativeGetAllOrders(ctx context.Context, productID, orderType, orderSide, productType string, orderStatus []string, limit int32, startDate, endDate time.Time) ([]GetOrderResponse, error) { - var hasNext bool - var resp []GetOrderResponse - var cursor string - for hasNext { - interResp, err := c.GetAllOrders(ctx, productID, "", orderType, orderSide, cursor, productType, "", "", "", - orderStatus, nil, limit, startDate, endDate) - if err != nil { - return nil, err - } - resp = append(resp, interResp.Orders...) - hasNext = interResp.HasNext - cursor = interResp.Cursor - } - return resp, nil -} - // CancelAllOrders cancels all orders associated with a currency pair func (c *CoinbasePro) CancelAllOrders(ctx context.Context, can *order.Cancel) (order.CancelAllResponse, error) { var resp order.CancelAllResponse @@ -834,142 +763,6 @@ func (c *CoinbasePro) CancelAllOrders(ctx context.Context, can *order.Cancel) (o return resp, nil } -func (c *CoinbasePro) getOrderRespToOrderDetail(genOrderDetail *GetOrderResponse, pair currency.Pair, assetItem asset.Item) (*order.Detail, error) { - var amount float64 - var quoteAmount float64 - var orderType order.Type - var err error - if genOrderDetail.OrderConfiguration.MarketMarketIOC != nil { - err = stringToFloatPtr("eAmount, genOrderDetail.OrderConfiguration.MarketMarketIOC.QuoteSize) - if err != nil { - return nil, err - } - err = stringToFloatPtr(&amount, genOrderDetail.OrderConfiguration.MarketMarketIOC.BaseSize) - if err != nil { - return nil, err - } - orderType = order.Market - } - var price float64 - var postOnly bool - if genOrderDetail.OrderConfiguration.LimitLimitGTC != nil { - err = stringToFloatPtr(&amount, genOrderDetail.OrderConfiguration.LimitLimitGTC.BaseSize) - if err != nil { - return nil, err - } - err = stringToFloatPtr(&price, genOrderDetail.OrderConfiguration.LimitLimitGTC.LimitPrice) - if err != nil { - return nil, err - } - postOnly = genOrderDetail.OrderConfiguration.LimitLimitGTC.PostOnly - orderType = order.Limit - } - if genOrderDetail.OrderConfiguration.LimitLimitGTD != nil { - err = stringToFloatPtr(&amount, genOrderDetail.OrderConfiguration.LimitLimitGTD.BaseSize) - if err != nil { - return nil, err - } - err = stringToFloatPtr(&price, genOrderDetail.OrderConfiguration.LimitLimitGTD.LimitPrice) - if err != nil { - return nil, err - } - postOnly = genOrderDetail.OrderConfiguration.LimitLimitGTD.PostOnly - orderType = order.Limit - } - var triggerPrice float64 - if genOrderDetail.OrderConfiguration.StopLimitStopLimitGTC != nil { - err = stringToFloatPtr(&amount, genOrderDetail.OrderConfiguration.StopLimitStopLimitGTC.BaseSize) - if err != nil { - return nil, err - } - err = stringToFloatPtr(&price, genOrderDetail.OrderConfiguration.StopLimitStopLimitGTC.LimitPrice) - if err != nil { - return nil, err - } - err = stringToFloatPtr(&triggerPrice, genOrderDetail.OrderConfiguration.StopLimitStopLimitGTC.StopPrice) - if err != nil { - return nil, err - } - orderType = order.StopLimit - } - if genOrderDetail.OrderConfiguration.StopLimitStopLimitGTD != nil { - err = stringToFloatPtr(&amount, genOrderDetail.OrderConfiguration.StopLimitStopLimitGTD.BaseSize) - if err != nil { - return nil, err - } - err = stringToFloatPtr(&price, genOrderDetail.OrderConfiguration.StopLimitStopLimitGTD.LimitPrice) - if err != nil { - return nil, err - } - err = stringToFloatPtr(&triggerPrice, genOrderDetail.OrderConfiguration.StopLimitStopLimitGTD.StopPrice) - if err != nil { - return nil, err - } - orderType = order.StopLimit - } - var remainingAmount float64 - if !genOrderDetail.SizeInQuote { - remainingAmount = amount - genOrderDetail.FilledSize - } - var orderSide order.Side - switch genOrderDetail.Side { - case order.Buy.String(): - orderSide = order.Buy - case order.Sell.String(): - orderSide = order.Sell - } - var orderStatus order.Status - switch genOrderDetail.Status { - case order.Open.String(): - orderStatus = order.Open - case order.Filled.String(): - orderStatus = order.Filled - case order.Cancelled.String(): - orderStatus = order.Cancelled - case order.Expired.String(): - orderStatus = order.Expired - case "FAILED": - orderStatus = order.Rejected - case "UNKNOWN_ORDER_STATUS": - orderStatus = order.UnknownStatus - } - var closeTime time.Time - if genOrderDetail.Settled { - closeTime = genOrderDetail.LastFillTime - } - var lastUpdateTime time.Time - if len(genOrderDetail.EditHistory) > 0 { - lastUpdateTime = genOrderDetail.EditHistory[len(genOrderDetail.EditHistory)-1].ReplaceAcceptTimestamp - } - - response := order.Detail{ - ImmediateOrCancel: genOrderDetail.OrderConfiguration.MarketMarketIOC != nil, - PostOnly: postOnly, - Price: price, - Amount: amount, - TriggerPrice: triggerPrice, - AverageExecutedPrice: genOrderDetail.AverageFilledPrice, - QuoteAmount: quoteAmount, - ExecutedAmount: genOrderDetail.FilledSize, - RemainingAmount: remainingAmount, - Cost: genOrderDetail.TotalValueAfterFees, - Fee: genOrderDetail.TotalFees, - Exchange: c.GetName(), - OrderID: genOrderDetail.OrderID, - ClientOrderID: genOrderDetail.ClientOID, - ClientID: genOrderDetail.UserID, - Type: orderType, - Side: orderSide, - Status: orderStatus, - AssetType: assetItem, - Date: genOrderDetail.CreatedTime, - CloseTime: closeTime, - LastUpdated: lastUpdateTime, - Pair: pair, - } - return &response, nil -} - // GetOrderInfo returns order information based on order ID func (c *CoinbasePro) GetOrderInfo(ctx context.Context, orderID string, pair currency.Pair, assetItem asset.Item) (*order.Detail, error) { genOrderDetail, err := c.GetOrderByID(ctx, orderID, "", "") @@ -1251,28 +1044,6 @@ func (c *CoinbasePro) GetOrderHistory(ctx context.Context, req *order.MultiOrder return req.Filter(c.Name, orders), nil } -func formatExchangeKlineInterval(interval kline.Interval) string { - switch interval { - case kline.OneMin: - return granOneMin - case kline.FiveMin: - return granFiveMin - case kline.FifteenMin: - return granFifteenMin - case kline.ThirtyMin: - return granThirtyMin - case kline.OneHour: - return granOneHour - case kline.TwoHour: - return granTwoHour - case kline.SixHour: - return granSixHour - case kline.OneDay: - return granOneDay - } - return errIntervalNotSupported -} - // GetHistoricCandles returns a set of candle between two time periods for a // designated time period func (c *CoinbasePro) GetHistoricCandles(ctx context.Context, pair currency.Pair, a asset.Item, interval kline.Interval, start, end time.Time) (*kline.Item, error) { @@ -1473,6 +1244,22 @@ func (c *CoinbasePro) UpdateOrderExecutionLimits(ctx context.Context, a asset.It 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) { + products, err := c.GetAllProducts(ctx, 2<<30-1, 0, "FUTURE", "", "", nil) + if err != nil { + return AllProducts{}, err + } + products2, err := c.GetAllProducts(ctx, 2<<30-1, 0, "FUTURE", "PERPETUAL", "", nil) + if err != nil { + return AllProducts{}, err + } + products.Products = append(products.Products, products2.Products...) + return products, nil +} + // cancelOrdersReturnMapAndCount is a helper function for CancelBatchOrders and CancelAllOrders, // calling the appropriate Coinbase endpoint, and returning information useful to both func (c *CoinbasePro) cancelOrdersReturnMapAndCount(ctx context.Context, o []order.Cancel) (map[string]string, int64, error) { @@ -1503,6 +1290,225 @@ func (c *CoinbasePro) cancelOrdersReturnMapAndCount(ctx context.Context, o []ord return status, counter, nil } +// processFundingData is a helper function for GetAccountFundingHistory and GetWithdrawalsHistory +func (c *CoinbasePro) processFundingData(accHistory []DeposWithdrData, cryptoHistory []TransactionData) []exchange.FundingHistory { + fundingData := make([]exchange.FundingHistory, len(accHistory)+len(cryptoHistory)) + for i := range accHistory { + fundingData[i] = exchange.FundingHistory{ + ExchangeName: c.Name, + Status: accHistory[i].Status, + TransferID: accHistory[i].ID, + Timestamp: accHistory[i].PayoutAt, + Currency: accHistory[i].Amount.Currency, + Amount: accHistory[i].Amount.Amount, + Fee: accHistory[i].Fee.Amount, + TransferType: accHistory[i].TransferType.String(), + } + } + + for i := range cryptoHistory { + fundingData[i+len(accHistory)] = exchange.FundingHistory{ + ExchangeName: c.Name, + Status: cryptoHistory[i].Status, + TransferID: cryptoHistory[i].ID, + Description: cryptoHistory[i].Details.Title + cryptoHistory[i].Details.Subtitle, + Timestamp: cryptoHistory[i].CreatedAt, + Currency: cryptoHistory[i].Amount.Currency, + Amount: cryptoHistory[i].Amount.Amount, + CryptoChain: cryptoHistory[i].Network.Name, + } + if cryptoHistory[i].Type == "receive" { + fundingData[i+len(accHistory)].TransferType = "deposit" + fundingData[i+len(accHistory)].CryptoFromAddress = cryptoHistory[i].To.ID + } + if cryptoHistory[i].Type == "send" { + fundingData[i+len(accHistory)].TransferType = "withdrawal" + fundingData[i+len(accHistory)].CryptoToAddress = cryptoHistory[i].From.ID + } + } + return fundingData +} + +// iterativeGetAllOrders is a helper function used in CancelAllOrders, GetActiveOrders, and GetOrderHistory +// to repeatedly call GetAllOrders until all orders have been retrieved +func (c *CoinbasePro) iterativeGetAllOrders(ctx context.Context, productID, orderType, orderSide, productType string, orderStatus []string, limit int32, startDate, endDate time.Time) ([]GetOrderResponse, error) { + var hasNext bool + var resp []GetOrderResponse + var cursor string + for hasNext { + interResp, err := c.GetAllOrders(ctx, productID, "", orderType, orderSide, cursor, productType, "", "", "", + orderStatus, nil, limit, startDate, endDate) + if err != nil { + return nil, err + } + resp = append(resp, interResp.Orders...) + hasNext = interResp.HasNext + cursor = interResp.Cursor + } + return resp, nil +} + +// formatExchangeKlineInterval is a helper function used in GetHistoricCandles and GetHistoricCandlesExtended +// to convert kline.Interval to the string format used by Coinbase +func formatExchangeKlineInterval(interval kline.Interval) string { + switch interval { + case kline.OneMin: + return granOneMin + case kline.FiveMin: + return granFiveMin + case kline.FifteenMin: + return granFifteenMin + case kline.ThirtyMin: + return granThirtyMin + case kline.OneHour: + return granOneHour + case kline.TwoHour: + return granTwoHour + case kline.SixHour: + return granSixHour + case kline.OneDay: + return granOneDay + } + return errIntervalNotSupported +} + +// getOrderRespToOrderDetail is a helper function used in GetOrderInfo, GetActiveOrders, and GetOrderHistory +func (c *CoinbasePro) getOrderRespToOrderDetail(genOrderDetail *GetOrderResponse, pair currency.Pair, assetItem asset.Item) (*order.Detail, error) { + var amount float64 + var quoteAmount float64 + var orderType order.Type + var err error + if genOrderDetail.OrderConfiguration.MarketMarketIOC != nil { + err = stringToFloatPtr("eAmount, genOrderDetail.OrderConfiguration.MarketMarketIOC.QuoteSize) + if err != nil { + return nil, err + } + err = stringToFloatPtr(&amount, genOrderDetail.OrderConfiguration.MarketMarketIOC.BaseSize) + if err != nil { + return nil, err + } + orderType = order.Market + } + var price float64 + var postOnly bool + if genOrderDetail.OrderConfiguration.LimitLimitGTC != nil { + err = stringToFloatPtr(&amount, genOrderDetail.OrderConfiguration.LimitLimitGTC.BaseSize) + if err != nil { + return nil, err + } + err = stringToFloatPtr(&price, genOrderDetail.OrderConfiguration.LimitLimitGTC.LimitPrice) + if err != nil { + return nil, err + } + postOnly = genOrderDetail.OrderConfiguration.LimitLimitGTC.PostOnly + orderType = order.Limit + } + if genOrderDetail.OrderConfiguration.LimitLimitGTD != nil { + err = stringToFloatPtr(&amount, genOrderDetail.OrderConfiguration.LimitLimitGTD.BaseSize) + if err != nil { + return nil, err + } + err = stringToFloatPtr(&price, genOrderDetail.OrderConfiguration.LimitLimitGTD.LimitPrice) + if err != nil { + return nil, err + } + postOnly = genOrderDetail.OrderConfiguration.LimitLimitGTD.PostOnly + orderType = order.Limit + } + var triggerPrice float64 + if genOrderDetail.OrderConfiguration.StopLimitStopLimitGTC != nil { + err = stringToFloatPtr(&amount, genOrderDetail.OrderConfiguration.StopLimitStopLimitGTC.BaseSize) + if err != nil { + return nil, err + } + err = stringToFloatPtr(&price, genOrderDetail.OrderConfiguration.StopLimitStopLimitGTC.LimitPrice) + if err != nil { + return nil, err + } + err = stringToFloatPtr(&triggerPrice, genOrderDetail.OrderConfiguration.StopLimitStopLimitGTC.StopPrice) + if err != nil { + return nil, err + } + orderType = order.StopLimit + } + if genOrderDetail.OrderConfiguration.StopLimitStopLimitGTD != nil { + err = stringToFloatPtr(&amount, genOrderDetail.OrderConfiguration.StopLimitStopLimitGTD.BaseSize) + if err != nil { + return nil, err + } + err = stringToFloatPtr(&price, genOrderDetail.OrderConfiguration.StopLimitStopLimitGTD.LimitPrice) + if err != nil { + return nil, err + } + err = stringToFloatPtr(&triggerPrice, genOrderDetail.OrderConfiguration.StopLimitStopLimitGTD.StopPrice) + if err != nil { + return nil, err + } + orderType = order.StopLimit + } + var remainingAmount float64 + if !genOrderDetail.SizeInQuote { + remainingAmount = amount - genOrderDetail.FilledSize + } + var orderSide order.Side + switch genOrderDetail.Side { + case order.Buy.String(): + orderSide = order.Buy + case order.Sell.String(): + orderSide = order.Sell + } + var orderStatus order.Status + switch genOrderDetail.Status { + case order.Open.String(): + orderStatus = order.Open + case order.Filled.String(): + orderStatus = order.Filled + case order.Cancelled.String(): + orderStatus = order.Cancelled + case order.Expired.String(): + orderStatus = order.Expired + case "FAILED": + orderStatus = order.Rejected + case "UNKNOWN_ORDER_STATUS": + orderStatus = order.UnknownStatus + } + var closeTime time.Time + if genOrderDetail.Settled { + closeTime = genOrderDetail.LastFillTime + } + var lastUpdateTime time.Time + if len(genOrderDetail.EditHistory) > 0 { + lastUpdateTime = genOrderDetail.EditHistory[len(genOrderDetail.EditHistory)-1].ReplaceAcceptTimestamp + } + + response := order.Detail{ + ImmediateOrCancel: genOrderDetail.OrderConfiguration.MarketMarketIOC != nil, + PostOnly: postOnly, + Price: price, + Amount: amount, + TriggerPrice: triggerPrice, + AverageExecutedPrice: genOrderDetail.AverageFilledPrice, + QuoteAmount: quoteAmount, + ExecutedAmount: genOrderDetail.FilledSize, + RemainingAmount: remainingAmount, + Cost: genOrderDetail.TotalValueAfterFees, + Fee: genOrderDetail.TotalFees, + Exchange: c.GetName(), + OrderID: genOrderDetail.OrderID, + ClientOrderID: genOrderDetail.ClientOID, + ClientID: genOrderDetail.UserID, + Type: orderType, + Side: orderSide, + Status: orderStatus, + AssetType: assetItem, + Date: genOrderDetail.CreatedTime, + CloseTime: closeTime, + LastUpdated: lastUpdateTime, + Pair: pair, + } + return &response, nil +} + // stringToFloatPtr essentially calls ParseFloat, but leaves the float alone instead of erroring out // if the string is empty. func stringToFloatPtr(outgoing *float64, incoming string) error { diff --git a/exchanges/coinbasepro/ratelimit.go b/exchanges/coinbasepro/ratelimit.go index 61b37aad687..936affd12ec 100644 --- a/exchanges/coinbasepro/ratelimit.go +++ b/exchanges/coinbasepro/ratelimit.go @@ -11,34 +11,46 @@ import ( // Coinbasepro rate limit constants const ( coinbaseV3Interval = time.Second - coinbaseV3Rate = 30 + coinbaseV3Rate = 27 coinbaseV2Interval = time.Hour coinbaseV2Rate = 10000 + + coinbaseWSInterval = time.Second + coinbaseWSRate = 750 ) const ( V2Rate request.EndpointLimit = iota V3Rate + WSRate ) // RateLimit implements the request.Limiter interface type RateLimit struct { RateLimV3 *rate.Limiter RateLimV2 *rate.Limiter + RateLimWS *rate.Limiter } // Limit limits outbound calls func (r *RateLimit) Limit(ctx context.Context, f request.EndpointLimit) error { - if f == V3Rate { + switch f { + case V3Rate: return r.RateLimV3.Wait(ctx) + case V2Rate: + return r.RateLimV2.Wait(ctx) + case WSRate: + return r.RateLimWS.Wait(ctx) + default: + return errUnknownEndpointLimit } - return r.RateLimV2.Wait(ctx) } // SetRateLimit returns the rate limit for the exchange func SetRateLimit() *RateLimit { return &RateLimit{ + RateLimWS: request.NewRateLimit(coinbaseWSInterval, coinbaseWSRate), RateLimV3: request.NewRateLimit(coinbaseV3Interval, coinbaseV3Rate), RateLimV2: request.NewRateLimit(coinbaseV2Interval, coinbaseV2Rate), } diff --git a/exchanges/protocol/features.go b/exchanges/protocol/features.go index 2d12a992f1e..7ba81eaee82 100644 --- a/exchanges/protocol/features.go +++ b/exchanges/protocol/features.go @@ -3,6 +3,7 @@ package protocol // Features holds all variables for the exchanges supported features // for a protocol (e.g REST or Websocket) type Features struct { + // TickerBatching allows the REST endpoint to fetch the entire ticker list available to the exchange TickerBatching bool `json:"tickerBatching,omitempty"` AutoPairUpdates bool `json:"autoPairUpdates,omitempty"` AccountBalance bool `json:"accountBalance,omitempty"` From 2881841ff0d9db80caa4a76655fd0d11e66292ab Mon Sep 17 00:00:00 2001 From: Samuel Reid <43227667+cranktakular@users.noreply.github.com> Date: Tue, 16 Jan 2024 13:09:26 +1100 Subject: [PATCH 18/79] Post-merge fix --- exchanges/coinbasepro/coinbasepro_types.go | 146 ++++++++++----------- 1 file changed, 73 insertions(+), 73 deletions(-) diff --git a/exchanges/coinbasepro/coinbasepro_types.go b/exchanges/coinbasepro/coinbasepro_types.go index 65490fb6fb6..616786b7cc0 100644 --- a/exchanges/coinbasepro/coinbasepro_types.go +++ b/exchanges/coinbasepro/coinbasepro_types.go @@ -4,9 +4,9 @@ import ( "net/url" "time" - "github.com/thrasher-corp/gocryptotrader/common/convert" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" + "github.com/thrasher-corp/gocryptotrader/types" ) // CoinbasePro is the overarching type across the coinbasepro package @@ -89,60 +89,60 @@ type ProductBookResponse struct { // Product holds product information, returned by GetProductByID, and used as a sub-struct // in the type AllProducts type Product struct { - ID string `json:"product_id"` - Price convert.StringToFloat64 `json:"price"` - PricePercentageChange24H convert.StringToFloat64 `json:"price_percentage_change_24h"` - Volume24H convert.StringToFloat64 `json:"volume_24h"` - VolumePercentageChange24H convert.StringToFloat64 `json:"volume_percentage_change_24h"` - BaseIncrement convert.StringToFloat64 `json:"base_increment"` - QuoteIncrement convert.StringToFloat64 `json:"quote_increment"` - QuoteMinSize convert.StringToFloat64 `json:"quote_min_size"` - QuoteMaxSize convert.StringToFloat64 `json:"quote_max_size"` - BaseMinSize convert.StringToFloat64 `json:"base_min_size"` - BaseMaxSize convert.StringToFloat64 `json:"base_max_size"` - BaseName string `json:"base_name"` - QuoteName string `json:"quote_name"` - Watched bool `json:"watched"` - IsDisabled bool `json:"is_disabled"` - New bool `json:"new"` - Status string `json:"status"` - CancelOnly bool `json:"cancel_only"` - LimitOnly bool `json:"limit_only"` - PostOnly bool `json:"post_only"` - TradingDisabled bool `json:"trading_disabled"` - AuctionMode bool `json:"auction_mode"` - ProductType string `json:"product_type"` - QuoteCurrencyID string `json:"quote_currency_id"` - BaseCurrencyID string `json:"base_currency_id"` + ID string `json:"product_id"` + Price types.Number `json:"price"` + PricePercentageChange24H types.Number `json:"price_percentage_change_24h"` + Volume24H types.Number `json:"volume_24h"` + VolumePercentageChange24H types.Number `json:"volume_percentage_change_24h"` + BaseIncrement types.Number `json:"base_increment"` + QuoteIncrement types.Number `json:"quote_increment"` + QuoteMinSize types.Number `json:"quote_min_size"` + QuoteMaxSize types.Number `json:"quote_max_size"` + BaseMinSize types.Number `json:"base_min_size"` + BaseMaxSize types.Number `json:"base_max_size"` + BaseName string `json:"base_name"` + QuoteName string `json:"quote_name"` + Watched bool `json:"watched"` + IsDisabled bool `json:"is_disabled"` + New bool `json:"new"` + Status string `json:"status"` + CancelOnly bool `json:"cancel_only"` + LimitOnly bool `json:"limit_only"` + PostOnly bool `json:"post_only"` + TradingDisabled bool `json:"trading_disabled"` + AuctionMode bool `json:"auction_mode"` + ProductType string `json:"product_type"` + QuoteCurrencyID string `json:"quote_currency_id"` + BaseCurrencyID string `json:"base_currency_id"` FCMTradingSessionDetails struct { IsSessionOpen bool `json:"is_session_open"` OpenTime time.Time `json:"open_time"` CloseTime time.Time `json:"close_time"` } `json:"fcm_trading_session_details"` - MidMarketPrice convert.StringToFloat64 `json:"mid_market_price"` - Alias string `json:"alias"` - AliasTo []string `json:"alias_to"` - BaseDisplaySymbol string `json:"base_display_symbol"` - QuoteDisplaySymbol string `json:"quote_display_symbol"` - ViewOnly bool `json:"view_only"` - PriceIncrement convert.StringToFloat64 `json:"price_increment"` - DisplayName string `json:"display_name"` + MidMarketPrice types.Number `json:"mid_market_price"` + Alias string `json:"alias"` + AliasTo []string `json:"alias_to"` + BaseDisplaySymbol string `json:"base_display_symbol"` + QuoteDisplaySymbol string `json:"quote_display_symbol"` + ViewOnly bool `json:"view_only"` + PriceIncrement types.Number `json:"price_increment"` + DisplayName string `json:"display_name"` FutureProductDetails struct { - Venue string `json:"venue"` - ContractCode string `json:"contract_code"` - ContractExpiry time.Time `json:"contract_expiry"` - ContractSize convert.StringToFloat64 `json:"contract_size"` - ContractRootUnit string `json:"contract_root_unit"` - GroupDescription string `json:"group_description"` - ContractExpiryTimezone string `json:"contract_expiry_timezone"` - GroupShortDescription string `json:"group_short_description"` - RiskManagedBy string `json:"risk_managed_by"` - ContractExpiryType string `json:"contract_expiry_type"` + Venue string `json:"venue"` + ContractCode string `json:"contract_code"` + ContractExpiry time.Time `json:"contract_expiry"` + ContractSize types.Number `json:"contract_size"` + ContractRootUnit string `json:"contract_root_unit"` + GroupDescription string `json:"group_description"` + ContractExpiryTimezone string `json:"contract_expiry_timezone"` + GroupShortDescription string `json:"group_short_description"` + RiskManagedBy string `json:"risk_managed_by"` + ContractExpiryType string `json:"contract_expiry_type"` PerpetualDetails struct { - OpenInterest convert.StringToFloat64 `json:"open_interest"` - FundingRate convert.StringToFloat64 `json:"funding_rate"` - FundingTime time.Time `json:"funding_time"` - MaxLeverage convert.StringToFloat64 `json:"max_leverage"` + OpenInterest types.Number `json:"open_interest"` + FundingRate types.Number `json:"funding_rate"` + FundingTime time.Time `json:"funding_time"` + MaxLeverage types.Number `json:"max_leverage"` } `json:"perpetual_details"` ContractDisplayName string `json:"contract_display_name"` } `json:"future_product_details"` @@ -174,17 +174,17 @@ type History struct { // Ticker holds basic ticker information, returned by GetTicker type Ticker struct { Trades []struct { - TradeID string `json:"trade_id"` - ProductID string `json:"product_id"` - Price float64 `json:"price,string"` - Size float64 `json:"size,string"` - Time time.Time `json:"time"` - Side string `json:"side"` - Bid convert.StringToFloat64 `json:"bid"` - Ask convert.StringToFloat64 `json:"ask"` + TradeID string `json:"trade_id"` + ProductID string `json:"product_id"` + Price float64 `json:"price,string"` + Size float64 `json:"size,string"` + Time time.Time `json:"time"` + Side string `json:"side"` + Bid types.Number `json:"bid"` + Ask types.Number `json:"ask"` } `json:"trades"` - BestBid convert.StringToFloat64 `json:"best_bid"` - BestAsk convert.StringToFloat64 `json:"best_ask"` + BestBid types.Number `json:"best_bid"` + BestAsk types.Number `json:"best_ask"` } // MarketMarketIOC is a sub-struct used in the type OrderConfiguration @@ -503,13 +503,13 @@ type TransactionSummary struct { TotalVolume float64 `json:"total_volume"` TotalFees float64 `json:"total_fees"` FeeTier struct { - PricingTier string `json:"pricing_tier"` - USDFrom float64 `json:"usd_from,string"` - USDTo float64 `json:"usd_to,string"` - TakerFeeRate float64 `json:"taker_fee_rate,string"` - MakerFeeRate float64 `json:"maker_fee_rate,string"` - AOPFrom convert.StringToFloat64 `json:"aop_from"` - AOPTo convert.StringToFloat64 `json:"aop_to"` + PricingTier string `json:"pricing_tier"` + USDFrom float64 `json:"usd_from,string"` + USDTo float64 `json:"usd_to,string"` + TakerFeeRate float64 `json:"taker_fee_rate,string"` + MakerFeeRate float64 `json:"maker_fee_rate,string"` + AOPFrom types.Number `json:"aop_from"` + AOPTo types.Number `json:"aop_to"` } `json:"fee_tier"` MarginRate struct { Value float64 `json:"value,string"` @@ -518,12 +518,12 @@ type TransactionSummary struct { Rate float64 `json:"rate,string"` Type string `json:"type"` } `json:"goods_and_services_tax"` - AdvancedTradeOnlyVolume float64 `json:"advanced_trade_only_volume"` - AdvancedTradeOnlyFees float64 `json:"advanced_trade_only_fees"` - CoinbaseProVolume float64 `json:"coinbase_pro_volume"` - CoinbaseProFees float64 `json:"coinbase_pro_fees"` - TotalBalance convert.StringToFloat64 `json:"total_balance"` - HasPromoFee bool `json:"has_promo_fee"` + AdvancedTradeOnlyVolume float64 `json:"advanced_trade_only_volume"` + AdvancedTradeOnlyFees float64 `json:"advanced_trade_only_fees"` + CoinbaseProVolume float64 `json:"coinbase_pro_volume"` + CoinbaseProFees float64 `json:"coinbase_pro_fees"` + TotalBalance types.Number `json:"total_balance"` + HasPromoFee bool `json:"has_promo_fee"` } // GetAllOrdersResp contains information on a lot of orders, returned by GetAllOrders @@ -1045,8 +1045,8 @@ type GetCryptocurrenciesResp struct { // GetExchangeRatesResp holds information on exchange rates. Returned by GetExchangeRates type GetExchangeRatesResp struct { Data struct { - Currency string `json:"currency"` - Rates map[string]convert.StringToFloat64 `json:"rates"` + Currency string `json:"currency"` + Rates map[string]types.Number `json:"rates"` } `json:"data"` } From 2166886265df90c4617d1494a7ab6fe40d28b9c4 Mon Sep 17 00:00:00 2001 From: Samuel Reid <43227667+cranktakular@users.noreply.github.com> Date: Tue, 16 Jan 2024 16:33:08 +1100 Subject: [PATCH 19/79] WS revamp begins --- exchanges/coinbasepro/coinbasepro.go | 1 + exchanges/coinbasepro/coinbasepro_types.go | 58 +++-- .../coinbasepro/coinbasepro_websocket.go | 207 +++++++++++------- 3 files changed, 156 insertions(+), 110 deletions(-) diff --git a/exchanges/coinbasepro/coinbasepro.go b/exchanges/coinbasepro/coinbasepro.go index 5b88891a350..5d5649ea64a 100644 --- a/exchanges/coinbasepro/coinbasepro.go +++ b/exchanges/coinbasepro/coinbasepro.go @@ -117,6 +117,7 @@ var ( errPortfolioIDEmpty = errors.New("portfolio id cannot be empty") errFeeTypeNotSupported = errors.New("fee type not supported") errUnknownEndpointLimit = errors.New("unknown endpoint limit") + errNoEventsWS = errors.New("no events returned from websocket") ) // GetAllAccounts returns information on all trading accounts associated with the API key diff --git a/exchanges/coinbasepro/coinbasepro_types.go b/exchanges/coinbasepro/coinbasepro_types.go index 616786b7cc0..022d5df84bb 100644 --- a/exchanges/coinbasepro/coinbasepro_types.go +++ b/exchanges/coinbasepro/coinbasepro_types.go @@ -1069,21 +1069,21 @@ type ServerTimeV2 struct { // WebsocketSubscribe takes in subscription information type WebsocketSubscribe struct { - Type string `json:"type"` - ProductIDs []string `json:"product_ids,omitempty"` - Channels []WsChannels `json:"channels,omitempty"` - Signature string `json:"signature,omitempty"` - Key string `json:"key,omitempty"` - Passphrase string `json:"passphrase,omitempty"` - Timestamp string `json:"timestamp,omitempty"` -} - -// WsChannels defines outgoing channels for subscription purposes -type WsChannels struct { - Name string `json:"name"` + Type string `json:"type"` ProductIDs []string `json:"product_ids,omitempty"` + Channel string `json:"channel,omitempty"` + Signature string `json:"signature,omitempty"` + Key string `json:"api_key,omitempty"` + Timestamp string `json:"timestamp,omitempty"` + JWT string `json:"jwt,omitempty"` } +// // WsChannels defines outgoing channels for subscription purposes +// type WsChannels struct { +// Name string `json:"name"` +// ProductIDs []string `json:"product_ids,omitempty"` +// } + // wsOrderReceived holds websocket received values type wsOrderReceived struct { Type string `json:"type"` @@ -1125,21 +1125,15 @@ type WebsocketHeartBeat struct { // WebsocketTicker defines ticker websocket response type WebsocketTicker struct { - Type string `json:"type"` - Sequence int64 `json:"sequence"` - ProductID currency.Pair `json:"product_id"` - Price float64 `json:"price,string"` - Open24H float64 `json:"open_24h,string"` - Volume24H float64 `json:"volume_24h,string"` - Low24H float64 `json:"low_24h,string"` - High24H float64 `json:"high_24h,string"` - Volume30D float64 `json:"volume_30d,string"` - BestBid float64 `json:"best_bid,string"` - BestAsk float64 `json:"best_ask,string"` - Side string `json:"side"` - Time time.Time `json:"time"` - TradeID int64 `json:"trade_id"` - LastSize float64 `json:"last_size,string"` + Type string `json:"type"` + ProductID currency.Pair `json:"product_id"` + Price float64 `json:"price,string"` + Volume24H float64 `json:"volume_24_h,string"` + Low24H float64 `json:"low_24_h,string"` + High24H float64 `json:"high_24_h,string"` + Low52W float64 `json:"low_52_w,string"` + High52W float64 `json:"high_52_w,string"` + PricePercentageChange24H float64 `json:"price_percent_chg_24_h,string"` } // WebsocketOrderbookSnapshot defines a snapshot response @@ -1159,10 +1153,12 @@ type WebsocketL2Update struct { Changes [][3]string `json:"changes"` } -type wsMsgType struct { - Type string `json:"type"` - Sequence int64 `json:"sequence"` - ProductID string `json:"product_id"` +type wsGen struct { + Channel string `json:"channel"` + ClientID string `json:"client_id"` + Timestamp time.Time `json:"timestamp"` + SequenceNum uint64 `json:"sequence_num"` + Events []interface{} `json:"events"` } type wsStatus struct { diff --git a/exchanges/coinbasepro/coinbasepro_websocket.go b/exchanges/coinbasepro/coinbasepro_websocket.go index 3be31a7cd77..ae425ba32c2 100644 --- a/exchanges/coinbasepro/coinbasepro_websocket.go +++ b/exchanges/coinbasepro/coinbasepro_websocket.go @@ -2,6 +2,7 @@ package coinbasepro import ( "context" + "encoding/hex" "encoding/json" "errors" "fmt" @@ -14,7 +15,6 @@ import ( "github.com/thrasher-corp/gocryptotrader/common/convert" "github.com/thrasher-corp/gocryptotrader/common/crypto" "github.com/thrasher-corp/gocryptotrader/currency" - "github.com/thrasher-corp/gocryptotrader/exchanges/account" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" @@ -24,7 +24,7 @@ import ( ) const ( - coinbaseproWebsocketURL = "wss://ws-feed.pro.coinbase.com" + coinbaseproWebsocketURL = "wss://advanced-trade-ws.coinbase.com" ) // WsConnect initiates a websocket connection @@ -60,17 +60,20 @@ func (c *CoinbasePro) wsReadData() { } func (c *CoinbasePro) wsHandleData(respRaw []byte) error { - msgType := wsMsgType{} - err := json.Unmarshal(respRaw, &msgType) + fmt.Println("WHADDUP:", string(respRaw)) + genData := wsGen{} + err := json.Unmarshal(respRaw, &genData) if err != nil { return err } - if msgType.Type == "subscriptions" || msgType.Type == "heartbeat" { + if genData.Channel == "subscriptions" || genData.Channel == "heartbeats" { return nil } - switch msgType.Type { + fmt.Printf("=== OH NOO LOOK AT THIS DATA WE HAVE TO DEAL WITH: %s ===\n", genData.Events) + + switch genData.Channel { case "status": var status wsStatus err = json.Unmarshal(respRaw, &status) @@ -81,26 +84,27 @@ func (c *CoinbasePro) wsHandleData(respRaw []byte) error { case "error": c.Websocket.DataHandler <- errors.New(string(respRaw)) case "ticker": - wsTicker := WebsocketTicker{} + var wsTicker []WebsocketTicker + if len(genData.Events) == 0 { + return errNoEventsWS + } err := json.Unmarshal(respRaw, &wsTicker) if err != nil { return err } - c.Websocket.DataHandler <- &ticker.Price{ - LastUpdated: wsTicker.Time, - Pair: wsTicker.ProductID, - AssetType: asset.Spot, - ExchangeName: c.Name, - Open: wsTicker.Open24H, - High: wsTicker.High24H, - Low: wsTicker.Low24H, - Last: wsTicker.Price, - Volume: wsTicker.Volume24H, - Bid: wsTicker.BestBid, - Ask: wsTicker.BestAsk, + for i := range wsTicker { + c.Websocket.DataHandler <- &ticker.Price{ + LastUpdated: genData.Timestamp, + Pair: wsTicker[i].ProductID, + AssetType: asset.Spot, + ExchangeName: c.Name, + High: wsTicker[i].High24H, + Low: wsTicker[i].Low24H, + Last: wsTicker[i].Price, + Volume: wsTicker[i].Volume24H, + } } - case "snapshot": var snapshot WebsocketOrderbookSnapshot err := json.Unmarshal(respRaw, &snapshot) @@ -366,21 +370,19 @@ func (c *CoinbasePro) ProcessUpdate(update *WebsocketL2Update) error { // GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions() func (c *CoinbasePro) GenerateDefaultSubscriptions() ([]stream.ChannelSubscription, error) { - var channels = []string{"heartbeat", - "level2_batch", /*Other orderbook feeds require authentication. This is batched in 50ms lots.*/ + var channels = []string{ + "heartbeats", + // "level2_batch", /*Other orderbook feeds require authentication. This is batched in 50ms lots.*/ "ticker", - "user", - "matches"} + // "user", + // "matches", + } enabledCurrencies, err := c.GetEnabledPairs(asset.Spot) if err != nil { return nil, err } var subscriptions []stream.ChannelSubscription for i := range channels { - if (channels[i] == "user" || channels[i] == "full") && - !c.IsWebsocketAuthenticationSupported() { - continue - } for j := range enabledCurrencies { fPair, err := c.FormatExchangeCurrency(enabledCurrencies[j], asset.Spot) @@ -397,59 +399,96 @@ func (c *CoinbasePro) GenerateDefaultSubscriptions() ([]stream.ChannelSubscripti return subscriptions, nil } -// Subscribe sends a websocket message to receive data from the channel -func (c *CoinbasePro) Subscribe(channelsToSubscribe []stream.ChannelSubscription) error { - var creds *account.Credentials - var err error - if c.IsWebsocketAuthenticationSupported() { - creds, err = c.GetCredentials(context.TODO()) - if err != nil { - return err - } +func (c *CoinbasePro) sendRequest(msgType, channel string, productID currency.Pair) error { + creds, err := c.GetCredentials(context.Background()) + if err != nil { + return err } - subscribe := WebsocketSubscribe{ - Type: "subscribe", + n := strconv.FormatInt(time.Now().Unix(), 10) + + message := n + channel + productID.String() + + hmac, err := crypto.GetHMAC(crypto.HashSHA256, + []byte(message), + []byte(creds.Secret)) + if err != nil { + return err } -subscriptions: - for i := range channelsToSubscribe { - p := channelsToSubscribe[i].Currency.String() - if !common.StringDataCompare(subscribe.ProductIDs, p) && p != "" { - subscribe.ProductIDs = append(subscribe.ProductIDs, p) - } + req := WebsocketSubscribe{ + Type: msgType, + ProductIDs: []string{productID.String()}, + Channel: channel, + Signature: hex.EncodeToString(hmac), + Key: creds.Key, + Timestamp: n, + } - for j := range subscribe.Channels { - if subscribe.Channels[j].Name == channelsToSubscribe[i].Channel { - continue subscriptions - } - } + meow, _ := json.Marshal(req) - subscribe.Channels = append(subscribe.Channels, WsChannels{ - Name: channelsToSubscribe[i].Channel, - }) + fmt.Print(string(meow)) - if (channelsToSubscribe[i].Channel == "user" || - channelsToSubscribe[i].Channel == "full") && creds != nil { - n := strconv.FormatInt(time.Now().Unix(), 10) - message := n + http.MethodGet + "/users/self/verify" - var hmac []byte - hmac, err = crypto.GetHMAC(crypto.HashSHA256, - []byte(message), - []byte(creds.Secret)) - if err != nil { - return err - } - subscribe.Signature = crypto.Base64Encode(hmac) - subscribe.Key = creds.Key - subscribe.Passphrase = creds.ClientID - subscribe.Timestamp = n - } - } - err = c.Websocket.Conn.SendJSONMessage(subscribe) + err = c.Websocket.Conn.SendJSONMessage(req) if err != nil { return err } + return nil +} + +// Subscribe sends a websocket message to receive data from the channel +func (c *CoinbasePro) Subscribe(channelsToSubscribe []stream.ChannelSubscription) error { + + fmt.Printf("SUBSCRIBE: %v\n", channelsToSubscribe) + // var creds *account.Credentials + // var err error + // if c.IsWebsocketAuthenticationSupported() { + // creds, err = c.GetCredentials(context.TODO()) + // if err != nil { + // return err + // } + // } + + // subscribe := WebsocketSubscribe{ + // Type: "subscribe", + // } + + // subscriptions: + // for i := range channelsToSubscribe { + // p := channelsToSubscribe[i].Currency.String() + // if !common.StringDataCompare(subscribe.ProductIDs, p) && p != "" { + // subscribe.ProductIDs = append(subscribe.ProductIDs, p) + // } + + // if subscribe.Channel == channelsToSubscribe[i].Channel { + // continue subscriptions + // } + + // subscribe.Channel = channelsToSubscribe[i].Channel + + // if (channelsToSubscribe[i].Channel == "user" || + // channelsToSubscribe[i].Channel == "full") && creds != nil { + // n := strconv.FormatInt(time.Now().Unix(), 10) + // message := n + http.MethodGet + "/users/self/verify" + // var hmac []byte + // hmac, err = crypto.GetHMAC(crypto.HashSHA256, + // []byte(message), + // []byte(creds.Secret)) + // if err != nil { + // return err + // } + // subscribe.Signature = crypto.Base64Encode(hmac) + // subscribe.Key = creds.Key + // subscribe.Timestamp = n + // } + // } + for i := range channelsToSubscribe { + err := c.sendRequest("subscribe", channelsToSubscribe[i].Channel, channelsToSubscribe[i].Currency) + if err != nil { + return err + } + } + c.Websocket.AddSuccessfulSubscriptions(channelsToSubscribe...) return nil } @@ -460,22 +499,17 @@ func (c *CoinbasePro) Unsubscribe(channelsToUnsubscribe []stream.ChannelSubscrip Type: "unsubscribe", } -unsubscriptions: for i := range channelsToUnsubscribe { p := channelsToUnsubscribe[i].Currency.String() if !common.StringDataCompare(unsubscribe.ProductIDs, p) && p != "" { unsubscribe.ProductIDs = append(unsubscribe.ProductIDs, p) } - for j := range unsubscribe.Channels { - if unsubscribe.Channels[j].Name == channelsToUnsubscribe[i].Channel { - continue unsubscriptions - } + if unsubscribe.Channel == channelsToUnsubscribe[i].Channel { + unsubscribe.Channel = channelsToUnsubscribe[i].Channel + } - unsubscribe.Channels = append(unsubscribe.Channels, WsChannels{ - Name: channelsToUnsubscribe[i].Channel, - }) } err := c.Websocket.Conn.SendJSONMessage(unsubscribe) if err != nil { @@ -484,3 +518,18 @@ unsubscriptions: c.Websocket.RemoveSubscriptions(channelsToUnsubscribe...) return nil } + +// const wow = "-----BEGIN EC PRIVATE KEY-----\n%s\n-----END EC PRIVATE KEY-----\n" + +// func (c *CoinbasePro) GetJWT(ctx context.Context) (string, error) { +// creds, err := c.GetCredentials(ctx) +// if err != nil { +// return "", err +// } + +// block, _ := pem.Decode([]byte(fmt.Sprintf(wow, creds.Secret))) +// if block == nil { +// return "", fmt.Errorf("jwt: Could not decode private key") +// } + +// } From 35eda67763246a9e3c6c9abc97d9ea2d7824f232 Mon Sep 17 00:00:00 2001 From: Samuel Reid <43227667+cranktakular@users.noreply.github.com> Date: Mon, 22 Jan 2024 17:59:45 +1100 Subject: [PATCH 20/79] WS Main Revamp Done? --- exchanges/coinbasepro/coinbasepro.go | 14 +- exchanges/coinbasepro/coinbasepro_test.go | 4 + exchanges/coinbasepro/coinbasepro_types.go | 254 ++++++--- .../coinbasepro/coinbasepro_websocket.go | 534 ++++++++++-------- exchanges/coinbasepro/coinbasepro_wrapper.go | 24 +- exchanges/coinbasepro/ratelimit.go | 5 +- 6 files changed, 509 insertions(+), 326 deletions(-) diff --git a/exchanges/coinbasepro/coinbasepro.go b/exchanges/coinbasepro/coinbasepro.go index 5d5649ea64a..7d85fe14468 100644 --- a/exchanges/coinbasepro/coinbasepro.go +++ b/exchanges/coinbasepro/coinbasepro.go @@ -87,6 +87,9 @@ const ( errPayMethodNotFound = "payment method '%v' not found" errIntervalNotSupported = "interval not supported" + errUnknownEndpointLimit = "unknown endpoint limit %v" + errUnknownL2DataType = "unknown l2update data type %v" + errUnknownSide = "unknown side %v" ) var ( @@ -116,8 +119,8 @@ var ( errNameEmpty = errors.New("name cannot be empty") errPortfolioIDEmpty = errors.New("portfolio id cannot be empty") errFeeTypeNotSupported = errors.New("fee type not supported") - errUnknownEndpointLimit = errors.New("unknown endpoint limit") errNoEventsWS = errors.New("no events returned from websocket") + errCantDecodePrivKey = errors.New("cannot decode private key") ) // GetAllAccounts returns information on all trading accounts associated with the API key @@ -360,7 +363,7 @@ func (c *CoinbasePro) PlaceOrder(ctx context.Context, clientOID, productID, side coinbaseV3+coinbaseOrders, "", req, true, &resp, nil) } -// CancelOrders cancels orders by orderID +// 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 if len(orderIDs) == 0 { @@ -1345,9 +1348,16 @@ func (c *CoinbasePro) SendAuthenticatedHTTPRequest(ctx context.Context, ep excha return nil, err } + // jwt, err := c.GetJWT(ctx, method+" "+path) + // if err != nil { + // return nil, err + // } + // fmt.Printf("Here's the JWT you were looking for: %s\n", jwt) + headers := make(map[string]string) headers["CB-ACCESS-KEY"] = creds.Key headers["CB-ACCESS-SIGN"] = hex.EncodeToString(hmac) + // headers["Authorization"] = "Bearer " + jwt headers["CB-ACCESS-TIMESTAMP"] = n headers["Content-Type"] = "application/json" headers["CB-VERSION"] = "2023-11-13" diff --git a/exchanges/coinbasepro/coinbasepro_test.go b/exchanges/coinbasepro/coinbasepro_test.go index 1078d40c568..be16378ca9e 100644 --- a/exchanges/coinbasepro/coinbasepro_test.go +++ b/exchanges/coinbasepro/coinbasepro_test.go @@ -2114,6 +2114,10 @@ func TestStringToFloatPtr(t *testing.T) { } } +func TestWsSomethingOrOther(t *testing.T) { + +} + func skipTestIfLowOnFunds(t *testing.T) { accounts, err := c.GetAllAccounts(context.Background(), 250, "") if err != nil { diff --git a/exchanges/coinbasepro/coinbasepro_types.go b/exchanges/coinbasepro/coinbasepro_types.go index 022d5df84bb..657666eacb7 100644 --- a/exchanges/coinbasepro/coinbasepro_types.go +++ b/exchanges/coinbasepro/coinbasepro_types.go @@ -6,12 +6,15 @@ import ( "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" + "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/types" ) // CoinbasePro is the overarching type across the coinbasepro package type CoinbasePro struct { exchange.Base + jwt string + jwtLastRegen time.Time } // Version is used for the niche cases where the Version of the API must be specified and passed @@ -1085,43 +1088,43 @@ type WebsocketSubscribe struct { // } // wsOrderReceived holds websocket received values -type wsOrderReceived struct { - Type string `json:"type"` - OrderID string `json:"order_id"` - OrderType string `json:"order_type"` - Size float64 `json:"size,string"` - Price float64 `json:"price,omitempty,string"` - Funds float64 `json:"funds,omitempty,string"` - Side string `json:"side"` - ClientOID string `json:"client_oid"` - ProductID string `json:"product_id"` - Sequence int64 `json:"sequence"` - Time time.Time `json:"time"` - RemainingSize float64 `json:"remaining_size,string"` - NewSize float64 `json:"new_size,string"` - OldSize float64 `json:"old_size,string"` - Reason string `json:"reason"` - Timestamp float64 `json:"timestamp,string"` - UserID string `json:"user_id"` - ProfileID string `json:"profile_id"` - StopType string `json:"stop_type"` - StopPrice float64 `json:"stop_price,string"` - TakerFeeRate float64 `json:"taker_fee_rate,string"` - Private bool `json:"private"` - TradeID int64 `json:"trade_id"` - MakerOrderID string `json:"maker_order_id"` - TakerOrderID string `json:"taker_order_id"` - TakerUserID string `json:"taker_user_id"` -} +// type wsOrderReceived struct { +// Type string `json:"type"` +// OrderID string `json:"order_id"` +// OrderType string `json:"order_type"` +// Size float64 `json:"size,string"` +// Price float64 `json:"price,omitempty,string"` +// Funds float64 `json:"funds,omitempty,string"` +// Side string `json:"side"` +// ClientOID string `json:"client_oid"` +// ProductID string `json:"product_id"` +// Sequence int64 `json:"sequence"` +// Time time.Time `json:"time"` +// RemainingSize float64 `json:"remaining_size,string"` +// NewSize float64 `json:"new_size,string"` +// OldSize float64 `json:"old_size,string"` +// Reason string `json:"reason"` +// Timestamp float64 `json:"timestamp,string"` +// UserID string `json:"user_id"` +// ProfileID string `json:"profile_id"` +// StopType string `json:"stop_type"` +// StopPrice float64 `json:"stop_price,string"` +// TakerFeeRate float64 `json:"taker_fee_rate,string"` +// Private bool `json:"private"` +// TradeID int64 `json:"trade_id"` +// MakerOrderID string `json:"maker_order_id"` +// TakerOrderID string `json:"taker_order_id"` +// TakerUserID string `json:"taker_user_id"` +// } // WebsocketHeartBeat defines JSON response for a heart beat message -type WebsocketHeartBeat struct { - Type string `json:"type"` - Sequence int64 `json:"sequence"` - LastTradeID int64 `json:"last_trade_id"` - ProductID string `json:"product_id"` - Time string `json:"time"` -} +// type WebsocketHeartBeat struct { +// Type string `json:"type"` +// Sequence int64 `json:"sequence"` +// LastTradeID int64 `json:"last_trade_id"` +// ProductID string `json:"product_id"` +// Time string `json:"time"` +// } // WebsocketTicker defines ticker websocket response type WebsocketTicker struct { @@ -1136,23 +1139,118 @@ type WebsocketTicker struct { PricePercentageChange24H float64 `json:"price_percent_chg_24_h,string"` } -// WebsocketOrderbookSnapshot defines a snapshot response -type WebsocketOrderbookSnapshot struct { - ProductID string `json:"product_id"` - Type string `json:"type"` - Bids [][2]string `json:"bids"` - Asks [][2]string `json:"asks"` - Time time.Time `json:"time"` +// WebsocketTickerHolder holds a variety of ticker responses +type WebsocketTickerHolder struct { + Type string `json:"type"` + Tickers []WebsocketTicker `json:"tickers"` } -// WebsocketL2Update defines an update on the L2 orderbooks -type WebsocketL2Update struct { - Type string `json:"type"` - ProductID string `json:"product_id"` - Time time.Time `json:"time"` - Changes [][3]string `json:"changes"` +// WebsocketCandle defines a candle websocket response +type WebsocketCandle 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"` + ProductID currency.Pair `json:"product_id"` +} + +// WebsocketCandleHolder holds a variety of candle responses +type WebsocketCandleHolder struct { + Type string `json:"type"` + Candles []WebsocketCandle `json:"candles"` +} + +// WebsocketMarketTrade defines a market trade websocket response +type WebsocketMarketTrade struct { + TradeID string `json:"trade_id"` + ProductID currency.Pair `json:"product_id"` + Price float64 `json:"price,string"` + Size float64 `json:"size,string"` + Side order.Side `json:"side"` + Time time.Time `json:"time"` +} + +// WebsocketMarketTradeHolder holds a variety of market trade responses +type WebsocketMarketTradeHolder struct { + Type string `json:"type"` + Trades []WebsocketMarketTrade `json:"trades"` +} + +// WebsocketProduct defines a product websocket response +type WebsocketProduct struct { + ProductType string `json:"product_type"` + ID currency.Pair `json:"id"` + BaseCurrency string `json:"base_currency"` + QuoteCurrency string `json:"quote_currency"` + BaseIncrement float64 `json:"base_increment,string"` + QuoteIncrement float64 `json:"quote_increment,string"` + DisplayName string `json:"display_name"` + Status string `json:"status"` + StatusMessage string `json:"status_message"` + MinMarketFunds float64 `json:"min_market_funds,string"` +} + +// WebsocketProductHolder holds a variety of product responses +type WebsocketProductHolder struct { + Type string `json:"type"` + Products []WebsocketProduct `json:"products"` } +// WebsocketOrderbookData defines a websocket orderbook response +type WebsocketOrderbookData struct { + Side string `json:"side"` + EventTime time.Time `json:"event_time"` + PriceLevel float64 `json:"price_level,string"` + NewQuantity float64 `json:"new_quantity,string"` +} + +// WebsocketOrderbookDataHolder holds a variety of orderbook responses +type WebsocketOrderbookDataHolder struct { + Type string `json:"type"` + ProductID currency.Pair `json:"product_id"` + Changes []WebsocketOrderbookData `json:"updates"` +} + +// WebsocketOrderData defines a websocket order response +type WebsocketOrderData struct { + OrderID string `json:"order_id"` + ClientOrderID string `json:"client_order_id"` + CumulativeQuantity float64 `json:"cumulative_quantity,string"` + LeavesQuantity float64 `json:"leaves_quantity,string"` + AveragePrice float64 `json:"avg_price,string"` + TotalFees float64 `json:"total_fees,string"` + Status string `json:"status"` + ProductID currency.Pair `json:"product_id"` + CreationTime time.Time `json:"creation_time"` + OrderSide string `json:"order_side"` + OrderType string `json:"order_type"` +} + +// WebsocketOrderDataHolder holds a variety of order responses +type WebsocketOrderDataHolder struct { + Type string `json:"type"` + Orders []WebsocketOrderData `json:"orders"` +} + +// WebsocketOrderbookSnapshot defines a snapshot response +// type WebsocketOrderbookSnapshot struct { +// ProductID string `json:"product_id"` +// Type string `json:"type"` +// Bids [][2]string `json:"bids"` +// Asks [][2]string `json:"asks"` +// Time time.Time `json:"time"` +// } + +// WebsocketL2Update defines an update on the L2 orderbooks +// type WebsocketL2Update struct { +// Type string `json:"type"` +// ProductID string `json:"product_id"` +// Time time.Time `json:"time"` +// Changes [][3]string `json:"changes"` +// } + type wsGen struct { Channel string `json:"channel"` ClientID string `json:"client_id"` @@ -1161,36 +1259,36 @@ type wsGen struct { Events []interface{} `json:"events"` } -type wsStatus struct { - Currencies []struct { - ConvertibleTo []string `json:"convertible_to"` - Details struct{} `json:"details"` - ID string `json:"id"` - MaxPrecision float64 `json:"max_precision,string"` - MinSize float64 `json:"min_size,string"` - Name string `json:"name"` - Status string `json:"status"` - StatusMessage interface{} `json:"status_message"` - } `json:"currencies"` - Products []struct { - BaseCurrency string `json:"base_currency"` - BaseIncrement float64 `json:"base_increment,string"` - BaseMaxSize float64 `json:"base_max_size,string"` - BaseMinSize float64 `json:"base_min_size,string"` - CancelOnly bool `json:"cancel_only"` - DisplayName string `json:"display_name"` - ID string `json:"id"` - LimitOnly bool `json:"limit_only"` - MaxMarketFunds float64 `json:"max_market_funds,string"` - MinMarketFunds float64 `json:"min_market_funds,string"` - PostOnly bool `json:"post_only"` - QuoteCurrency string `json:"quote_currency"` - QuoteIncrement float64 `json:"quote_increment,string"` - Status string `json:"status"` - StatusMessage interface{} `json:"status_message"` - } `json:"products"` - Type string `json:"type"` -} +// type wsStatus struct { +// Currencies []struct { +// ConvertibleTo []string `json:"convertible_to"` +// Details struct{} `json:"details"` +// ID string `json:"id"` +// MaxPrecision float64 `json:"max_precision,string"` +// MinSize float64 `json:"min_size,string"` +// Name string `json:"name"` +// Status string `json:"status"` +// StatusMessage interface{} `json:"status_message"` +// } `json:"currencies"` +// Products []struct { +// BaseCurrency string `json:"base_currency"` +// BaseIncrement float64 `json:"base_increment,string"` +// BaseMaxSize float64 `json:"base_max_size,string"` +// BaseMinSize float64 `json:"base_min_size,string"` +// CancelOnly bool `json:"cancel_only"` +// DisplayName string `json:"display_name"` +// ID string `json:"id"` +// LimitOnly bool `json:"limit_only"` +// MaxMarketFunds float64 `json:"max_market_funds,string"` +// MinMarketFunds float64 `json:"min_market_funds,string"` +// PostOnly bool `json:"post_only"` +// QuoteCurrency string `json:"quote_currency"` +// QuoteIncrement float64 `json:"quote_increment,string"` +// Status string `json:"status"` +// StatusMessage interface{} `json:"status_message"` +// } `json:"products"` +// Type string `json:"type"` +// } // Params is used within functions to make the setting of parameters easier type Params struct { diff --git a/exchanges/coinbasepro/coinbasepro_websocket.go b/exchanges/coinbasepro/coinbasepro_websocket.go index ae425ba32c2..05c84f5ac89 100644 --- a/exchanges/coinbasepro/coinbasepro_websocket.go +++ b/exchanges/coinbasepro/coinbasepro_websocket.go @@ -2,22 +2,29 @@ package coinbasepro import ( "context" + "crypto/ecdsa" + "crypto/rand" + "crypto/sha256" + "crypto/x509" "encoding/hex" "encoding/json" - "errors" + "encoding/pem" "fmt" "net/http" "strconv" + "strings" "time" + "github.com/buger/jsonparser" "github.com/gorilla/websocket" + "github.com/pkg/errors" "github.com/thrasher-corp/gocryptotrader/common" - "github.com/thrasher-corp/gocryptotrader/common/convert" "github.com/thrasher-corp/gocryptotrader/common/crypto" "github.com/thrasher-corp/gocryptotrader/currency" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" + "github.com/thrasher-corp/gocryptotrader/exchanges/request" "github.com/thrasher-corp/gocryptotrader/exchanges/stream" "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" "github.com/thrasher-corp/gocryptotrader/exchanges/trade" @@ -60,7 +67,7 @@ func (c *CoinbasePro) wsReadData() { } func (c *CoinbasePro) wsHandleData(respRaw []byte) error { - fmt.Println("WHADDUP:", string(respRaw)) + // fmt.Println("WHADDUP:", string(respRaw)) genData := wsGen{} err := json.Unmarshal(respRaw, &genData) if err != nil { @@ -71,191 +78,182 @@ func (c *CoinbasePro) wsHandleData(respRaw []byte) error { return nil } - fmt.Printf("=== OH NOO LOOK AT THIS DATA WE HAVE TO DEAL WITH: %s ===\n", genData.Events) + // fmt.Printf("=== OH NOO LOOK AT THIS DATA WE HAVE TO DEAL WITH: %s ===\n", genData.Events) + + // data, _, _, err := jsonparser.Get(respRaw, "events") + // if err != nil { + // return err + // } + // specData := []WebsocketTickerHolder{} + // err = json.Unmarshal(data, &specData) + // if err != nil { + // return err + // } + // fmt.Printf("===== AWESOME, WE'VE GOT THE GOOD DATA: %v =====\n", specData) + + if len(genData.Events) == 0 { + return errNoEventsWS + } + + data, _, _, err := jsonparser.Get(respRaw, "events") + if err != nil { + return err + } + // fmt.Printf("==== WEEWOO WE'VE GOT THE NASTY DATA: %s ====\n", data) switch genData.Channel { case "status": - var status wsStatus - err = json.Unmarshal(respRaw, &status) + wsStatus := []WebsocketProductHolder{} + + err = json.Unmarshal(data, &wsStatus) if err != nil { return err } - c.Websocket.DataHandler <- status + case "error": c.Websocket.DataHandler <- errors.New(string(respRaw)) - case "ticker": - var wsTicker []WebsocketTicker - if len(genData.Events) == 0 { - return errNoEventsWS - } - err := json.Unmarshal(respRaw, &wsTicker) + case "ticker", "ticker_batch": + wsTicker := []WebsocketTickerHolder{} + + err = json.Unmarshal(data, &wsTicker) if err != nil { return err } for i := range wsTicker { - c.Websocket.DataHandler <- &ticker.Price{ - LastUpdated: genData.Timestamp, - Pair: wsTicker[i].ProductID, - AssetType: asset.Spot, - ExchangeName: c.Name, - High: wsTicker[i].High24H, - Low: wsTicker[i].Low24H, - Last: wsTicker[i].Price, - Volume: wsTicker[i].Volume24H, + for j := range wsTicker[i].Tickers { + c.Websocket.DataHandler <- &ticker.Price{ + LastUpdated: genData.Timestamp, + Pair: wsTicker[i].Tickers[j].ProductID, + AssetType: asset.Spot, + ExchangeName: c.Name, + High: wsTicker[i].Tickers[j].High24H, + Low: wsTicker[i].Tickers[j].Low24H, + Last: wsTicker[i].Tickers[j].Price, + Volume: wsTicker[i].Tickers[j].Volume24H, + } } } - case "snapshot": - var snapshot WebsocketOrderbookSnapshot - err := json.Unmarshal(respRaw, &snapshot) - if err != nil { - return err - } + // fmt.Printf("=== WOOT, IT WORKED ===\n") + case "candles": + wsCandles := []WebsocketCandleHolder{} - err = c.ProcessSnapshot(&snapshot) - if err != nil { - return err - } - case "l2update": - var update WebsocketL2Update - err := json.Unmarshal(respRaw, &update) + err = json.Unmarshal(data, &wsCandles) if err != nil { return err } - err = c.ProcessUpdate(&update) - if err != nil { - return err + for i := range wsCandles { + for j := range wsCandles[i].Candles { + c.Websocket.DataHandler <- &stream.KlineData{ + Timestamp: genData.Timestamp, + Pair: wsCandles[i].Candles[j].ProductID, + AssetType: asset.Spot, + Exchange: c.Name, + StartTime: wsCandles[i].Candles[j].Start.Time(), + OpenPrice: wsCandles[i].Candles[j].Open, + ClosePrice: wsCandles[i].Candles[j].Close, + HighPrice: wsCandles[i].Candles[j].High, + LowPrice: wsCandles[i].Candles[j].Low, + Volume: wsCandles[i].Candles[j].Volume, + } + } } - case "received", "open", "done", "change", "activate": - var wsOrder wsOrderReceived - err := json.Unmarshal(respRaw, &wsOrder) + // fmt.Print("=== RECEIVED AND PROCESSED ===\n") + case "market_trades": + wsTrades := []WebsocketMarketTradeHolder{} + + err = json.Unmarshal(data, &wsTrades) if err != nil { return err } - var oType order.Type - var oSide order.Side - var oStatus order.Status - oType, err = order.StringToOrderType(wsOrder.OrderType) - if err != nil { - c.Websocket.DataHandler <- order.ClassificationError{ - Exchange: c.Name, - OrderID: wsOrder.OrderID, - Err: err, - } - } - oSide, err = order.StringToOrderSide(wsOrder.Side) - if err != nil { - c.Websocket.DataHandler <- order.ClassificationError{ - Exchange: c.Name, - OrderID: wsOrder.OrderID, - Err: err, + + for i := range wsTrades { + for j := range wsTrades[i].Trades { + c.Websocket.DataHandler <- &trade.Data{ + TID: wsTrades[i].Trades[j].TradeID, + Exchange: c.Name, + CurrencyPair: wsTrades[i].Trades[j].ProductID, + AssetType: asset.Spot, + Side: wsTrades[i].Trades[j].Side, + Price: wsTrades[i].Trades[j].Price, + Amount: wsTrades[i].Trades[j].Size, + Timestamp: wsTrades[i].Trades[j].Time, + } } } - oStatus, err = statusToStandardStatus(wsOrder.Type) + // fmt.Print("=== RECEIVED AND PROCESSED ===\n") + case "l2_data": + var wsL2 []WebsocketOrderbookDataHolder + err := json.Unmarshal(data, &wsL2) if err != nil { - c.Websocket.DataHandler <- order.ClassificationError{ - Exchange: c.Name, - OrderID: wsOrder.OrderID, - Err: err, - } - } - if wsOrder.Reason == "canceled" { - oStatus = order.Cancelled - } - ts := wsOrder.Time - if wsOrder.Type == "activate" { - ts = convert.TimeFromUnixTimestampDecimal(wsOrder.Timestamp) + return err } - creds, err := c.GetCredentials(context.TODO()) - if err != nil { - c.Websocket.DataHandler <- order.ClassificationError{ - Exchange: c.Name, - OrderID: wsOrder.OrderID, - Err: err, + for i := range wsL2 { + // fmt.Printf("======== DATA THAT WE JUST HIT: %v ========\n", wsL2[i]) + switch wsL2[i].Type { + case "snapshot": + err = c.ProcessSnapshot(wsL2[i], genData.Timestamp) + case "update": + err = c.ProcessUpdate(wsL2[i], genData.Timestamp) + default: + err = errors.Errorf(errUnknownL2DataType, wsL2[i].Type) } - } - - if wsOrder.UserID != "" { - var p currency.Pair - var a asset.Item - p, a, err = c.GetRequestFormattedPairAndAssetType(wsOrder.ProductID) if err != nil { return err } - c.Websocket.DataHandler <- &order.Detail{ - HiddenOrder: wsOrder.Private, - Price: wsOrder.Price, - Amount: wsOrder.Size, - TriggerPrice: wsOrder.StopPrice, - ExecutedAmount: wsOrder.Size - wsOrder.RemainingSize, - RemainingAmount: wsOrder.RemainingSize, - Fee: wsOrder.TakerFeeRate, - Exchange: c.Name, - OrderID: wsOrder.OrderID, - AccountID: wsOrder.ProfileID, - ClientID: creds.ClientID, - Type: oType, - Side: oSide, - Status: oStatus, - AssetType: a, - Date: ts, - Pair: p, - } - } - case "match": - var wsOrder wsOrderReceived - err := json.Unmarshal(respRaw, &wsOrder) - if err != nil { - return err - } - oSide, err := order.StringToOrderSide(wsOrder.Side) - if err != nil { - c.Websocket.DataHandler <- order.ClassificationError{ - Exchange: c.Name, - Err: err, - } + } - var p currency.Pair - var a asset.Item - p, a, err = c.GetRequestFormattedPairAndAssetType(wsOrder.ProductID) + case "user": + var wsUser []WebsocketOrderDataHolder + err := json.Unmarshal(data, &wsUser) if err != nil { return err } - if wsOrder.UserID != "" { - c.Websocket.DataHandler <- &order.Detail{ - OrderID: wsOrder.OrderID, - Pair: p, - AssetType: a, - Trades: []order.TradeHistory{ - { - Price: wsOrder.Price, - Amount: wsOrder.Size, - Exchange: c.Name, - TID: strconv.FormatInt(wsOrder.TradeID, 10), - Side: oSide, - Timestamp: wsOrder.Time, - IsMaker: wsOrder.TakerUserID == "", - }, - }, - } - } else { - if !c.IsSaveTradeDataEnabled() { - return nil + for i := range wsUser { + for j := range wsUser[i].Orders { + var oType order.Type + oType, err = order.StringToOrderType(wsUser[i].Orders[j].OrderType) + if err != nil { + c.Websocket.DataHandler <- stream.UnhandledMessageWarning{Message: c.Name + stream.UnhandledMessage + string(respRaw)} + continue + } + + var oSide order.Side + oSide, err = order.StringToOrderSide(wsUser[i].Orders[j].OrderSide) + if err != nil { + c.Websocket.DataHandler <- stream.UnhandledMessageWarning{Message: c.Name + stream.UnhandledMessage + string(respRaw)} + continue + } + + var oStatus order.Status + oStatus, err = statusToStandardStatus(wsUser[i].Orders[j].Status) + if err != nil { + c.Websocket.DataHandler <- stream.UnhandledMessageWarning{Message: c.Name + stream.UnhandledMessage + string(respRaw)} + continue + } + + c.Websocket.DataHandler <- &order.Detail{ + Price: wsUser[i].Orders[j].AveragePrice, + Amount: wsUser[i].Orders[j].CumulativeQuantity + wsUser[i].Orders[j].LeavesQuantity, + ExecutedAmount: wsUser[i].Orders[j].CumulativeQuantity, + RemainingAmount: wsUser[i].Orders[j].LeavesQuantity, + Fee: wsUser[i].Orders[j].TotalFees, + Exchange: c.Name, + OrderID: wsUser[i].Orders[j].OrderID, + ClientOrderID: wsUser[i].Orders[j].ClientOrderID, + Type: oType, + Side: oSide, + Status: oStatus, + AssetType: asset.Spot, + Date: wsUser[i].Orders[j].CreationTime, + Pair: wsUser[i].Orders[j].ProductID, + } } - return trade.AddTradesToBuffer(c.Name, trade.Data{ - Timestamp: wsOrder.Time, - Exchange: c.Name, - CurrencyPair: p, - AssetType: a, - Price: wsOrder.Price, - Amount: wsOrder.Size, - Side: oSide, - TID: strconv.FormatInt(wsOrder.TradeID, 10), - }) } + default: c.Websocket.DataHandler <- stream.UnhandledMessageWarning{Message: c.Name + stream.UnhandledMessage + string(respRaw)} return nil @@ -281,101 +279,80 @@ func statusToStandardStatus(stat string) (order.Status, error) { } // ProcessSnapshot processes the initial orderbook snap shot -func (c *CoinbasePro) ProcessSnapshot(snapshot *WebsocketOrderbookSnapshot) error { - pair, err := currency.NewPairFromString(snapshot.ProductID) +func (c *CoinbasePro) ProcessSnapshot(snapshot WebsocketOrderbookDataHolder, timestamp time.Time) error { + bids, asks, err := processBidAskArray(snapshot) + if err != nil { return err } - base := orderbook.Base{ - Pair: pair, - Bids: make(orderbook.Items, len(snapshot.Bids)), - Asks: make(orderbook.Items, len(snapshot.Asks)), - } - - for i := range snapshot.Bids { - var price float64 - price, err = strconv.ParseFloat(snapshot.Bids[i][0], 64) - if err != nil { - return err - } - var amount float64 - amount, err = strconv.ParseFloat(snapshot.Bids[i][1], 64) - if err != nil { - return err - } - base.Bids[i] = orderbook.Item{Price: price, Amount: amount} - } - - for i := range snapshot.Asks { - var price float64 - price, err = strconv.ParseFloat(snapshot.Asks[i][0], 64) - if err != nil { - return err - } - var amount float64 - amount, err = strconv.ParseFloat(snapshot.Asks[i][1], 64) - if err != nil { - return err - } - base.Asks[i] = orderbook.Item{Price: price, Amount: amount} - } - - base.Asset = asset.Spot - base.Pair = pair - base.Exchange = c.Name - base.VerifyOrderbook = c.CanVerifyOrderbook - base.LastUpdated = snapshot.Time - return c.Websocket.Orderbook.LoadSnapshot(&base) + return c.Websocket.Orderbook.LoadSnapshot(&orderbook.Base{ + Bids: bids, + Asks: asks, + Exchange: c.Name, + Pair: snapshot.ProductID, + Asset: asset.Spot, + LastUpdated: timestamp, + VerifyOrderbook: c.CanVerifyOrderbook, + }) } // ProcessUpdate updates the orderbook local cache -func (c *CoinbasePro) ProcessUpdate(update *WebsocketL2Update) error { - if len(update.Changes) == 0 { - return errors.New("no data in websocket update") - } +func (c *CoinbasePro) ProcessUpdate(update WebsocketOrderbookDataHolder, timestamp time.Time) error { + // fmt.Printf("====== DATA THAT WE'RE USING TO UPDATE: %v ======\n", update) + + bids, asks, err := processBidAskArray(update) - p, err := currency.NewPairFromString(update.ProductID) if err != nil { return err } - asks := make(orderbook.Items, 0, len(update.Changes)) - bids := make(orderbook.Items, 0, len(update.Changes)) - - for i := range update.Changes { - price, err := strconv.ParseFloat(update.Changes[i][1], 64) - if err != nil { - return err - } - volume, err := strconv.ParseFloat(update.Changes[i][2], 64) - if err != nil { - return err - } - if update.Changes[i][0] == order.Buy.Lower() { - bids = append(bids, orderbook.Item{Price: price, Amount: volume}) - } else { - asks = append(asks, orderbook.Item{Price: price, Amount: volume}) - } - } - - return c.Websocket.Orderbook.Update(&orderbook.Update{ + obU := orderbook.Update{ Bids: bids, Asks: asks, - Pair: p, - UpdateTime: update.Time, + Pair: update.ProductID, + UpdateTime: timestamp, Asset: asset.Spot, - }) + } + + // fmt.Printf("===== WE'RE ABOUT TO UPDATE THE ORDERBOOK: %v =====\n", obU) + + return c.Websocket.Orderbook.Update(&obU) +} + +// processBidAskArray is a helper function that turns WebsocketOrderbookDataHolder into arrays +// of bids and asks +func processBidAskArray(data WebsocketOrderbookDataHolder) ([]orderbook.Item, []orderbook.Item, error) { + var bids, asks []orderbook.Item + for i := range data.Changes { + switch data.Changes[i].Side { + case "bid": + bids = append(bids, orderbook.Item{ + Price: data.Changes[i].PriceLevel, + Amount: data.Changes[i].NewQuantity, + }) + case "offer": + asks = append(asks, orderbook.Item{ + Price: data.Changes[i].PriceLevel, + Amount: data.Changes[i].NewQuantity, + }) + default: + return nil, nil, errors.Errorf(errUnknownSide, data.Changes[i].Side) + } + } + return bids, asks, nil } // GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions() func (c *CoinbasePro) GenerateDefaultSubscriptions() ([]stream.ChannelSubscription, error) { var channels = []string{ "heartbeats", - // "level2_batch", /*Other orderbook feeds require authentication. This is batched in 50ms lots.*/ "ticker", - // "user", - // "matches", + "ticker_batch", + "candles", + "market_trades", + "level2", + "user", } enabledCurrencies, err := c.GetEnabledPairs(asset.Spot) if err != nil { @@ -399,7 +376,7 @@ func (c *CoinbasePro) GenerateDefaultSubscriptions() ([]stream.ChannelSubscripti return subscriptions, nil } -func (c *CoinbasePro) sendRequest(msgType, channel string, productID currency.Pair) error { +func (c *CoinbasePro) sendRequest(msgType, channel string, productID currency.Pair, rLim RateLimit) error { creds, err := c.GetCredentials(context.Background()) if err != nil { return err @@ -416,19 +393,29 @@ func (c *CoinbasePro) sendRequest(msgType, channel string, productID currency.Pa return err } + // jwt, err := c.GetJWT(context.Background(), "") + // if err != nil { + // return err + // } + req := WebsocketSubscribe{ Type: msgType, ProductIDs: []string{productID.String()}, Channel: channel, Signature: hex.EncodeToString(hmac), - Key: creds.Key, - Timestamp: n, + // JWT: jwt, + Key: creds.Key, + Timestamp: n, } - meow, _ := json.Marshal(req) + reqMarshal, _ := json.Marshal(req) - fmt.Print(string(meow)) + fmt.Print(string(reqMarshal)) + err = rLim.Limit(context.Background(), WSRate) + if err != nil { + return err + } err = c.Websocket.Conn.SendJSONMessage(req) if err != nil { return err @@ -482,8 +469,13 @@ func (c *CoinbasePro) Subscribe(channelsToSubscribe []stream.ChannelSubscription // subscribe.Timestamp = n // } // } + + var rLim RateLimit + + rLim.RateLimWS = request.NewRateLimit(coinbaseWSInterval, coinbaseWSRate) + for i := range channelsToSubscribe { - err := c.sendRequest("subscribe", channelsToSubscribe[i].Channel, channelsToSubscribe[i].Currency) + err := c.sendRequest("subscribe", channelsToSubscribe[i].Channel, channelsToSubscribe[i].Currency, rLim) if err != nil { return err } @@ -511,25 +503,89 @@ func (c *CoinbasePro) Unsubscribe(channelsToUnsubscribe []stream.ChannelSubscrip } } - err := c.Websocket.Conn.SendJSONMessage(unsubscribe) + var rLim RateLimit + + rLim.RateLimWS = request.NewRateLimit(coinbaseWSInterval, coinbaseWSRate) + + err := rLim.Limit(context.Background(), WSRate) + if err != nil { + return err + } + + err = c.Websocket.Conn.SendJSONMessage(unsubscribe) if err != nil { return err } + c.Websocket.RemoveSubscriptions(channelsToUnsubscribe...) return nil } -// const wow = "-----BEGIN EC PRIVATE KEY-----\n%s\n-----END EC PRIVATE KEY-----\n" +// GetJWT checks if the current JWT is valid, returns it if it is, generates a new one if it isn't +// Also suitable for use in REST requests, by checking for the presence of a URI, and always generating +// a new JWT if one is not provided +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 + } -// func (c *CoinbasePro) GetJWT(ctx context.Context) (string, error) { -// creds, err := c.GetCredentials(ctx) -// if err != nil { -// return "", err -// } + key, err := x509.ParseECPrivateKey(block.Bytes) + if err != nil { + return "", err + } -// block, _ := pem.Decode([]byte(fmt.Sprintf(wow, creds.Secret))) -// if block == nil { -// return "", fmt.Errorf("jwt: Could not decode private key") -// } + 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 != "" { + body["uri"] = uri + } + bodyJson, err := json.Marshal(body) + if err != nil { + 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 +} + +// Base64URLEncode is a helper function that does some tweaks to standard Base64 encoding, in a way +// which JWT requires +func Base64URLEncode(b []byte) string { + s := crypto.Base64Encode(b) + s = strings.Split(s, "=")[0] + s = strings.ReplaceAll(s, "+", "-") + s = strings.ReplaceAll(s, "/", "_") + return s +} diff --git a/exchanges/coinbasepro/coinbasepro_wrapper.go b/exchanges/coinbasepro/coinbasepro_wrapper.go index 4759dfd5f9d..f500edf689a 100644 --- a/exchanges/coinbasepro/coinbasepro_wrapper.go +++ b/exchanges/coinbasepro/coinbasepro_wrapper.go @@ -1263,11 +1263,12 @@ func (c *CoinbasePro) fetchFutures(ctx context.Context) (AllProducts, error) { // cancelOrdersReturnMapAndCount is a helper function for CancelBatchOrders and CancelAllOrders, // calling the appropriate Coinbase endpoint, and returning information useful to both func (c *CoinbasePro) cancelOrdersReturnMapAndCount(ctx context.Context, o []order.Cancel) (map[string]string, int64, error) { - if len(o) == 0 { + ordToCancel := len(o) + if ordToCancel == 0 { return nil, 0, errOrderIDEmpty } status := make(map[string]string) - ordIDSlice := make([]string, len(o)) + ordIDSlice := make([]string, ordToCancel) for i := range o { err := o[i].Validate(o[i].StandardCancel()) if err != nil { @@ -1276,10 +1277,23 @@ func (c *CoinbasePro) cancelOrdersReturnMapAndCount(ctx context.Context, o []ord ordIDSlice[i] = o[i].OrderID status[o[i].OrderID] = "Failed to cancel" } - resp, err := c.CancelOrders(ctx, ordIDSlice) - if err != nil { - return nil, 0, err + + var resp CancelOrderResp + + for i := 0; i < ordToCancel; i += 100 { + var tempOrdIDSlice []string + if ordToCancel-i < 100 { + tempOrdIDSlice = ordIDSlice[i:] + } else { + tempOrdIDSlice = ordIDSlice[i : i+100] + } + tempResp, err := c.CancelOrders(ctx, tempOrdIDSlice) + if err != nil { + return nil, 0, err + } + resp.Results = append(resp.Results, tempResp.Results...) } + var counter int64 for i := range resp.Results { if resp.Results[i].Success { diff --git a/exchanges/coinbasepro/ratelimit.go b/exchanges/coinbasepro/ratelimit.go index 936affd12ec..8123e6de3ef 100644 --- a/exchanges/coinbasepro/ratelimit.go +++ b/exchanges/coinbasepro/ratelimit.go @@ -4,6 +4,7 @@ import ( "context" "time" + "github.com/pkg/errors" "github.com/thrasher-corp/gocryptotrader/exchanges/request" "golang.org/x/time/rate" ) @@ -17,7 +18,7 @@ const ( coinbaseV2Rate = 10000 coinbaseWSInterval = time.Second - coinbaseWSRate = 750 + coinbaseWSRate = 75 ) const ( @@ -43,7 +44,7 @@ func (r *RateLimit) Limit(ctx context.Context, f request.EndpointLimit) error { case WSRate: return r.RateLimWS.Wait(ctx) default: - return errUnknownEndpointLimit + return errors.Errorf(errUnknownEndpointLimit, f) } } From 6ac86cdd5bdf794f867e234a9431655eb968470e Mon Sep 17 00:00:00 2001 From: Samuel Reid <43227667+cranktakular@users.noreply.github.com> Date: Wed, 31 Jan 2024 16:48:07 +1100 Subject: [PATCH 21/79] CB websocket tidying up --- exchanges/coinbasepro/coinbasepro.go | 51 +--- exchanges/coinbasepro/coinbasepro_test.go | 129 +++++---- exchanges/coinbasepro/coinbasepro_types.go | 14 +- .../coinbasepro/coinbasepro_websocket.go | 273 +++++++++++++----- exchanges/coinbasepro/coinbasepro_wrapper.go | 8 +- exchanges/coinbasepro/ratelimit.go | 2 +- 6 files changed, 291 insertions(+), 186 deletions(-) diff --git a/exchanges/coinbasepro/coinbasepro.go b/exchanges/coinbasepro/coinbasepro.go index 7d85fe14468..1db8939f6ab 100644 --- a/exchanges/coinbasepro/coinbasepro.go +++ b/exchanges/coinbasepro/coinbasepro.go @@ -90,6 +90,7 @@ const ( errUnknownEndpointLimit = "unknown endpoint limit %v" errUnknownL2DataType = "unknown l2update data type %v" errUnknownSide = "unknown side %v" + warnSequenceIssue = "Out of order sequence number. Received %v, expected %v" ) var ( @@ -119,8 +120,8 @@ var ( errNameEmpty = errors.New("name cannot be empty") errPortfolioIDEmpty = errors.New("portfolio id cannot be empty") errFeeTypeNotSupported = errors.New("fee type not supported") - errNoEventsWS = errors.New("no events returned from websocket") errCantDecodePrivKey = errors.New("cannot decode private key") + errNoWalletForCurrency = errors.New("no wallet found for currency, address creation impossible") ) // GetAllAccounts returns information on all trading accounts associated with the API key @@ -474,7 +475,7 @@ func (c *CoinbasePro) GetAllOrders(ctx context.Context, productID, userNativeCur pathParams, nil, true, &resp, nil) } -// GetFills returns information of recent fills on the specified profile +// 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 var params Params @@ -503,14 +504,14 @@ func (c *CoinbasePro) GetFills(ctx context.Context, orderID, productID, cursor s } // GetOrderByID returns a single order by order id. -func (c *CoinbasePro) GetOrderByID(ctx context.Context, orderID, clientID, userNativeCurrency string) (*GetOrderResponse, error) { +func (c *CoinbasePro) GetOrderByID(ctx context.Context, orderID, clientOID, userNativeCurrency string) (*GetOrderResponse, error) { if orderID == "" { return nil, errOrderIDEmpty } var resp GetOrderResponse var params Params params.urlVals = url.Values{} - params.urlVals.Set("client_order_id", clientID) + 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) @@ -854,20 +855,6 @@ func (c *CoinbasePro) UpdateUser(ctx context.Context, name, timeZone, nativeCurr coinbaseV2+coinbaseUser, "", req, false, &resp, nil) } -// CreateWallet creates a new wallet for the specified currency -func (c *CoinbasePro) CreateWallet(ctx context.Context, currency string) (*GenWalletResponse, error) { - if currency == "" { - return nil, errCurrencyEmpty - } - - path := fmt.Sprintf("%s%s/%s", coinbaseV2, coinbaseAccounts, currency) - - var resp *GenWalletResponse - - return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - path, "", 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 @@ -904,34 +891,6 @@ func (c *CoinbasePro) GetWalletByID(ctx context.Context, walletID, currency stri path, "", nil, false, &resp, nil) } -// UpdateWalletName updates the name of a wallet -func (c *CoinbasePro) UpdateWalletName(ctx context.Context, walletID, newName string) (*GenWalletResponse, error) { - if walletID == "" { - return nil, errWalletIDEmpty - } - - path := fmt.Sprintf("%s%s/%s", coinbaseV2, coinbaseAccounts, walletID) - - req := map[string]interface{}{"name": newName} - - var resp *GenWalletResponse - - return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPut, - path, "", req, false, &resp, nil) -} - -// DeleteWallet deletes a wallet -func (c *CoinbasePro) DeleteWallet(ctx context.Context, walletID string) error { - if walletID == "" { - return errWalletIDEmpty - } - - path := fmt.Sprintf("%s%s/%s", coinbaseV2, coinbaseAccounts, walletID) - - return c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodDelete, path, "", nil, - false, nil, nil) -} - // CreateAddress generates a crypto address for depositing to the specified wallet func (c *CoinbasePro) CreateAddress(ctx context.Context, walletID, name string) (*GenAddrResponse, error) { if walletID == "" { diff --git a/exchanges/coinbasepro/coinbasepro_test.go b/exchanges/coinbasepro/coinbasepro_test.go index be16378ca9e..96eeaf657fd 100644 --- a/exchanges/coinbasepro/coinbasepro_test.go +++ b/exchanges/coinbasepro/coinbasepro_test.go @@ -13,6 +13,7 @@ import ( "testing" "time" + "github.com/buger/jsonparser" "github.com/gofrs/uuid" "github.com/stretchr/testify/assert" "github.com/thrasher-corp/gocryptotrader/common" @@ -25,6 +26,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/exchanges/kline" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues" + "github.com/thrasher-corp/gocryptotrader/exchanges/stream" gctlog "github.com/thrasher-corp/gocryptotrader/log" "github.com/thrasher-corp/gocryptotrader/portfolio/withdraw" ) @@ -76,6 +78,8 @@ const ( errExpectedFeeRange = "expected fee range of %v and %v, received %v" errJsonNumberIntoString = "json: cannot unmarshal number into Go value of type string" errParseIntValueOutOfRange = `strconv.ParseInt: parsing "922337203685477580700": value out of range` + errParseUintInvalidSyntax = `strconv.ParseUint: parsing "l": invalid syntax` + errJsonInvalidCharacter = `invalid character ':' after array element` expectedTimestamp = "1970-01-01 00:20:34 +0000 UTC" @@ -822,19 +826,6 @@ func TestUpdateUser(t *testing.T) { assert.NotEmpty(t, resp, errExpectedNonEmpty) } -func TestCreateWallet(t *testing.T) { - _, err := c.CreateWallet(context.Background(), "") - if !errors.Is(err, errCurrencyEmpty) { - t.Errorf(errExpectMismatch, err, errCurrencyEmpty) - } - sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) - resp, err := c.CreateWallet(context.Background(), testCrypto.String()) - if err != nil { - t.Error(err) - } - assert.NotEmpty(t, resp, errExpectedNonEmpty) -} - func TestGetAllWallets(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) pagIn := PaginationInp{Limit: 2} @@ -872,46 +863,6 @@ func TestGetWalletByID(t *testing.T) { assert.NotEmpty(t, resp, errExpectedNonEmpty) } -func TestUpdateWalletName(t *testing.T) { - _, err := c.UpdateWalletName(context.Background(), "", "") - if !errors.Is(err, errWalletIDEmpty) { - t.Errorf(errExpectMismatch, err, errWalletIDEmpty) - } - sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) - wID, err := c.GetAllWallets(context.Background(), PaginationInp{}) - if err != nil { - t.Error(err) - } - if len(wID.Data) == 0 { - t.Fatal(errExpectedNonEmpty) - } - resp, err := c.UpdateWalletName(context.Background(), wID.Data[len(wID.Data)-1].ID, "Wallet Tested by GCT") - if err != nil { - t.Error(err) - } - assert.NotEmpty(t, resp, errExpectedNonEmpty) -} - -func TestDeleteWallet(t *testing.T) { - err := c.DeleteWallet(context.Background(), "") - if !errors.Is(err, errWalletIDEmpty) { - t.Errorf(errExpectMismatch, err, errWalletIDEmpty) - } - sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) - wID, err := c.CreateWallet(context.Background(), testCrypto.String()) - if err != nil { - t.Error(err) - } - // As of now, it seems like this next step always fails. DeleteWallet only lets you delete non-primary - // non-fiat wallets, but the only non-primary wallet is fiat. Trying to create a secondary wallet for - // any cryptocurrency using CreateWallet simply returns the details of the existing primary wallet. - t.Skip("endpoint bugged on their end, skipping") - err = c.DeleteWallet(context.Background(), wID.Data.ID) - if err != nil { - t.Error(err) - } -} - func TestCreateAddress(t *testing.T) { _, err := c.CreateAddress(context.Background(), "", "") if !errors.Is(err, errWalletIDEmpty) { @@ -1765,8 +1716,12 @@ func TestGetOrderInfo(t *testing.T) { } func TestGetDepositAddress(t *testing.T) { - sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) - _, err := c.GetDepositAddress(context.Background(), currency.BTC, "", "") + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + _, err := c.GetDepositAddress(context.Background(), currency.NewCode("fake currency that doesn't exist"), "", "") + if !errors.Is(err, errNoWalletForCurrency) { + t.Errorf(errExpectMismatch, err, errNoWalletForCurrency) + } + _, err = c.GetDepositAddress(context.Background(), testCrypto, "", "") if err != nil { t.Error(err) } @@ -2114,8 +2069,70 @@ func TestStringToFloatPtr(t *testing.T) { } } -func TestWsSomethingOrOther(t *testing.T) { +func TestWsConnect(t *testing.T) { + c.Websocket.Disable() + err := c.WsConnect() + if err.Error() != stream.WebsocketNotEnabled { + t.Errorf(errExpectMismatch, err, stream.WebsocketNotEnabled) + } + c.Websocket.Enable() + err = c.WsConnect() + if err != nil { + t.Error(err) + } +} +func TestWsHandleData(t *testing.T) { + c.Websocket.DataHandler = make(chan interface{}, 4) + _, err := c.wsHandleData(nil, 0) + if !errors.Is(err, jsonparser.KeyPathNotFoundError) { + t.Errorf(errExpectMismatch, err, jsonparser.KeyPathNotFoundError) + } + mockJson := []byte(`{"sequence_num": "l"}`) + _, err = c.wsHandleData(mockJson, 0) + if err.Error() != errParseUintInvalidSyntax { + t.Errorf(errExpectMismatch, err, errParseUintInvalidSyntax) + } + mockJson = []byte(`{"sequence_num": 1, /\\/"""}`) + _, err = c.wsHandleData(mockJson, 0) + if !errors.Is(err, jsonparser.KeyPathNotFoundError) { + t.Errorf(errExpectMismatch, err, jsonparser.KeyPathNotFoundError) + } + mockJson = []byte(`{"sequence_num": 0, "channel": "subscriptions"}`) + _, err = c.wsHandleData(mockJson, 0) + if err != nil { + t.Error(err) + } + mockJson = []byte(`{"sequence_num": 0, "channel": "", "events":}`) + _, err = c.wsHandleData(mockJson, 0) + if !errors.Is(err, jsonparser.UnknownValueTypeError) { + t.Errorf(errExpectMismatch, err, jsonparser.UnknownValueTypeError) + } + mockJson = []byte(`{"sequence_num": 0, "channel": "status", "events": ["type": 1234]}`) + _, err = c.wsHandleData(mockJson, 0) + if err.Error() != errJsonInvalidCharacter { + t.Errorf(errExpectMismatch, err, errJsonInvalidCharacter) + } + mockJson = []byte(`{"sequence_num": 0, "channel": "status", "events": [{"type": "moo"}]}`) + _, err = c.wsHandleData(mockJson, 0) + if err != nil { + t.Error(err) + } + mockJson = []byte(`{"sequence_num": 0, "channel": "error", "events": [{"type": "moo"}]}`) + _, err = c.wsHandleData(mockJson, 0) + if err != nil { + t.Error(err) + } + mockJson = []byte(`{"sequence_num": 0, "channel": "ticker", "events": ["type": ""}]}`) + _, err = c.wsHandleData(mockJson, 0) + if err.Error() != errJsonInvalidCharacter { + t.Errorf(errExpectMismatch, err, errJsonInvalidCharacter) + } + mockJson = []byte(`{"sequence_num": 0, "channel": "ticker", "events": [{"type": "moo", "tickers": [{"price": "1.1"}]}]}`) + _, err = c.wsHandleData(mockJson, 0) + if err != nil { + t.Error(err) + } } func skipTestIfLowOnFunds(t *testing.T) { diff --git a/exchanges/coinbasepro/coinbasepro_types.go b/exchanges/coinbasepro/coinbasepro_types.go index 657666eacb7..f6dbc33ae83 100644 --- a/exchanges/coinbasepro/coinbasepro_types.go +++ b/exchanges/coinbasepro/coinbasepro_types.go @@ -1251,13 +1251,13 @@ type WebsocketOrderDataHolder struct { // Changes [][3]string `json:"changes"` // } -type wsGen struct { - Channel string `json:"channel"` - ClientID string `json:"client_id"` - Timestamp time.Time `json:"timestamp"` - SequenceNum uint64 `json:"sequence_num"` - Events []interface{} `json:"events"` -} +// type wsGen struct { +// Channel string `json:"channel"` +// ClientID string `json:"client_id"` +// Timestamp time.Time `json:"timestamp"` +// SequenceNum uint64 `json:"sequence_num"` +// Events []interface{} `json:"events"` +// } // type wsStatus struct { // Currencies []struct { diff --git a/exchanges/coinbasepro/coinbasepro_websocket.go b/exchanges/coinbasepro/coinbasepro_websocket.go index 05c84f5ac89..e91750883fa 100644 --- a/exchanges/coinbasepro/coinbasepro_websocket.go +++ b/exchanges/coinbasepro/coinbasepro_websocket.go @@ -11,8 +11,10 @@ import ( "encoding/pem" "fmt" "net/http" + _ "net/http/pprof" "strconv" "strings" + "sync" "time" "github.com/buger/jsonparser" @@ -24,7 +26,6 @@ import ( "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" - "github.com/thrasher-corp/gocryptotrader/exchanges/request" "github.com/thrasher-corp/gocryptotrader/exchanges/stream" "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" "github.com/thrasher-corp/gocryptotrader/exchanges/trade" @@ -54,29 +55,91 @@ func (c *CoinbasePro) WsConnect() error { func (c *CoinbasePro) wsReadData() { defer c.Websocket.Wg.Done() + var seqCount uint64 + for { resp := c.Websocket.Conn.ReadMessage() if resp.Raw == nil { return } - err := c.wsHandleData(resp.Raw) + warn, err := c.wsHandleData(resp.Raw, seqCount) if err != nil { c.Websocket.DataHandler <- err } + if warn != "" { + c.Websocket.DataHandler <- warn + tempStr := strings.SplitN(warn, "Out of order sequence number. Received ", 2)[1] + tempStr = strings.SplitN(tempStr, ", expected ", 2)[0] + tempNum, err := strconv.ParseUint(tempStr, 10, 64) + if err != nil { + c.Websocket.DataHandler <- err + } else { + seqCount = tempNum + } + } + seqCount++ } } -func (c *CoinbasePro) wsHandleData(respRaw []byte) error { - // fmt.Println("WHADDUP:", string(respRaw)) - genData := wsGen{} - err := json.Unmarshal(respRaw, &genData) - if err != nil { - return err +var meow sync.Mutex +var count int +var wcTime time.Duration +var bruh bool + +func WOW(tThen time.Time, msg []byte) { + meow.Lock() + if !bruh { + bruh = true + go func() { + for { + select { + case <-time.After(time.Second * 5): + meow.Lock() + fmt.Printf("COINBASEPRO: %v\n", count) + count = 0 + wcTime = 0 + meow.Unlock() + } + } + }() + } + this := time.Since(tThen) + if wcTime == 0 || this > wcTime { + fmt.Printf("Uh-oh, we took %s to process this message\n", this) + wcTime = this + if this > time.Millisecond*50 { + fmt.Printf("Oh jeez, I think I found the big one! %s\n", msg) + } } + count++ + meow.Unlock() +} - if genData.Channel == "subscriptions" || genData.Channel == "heartbeats" { - return nil +var alreadyDone bool + +func launchProfiling() { + if alreadyDone { + return } + alreadyDone = true + // go func() { + // http.ListenAndServe("localhost:6060", nil) + // }() + // fmt.Print("30 second pause, 1") + // time.Sleep(time.Second * 30) + // fmt.Print("5 second pause, 1") + // time.Sleep(time.Second * 5) +} + +func (c *CoinbasePro) wsHandleData(respRaw []byte, seqCount uint64) (string, error) { + // fmt.Println("WHADDUP:", string(respRaw)) + + // genData := wsGen{} + var warnString string + // err := json.Unmarshal(respRaw, &genData) + // if err != nil { + // return warnString, err + // } // fmt.Printf("=== OH NOO LOOK AT THIS DATA WE HAVE TO DEAL WITH: %s ===\n", genData.Events) @@ -91,24 +154,54 @@ func (c *CoinbasePro) wsHandleData(respRaw []byte) error { // } // fmt.Printf("===== AWESOME, WE'VE GOT THE GOOD DATA: %v =====\n", specData) - if len(genData.Events) == 0 { - return errNoEventsWS + // if len(genData.Events) == 0 { + // return warnString, errNoEventsWS + // } + + 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) + + tn := time.Now() + defer WOW(tn, channelRaw) + + if channel == "subscriptions" || channel == "heartbeats" { + return warnString, nil } data, _, _, err := jsonparser.Get(respRaw, "events") if err != nil { - return err + return warnString, err } // fmt.Printf("==== WEEWOO WE'VE GOT THE NASTY DATA: %s ====\n", data) - switch genData.Channel { + switch channel { case "status": wsStatus := []WebsocketProductHolder{} err = json.Unmarshal(data, &wsStatus) if err != nil { - return err + return warnString, err } + c.Websocket.DataHandler <- wsStatus case "error": c.Websocket.DataHandler <- errors.New(string(respRaw)) @@ -117,13 +210,20 @@ func (c *CoinbasePro) wsHandleData(respRaw []byte) error { err = json.Unmarshal(data, &wsTicker) if err != nil { - return err + return warnString, err + } + + sliToSend := []ticker.Price{} + + timestamp, err := getTimestamp(respRaw) + if err != nil { + return warnString, err } for i := range wsTicker { for j := range wsTicker[i].Tickers { - c.Websocket.DataHandler <- &ticker.Price{ - LastUpdated: genData.Timestamp, + sliToSend = append(sliToSend, ticker.Price{ + LastUpdated: timestamp, Pair: wsTicker[i].Tickers[j].ProductID, AssetType: asset.Spot, ExchangeName: c.Name, @@ -131,22 +231,30 @@ func (c *CoinbasePro) wsHandleData(respRaw []byte) error { Low: wsTicker[i].Tickers[j].Low24H, Last: wsTicker[i].Tickers[j].Price, Volume: wsTicker[i].Tickers[j].Volume24H, - } + }) } } + c.Websocket.DataHandler <- sliToSend // fmt.Printf("=== WOOT, IT WORKED ===\n") case "candles": wsCandles := []WebsocketCandleHolder{} err = json.Unmarshal(data, &wsCandles) if err != nil { - return err + return warnString, err + } + + sliToSend := []stream.KlineData{} + + timestamp, err := getTimestamp(respRaw) + if err != nil { + return warnString, err } for i := range wsCandles { for j := range wsCandles[i].Candles { - c.Websocket.DataHandler <- &stream.KlineData{ - Timestamp: genData.Timestamp, + sliToSend = append(sliToSend, stream.KlineData{ + Timestamp: timestamp, Pair: wsCandles[i].Candles[j].ProductID, AssetType: asset.Spot, Exchange: c.Name, @@ -156,21 +264,24 @@ func (c *CoinbasePro) wsHandleData(respRaw []byte) error { HighPrice: wsCandles[i].Candles[j].High, LowPrice: wsCandles[i].Candles[j].Low, Volume: wsCandles[i].Candles[j].Volume, - } + }) } } + c.Websocket.DataHandler <- sliToSend // fmt.Print("=== RECEIVED AND PROCESSED ===\n") case "market_trades": wsTrades := []WebsocketMarketTradeHolder{} err = json.Unmarshal(data, &wsTrades) if err != nil { - return err + return warnString, err } + sliToSend := []trade.Data{} + for i := range wsTrades { for j := range wsTrades[i].Trades { - c.Websocket.DataHandler <- &trade.Data{ + sliToSend = append(sliToSend, trade.Data{ TID: wsTrades[i].Trades[j].TradeID, Exchange: c.Name, CurrencyPair: wsTrades[i].Trades[j].ProductID, @@ -179,29 +290,35 @@ func (c *CoinbasePro) wsHandleData(respRaw []byte) error { Price: wsTrades[i].Trades[j].Price, Amount: wsTrades[i].Trades[j].Size, Timestamp: wsTrades[i].Trades[j].Time, - } + }) } } + c.Websocket.DataHandler <- sliToSend // fmt.Print("=== RECEIVED AND PROCESSED ===\n") case "l2_data": var wsL2 []WebsocketOrderbookDataHolder err := json.Unmarshal(data, &wsL2) if err != nil { - return err + return warnString, err + } + + timestamp, err := getTimestamp(respRaw) + if err != nil { + return warnString, err } for i := range wsL2 { // fmt.Printf("======== DATA THAT WE JUST HIT: %v ========\n", wsL2[i]) switch wsL2[i].Type { case "snapshot": - err = c.ProcessSnapshot(wsL2[i], genData.Timestamp) + err = c.ProcessSnapshot(wsL2[i], timestamp) case "update": - err = c.ProcessUpdate(wsL2[i], genData.Timestamp) + err = c.ProcessUpdate(wsL2[i], timestamp) default: err = errors.Errorf(errUnknownL2DataType, wsL2[i].Type) } if err != nil { - return err + return warnString, err } } @@ -209,9 +326,10 @@ func (c *CoinbasePro) wsHandleData(respRaw []byte) error { var wsUser []WebsocketOrderDataHolder err := json.Unmarshal(data, &wsUser) if err != nil { - return err + return warnString, err } + sliToSend := []order.Detail{} for i := range wsUser { for j := range wsUser[i].Orders { var oType order.Type @@ -235,7 +353,7 @@ func (c *CoinbasePro) wsHandleData(respRaw []byte) error { continue } - c.Websocket.DataHandler <- &order.Detail{ + sliToSend = append(sliToSend, order.Detail{ Price: wsUser[i].Orders[j].AveragePrice, Amount: wsUser[i].Orders[j].CumulativeQuantity + wsUser[i].Orders[j].LeavesQuantity, ExecutedAmount: wsUser[i].Orders[j].CumulativeQuantity, @@ -250,15 +368,16 @@ func (c *CoinbasePro) wsHandleData(respRaw []byte) error { AssetType: asset.Spot, Date: wsUser[i].Orders[j].CreationTime, Pair: wsUser[i].Orders[j].ProductID, - } + }) } } + c.Websocket.DataHandler <- sliToSend default: c.Websocket.DataHandler <- stream.UnhandledMessageWarning{Message: c.Name + stream.UnhandledMessage + string(respRaw)} - return nil + return warnString, nil } - return nil + return warnString, nil } func statusToStandardStatus(stat string) (order.Status, error) { @@ -347,12 +466,13 @@ func processBidAskArray(data WebsocketOrderbookDataHolder) ([]orderbook.Item, [] func (c *CoinbasePro) GenerateDefaultSubscriptions() ([]stream.ChannelSubscription, error) { var channels = []string{ "heartbeats", + // "status", "ticker", - "ticker_batch", + // "ticker_batch", "candles", - "market_trades", + // "market_trades", "level2", - "user", + // "user", } enabledCurrencies, err := c.GetEnabledPairs(asset.Spot) if err != nil { @@ -376,7 +496,7 @@ func (c *CoinbasePro) GenerateDefaultSubscriptions() ([]stream.ChannelSubscripti return subscriptions, nil } -func (c *CoinbasePro) sendRequest(msgType, channel string, productID currency.Pair, rLim RateLimit) error { +func (c *CoinbasePro) sendRequest(msgType, channel string, productIDs currency.Pairs) error { creds, err := c.GetCredentials(context.Background()) if err != nil { return err @@ -384,7 +504,7 @@ func (c *CoinbasePro) sendRequest(msgType, channel string, productID currency.Pa n := strconv.FormatInt(time.Now().Unix(), 10) - message := n + channel + productID.String() + message := n + channel + productIDs.Join() hmac, err := crypto.GetHMAC(crypto.HashSHA256, []byte(message), @@ -400,7 +520,7 @@ func (c *CoinbasePro) sendRequest(msgType, channel string, productID currency.Pa req := WebsocketSubscribe{ Type: msgType, - ProductIDs: []string{productID.String()}, + ProductIDs: productIDs.Strings(), Channel: channel, Signature: hex.EncodeToString(hmac), // JWT: jwt, @@ -408,14 +528,15 @@ func (c *CoinbasePro) sendRequest(msgType, channel string, productID currency.Pa Timestamp: n, } - reqMarshal, _ := json.Marshal(req) + // reqMarshal, _ := json.Marshal(req) - fmt.Print(string(reqMarshal)) + // fmt.Print(string(reqMarshal)) - err = rLim.Limit(context.Background(), WSRate) + // err = rLim.Limit(context.Background(), WSRate) if err != nil { return err } + // data, err := c.Websocket.Conn.SendMessageReturnResponse(nil, req) err = c.Websocket.Conn.SendJSONMessage(req) if err != nil { return err @@ -426,7 +547,9 @@ func (c *CoinbasePro) sendRequest(msgType, channel string, productID currency.Pa // Subscribe sends a websocket message to receive data from the channel func (c *CoinbasePro) Subscribe(channelsToSubscribe []stream.ChannelSubscription) error { - fmt.Printf("SUBSCRIBE: %v\n", channelsToSubscribe) + launchProfiling() + + // fmt.Printf("SUBSCRIBE: %v\n", channelsToSubscribe) // var creds *account.Credentials // var err error // if c.IsWebsocketAuthenticationSupported() { @@ -470,15 +593,25 @@ func (c *CoinbasePro) Subscribe(channelsToSubscribe []stream.ChannelSubscription // } // } - var rLim RateLimit + // var ( + // rLim RateLimit + // ) + + chanKeys := make(map[string]currency.Pairs) - rLim.RateLimWS = request.NewRateLimit(coinbaseWSInterval, coinbaseWSRate) + // rLim.RateLimWS = request.NewRateLimit(coinbaseWSInterval, coinbaseWSRate) for i := range channelsToSubscribe { - err := c.sendRequest("subscribe", channelsToSubscribe[i].Channel, channelsToSubscribe[i].Currency, rLim) + chanKeys[channelsToSubscribe[i].Channel] = + chanKeys[channelsToSubscribe[i].Channel].Add(channelsToSubscribe[i].Currency) + + } + for s := range chanKeys { + err := c.sendRequest("subscribe", s, chanKeys[s]) if err != nil { return err } + time.Sleep(time.Millisecond * 10) } c.Websocket.AddSuccessfulSubscriptions(channelsToSubscribe...) @@ -487,34 +620,20 @@ func (c *CoinbasePro) Subscribe(channelsToSubscribe []stream.ChannelSubscription // Unsubscribe sends a websocket message to stop receiving data from the channel func (c *CoinbasePro) Unsubscribe(channelsToUnsubscribe []stream.ChannelSubscription) error { - unsubscribe := WebsocketSubscribe{ - Type: "unsubscribe", - } + chanKeys := make(map[string]currency.Pairs) for i := range channelsToUnsubscribe { - p := channelsToUnsubscribe[i].Currency.String() - if !common.StringDataCompare(unsubscribe.ProductIDs, p) && p != "" { - unsubscribe.ProductIDs = append(unsubscribe.ProductIDs, p) - } - - if unsubscribe.Channel == channelsToUnsubscribe[i].Channel { - unsubscribe.Channel = channelsToUnsubscribe[i].Channel - - } - - } - var rLim RateLimit - - rLim.RateLimWS = request.NewRateLimit(coinbaseWSInterval, coinbaseWSRate) + chanKeys[channelsToUnsubscribe[i].Channel] = + chanKeys[channelsToUnsubscribe[i].Channel].Add(channelsToUnsubscribe[i].Currency) - err := rLim.Limit(context.Background(), WSRate) - if err != nil { - return err } - err = c.Websocket.Conn.SendJSONMessage(unsubscribe) - if err != nil { - return err + for s := range chanKeys { + err := c.sendRequest("unsubscribe", s, chanKeys[s]) + if err != nil { + return err + } + time.Sleep(time.Millisecond * 10) } c.Websocket.RemoveSubscriptions(channelsToUnsubscribe...) @@ -580,6 +699,18 @@ func (c *CoinbasePro) GetJWT(ctx context.Context, uri string) (string, error) { return headEncode + "." + bodyEncode + "." + sigEncode, nil } +func getTimestamp(rawData []byte) (time.Time, error) { + data, _, _, err := jsonparser.Get(rawData, "timestamp") + if err != nil { + return time.Time{}, err + } + timestamp, err := time.Parse(time.RFC3339, string(data)) + if err != nil { + return time.Time{}, err + } + return timestamp, nil +} + // Base64URLEncode is a helper function that does some tweaks to standard Base64 encoding, in a way // which JWT requires func Base64URLEncode(b []byte) string { diff --git a/exchanges/coinbasepro/coinbasepro_wrapper.go b/exchanges/coinbasepro/coinbasepro_wrapper.go index f500edf689a..4baa635d376 100644 --- a/exchanges/coinbasepro/coinbasepro_wrapper.go +++ b/exchanges/coinbasepro/coinbasepro_wrapper.go @@ -73,6 +73,8 @@ func (c *CoinbasePro) SetDefaults() { log.Errorln(log.ExchangeSys, err) } + // c.SetKeyChain(account.Primary, account.Secondary) + c.Features = exchange.Features{ Supports: exchange.FeaturesSupported{ REST: true, @@ -828,11 +830,7 @@ func (c *CoinbasePro) GetDepositAddress(ctx context.Context, cryptocurrency curr } } if targetWalletID == "" { - createWalResp, err := c.CreateWallet(ctx, cryptocurrency.String()) - if err != nil { - return nil, err - } - targetWalletID = createWalResp.Data.ID + return nil, errNoWalletForCurrency } resp, err := c.CreateAddress(ctx, targetWalletID, "") if err != nil { diff --git a/exchanges/coinbasepro/ratelimit.go b/exchanges/coinbasepro/ratelimit.go index 8123e6de3ef..916a6337d84 100644 --- a/exchanges/coinbasepro/ratelimit.go +++ b/exchanges/coinbasepro/ratelimit.go @@ -18,7 +18,7 @@ const ( coinbaseV2Rate = 10000 coinbaseWSInterval = time.Second - coinbaseWSRate = 75 + coinbaseWSRate = 750 ) const ( From cfe6e112289f6cceab82e0b96cc1648b15da43c4 Mon Sep 17 00:00:00 2001 From: Samuel Reid <43227667+cranktakular@users.noreply.github.com> Date: Tue, 6 Feb 2024 17:59:39 +1100 Subject: [PATCH 22/79] Coinbase WS wrapperupperer --- exchanges/coinbasepro/coinbasepro.go | 224 ++- exchanges/coinbasepro/coinbasepro_test.go | 1418 +++++++---------- exchanges/coinbasepro/coinbasepro_types.go | 91 ++ .../coinbasepro/coinbasepro_websocket.go | 209 +-- exchanges/coinbasepro/coinbasepro_wrapper.go | 1 + exchanges/stream/buffer/buffer.go | 3 + 6 files changed, 903 insertions(+), 1043 deletions(-) diff --git a/exchanges/coinbasepro/coinbasepro.go b/exchanges/coinbasepro/coinbasepro.go index 1db8939f6ab..b68f0ea8f48 100644 --- a/exchanges/coinbasepro/coinbasepro.go +++ b/exchanges/coinbasepro/coinbasepro.go @@ -39,6 +39,7 @@ const ( coinbaseFills = "fills" coinbaseCandles = "candles" coinbaseTicker = "ticker" + coinbasePreview = "preview" coinbasePortfolios = "portfolios" coinbaseMoveFunds = "move_funds" coinbaseCFM = "cfm" @@ -46,6 +47,9 @@ const ( coinbasePositions = "positions" coinbaseSweeps = "sweeps" coinbaseSchedule = "schedule" + coinbaseIntx = "intx" + coinbaseAllocate = "allocate" + coinbasePortfolio = "portfolio" coinbaseTransactionSummary = "transaction_summary" coinbaseConvert = "convert" coinbaseQuote = "quote" @@ -122,6 +126,7 @@ var ( errFeeTypeNotSupported = errors.New("fee type not supported") errCantDecodePrivKey = errors.New("cannot decode private key") errNoWalletForCurrency = errors.New("no wallet found for currency, address creation impossible") + errChannelNameUnknown = errors.New("unknown channel name") ) // GetAllAccounts returns information on all trading accounts associated with the API key @@ -301,61 +306,17 @@ func (c *CoinbasePro) PlaceOrder(ctx context.Context, clientOID, productID, side return nil, errAmountEmpty } - var orderConfig OrderConfiguration - - switch orderType { - case order.Market.String(), order.ImmediateOrCancel.String(): - orderConfig.MarketMarketIOC = &MarketMarketIOC{} - if side == order.Buy.String() { - orderConfig.MarketMarketIOC.QuoteSize = strconv.FormatFloat(amount, 'f', -1, 64) - } - if side == order.Sell.String() { - orderConfig.MarketMarketIOC.BaseSize = strconv.FormatFloat(amount, 'f', -1, 64) - } - case order.Limit.String(): - if endTime == (time.Time{}) { - orderConfig.LimitLimitGTC = &LimitLimitGTC{} - orderConfig.LimitLimitGTC.BaseSize = strconv.FormatFloat(amount, 'f', -1, 64) - orderConfig.LimitLimitGTC.LimitPrice = strconv.FormatFloat(limitPrice, 'f', -1, 64) - orderConfig.LimitLimitGTC.PostOnly = postOnly - } else { - orderConfig.LimitLimitGTD = &LimitLimitGTD{} - orderConfig.LimitLimitGTD.BaseSize = strconv.FormatFloat(amount, 'f', -1, 64) - orderConfig.LimitLimitGTD.LimitPrice = strconv.FormatFloat(limitPrice, 'f', -1, 64) - orderConfig.LimitLimitGTD.PostOnly = postOnly - orderConfig.LimitLimitGTD.EndTime = endTime - } - case order.StopLimit.String(): - if endTime == (time.Time{}) { - orderConfig.StopLimitStopLimitGTC = &StopLimitStopLimitGTC{} - orderConfig.StopLimitStopLimitGTC.BaseSize = strconv.FormatFloat(amount, 'f', -1, 64) - orderConfig.StopLimitStopLimitGTC.LimitPrice = strconv.FormatFloat(limitPrice, 'f', -1, - 64) - orderConfig.StopLimitStopLimitGTC.StopPrice = strconv.FormatFloat(stopPrice, 'f', -1, 64) - orderConfig.StopLimitStopLimitGTC.StopDirection = stopDirection - } else { - orderConfig.StopLimitStopLimitGTD = &StopLimitStopLimitGTD{} - orderConfig.StopLimitStopLimitGTD.BaseSize = strconv.FormatFloat(amount, 'f', -1, 64) - orderConfig.StopLimitStopLimitGTD.LimitPrice = strconv.FormatFloat(limitPrice, 'f', -1, - 64) - orderConfig.StopLimitStopLimitGTD.StopPrice = strconv.FormatFloat(stopPrice, 'f', -1, 64) - orderConfig.StopLimitStopLimitGTD.StopDirection = stopDirection - orderConfig.StopLimitStopLimitGTD.EndTime = endTime - } - default: - return nil, errInvalidOrderType + 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} - if marginType == "ISOLATED" || marginType == "CROSS" { - req["margin_type"] = marginType - } - if marginType == "MULTI" { - req["margin_type"] = "CROSS" - } + prepareMarginType(marginType, req) var resp PlaceOrderResp @@ -520,6 +481,35 @@ func (c *CoinbasePro) GetOrderByID(ctx context.Context, orderID, clientOID, user return &resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, pathParams, nil, true, &resp, nil) } +// PreviewOrder simulates the results of an order request +func (c *CoinbasePro) PreviewOrder(ctx context.Context, productID, side, orderType, stopDirection, marginType string, commissionValue, amount, limitPrice, stopPrice, tradableBalance, leverage float64, postOnly, isMax, skipFCMRiskCheck bool, endTime time.Time) (*PreviewOrderResp, error) { + 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, + "tradable_balance": strconv.FormatFloat(tradableBalance, 'f', -1, 64), + "skip_fcm_risk_check": skipFCMRiskCheck, "leverage": strconv.FormatFloat(leverage, 'f', -1, 64)} + + prepareMarginType(marginType, req) + + var resp *PreviewOrderResp + + path := fmt.Sprintf("%s%s/%s", coinbaseV3, coinbaseOrders, coinbasePreview) + + return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, path, "", 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) { var resp AllPortfolioResponse @@ -695,6 +685,75 @@ func (c *CoinbasePro) CancelPendingFuturesSweep(ctx context.Context) (SuccessBoo path, "", nil, true, &resp, nil) } +// AllocatePortfolio allocates funds to a position in your perpetuals portfolio +func (c *CoinbasePro) AllocatePortfolio(ctx context.Context, portfolioID, productID, currency string, amount float64) error { + if portfolioID == "" { + return errPortfolioIDEmpty + } + if productID == "" { + return errProductIDEmpty + } + if currency == "" { + return errCurrencyEmpty + } + 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) + + return c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, + path, "", 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 + + if portfolioID == "" { + return resp, errPortfolioIDEmpty + } + + path := fmt.Sprintf("%s%s/%s/%s", coinbaseV3, coinbaseIntx, coinbasePortfolio, portfolioID) + + return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, + path, "", 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 + + if portfolioID == "" { + return resp, errPortfolioIDEmpty + } + + path := fmt.Sprintf("%s%s/%s/%s", coinbaseV3, coinbaseIntx, coinbasePositions, portfolioID) + + return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, + path, "", 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 + + if portfolioID == "" { + return resp, errPortfolioIDEmpty + } + if productID == "" { + return resp, errProductIDEmpty + } + + path := fmt.Sprintf("%s%s/%s/%s/%s", coinbaseV3, coinbaseIntx, coinbasePositions, portfolioID, productID) + + return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, + path, "", 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) { @@ -1271,7 +1330,7 @@ func (c *CoinbasePro) SendHTTPRequest(ctx context.Context, ep exchange.URL, path } // SendAuthenticatedHTTPRequest sends an authenticated HTTP request -func (c *CoinbasePro) SendAuthenticatedHTTPRequest(ctx context.Context, ep exchange.URL, method, path, queryParams string, bodyParams map[string]interface{}, istrue bool, result interface{}, returnHead *http.Header) (err error) { +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) { creds, err := c.GetCredentials(ctx) if err != nil { return err @@ -1282,7 +1341,7 @@ func (c *CoinbasePro) SendAuthenticatedHTTPRequest(ctx context.Context, ep excha } // Version 2 wants query params in the path during signing - if !istrue { + if !isVersion3 { path = path + queryParams } @@ -1322,7 +1381,7 @@ func (c *CoinbasePro) SendAuthenticatedHTTPRequest(ctx context.Context, ep excha headers["CB-VERSION"] = "2023-11-13" // Version 3 only wants query params in the path when the request is sent - if istrue { + if isVersion3 { path = path + queryParams } @@ -1339,7 +1398,7 @@ func (c *CoinbasePro) SendAuthenticatedHTTPRequest(ctx context.Context, ep excha }, nil } rateLim := V2Rate - if istrue { + if isVersion3 { rateLim = V3Rate } @@ -1488,6 +1547,65 @@ func (p *Params) preparePagination(pag PaginationInp) { } } +// prepareOrderConfig is a helper function that popoulates 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{} + if side == order.Buy.String() { + orderConfig.MarketMarketIOC.QuoteSize = strconv.FormatFloat(amount, 'f', -1, 64) + } + if side == order.Sell.String() { + orderConfig.MarketMarketIOC.BaseSize = strconv.FormatFloat(amount, 'f', -1, 64) + } + case order.Limit.String(): + if endTime == (time.Time{}) { + orderConfig.LimitLimitGTC = &LimitLimitGTC{} + orderConfig.LimitLimitGTC.BaseSize = strconv.FormatFloat(amount, 'f', -1, 64) + orderConfig.LimitLimitGTC.LimitPrice = strconv.FormatFloat(limitPrice, 'f', -1, 64) + orderConfig.LimitLimitGTC.PostOnly = postOnly + } else { + orderConfig.LimitLimitGTD = &LimitLimitGTD{} + orderConfig.LimitLimitGTD.BaseSize = strconv.FormatFloat(amount, 'f', -1, 64) + orderConfig.LimitLimitGTD.LimitPrice = strconv.FormatFloat(limitPrice, 'f', -1, 64) + orderConfig.LimitLimitGTD.PostOnly = postOnly + orderConfig.LimitLimitGTD.EndTime = endTime + } + case order.StopLimit.String(): + if endTime == (time.Time{}) { + orderConfig.StopLimitStopLimitGTC = &StopLimitStopLimitGTC{} + orderConfig.StopLimitStopLimitGTC.BaseSize = strconv.FormatFloat(amount, 'f', -1, 64) + orderConfig.StopLimitStopLimitGTC.LimitPrice = strconv.FormatFloat(limitPrice, 'f', -1, + 64) + orderConfig.StopLimitStopLimitGTC.StopPrice = strconv.FormatFloat(stopPrice, 'f', -1, 64) + orderConfig.StopLimitStopLimitGTC.StopDirection = stopDirection + } else { + orderConfig.StopLimitStopLimitGTD = &StopLimitStopLimitGTD{} + orderConfig.StopLimitStopLimitGTD.BaseSize = strconv.FormatFloat(amount, 'f', -1, 64) + orderConfig.StopLimitStopLimitGTD.LimitPrice = strconv.FormatFloat(limitPrice, 'f', -1, + 64) + orderConfig.StopLimitStopLimitGTD.StopPrice = strconv.FormatFloat(stopPrice, 'f', -1, 64) + orderConfig.StopLimitStopLimitGTD.StopDirection = stopDirection + orderConfig.StopLimitStopLimitGTD.EndTime = endTime + } + default: + return orderConfig, errInvalidOrderType + } + return orderConfig, nil +} + +// prepareMarginType is a helper function that properly formats the margin type for the request +func prepareMarginType(marginType string, req map[string]interface{}) { + if marginType == "ISOLATED" || marginType == "CROSS" { + req["margin_type"] = marginType + } + if marginType == "MULTI" { + req["margin_type"] = "CROSS" + } +} + func (f FiatTransferType) String() string { if f { return "withdrawal" diff --git a/exchanges/coinbasepro/coinbasepro_test.go b/exchanges/coinbasepro/coinbasepro_test.go index 96eeaf657fd..2322212947f 100644 --- a/exchanges/coinbasepro/coinbasepro_test.go +++ b/exchanges/coinbasepro/coinbasepro_test.go @@ -2,12 +2,11 @@ package coinbasepro import ( "context" - "errors" "strconv" + "strings" "fmt" "log" - "net/url" "os" "sync" "testing" @@ -80,6 +79,11 @@ const ( errParseIntValueOutOfRange = `strconv.ParseInt: parsing "922337203685477580700": value out of range` errParseUintInvalidSyntax = `strconv.ParseUint: parsing "l": invalid syntax` errJsonInvalidCharacter = `invalid character ':' after array element` + errL2DataMoo = "unknown l2update data type moo" + errUnrecognisedOrderType = `'' unrecognised order type` + errOrderSideInvalid = `'' order side is invalid` + errUnrecognisedStatusType = " not recognised as status type" + errFakeSide = "unknown side fakeside" expectedTimestamp = "1970-01-01 00:20:34 +0000 UTC" @@ -87,28 +91,16 @@ const ( testPrice = 1e+09 ) -func TestGetDefaultConfig(t *testing.T) { - sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - _, err := c.GetDefaultConfig(context.Background()) - if err != nil { - t.Error(err) - } -} - func TestSetup(t *testing.T) { - err := c.Setup(nil) - if !errors.Is(err, config.ErrExchangeConfigIsNil) { - t.Errorf(errExpectMismatch, err, config.ErrExchangeConfigIsNil) - } + // err := c.Setup(nil) + // assert.ErrorIs(t, err, config.ErrExchangeConfigIsNil) cfg, err := c.GetStandardConfig() - if err != nil { - t.Error(err) - } + assert.NoError(t, err) cfg.API.AuthenticatedSupport = true cfg.API.Credentials.Key = apiKey cfg.API.Credentials.Secret = apiSecret cfg.Enabled = false - _ = c.Setup(cfg) + // _ = c.Setup(cfg) cfg.Enabled = true cfg.ProxyAddress = string(rune(0x7f)) err = c.Setup(cfg) @@ -120,9 +112,7 @@ func TestSetup(t *testing.T) { func TestWrapperStart(t *testing.T) { wg := sync.WaitGroup{} err := c.Start(context.Background(), &wg) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) } func TestMain(m *testing.M) { @@ -144,7 +134,6 @@ func TestMain(m *testing.M) { if apiKey != "" { gdxConfig.API.Credentials.Key = apiKey gdxConfig.API.Credentials.Secret = apiSecret - // gdxConfig.API.Credentials.ClientID = clientID gdxConfig.API.AuthenticatedSupport = true gdxConfig.API.AuthenticatedWebsocketSupport = true } @@ -157,18 +146,17 @@ func TestMain(m *testing.M) { c.GetBase().API.AuthenticatedSupport = true c.GetBase().API.AuthenticatedWebsocketSupport = true } - c.Verbose = true + // c.Verbose = true err = gctlog.SetGlobalLogConfig(gctlog.GenDefaultSettings()) fmt.Println(err) + c.Websocket.Enable() os.Exit(m.Run()) } func TestStart(t *testing.T) { t.Parallel() err := c.Start(context.Background(), nil) - if !errors.Is(err, common.ErrNilPointer) { - t.Fatalf(errExpectMismatch, err, common.ErrNilPointer) - } + assert.ErrorIs(t, err, common.ErrNilPointer) var testWg sync.WaitGroup err = c.Start(context.Background(), &testWg) if err != nil { @@ -190,29 +178,21 @@ func TestStart(t *testing.T) { func TestGetAllAccounts(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) resp, err := c.GetAllAccounts(context.Background(), 50, "") - if err != nil { - t.Error(err) - } + assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) } func TestGetAccountByID(t *testing.T) { _, err := c.GetAccountByID(context.Background(), "") - if !errors.Is(err, errAccountIDEmpty) { - t.Errorf(errExpectMismatch, err, errAccountIDEmpty) - } + assert.ErrorIs(t, err, errAccountIDEmpty) sharedtestvalues.SkipTestIfCredentialsUnset(t, c) longResp, err := c.GetAllAccounts(context.Background(), 49, "") - if err != nil { - t.Error(err) - } + assert.NoError(t, err) if len(longResp.Accounts) == 0 { t.Fatal(errExpectedNonEmpty) } shortResp, err := c.GetAccountByID(context.Background(), longResp.Accounts[0].UUID) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) if *shortResp != longResp.Accounts[0] { t.Errorf(errExpectMismatch, shortResp, longResp.Accounts[0]) } @@ -222,22 +202,16 @@ func TestGetBestBidAsk(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) testPairs := []string{testPair.String(), "ETH-USD"} resp, err := c.GetBestBidAsk(context.Background(), testPairs) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) } func TestGetProductBook(t *testing.T) { _, err := c.GetProductBook(context.Background(), "", 0) - if !errors.Is(err, errProductIDEmpty) { - t.Errorf(errExpectMismatch, err, errProductIDEmpty) - } + assert.ErrorIs(t, err, errProductIDEmpty) sharedtestvalues.SkipTestIfCredentialsUnset(t, c) resp, err := c.GetProductBook(context.Background(), testPair.String(), 2) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) } @@ -247,31 +221,23 @@ func TestGetAllProducts(t *testing.T) { // var testPairs []string resp, err := c.GetAllProducts(context.Background(), 30000, 0, "SPOT", "PERPETUAL", "", testPairs) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) // log.Printf("%+v\n%+v", resp.NumProducts, len(resp.Products)) } func TestGetProductByID(t *testing.T) { _, err := c.GetProductByID(context.Background(), "") - if !errors.Is(err, errProductIDEmpty) { - t.Errorf(errExpectMismatch, err, errProductIDEmpty) - } + assert.ErrorIs(t, err, errProductIDEmpty) sharedtestvalues.SkipTestIfCredentialsUnset(t, c) resp, err := c.GetProductByID(context.Background(), testPair.String()) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) } func TestGetHistoricRates(t *testing.T) { _, err := c.GetHistoricRates(context.Background(), "", granUnknown, time.Time{}, time.Time{}) - if !errors.Is(err, errProductIDEmpty) { - t.Errorf(errExpectMismatch, err, errProductIDEmpty) - } + assert.ErrorIs(t, err, errProductIDEmpty) _, err = c.GetHistoricRates(context.Background(), testPair.String(), "blorbo", time.Time{}, time.Time{}) if err.Error() != errBlorboGranularity { t.Errorf(errExpectMismatch, err, errBlorboGranularity) @@ -279,135 +245,89 @@ func TestGetHistoricRates(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) resp, err := c.GetHistoricRates(context.Background(), testPair.String(), granOneMin, time.Now().Add(-5*time.Minute), time.Now()) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) } func TestGetTicker(t *testing.T) { _, err := c.GetTicker(context.Background(), "", 1, time.Time{}, time.Time{}) - if !errors.Is(err, errProductIDEmpty) { - t.Errorf(errExpectMismatch, err, errProductIDEmpty) - } + assert.ErrorIs(t, err, errProductIDEmpty) sharedtestvalues.SkipTestIfCredentialsUnset(t, c) resp, err := c.GetTicker(context.Background(), testPair.String(), 5, time.Time{}, time.Time{}) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) } func TestPlaceOrder(t *testing.T) { _, err := c.PlaceOrder(context.Background(), "", "", "", "", "", "", "", "", 0, 0, 0, 0, false, time.Time{}) - if !errors.Is(err, errClientOrderIDEmpty) { - t.Errorf(errExpectMismatch, err, errClientOrderIDEmpty) - } + assert.ErrorIs(t, err, errClientOrderIDEmpty) _, err = c.PlaceOrder(context.Background(), "meow", "", "", "", "", "", "", "", 0, 0, 0, 0, false, time.Time{}) - if !errors.Is(err, errProductIDEmpty) { - t.Errorf(errExpectMismatch, err, errProductIDEmpty) - } + assert.ErrorIs(t, err, errProductIDEmpty) _, err = c.PlaceOrder(context.Background(), "meow", testPair.String(), order.Sell.String(), "", "", "", "", "", 0, 0, 0, 0, false, time.Time{}) - if !errors.Is(err, errAmountEmpty) { - t.Errorf(errExpectMismatch, err, errAmountEmpty) - } + assert.ErrorIs(t, err, errAmountEmpty) sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) skipTestIfLowOnFunds(t) id, err := uuid.NewV4() - if err != nil { - t.Error(err) - } + assert.NoError(t, err) resp, err := c.PlaceOrder(context.Background(), id.String(), testPair.String(), order.Sell.String(), "", order.Limit.String(), "", "CROSS", "", testAmount, testPrice, 0, 9999, false, time.Now().Add(time.Hour)) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) id, err = uuid.NewV4() - if err != nil { - t.Error(err) - } + assert.NoError(t, err) resp, err = c.PlaceOrder(context.Background(), id.String(), testPair.String(), order.Sell.String(), "", order.Limit.String(), "", "MULTI", "", testAmount, testPrice, 0, 9999, false, time.Now().Add(time.Hour)) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) } func TestCancelOrders(t *testing.T) { var OrderSlice []string _, err := c.CancelOrders(context.Background(), OrderSlice) - if !errors.Is(err, errOrderIDEmpty) { - t.Errorf(errExpectMismatch, err, errOrderIDEmpty) - } + assert.ErrorIs(t, err, errOrderIDEmpty) sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) skipTestIfLowOnFunds(t) ordID, err := c.PlaceOrder(context.Background(), "meow", testPair.String(), order.Sell.String(), "", order.Limit.String(), "", "", "", testPrice, testAmount, 0, 9999, false, time.Time{}) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) OrderSlice = append(OrderSlice, ordID.OrderID) resp, err := c.CancelOrders(context.Background(), OrderSlice) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) } func TestEditOrder(t *testing.T) { _, err := c.EditOrder(context.Background(), "", 0, 0) - if !errors.Is(err, errOrderIDEmpty) { - t.Errorf(errExpectMismatch, err, errOrderIDEmpty) - } + assert.ErrorIs(t, err, errOrderIDEmpty) _, err = c.EditOrder(context.Background(), "meow", 0, 0) - if !errors.Is(err, errSizeAndPriceZero) { - t.Errorf(errExpectMismatch, err, errSizeAndPriceZero) - } + assert.ErrorIs(t, err, errSizeAndPriceZero) sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) skipTestIfLowOnFunds(t) id, err := uuid.NewV4() - if err != nil { - t.Error(err) - } + assert.NoError(t, err) ordID, err := c.PlaceOrder(context.Background(), id.String(), testPair.String(), order.Sell.String(), "", order.Limit.String(), "", "", "", testAmount, testPrice, 0, 9999, false, time.Time{}) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) resp, err := c.EditOrder(context.Background(), ordID.OrderID, testAmount, testPrice*10) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) } func TestEditOrderPreview(t *testing.T) { _, err := c.EditOrderPreview(context.Background(), "", 0, 0) - if !errors.Is(err, errOrderIDEmpty) { - t.Errorf(errExpectMismatch, err, errOrderIDEmpty) - } + assert.ErrorIs(t, err, errOrderIDEmpty) _, err = c.EditOrderPreview(context.Background(), "meow", 0, 0) - if !errors.Is(err, errSizeAndPriceZero) { - t.Errorf(errExpectMismatch, err, errSizeAndPriceZero) - } + assert.ErrorIs(t, err, errSizeAndPriceZero) sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) skipTestIfLowOnFunds(t) id, err := uuid.NewV4() - if err != nil { - t.Error(err) - } + assert.NoError(t, err) ordID, err := c.PlaceOrder(context.Background(), id.String(), testPair.String(), order.Sell.String(), "", order.Limit.String(), "", "", "", testAmount, testPrice, 0, 9999, false, time.Time{}) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) resp, err := c.EditOrderPreview(context.Background(), ordID.OrderID, testAmount, testPrice*10) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) } @@ -416,138 +336,115 @@ func TestGetAllOrders(t *testing.T) { status := make([]string, 2) _, err := c.GetAllOrders(context.Background(), "", "", "", "", "", "", "", "", "", status, assets, 0, time.Unix(2, 2), time.Unix(1, 1)) - if !errors.Is(err, common.ErrStartAfterEnd) { - t.Errorf(errExpectMismatch, err, common.ErrStartAfterEnd) - } + assert.ErrorIs(t, err, common.ErrStartAfterEnd) status[0] = "CANCELLED" status[1] = "OPEN" _, err = c.GetAllOrders(context.Background(), "", "", "", "", "", "", "", "", "", status, assets, 0, time.Time{}, time.Time{}) - if !errors.Is(err, errOpenPairWithOtherTypes) { - t.Errorf(errExpectMismatch, err, errOpenPairWithOtherTypes) - } + assert.ErrorIs(t, err, errOpenPairWithOtherTypes) sharedtestvalues.SkipTestIfCredentialsUnset(t, c) status = make([]string, 0) assets = make([]string, 1) assets[0] = testCrypto.String() _, err = c.GetAllOrders(context.Background(), "", "USD", "LIMIT", "SELL", "", "SPOT", "RETAIL_ADVANCED", "UNKNOWN_CONTRACT_EXPIRY_TYPE", "2", status, assets, 10, time.Time{}, time.Time{}) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) } func TestGetFills(t *testing.T) { _, err := c.GetFills(context.Background(), "", "", "", time.Unix(2, 2), time.Unix(1, 1), 0) - if !errors.Is(err, common.ErrStartAfterEnd) { - t.Errorf(errExpectMismatch, err, common.ErrStartAfterEnd) - } + assert.ErrorIs(t, err, common.ErrStartAfterEnd) sharedtestvalues.SkipTestIfCredentialsUnset(t, c) _, err = c.GetFills(context.Background(), "", testPair.String(), "", time.Unix(1, 1), time.Now(), 5) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) status := []string{"OPEN"} ordID, err := c.GetAllOrders(context.Background(), "", "", "", "", "", "", "", "", "", status, nil, 3, time.Time{}, time.Time{}) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) if len(ordID.Orders) == 0 { t.Skip(skipInsufficientOrders) } _, err = c.GetFills(context.Background(), ordID.Orders[0].OrderID, "", "", time.Time{}, time.Time{}, 5) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) } func TestGetOrderByID(t *testing.T) { _, err := c.GetOrderByID(context.Background(), "", "", "") - if !errors.Is(err, errOrderIDEmpty) { - t.Errorf(errExpectMismatch, err, errOrderIDEmpty) - } + assert.ErrorIs(t, err, errOrderIDEmpty) sharedtestvalues.SkipTestIfCredentialsUnset(t, c) ordID, err := c.GetAllOrders(context.Background(), "", "", "", "", "", "", "", "", "", nil, nil, 10, time.Time{}, time.Time{}) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) if len(ordID.Orders) == 0 { t.Skip(skipInsufficientOrders) } - _, err = c.GetOrderByID(context.Background(), ordID.Orders[0].OrderID, ordID.Orders[0].ClientOID, "") - if err != nil { - t.Error(err) - } + resp, err := c.GetOrderByID(context.Background(), ordID.Orders[0].OrderID, ordID.Orders[0].ClientOID, "") + assert.NoError(t, err) + assert.NotEmpty(t, resp, errExpectedNonEmpty) +} + +func TestPreviewOrder(t *testing.T) { + _, err := c.PreviewOrder(context.Background(), "", "", "", "", "", 0, 0, 0, 0, 0, 0, false, false, false, time.Time{}) + assert.ErrorIs(t, err, errAmountEmpty) + _, err = c.PreviewOrder(context.Background(), "", "", "", "", "", 0, 1, 0, 0, 0, 0, false, false, false, time.Time{}) + assert.ErrorIs(t, err, errInvalidOrderType) + skipTestIfLowOnFunds(t) + resp, err := c.PreviewOrder(context.Background(), testPair.String(), "BUY", "MARKET", "", "", 0, 1, 0, 0, 0, 0, false, + false, false, time.Time{}) + assert.NoError(t, err) + assert.NotEmpty(t, resp, errExpectedNonEmpty) } func TestGetAllPortfolios(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) resp, err := c.GetAllPortfolios(context.Background(), "DEFAULT") - if err != nil { - t.Error(err) - } + assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) } func TestCreatePortfolio(t *testing.T) { _, err := c.CreatePortfolio(context.Background(), "") - if !errors.Is(err, errNameEmpty) { - t.Errorf(errExpectMismatch, err, errNameEmpty) - } + assert.ErrorIs(t, err, errNameEmpty) sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) - _, err = c.CreatePortfolio(context.Background(), "GCT Test Portfolio") + resp, err := c.CreatePortfolio(context.Background(), "GCT Test Portfolio") if err != nil && err.Error() != errPortfolioNameDuplicate { t.Error(err) } + assert.NotEmpty(t, resp, errExpectedNonEmpty) } func TestMovePortfolioFunds(t *testing.T) { _, err := c.MovePortfolioFunds(context.Background(), "", "", "", 0) - if !errors.Is(err, errPortfolioIDEmpty) { - t.Errorf(errExpectMismatch, err, errPortfolioIDEmpty) - } + assert.ErrorIs(t, err, errPortfolioIDEmpty) _, err = c.MovePortfolioFunds(context.Background(), "", "meowPort", "woofPort", 0) - if !errors.Is(err, errCurrencyEmpty) { - t.Errorf(errExpectMismatch, err, errCurrencyEmpty) - } + assert.ErrorIs(t, err, errCurrencyEmpty) _, err = c.MovePortfolioFunds(context.Background(), testCrypto.String(), "meowPort", "woofPort", 0) - if !errors.Is(err, errAmountEmpty) { - t.Errorf(errExpectMismatch, err, errAmountEmpty) - } + assert.ErrorIs(t, err, errAmountEmpty) sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) portID, err := c.GetAllPortfolios(context.Background(), "") - if err != nil { - t.Error(err) - } + assert.NoError(t, err) if len(portID.Portfolios) < 2 { t.Skip(skipInsufficientPortfolios) } - _, err = c.MovePortfolioFunds(context.Background(), testCrypto.String(), portID.Portfolios[0].UUID, portID.Portfolios[1].UUID, + resp, err := c.MovePortfolioFunds(context.Background(), testCrypto.String(), portID.Portfolios[0].UUID, portID.Portfolios[1].UUID, testAmount) if err != nil && err.Error() != errPortTransferInsufFunds { t.Error(err) } + assert.NotEmpty(t, resp, errExpectedNonEmpty) } func TestGetPortfolioByID(t *testing.T) { _, err := c.GetPortfolioByID(context.Background(), "") - if !errors.Is(err, errPortfolioIDEmpty) { - t.Errorf(errExpectMismatch, err, errPortfolioIDEmpty) - } + assert.ErrorIs(t, err, errPortfolioIDEmpty) sharedtestvalues.SkipTestIfCredentialsUnset(t, c) portID, err := c.GetAllPortfolios(context.Background(), "") - if err != nil { - t.Error(err) - } + assert.NoError(t, err) if len(portID.Portfolios) == 0 { t.Fatal(errExpectedNonEmpty) } resp, err := c.GetPortfolioByID(context.Background(), portID.Portfolios[0].UUID) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) if resp.Breakdown.Portfolio != portID.Portfolios[0] { t.Errorf(errExpectMismatch, resp.Breakdown.Portfolio, portID.Portfolios[0]) } @@ -555,72 +452,52 @@ func TestGetPortfolioByID(t *testing.T) { func TestDeletePortfolio(t *testing.T) { err := c.DeletePortfolio(context.Background(), "") - if !errors.Is(err, errPortfolioIDEmpty) { - t.Errorf(errExpectMismatch, err, errPortfolioIDEmpty) - } - sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) - - pID := portfolioTestHelper(t, "GCT Test Portfolio To-Delete") - + assert.ErrorIs(t, err, errPortfolioIDEmpty) + pID := portfolioIDFromName(t, "GCT Test Portfolio To-Delete") err = c.DeletePortfolio(context.Background(), pID) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) } func TestEditPortfolio(t *testing.T) { _, err := c.EditPortfolio(context.Background(), "", "") - if !errors.Is(err, errPortfolioIDEmpty) { - t.Errorf(errExpectMismatch, err, errPortfolioIDEmpty) - } + assert.ErrorIs(t, err, errPortfolioIDEmpty) _, err = c.EditPortfolio(context.Background(), "meow", "") - if !errors.Is(err, errNameEmpty) { - t.Errorf(errExpectMismatch, err, errNameEmpty) - } - sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) - - pID := portfolioTestHelper(t, "GCT Test Portfolio To-Edit") - - _, err = c.EditPortfolio(context.Background(), pID, "GCT Test Portfolio Edited") + assert.ErrorIs(t, err, errNameEmpty) + pID := portfolioIDFromName(t, "GCT Test Portfolio To-Edit") + resp, err := c.EditPortfolio(context.Background(), pID, "GCT Test Portfolio Edited") if err != nil && err.Error() != errPortfolioNameDuplicate { t.Error(err) } + assert.NotEmpty(t, resp, errExpectedNonEmpty) } func TestGetFuturesBalanceSummary(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - _, err := c.GetFuturesBalanceSummary(context.Background()) - if err != nil { - t.Error(err) - } + resp, err := c.GetFuturesBalanceSummary(context.Background()) + assert.NoError(t, err) + assert.NotEmpty(t, resp, errExpectedNonEmpty) } func TestGetAllFuturesPositions(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - _, err := c.GetAllFuturesPositions(context.Background()) - if err != nil { - t.Error(err) - } + resp, err := c.GetAllFuturesPositions(context.Background()) + assert.NoError(t, err) + assert.NotEmpty(t, resp, errExpectedNonEmpty) } func TestGetFuturesPositionByID(t *testing.T) { _, err := c.GetFuturesPositionByID(context.Background(), "") - if !errors.Is(err, errProductIDEmpty) { - t.Errorf(errExpectMismatch, err, errProductIDEmpty) - } + assert.ErrorIs(t, err, errProductIDEmpty) sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - _, err = c.GetFuturesPositionByID(context.Background(), "meow") - if err != nil { - t.Error(err) - } + resp, err := c.GetFuturesPositionByID(context.Background(), "meow") + assert.NoError(t, err) + assert.NotEmpty(t, resp, errExpectedNonEmpty) } func TestScheduleFuturesSweep(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) curSweeps, err := c.ListFuturesSweeps(context.Background()) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) preCancel := false if len(curSweeps.Sweeps) > 0 { for i := range curSweeps.Sweeps { @@ -637,31 +514,24 @@ func TestScheduleFuturesSweep(t *testing.T) { } } _, err = c.ScheduleFuturesSweep(context.Background(), 0.001337) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) } func TestListFuturesSweeps(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) _, err := c.ListFuturesSweeps(context.Background()) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) } func TestCancelPendingFuturesSweep(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) curSweeps, err := c.ListFuturesSweeps(context.Background()) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) partialSkip := false if len(curSweeps.Sweeps) > 0 { for i := range curSweeps.Sweeps { if curSweeps.Sweeps[i].Status == "PENDING" { partialSkip = true - } } } @@ -673,139 +543,137 @@ func TestCancelPendingFuturesSweep(t *testing.T) { } _, err = c.CancelPendingFuturesSweep(context.Background()) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) +} + +func TestAllocatePortfolio(t *testing.T) { + err := c.AllocatePortfolio(context.Background(), "", "", "", 0) + assert.ErrorIs(t, err, errPortfolioIDEmpty) + err = c.AllocatePortfolio(context.Background(), "meow", "", "", 0) + assert.ErrorIs(t, err, errProductIDEmpty) + err = c.AllocatePortfolio(context.Background(), "meow", "bark", "", 0) + assert.ErrorIs(t, err, errCurrencyEmpty) + sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) + pID := portfolioIDFromType(t, "INTX") + err = c.AllocatePortfolio(context.Background(), pID, testCrypto.String(), "USD", 0.001337) + assert.NoError(t, err) +} + +func TestGetPerpetualsPortfolioSummary(t *testing.T) { + _, err := c.GetPerpetualsPortfolioSummary(context.Background(), "") + assert.ErrorIs(t, err, errPortfolioIDEmpty) + pID := portfolioIDFromType(t, "INTX") + resp, err := c.GetPerpetualsPortfolioSummary(context.Background(), pID) + assert.NoError(t, err) + assert.NotEmpty(t, resp, errExpectedNonEmpty) +} + +func TestGetAllPerpetualsPositions(t *testing.T) { + _, err := c.GetAllPerpetualsPositions(context.Background(), "") + assert.ErrorIs(t, err, errPortfolioIDEmpty) + pID := portfolioIDFromType(t, "INTX") + _, err = c.GetAllPerpetualsPositions(context.Background(), pID) + assert.NoError(t, err) +} + +func TestGetPerpetualsPositionByID(t *testing.T) { + _, err := c.GetPerpetualsPositionByID(context.Background(), "", "") + assert.ErrorIs(t, err, errPortfolioIDEmpty) + _, err = c.GetPerpetualsPositionByID(context.Background(), "meow", "") + assert.ErrorIs(t, err, errProductIDEmpty) + pID := portfolioIDFromType(t, "INTX") + _, err = c.GetPerpetualsPositionByID(context.Background(), pID, testPair.String()) + assert.NoError(t, err) } func TestGetTransactionSummary(t *testing.T) { _, err := c.GetTransactionSummary(context.Background(), time.Unix(2, 2), time.Unix(1, 1), "", "", "") - if !errors.Is(err, common.ErrStartAfterEnd) { - t.Errorf(errExpectMismatch, err, common.ErrStartAfterEnd) - } + 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") - if err != nil { - t.Error(err) - } + assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) } func TestCreateConvertQuote(t *testing.T) { _, err := c.CreateConvertQuote(context.Background(), "", "", "", "", 0) - if !errors.Is(err, errAccountIDEmpty) { - t.Errorf(errExpectMismatch, err, errAccountIDEmpty) - } + assert.ErrorIs(t, err, errAccountIDEmpty) _, err = c.CreateConvertQuote(context.Background(), "meow", "123", "", "", 0) - if !errors.Is(err, errAmountEmpty) { - t.Errorf(errExpectMismatch, err, errAmountEmpty) - } + assert.ErrorIs(t, err, errAmountEmpty) sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) fromAccID, toAccID := convertTestHelper(t) resp, err := c.CreateConvertQuote(context.Background(), fromAccID, toAccID, "", "", 0.01) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) } func TestCommitConvertTrade(t *testing.T) { _, err := c.CommitConvertTrade(context.Background(), "", "", "") - if !errors.Is(err, errTransactionIDEmpty) { - t.Errorf(errExpectMismatch, err, errTransactionIDEmpty) - } + assert.ErrorIs(t, err, errTransactionIDEmpty) _, err = c.CommitConvertTrade(context.Background(), "meow", "", "") - if !errors.Is(err, errAccountIDEmpty) { - t.Errorf(errExpectMismatch, err, errAccountIDEmpty) - } + assert.ErrorIs(t, err, errAccountIDEmpty) sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) fromAccID, toAccID := convertTestHelper(t) resp, err := c.CreateConvertQuote(context.Background(), fromAccID, toAccID, "", "", 0.01) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) resp, err = c.CommitConvertTrade(context.Background(), resp.Trade.ID, fromAccID, toAccID) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) } func TestGetConvertTradeByID(t *testing.T) { _, err := c.GetConvertTradeByID(context.Background(), "", "", "") - if !errors.Is(err, errTransactionIDEmpty) { - t.Errorf(errExpectMismatch, err, errTransactionIDEmpty) - } + assert.ErrorIs(t, err, errTransactionIDEmpty) _, err = c.GetConvertTradeByID(context.Background(), "meow", "", "") - if !errors.Is(err, errAccountIDEmpty) { - t.Errorf(errExpectMismatch, err, errAccountIDEmpty) - } + assert.ErrorIs(t, err, errAccountIDEmpty) sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) fromAccID, toAccID := convertTestHelper(t) resp, err := c.CreateConvertQuote(context.Background(), fromAccID, toAccID, "", "", 0.01) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) resp, err = c.GetConvertTradeByID(context.Background(), resp.Trade.ID, fromAccID, toAccID) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) } func TestGetV3Time(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) resp, err := c.GetV3Time(context.Background()) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) } func TestListNotifications(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) _, err := c.ListNotifications(context.Background(), PaginationInp{}) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) } func TestGetUserByID(t *testing.T) { _, err := c.GetUserByID(context.Background(), "") - if !errors.Is(err, errUserIDEmpty) { - t.Errorf(errExpectMismatch, err, errUserIDEmpty) - } + assert.ErrorIs(t, err, errUserIDEmpty) sharedtestvalues.SkipTestIfCredentialsUnset(t, c) resp, err := c.GetCurrentUser(context.Background()) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) if resp == nil { t.Fatal(errExpectedNonEmpty) } resp, err = c.GetUserByID(context.Background(), resp.Data.ID) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) } func TestGetCurrentUser(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) resp, err := c.GetCurrentUser(context.Background()) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) } func TestGetAuthInfo(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) resp, err := c.GetAuthInfo(context.Background()) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) } @@ -816,13 +684,9 @@ func TestUpdateUser(t *testing.T) { t.Fatal(err) } _, err = c.UpdateUser(context.Background(), "Name changed as per GCT testing", "Sydney", testFiat.String()) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) resp, err := c.UpdateUser(context.Background(), oldData.Data.Name, oldData.Data.TimeZone, oldData.Data.NativeCurrency) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) } @@ -830,155 +694,101 @@ func TestGetAllWallets(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) pagIn := PaginationInp{Limit: 2} resp, err := c.GetAllWallets(context.Background(), pagIn) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) if resp.Pagination.NextStartingAfter == "" { t.Skip(skipInsufficientWallets) } pagIn.StartingAfter = resp.Pagination.NextStartingAfter resp, err = c.GetAllWallets(context.Background(), pagIn) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) } func TestGetWalletByID(t *testing.T) { _, err := c.GetWalletByID(context.Background(), "", "") - if !errors.Is(err, errCurrWalletConflict) { - t.Errorf(errExpectMismatch, err, errCurrWalletConflict) - } + assert.ErrorIs(t, err, errCurrWalletConflict) sharedtestvalues.SkipTestIfCredentialsUnset(t, c) resp, err := c.GetWalletByID(context.Background(), "", testCrypto.String()) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) resp, err = c.GetWalletByID(context.Background(), resp.Data.ID, "") - if err != nil { - t.Error(err) - } + assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) } func TestCreateAddress(t *testing.T) { _, err := c.CreateAddress(context.Background(), "", "") - if !errors.Is(err, errWalletIDEmpty) { - t.Errorf(errExpectMismatch, err, errWalletIDEmpty) - } + assert.ErrorIs(t, err, errWalletIDEmpty) sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) wID, err := c.GetWalletByID(context.Background(), "", testCrypto.String()) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) assert.NotEmpty(t, wID, errExpectedNonEmpty) resp, err := c.CreateAddress(context.Background(), wID.Data.ID, "") - if err != nil { - t.Error(err) - } + assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) } func TestGetAllAddresses(t *testing.T) { var pag PaginationInp _, err := c.GetAllAddresses(context.Background(), "", pag) - if !errors.Is(err, errWalletIDEmpty) { - t.Errorf(errExpectMismatch, err, errWalletIDEmpty) - } + assert.ErrorIs(t, err, errWalletIDEmpty) sharedtestvalues.SkipTestIfCredentialsUnset(t, c) wID, err := c.GetWalletByID(context.Background(), "", testCrypto.String()) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) assert.NotEmpty(t, wID, errExpectedNonEmpty) resp, err := c.GetAllAddresses(context.Background(), wID.Data.ID, pag) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) } func TestGetAddressByID(t *testing.T) { _, err := c.GetAddressByID(context.Background(), "", "") - if !errors.Is(err, errWalletIDEmpty) { - t.Errorf(errExpectMismatch, err, errWalletIDEmpty) - } + assert.ErrorIs(t, err, errWalletIDEmpty) _, err = c.GetAddressByID(context.Background(), "123", "") - if !errors.Is(err, errAddressIDEmpty) { - t.Errorf(errExpectMismatch, err, errAddressIDEmpty) - } + assert.ErrorIs(t, err, errAddressIDEmpty) sharedtestvalues.SkipTestIfCredentialsUnset(t, c) wID, err := c.GetWalletByID(context.Background(), "", testCrypto.String()) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) assert.NotEmpty(t, wID, errExpectedNonEmpty) addID, err := c.GetAllAddresses(context.Background(), wID.Data.ID, PaginationInp{}) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) assert.NotEmpty(t, addID, errExpectedNonEmpty) resp, err := c.GetAddressByID(context.Background(), wID.Data.ID, addID.Data[0].ID) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) } func TestGetAddressTransactions(t *testing.T) { _, err := c.GetAddressTransactions(context.Background(), "", "", PaginationInp{}) - if !errors.Is(err, errWalletIDEmpty) { - t.Errorf(errExpectMismatch, err, errWalletIDEmpty) - } + assert.ErrorIs(t, err, errWalletIDEmpty) _, err = c.GetAddressTransactions(context.Background(), "123", "", PaginationInp{}) - if !errors.Is(err, errAddressIDEmpty) { - t.Errorf(errExpectMismatch, err, errAddressIDEmpty) - } + assert.ErrorIs(t, err, errAddressIDEmpty) sharedtestvalues.SkipTestIfCredentialsUnset(t, c) wID, err := c.GetWalletByID(context.Background(), "", testCrypto.String()) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) assert.NotEmpty(t, wID, errExpectedNonEmpty) addID, err := c.GetAllAddresses(context.Background(), wID.Data.ID, PaginationInp{}) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) assert.NotEmpty(t, addID, errExpectedNonEmpty) _, err = c.GetAddressTransactions(context.Background(), wID.Data.ID, addID.Data[0].ID, PaginationInp{}) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) } func TestSendMoney(t *testing.T) { _, err := c.SendMoney(context.Background(), "", "", "", "", "", "", "", "", 0, false, false) - if !errors.Is(err, errTransactionTypeEmpty) { - t.Errorf(errExpectMismatch, err, errTransactionTypeEmpty) - } + assert.ErrorIs(t, err, errTransactionTypeEmpty) _, err = c.SendMoney(context.Background(), "123", "", "", "", "", "", "", "", 0, false, false) - if !errors.Is(err, errWalletIDEmpty) { - t.Errorf(errExpectMismatch, err, errWalletIDEmpty) - } + assert.ErrorIs(t, err, errWalletIDEmpty) _, err = c.SendMoney(context.Background(), "123", "123", "", "", "", "", "", "", 0, false, false) - if !errors.Is(err, errToEmpty) { - t.Errorf(errExpectMismatch, err, errToEmpty) - } + assert.ErrorIs(t, err, errToEmpty) _, err = c.SendMoney(context.Background(), "123", "123", "123", "", "", "", "", "", 0, false, false) - if !errors.Is(err, errAmountEmpty) { - t.Errorf(errExpectMismatch, err, errAmountEmpty) - } + assert.ErrorIs(t, err, errAmountEmpty) _, err = c.SendMoney(context.Background(), "123", "123", "123", "", "", "", "", "", 1, false, false) - if !errors.Is(err, errCurrencyEmpty) { - t.Errorf(errExpectMismatch, err, errCurrencyEmpty) - } + assert.ErrorIs(t, err, errCurrencyEmpty) sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) wID, err := c.GetAllWallets(context.Background(), PaginationInp{}) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) if len(wID.Data) < 2 { t.Skip(skipInsufficientWallets) } @@ -988,7 +798,7 @@ func TestSendMoney(t *testing.T) { ) for i := range wID.Data { if wID.Data[i].Currency.Name == testCrypto.String() { - if wID.Data[i].Balance.Amount > testAmount { + if wID.Data[i].Balance.Amount > testAmount*100 { fromID = wID.Data[i].ID } else { toID = wID.Data[i].ID @@ -1001,108 +811,73 @@ func TestSendMoney(t *testing.T) { if fromID == "" || toID == "" { t.Skip(skipInsufficientFundsOrWallets) } - _, err = c.SendMoney(context.Background(), "transfer", wID.Data[0].ID, wID.Data[1].ID, + resp, err := c.SendMoney(context.Background(), "transfer", wID.Data[0].ID, wID.Data[1].ID, testCrypto.String(), "GCT Test", "123", "", "", testAmount, false, false) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) + assert.NotEmpty(t, resp, errExpectedNonEmpty) } func TestGetAllTransactions(t *testing.T) { var pag PaginationInp _, err := c.GetAllTransactions(context.Background(), "", pag) - if !errors.Is(err, errWalletIDEmpty) { - t.Errorf(errExpectMismatch, err, errWalletIDEmpty) - } + assert.ErrorIs(t, err, errWalletIDEmpty) sharedtestvalues.SkipTestIfCredentialsUnset(t, c) wID, err := c.GetWalletByID(context.Background(), "", testCrypto.String()) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) assert.NotEmpty(t, wID, errExpectedNonEmpty) _, err = c.GetAllTransactions(context.Background(), wID.Data.ID, pag) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) } func TestGetTransactionByID(t *testing.T) { _, err := c.GetTransactionByID(context.Background(), "", "") - if !errors.Is(err, errWalletIDEmpty) { - t.Errorf(errExpectMismatch, err, errWalletIDEmpty) - } + assert.ErrorIs(t, err, errWalletIDEmpty) _, err = c.GetTransactionByID(context.Background(), "123", "") - if !errors.Is(err, errTransactionIDEmpty) { - t.Errorf(errExpectMismatch, err, errTransactionIDEmpty) - } + assert.ErrorIs(t, err, errTransactionIDEmpty) sharedtestvalues.SkipTestIfCredentialsUnset(t, c) wID, err := c.GetWalletByID(context.Background(), "", testCrypto.String()) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) assert.NotEmpty(t, wID, errExpectedNonEmpty) tID, err := c.GetAllTransactions(context.Background(), wID.Data.ID, PaginationInp{}) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) if len(tID.Data) == 0 { t.Skip(skipInsufficientTransactions) } resp, err := c.GetTransactionByID(context.Background(), wID.Data.ID, tID.Data[0].ID) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) } func TestFiatTransfer(t *testing.T) { _, err := c.FiatTransfer(context.Background(), "", "", "", 0, false, FiatDeposit) - if !errors.Is(err, errWalletIDEmpty) { - t.Errorf(errExpectMismatch, err, errWalletIDEmpty) - } + assert.ErrorIs(t, err, errWalletIDEmpty) _, err = c.FiatTransfer(context.Background(), "123", "", "", 0, false, FiatDeposit) - if !errors.Is(err, errAmountEmpty) { - t.Errorf(errExpectMismatch, err, errAmountEmpty) - } + assert.ErrorIs(t, err, errAmountEmpty) _, err = c.FiatTransfer(context.Background(), "123", "", "", 1, false, FiatDeposit) - if !errors.Is(err, errCurrencyEmpty) { - t.Errorf(errExpectMismatch, err, errCurrencyEmpty) - } + assert.ErrorIs(t, err, errCurrencyEmpty) _, err = c.FiatTransfer(context.Background(), "123", "123", "", 1, false, FiatDeposit) - if !errors.Is(err, errPaymentMethodEmpty) { - t.Errorf(errExpectMismatch, err, errPaymentMethodEmpty) - } + assert.ErrorIs(t, err, errPaymentMethodEmpty) sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) wallets, err := c.GetAllWallets(context.Background(), PaginationInp{}) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) assert.NotEmpty(t, wallets, errExpectedNonEmpty) wID, pmID := transferTestHelper(t, wallets) - _, err = c.FiatTransfer(context.Background(), wID, testFiat.String(), pmID, testAmount, false, FiatDeposit) - if err != nil { - t.Error(err) - } - _, err = c.FiatTransfer(context.Background(), wID, testFiat.String(), pmID, testAmount, false, FiatWithdrawal) - if err != nil { - t.Error(err) - } + resp, err := c.FiatTransfer(context.Background(), wID, testFiat.String(), pmID, testAmount, false, FiatDeposit) + assert.NoError(t, err) + assert.NotEmpty(t, resp, errExpectedNonEmpty) + resp, err = c.FiatTransfer(context.Background(), wID, testFiat.String(), pmID, testAmount, false, FiatWithdrawal) + assert.NoError(t, err) + assert.NotEmpty(t, resp, errExpectedNonEmpty) } func TestCommitTransfer(t *testing.T) { _, err := c.CommitTransfer(context.Background(), "", "", FiatDeposit) - if !errors.Is(err, errWalletIDEmpty) { - t.Errorf(errExpectMismatch, err, errWalletIDEmpty) - } + assert.ErrorIs(t, err, errWalletIDEmpty) _, err = c.CommitTransfer(context.Background(), "123", "", FiatDeposit) - if !errors.Is(err, errDepositIDEmpty) { - t.Errorf(errExpectMismatch, err, errDepositIDEmpty) - } + assert.ErrorIs(t, err, errDepositIDEmpty) sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) wallets, err := c.GetAllWallets(context.Background(), PaginationInp{}) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) assert.NotEmpty(t, wallets, errExpectedNonEmpty) wID, pmID := transferTestHelper(t, wallets) depID, err := c.FiatTransfer(context.Background(), wID, testFiat.String(), pmID, testAmount, @@ -1110,27 +885,23 @@ func TestCommitTransfer(t *testing.T) { if err != nil { t.Fatal(err) } - _, err = c.CommitTransfer(context.Background(), wID, depID.Data.ID, FiatDeposit) - if err != nil { - t.Error(err) - } + resp, err := c.CommitTransfer(context.Background(), wID, depID.Data.ID, FiatDeposit) + assert.NoError(t, err) + assert.NotEmpty(t, resp, errExpectedNonEmpty) depID, err = c.FiatTransfer(context.Background(), wID, testFiat.String(), pmID, testAmount, false, FiatWithdrawal) if err != nil { t.Fatal(err) } - _, err = c.CommitTransfer(context.Background(), wID, depID.Data.ID, FiatWithdrawal) - if err != nil { - t.Error(err) - } + resp, err = c.CommitTransfer(context.Background(), wID, depID.Data.ID, FiatWithdrawal) + assert.NoError(t, err) + assert.NotEmpty(t, resp, errExpectedNonEmpty) } func TestGetAllFiatTransfers(t *testing.T) { var pag PaginationInp _, err := c.GetAllFiatTransfers(context.Background(), "", pag, FiatDeposit) - if !errors.Is(err, errWalletIDEmpty) { - t.Errorf(errExpectMismatch, err, errWalletIDEmpty) - } + assert.ErrorIs(t, err, errWalletIDEmpty) sharedtestvalues.SkipTestIfCredentialsUnset(t, c) wID, err := c.GetWalletByID(context.Background(), "", "AUD") if err != nil { @@ -1138,24 +909,16 @@ func TestGetAllFiatTransfers(t *testing.T) { } assert.NotEmpty(t, wID, errExpectedNonEmpty) _, err = c.GetAllFiatTransfers(context.Background(), wID.Data.ID, pag, FiatDeposit) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) _, err = c.GetAllFiatTransfers(context.Background(), wID.Data.ID, pag, FiatWithdrawal) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) } func TestGetFiatTransferByID(t *testing.T) { _, err := c.GetFiatTransferByID(context.Background(), "", "", FiatDeposit) - if !errors.Is(err, errWalletIDEmpty) { - t.Errorf(errExpectMismatch, err, errWalletIDEmpty) - } + assert.ErrorIs(t, err, errWalletIDEmpty) _, err = c.GetFiatTransferByID(context.Background(), "123", "", FiatDeposit) - if !errors.Is(err, errDepositIDEmpty) { - t.Errorf(errExpectMismatch, err, errDepositIDEmpty) - } + assert.ErrorIs(t, err, errDepositIDEmpty) sharedtestvalues.SkipTestIfCredentialsUnset(t, c) wID, err := c.GetWalletByID(context.Background(), "", "AUD") if err != nil { @@ -1163,104 +926,74 @@ func TestGetFiatTransferByID(t *testing.T) { } assert.NotEmpty(t, wID, errExpectedNonEmpty) dID, err := c.GetAllFiatTransfers(context.Background(), wID.Data.ID, PaginationInp{}, FiatDeposit) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) if len(dID.Data) == 0 { t.Skip(skipInsufficientTransactions) } resp, err := c.GetFiatTransferByID(context.Background(), wID.Data.ID, dID.Data[0].ID, FiatDeposit) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) resp, err = c.GetFiatTransferByID(context.Background(), wID.Data.ID, dID.Data[0].ID, FiatWithdrawal) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) } func TestGetAllPaymentMethods(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) resp, err := c.GetAllPaymentMethods(context.Background(), PaginationInp{}) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) } func TestGetPaymentMethodByID(t *testing.T) { _, err := c.GetPaymentMethodByID(context.Background(), "") - if !errors.Is(err, errPaymentMethodEmpty) { - t.Errorf(errExpectMismatch, err, errPaymentMethodEmpty) - } + assert.ErrorIs(t, err, errPaymentMethodEmpty) sharedtestvalues.SkipTestIfCredentialsUnset(t, c) pmID, err := c.GetAllPaymentMethods(context.Background(), PaginationInp{}) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) if len(pmID.Data) == 0 { t.Skip(skipPayMethodNotFound) } resp, err := c.GetPaymentMethodByID(context.Background(), pmID.Data[0].ID) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) } func TestGetFiatCurrencies(t *testing.T) { resp, err := c.GetFiatCurrencies(context.Background()) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) } func TestGetCryptocurrencies(t *testing.T) { resp, err := c.GetCryptocurrencies(context.Background()) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) } func TestGetExchangeRates(t *testing.T) { resp, err := c.GetExchangeRates(context.Background(), "") - if err != nil { - t.Error(err) - } + assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) } func TestGetPrice(t *testing.T) { _, err := c.GetPrice(context.Background(), "", "") - if !errors.Is(err, errInvalidPriceType) { - t.Errorf(errExpectMismatch, err, errInvalidPriceType) - } + assert.ErrorIs(t, err, errInvalidPriceType) resp, err := c.GetPrice(context.Background(), testPair.String(), asset.Spot.String()) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) resp, err = c.GetPrice(context.Background(), testPair.String(), "buy") - if err != nil { - t.Error(err) - } + assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) resp, err = c.GetPrice(context.Background(), testPair.String(), "sell") - if err != nil { - t.Error(err) - } + assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) } func TestGetV2Time(t *testing.T) { resp, err := c.GetV2Time(context.Background()) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) } @@ -1274,9 +1007,7 @@ func TestSendHTTPRequest(t *testing.T) { func TestSendAuthenticatedHTTPRequest(t *testing.T) { fc := &CoinbasePro{} err := fc.SendAuthenticatedHTTPRequest(context.Background(), exchange.EdgeCase3, "", "", "", nil, false, nil, nil) - if !errors.Is(err, exchange.ErrCredentialsAreEmpty) { - t.Errorf(errExpectMismatch, err, exchange.ErrCredentialsAreEmpty) - } + assert.ErrorIs(t, err, exchange.ErrCredentialsAreEmpty) err = c.SendAuthenticatedHTTPRequest(context.Background(), exchange.EdgeCase3, "", "", "", nil, false, nil, nil) if err.Error() != errNoEndpointPathEdgeCase3 { t.Errorf(errExpectMismatch, err, errNoEndpointPathEdgeCase3) @@ -1300,267 +1031,143 @@ func TestGetFee(t *testing.T) { PurchasePrice: 1, } resp, err := c.GetFee(context.Background(), &feeBuilder) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) if resp != 0.008 { t.Errorf(errExpectMismatch, resp, 0.008) } feeBuilder.IsMaker = true resp, err = c.GetFee(context.Background(), &feeBuilder) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) if resp != 0.006 { t.Errorf(errExpectMismatch, resp, 0.006) } feeBuilder.Pair = currency.NewPair(currency.USDT, currency.USD) resp, err = c.GetFee(context.Background(), &feeBuilder) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) if resp != 0 { t.Errorf(errExpectMismatch, resp, 0) } feeBuilder.IsMaker = false resp, err = c.GetFee(context.Background(), &feeBuilder) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) if resp != 0.00001 { t.Errorf(errExpectMismatch, resp, 0.00001) } feeBuilder.FeeType = exchange.CryptocurrencyDepositFee _, err = c.GetFee(context.Background(), &feeBuilder) - if err != errFeeTypeNotSupported { - t.Errorf(errExpectMismatch, errFeeTypeNotSupported, err) - } + assert.ErrorIs(t, err, errFeeTypeNotSupported) feeBuilder.Pair = currency.Pair{} sharedtestvalues.SkipTestIfCredentialsUnset(t, c) feeBuilder.FeeType = exchange.CryptocurrencyTradeFee resp, err = c.GetFee(context.Background(), &feeBuilder) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) if !(resp <= 0.008 && resp >= 0.0005) { t.Errorf(errExpectedFeeRange, 0.0005, 0.008, resp) } feeBuilder.IsMaker = true resp, err = c.GetFee(context.Background(), &feeBuilder) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) if !(resp <= 0.006 && resp >= 0) { t.Errorf(errExpectedFeeRange, 0, 0.006, resp) } } -func TestPrepareDateString(t *testing.T) { - t.Parallel() - var expectedResult Params - expectedResult.urlVals = map[string][]string{ - "start_date": {"1970-01-01T00:00:01Z"}, - "end_date": {"1970-01-01T00:00:02Z"}, - } - var result Params - - result.urlVals = make(url.Values) - - labelStart := "start_date" - labelEnd := "end_date" - - err := result.prepareDateString(time.Unix(1, 1).UTC(), time.Unix(2, 2).UTC(), labelStart, labelEnd) - if err != nil { - t.Error(err) - } - if fmt.Sprint(expectedResult) != fmt.Sprint(result) { - t.Errorf(errExpectMismatch, result, expectedResult) - } - - var newTime time.Time - err = result.prepareDateString(newTime, newTime, labelStart, labelEnd) - if err != nil { - t.Error(err) - } - - err = result.prepareDateString(time.Unix(2, 2).UTC(), time.Unix(1, 1).UTC(), labelStart, labelEnd) - if !errors.Is(err, common.ErrStartAfterEnd) { - t.Errorf(errExpectMismatch, err, common.ErrStartAfterEnd) - } -} - -func TestPreparePagination(t *testing.T) { - t.Parallel() - var expectedResult Params - expectedResult.urlVals = map[string][]string{"limit": {"1"}, "order": {"asc"}, "starting_after": {"meow"}, - "ending_before": {"woof"}} - - var result Params - result.urlVals = make(url.Values) - - pagIn := PaginationInp{Limit: 1, OrderAscend: true, StartingAfter: "meow", EndingBefore: "woof"} - - result.preparePagination(pagIn) - - if fmt.Sprint(expectedResult) != fmt.Sprint(result) { - t.Errorf(errExpectMismatch, result, expectedResult) - } -} - func TestFetchTradablePairs(t *testing.T) { - sharedtestvalues.SkipTestIfCredentialsUnset(t, c) _, err := c.FetchTradablePairs(context.Background(), asset.Empty) - if !errors.Is(err, asset.ErrNotSupported) { - t.Errorf(errExpectMismatch, err, asset.ErrNotSupported) - } - _, err = c.FetchTradablePairs(context.Background(), asset.Spot) - if err != nil { - t.Error(err) - } - _, err = c.FetchTradablePairs(context.Background(), asset.Futures) - if err != nil { - t.Error(err) - } + assert.ErrorIs(t, err, asset.ErrNotSupported) + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + resp, err := c.FetchTradablePairs(context.Background(), asset.Spot) + assert.NoError(t, err) + assert.NotEmpty(t, resp, errExpectedNonEmpty) + resp, err = c.FetchTradablePairs(context.Background(), asset.Futures) + assert.NoError(t, err) + assert.NotEmpty(t, resp, errExpectedNonEmpty) } func TestUpdateTradablePairs(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) err := c.UpdateTradablePairs(context.Background(), false) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) } func TestUpdateAccountInfo(t *testing.T) { - sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - _, err := c.UpdateAccountInfo(context.Background(), asset.Spot) - if err != nil { - t.Error(err) - } + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + resp, err := c.UpdateAccountInfo(context.Background(), asset.Spot) + assert.NoError(t, err) + assert.NotEmpty(t, resp, errExpectedNonEmpty) } func TestFetchAccountInfo(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - _, err := c.FetchAccountInfo(context.Background(), asset.Spot) - if err != nil { - t.Error(err) - } + resp, err := c.FetchAccountInfo(context.Background(), asset.Spot) + assert.NoError(t, err) + assert.NotEmpty(t, resp, errExpectedNonEmpty) } func TestUpdateTickers(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) err := c.UpdateTickers(context.Background(), asset.Futures) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) err = c.UpdateTickers(context.Background(), asset.Spot) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) } func TestUpdateTicker(t *testing.T) { - sharedtestvalues.SkipTestIfCredentialsUnset(t, c) _, err := c.UpdateTicker(context.Background(), currency.Pair{}, asset.Empty) - if !errors.Is(err, currency.ErrCurrencyPairEmpty) { - t.Errorf(errExpectMismatch, err, currency.ErrCurrencyPairEmpty) - } - _, err = c.UpdateTicker(context.Background(), testPair, asset.Spot) - if err != nil { - t.Error(err) - } + assert.ErrorIs(t, err, currency.ErrCurrencyPairEmpty) + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + resp, err := c.UpdateTicker(context.Background(), testPair, asset.Spot) + assert.NoError(t, err) + assert.NotEmpty(t, resp, errExpectedNonEmpty) } func TestFetchTicker(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - _, err := c.FetchTicker(context.Background(), testPair, asset.Spot) - if err != nil { - t.Error(err) - } + resp, err := c.FetchTicker(context.Background(), testPair, asset.Spot) + assert.NoError(t, err) + assert.NotEmpty(t, resp, errExpectedNonEmpty) } func TestFetchOrderbook(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - _, err := c.FetchOrderbook(context.Background(), testPair, asset.Spot) - if err != nil { - t.Error(err) - } + resp, err := c.FetchOrderbook(context.Background(), testPair, asset.Spot) + assert.NoError(t, err) + assert.NotEmpty(t, resp, errExpectedNonEmpty) } func TestUpdateOrderbook(t *testing.T) { _, err := c.UpdateOrderbook(context.Background(), currency.Pair{}, asset.Empty) - if !errors.Is(err, currency.ErrCurrencyPairEmpty) { - t.Errorf(errExpectMismatch, err, currency.ErrCurrencyPairEmpty) - } + assert.ErrorIs(t, err, currency.ErrCurrencyPairEmpty) _, err = c.UpdateOrderbook(context.Background(), currency.NewPairWithDelimiter("meow", "woof", "-"), asset.Spot) if err.Error() != errInvalidProductID { t.Errorf(errExpectMismatch, err, errInvalidProductID) } sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - _, err = c.UpdateOrderbook(context.Background(), testPair, asset.Spot) - if err != nil { - t.Error(err) - } -} - -func TestProcessFundingData(t *testing.T) { - accHist := make([]DeposWithdrData, 1) - genAmcur := AmCur{Amount: 1, Currency: "DOGE"} - accHist[0] = DeposWithdrData{Status: "meow", ID: "woof", PayoutAt: time.Unix(1, 1).UTC(), Amount: genAmcur, - Fee: genAmcur, TransferType: FiatWithdrawal} - genAmcur.Amount = 2 - cryptHist := make([]TransactionData, 2) - cryptHist[0] = TransactionData{Status: "moo", ID: "oink", CreatedAt: time.Unix(2, 2).UTC(), Amount: genAmcur, - Type: "receive"} - cryptHist[0].Network.Name = "neigh" - cryptHist[0].To.ID = "The Barnyard" - cryptHist[1].Type = "send" - - expectedResult := make([]exchange.FundingHistory, 3) - expectedResult[0] = exchange.FundingHistory{ExchangeName: "CoinbasePro", Status: "meow", TransferID: "woof", - Timestamp: time.Unix(1, 1).UTC(), Currency: "DOGE", Amount: 1, Fee: 1, TransferType: "withdrawal"} - expectedResult[1] = exchange.FundingHistory{ExchangeName: "CoinbasePro", Status: "moo", TransferID: "oink", - Timestamp: time.Unix(2, 2).UTC(), Currency: "DOGE", Amount: 2, Fee: 0, TransferType: "deposit", - CryptoFromAddress: "The Barnyard", CryptoChain: "neigh"} - expectedResult[2] = exchange.FundingHistory{ExchangeName: "CoinbasePro", TransferType: "withdrawal"} - - resp := c.processFundingData(accHist, cryptHist) - - if fmt.Sprint(expectedResult) != fmt.Sprint(resp) { - t.Errorf(errExpectMismatch, resp, expectedResult) - } + resp, err := c.UpdateOrderbook(context.Background(), testPair, asset.Spot) + assert.NoError(t, err) + assert.NotEmpty(t, resp, errExpectedNonEmpty) } func TestGetAccountFundingHistory(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) _, err := c.GetAccountFundingHistory(context.Background()) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) } func TestGetWithdrawalsHistory(t *testing.T) { - sharedtestvalues.SkipTestIfCredentialsUnset(t, c) _, err := c.GetWithdrawalsHistory(context.Background(), currency.NewCode("meow"), asset.Spot) - if !errors.Is(err, errNoMatchingWallets) { - t.Errorf(errExpectMismatch, err, errNoMatchingWallets) - } + assert.ErrorIs(t, err, errNoMatchingWallets) + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) _, err = c.GetWithdrawalsHistory(context.Background(), currency.BTC, asset.Spot) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) } func TestSubmitOrder(t *testing.T) { _, err := c.SubmitOrder(context.Background(), nil) - if !errors.Is(err, common.ErrNilPointer) { - t.Errorf(errExpectMismatch, err, common.ErrNilPointer) - } + assert.ErrorIs(t, err, common.ErrNilPointer) var ord order.Submit _, err = c.SubmitOrder(context.Background(), &ord) - if !errors.Is(err, common.ErrExchangeNameUnset) { - t.Errorf(errExpectMismatch, err, common.ErrExchangeNameUnset) - } + assert.ErrorIs(t, err, common.ErrExchangeNameUnset) sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) skipTestIfLowOnFunds(t) ord.Exchange = c.Name @@ -1569,59 +1176,49 @@ func TestSubmitOrder(t *testing.T) { ord.Side = order.Sell ord.Type = order.StopLimit ord.StopDirection = order.StopUp - ord.Amount = 0.0000001 - ord.Price = 1000000000000 + ord.Amount = testAmount + ord.Price = testPrice ord.RetrieveFees = true ord.ClientOrderID = strconv.FormatInt(time.Now().UnixMilli(), 18) + "GCTSubmitOrderTest" - _, err = c.SubmitOrder(context.Background(), &ord) - if err != nil { - t.Error(err) - } + resp, err := c.SubmitOrder(context.Background(), &ord) + assert.NoError(t, err) + assert.NotEmpty(t, resp, errExpectedNonEmpty) ord.StopDirection = order.StopDown ord.Side = order.Buy - _, err = c.SubmitOrder(context.Background(), &ord) - if err != nil { - t.Error(err) - } + resp, err = c.SubmitOrder(context.Background(), &ord) + assert.NoError(t, err) + assert.NotEmpty(t, resp, errExpectedNonEmpty) ord.Type = order.Market - ord.QuoteAmount = 0.0000001 - _, err = c.SubmitOrder(context.Background(), &ord) - if err != nil { - t.Error(err) - } + ord.QuoteAmount = testAmount + resp, err = c.SubmitOrder(context.Background(), &ord) + assert.NoError(t, err) + assert.NotEmpty(t, resp, errExpectedNonEmpty) } func TestModifyOrder(t *testing.T) { _, err := c.ModifyOrder(context.Background(), nil) - if !errors.Is(err, common.ErrNilPointer) { - t.Errorf(errExpectMismatch, err, common.ErrNilPointer) - } + assert.ErrorIs(t, err, common.ErrNilPointer) var ord order.Modify _, err = c.ModifyOrder(context.Background(), &ord) - if !errors.Is(err, order.ErrPairIsEmpty) { - t.Errorf(errExpectMismatch, err, order.ErrPairIsEmpty) - } + assert.ErrorIs(t, err, order.ErrPairIsEmpty) sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) skipTestIfLowOnFunds(t) resp, err := c.PlaceOrder(context.Background(), strconv.FormatInt(time.Now().UnixMilli(), 18)+"GCTModifyOrderTest", - testPair.String(), order.Sell.String(), "", order.Limit.String(), "", "", "", 0.0000001, 1000000000000, 0, 9999, + testPair.String(), order.Sell.String(), "", order.Limit.String(), "", "", "", testAmount, testPrice, 0, 9999, false, time.Time{}) if err != nil { t.Fatal(err) } ord.OrderID = resp.OrderID - ord.Price = 1000000000001 - _, err = c.ModifyOrder(context.Background(), &ord) - if err != nil { - t.Error(err) - } + ord.Price = testPrice + 1 + resp2, err := c.ModifyOrder(context.Background(), &ord) + assert.NoError(t, err) + assert.NotEmpty(t, resp2, errExpectedNonEmpty) } func TestCancelOrder(t *testing.T) { err := c.CancelOrder(context.Background(), nil) - if !errors.Is(err, common.ErrNilPointer) { - t.Errorf(errExpectMismatch, err, common.ErrNilPointer) - } + assert.ErrorIs(t, err, common.ErrNilPointer) var can order.Cancel err = c.CancelOrder(context.Background(), &can) if err.Error() != errIDNotSet { @@ -1629,30 +1226,25 @@ func TestCancelOrder(t *testing.T) { } sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) can.OrderID = "0" - c.Verbose = true err = c.CancelOrder(context.Background(), &can) if err.Error() != errOrder0CancelFail { t.Errorf(errExpectMismatch, err, errOrder0CancelFail) } skipTestIfLowOnFunds(t) resp, err := c.PlaceOrder(context.Background(), strconv.FormatInt(time.Now().UnixMilli(), 18)+"GCTCancelOrderTest", - testPair.String(), order.Sell.String(), "", order.Limit.String(), "", "", "", 0.0000001, 1000000000000, 0, 9999, + testPair.String(), order.Sell.String(), "", order.Limit.String(), "", "", "", testAmount, testPrice, 0, 9999, false, time.Time{}) if err != nil { t.Fatal(err) } can.OrderID = resp.OrderID err = c.CancelOrder(context.Background(), &can) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) } func TestCancelBatchOrders(t *testing.T) { _, err := c.CancelBatchOrders(context.Background(), nil) - if !errors.Is(err, errOrderIDEmpty) { - t.Errorf(errExpectMismatch, err, errOrderIDEmpty) - } + assert.ErrorIs(t, err, errOrderIDEmpty) can := make([]order.Cancel, 1) _, err = c.CancelBatchOrders(context.Background(), can) if err.Error() != errIDNotSet { @@ -1662,41 +1254,35 @@ func TestCancelBatchOrders(t *testing.T) { skipTestIfLowOnFunds(t) resp, err := c.PlaceOrder(context.Background(), strconv.FormatInt(time.Now().UnixMilli(), 18)+"GCTCancelBatchOrdersTest", testPair.String(), - order.Sell.String(), "", order.Limit.String(), "", "", "", 0.0000001, 1000000000000, 0, 9999, false, time.Time{}) + order.Sell.String(), "", order.Limit.String(), "", "", "", testAmount, testPrice, 0, 9999, false, time.Time{}) if err != nil { t.Fatal(err) } can[0].OrderID = resp.OrderID - _, err = c.CancelBatchOrders(context.Background(), can) - if err != nil { - t.Error(err) - } + resp2, err := c.CancelBatchOrders(context.Background(), can) + assert.NoError(t, err) + assert.NotEmpty(t, resp2, errExpectedNonEmpty) } func TestCancelAllOrders(t *testing.T) { _, err := c.CancelAllOrders(context.Background(), nil) - if !errors.Is(err, common.ErrNilPointer) { - t.Errorf(errExpectMismatch, err, common.ErrNilPointer) - } + assert.ErrorIs(t, err, common.ErrNilPointer) var can order.Cancel _, err = c.CancelAllOrders(context.Background(), &can) - if !errors.Is(err, order.ErrPairIsEmpty) { - t.Errorf(errExpectMismatch, err, order.ErrPairIsEmpty) - } + assert.ErrorIs(t, err, order.ErrPairIsEmpty) sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) skipTestIfLowOnFunds(t) _, err = c.PlaceOrder(context.Background(), strconv.FormatInt(time.Now().UnixMilli(), 18)+"GCTCancelAllOrdersTest", testPair.String(), - order.Sell.String(), "", order.Limit.String(), "", "", "", 0.0000001, 1000000000000, 0, 9999, false, time.Time{}) + order.Sell.String(), "", order.Limit.String(), "", "", "", testAmount, testPrice, 0, 9999, false, time.Time{}) if err != nil { t.Fatal(err) } can.Pair = testPair can.AssetType = asset.Spot - _, err = c.CancelAllOrders(context.Background(), &can) - if err != nil { - t.Error(err) - } + resp, err := c.CancelAllOrders(context.Background(), &can) + assert.NoError(t, err) + assert.NotEmpty(t, resp, errExpectedNonEmpty) } func TestGetOrderInfo(t *testing.T) { @@ -1709,49 +1295,39 @@ func TestGetOrderInfo(t *testing.T) { if len(ordID.Orders) == 0 { t.Skip(skipInsufficientOrders) } - _, err = c.GetOrderInfo(context.Background(), ordID.Orders[0].OrderID, testPair, asset.Spot) - if err != nil { - t.Error(err) - } + resp, err := c.GetOrderInfo(context.Background(), ordID.Orders[0].OrderID, testPair, asset.Spot) + assert.NoError(t, err) + assert.NotEmpty(t, resp, errExpectedNonEmpty) } func TestGetDepositAddress(t *testing.T) { - sharedtestvalues.SkipTestIfCredentialsUnset(t, c) _, err := c.GetDepositAddress(context.Background(), currency.NewCode("fake currency that doesn't exist"), "", "") - if !errors.Is(err, errNoWalletForCurrency) { - t.Errorf(errExpectMismatch, err, errNoWalletForCurrency) - } - _, err = c.GetDepositAddress(context.Background(), testCrypto, "", "") - if err != nil { - t.Error(err) - } + assert.ErrorIs(t, err, errNoWalletForCurrency) + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + resp, err := c.GetDepositAddress(context.Background(), testCrypto, "", "") + assert.NoError(t, err) + assert.NotEmpty(t, resp, errExpectedNonEmpty) } func TestWithdrawCryptocurrencyFunds(t *testing.T) { req := withdraw.Request{} _, err := c.WithdrawCryptocurrencyFunds(context.Background(), &req) - if !errors.Is(err, common.ErrExchangeNameUnset) { - t.Errorf(errExpectMismatch, err, common.ErrExchangeNameUnset) - } + assert.ErrorIs(t, err, common.ErrExchangeNameUnset) req.Exchange = c.Name req.Currency = currency.BTC req.Amount = testAmount req.Type = withdraw.Crypto req.Crypto.Address = testAddress _, err = c.WithdrawCryptocurrencyFunds(context.Background(), &req) - if !errors.Is(err, errWalletIDEmpty) { - t.Errorf(errExpectMismatch, err, errWalletIDEmpty) - } + assert.ErrorIs(t, err, errWalletIDEmpty) sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) wallets, err := c.GetAllWallets(context.Background(), PaginationInp{}) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) if len(wallets.Data) == 0 { t.Fatal(errExpectedNonEmpty) } for i := range wallets.Data { - if wallets.Data[i].Currency.Name == currency.BTC.String() && wallets.Data[i].Balance.Amount > testAmount { + if wallets.Data[i].Currency.Name == currency.BTC.String() && wallets.Data[i].Balance.Amount > testAmount*100 { req.WalletID = wallets.Data[i].ID break } @@ -1759,18 +1335,15 @@ func TestWithdrawCryptocurrencyFunds(t *testing.T) { if req.WalletID == "" { t.Skip(skipInsufficientFunds) } - _, err = c.WithdrawCryptocurrencyFunds(context.Background(), &req) - if err != nil { - t.Error(err) - } + resp, err := c.WithdrawCryptocurrencyFunds(context.Background(), &req) + assert.NoError(t, err) + assert.NotEmpty(t, resp, errExpectedNonEmpty) } func TestWithdrawFiatFunds(t *testing.T) { req := withdraw.Request{} _, err := c.WithdrawFiatFunds(context.Background(), &req) - if !errors.Is(err, common.ErrExchangeNameUnset) { - t.Errorf(errExpectMismatch, err, common.ErrExchangeNameUnset) - } + assert.ErrorIs(t, err, common.ErrExchangeNameUnset) req.Exchange = c.Name req.Currency = currency.AUD req.Amount = 1 @@ -1782,9 +1355,7 @@ func TestWithdrawFiatFunds(t *testing.T) { req.Fiat.Bank.SWIFTCode = "456" req.Fiat.Bank.BSBNumber = "789" _, err = c.WithdrawFiatFunds(context.Background(), &req) - if !errors.Is(err, errWalletIDEmpty) { - t.Errorf(errExpectMismatch, err, errWalletIDEmpty) - } + assert.ErrorIs(t, err, errWalletIDEmpty) req.WalletID = "meow" req.Fiat.Bank.BankName = "GCT's Fake and Not Real Test Bank Meow Meow" expectedError := fmt.Sprintf(errPayMethodNotFound, req.Fiat.Bank.BankName) @@ -1794,15 +1365,13 @@ func TestWithdrawFiatFunds(t *testing.T) { } sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) wallets, err := c.GetAllWallets(context.Background(), PaginationInp{}) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) if len(wallets.Data) == 0 { t.Fatal(errExpectedNonEmpty) } req.WalletID = "" for i := range wallets.Data { - if wallets.Data[i].Currency.Name == currency.AUD.String() && wallets.Data[i].Balance.Amount > testAmount { + if wallets.Data[i].Currency.Name == currency.AUD.String() && wallets.Data[i].Balance.Amount > testAmount*100 { req.WalletID = wallets.Data[i].ID break } @@ -1811,18 +1380,16 @@ func TestWithdrawFiatFunds(t *testing.T) { t.Skip(skipInsufficientFunds) } req.Fiat.Bank.BankName = "AUD Wallet" - _, err = c.WithdrawFiatFunds(context.Background(), &req) - if err != nil { - t.Error(err) - } + resp, err := c.WithdrawFiatFunds(context.Background(), &req) + assert.NoError(t, err) + assert.NotEmpty(t, resp, errExpectedNonEmpty) } func TestWithdrawFiatFundsToInternationalBank(t *testing.T) { req := withdraw.Request{} _, err := c.WithdrawFiatFundsToInternationalBank(context.Background(), &req) - if !errors.Is(err, common.ErrExchangeNameUnset) { - t.Errorf(errExpectMismatch, err, common.ErrExchangeNameUnset) - } + assert.ErrorIs(t, err, common.ErrExchangeNameUnset) + req.Exchange = c.Name } func TestGetFeeByType(t *testing.T) { @@ -1836,9 +1403,7 @@ func TestGetFeeByType(t *testing.T) { feeBuilder.Amount = 1 feeBuilder.PurchasePrice = 1 resp, err := c.GetFeeByType(context.Background(), &feeBuilder) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) if resp != 0.008 { t.Errorf(errExpectMismatch, resp, 0.008) } @@ -1846,9 +1411,7 @@ func TestGetFeeByType(t *testing.T) { func TestGetActiveOrders(t *testing.T) { _, err := c.GetActiveOrders(context.Background(), nil) - if !errors.Is(err, common.ErrNilPointer) { - t.Errorf(errExpectMismatch, err, common.ErrNilPointer) - } + assert.ErrorIs(t, err, common.ErrNilPointer) var req order.MultiOrderRequest _, err = c.GetActiveOrders(context.Background(), &req) if err.Error() != errUnsupportedAssetType { @@ -1859,87 +1422,63 @@ func TestGetActiveOrders(t *testing.T) { req.Side = order.AnySide req.Type = order.AnyType _, err = c.GetActiveOrders(context.Background(), &req) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) req.Pairs = req.Pairs.Add(currency.NewPair(currency.BTC, currency.USD)) _, err = c.GetActiveOrders(context.Background(), &req) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) } func TestGetOrderHistory(t *testing.T) { _, err := c.GetOrderHistory(context.Background(), nil) - if !errors.Is(err, order.ErrGetOrdersRequestIsNil) { - t.Errorf(errExpectMismatch, err, order.ErrGetOrdersRequestIsNil) - } + assert.ErrorIs(t, err, order.ErrGetOrdersRequestIsNil) sharedtestvalues.SkipTestIfCredentialsUnset(t, c) var req order.MultiOrderRequest req.AssetType = asset.Spot req.Side = order.AnySide req.Type = order.AnyType _, err = c.GetOrderHistory(context.Background(), &req) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) req.Pairs = req.Pairs.Add(testPair) _, err = c.GetOrderHistory(context.Background(), &req) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) } func TestGetHistoricCandles(t *testing.T) { _, err := c.GetHistoricCandles(context.Background(), currency.Pair{}, asset.Empty, kline.OneYear, time.Time{}, time.Time{}) - if !errors.Is(err, currency.ErrCurrencyPairEmpty) { - t.Errorf(errExpectMismatch, err, currency.ErrCurrencyPairEmpty) - } + assert.ErrorIs(t, err, currency.ErrCurrencyPairEmpty) sharedtestvalues.SkipTestIfCredentialsUnset(t, c) _, err = c.GetHistoricCandles(context.Background(), testPair, asset.Spot, kline.ThreeHour, time.Now().Add(-time.Hour*30), time.Now()) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) } func TestGetHistoricCandlesExtended(t *testing.T) { _, err := c.GetHistoricCandlesExtended(context.Background(), currency.Pair{}, asset.Empty, kline.OneYear, time.Time{}, time.Time{}) - if !errors.Is(err, currency.ErrCurrencyPairEmpty) { - t.Errorf(errExpectMismatch, err, currency.ErrCurrencyPairEmpty) - } + assert.ErrorIs(t, err, currency.ErrCurrencyPairEmpty) sharedtestvalues.SkipTestIfCredentialsUnset(t, c) resp, err := c.GetHistoricCandlesExtended(context.Background(), testPair, asset.Spot, kline.OneMin, time.Now().Add(-time.Hour*9), time.Now()) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) } func TestValidateAPICredentials(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) err := c.ValidateAPICredentials(context.Background(), asset.Spot) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) } func TestGetServerTime(t *testing.T) { _, err := c.GetServerTime(context.Background(), 0) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) } func TestGetLatestFundingRates(t *testing.T) { _, err := c.GetLatestFundingRates(context.Background(), nil) - if !errors.Is(err, common.ErrNilPointer) { - t.Errorf(errExpectMismatch, err, common.ErrNilPointer) - } + assert.ErrorIs(t, err, common.ErrNilPointer) req := fundingrate.LatestRateRequest{Asset: asset.UpsideProfitContract} _, err = c.GetLatestFundingRates(context.Background(), &req) if err.Error() != errUpsideUnsupported { @@ -1948,25 +1487,19 @@ func TestGetLatestFundingRates(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) req.Asset = asset.Futures _, err = c.GetLatestFundingRates(context.Background(), &req) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) } func TestGetFuturesContractDetails(t *testing.T) { _, err := c.GetFuturesContractDetails(context.Background(), asset.Empty) - if !errors.Is(err, futures.ErrNotFuturesAsset) { - t.Errorf(errExpectMismatch, err, futures.ErrNotFuturesAsset) - } + assert.ErrorIs(t, err, futures.ErrNotFuturesAsset) _, err = c.GetFuturesContractDetails(context.Background(), asset.UpsideProfitContract) if err.Error() != errUpsideUnsupported { t.Errorf(errExpectMismatch, err, errUpsideUnsupported) } sharedtestvalues.SkipTestIfCredentialsUnset(t, c) _, err = c.GetFuturesContractDetails(context.Background(), asset.Futures) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) } func TestUpdateOrderExecutionLimits(t *testing.T) { @@ -1976,9 +1509,7 @@ func TestUpdateOrderExecutionLimits(t *testing.T) { } sharedtestvalues.SkipTestIfCredentialsUnset(t, c) err = c.UpdateOrderExecutionLimits(context.Background(), asset.Futures) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) } func TestFiatTransferTypeString(t *testing.T) { @@ -2005,9 +1536,7 @@ func TestUnixTimestampUnmarshalJSON(t *testing.T) { t.Errorf(errExpectMismatch, err, errParseIntValueOutOfRange) } err = u.UnmarshalJSON([]byte("\"1234\"")) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) } func TestUnixTimestampString(t *testing.T) { @@ -2055,18 +1584,12 @@ func TestFormatExchangeKlineInterval(t *testing.T) { func TestStringToFloatPtr(t *testing.T) { t.Parallel() err := stringToFloatPtr(nil, "") - if err != errPointerNil { - t.Errorf(errExpectMismatch, err, errPointerNil) - } + assert.ErrorIs(t, err, errPointerNil) var fl float64 err = stringToFloatPtr(&fl, "") - if err != nil { - t.Error(err) - } + assert.NoError(t, err) err = stringToFloatPtr(&fl, "1.1") - if err != nil { - t.Error(err) - } + assert.NoError(t, err) } func TestWsConnect(t *testing.T) { @@ -2076,18 +1599,19 @@ func TestWsConnect(t *testing.T) { t.Errorf(errExpectMismatch, err, stream.WebsocketNotEnabled) } c.Websocket.Enable() - err = c.WsConnect() - if err != nil { - t.Error(err) - } + // err = c.WsConnect() + // assert.NoError(t, err) } func TestWsHandleData(t *testing.T) { - c.Websocket.DataHandler = make(chan interface{}, 4) + // c.Websocket.DataHandler = make(chan interface{}, 100) + go func() { + for range c.Websocket.DataHandler { + continue + } + }() _, err := c.wsHandleData(nil, 0) - if !errors.Is(err, jsonparser.KeyPathNotFoundError) { - t.Errorf(errExpectMismatch, err, jsonparser.KeyPathNotFoundError) - } + assert.ErrorIs(t, err, jsonparser.KeyPathNotFoundError) mockJson := []byte(`{"sequence_num": "l"}`) _, err = c.wsHandleData(mockJson, 0) if err.Error() != errParseUintInvalidSyntax { @@ -2095,19 +1619,13 @@ func TestWsHandleData(t *testing.T) { } mockJson = []byte(`{"sequence_num": 1, /\\/"""}`) _, err = c.wsHandleData(mockJson, 0) - if !errors.Is(err, jsonparser.KeyPathNotFoundError) { - t.Errorf(errExpectMismatch, err, jsonparser.KeyPathNotFoundError) - } + assert.ErrorIs(t, err, jsonparser.KeyPathNotFoundError) mockJson = []byte(`{"sequence_num": 0, "channel": "subscriptions"}`) _, err = c.wsHandleData(mockJson, 0) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) mockJson = []byte(`{"sequence_num": 0, "channel": "", "events":}`) _, err = c.wsHandleData(mockJson, 0) - if !errors.Is(err, jsonparser.UnknownValueTypeError) { - t.Errorf(errExpectMismatch, err, jsonparser.UnknownValueTypeError) - } + assert.ErrorIs(t, err, jsonparser.UnknownValueTypeError) mockJson = []byte(`{"sequence_num": 0, "channel": "status", "events": ["type": 1234]}`) _, err = c.wsHandleData(mockJson, 0) if err.Error() != errJsonInvalidCharacter { @@ -2115,14 +1633,10 @@ func TestWsHandleData(t *testing.T) { } mockJson = []byte(`{"sequence_num": 0, "channel": "status", "events": [{"type": "moo"}]}`) _, err = c.wsHandleData(mockJson, 0) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) mockJson = []byte(`{"sequence_num": 0, "channel": "error", "events": [{"type": "moo"}]}`) _, err = c.wsHandleData(mockJson, 0) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) mockJson = []byte(`{"sequence_num": 0, "channel": "ticker", "events": ["type": ""}]}`) _, err = c.wsHandleData(mockJson, 0) if err.Error() != errJsonInvalidCharacter { @@ -2130,16 +1644,132 @@ func TestWsHandleData(t *testing.T) { } mockJson = []byte(`{"sequence_num": 0, "channel": "ticker", "events": [{"type": "moo", "tickers": [{"price": "1.1"}]}]}`) _, err = c.wsHandleData(mockJson, 0) + assert.ErrorIs(t, err, jsonparser.KeyPathNotFoundError) + mockJson = []byte(`{"sequence_num": 0, "channel": "ticker", "timestamp": "2006-01-02T15:04:05Z", "events": [{"type": "moo", "tickers": [{"price": "1.1"}]}]}`) + _, err = c.wsHandleData(mockJson, 0) + assert.NoError(t, err) + mockJson = []byte(`{"sequence_num": 0, "channel": "candles", "events": ["type": ""}]}`) + _, err = c.wsHandleData(mockJson, 0) + if err.Error() != errJsonInvalidCharacter { + t.Errorf(errExpectMismatch, err, errJsonInvalidCharacter) + } + mockJson = []byte(`{"sequence_num": 0, "channel": "candles", "events": [{"type": "moo", "candles": [{"low": "1.1"}]}]}`) + _, err = c.wsHandleData(mockJson, 0) + assert.ErrorIs(t, err, jsonparser.KeyPathNotFoundError) + mockJson = []byte(`{"sequence_num": 0, "channel": "candles", "timestamp": "2006-01-02T15:04:05Z", "events": [{"type": "moo", "candles": [{"low": "1.1"}]}]}`) + _, err = c.wsHandleData(mockJson, 0) + assert.NoError(t, err) + mockJson = []byte(`{"sequence_num": 0, "channel": "market_trades", "events": ["type": ""}]}`) + _, err = c.wsHandleData(mockJson, 0) + if err.Error() != errJsonInvalidCharacter { + t.Errorf(errExpectMismatch, err, errJsonInvalidCharacter) + } + mockJson = []byte(`{"sequence_num": 0, "channel": "market_trades", "events": [{"type": "moo", "trades": [{"price": "1.1"}]}]}`) + _, err = c.wsHandleData(mockJson, 0) + assert.NoError(t, err) + mockJson = []byte(`{"sequence_num": 0, "channel": "l2_data", "events": ["type": ""}]}`) + _, err = c.wsHandleData(mockJson, 0) + if err.Error() != errJsonInvalidCharacter { + t.Errorf(errExpectMismatch, err, errJsonInvalidCharacter) + } + mockJson = []byte(`{"sequence_num": 0, "channel": "l2_data", "events": [{"type": "moo", "updates": [{"price_level": "1.1"}]}]}`) + _, err = c.wsHandleData(mockJson, 0) + assert.ErrorIs(t, err, jsonparser.KeyPathNotFoundError) + mockJson = []byte(`{"sequence_num": 0, "channel": "l2_data", "timestamp": "2006-01-02T15:04:05Z", "events": [{"type": "moo", "updates": [{"price_level": "1.1"}]}]}`) + _, err = c.wsHandleData(mockJson, 0) + if err.Error() != errL2DataMoo { + t.Errorf(errExpectMismatch, err, errL2DataMoo) + } + mockJson = []byte(`{"sequence_num": 0, "channel": "l2_data", "timestamp": "2006-01-02T15:04:05Z", "events": [{"type": "snapshot", "product_id": "BTC-USD", "updates": [{"side": "bid", "price_level": "1.1", "new_quantity": "2.2"}]}]}`) + _, err = c.wsHandleData(mockJson, 0) + assert.NoError(t, err) + mockJson = []byte(`{"sequence_num": 0, "channel": "l2_data", "timestamp": "2006-01-02T15:04:05Z", "events": [{"type": "update", "product_id": "BTC-USD", "updates": [{"side": "bid", "price_level": "1.1", "new_quantity": "2.2"}]}]}`) + _, err = c.wsHandleData(mockJson, 0) + assert.NoError(t, err) + mockJson = []byte(`{"sequence_num": 0, "channel": "user", "events": ["type": ""}]}`) + _, err = c.wsHandleData(mockJson, 0) + if err.Error() != errJsonInvalidCharacter { + t.Errorf(errExpectMismatch, err, errJsonInvalidCharacter) + } + mockJson = []byte(`{"sequence_num": 0, "channel": "user", "events": [{"type": "moo", "orders": [{"total_fees": "1.1"}]}]}`) + _, err = c.wsHandleData(mockJson, 0) + if err.Error() != errUnrecognisedOrderType { + t.Errorf(errExpectMismatch, err, errUnrecognisedOrderType) + } + mockJson = []byte(`{"sequence_num": 0, "channel": "user", "events": [{"type": "moo", "orders": [{"total_fees": "1.1", "order_type": "ioc"}]}]}`) + _, err = c.wsHandleData(mockJson, 0) + if err.Error() != errOrderSideInvalid { + t.Errorf(errExpectMismatch, err, errOrderSideInvalid) + } + mockJson = []byte(`{"sequence_num": 0, "channel": "user", "events": [{"type": "moo", "orders": [{"total_fees": "1.1", "order_type": "ioc", "order_side": "buy"}]}]}`) + _, err = c.wsHandleData(mockJson, 0) + if err.Error() != errUnrecognisedStatusType { + t.Errorf(errExpectMismatch, err, errUnrecognisedStatusType) + } + mockJson = []byte(`{"sequence_num": 0, "channel": "user", "events": [{"type": "moo", "orders": [{"total_fees": "1.1", "order_type": "ioc", "order_side": "buy", "status": "done"}]}]}`) + _, err = c.wsHandleData(mockJson, 0) + assert.NoError(t, err) + mockJson = []byte(`{"sequence_num": 0, "channel": "fakechan", "events": ["type": ""}]}`) + _, err = c.wsHandleData(mockJson, 0) + assert.ErrorIs(t, err, errChannelNameUnknown) +} + +func TestProcessSnapshotUpdate(t *testing.T) { + req := WebsocketOrderbookDataHolder{Changes: []WebsocketOrderbookData{{Side: "fakeside", PriceLevel: 1.1, + NewQuantity: 2.2}}, ProductID: currency.NewBTCUSD()} + err := c.ProcessSnapshot(req, time.Time{}) + if err.Error() != errFakeSide { + t.Errorf(errExpectMismatch, err, errFakeSide) + } + err = c.ProcessUpdate(req, time.Time{}) + if err.Error() != errFakeSide { + t.Errorf(errExpectMismatch, err, errFakeSide) + } + req.Changes[0].Side = "offer" + err = c.ProcessSnapshot(req, time.Now()) + assert.NoError(t, err) + err = c.ProcessUpdate(req, time.Now()) + assert.NoError(t, err) +} + +func TestGenerateDefaultSubscriptions(t *testing.T) { + comparison := []stream.ChannelSubscription{{Channel: "heartbeats"}, {Channel: "status"}, {Channel: "ticker"}, + {Channel: "ticker_batch"}, {Channel: "candles"}, {Channel: "market_trades"}, {Channel: "level2"}, + {Channel: "user"}} + for i := range comparison { + comparison[i].Currency = currency.NewPairWithDelimiter(currency.BTC.String(), currency.USD.String(), "-") + comparison[i].Asset = asset.Spot + } + resp, err := c.GenerateDefaultSubscriptions() if err != nil { - t.Error(err) + t.Fatal(err) + } + assert.ElementsMatch(t, comparison, resp) +} + +func TestSubscribeUnsubscribe(t *testing.T) { + req := []stream.ChannelSubscription{{Channel: "heartbeats", Asset: asset.Spot, + Currency: currency.NewPairWithDelimiter(currency.BTC.String(), currency.USD.String(), "-")}} + err := c.Subscribe(req) + assert.NoError(t, err) + err = c.Unsubscribe(req) + assert.NoError(t, err) +} + +func TestGetJWT(t *testing.T) { + creds, err := c.GetCredentials(context.Background()) + assert.NoError(t, err) + _, err = c.GetJWT(context.Background(), "") + if strings.HasPrefix(creds.Secret, "-----BEGIN EC PRIVATE KEY-----\n") { + assert.NoError(t, err) + } else { + assert.ErrorIs(t, err, errCantDecodePrivKey) } } func skipTestIfLowOnFunds(t *testing.T) { accounts, err := c.GetAllAccounts(context.Background(), 250, "") - if err != nil { - t.Error(err) - } + assert.NoError(t, err) if len(accounts.Accounts) == 0 { t.Fatal(errExpectedNonEmpty) } @@ -2154,7 +1784,7 @@ func skipTestIfLowOnFunds(t *testing.T) { } } -func portfolioTestHelper(t *testing.T, targetName string) string { +func portfolioIDFromName(t *testing.T, targetName string) string { sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) createResp, err := c.CreatePortfolio(context.Background(), targetName) var targetID string @@ -2181,11 +1811,29 @@ func portfolioTestHelper(t *testing.T, targetName string) string { return targetID } +func portfolioIDFromType(t *testing.T, targetType string) string { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + resp, err := c.GetAllPortfolios(context.Background(), "") + assert.NoError(t, err) + if len(resp.Portfolios) == 0 { + t.Skip(skipInsufficientPortfolios) + } + var targetID string + for i := range resp.Portfolios { + if resp.Portfolios[i].Type == targetType { + targetID = resp.Portfolios[i].UUID + break + } + } + if targetID == "" { + t.Skip(skipInsufficientPortfolios) + } + return targetID +} + func convertTestHelper(t *testing.T) (string, string) { accIDs, err := c.GetAllAccounts(context.Background(), 250, "") - if err != nil { - t.Error(err) - } + assert.NoError(t, err) if len(accIDs.Accounts) == 0 { t.Fatal(errExpectedNonEmpty) } @@ -2223,9 +1871,7 @@ func transferTestHelper(t *testing.T, wallets GetAllWalletsResponse) (string, st t.Skip(skipInsufficientFunds) } pmID, err := c.GetAllPaymentMethods(context.Background(), PaginationInp{}) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) if len(pmID.Data) == 0 { t.Skip(skipPayMethodNotFound) } diff --git a/exchanges/coinbasepro/coinbasepro_types.go b/exchanges/coinbasepro/coinbasepro_types.go index f6dbc33ae83..7208ca10cef 100644 --- a/exchanges/coinbasepro/coinbasepro_types.go +++ b/exchanges/coinbasepro/coinbasepro_types.go @@ -337,6 +337,25 @@ type FillResponse struct { Cursor string `json:"cursor"` } +// PreviewOrderResp contains information on the effects of placing an order, returned by +// PreviewOrder +type PreviewOrderResp struct { + OrderTotal float64 `json:"order_total,string"` + CommissionTotal float64 `json:"commission_total,string"` + Errs []string `json:"errs"` + Warning []string `json:"warning"` + QuoteSize float64 `json:"quote_size,string"` + BaseSize float64 `json:"base_size,string"` + BestBid float64 `json:"best_bid,string"` + BestAsk float64 `json:"best_ask,string"` + IsMax bool `json:"is_max"` + OrderMarginTotal float64 `json:"order_margin_total,string"` + Leverage float64 `json:"leverage,string"` + LongLeverage float64 `json:"long_leverage,string"` + ShortLeverage float64 `json:"short_leverage,string"` + Slippage float64 `json:"slippage,string"` +} + // SimplePortfolioData is a sub-struct used in the types AllPortfolioResponse, // SimplePortfolioResponse, and DetailedPortfolioResponse type SimplePortfolioData struct { @@ -500,6 +519,78 @@ type ListFuturesSweepsResponse struct { } `json:"sweeps"` } +// PerpetualsPortfolioSummary contains information on perpetuals portfolio balances, used as +// a sub-struct in the types GetPerpetualsPortfolioSummary, AllPerpPosResponse, and +// OnePerpPosResponse +type PerpetualsPortfolioSummary struct { + PortfolioUUID string `json:"portfolio_uuid"` + Collateral float64 `json:"collateral,string"` + PositionNotional float64 `json:"position_notional,string"` + OpenPositionNotional float64 `json:"open_position_notional,string"` + PendingFees float64 `json:"pending_fees,string"` + Borrow float64 `json:"borrow,string"` + AccruedInterest float64 `json:"accrued_interest,string"` + RollingDebt float64 `json:"rolling_debt,string"` + PortfolioInitialMargin float64 `json:"portfolio_initial_margin,string"` + PortfolioIMNotional ValCur `json:"portfolio_im_notional"` + PortfolioMaintenanceMargin float64 `json:"portfolio_maintenance_margin,string"` + PortfolioMMNotional ValCur `json:"portfolio_mm_notional"` + LiquidationPercentage float64 `json:"liquidation_percentage,string"` + LiquidationBuffer float64 `json:"liquidation_buffer,string"` + MarginType string `json:"margin_type"` + MarginFlags string `json:"margin_flags"` + LiquidationStatus string `json:"liquidation_status"` + UnrealizedPNL ValCur `json:"unrealized_pnl"` + BuyingPower ValCur `json:"buying_power"` + TotalBalance ValCur `json:"total_balance"` + MaxWithDrawal ValCur `json:"max_withdrawal"` +} + +// PerpetualPortResponse contains information on perpetuals portfolio balances, returned by +// GetPerpetualsPortfolioSummary +type PerpetualPortResponse struct { + Summary PerpetualsPortfolioSummary `json:"summary"` +} + +// PerpPositionDetail contains information on a single perpetuals position, used as a sub-struct +// in the types AllPerpPosResponse and OnePerpPosResponse +type PerpPositionDetail struct { + ProductID string `json:"product_id"` + ProductUUID string `json:"product_uuid"` + Symbol string `json:"symbol"` + VWAP ValCur `json:"vwap"` + PositionSide string `json:"position_side"` + NetSize float64 `json:"net_size,string"` + BuyOrderSize float64 `json:"buy_order_size,string"` + SellOrderSize float64 `json:"sell_order_size,string"` + IMContribution float64 `json:"im_contribution,string"` + UnrealizedPNL ValCur `json:"unrealized_pnl"` + MarkPrice ValCur `json:"mark_price"` + LiquidationPrice ValCur `json:"liquidation_price"` + Leverage float64 `json:"leverage,string"` + IMNotional ValCur `json:"im_notional"` + MMNotional ValCur `json:"mm_notional"` + PositionNotional ValCur `json:"position_notional"` + MarginType string `json:"margin_type"` + LiquidationBuffer float64 `json:"liquidation_buffer,string"` + LiquidationPercentage float64 `json:"liquidation_percentage,string"` + PortfolioSummary PerpetualsPortfolioSummary `json:"portfolio_summary"` +} + +// PerpetualPositionsResponse contains information on perpetuals positions, returned by +// GetAllPerpetualsPositions +type AllPerpPosResponse struct { + Positions []PerpPositionDetail `json:"positions"` + PortfolioSummary PerpetualsPortfolioSummary `json:"portfolio_summary"` +} + +// OnePerpPosResponse contains information on a single perpetuals position, returned by +// GetPerpetualsPositionByID +type OnePerpPosResponse struct { + Position PerpPositionDetail `json:"position"` + PortfolioSummary PerpetualsPortfolioSummary `json:"portfolio_summary"` +} + // TransactionSummary contains a summary of transaction fees, volume, and the like. Returned // by GetTransactionSummary type TransactionSummary struct { diff --git a/exchanges/coinbasepro/coinbasepro_websocket.go b/exchanges/coinbasepro/coinbasepro_websocket.go index e91750883fa..cd71f30aa0b 100644 --- a/exchanges/coinbasepro/coinbasepro_websocket.go +++ b/exchanges/coinbasepro/coinbasepro_websocket.go @@ -335,22 +335,19 @@ func (c *CoinbasePro) wsHandleData(respRaw []byte, seqCount uint64) (string, err var oType order.Type oType, err = order.StringToOrderType(wsUser[i].Orders[j].OrderType) if err != nil { - c.Websocket.DataHandler <- stream.UnhandledMessageWarning{Message: c.Name + stream.UnhandledMessage + string(respRaw)} - continue + return warnString, err } var oSide order.Side oSide, err = order.StringToOrderSide(wsUser[i].Orders[j].OrderSide) if err != nil { - c.Websocket.DataHandler <- stream.UnhandledMessageWarning{Message: c.Name + stream.UnhandledMessage + string(respRaw)} - continue + return warnString, err } var oStatus order.Status oStatus, err = statusToStandardStatus(wsUser[i].Orders[j].Status) if err != nil { - c.Websocket.DataHandler <- stream.UnhandledMessageWarning{Message: c.Name + stream.UnhandledMessage + string(respRaw)} - continue + return warnString, err } sliToSend = append(sliToSend, order.Detail{ @@ -374,29 +371,11 @@ func (c *CoinbasePro) wsHandleData(respRaw []byte, seqCount uint64) (string, err c.Websocket.DataHandler <- sliToSend default: - c.Websocket.DataHandler <- stream.UnhandledMessageWarning{Message: c.Name + stream.UnhandledMessage + string(respRaw)} - return warnString, nil + return warnString, errChannelNameUnknown } return warnString, nil } -func statusToStandardStatus(stat string) (order.Status, error) { - switch stat { - case "received": - return order.New, nil - case "open": - return order.Active, nil - case "done": - return order.Filled, nil - case "match": - return order.PartiallyFilled, nil - case "change", "activate": - return order.Active, nil - default: - return order.UnknownStatus, fmt.Errorf("%s not recognised as status type", stat) - } -} - // ProcessSnapshot processes the initial orderbook snap shot func (c *CoinbasePro) ProcessSnapshot(snapshot WebsocketOrderbookDataHolder, timestamp time.Time) error { bids, asks, err := processBidAskArray(snapshot) @@ -439,40 +418,17 @@ func (c *CoinbasePro) ProcessUpdate(update WebsocketOrderbookDataHolder, timesta return c.Websocket.Orderbook.Update(&obU) } -// processBidAskArray is a helper function that turns WebsocketOrderbookDataHolder into arrays -// of bids and asks -func processBidAskArray(data WebsocketOrderbookDataHolder) ([]orderbook.Item, []orderbook.Item, error) { - var bids, asks []orderbook.Item - for i := range data.Changes { - switch data.Changes[i].Side { - case "bid": - bids = append(bids, orderbook.Item{ - Price: data.Changes[i].PriceLevel, - Amount: data.Changes[i].NewQuantity, - }) - case "offer": - asks = append(asks, orderbook.Item{ - Price: data.Changes[i].PriceLevel, - Amount: data.Changes[i].NewQuantity, - }) - default: - return nil, nil, errors.Errorf(errUnknownSide, data.Changes[i].Side) - } - } - return bids, asks, nil -} - // GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions() func (c *CoinbasePro) GenerateDefaultSubscriptions() ([]stream.ChannelSubscription, error) { var channels = []string{ "heartbeats", - // "status", + "status", "ticker", - // "ticker_batch", + "ticker_batch", "candles", - // "market_trades", + "market_trades", "level2", - // "user", + "user", } enabledCurrencies, err := c.GetEnabledPairs(asset.Spot) if err != nil { @@ -496,54 +452,6 @@ func (c *CoinbasePro) GenerateDefaultSubscriptions() ([]stream.ChannelSubscripti return subscriptions, nil } -func (c *CoinbasePro) sendRequest(msgType, channel string, productIDs currency.Pairs) error { - creds, err := c.GetCredentials(context.Background()) - 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 - } - - // jwt, err := c.GetJWT(context.Background(), "") - // if err != nil { - // return err - // } - - req := WebsocketSubscribe{ - Type: msgType, - ProductIDs: productIDs.Strings(), - Channel: channel, - Signature: hex.EncodeToString(hmac), - // JWT: jwt, - Key: creds.Key, - Timestamp: n, - } - - // reqMarshal, _ := json.Marshal(req) - - // fmt.Print(string(reqMarshal)) - - // err = rLim.Limit(context.Background(), WSRate) - if err != nil { - return err - } - // data, err := c.Websocket.Conn.SendMessageReturnResponse(nil, req) - err = c.Websocket.Conn.SendJSONMessage(req) - if err != nil { - return err - } - return nil -} - // Subscribe sends a websocket message to receive data from the channel func (c *CoinbasePro) Subscribe(channelsToSubscribe []stream.ChannelSubscription) error { @@ -673,7 +581,7 @@ func (c *CoinbasePro) GetJWT(ctx context.Context, uri string) (string, error) { if err != nil { return "", err } - headEncode := Base64URLEncode(headJson) + headEncode := base64URLEncode(headJson) c.jwtLastRegen = time.Now() @@ -686,7 +594,7 @@ func (c *CoinbasePro) GetJWT(ctx context.Context, uri string) (string, error) { if err != nil { return "", err } - bodyEncode := Base64URLEncode(bodyJson) + bodyEncode := base64URLEncode(bodyJson) hash := sha256.Sum256([]byte(headEncode + "." + bodyEncode)) @@ -694,11 +602,13 @@ func (c *CoinbasePro) GetJWT(ctx context.Context, uri string) (string, error) { if err != nil { return "", err } - sigEncode := Base64URLEncode(sig) + sigEncode := base64URLEncode(sig) return headEncode + "." + bodyEncode + "." + sigEncode, nil } +// getTimestamp is a helper function which pulls a RFC3339-formatted timestamp from a byte slice +// of JSON data func getTimestamp(rawData []byte) (time.Time, error) { data, _, _, err := jsonparser.Get(rawData, "timestamp") if err != nil { @@ -711,9 +621,100 @@ func getTimestamp(rawData []byte) (time.Time, error) { return timestamp, nil } +// sendRequest is a helper function which sends a websocket message to the Coinbase server +func (c *CoinbasePro) sendRequest(msgType, channel string, productIDs currency.Pairs) error { + creds, err := c.GetCredentials(context.Background()) + 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 + } + + // jwt, err := c.GetJWT(context.Background(), "") + // if err != nil { + // return err + // } + + req := WebsocketSubscribe{ + Type: msgType, + ProductIDs: productIDs.Strings(), + Channel: channel, + Signature: hex.EncodeToString(hmac), + // JWT: jwt, + Key: creds.Key, + Timestamp: n, + } + + // reqMarshal, _ := json.Marshal(req) + + // fmt.Print(string(reqMarshal)) + + // err = rLim.Limit(context.Background(), WSRate) + if err != nil { + return err + } + // data, err := c.Websocket.Conn.SendMessageReturnResponse(nil, req) + err = c.Websocket.Conn.SendJSONMessage(req) + if err != nil { + return err + } + return nil +} + +// processBidAskArray is a helper function that turns WebsocketOrderbookDataHolder into arrays +// of bids and asks +func processBidAskArray(data WebsocketOrderbookDataHolder) ([]orderbook.Item, []orderbook.Item, error) { + var bids, asks []orderbook.Item + for i := range data.Changes { + switch data.Changes[i].Side { + case "bid": + bids = append(bids, orderbook.Item{ + Price: data.Changes[i].PriceLevel, + Amount: data.Changes[i].NewQuantity, + }) + case "offer": + asks = append(asks, orderbook.Item{ + Price: data.Changes[i].PriceLevel, + Amount: data.Changes[i].NewQuantity, + }) + default: + return nil, nil, errors.Errorf(errUnknownSide, data.Changes[i].Side) + } + } + return bids, asks, nil +} + +// statusToStandardStatus is a helper function that converts a Coinbase Pro status string to a +// standardised order.Status type +func statusToStandardStatus(stat string) (order.Status, error) { + switch stat { + case "received": + return order.New, nil + case "open": + return order.Active, nil + case "done": + return order.Filled, nil + case "match": + return order.PartiallyFilled, nil + case "change", "activate": + return order.Active, nil + default: + return order.UnknownStatus, fmt.Errorf("%s not recognised as status type", stat) + } +} + // Base64URLEncode is a helper function that does some tweaks to standard Base64 encoding, in a way // which JWT requires -func Base64URLEncode(b []byte) string { +func base64URLEncode(b []byte) string { s := crypto.Base64Encode(b) s = strings.Split(s, "=")[0] s = strings.ReplaceAll(s, "+", "-") diff --git a/exchanges/coinbasepro/coinbasepro_wrapper.go b/exchanges/coinbasepro/coinbasepro_wrapper.go index 4baa635d376..445b67ba35f 100644 --- a/exchanges/coinbasepro/coinbasepro_wrapper.go +++ b/exchanges/coinbasepro/coinbasepro_wrapper.go @@ -166,6 +166,7 @@ func (c *CoinbasePro) Setup(exch *config.Exchange) error { return err } if !exch.Enabled { + panic("BRUHS") c.SetEnabled(false) return nil } diff --git a/exchanges/stream/buffer/buffer.go b/exchanges/stream/buffer/buffer.go index 57c2b5fd437..9cb45b7d2ce 100644 --- a/exchanges/stream/buffer/buffer.go +++ b/exchanges/stream/buffer/buffer.go @@ -312,6 +312,9 @@ func (w *Orderbook) LoadSnapshot(book *orderbook.Base) error { w.mtx.Lock() defer w.mtx.Unlock() + if w.ob == nil { + w.ob = make(map[key.PairAsset]*orderbookHolder) + } holder, ok := w.ob[key.PairAsset{Base: book.Pair.Base.Item, Quote: book.Pair.Quote.Item, Asset: book.Asset}] if !ok { // Associate orderbook pointer with local exchange depth map From 2f770d5a90054ed428039014b4dfdbbf127c943c Mon Sep 17 00:00:00 2001 From: Samuel Reid <43227667+cranktakular@users.noreply.github.com> Date: Wed, 14 Feb 2024 15:02:07 +1100 Subject: [PATCH 23/79] Coinbase revamp done?? --- exchanges/coinbasepro/coinbasepro.go | 56 ++-- exchanges/coinbasepro/coinbasepro_test.go | 315 ++++++++---------- exchanges/coinbasepro/coinbasepro_types.go | 166 ++------- .../coinbasepro/coinbasepro_websocket.go | 160 +-------- exchanges/coinbasepro/coinbasepro_wrapper.go | 16 +- 5 files changed, 200 insertions(+), 513 deletions(-) diff --git a/exchanges/coinbasepro/coinbasepro.go b/exchanges/coinbasepro/coinbasepro.go index b68f0ea8f48..1fa4b3179a2 100644 --- a/exchanges/coinbasepro/coinbasepro.go +++ b/exchanges/coinbasepro/coinbasepro.go @@ -95,6 +95,17 @@ const ( errUnknownL2DataType = "unknown l2update data type %v" errUnknownSide = "unknown side %v" warnSequenceIssue = "Out of order sequence number. Received %v, expected %v" + + // While the exchange's fee pages say the worst taker/maker fees are 0.002 lower than the ones listed + // here, the data returned by the GetTransactionsSummary endpoint are consistent with these worst + // case scenarios. The best case scenarios are untested, and assumed to be in line with the fee pages + WorstCaseTakerFee = 0.008 + WorstCaseMakerFee = 0.006 + BestCaseTakerFee = 0.0005 + BestCaseMakerFee = 0 + StablePairMakerFee = 0 + WorstCaseStablePairTakerFee = 0.000045 + BestCaseStablePairTakerFee = 0.00001 ) var ( @@ -127,6 +138,7 @@ var ( errCantDecodePrivKey = errors.New("cannot decode private key") errNoWalletForCurrency = errors.New("no wallet found for currency, address creation impossible") errChannelNameUnknown = errors.New("unknown channel name") + errNoWalletsReturned = errors.New("no wallets returned") ) // GetAllAccounts returns information on all trading accounts associated with the API key @@ -863,6 +875,7 @@ func (c *CoinbasePro) ListNotifications(ctx context.Context, pag PaginationInp) coinbaseV2+coinbaseNotifications, pathParams, nil, false, &resp, nil) } +// GetUserByID returns information about a user, given their ID func (c *CoinbasePro) GetUserByID(ctx context.Context, userID string) (*UserResponse, error) { if userID == "" { return nil, errUserIDEmpty @@ -894,26 +907,6 @@ func (c *CoinbasePro) GetAuthInfo(ctx context.Context) (AuthResponse, error) { path, "", nil, false, &resp, nil) } -// UpdateUser modifies certain user preferences -func (c *CoinbasePro) UpdateUser(ctx context.Context, name, timeZone, nativeCurrency string) (*UserResponse, error) { - var resp *UserResponse - - req := map[string]interface{}{} - - if name != "" { - req["name"] = name - } - if timeZone != "" { - req["time_zone"] = timeZone - } - if nativeCurrency != "" { - req["native_currency"] = nativeCurrency - } - - return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPut, - coinbaseV2+coinbaseUser, "", req, 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 @@ -1366,19 +1359,14 @@ func (c *CoinbasePro) SendAuthenticatedHTTPRequest(ctx context.Context, ep excha return nil, err } - // jwt, err := c.GetJWT(ctx, method+" "+path) - // if err != nil { - // return nil, err - // } - // fmt.Printf("Here's the JWT you were looking for: %s\n", jwt) + // 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["Authorization"] = "Bearer " + jwt headers["CB-ACCESS-TIMESTAMP"] = n headers["Content-Type"] = "application/json" - headers["CB-VERSION"] = "2023-11-13" + headers["CB-VERSION"] = "2024-02-14" // Version 3 only wants query params in the path when the request is sent if isVersion3 { @@ -1476,14 +1464,15 @@ func (c *CoinbasePro) GetFee(ctx context.Context, feeBuilder *exchange.FeeBuilde } case feeBuilder.IsMaker && isStablePair(feeBuilder.Pair) && (feeBuilder.FeeType == exchange.CryptocurrencyTradeFee || feeBuilder.FeeType == exchange.OfflineTradeFee): - fee = 0 + fee = StablePairMakerFee case !feeBuilder.IsMaker && isStablePair(feeBuilder.Pair) && (feeBuilder.FeeType == exchange.CryptocurrencyTradeFee || feeBuilder.FeeType == exchange.OfflineTradeFee): - fee = 0.00001 + fee = WorstCaseStablePairTakerFee case feeBuilder.IsMaker && !isStablePair(feeBuilder.Pair) && feeBuilder.FeeType == exchange.OfflineTradeFee: - fee = 0.006 + fee = WorstCaseMakerFee + fmt.Printf("IsMaker is %v\n", feeBuilder.IsMaker) case !feeBuilder.IsMaker && !isStablePair(feeBuilder.Pair) && feeBuilder.FeeType == exchange.OfflineTradeFee: - fee = 0.008 + fee = WorstCaseTakerFee default: return 0, errFeeTypeNotSupported } @@ -1532,6 +1521,7 @@ func (p *Params) prepareDateString(startDate, endDate time.Time, labelStart, lab 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)) @@ -1547,7 +1537,7 @@ func (p *Params) preparePagination(pag PaginationInp) { } } -// prepareOrderConfig is a helper function that popoulates the OrderConfiguration struct +// 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 @@ -1596,7 +1586,7 @@ func prepareOrderConfig(orderType, side, stopDirection string, amount, limitPric return orderConfig, nil } -// prepareMarginType is a helper function that properly formats the margin type for the request +// prepareMarginType properly formats the margin type for the request func prepareMarginType(marginType string, req map[string]interface{}) { if marginType == "ISOLATED" || marginType == "CROSS" { req["margin_type"] = marginType diff --git a/exchanges/coinbasepro/coinbasepro_test.go b/exchanges/coinbasepro/coinbasepro_test.go index 2322212947f..444203b2618 100644 --- a/exchanges/coinbasepro/coinbasepro_test.go +++ b/exchanges/coinbasepro/coinbasepro_test.go @@ -39,9 +39,8 @@ var ( // Please supply your APIKeys here for better testing const ( - apiKey = "" - apiSecret = "" - // clientID = "" // passphrase you made at API CREATION, might not exist any more + apiKey = "" + apiSecret = "" canManipulateRealOrders = false testingInSandbox = false ) @@ -92,15 +91,12 @@ const ( ) func TestSetup(t *testing.T) { - // err := c.Setup(nil) - // assert.ErrorIs(t, err, config.ErrExchangeConfigIsNil) cfg, err := c.GetStandardConfig() assert.NoError(t, err) cfg.API.AuthenticatedSupport = true cfg.API.Credentials.Key = apiKey cfg.API.Credentials.Secret = apiSecret cfg.Enabled = false - // _ = c.Setup(cfg) cfg.Enabled = true cfg.ProxyAddress = string(rune(0x7f)) err = c.Setup(cfg) @@ -146,7 +142,6 @@ func TestMain(m *testing.M) { c.GetBase().API.AuthenticatedSupport = true c.GetBase().API.AuthenticatedWebsocketSupport = true } - // c.Verbose = true err = gctlog.SetGlobalLogConfig(gctlog.GenDefaultSettings()) fmt.Println(err) c.Websocket.Enable() @@ -165,16 +160,6 @@ func TestStart(t *testing.T) { testWg.Wait() } -// func TestSendAuthenticatedHTTPRequest(t *testing.T) { -// sharedtestvalues.SkipTestIfCredentialsUnset(t, c) -// var resp interface{} -// err := c.SendAuthenticatedHTTPRequest(context.Background(), exchange.RestSpot, "", "", "", nil, &resp, nil) -// if err != nil { -// t.Error("SendAuthenticatedHTTPRequest() error", err) -// } -// log.Printf("%+v", resp) -// } - func TestGetAllAccounts(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) resp, err := c.GetAllAccounts(context.Background(), 50, "") @@ -218,12 +203,10 @@ func TestGetProductBook(t *testing.T) { func TestGetAllProducts(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) testPairs := []string{testPair.String(), "ETH-USD"} - // var testPairs []string resp, err := c.GetAllProducts(context.Background(), 30000, 0, "SPOT", "PERPETUAL", "", testPairs) assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) - // log.Printf("%+v\n%+v", resp.NumProducts, len(resp.Products)) } func TestGetProductByID(t *testing.T) { @@ -406,11 +389,10 @@ func TestCreatePortfolio(t *testing.T) { _, err := c.CreatePortfolio(context.Background(), "") assert.ErrorIs(t, err, errNameEmpty) sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) - resp, err := c.CreatePortfolio(context.Background(), "GCT Test Portfolio") + _, err = c.CreatePortfolio(context.Background(), "GCT Test Portfolio") if err != nil && err.Error() != errPortfolioNameDuplicate { t.Error(err) } - assert.NotEmpty(t, resp, errExpectedNonEmpty) } func TestMovePortfolioFunds(t *testing.T) { @@ -426,12 +408,11 @@ func TestMovePortfolioFunds(t *testing.T) { if len(portID.Portfolios) < 2 { t.Skip(skipInsufficientPortfolios) } - resp, err := c.MovePortfolioFunds(context.Background(), testCrypto.String(), portID.Portfolios[0].UUID, portID.Portfolios[1].UUID, + _, err = c.MovePortfolioFunds(context.Background(), testCrypto.String(), portID.Portfolios[0].UUID, portID.Portfolios[1].UUID, testAmount) if err != nil && err.Error() != errPortTransferInsufFunds { t.Error(err) } - assert.NotEmpty(t, resp, errExpectedNonEmpty) } func TestGetPortfolioByID(t *testing.T) { @@ -464,34 +445,29 @@ func TestEditPortfolio(t *testing.T) { _, err = c.EditPortfolio(context.Background(), "meow", "") assert.ErrorIs(t, err, errNameEmpty) pID := portfolioIDFromName(t, "GCT Test Portfolio To-Edit") - resp, err := c.EditPortfolio(context.Background(), pID, "GCT Test Portfolio Edited") + _, err = c.EditPortfolio(context.Background(), pID, "GCT Test Portfolio Edited") if err != nil && err.Error() != errPortfolioNameDuplicate { t.Error(err) } - assert.NotEmpty(t, resp, errExpectedNonEmpty) } func TestGetFuturesBalanceSummary(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - resp, err := c.GetFuturesBalanceSummary(context.Background()) + _, err := c.GetFuturesBalanceSummary(context.Background()) assert.NoError(t, err) - assert.NotEmpty(t, resp, errExpectedNonEmpty) } func TestGetAllFuturesPositions(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - resp, err := c.GetAllFuturesPositions(context.Background()) - assert.NoError(t, err) - assert.NotEmpty(t, resp, errExpectedNonEmpty) + testGetNoArgs(t, c.GetAllFuturesPositions) } func TestGetFuturesPositionByID(t *testing.T) { _, err := c.GetFuturesPositionByID(context.Background(), "") assert.ErrorIs(t, err, errProductIDEmpty) sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - resp, err := c.GetFuturesPositionByID(context.Background(), "meow") + _, err = c.GetFuturesPositionByID(context.Background(), "meow") assert.NoError(t, err) - assert.NotEmpty(t, resp, errExpectedNonEmpty) } func TestScheduleFuturesSweep(t *testing.T) { @@ -609,38 +585,16 @@ func TestCreateConvertQuote(t *testing.T) { } func TestCommitConvertTrade(t *testing.T) { - _, err := c.CommitConvertTrade(context.Background(), "", "", "") - assert.ErrorIs(t, err, errTransactionIDEmpty) - _, err = c.CommitConvertTrade(context.Background(), "meow", "", "") - assert.ErrorIs(t, err, errAccountIDEmpty) - sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) - fromAccID, toAccID := convertTestHelper(t) - resp, err := c.CreateConvertQuote(context.Background(), fromAccID, toAccID, "", "", 0.01) - assert.NoError(t, err) - resp, err = c.CommitConvertTrade(context.Background(), resp.Trade.ID, fromAccID, toAccID) - assert.NoError(t, err) - assert.NotEmpty(t, resp, errExpectedNonEmpty) + convertTestShared(t, c.CommitConvertTrade) } func TestGetConvertTradeByID(t *testing.T) { - _, err := c.GetConvertTradeByID(context.Background(), "", "", "") - assert.ErrorIs(t, err, errTransactionIDEmpty) - _, err = c.GetConvertTradeByID(context.Background(), "meow", "", "") - assert.ErrorIs(t, err, errAccountIDEmpty) - sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) - fromAccID, toAccID := convertTestHelper(t) - resp, err := c.CreateConvertQuote(context.Background(), fromAccID, toAccID, "", "", 0.01) - assert.NoError(t, err) - resp, err = c.GetConvertTradeByID(context.Background(), resp.Trade.ID, fromAccID, toAccID) - assert.NoError(t, err) - assert.NotEmpty(t, resp, errExpectedNonEmpty) + convertTestShared(t, c.GetConvertTradeByID) } func TestGetV3Time(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - resp, err := c.GetV3Time(context.Background()) - assert.NoError(t, err) - assert.NotEmpty(t, resp, errExpectedNonEmpty) + testGetNoArgs(t, c.GetV3Time) } func TestListNotifications(t *testing.T) { @@ -665,29 +619,12 @@ func TestGetUserByID(t *testing.T) { func TestGetCurrentUser(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - resp, err := c.GetCurrentUser(context.Background()) - assert.NoError(t, err) - assert.NotEmpty(t, resp, errExpectedNonEmpty) + testGetNoArgs(t, c.GetCurrentUser) } func TestGetAuthInfo(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - resp, err := c.GetAuthInfo(context.Background()) - assert.NoError(t, err) - assert.NotEmpty(t, resp, errExpectedNonEmpty) -} - -func TestUpdateUser(t *testing.T) { - sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) - oldData, err := c.GetCurrentUser(context.Background()) - if err != nil { - t.Fatal(err) - } - _, err = c.UpdateUser(context.Background(), "Name changed as per GCT testing", "Sydney", testFiat.String()) - assert.NoError(t, err) - resp, err := c.UpdateUser(context.Background(), oldData.Data.Name, oldData.Data.TimeZone, oldData.Data.NativeCurrency) - assert.NoError(t, err) - assert.NotEmpty(t, resp, errExpectedNonEmpty) + testGetNoArgs(t, c.GetAuthInfo) } func TestGetAllWallets(t *testing.T) { @@ -960,15 +897,11 @@ func TestGetPaymentMethodByID(t *testing.T) { } func TestGetFiatCurrencies(t *testing.T) { - resp, err := c.GetFiatCurrencies(context.Background()) - assert.NoError(t, err) - assert.NotEmpty(t, resp, errExpectedNonEmpty) + testGetNoArgs(t, c.GetFiatCurrencies) } func TestGetCryptocurrencies(t *testing.T) { - resp, err := c.GetCryptocurrencies(context.Background()) - assert.NoError(t, err) - assert.NotEmpty(t, resp, errExpectedNonEmpty) + testGetNoArgs(t, c.GetCryptocurrencies) } func TestGetExchangeRates(t *testing.T) { @@ -992,9 +925,7 @@ func TestGetPrice(t *testing.T) { } func TestGetV2Time(t *testing.T) { - resp, err := c.GetV2Time(context.Background()) - assert.NoError(t, err) - assert.NotEmpty(t, resp, errExpectedNonEmpty) + testGetNoArgs(t, c.GetV2Time) } func TestSendHTTPRequest(t *testing.T) { @@ -1032,26 +963,26 @@ func TestGetFee(t *testing.T) { } resp, err := c.GetFee(context.Background(), &feeBuilder) assert.NoError(t, err) - if resp != 0.008 { - t.Errorf(errExpectMismatch, resp, 0.008) + if resp != WorstCaseTakerFee { + t.Errorf(errExpectMismatch, resp, WorstCaseTakerFee) } feeBuilder.IsMaker = true resp, err = c.GetFee(context.Background(), &feeBuilder) assert.NoError(t, err) - if resp != 0.006 { - t.Errorf(errExpectMismatch, resp, 0.006) + if resp != WorstCaseMakerFee { + t.Errorf(errExpectMismatch, resp, WorstCaseMakerFee) } feeBuilder.Pair = currency.NewPair(currency.USDT, currency.USD) resp, err = c.GetFee(context.Background(), &feeBuilder) assert.NoError(t, err) if resp != 0 { - t.Errorf(errExpectMismatch, resp, 0) + t.Errorf(errExpectMismatch, resp, StablePairMakerFee) } feeBuilder.IsMaker = false resp, err = c.GetFee(context.Background(), &feeBuilder) assert.NoError(t, err) - if resp != 0.00001 { - t.Errorf(errExpectMismatch, resp, 0.00001) + if resp != WorstCaseStablePairTakerFee { + t.Errorf(errExpectMismatch, resp, WorstCaseStablePairTakerFee) } feeBuilder.FeeType = exchange.CryptocurrencyDepositFee _, err = c.GetFee(context.Background(), &feeBuilder) @@ -1061,14 +992,14 @@ func TestGetFee(t *testing.T) { feeBuilder.FeeType = exchange.CryptocurrencyTradeFee resp, err = c.GetFee(context.Background(), &feeBuilder) assert.NoError(t, err) - if !(resp <= 0.008 && resp >= 0.0005) { - t.Errorf(errExpectedFeeRange, 0.0005, 0.008, resp) + if !(resp <= WorstCaseTakerFee && resp >= BestCaseTakerFee) { + t.Errorf(errExpectedFeeRange, BestCaseTakerFee, WorstCaseTakerFee, resp) } feeBuilder.IsMaker = true resp, err = c.GetFee(context.Background(), &feeBuilder) assert.NoError(t, err) - if !(resp <= 0.006 && resp >= 0) { - t.Errorf(errExpectedFeeRange, 0, 0.006, resp) + if !(resp <= WorstCaseMakerFee && resp >= BestCaseMakerFee) { + t.Errorf(errExpectedFeeRange, BestCaseMakerFee, WorstCaseMakerFee, resp) } } @@ -1158,7 +1089,7 @@ func TestGetWithdrawalsHistory(t *testing.T) { _, err := c.GetWithdrawalsHistory(context.Background(), currency.NewCode("meow"), asset.Spot) assert.ErrorIs(t, err, errNoMatchingWallets) sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - _, err = c.GetWithdrawalsHistory(context.Background(), currency.BTC, asset.Spot) + _, err = c.GetWithdrawalsHistory(context.Background(), testCrypto, asset.Spot) assert.NoError(t, err) } @@ -1314,7 +1245,7 @@ func TestWithdrawCryptocurrencyFunds(t *testing.T) { _, err := c.WithdrawCryptocurrencyFunds(context.Background(), &req) assert.ErrorIs(t, err, common.ErrExchangeNameUnset) req.Exchange = c.Name - req.Currency = currency.BTC + req.Currency = testCrypto req.Amount = testAmount req.Type = withdraw.Crypto req.Crypto.Address = testAddress @@ -1327,7 +1258,7 @@ func TestWithdrawCryptocurrencyFunds(t *testing.T) { t.Fatal(errExpectedNonEmpty) } for i := range wallets.Data { - if wallets.Data[i].Currency.Name == currency.BTC.String() && wallets.Data[i].Balance.Amount > testAmount*100 { + if wallets.Data[i].Currency.Name == testCrypto.String() && wallets.Data[i].Balance.Amount > testAmount*100 { req.WalletID = wallets.Data[i].ID break } @@ -1341,55 +1272,11 @@ func TestWithdrawCryptocurrencyFunds(t *testing.T) { } func TestWithdrawFiatFunds(t *testing.T) { - req := withdraw.Request{} - _, err := c.WithdrawFiatFunds(context.Background(), &req) - assert.ErrorIs(t, err, common.ErrExchangeNameUnset) - req.Exchange = c.Name - req.Currency = currency.AUD - req.Amount = 1 - req.Type = withdraw.Fiat - req.Fiat.Bank.Enabled = true - req.Fiat.Bank.SupportedExchanges = "CoinbasePro" - req.Fiat.Bank.SupportedCurrencies = "AUD" - req.Fiat.Bank.AccountNumber = "123" - req.Fiat.Bank.SWIFTCode = "456" - req.Fiat.Bank.BSBNumber = "789" - _, err = c.WithdrawFiatFunds(context.Background(), &req) - assert.ErrorIs(t, err, errWalletIDEmpty) - req.WalletID = "meow" - req.Fiat.Bank.BankName = "GCT's Fake and Not Real Test Bank Meow Meow" - expectedError := fmt.Sprintf(errPayMethodNotFound, req.Fiat.Bank.BankName) - _, err = c.WithdrawFiatFunds(context.Background(), &req) - if err.Error() != expectedError { - t.Errorf(errExpectMismatch, err, expectedError) - } - sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) - wallets, err := c.GetAllWallets(context.Background(), PaginationInp{}) - assert.NoError(t, err) - if len(wallets.Data) == 0 { - t.Fatal(errExpectedNonEmpty) - } - req.WalletID = "" - for i := range wallets.Data { - if wallets.Data[i].Currency.Name == currency.AUD.String() && wallets.Data[i].Balance.Amount > testAmount*100 { - req.WalletID = wallets.Data[i].ID - break - } - } - if req.WalletID == "" { - t.Skip(skipInsufficientFunds) - } - req.Fiat.Bank.BankName = "AUD Wallet" - resp, err := c.WithdrawFiatFunds(context.Background(), &req) - assert.NoError(t, err) - assert.NotEmpty(t, resp, errExpectedNonEmpty) + withdrawFiatFundsHelper(t, c.WithdrawFiatFunds) } func TestWithdrawFiatFundsToInternationalBank(t *testing.T) { - req := withdraw.Request{} - _, err := c.WithdrawFiatFundsToInternationalBank(context.Background(), &req) - assert.ErrorIs(t, err, common.ErrExchangeNameUnset) - req.Exchange = c.Name + withdrawFiatFundsHelper(t, c.WithdrawFiatFundsToInternationalBank) } func TestGetFeeByType(t *testing.T) { @@ -1404,8 +1291,8 @@ func TestGetFeeByType(t *testing.T) { feeBuilder.PurchasePrice = 1 resp, err := c.GetFeeByType(context.Background(), &feeBuilder) assert.NoError(t, err) - if resp != 0.008 { - t.Errorf(errExpectMismatch, resp, 0.008) + if resp != WorstCaseTakerFee { + t.Errorf(errExpectMismatch, resp, WorstCaseTakerFee) } } @@ -1423,7 +1310,7 @@ func TestGetActiveOrders(t *testing.T) { req.Type = order.AnyType _, err = c.GetActiveOrders(context.Background(), &req) assert.NoError(t, err) - req.Pairs = req.Pairs.Add(currency.NewPair(currency.BTC, currency.USD)) + req.Pairs = req.Pairs.Add(currency.NewPair(testCrypto, testFiat)) _, err = c.GetActiveOrders(context.Background(), &req) assert.NoError(t, err) } @@ -1449,9 +1336,10 @@ func TestGetHistoricCandles(t *testing.T) { time.Time{}) assert.ErrorIs(t, err, currency.ErrCurrencyPairEmpty) sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - _, err = c.GetHistoricCandles(context.Background(), testPair, asset.Spot, kline.ThreeHour, + resp, err := c.GetHistoricCandles(context.Background(), testPair, asset.Spot, kline.ThreeHour, time.Now().Add(-time.Hour*30), time.Now()) assert.NoError(t, err) + assert.NotEmpty(t, resp, errExpectedNonEmpty) } func TestGetHistoricCandlesExtended(t *testing.T) { @@ -1486,8 +1374,9 @@ func TestGetLatestFundingRates(t *testing.T) { } sharedtestvalues.SkipTestIfCredentialsUnset(t, c) req.Asset = asset.Futures - _, err = c.GetLatestFundingRates(context.Background(), &req) + resp, err := c.GetLatestFundingRates(context.Background(), &req) assert.NoError(t, err) + assert.NotEmpty(t, resp, errExpectedNonEmpty) } func TestGetFuturesContractDetails(t *testing.T) { @@ -1498,8 +1387,9 @@ func TestGetFuturesContractDetails(t *testing.T) { t.Errorf(errExpectMismatch, err, errUpsideUnsupported) } sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - _, err = c.GetFuturesContractDetails(context.Background(), asset.Futures) + resp, err := c.GetFuturesContractDetails(context.Background(), asset.Futures) assert.NoError(t, err) + assert.NotEmpty(t, resp, errExpectedNonEmpty) } func TestUpdateOrderExecutionLimits(t *testing.T) { @@ -1551,33 +1441,19 @@ func TestUnixTimestampString(t *testing.T) { func TestFormatExchangeKlineInterval(t *testing.T) { t.Parallel() - resp := formatExchangeKlineInterval(kline.FiveMin) - if resp != granFiveMin { - t.Errorf(errExpectMismatch, resp, granFiveMin) - } - resp = formatExchangeKlineInterval(kline.FifteenMin) - if resp != granFifteenMin { - t.Errorf(errExpectMismatch, resp, granFifteenMin) - } - resp = formatExchangeKlineInterval(kline.ThirtyMin) - if resp != granThirtyMin { - t.Errorf(errExpectMismatch, resp, granThirtyMin) - } - resp = formatExchangeKlineInterval(kline.TwoHour) - if resp != granTwoHour { - t.Errorf(errExpectMismatch, resp, granTwoHour) - } - resp = formatExchangeKlineInterval(kline.SixHour) - if resp != granSixHour { - t.Errorf(errExpectMismatch, resp, granSixHour) - } - resp = formatExchangeKlineInterval(kline.OneDay) - if resp != granOneDay { - t.Errorf(errExpectMismatch, resp, granOneDay) - } - resp = formatExchangeKlineInterval(kline.OneWeek) - if resp != errIntervalNotSupported { - t.Errorf(errExpectMismatch, resp, errIntervalNotSupported) + testSequence := map[kline.Interval]string{ + kline.FiveMin: granFiveMin, + kline.FifteenMin: granFifteenMin, + kline.ThirtyMin: granThirtyMin, + kline.TwoHour: granTwoHour, + kline.SixHour: granSixHour, + kline.OneDay: granOneDay, + kline.OneWeek: errIntervalNotSupported} + for k := range testSequence { + resp := formatExchangeKlineInterval(k) + if resp != testSequence[k] { + t.Errorf(errExpectMismatch, resp, testSequence[k]) + } } } @@ -1599,12 +1475,9 @@ func TestWsConnect(t *testing.T) { t.Errorf(errExpectMismatch, err, stream.WebsocketNotEnabled) } c.Websocket.Enable() - // err = c.WsConnect() - // assert.NoError(t, err) } func TestWsHandleData(t *testing.T) { - // c.Websocket.DataHandler = make(chan interface{}, 100) go func() { for range c.Websocket.DataHandler { continue @@ -1737,7 +1610,7 @@ func TestGenerateDefaultSubscriptions(t *testing.T) { {Channel: "ticker_batch"}, {Channel: "candles"}, {Channel: "market_trades"}, {Channel: "level2"}, {Channel: "user"}} for i := range comparison { - comparison[i].Currency = currency.NewPairWithDelimiter(currency.BTC.String(), currency.USD.String(), "-") + comparison[i].Currency = currency.NewPairWithDelimiter(testCrypto.String(), testFiat.String(), "-") comparison[i].Asset = asset.Spot } resp, err := c.GenerateDefaultSubscriptions() @@ -1749,7 +1622,7 @@ func TestGenerateDefaultSubscriptions(t *testing.T) { func TestSubscribeUnsubscribe(t *testing.T) { req := []stream.ChannelSubscription{{Channel: "heartbeats", Asset: asset.Spot, - Currency: currency.NewPairWithDelimiter(currency.BTC.String(), currency.USD.String(), "-")}} + Currency: currency.NewPairWithDelimiter(testCrypto.String(), testFiat.String(), "-")}} err := c.Subscribe(req) assert.NoError(t, err) err = c.Unsubscribe(req) @@ -1877,3 +1750,79 @@ func transferTestHelper(t *testing.T, wallets GetAllWalletsResponse) (string, st } return wID, pmID.Data[0].FiatAccount.ID } + +type withdrawFiatFunc func(context.Context, *withdraw.Request) (*withdraw.ExchangeResponse, error) + +func withdrawFiatFundsHelper(t *testing.T, fn withdrawFiatFunc) { + req := withdraw.Request{} + _, err := fn(context.Background(), &req) + assert.ErrorIs(t, err, common.ErrExchangeNameUnset) + req.Exchange = c.Name + req.Currency = testFiat + req.Amount = 1 + req.Type = withdraw.Fiat + req.Fiat.Bank.Enabled = true + req.Fiat.Bank.SupportedExchanges = "CoinbasePro" + req.Fiat.Bank.SupportedCurrencies = testFiat.String() + req.Fiat.Bank.AccountNumber = "123" + req.Fiat.Bank.SWIFTCode = "456" + req.Fiat.Bank.BSBNumber = "789" + _, err = fn(context.Background(), &req) + assert.ErrorIs(t, err, errWalletIDEmpty) + req.WalletID = "meow" + req.Fiat.Bank.BankName = "GCT's Fake and Not Real Test Bank Meow Meow Meow" + expectedError := fmt.Sprintf(errPayMethodNotFound, req.Fiat.Bank.BankName) + _, err = fn(context.Background(), &req) + if err.Error() != expectedError { + t.Errorf(errExpectMismatch, err, expectedError) + } + sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) + wallets, err := c.GetAllWallets(context.Background(), PaginationInp{}) + assert.NoError(t, err) + if len(wallets.Data) == 0 { + t.Fatal(errExpectedNonEmpty) + } + req.WalletID = "" + for i := range wallets.Data { + if wallets.Data[i].Currency.Name == testFiat.String() && wallets.Data[i].Balance.Amount > testAmount*100 { + req.WalletID = wallets.Data[i].ID + break + } + } + if req.WalletID == "" { + t.Skip(skipInsufficientFunds) + } + req.Fiat.Bank.BankName = "AUD Wallet" + resp, err := fn(context.Background(), &req) + assert.NoError(t, err) + assert.NotEmpty(t, resp, errExpectedNonEmpty) +} + +type getNoArgsResp interface { + AllFuturesPositions | ServerTimeV3 | *UserResponse | AuthResponse | GetFiatCurrenciesResp | + GetCryptocurrenciesResp | ServerTimeV2 +} + +type getNoArgsAssertNotEmpty[G getNoArgsResp] func(context.Context) (G, error) + +func testGetNoArgs[G getNoArgsResp](t *testing.T, f getNoArgsAssertNotEmpty[G]) { + resp, err := f(context.Background()) + assert.NoError(t, err) + assert.NotEmpty(t, resp, errExpectedNonEmpty) +} + +type genConvertTestFunc func(context.Context, string, string, string) (ConvertResponse, error) + +func convertTestShared(t *testing.T, f genConvertTestFunc) { + _, err := f(context.Background(), "", "", "") + assert.ErrorIs(t, err, errTransactionIDEmpty) + _, err = f(context.Background(), "meow", "", "") + assert.ErrorIs(t, err, errAccountIDEmpty) + sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) + fromAccID, toAccID := convertTestHelper(t) + resp, err := c.CreateConvertQuote(context.Background(), fromAccID, toAccID, "", "", 0.01) + assert.NoError(t, err) + resp, err = f(context.Background(), resp.Trade.ID, fromAccID, toAccID) + assert.NoError(t, err) + assert.NotEmpty(t, resp, errExpectedNonEmpty) +} diff --git a/exchanges/coinbasepro/coinbasepro_types.go b/exchanges/coinbasepro/coinbasepro_types.go index 7208ca10cef..36b38cc2c33 100644 --- a/exchanges/coinbasepro/coinbasepro_types.go +++ b/exchanges/coinbasepro/coinbasepro_types.go @@ -26,7 +26,8 @@ type Version bool type FiatTransferType bool // ValCur is a sub-struct used in the types Account, NativeAndRaw, DetailedPortfolioResponse, -// FuturesBalanceSummary, ListFuturesSweepsResponse, FeeStruct, AmScale, and ConvertResponse +// FuturesBalanceSummary, ListFuturesSweepsResponse, PerpetualsPortfolioSummary, PerpPositionDetail, +// FeeStruct, AmScale, and ConvertResponse type ValCur struct { Value float64 `json:"value,string"` Currency string `json:"currency"` @@ -58,6 +59,11 @@ type AllAccountsResponse struct { Size uint8 `json:"size"` } +// Params is used within functions to make the setting of parameters easier +type Params struct { + urlVals url.Values +} + // OneAccountResponse is a temporary struct used for unmarshalling in GetAccountByID type OneAccountResponse struct { Account Account `json:"account"` @@ -159,7 +165,7 @@ type AllProducts struct { } // UnixTimestamp is a type used to unmarshal unix timestamps returned from -// the exchange, used in the type History +// the exchange, used in the types History and WebsocketCandle type UnixTimestamp time.Time // History holds historic rate information, returned by GetHistoricRates @@ -228,7 +234,7 @@ type StopLimitStopLimitGTD struct { StopDirection string `json:"stop_direction"` } -// OrderConfiguration is a struct used in the formation of requests for PlaceOrder, and is +// OrderConfiguration is a struct used in the formation of requests in PrepareOrderConfig, and is // a sub-struct used in the types PlaceOrderResp and GetOrderResponse type OrderConfiguration struct { MarketMarketIOC *MarketMarketIOC `json:"market_market_ioc,omitempty"` @@ -520,7 +526,7 @@ type ListFuturesSweepsResponse struct { } // PerpetualsPortfolioSummary contains information on perpetuals portfolio balances, used as -// a sub-struct in the types GetPerpetualsPortfolioSummary, AllPerpPosResponse, and +// a sub-struct in the types PerpetualPortResponse, PerpPositionDetail, AllPerpPosResponse, and // OnePerpPosResponse type PerpetualsPortfolioSummary struct { PortfolioUUID string `json:"portfolio_uuid"` @@ -729,6 +735,7 @@ type ConvertResponse struct { } `json:"trade"` } +// ServerTimeV3 holds information on the server's time, returned by GetV3Time type ServerTimeV3 struct { Iso time.Time `json:"iso"` EpochSeconds int64 `json:"epochSeconds,string"` @@ -736,7 +743,7 @@ type ServerTimeV3 struct { } // IDResource holds an ID, resource type, and associated data, used in ListNotificationsResponse, -// TransactionData, DeposWithdrData, PaymentMethodData, and PaymentMethod +// TransactionData, DeposWithdrData, and PaymentMethodData type IDResource struct { ID string `json:"id"` Resource string `json:"resource"` @@ -769,7 +776,7 @@ type PaginationInp struct { } // AmCur is a sub-struct used in ListNotificationsResponse, WalletData, TransactionData, -// DeposWithdrData, PaymentMethodData, LimitStruct, and PaymentMethod +// DeposWithdrData, and PaymentMethodData type AmCur struct { Amount float64 `json:"amount,string"` Currency string `json:"currency"` @@ -819,13 +826,13 @@ type ListNotificationsResponse struct { } `json:"data"` } -// CodeName is a sub-struct holding a code and a name, used in UserResponse and CoinbaseAccounts +// CodeName is a sub-struct holding a code and a name, used in UserResponse type CodeName struct { Code string `json:"code"` Name string `json:"name"` } -// UserResponse holds information on a user, returned by GetUserByID, GetCurrentUser, and UpdateUser +// UserResponse holds information on a user, returned by GetUserByID and GetCurrentUser type UserResponse struct { Data struct { ID string `json:"id"` @@ -914,8 +921,7 @@ type WalletData struct { AllowWithdrawals bool `json:"allow_withdrawals"` } -// GenWalletResponse holds information on a single wallet, returned by CreateWallet, -// GetWalletByID, and UpdateWalletName +// GenWalletResponse holds information on a single wallet, returned by GetWalletByID type GenWalletResponse struct { Data WalletData `json:"data"` } @@ -926,8 +932,7 @@ type GetAllWalletsResponse struct { Data []WalletData `json:"data"` } -// AddressInfo holds an address and a destination tag, used in AddressData, AddAddressRequest, -// AddAddressResponse, and CryptoAddressResponse +// AddressInfo holds an address and a destination tag, used in AddressData type AddressInfo struct { Address string `json:"address"` DestinationTag string `json:"destination_tag"` @@ -1161,8 +1166,8 @@ type ServerTimeV2 struct { } `json:"data"` } -// WebsocketSubscribe takes in subscription information -type WebsocketSubscribe struct { +// WebsocketRequest is an aspect of constructing a requst to the websocket server, used in sendRequest +type WebsocketRequest struct { Type string `json:"type"` ProductIDs []string `json:"product_ids,omitempty"` Channel string `json:"channel,omitempty"` @@ -1172,52 +1177,7 @@ type WebsocketSubscribe struct { JWT string `json:"jwt,omitempty"` } -// // WsChannels defines outgoing channels for subscription purposes -// type WsChannels struct { -// Name string `json:"name"` -// ProductIDs []string `json:"product_ids,omitempty"` -// } - -// wsOrderReceived holds websocket received values -// type wsOrderReceived struct { -// Type string `json:"type"` -// OrderID string `json:"order_id"` -// OrderType string `json:"order_type"` -// Size float64 `json:"size,string"` -// Price float64 `json:"price,omitempty,string"` -// Funds float64 `json:"funds,omitempty,string"` -// Side string `json:"side"` -// ClientOID string `json:"client_oid"` -// ProductID string `json:"product_id"` -// Sequence int64 `json:"sequence"` -// Time time.Time `json:"time"` -// RemainingSize float64 `json:"remaining_size,string"` -// NewSize float64 `json:"new_size,string"` -// OldSize float64 `json:"old_size,string"` -// Reason string `json:"reason"` -// Timestamp float64 `json:"timestamp,string"` -// UserID string `json:"user_id"` -// ProfileID string `json:"profile_id"` -// StopType string `json:"stop_type"` -// StopPrice float64 `json:"stop_price,string"` -// TakerFeeRate float64 `json:"taker_fee_rate,string"` -// Private bool `json:"private"` -// TradeID int64 `json:"trade_id"` -// MakerOrderID string `json:"maker_order_id"` -// TakerOrderID string `json:"taker_order_id"` -// TakerUserID string `json:"taker_user_id"` -// } - -// WebsocketHeartBeat defines JSON response for a heart beat message -// type WebsocketHeartBeat struct { -// Type string `json:"type"` -// Sequence int64 `json:"sequence"` -// LastTradeID int64 `json:"last_trade_id"` -// ProductID string `json:"product_id"` -// Time string `json:"time"` -// } - -// WebsocketTicker defines ticker websocket response +// WebsocketTicker defines a ticker websocket response, used in WebsocketTickerHolder type WebsocketTicker struct { Type string `json:"type"` ProductID currency.Pair `json:"product_id"` @@ -1230,13 +1190,13 @@ type WebsocketTicker struct { PricePercentageChange24H float64 `json:"price_percent_chg_24_h,string"` } -// WebsocketTickerHolder holds a variety of ticker responses +// WebsocketTickerHolder holds a variety of ticker responses, used when wsHandleData processes tickers type WebsocketTickerHolder struct { Type string `json:"type"` Tickers []WebsocketTicker `json:"tickers"` } -// WebsocketCandle defines a candle websocket response +// WebsocketCandle defines a candle websocket response, used in WebsocketCandleHolder type WebsocketCandle struct { Start UnixTimestamp `json:"start"` Low float64 `json:"low,string"` @@ -1247,13 +1207,13 @@ type WebsocketCandle struct { ProductID currency.Pair `json:"product_id"` } -// WebsocketCandleHolder holds a variety of candle responses +// WebsocketCandleHolder holds a variety of candle responses, used when wsHandleData processes candles type WebsocketCandleHolder struct { Type string `json:"type"` Candles []WebsocketCandle `json:"candles"` } -// WebsocketMarketTrade defines a market trade websocket response +// WebsocketMarketTrade defines a market trade websocket response, used in WebsocketMarketTradeHolder type WebsocketMarketTrade struct { TradeID string `json:"trade_id"` ProductID currency.Pair `json:"product_id"` @@ -1263,13 +1223,14 @@ type WebsocketMarketTrade struct { Time time.Time `json:"time"` } -// WebsocketMarketTradeHolder holds a variety of market trade responses +// WebsocketMarketTradeHolder holds a variety of market trade responses, used when wsHandleData +// processes trades type WebsocketMarketTradeHolder struct { Type string `json:"type"` Trades []WebsocketMarketTrade `json:"trades"` } -// WebsocketProduct defines a product websocket response +// WebsocketProduct defines a product websocket response, used in WebsocketProductHolder type WebsocketProduct struct { ProductType string `json:"product_type"` ID currency.Pair `json:"id"` @@ -1283,13 +1244,14 @@ type WebsocketProduct struct { MinMarketFunds float64 `json:"min_market_funds,string"` } -// WebsocketProductHolder holds a variety of product responses +// WebsocketProductHolder holds a variety of product responses, used when wsHandleData processes +// an update on a product's status type WebsocketProductHolder struct { Type string `json:"type"` Products []WebsocketProduct `json:"products"` } -// WebsocketOrderbookData defines a websocket orderbook response +// WebsocketOrderbookData defines a websocket orderbook response, used in WebsocketOrderbookDataHolder type WebsocketOrderbookData struct { Side string `json:"side"` EventTime time.Time `json:"event_time"` @@ -1297,14 +1259,15 @@ type WebsocketOrderbookData struct { NewQuantity float64 `json:"new_quantity,string"` } -// WebsocketOrderbookDataHolder holds a variety of orderbook responses +// WebsocketOrderbookDataHolder holds a variety of orderbook responses, used when wsHandleData processes +// orderbooks, as well as under typical operation of ProcessSnapshot, ProcessUpdate, and processBidAskArray type WebsocketOrderbookDataHolder struct { Type string `json:"type"` ProductID currency.Pair `json:"product_id"` Changes []WebsocketOrderbookData `json:"updates"` } -// WebsocketOrderData defines a websocket order response +// WebsocketOrderData defines a websocket order response, used in WebsocketOrderDataHolder type WebsocketOrderData struct { OrderID string `json:"order_id"` ClientOrderID string `json:"client_order_id"` @@ -1319,69 +1282,8 @@ type WebsocketOrderData struct { OrderType string `json:"order_type"` } -// WebsocketOrderDataHolder holds a variety of order responses +// WebsocketOrderDataHolder holds a variety of order responses, used when wsHandleData processes orders type WebsocketOrderDataHolder struct { Type string `json:"type"` Orders []WebsocketOrderData `json:"orders"` } - -// WebsocketOrderbookSnapshot defines a snapshot response -// type WebsocketOrderbookSnapshot struct { -// ProductID string `json:"product_id"` -// Type string `json:"type"` -// Bids [][2]string `json:"bids"` -// Asks [][2]string `json:"asks"` -// Time time.Time `json:"time"` -// } - -// WebsocketL2Update defines an update on the L2 orderbooks -// type WebsocketL2Update struct { -// Type string `json:"type"` -// ProductID string `json:"product_id"` -// Time time.Time `json:"time"` -// Changes [][3]string `json:"changes"` -// } - -// type wsGen struct { -// Channel string `json:"channel"` -// ClientID string `json:"client_id"` -// Timestamp time.Time `json:"timestamp"` -// SequenceNum uint64 `json:"sequence_num"` -// Events []interface{} `json:"events"` -// } - -// type wsStatus struct { -// Currencies []struct { -// ConvertibleTo []string `json:"convertible_to"` -// Details struct{} `json:"details"` -// ID string `json:"id"` -// MaxPrecision float64 `json:"max_precision,string"` -// MinSize float64 `json:"min_size,string"` -// Name string `json:"name"` -// Status string `json:"status"` -// StatusMessage interface{} `json:"status_message"` -// } `json:"currencies"` -// Products []struct { -// BaseCurrency string `json:"base_currency"` -// BaseIncrement float64 `json:"base_increment,string"` -// BaseMaxSize float64 `json:"base_max_size,string"` -// BaseMinSize float64 `json:"base_min_size,string"` -// CancelOnly bool `json:"cancel_only"` -// DisplayName string `json:"display_name"` -// ID string `json:"id"` -// LimitOnly bool `json:"limit_only"` -// MaxMarketFunds float64 `json:"max_market_funds,string"` -// MinMarketFunds float64 `json:"min_market_funds,string"` -// PostOnly bool `json:"post_only"` -// QuoteCurrency string `json:"quote_currency"` -// QuoteIncrement float64 `json:"quote_increment,string"` -// Status string `json:"status"` -// StatusMessage interface{} `json:"status_message"` -// } `json:"products"` -// Type string `json:"type"` -// } - -// Params is used within functions to make the setting of parameters easier -type Params struct { - urlVals url.Values -} diff --git a/exchanges/coinbasepro/coinbasepro_websocket.go b/exchanges/coinbasepro/coinbasepro_websocket.go index cd71f30aa0b..b6d8282975e 100644 --- a/exchanges/coinbasepro/coinbasepro_websocket.go +++ b/exchanges/coinbasepro/coinbasepro_websocket.go @@ -14,7 +14,6 @@ import ( _ "net/http/pprof" "strconv" "strings" - "sync" "time" "github.com/buger/jsonparser" @@ -81,82 +80,9 @@ func (c *CoinbasePro) wsReadData() { } } -var meow sync.Mutex -var count int -var wcTime time.Duration -var bruh bool - -func WOW(tThen time.Time, msg []byte) { - meow.Lock() - if !bruh { - bruh = true - go func() { - for { - select { - case <-time.After(time.Second * 5): - meow.Lock() - fmt.Printf("COINBASEPRO: %v\n", count) - count = 0 - wcTime = 0 - meow.Unlock() - } - } - }() - } - this := time.Since(tThen) - if wcTime == 0 || this > wcTime { - fmt.Printf("Uh-oh, we took %s to process this message\n", this) - wcTime = this - if this > time.Millisecond*50 { - fmt.Printf("Oh jeez, I think I found the big one! %s\n", msg) - } - } - count++ - meow.Unlock() -} - -var alreadyDone bool - -func launchProfiling() { - if alreadyDone { - return - } - alreadyDone = true - // go func() { - // http.ListenAndServe("localhost:6060", nil) - // }() - // fmt.Print("30 second pause, 1") - // time.Sleep(time.Second * 30) - // fmt.Print("5 second pause, 1") - // time.Sleep(time.Second * 5) -} - +// wsHandleData handles all the websocket data coming from the websocket connection func (c *CoinbasePro) wsHandleData(respRaw []byte, seqCount uint64) (string, error) { - // fmt.Println("WHADDUP:", string(respRaw)) - - // genData := wsGen{} var warnString string - // err := json.Unmarshal(respRaw, &genData) - // if err != nil { - // return warnString, err - // } - - // fmt.Printf("=== OH NOO LOOK AT THIS DATA WE HAVE TO DEAL WITH: %s ===\n", genData.Events) - - // data, _, _, err := jsonparser.Get(respRaw, "events") - // if err != nil { - // return err - // } - // specData := []WebsocketTickerHolder{} - // err = json.Unmarshal(data, &specData) - // if err != nil { - // return err - // } - // fmt.Printf("===== AWESOME, WE'VE GOT THE GOOD DATA: %v =====\n", specData) - - // if len(genData.Events) == 0 { - // return warnString, errNoEventsWS - // } seqData, _, _, err := jsonparser.Get(respRaw, "sequence_num") if err != nil { @@ -180,9 +106,6 @@ func (c *CoinbasePro) wsHandleData(respRaw []byte, seqCount uint64) (string, err channel := string(channelRaw) - tn := time.Now() - defer WOW(tn, channelRaw) - if channel == "subscriptions" || channel == "heartbeats" { return warnString, nil } @@ -191,7 +114,6 @@ func (c *CoinbasePro) wsHandleData(respRaw []byte, seqCount uint64) (string, err if err != nil { return warnString, err } - // fmt.Printf("==== WEEWOO WE'VE GOT THE NASTY DATA: %s ====\n", data) switch channel { case "status": @@ -235,7 +157,6 @@ func (c *CoinbasePro) wsHandleData(respRaw []byte, seqCount uint64) (string, err } } c.Websocket.DataHandler <- sliToSend - // fmt.Printf("=== WOOT, IT WORKED ===\n") case "candles": wsCandles := []WebsocketCandleHolder{} @@ -268,7 +189,6 @@ func (c *CoinbasePro) wsHandleData(respRaw []byte, seqCount uint64) (string, err } } c.Websocket.DataHandler <- sliToSend - // fmt.Print("=== RECEIVED AND PROCESSED ===\n") case "market_trades": wsTrades := []WebsocketMarketTradeHolder{} @@ -294,7 +214,6 @@ func (c *CoinbasePro) wsHandleData(respRaw []byte, seqCount uint64) (string, err } } c.Websocket.DataHandler <- sliToSend - // fmt.Print("=== RECEIVED AND PROCESSED ===\n") case "l2_data": var wsL2 []WebsocketOrderbookDataHolder err := json.Unmarshal(data, &wsL2) @@ -308,7 +227,6 @@ func (c *CoinbasePro) wsHandleData(respRaw []byte, seqCount uint64) (string, err } for i := range wsL2 { - // fmt.Printf("======== DATA THAT WE JUST HIT: %v ========\n", wsL2[i]) switch wsL2[i].Type { case "snapshot": err = c.ProcessSnapshot(wsL2[i], timestamp) @@ -397,8 +315,6 @@ func (c *CoinbasePro) ProcessSnapshot(snapshot WebsocketOrderbookDataHolder, tim // ProcessUpdate updates the orderbook local cache func (c *CoinbasePro) ProcessUpdate(update WebsocketOrderbookDataHolder, timestamp time.Time) error { - // fmt.Printf("====== DATA THAT WE'RE USING TO UPDATE: %v ======\n", update) - bids, asks, err := processBidAskArray(update) if err != nil { @@ -413,8 +329,6 @@ func (c *CoinbasePro) ProcessUpdate(update WebsocketOrderbookDataHolder, timesta Asset: asset.Spot, } - // fmt.Printf("===== WE'RE ABOUT TO UPDATE THE ORDERBOOK: %v =====\n", obU) - return c.Websocket.Orderbook.Update(&obU) } @@ -454,61 +368,8 @@ func (c *CoinbasePro) GenerateDefaultSubscriptions() ([]stream.ChannelSubscripti // Subscribe sends a websocket message to receive data from the channel func (c *CoinbasePro) Subscribe(channelsToSubscribe []stream.ChannelSubscription) error { - - launchProfiling() - - // fmt.Printf("SUBSCRIBE: %v\n", channelsToSubscribe) - // var creds *account.Credentials - // var err error - // if c.IsWebsocketAuthenticationSupported() { - // creds, err = c.GetCredentials(context.TODO()) - // if err != nil { - // return err - // } - // } - - // subscribe := WebsocketSubscribe{ - // Type: "subscribe", - // } - - // subscriptions: - // for i := range channelsToSubscribe { - // p := channelsToSubscribe[i].Currency.String() - // if !common.StringDataCompare(subscribe.ProductIDs, p) && p != "" { - // subscribe.ProductIDs = append(subscribe.ProductIDs, p) - // } - - // if subscribe.Channel == channelsToSubscribe[i].Channel { - // continue subscriptions - // } - - // subscribe.Channel = channelsToSubscribe[i].Channel - - // if (channelsToSubscribe[i].Channel == "user" || - // channelsToSubscribe[i].Channel == "full") && creds != nil { - // n := strconv.FormatInt(time.Now().Unix(), 10) - // message := n + http.MethodGet + "/users/self/verify" - // var hmac []byte - // hmac, err = crypto.GetHMAC(crypto.HashSHA256, - // []byte(message), - // []byte(creds.Secret)) - // if err != nil { - // return err - // } - // subscribe.Signature = crypto.Base64Encode(hmac) - // subscribe.Key = creds.Key - // subscribe.Timestamp = n - // } - // } - - // var ( - // rLim RateLimit - // ) - chanKeys := make(map[string]currency.Pairs) - // rLim.RateLimWS = request.NewRateLimit(coinbaseWSInterval, coinbaseWSRate) - for i := range channelsToSubscribe { chanKeys[channelsToSubscribe[i].Channel] = chanKeys[channelsToSubscribe[i].Channel].Add(channelsToSubscribe[i].Currency) @@ -639,30 +500,21 @@ func (c *CoinbasePro) sendRequest(msgType, channel string, productIDs currency.P return err } - // jwt, err := c.GetJWT(context.Background(), "") - // 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 := WebsocketSubscribe{ + req := WebsocketRequest{ Type: msgType, ProductIDs: productIDs.Strings(), Channel: channel, Signature: hex.EncodeToString(hmac), - // JWT: jwt, - Key: creds.Key, - Timestamp: n, + Key: creds.Key, + Timestamp: n, } - // reqMarshal, _ := json.Marshal(req) - - // fmt.Print(string(reqMarshal)) - - // err = rLim.Limit(context.Background(), WSRate) if err != nil { return err } - // data, err := c.Websocket.Conn.SendMessageReturnResponse(nil, req) err = c.Websocket.Conn.SendJSONMessage(req) if err != nil { return err diff --git a/exchanges/coinbasepro/coinbasepro_wrapper.go b/exchanges/coinbasepro/coinbasepro_wrapper.go index 445b67ba35f..643f4f44cc6 100644 --- a/exchanges/coinbasepro/coinbasepro_wrapper.go +++ b/exchanges/coinbasepro/coinbasepro_wrapper.go @@ -3,7 +3,6 @@ package coinbasepro import ( "context" "encoding/hex" - "errors" "fmt" "strconv" "sync" @@ -60,10 +59,8 @@ func (c *CoinbasePro) GetDefaultConfig(ctx context.Context) (*config.Exchange, e func (c *CoinbasePro) SetDefaults() { c.Name = "CoinbasePro" c.Enabled = true - c.Verbose = true c.API.CredentialsValidator.RequiresKey = true c.API.CredentialsValidator.RequiresSecret = true - // c.API.CredentialsValidator.RequiresClientID = true c.API.CredentialsValidator.RequiresBase64DecodeSecret = false requestFmt := ¤cy.PairFormat{Delimiter: currency.DashDelimiter, Uppercase: true} @@ -73,8 +70,6 @@ func (c *CoinbasePro) SetDefaults() { log.Errorln(log.ExchangeSys, err) } - // c.SetKeyChain(account.Primary, account.Secondary) - c.Features = exchange.Features{ Supports: exchange.FeaturesSupported{ REST: true, @@ -166,7 +161,6 @@ func (c *CoinbasePro) Setup(exch *config.Exchange) error { return err } if !exch.Enabled { - panic("BRUHS") c.SetEnabled(false) return nil } @@ -194,7 +188,6 @@ func (c *CoinbasePro) Setup(exch *config.Exchange) error { }, }) if err != nil { - fmt.Println("COINBASE ISSUE") return err } @@ -347,7 +340,6 @@ func (c *CoinbasePro) FetchAccountInfo(ctx context.Context, assetType asset.Item return account.Holdings{}, err } acc, err := account.GetHoldings(c.Name, creds, assetType) - fmt.Printf("Error: %v\n", err) if err != nil { return c.UpdateAccountInfo(ctx, assetType) } @@ -504,7 +496,7 @@ func (c *CoinbasePro) GetAccountFundingHistory(ctx context.Context) ([]exchange. return nil, err } if len(wallIDs.Data) == 0 { - return nil, errors.New("no wallets returned") + return nil, errNoWalletsReturned } var accHistory []DeposWithdrData @@ -549,7 +541,7 @@ func (c *CoinbasePro) GetWithdrawalsHistory(ctx context.Context, cur currency.Co return nil, err } if len(tempWallIDs.Data) == 0 { - return nil, errors.New("no wallets returned") + return nil, errNoWalletsReturned } var wallIDs []string @@ -1303,7 +1295,8 @@ func (c *CoinbasePro) cancelOrdersReturnMapAndCount(ctx context.Context, o []ord return status, counter, nil } -// processFundingData is a helper function for GetAccountFundingHistory and GetWithdrawalsHistory +// processFundingData is a helper function for GetAccountFundingHistory and GetWithdrawalsHistory, +// transforming the data returned by the Coinbase API into a format suitable for the exchange package func (c *CoinbasePro) processFundingData(accHistory []DeposWithdrData, cryptoHistory []TransactionData) []exchange.FundingHistory { fundingData := make([]exchange.FundingHistory, len(accHistory)+len(cryptoHistory)) for i := range accHistory { @@ -1386,6 +1379,7 @@ func formatExchangeKlineInterval(interval kline.Interval) string { } // getOrderRespToOrderDetail is a helper function used in GetOrderInfo, GetActiveOrders, and GetOrderHistory +// to convert data returned by the Coinbase API into a format suitable for the exchange package func (c *CoinbasePro) getOrderRespToOrderDetail(genOrderDetail *GetOrderResponse, pair currency.Pair, assetItem asset.Item) (*order.Detail, error) { var amount float64 var quoteAmount float64 From 2f05c05a1611092d0a7356779502956884ba0f75 Mon Sep 17 00:00:00 2001 From: Samuel Reid <43227667+cranktakular@users.noreply.github.com> Date: Tue, 20 Feb 2024 12:07:53 +1100 Subject: [PATCH 24/79] Linter progress --- exchanges/coinbasepro/coinbasepro.go | 61 ++++++++----- exchanges/coinbasepro/coinbasepro_test.go | 89 +++++++++++-------- exchanges/coinbasepro/coinbasepro_types.go | 6 +- .../coinbasepro/coinbasepro_websocket.go | 15 ++-- exchanges/coinbasepro/coinbasepro_wrapper.go | 6 +- exchanges/coinbasepro/ratelimit.go | 1 + exchanges/order/order_types.go | 2 + exchanges/request/retry.go | 1 + exchanges/request/retry_test.go | 2 +- 9 files changed, 106 insertions(+), 77 deletions(-) diff --git a/exchanges/coinbasepro/coinbasepro.go b/exchanges/coinbasepro/coinbasepro.go index 1fa4b3179a2..26bb9a1f6ae 100644 --- a/exchanges/coinbasepro/coinbasepro.go +++ b/exchanges/coinbasepro/coinbasepro.go @@ -71,23 +71,21 @@ const ( coinbasePrices = "prices" coinbaseTime = "time" - FiatDeposit FiatTransferType = false - FiatWithdrawal FiatTransferType = true - pageNone = "" - pageBefore = "before" - pageAfter = "after" - unknownContract = "UNKNOWN_CONTRACT_EXPIRY_TYPE" - granUnknown = "UNKNOWN_GRANULARITY" - granOneMin = "ONE_MINUTE" - granFiveMin = "FIVE_MINUTE" - granFifteenMin = "FIFTEEN_MINUTE" - granThirtyMin = "THIRTY_MINUTE" - granOneHour = "ONE_HOUR" - granTwoHour = "TWO_HOUR" - granSixHour = "SIX_HOUR" - granOneDay = "ONE_DAY" - startDateString = "start_date" - endDateString = "end_date" + pageNone = "" + pageBefore = "before" + pageAfter = "after" + unknownContract = "UNKNOWN_CONTRACT_EXPIRY_TYPE" + granUnknown = "UNKNOWN_GRANULARITY" + granOneMin = "ONE_MINUTE" + granFiveMin = "FIVE_MINUTE" + granFifteenMin = "FIFTEEN_MINUTE" + granThirtyMin = "THIRTY_MINUTE" + granOneHour = "ONE_HOUR" + granTwoHour = "TWO_HOUR" + granSixHour = "SIX_HOUR" + granOneDay = "ONE_DAY" + startDateString = "start_date" + endDateString = "end_date" errPayMethodNotFound = "payment method '%v' not found" errIntervalNotSupported = "interval not supported" @@ -95,10 +93,19 @@ const ( errUnknownL2DataType = "unknown l2update data type %v" errUnknownSide = "unknown side %v" warnSequenceIssue = "Out of order sequence number. Received %v, expected %v" +) + +// Constants defining whether a transfer is a deposit or withdrawal, used to simplify +// interactions with a few endpoints +const ( + FiatDeposit FiatTransferType = false + FiatWithdrawal FiatTransferType = true +) - // While the exchange's fee pages say the worst taker/maker fees are 0.002 lower than the ones listed - // here, the data returned by the GetTransactionsSummary endpoint are consistent with these worst - // case scenarios. The best case scenarios are untested, and assumed to be in line with the fee pages +// While the exchange's fee pages say the worst taker/maker fees are 0.002 lower than the ones listed +// here, the data returned by the GetTransactionsSummary endpoint are consistent with these worst +// case scenarios. The best case scenarios are untested, and assumed to be in line with the fee pages +const ( WorstCaseTakerFee = 0.008 WorstCaseMakerFee = 0.006 BestCaseTakerFee = 0.0005 @@ -129,7 +136,7 @@ var ( errInvalidPriceType = errors.New("price type must be spot, buy, or sell") errInvalidOrderType = errors.New("order type must be market, limit, or stop") errNoMatchingWallets = errors.New("no matching wallets returned") - errOrderModFailNoErr = errors.New("order modification failed but no error returned") + errOrderModFailNoRet = errors.New("order modification failed but no error returned") errNoMatchingOrders = errors.New("no matching orders returned") errPointerNil = errors.New("relevant pointer is nil") errNameEmpty = errors.New("name cannot be empty") @@ -220,6 +227,10 @@ func (c *CoinbasePro) GetAllProducts(ctx context.Context, limit, offset int32, p params.urlVals.Set("contract_expiry_type", contractExpiryType) } + if expiringContractStatus != "" { + params.urlVals.Set("expiring_contract_status", expiringContractStatus) + } + if len(productIDs) > 0 { for x := range productIDs { params.urlVals.Add("product_ids", productIDs[x]) @@ -1335,7 +1346,7 @@ func (c *CoinbasePro) SendAuthenticatedHTTPRequest(ctx context.Context, ep excha // Version 2 wants query params in the path during signing if !isVersion3 { - path = path + queryParams + path += queryParams } interim := json.RawMessage{} @@ -1370,7 +1381,7 @@ func (c *CoinbasePro) SendAuthenticatedHTTPRequest(ctx context.Context, ep excha // Version 3 only wants query params in the path when the request is sent if isVersion3 { - path = path + queryParams + path += queryParams } return &request.Item{ @@ -1596,6 +1607,7 @@ func prepareMarginType(marginType string, req map[string]interface{}) { } } +// String implements the stringer interface func (f FiatTransferType) String() string { if f { return "withdrawal" @@ -1603,6 +1615,7 @@ func (f FiatTransferType) String() string { return "deposit" } +// UnmarshalJSON unmarshals the JSON input into a UnixTimestamp type func (t *UnixTimestamp) UnmarshalJSON(b []byte) error { var timestampStr string err := json.Unmarshal(b, ×tampStr) @@ -1617,10 +1630,12 @@ func (t *UnixTimestamp) UnmarshalJSON(b []byte) error { return nil } +// String implements the stringer interface func (t *UnixTimestamp) String() string { return t.Time().String() } +// Time returns the time.Time representation of the UnixTimestamp 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 d4b4f93729f..50498e1d874 100644 --- a/exchanges/coinbasepro/coinbasepro_test.go +++ b/exchanges/coinbasepro/coinbasepro_test.go @@ -2,13 +2,12 @@ package coinbasepro import ( "context" - "net/http" - "strconv" - "strings" - "fmt" "log" + "net/http" "os" + "strconv" + "strings" "testing" "time" @@ -74,12 +73,12 @@ const ( errUpsideUnsupported = "unsupported asset type upsideprofitcontract" errBlorboGranularity = "invalid granularity blorbo, allowed granularities are: [ONE_MINUTE FIVE_MINUTE FIFTEEN_MINUTE THIRTY_MINUTE ONE_HOUR TWO_HOUR SIX_HOUR ONE_DAY]" errNoEndpointPathEdgeCase3 = "no endpoint path found for the given key: EdgeCase3URL" - errJsonUnsupportedChan = "json: unsupported type: chan struct {}, authenticated request failed" + errJSONUnsupportedChan = "json: unsupported type: chan struct {}, authenticated request failed" errExpectedFeeRange = "expected fee range of %v and %v, received %v" - errJsonNumberIntoString = "json: cannot unmarshal number into Go value of type string" + errJSONNumberIntoString = "json: cannot unmarshal number into Go value of type string" errParseIntValueOutOfRange = `strconv.ParseInt: parsing "922337203685477580700": value out of range` errParseUintInvalidSyntax = `strconv.ParseUint: parsing "l": invalid syntax` - errJsonInvalidCharacter = `invalid character ':' after array element` + errJSONInvalidCharacter = `invalid character ':' after array element` errL2DataMoo = "unknown l2update data type moo" errUnrecognisedOrderType = `'' unrecognised order type` errOrderSideInvalid = `'' order side is invalid` @@ -110,9 +109,10 @@ func TestSetup(t *testing.T) { func TestMain(m *testing.M) { c.SetDefaults() if testingInSandbox { - c.API.Endpoints.SetDefaultEndpoints(map[exchange.URL]string{ + err := c.API.Endpoints.SetDefaultEndpoints(map[exchange.URL]string{ exchange.RestSpot: coinbaseproSandboxAPIURL, }) + log.Fatal("failed to set sandbox endpoint", err) } cfg := config.GetConfig() err := cfg.LoadConfig("../../testdata/configtest.json", true) @@ -188,7 +188,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", "", + resp, err := c.GetAllProducts(context.Background(), 30000, 0, "SPOT", "PERPETUAL", "STATUS_ALL", testPairs) assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) @@ -516,7 +516,7 @@ func TestAllocatePortfolio(t *testing.T) { err = c.AllocatePortfolio(context.Background(), "meow", "bark", "", 0) assert.ErrorIs(t, err, errCurrencyEmpty) sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) - pID := portfolioIDFromType(t, "INTX") + pID := getINTXPortfolio(t) err = c.AllocatePortfolio(context.Background(), pID, testCrypto.String(), "USD", 0.001337) assert.NoError(t, err) } @@ -524,7 +524,7 @@ func TestAllocatePortfolio(t *testing.T) { func TestGetPerpetualsPortfolioSummary(t *testing.T) { _, err := c.GetPerpetualsPortfolioSummary(context.Background(), "") assert.ErrorIs(t, err, errPortfolioIDEmpty) - pID := portfolioIDFromType(t, "INTX") + pID := getINTXPortfolio(t) resp, err := c.GetPerpetualsPortfolioSummary(context.Background(), pID) assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) @@ -533,7 +533,7 @@ func TestGetPerpetualsPortfolioSummary(t *testing.T) { func TestGetAllPerpetualsPositions(t *testing.T) { _, err := c.GetAllPerpetualsPositions(context.Background(), "") assert.ErrorIs(t, err, errPortfolioIDEmpty) - pID := portfolioIDFromType(t, "INTX") + pID := getINTXPortfolio(t) _, err = c.GetAllPerpetualsPositions(context.Background(), pID) assert.NoError(t, err) } @@ -543,7 +543,7 @@ func TestGetPerpetualsPositionByID(t *testing.T) { assert.ErrorIs(t, err, errPortfolioIDEmpty) _, err = c.GetPerpetualsPositionByID(context.Background(), "meow", "") assert.ErrorIs(t, err, errProductIDEmpty) - pID := portfolioIDFromType(t, "INTX") + pID := getINTXPortfolio(t) _, err = c.GetPerpetualsPositionByID(context.Background(), pID, testPair.String()) assert.NoError(t, err) } @@ -933,8 +933,8 @@ func TestSendAuthenticatedHTTPRequest(t *testing.T) { ch := make(chan struct{}) body := map[string]interface{}{"Unmarshalable": ch} err = c.SendAuthenticatedHTTPRequest(context.Background(), exchange.RestSpot, "", "", "", body, false, nil, nil) - if err.Error() != errJsonUnsupportedChan { - t.Errorf(errExpectMismatch, err, errJsonUnsupportedChan) + if err.Error() != errJSONUnsupportedChan { + t.Errorf(errExpectMismatch, err, errJSONUnsupportedChan) } } @@ -1405,8 +1405,8 @@ func TestUnixTimestampUnmarshalJSON(t *testing.T) { t.Parallel() var u UnixTimestamp err := u.UnmarshalJSON([]byte("0")) - if err.Error() != errJsonNumberIntoString { - t.Errorf(errExpectMismatch, err, errJsonNumberIntoString) + if err.Error() != errJSONNumberIntoString { + t.Errorf(errExpectMismatch, err, errJSONNumberIntoString) } err = u.UnmarshalJSON([]byte("\"922337203685477580700\"")) if err.Error() != errParseIntValueOutOfRange { @@ -1419,7 +1419,8 @@ func TestUnixTimestampUnmarshalJSON(t *testing.T) { func TestUnixTimestampString(t *testing.T) { t.Parallel() var u UnixTimestamp - u.UnmarshalJSON([]byte("\"1234\"")) + err := u.UnmarshalJSON([]byte("\"1234\"")) + assert.NoError(t, err) s := u.String() if s != expectedTimestamp { t.Errorf(errExpectMismatch, s, expectedTimestamp) @@ -1508,12 +1509,14 @@ func TestStringToFloatPtr(t *testing.T) { } func TestWsConnect(t *testing.T) { - c.Websocket.Disable() - err := c.WsConnect() + err := c.Websocket.Disable() + assert.NoError(t, err) + err = c.WsConnect() if err.Error() != stream.WebsocketNotEnabled { t.Errorf(errExpectMismatch, err, stream.WebsocketNotEnabled) } - c.Websocket.Enable() + err = c.Websocket.Enable() + assert.NoError(t, err) } func TestWsHandleData(t *testing.T) { @@ -1540,8 +1543,8 @@ func TestWsHandleData(t *testing.T) { assert.ErrorIs(t, err, jsonparser.UnknownValueTypeError) mockJson = []byte(`{"sequence_num": 0, "channel": "status", "events": ["type": 1234]}`) _, err = c.wsHandleData(mockJson, 0) - if err.Error() != errJsonInvalidCharacter { - t.Errorf(errExpectMismatch, err, errJsonInvalidCharacter) + if err.Error() != errJSONInvalidCharacter { + t.Errorf(errExpectMismatch, err, errJSONInvalidCharacter) } mockJson = []byte(`{"sequence_num": 0, "channel": "status", "events": [{"type": "moo"}]}`) _, err = c.wsHandleData(mockJson, 0) @@ -1551,8 +1554,8 @@ func TestWsHandleData(t *testing.T) { assert.NoError(t, err) mockJson = []byte(`{"sequence_num": 0, "channel": "ticker", "events": ["type": ""}]}`) _, err = c.wsHandleData(mockJson, 0) - if err.Error() != errJsonInvalidCharacter { - t.Errorf(errExpectMismatch, err, errJsonInvalidCharacter) + if err.Error() != errJSONInvalidCharacter { + t.Errorf(errExpectMismatch, err, errJSONInvalidCharacter) } mockJson = []byte(`{"sequence_num": 0, "channel": "ticker", "events": [{"type": "moo", "tickers": [{"price": "1.1"}]}]}`) _, err = c.wsHandleData(mockJson, 0) @@ -1562,8 +1565,8 @@ func TestWsHandleData(t *testing.T) { assert.NoError(t, err) mockJson = []byte(`{"sequence_num": 0, "channel": "candles", "events": ["type": ""}]}`) _, err = c.wsHandleData(mockJson, 0) - if err.Error() != errJsonInvalidCharacter { - t.Errorf(errExpectMismatch, err, errJsonInvalidCharacter) + if err.Error() != errJSONInvalidCharacter { + t.Errorf(errExpectMismatch, err, errJSONInvalidCharacter) } mockJson = []byte(`{"sequence_num": 0, "channel": "candles", "events": [{"type": "moo", "candles": [{"low": "1.1"}]}]}`) _, err = c.wsHandleData(mockJson, 0) @@ -1573,16 +1576,16 @@ func TestWsHandleData(t *testing.T) { assert.NoError(t, err) mockJson = []byte(`{"sequence_num": 0, "channel": "market_trades", "events": ["type": ""}]}`) _, err = c.wsHandleData(mockJson, 0) - if err.Error() != errJsonInvalidCharacter { - t.Errorf(errExpectMismatch, err, errJsonInvalidCharacter) + if err.Error() != errJSONInvalidCharacter { + t.Errorf(errExpectMismatch, err, errJSONInvalidCharacter) } mockJson = []byte(`{"sequence_num": 0, "channel": "market_trades", "events": [{"type": "moo", "trades": [{"price": "1.1"}]}]}`) _, err = c.wsHandleData(mockJson, 0) assert.NoError(t, err) mockJson = []byte(`{"sequence_num": 0, "channel": "l2_data", "events": ["type": ""}]}`) _, err = c.wsHandleData(mockJson, 0) - if err.Error() != errJsonInvalidCharacter { - t.Errorf(errExpectMismatch, err, errJsonInvalidCharacter) + if err.Error() != errJSONInvalidCharacter { + t.Errorf(errExpectMismatch, err, errJSONInvalidCharacter) } mockJson = []byte(`{"sequence_num": 0, "channel": "l2_data", "events": [{"type": "moo", "updates": [{"price_level": "1.1"}]}]}`) _, err = c.wsHandleData(mockJson, 0) @@ -1600,8 +1603,8 @@ func TestWsHandleData(t *testing.T) { assert.NoError(t, err) mockJson = []byte(`{"sequence_num": 0, "channel": "user", "events": ["type": ""}]}`) _, err = c.wsHandleData(mockJson, 0) - if err.Error() != errJsonInvalidCharacter { - t.Errorf(errExpectMismatch, err, errJsonInvalidCharacter) + if err.Error() != errJSONInvalidCharacter { + t.Errorf(errExpectMismatch, err, errJSONInvalidCharacter) } mockJson = []byte(`{"sequence_num": 0, "channel": "user", "events": [{"type": "moo", "orders": [{"total_fees": "1.1"}]}]}`) _, err = c.wsHandleData(mockJson, 0) @@ -1629,18 +1632,18 @@ func TestWsHandleData(t *testing.T) { func TestProcessSnapshotUpdate(t *testing.T) { req := WebsocketOrderbookDataHolder{Changes: []WebsocketOrderbookData{{Side: "fakeside", PriceLevel: 1.1, NewQuantity: 2.2}}, ProductID: currency.NewBTCUSD()} - err := c.ProcessSnapshot(req, time.Time{}) + err := c.ProcessSnapshot(&req, time.Time{}) if err.Error() != errFakeSide { t.Errorf(errExpectMismatch, err, errFakeSide) } - err = c.ProcessUpdate(req, time.Time{}) + err = c.ProcessUpdate(&req, time.Time{}) if err.Error() != errFakeSide { t.Errorf(errExpectMismatch, err, errFakeSide) } req.Changes[0].Side = "offer" - err = c.ProcessSnapshot(req, time.Now()) + err = c.ProcessSnapshot(&req, time.Now()) assert.NoError(t, err) - err = c.ProcessUpdate(req, time.Now()) + err = c.ProcessUpdate(&req, time.Now()) assert.NoError(t, err) } @@ -1682,6 +1685,7 @@ func TestGetJWT(t *testing.T) { } func skipTestIfLowOnFunds(t *testing.T) { + t.Helper() accounts, err := c.GetAllAccounts(context.Background(), 250, "") assert.NoError(t, err) if len(accounts.Accounts) == 0 { @@ -1699,6 +1703,7 @@ func skipTestIfLowOnFunds(t *testing.T) { } func portfolioIDFromName(t *testing.T, targetName string) string { + t.Helper() sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) createResp, err := c.CreatePortfolio(context.Background(), targetName) var targetID string @@ -1725,7 +1730,8 @@ func portfolioIDFromName(t *testing.T, targetName string) string { return targetID } -func portfolioIDFromType(t *testing.T, targetType string) string { +func getINTXPortfolio(t *testing.T) string { + t.Helper() sharedtestvalues.SkipTestIfCredentialsUnset(t, c) resp, err := c.GetAllPortfolios(context.Background(), "") assert.NoError(t, err) @@ -1734,7 +1740,7 @@ func portfolioIDFromType(t *testing.T, targetType string) string { } var targetID string for i := range resp.Portfolios { - if resp.Portfolios[i].Type == targetType { + if resp.Portfolios[i].Type == "INTX" { targetID = resp.Portfolios[i].UUID break } @@ -1746,6 +1752,7 @@ func portfolioIDFromType(t *testing.T, targetType string) string { } func convertTestHelper(t *testing.T) (string, string) { + t.Helper() accIDs, err := c.GetAllAccounts(context.Background(), 250, "") assert.NoError(t, err) if len(accIDs.Accounts) == 0 { @@ -1773,6 +1780,7 @@ func convertTestHelper(t *testing.T) (string, string) { } func transferTestHelper(t *testing.T, wallets GetAllWalletsResponse) (string, string) { + t.Helper() var hasValidFunds bool var wID string for i := range wallets.Data { @@ -1795,6 +1803,7 @@ func transferTestHelper(t *testing.T, wallets GetAllWalletsResponse) (string, st type withdrawFiatFunc func(context.Context, *withdraw.Request) (*withdraw.ExchangeResponse, error) func withdrawFiatFundsHelper(t *testing.T, fn withdrawFiatFunc) { + t.Helper() req := withdraw.Request{} _, err := fn(context.Background(), &req) assert.ErrorIs(t, err, common.ErrExchangeNameUnset) @@ -1848,6 +1857,7 @@ type getNoArgsResp interface { type getNoArgsAssertNotEmpty[G getNoArgsResp] func(context.Context) (G, error) func testGetNoArgs[G getNoArgsResp](t *testing.T, f getNoArgsAssertNotEmpty[G]) { + t.Helper() resp, err := f(context.Background()) assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) @@ -1856,6 +1866,7 @@ func testGetNoArgs[G getNoArgsResp](t *testing.T, f getNoArgsAssertNotEmpty[G]) type genConvertTestFunc func(context.Context, string, string, string) (ConvertResponse, error) func convertTestShared(t *testing.T, f genConvertTestFunc) { + t.Helper() _, err := f(context.Background(), "", "", "") assert.ErrorIs(t, err, errTransactionIDEmpty) _, err = f(context.Background(), "meow", "", "") diff --git a/exchanges/coinbasepro/coinbasepro_types.go b/exchanges/coinbasepro/coinbasepro_types.go index 36b38cc2c33..951b59c98ee 100644 --- a/exchanges/coinbasepro/coinbasepro_types.go +++ b/exchanges/coinbasepro/coinbasepro_types.go @@ -583,7 +583,7 @@ type PerpPositionDetail struct { PortfolioSummary PerpetualsPortfolioSummary `json:"portfolio_summary"` } -// PerpetualPositionsResponse contains information on perpetuals positions, returned by +// AllPerpPosResponse contains information on perpetuals positions, returned by // GetAllPerpetualsPositions type AllPerpPosResponse struct { Positions []PerpPositionDetail `json:"positions"` @@ -928,8 +928,8 @@ type GenWalletResponse struct { // GetAllWalletsResponse holds information on many wallets, returned by GetAllWallets type GetAllWalletsResponse struct { - Pagination PaginationResp `json:"pagination"` - Data []WalletData `json:"data"` + Pagination *PaginationResp `json:"pagination"` + Data []WalletData `json:"data"` } // AddressInfo holds an address and a destination tag, used in AddressData diff --git a/exchanges/coinbasepro/coinbasepro_websocket.go b/exchanges/coinbasepro/coinbasepro_websocket.go index 0d80d565d7f..1fd8faaea9d 100644 --- a/exchanges/coinbasepro/coinbasepro_websocket.go +++ b/exchanges/coinbasepro/coinbasepro_websocket.go @@ -11,7 +11,6 @@ import ( "encoding/pem" "fmt" "net/http" - _ "net/http/pprof" "strconv" "strings" "time" @@ -230,9 +229,9 @@ func (c *CoinbasePro) wsHandleData(respRaw []byte, seqCount uint64) (string, err for i := range wsL2 { switch wsL2[i].Type { case "snapshot": - err = c.ProcessSnapshot(wsL2[i], timestamp) + err = c.ProcessSnapshot(&wsL2[i], timestamp) case "update": - err = c.ProcessUpdate(wsL2[i], timestamp) + err = c.ProcessUpdate(&wsL2[i], timestamp) default: err = errors.Errorf(errUnknownL2DataType, wsL2[i].Type) } @@ -299,7 +298,7 @@ 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 { +func (c *CoinbasePro) ProcessSnapshot(snapshot *WebsocketOrderbookDataHolder, timestamp time.Time) error { bids, asks, err := processBidAskArray(snapshot) if err != nil { @@ -318,7 +317,7 @@ func (c *CoinbasePro) ProcessSnapshot(snapshot WebsocketOrderbookDataHolder, tim } // ProcessUpdate updates the orderbook local cache -func (c *CoinbasePro) ProcessUpdate(update WebsocketOrderbookDataHolder, timestamp time.Time) error { +func (c *CoinbasePro) ProcessUpdate(update *WebsocketOrderbookDataHolder, timestamp time.Time) error { bids, asks, err := processBidAskArray(update) if err != nil { @@ -442,11 +441,11 @@ func (c *CoinbasePro) GetJWT(ctx context.Context, uri string) (string, error) { } head := map[string]interface{}{"kid": creds.ClientID, "typ": "JWT", "alg": "ES256", "nonce": nonce} - headJson, err := json.Marshal(head) + headJSON, err := json.Marshal(head) if err != nil { return "", err } - headEncode := base64URLEncode(headJson) + headEncode := base64URLEncode(headJSON) c.jwtLastRegen = time.Now() @@ -528,7 +527,7 @@ func (c *CoinbasePro) sendRequest(msgType, channel string, productIDs currency.P // processBidAskArray is a helper function that turns WebsocketOrderbookDataHolder into arrays // of bids and asks -func processBidAskArray(data WebsocketOrderbookDataHolder) ([]orderbook.Item, []orderbook.Item, error) { +func processBidAskArray(data *WebsocketOrderbookDataHolder) ([]orderbook.Item, []orderbook.Item, error) { var bids, asks []orderbook.Item for i := range data.Changes { switch data.Changes[i].Side { diff --git a/exchanges/coinbasepro/coinbasepro_wrapper.go b/exchanges/coinbasepro/coinbasepro_wrapper.go index 1c7b4556a76..2c7bcb8b5e9 100644 --- a/exchanges/coinbasepro/coinbasepro_wrapper.go +++ b/exchanges/coinbasepro/coinbasepro_wrapper.go @@ -570,12 +570,12 @@ func (c *CoinbasePro) GetWithdrawalsHistory(ctx context.Context, cur currency.Co } // GetRecentTrades returns the most recent trades for a currency and asset -func (c *CoinbasePro) GetRecentTrades(ctx context.Context, p currency.Pair, assetType asset.Item) ([]trade.Data, error) { +func (c *CoinbasePro) GetRecentTrades(_ context.Context, _ currency.Pair, _ asset.Item) ([]trade.Data, error) { return nil, common.ErrFunctionNotSupported } // GetHistoricTrades returns historic trade data within the timeframe provided -func (c *CoinbasePro) GetHistoricTrades(ctx context.Context, p currency.Pair, assetType asset.Item, startDate, endDate time.Time) ([]trade.Data, error) { +func (c *CoinbasePro) GetHistoricTrades(_ context.Context, _ currency.Pair, _ asset.Item, _, _ time.Time) ([]trade.Data, error) { return nil, common.ErrFunctionNotSupported } @@ -649,7 +649,7 @@ func (c *CoinbasePro) ModifyOrder(ctx context.Context, m *order.Modify) (*order. return nil, err } if !success.Success { - return nil, errOrderModFailNoErr + return nil, errOrderModFailNoRet } return m.DeriveModifyResponse() diff --git a/exchanges/coinbasepro/ratelimit.go b/exchanges/coinbasepro/ratelimit.go index 916a6337d84..38181d951ff 100644 --- a/exchanges/coinbasepro/ratelimit.go +++ b/exchanges/coinbasepro/ratelimit.go @@ -21,6 +21,7 @@ const ( coinbaseWSRate = 750 ) +// Coinbase pro rate limits const ( V2Rate request.EndpointLimit = iota V3Rate diff --git a/exchanges/order/order_types.go b/exchanges/order/order_types.go index 560abfbbe9c..d2688af87b8 100644 --- a/exchanges/order/order_types.go +++ b/exchanges/order/order_types.go @@ -362,6 +362,7 @@ const ( ConditionalStop // One-way stop order ) +// AllOrderTypes collects all order types for easy and consistent comparisons var AllOrderTypes = Limit | Market | PostOnly | @@ -440,6 +441,7 @@ type FilteredOrders []Detail // last trade price goes below the TriggerPrice type StopDirection bool +// StopDirection types const ( StopUp StopDirection = true StopDown StopDirection = false diff --git a/exchanges/request/retry.go b/exchanges/request/retry.go index 60fe1149d15..b851fed86d8 100644 --- a/exchanges/request/retry.go +++ b/exchanges/request/retry.go @@ -21,6 +21,7 @@ func DefaultRetryPolicy(resp *http.Response, err error) (bool, error) { } if resp.StatusCode == http.StatusTooManyRequests { + // If we've sent too many requests, we shouldn't immediately retry. return false, nil } diff --git a/exchanges/request/retry_test.go b/exchanges/request/retry_test.go index 6e3f4a6d3ae..df5bd9964a0 100644 --- a/exchanges/request/retry_test.go +++ b/exchanges/request/retry_test.go @@ -34,7 +34,7 @@ func TestDefaultRetryPolicy(t *testing.T) { }, "Too Many Requests": { Args: args{Response: &http.Response{StatusCode: http.StatusTooManyRequests}}, - Want: want{Retry: true}, + Want: want{Retry: false}, }, "Not Found": { Args: args{Response: &http.Response{StatusCode: http.StatusNotFound}}, From 005af4a52c10d4d6473db652219f20f51c3f6642 Mon Sep 17 00:00:00 2001 From: Samuel Reid <43227667+cranktakular@users.noreply.github.com> Date: Tue, 20 Feb 2024 13:08:52 +1100 Subject: [PATCH 25/79] Continued lint cleanup --- exchanges/coinbasepro/coinbasepro.go | 14 +- exchanges/coinbasepro/coinbasepro_test.go | 171 +++++++++--------- exchanges/coinbasepro/coinbasepro_types.go | 4 +- .../coinbasepro/coinbasepro_websocket.go | 12 +- exchanges/coinbasepro/coinbasepro_wrapper.go | 28 +-- 5 files changed, 116 insertions(+), 113 deletions(-) diff --git a/exchanges/coinbasepro/coinbasepro.go b/exchanges/coinbasepro/coinbasepro.go index 26bb9a1f6ae..785ced020f1 100644 --- a/exchanges/coinbasepro/coinbasepro.go +++ b/exchanges/coinbasepro/coinbasepro.go @@ -448,6 +448,9 @@ func (c *CoinbasePro) GetAllOrders(ctx context.Context, productID, userNativeCur if contractExpiryType != "" { params.urlVals.Set("contract_expiry_type", contractExpiryType) } + if retailPortfolioID != "" { + params.urlVals.Set("retail_portfolio_id", retailPortfolioID) + } if orderType != "" { params.urlVals.Set("order_type", orderType) } @@ -1215,8 +1218,15 @@ func (c *CoinbasePro) GetFiatTransferByID(ctx context.Context, walletID, deposit return nil, errDepositIDEmpty } - path := fmt.Sprintf("%s%s/%s/%s/%s", coinbaseV2, coinbaseAccounts, walletID, - coinbaseDeposits, depositID) + var path string + switch transferType { + case FiatDeposit: + path = fmt.Sprintf("%s%s/%s/%s/%s", coinbaseV2, coinbaseAccounts, walletID, + coinbaseDeposits, depositID) + case FiatWithdrawal: + path = fmt.Sprintf("%s%s/%s/%s/%s", coinbaseV2, coinbaseAccounts, walletID, + coinbaseWithdrawals, depositID) + } var resp *GenDeposWithdrResp diff --git a/exchanges/coinbasepro/coinbasepro_test.go b/exchanges/coinbasepro/coinbasepro_test.go index 50498e1d874..fa0f1ef4481 100644 --- a/exchanges/coinbasepro/coinbasepro_test.go +++ b/exchanges/coinbasepro/coinbasepro_test.go @@ -60,30 +60,31 @@ const ( skipInsufficientFundsOrWallets = "insufficient funds or wallets for test, skipping" skipInsufficientTransactions = "insufficient transactions for test, skipping" - errExpectMismatch = "received: '%v' but expected: '%v'" - errExpectedNonEmpty = "expected non-empty response" - errOrder0CancelFail = "order 0 failed to cancel" - errIDNotSet = "ID not set" - errx7f = "setting proxy address error parse \"\\x7f\": net/url: invalid control character in URL" - errPortfolioNameDuplicate = `CoinbasePro unsuccessful HTTP status code: 409 raw response: {"error":"CONFLICT","error_details":"[PORTFOLIO_ERROR_CODE_ALREADY_EXISTS] the requested portfolio name already exists","message":"[PORTFOLIO_ERROR_CODE_ALREADY_EXISTS] the requested portfolio name already exists"}, authenticated request failed` - errPortTransferInsufFunds = `CoinbasePro unsuccessful HTTP status code: 429 raw response: {"error":"unknown","error_details":"[PORTFOLIO_ERROR_CODE_INSUFFICIENT_FUNDS] insufficient funds in source account","message":"[PORTFOLIO_ERROR_CODE_INSUFFICIENT_FUNDS] insufficient funds in source account"}, authenticated request failed` - errInvalidProductID = `CoinbasePro unsuccessful HTTP status code: 400 raw response: {"error":"INVALID_ARGUMENT","error_details":"valid product_id is required","message":"valid product_id is required"}, authenticated request failed` - errFeeBuilderNil = "*exchange.FeeBuilder nil pointer" - errUnsupportedAssetType = " unsupported asset type" - errUpsideUnsupported = "unsupported asset type upsideprofitcontract" - errBlorboGranularity = "invalid granularity blorbo, allowed granularities are: [ONE_MINUTE FIVE_MINUTE FIFTEEN_MINUTE THIRTY_MINUTE ONE_HOUR TWO_HOUR SIX_HOUR ONE_DAY]" - errNoEndpointPathEdgeCase3 = "no endpoint path found for the given key: EdgeCase3URL" - errJSONUnsupportedChan = "json: unsupported type: chan struct {}, authenticated request failed" - errExpectedFeeRange = "expected fee range of %v and %v, received %v" - errJSONNumberIntoString = "json: cannot unmarshal number into Go value of type string" - errParseIntValueOutOfRange = `strconv.ParseInt: parsing "922337203685477580700": value out of range` - errParseUintInvalidSyntax = `strconv.ParseUint: parsing "l": invalid syntax` - errJSONInvalidCharacter = `invalid character ':' after array element` - errL2DataMoo = "unknown l2update data type moo" - errUnrecognisedOrderType = `'' unrecognised order type` - errOrderSideInvalid = `'' order side is invalid` - errUnrecognisedStatusType = " not recognised as status type" - errFakeSide = "unknown side fakeside" + errExpectMismatch = "received: '%v' but expected: '%v'" + errExpectedNonEmpty = "expected non-empty response" + errOrder0CancelFail = "order 0 failed to cancel" + errIDNotSet = "ID not set" + errx7f = "setting proxy address error parse \"\\x7f\": net/url: invalid control character in URL" + errPortfolioNameDuplicate = `CoinbasePro unsuccessful HTTP status code: 409 raw response: {"error":"CONFLICT","error_details":"[PORTFOLIO_ERROR_CODE_ALREADY_EXISTS] the requested portfolio name already exists","message":"[PORTFOLIO_ERROR_CODE_ALREADY_EXISTS] the requested portfolio name already exists"}, authenticated request failed` + errPortTransferInsufFunds = `CoinbasePro unsuccessful HTTP status code: 429 raw response: {"error":"unknown","error_details":"[PORTFOLIO_ERROR_CODE_INSUFFICIENT_FUNDS] insufficient funds in source account","message":"[PORTFOLIO_ERROR_CODE_INSUFFICIENT_FUNDS] insufficient funds in source account"}, authenticated request failed` + errInvalidProductID = `CoinbasePro unsuccessful HTTP status code: 400 raw response: {"error":"INVALID_ARGUMENT","error_details":"valid product_id is required","message":"valid product_id is required"}, authenticated request failed` + errFeeBuilderNil = "*exchange.FeeBuilder nil pointer" + errUnsupportedAssetType = " unsupported asset type" + errUpsideUnsupported = "unsupported asset type upsideprofitcontract" + errBlorboGranularity = "invalid granularity blorbo, allowed granularities are: [ONE_MINUTE FIVE_MINUTE FIFTEEN_MINUTE THIRTY_MINUTE ONE_HOUR TWO_HOUR SIX_HOUR ONE_DAY]" + errNoEndpointPathEdgeCase3 = "no endpoint path found for the given key: EdgeCase3URL" + errJSONUnsupportedChan = "json: unsupported type: chan struct {}, authenticated request failed" + errExpectedFeeRange = "expected fee range of %v and %v, received %v" + errJSONNumberIntoString = "json: cannot unmarshal number into Go value of type string" + errParseIntValueOutOfRange = `strconv.ParseInt: parsing "922337203685477580700": value out of range` + errParseUintInvalidSyntax = `strconv.ParseUint: parsing "l": invalid syntax` + errJSONInvalidCharacter = `invalid character ':' after array element` + errL2DataMoo = "unknown l2update data type moo" + errUnrecognisedOrderType = `'' unrecognised order type` + errOrderSideInvalid = `'' order side is invalid` + errUnrecognisedStatusType = " not recognised as status type" + errFakeSide = "unknown side fakeside" + errCoinbaseWSAlreadyDisabled = "websocket already disabled for exchange 'CoinbasePro'" expectedTimestamp = "1970-01-01 00:20:34 +0000 UTC" @@ -1509,8 +1510,11 @@ func TestStringToFloatPtr(t *testing.T) { } func TestWsConnect(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) err := c.Websocket.Disable() - assert.NoError(t, err) + if err != nil && err.Error() != errCoinbaseWSAlreadyDisabled { + t.Error(err) + } err = c.WsConnect() if err.Error() != stream.WebsocketNotEnabled { t.Errorf(errExpectMismatch, err, stream.WebsocketNotEnabled) @@ -1527,105 +1531,105 @@ func TestWsHandleData(t *testing.T) { }() _, err := c.wsHandleData(nil, 0) assert.ErrorIs(t, err, jsonparser.KeyPathNotFoundError) - mockJson := []byte(`{"sequence_num": "l"}`) - _, err = c.wsHandleData(mockJson, 0) + mockJSON := []byte(`{"sequence_num": "l"}`) + _, err = c.wsHandleData(mockJSON, 0) if err.Error() != errParseUintInvalidSyntax { t.Errorf(errExpectMismatch, err, errParseUintInvalidSyntax) } - mockJson = []byte(`{"sequence_num": 1, /\\/"""}`) - _, err = c.wsHandleData(mockJson, 0) + mockJSON = []byte(`{"sequence_num": 1, /\\/"""}`) + _, err = c.wsHandleData(mockJSON, 0) assert.ErrorIs(t, err, jsonparser.KeyPathNotFoundError) - mockJson = []byte(`{"sequence_num": 0, "channel": "subscriptions"}`) - _, err = c.wsHandleData(mockJson, 0) + mockJSON = []byte(`{"sequence_num": 0, "channel": "subscriptions"}`) + _, err = c.wsHandleData(mockJSON, 0) assert.NoError(t, err) - mockJson = []byte(`{"sequence_num": 0, "channel": "", "events":}`) - _, err = c.wsHandleData(mockJson, 0) + mockJSON = []byte(`{"sequence_num": 0, "channel": "", "events":}`) + _, err = c.wsHandleData(mockJSON, 0) assert.ErrorIs(t, err, jsonparser.UnknownValueTypeError) - mockJson = []byte(`{"sequence_num": 0, "channel": "status", "events": ["type": 1234]}`) - _, err = c.wsHandleData(mockJson, 0) + mockJSON = []byte(`{"sequence_num": 0, "channel": "status", "events": ["type": 1234]}`) + _, err = c.wsHandleData(mockJSON, 0) if err.Error() != errJSONInvalidCharacter { t.Errorf(errExpectMismatch, err, errJSONInvalidCharacter) } - mockJson = []byte(`{"sequence_num": 0, "channel": "status", "events": [{"type": "moo"}]}`) - _, err = c.wsHandleData(mockJson, 0) + mockJSON = []byte(`{"sequence_num": 0, "channel": "status", "events": [{"type": "moo"}]}`) + _, err = c.wsHandleData(mockJSON, 0) assert.NoError(t, err) - mockJson = []byte(`{"sequence_num": 0, "channel": "error", "events": [{"type": "moo"}]}`) - _, err = c.wsHandleData(mockJson, 0) + mockJSON = []byte(`{"sequence_num": 0, "channel": "error", "events": [{"type": "moo"}]}`) + _, err = c.wsHandleData(mockJSON, 0) assert.NoError(t, err) - mockJson = []byte(`{"sequence_num": 0, "channel": "ticker", "events": ["type": ""}]}`) - _, err = c.wsHandleData(mockJson, 0) + mockJSON = []byte(`{"sequence_num": 0, "channel": "ticker", "events": ["type": ""}]}`) + _, err = c.wsHandleData(mockJSON, 0) if err.Error() != errJSONInvalidCharacter { t.Errorf(errExpectMismatch, err, errJSONInvalidCharacter) } - mockJson = []byte(`{"sequence_num": 0, "channel": "ticker", "events": [{"type": "moo", "tickers": [{"price": "1.1"}]}]}`) - _, err = c.wsHandleData(mockJson, 0) + mockJSON = []byte(`{"sequence_num": 0, "channel": "ticker", "events": [{"type": "moo", "tickers": [{"price": "1.1"}]}]}`) + _, err = c.wsHandleData(mockJSON, 0) assert.ErrorIs(t, err, jsonparser.KeyPathNotFoundError) - mockJson = []byte(`{"sequence_num": 0, "channel": "ticker", "timestamp": "2006-01-02T15:04:05Z", "events": [{"type": "moo", "tickers": [{"price": "1.1"}]}]}`) - _, err = c.wsHandleData(mockJson, 0) + mockJSON = []byte(`{"sequence_num": 0, "channel": "ticker", "timestamp": "2006-01-02T15:04:05Z", "events": [{"type": "moo", "tickers": [{"price": "1.1"}]}]}`) + _, err = c.wsHandleData(mockJSON, 0) assert.NoError(t, err) - mockJson = []byte(`{"sequence_num": 0, "channel": "candles", "events": ["type": ""}]}`) - _, err = c.wsHandleData(mockJson, 0) + mockJSON = []byte(`{"sequence_num": 0, "channel": "candles", "events": ["type": ""}]}`) + _, err = c.wsHandleData(mockJSON, 0) if err.Error() != errJSONInvalidCharacter { t.Errorf(errExpectMismatch, err, errJSONInvalidCharacter) } - mockJson = []byte(`{"sequence_num": 0, "channel": "candles", "events": [{"type": "moo", "candles": [{"low": "1.1"}]}]}`) - _, err = c.wsHandleData(mockJson, 0) + mockJSON = []byte(`{"sequence_num": 0, "channel": "candles", "events": [{"type": "moo", "candles": [{"low": "1.1"}]}]}`) + _, err = c.wsHandleData(mockJSON, 0) assert.ErrorIs(t, err, jsonparser.KeyPathNotFoundError) - mockJson = []byte(`{"sequence_num": 0, "channel": "candles", "timestamp": "2006-01-02T15:04:05Z", "events": [{"type": "moo", "candles": [{"low": "1.1"}]}]}`) - _, err = c.wsHandleData(mockJson, 0) + mockJSON = []byte(`{"sequence_num": 0, "channel": "candles", "timestamp": "2006-01-02T15:04:05Z", "events": [{"type": "moo", "candles": [{"low": "1.1"}]}]}`) + _, err = c.wsHandleData(mockJSON, 0) assert.NoError(t, err) - mockJson = []byte(`{"sequence_num": 0, "channel": "market_trades", "events": ["type": ""}]}`) - _, err = c.wsHandleData(mockJson, 0) + mockJSON = []byte(`{"sequence_num": 0, "channel": "market_trades", "events": ["type": ""}]}`) + _, err = c.wsHandleData(mockJSON, 0) if err.Error() != errJSONInvalidCharacter { t.Errorf(errExpectMismatch, err, errJSONInvalidCharacter) } - mockJson = []byte(`{"sequence_num": 0, "channel": "market_trades", "events": [{"type": "moo", "trades": [{"price": "1.1"}]}]}`) - _, err = c.wsHandleData(mockJson, 0) + mockJSON = []byte(`{"sequence_num": 0, "channel": "market_trades", "events": [{"type": "moo", "trades": [{"price": "1.1"}]}]}`) + _, err = c.wsHandleData(mockJSON, 0) assert.NoError(t, err) - mockJson = []byte(`{"sequence_num": 0, "channel": "l2_data", "events": ["type": ""}]}`) - _, err = c.wsHandleData(mockJson, 0) + mockJSON = []byte(`{"sequence_num": 0, "channel": "l2_data", "events": ["type": ""}]}`) + _, err = c.wsHandleData(mockJSON, 0) if err.Error() != errJSONInvalidCharacter { t.Errorf(errExpectMismatch, err, errJSONInvalidCharacter) } - mockJson = []byte(`{"sequence_num": 0, "channel": "l2_data", "events": [{"type": "moo", "updates": [{"price_level": "1.1"}]}]}`) - _, err = c.wsHandleData(mockJson, 0) + mockJSON = []byte(`{"sequence_num": 0, "channel": "l2_data", "events": [{"type": "moo", "updates": [{"price_level": "1.1"}]}]}`) + _, err = c.wsHandleData(mockJSON, 0) assert.ErrorIs(t, err, jsonparser.KeyPathNotFoundError) - mockJson = []byte(`{"sequence_num": 0, "channel": "l2_data", "timestamp": "2006-01-02T15:04:05Z", "events": [{"type": "moo", "updates": [{"price_level": "1.1"}]}]}`) - _, err = c.wsHandleData(mockJson, 0) + mockJSON = []byte(`{"sequence_num": 0, "channel": "l2_data", "timestamp": "2006-01-02T15:04:05Z", "events": [{"type": "moo", "updates": [{"price_level": "1.1"}]}]}`) + _, err = c.wsHandleData(mockJSON, 0) if err.Error() != errL2DataMoo { t.Errorf(errExpectMismatch, err, errL2DataMoo) } - mockJson = []byte(`{"sequence_num": 0, "channel": "l2_data", "timestamp": "2006-01-02T15:04:05Z", "events": [{"type": "snapshot", "product_id": "BTC-USD", "updates": [{"side": "bid", "price_level": "1.1", "new_quantity": "2.2"}]}]}`) - _, err = c.wsHandleData(mockJson, 0) + mockJSON = []byte(`{"sequence_num": 0, "channel": "l2_data", "timestamp": "2006-01-02T15:04:05Z", "events": [{"type": "snapshot", "product_id": "BTC-USD", "updates": [{"side": "bid", "price_level": "1.1", "new_quantity": "2.2"}]}]}`) + _, err = c.wsHandleData(mockJSON, 0) assert.NoError(t, err) - mockJson = []byte(`{"sequence_num": 0, "channel": "l2_data", "timestamp": "2006-01-02T15:04:05Z", "events": [{"type": "update", "product_id": "BTC-USD", "updates": [{"side": "bid", "price_level": "1.1", "new_quantity": "2.2"}]}]}`) - _, err = c.wsHandleData(mockJson, 0) + mockJSON = []byte(`{"sequence_num": 0, "channel": "l2_data", "timestamp": "2006-01-02T15:04:05Z", "events": [{"type": "update", "product_id": "BTC-USD", "updates": [{"side": "bid", "price_level": "1.1", "new_quantity": "2.2"}]}]}`) + _, err = c.wsHandleData(mockJSON, 0) assert.NoError(t, err) - mockJson = []byte(`{"sequence_num": 0, "channel": "user", "events": ["type": ""}]}`) - _, err = c.wsHandleData(mockJson, 0) + mockJSON = []byte(`{"sequence_num": 0, "channel": "user", "events": ["type": ""}]}`) + _, err = c.wsHandleData(mockJSON, 0) if err.Error() != errJSONInvalidCharacter { t.Errorf(errExpectMismatch, err, errJSONInvalidCharacter) } - mockJson = []byte(`{"sequence_num": 0, "channel": "user", "events": [{"type": "moo", "orders": [{"total_fees": "1.1"}]}]}`) - _, err = c.wsHandleData(mockJson, 0) + mockJSON = []byte(`{"sequence_num": 0, "channel": "user", "events": [{"type": "moo", "orders": [{"total_fees": "1.1"}]}]}`) + _, err = c.wsHandleData(mockJSON, 0) if err.Error() != errUnrecognisedOrderType { t.Errorf(errExpectMismatch, err, errUnrecognisedOrderType) } - mockJson = []byte(`{"sequence_num": 0, "channel": "user", "events": [{"type": "moo", "orders": [{"total_fees": "1.1", "order_type": "ioc"}]}]}`) - _, err = c.wsHandleData(mockJson, 0) + mockJSON = []byte(`{"sequence_num": 0, "channel": "user", "events": [{"type": "moo", "orders": [{"total_fees": "1.1", "order_type": "ioc"}]}]}`) + _, err = c.wsHandleData(mockJSON, 0) if err.Error() != errOrderSideInvalid { t.Errorf(errExpectMismatch, err, errOrderSideInvalid) } - mockJson = []byte(`{"sequence_num": 0, "channel": "user", "events": [{"type": "moo", "orders": [{"total_fees": "1.1", "order_type": "ioc", "order_side": "buy"}]}]}`) - _, err = c.wsHandleData(mockJson, 0) + mockJSON = []byte(`{"sequence_num": 0, "channel": "user", "events": [{"type": "moo", "orders": [{"total_fees": "1.1", "order_type": "ioc", "order_side": "buy"}]}]}`) + _, err = c.wsHandleData(mockJSON, 0) if err.Error() != errUnrecognisedStatusType { t.Errorf(errExpectMismatch, err, errUnrecognisedStatusType) } - mockJson = []byte(`{"sequence_num": 0, "channel": "user", "events": [{"type": "moo", "orders": [{"total_fees": "1.1", "order_type": "ioc", "order_side": "buy", "status": "done"}]}]}`) - _, err = c.wsHandleData(mockJson, 0) + mockJSON = []byte(`{"sequence_num": 0, "channel": "user", "events": [{"type": "moo", "orders": [{"total_fees": "1.1", "order_type": "ioc", "order_side": "buy", "status": "done"}]}]}`) + _, err = c.wsHandleData(mockJSON, 0) assert.NoError(t, err) - mockJson = []byte(`{"sequence_num": 0, "channel": "fakechan", "events": ["type": ""}]}`) - _, err = c.wsHandleData(mockJson, 0) + mockJSON = []byte(`{"sequence_num": 0, "channel": "fakechan", "events": ["type": ""}]}`) + _, err = c.wsHandleData(mockJSON, 0) assert.ErrorIs(t, err, errChannelNameUnknown) } @@ -1751,17 +1755,13 @@ func getINTXPortfolio(t *testing.T) string { return targetID } -func convertTestHelper(t *testing.T) (string, string) { +func convertTestHelper(t *testing.T) (fromAccID, toAccID string) { t.Helper() accIDs, err := c.GetAllAccounts(context.Background(), 250, "") assert.NoError(t, err) if len(accIDs.Accounts) == 0 { t.Fatal(errExpectedNonEmpty) } - var ( - fromAccID string - toAccID string - ) for x := range accIDs.Accounts { if accIDs.Accounts[x].Currency == "USDC" { fromAccID = accIDs.Accounts[x].UUID @@ -1779,14 +1779,13 @@ func convertTestHelper(t *testing.T) (string, string) { return fromAccID, toAccID } -func transferTestHelper(t *testing.T, wallets GetAllWalletsResponse) (string, string) { +func transferTestHelper(t *testing.T, wallets GetAllWalletsResponse) (srcWalletID, tarWalletID string) { t.Helper() var hasValidFunds bool - var wID string for i := range wallets.Data { if wallets.Data[i].Currency.Code == testFiat.String() && wallets.Data[i].Balance.Amount > 10 { hasValidFunds = true - wID = wallets.Data[i].ID + srcWalletID = wallets.Data[i].ID } } if !hasValidFunds { @@ -1797,7 +1796,7 @@ func transferTestHelper(t *testing.T, wallets GetAllWalletsResponse) (string, st if len(pmID.Data) == 0 { t.Skip(skipPayMethodNotFound) } - return wID, pmID.Data[0].FiatAccount.ID + return srcWalletID, pmID.Data[0].FiatAccount.ID } type withdrawFiatFunc func(context.Context, *withdraw.Request) (*withdraw.ExchangeResponse, error) diff --git a/exchanges/coinbasepro/coinbasepro_types.go b/exchanges/coinbasepro/coinbasepro_types.go index 951b59c98ee..ae9d5af33b9 100644 --- a/exchanges/coinbasepro/coinbasepro_types.go +++ b/exchanges/coinbasepro/coinbasepro_types.go @@ -489,7 +489,7 @@ type FuturesBalanceSummary struct { // FuturesPosition contains information on a single futures position, returned by // GetFuturesPositionByID and used as a sub-struct in the type AllFuturesPositions type FuturesPosition struct { - // This may belong in a struct of its own called "position", requring a bit + // This may belong in a struct of its own called "position", requiring a bit // more abstraction, but for the moment I'll assume it doesn't ProductID string `json:"product_id"` ExpirationTime time.Time `json:"expiration_time"` @@ -1166,7 +1166,7 @@ type ServerTimeV2 struct { } `json:"data"` } -// WebsocketRequest is an aspect of constructing a requst to the websocket server, used in sendRequest +// WebsocketRequest is an aspect of constructing a request to the websocket server, used in sendRequest type WebsocketRequest struct { Type string `json:"type"` ProductIDs []string `json:"product_ids,omitempty"` diff --git a/exchanges/coinbasepro/coinbasepro_websocket.go b/exchanges/coinbasepro/coinbasepro_websocket.go index 1fd8faaea9d..3968ca9dc79 100644 --- a/exchanges/coinbasepro/coinbasepro_websocket.go +++ b/exchanges/coinbasepro/coinbasepro_websocket.go @@ -238,14 +238,10 @@ func (c *CoinbasePro) wsHandleData(respRaw []byte, seqCount uint64) (string, err if err != nil { return warnString, err } - } case "user": var wsUser []WebsocketOrderDataHolder err := json.Unmarshal(data, &wsUser) - // case "match", "last_match": - // var wsOrder wsOrderReceived - // err := json.Unmarshal(respRaw, &wsOrder) if err != nil { return warnString, err } @@ -376,7 +372,6 @@ func (c *CoinbasePro) Subscribe(channelsToSubscribe []subscription.Subscription) for i := range channelsToSubscribe { chanKeys[channelsToSubscribe[i].Channel] = chanKeys[channelsToSubscribe[i].Channel].Add(channelsToSubscribe[i].Pair) - } for s := range chanKeys { err := c.sendRequest("subscribe", s, chanKeys[s]) @@ -454,11 +449,11 @@ func (c *CoinbasePro) GetJWT(ctx context.Context, uri string) (string, error) { if uri != "" { body["uri"] = uri } - bodyJson, err := json.Marshal(body) + bodyJSON, err := json.Marshal(body) if err != nil { return "", err } - bodyEncode := base64URLEncode(bodyJson) + bodyEncode := base64URLEncode(bodyJSON) hash := sha256.Sum256([]byte(headEncode + "." + bodyEncode)) @@ -527,8 +522,7 @@ func (c *CoinbasePro) sendRequest(msgType, channel string, productIDs currency.P // processBidAskArray is a helper function that turns WebsocketOrderbookDataHolder into arrays // of bids and asks -func processBidAskArray(data *WebsocketOrderbookDataHolder) ([]orderbook.Item, []orderbook.Item, error) { - var bids, asks []orderbook.Item +func processBidAskArray(data *WebsocketOrderbookDataHolder) (bids, asks []orderbook.Item, err error) { for i := range data.Changes { switch data.Changes[i].Side { case "bid": diff --git a/exchanges/coinbasepro/coinbasepro_wrapper.go b/exchanges/coinbasepro/coinbasepro_wrapper.go index 2c7bcb8b5e9..2882384ed96 100644 --- a/exchanges/coinbasepro/coinbasepro_wrapper.go +++ b/exchanges/coinbasepro/coinbasepro_wrapper.go @@ -707,9 +707,9 @@ func (c *CoinbasePro) CancelAllOrders(ctx context.Context, can *order.Cancel) (o if len(ordStatus) == 0 { return resp, errNoMatchingOrders } - var orders []order.Cancel + orders := make([]order.Cancel, len(ordIDs)) for i := range ordIDs { - orders = append(orders, order.Cancel{OrderID: ordIDs[i].OrderID}) + orders[i] = order.Cancel{OrderID: ordIDs[i].OrderID} } batchResp, count, err := c.cancelOrdersReturnMapAndCount(ctx, orders) @@ -773,7 +773,7 @@ func (c *CoinbasePro) GetOrderInfo(ctx context.Context, orderID string, pair cur } // GetDepositAddress returns a deposit address for a specified currency -func (c *CoinbasePro) GetDepositAddress(ctx context.Context, cryptocurrency currency.Code, _, chain string) (*deposit.Address, error) { +func (c *CoinbasePro) GetDepositAddress(ctx context.Context, cryptocurrency currency.Code, _, _ string) (*deposit.Address, error) { allWalResp, err := c.GetAllWallets(ctx, PaginationInp{}) if err != nil { return nil, err @@ -1090,7 +1090,7 @@ func (c *CoinbasePro) GetLatestFundingRates(ctx context.Context, r *fundingrate. return nil, err } - var funding []fundingrate.LatestRateResponse + funding := make([]fundingrate.LatestRateResponse, len(products.Products)) for i := range products.Products { @@ -1103,13 +1103,13 @@ func (c *CoinbasePro) GetLatestFundingRates(ctx context.Context, r *fundingrate. Rate: decimal.NewFromFloat(products.Products[i].FutureProductDetails.PerpetualDetails.FundingRate.Float64()), } - funding = append(funding, fundingrate.LatestRateResponse{ + funding[i] = fundingrate.LatestRateResponse{ Exchange: c.Name, Asset: r.Asset, Pair: pair, LatestRate: funRate, TimeChecked: time.Now(), - }) + } } return funding, nil @@ -1129,7 +1129,7 @@ func (c *CoinbasePro) GetFuturesContractDetails(ctx context.Context, item asset. return nil, err } - var contracts []futures.Contract + contracts := make([]futures.Contract, len(products.Products)) for i := range products.Products { pair, err := currency.NewPairFromString(products.Products[i].ID) @@ -1139,7 +1139,7 @@ func (c *CoinbasePro) GetFuturesContractDetails(ctx context.Context, item asset. funRate := fundingrate.Rate{Time: products.Products[i].FutureProductDetails.PerpetualDetails.FundingTime, Rate: decimal.NewFromFloat(products.Products[i].FutureProductDetails.PerpetualDetails.FundingRate.Float64()), } - contracts = append(contracts, futures.Contract{ + contracts[i] = futures.Contract{ Exchange: c.Name, Name: pair, Asset: item, @@ -1150,7 +1150,7 @@ func (c *CoinbasePro) GetFuturesContractDetails(ctx context.Context, item asset. SettlementCurrencies: []currency.Code{currency.NewCode(products.Products[i].QuoteCurrencyID)}, Multiplier: products.Products[i].BaseIncrement.Float64(), LatestRate: funRate, - }) + } } return contracts, nil @@ -1173,7 +1173,7 @@ func (c *CoinbasePro) UpdateOrderExecutionLimits(ctx context.Context, a asset.It return err } - var limits []order.MinMaxLevel + limits := make([]order.MinMaxLevel, len(data.Products)) for i := range data.Products { pair, err := currency.NewPairFromString(data.Products[i].ID) @@ -1181,7 +1181,7 @@ func (c *CoinbasePro) UpdateOrderExecutionLimits(ctx context.Context, a asset.It return err } - limits = append(limits, order.MinMaxLevel{ + limits[i] = order.MinMaxLevel{ Pair: pair, Asset: a, MinPrice: data.Products[i].QuoteMinSize.Float64(), @@ -1194,7 +1194,7 @@ func (c *CoinbasePro) UpdateOrderExecutionLimits(ctx context.Context, a asset.It AmountStepIncrementSize: data.Products[i].BaseIncrement.Float64(), QuoteStepIncrementSize: data.Products[i].QuoteIncrement.Float64(), MaxTotalOrders: 1000, - }) + } } return c.LoadLimits(limits) @@ -1218,12 +1218,12 @@ func (c *CoinbasePro) fetchFutures(ctx context.Context) (AllProducts, error) { // cancelOrdersReturnMapAndCount is a helper function for CancelBatchOrders and CancelAllOrders, // calling the appropriate Coinbase endpoint, and returning information useful to both -func (c *CoinbasePro) cancelOrdersReturnMapAndCount(ctx context.Context, o []order.Cancel) (map[string]string, int64, error) { +func (c *CoinbasePro) cancelOrdersReturnMapAndCount(ctx context.Context, o []order.Cancel) (status map[string]string, count int64, err error) { ordToCancel := len(o) if ordToCancel == 0 { return nil, 0, errOrderIDEmpty } - status := make(map[string]string) + status = make(map[string]string) ordIDSlice := make([]string, ordToCancel) for i := range o { err := o[i].Validate(o[i].StandardCancel()) From 2b5df7c69f84f971f00940a333e088503a61f312 Mon Sep 17 00:00:00 2001 From: Samuel Reid <43227667+cranktakular@users.noreply.github.com> Date: Tue, 20 Feb 2024 13:22:07 +1100 Subject: [PATCH 26/79] Further lint cleanup --- exchanges/coinbasepro/coinbasepro.go | 5 +++-- exchanges/coinbasepro/coinbasepro_test.go | 5 +---- exchanges/coinbasepro/coinbasepro_websocket.go | 4 ++-- exchanges/coinbasepro/coinbasepro_wrapper.go | 2 -- 4 files changed, 6 insertions(+), 10 deletions(-) diff --git a/exchanges/coinbasepro/coinbasepro.go b/exchanges/coinbasepro/coinbasepro.go index 785ced020f1..ad5d32380ca 100644 --- a/exchanges/coinbasepro/coinbasepro.go +++ b/exchanges/coinbasepro/coinbasepro.go @@ -1373,7 +1373,8 @@ func (c *CoinbasePro) SendAuthenticatedHTTPRequest(ctx context.Context, ep excha message := n + method + path + string(payload) - hmac, err := crypto.GetHMAC(crypto.HashSHA256, + var hmac []byte + hmac, err = crypto.GetHMAC(crypto.HashSHA256, []byte(message), []byte(creds.Secret)) if err != nil { @@ -1426,7 +1427,7 @@ func (c *CoinbasePro) SendAuthenticatedHTTPRequest(ctx context.Context, ep excha PreviewFailureReason string `json:"preview_failure_reason"` NewOrderFailureReason string `json:"new_order_failure_reason"` }{} - if err := json.Unmarshal(interim, &singleErrCap); err == nil { + 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", singleErrCap.Message, singleErrCap.ErrorType, singleErrCap.ErrorDetails, singleErrCap.EditFailureReason, diff --git a/exchanges/coinbasepro/coinbasepro_test.go b/exchanges/coinbasepro/coinbasepro_test.go index fa0f1ef4481..063aac9547a 100644 --- a/exchanges/coinbasepro/coinbasepro_test.go +++ b/exchanges/coinbasepro/coinbasepro_test.go @@ -466,7 +466,6 @@ func TestScheduleFuturesSweep(t *testing.T) { for i := range curSweeps.Sweeps { if curSweeps.Sweeps[i].Status == "PENDING" { preCancel = true - } } } @@ -503,7 +502,6 @@ func TestCancelPendingFuturesSweep(t *testing.T) { if err != nil { t.Error(err) } - } _, err = c.CancelPendingFuturesSweep(context.Background()) assert.NoError(t, err) @@ -1316,7 +1314,6 @@ func TestGetOrderHistory(t *testing.T) { req.Pairs = req.Pairs.Add(testPair) _, err = c.GetOrderHistory(context.Background(), &req) assert.NoError(t, err) - } func TestGetHistoricCandles(t *testing.T) { @@ -1820,7 +1817,7 @@ func withdrawFiatFundsHelper(t *testing.T, fn withdrawFiatFunc) { assert.ErrorIs(t, err, errWalletIDEmpty) sharedtestvalues.SkipTestIfCredentialsUnset(t, c) req.WalletID = "meow" - req.Fiat.Bank.BankName = "GCT's Fake and Not Real Test Bank Meow Meow Meow" + req.Fiat.Bank.BankName = "GCT's Officially Fake and Not Real Test Bank" expectedError := fmt.Sprintf(errPayMethodNotFound, req.Fiat.Bank.BankName) _, err = fn(context.Background(), &req) if err.Error() != expectedError { diff --git a/exchanges/coinbasepro/coinbasepro_websocket.go b/exchanges/coinbasepro/coinbasepro_websocket.go index 3968ca9dc79..8d756eded09 100644 --- a/exchanges/coinbasepro/coinbasepro_websocket.go +++ b/exchanges/coinbasepro/coinbasepro_websocket.go @@ -137,7 +137,8 @@ func (c *CoinbasePro) wsHandleData(respRaw []byte, seqCount uint64) (string, err sliToSend := []ticker.Price{} - timestamp, err := getTimestamp(respRaw) + var timestamp time.Time + timestamp, err = getTimestamp(respRaw) if err != nil { return warnString, err } @@ -392,7 +393,6 @@ func (c *CoinbasePro) Unsubscribe(channelsToUnsubscribe []subscription.Subscript for i := range channelsToUnsubscribe { chanKeys[channelsToUnsubscribe[i].Channel] = chanKeys[channelsToUnsubscribe[i].Channel].Add(channelsToUnsubscribe[i].Pair) - } for s := range chanKeys { diff --git a/exchanges/coinbasepro/coinbasepro_wrapper.go b/exchanges/coinbasepro/coinbasepro_wrapper.go index 2882384ed96..5758e7ad6be 100644 --- a/exchanges/coinbasepro/coinbasepro_wrapper.go +++ b/exchanges/coinbasepro/coinbasepro_wrapper.go @@ -1093,7 +1093,6 @@ func (c *CoinbasePro) GetLatestFundingRates(ctx context.Context, r *fundingrate. 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 @@ -1158,7 +1157,6 @@ func (c *CoinbasePro) GetFuturesContractDetails(ctx context.Context, item asset. // UpdateOrderExecutionLimits updates order execution limits func (c *CoinbasePro) UpdateOrderExecutionLimits(ctx context.Context, a asset.Item) error { - var data AllProducts var err error switch a { From 93d6722e77abcc90d34e1ad6936128c0719d33c5 Mon Sep 17 00:00:00 2001 From: Samuel Reid <43227667+cranktakular@users.noreply.github.com> Date: Tue, 20 Feb 2024 13:34:17 +1100 Subject: [PATCH 27/79] Increased lint coverage --- exchanges/coinbasepro/coinbasepro_websocket.go | 3 ++- exchanges/coinbasepro/coinbasepro_wrapper.go | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/exchanges/coinbasepro/coinbasepro_websocket.go b/exchanges/coinbasepro/coinbasepro_websocket.go index 8d756eded09..d31b90b6574 100644 --- a/exchanges/coinbasepro/coinbasepro_websocket.go +++ b/exchanges/coinbasepro/coinbasepro_websocket.go @@ -168,7 +168,8 @@ func (c *CoinbasePro) wsHandleData(respRaw []byte, seqCount uint64) (string, err sliToSend := []stream.KlineData{} - timestamp, err := getTimestamp(respRaw) + var timestamp time.Time + timestamp, err = getTimestamp(respRaw) if err != nil { return warnString, err } diff --git a/exchanges/coinbasepro/coinbasepro_wrapper.go b/exchanges/coinbasepro/coinbasepro_wrapper.go index 5758e7ad6be..15ec7620f4b 100644 --- a/exchanges/coinbasepro/coinbasepro_wrapper.go +++ b/exchanges/coinbasepro/coinbasepro_wrapper.go @@ -254,11 +254,12 @@ func (c *CoinbasePro) UpdateAccountInfo(ctx context.Context, assetType asset.Ite done bool err error cursor string + accountResp AllAccountsResponse ) response.Exchange = c.Name for !done { - accountResp, err := c.GetAllAccounts(ctx, 250, cursor) + accountResp, err = c.GetAllAccounts(ctx, 250, cursor) if err != nil { return response, err } @@ -411,7 +412,7 @@ func (c *CoinbasePro) UpdateOrderbook(ctx context.Context, p currency.Pair, asse if p.IsEmpty() { return nil, currency.ErrCurrencyPairEmpty } - if err := c.CurrencyPairs.IsAssetEnabled(assetType); err != nil { + if err = c.CurrencyPairs.IsAssetEnabled(assetType); err != nil { return nil, err } book := &orderbook.Base{ From 529f7f317adc1e807d3c9067f2b0965b271e19b5 Mon Sep 17 00:00:00 2001 From: Samuel Reid <43227667+cranktakular@users.noreply.github.com> Date: Tue, 20 Feb 2024 13:41:34 +1100 Subject: [PATCH 28/79] Does this fix all sloppy reassigns & shadowing? --- exchanges/coinbasepro/coinbasepro_wrapper.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/exchanges/coinbasepro/coinbasepro_wrapper.go b/exchanges/coinbasepro/coinbasepro_wrapper.go index 15ec7620f4b..385aa3768ea 100644 --- a/exchanges/coinbasepro/coinbasepro_wrapper.go +++ b/exchanges/coinbasepro/coinbasepro_wrapper.go @@ -412,7 +412,8 @@ func (c *CoinbasePro) UpdateOrderbook(ctx context.Context, p currency.Pair, asse if p.IsEmpty() { return nil, currency.ErrCurrencyPairEmpty } - if err = c.CurrencyPairs.IsAssetEnabled(assetType); err != nil { + err = c.CurrencyPairs.IsAssetEnabled(assetType) + if err != nil { return nil, err } book := &orderbook.Base{ From 8bb2561868611763f5088e559fd38f207620f812 Mon Sep 17 00:00:00 2001 From: Samuel Reid <43227667+cranktakular@users.noreply.github.com> Date: Tue, 20 Feb 2024 14:46:38 +1100 Subject: [PATCH 29/79] Undoing retry policy change --- exchanges/request/retry.go | 3 +-- exchanges/request/retry_test.go | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/exchanges/request/retry.go b/exchanges/request/retry.go index b851fed86d8..9ac974bf936 100644 --- a/exchanges/request/retry.go +++ b/exchanges/request/retry.go @@ -21,8 +21,7 @@ func DefaultRetryPolicy(resp *http.Response, err error) (bool, error) { } if resp.StatusCode == http.StatusTooManyRequests { - // If we've sent too many requests, we shouldn't immediately retry. - return false, nil + return true, nil } if resp.Header.Get(headerRetryAfter) != "" { diff --git a/exchanges/request/retry_test.go b/exchanges/request/retry_test.go index df5bd9964a0..6e3f4a6d3ae 100644 --- a/exchanges/request/retry_test.go +++ b/exchanges/request/retry_test.go @@ -34,7 +34,7 @@ func TestDefaultRetryPolicy(t *testing.T) { }, "Too Many Requests": { Args: args{Response: &http.Response{StatusCode: http.StatusTooManyRequests}}, - Want: want{Retry: false}, + Want: want{Retry: true}, }, "Not Found": { Args: args{Response: &http.Response{StatusCode: http.StatusNotFound}}, From 5f813e3679a2b9ae4ed05556a06680a2d269524c Mon Sep 17 00:00:00 2001 From: Samuel Reid <43227667+cranktakular@users.noreply.github.com> Date: Tue, 20 Feb 2024 15:13:23 +1100 Subject: [PATCH 30/79] Documentation regeneration --- CONTRIBUTORS | 20 ++++----- LICENSE | 2 +- README.md | 30 ++++++------- engine/currency_state_manager.md | 74 ++++++++++++++++---------------- 4 files changed, 63 insertions(+), 63 deletions(-) diff --git a/CONTRIBUTORS b/CONTRIBUTORS index 1bd5d58401f..c184878f5b7 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -5,20 +5,20 @@ shazbert | https://github.com/shazbert dependabot[bot] | https://github.com/apps/dependabot gloriousCode | https://github.com/gloriousCode dependabot-preview[bot] | https://github.com/apps/dependabot-preview -xtda | https://github.com/xtda gbjk | https://github.com/gbjk +xtda | https://github.com/xtda lrascao | https://github.com/lrascao -Rots | https://github.com/Rots -vazha | https://github.com/vazha ydm | https://github.com/ydm +vazha | https://github.com/vazha +Rots | https://github.com/Rots +Beadko | https://github.com/Beadko ermalguni | https://github.com/ermalguni MadCozBadd | https://github.com/MadCozBadd -Beadko | https://github.com/Beadko vadimzhukck | https://github.com/vadimzhukck -140am | https://github.com/140am -marcofranssen | https://github.com/marcofranssen geseq | https://github.com/geseq samuael | https://github.com/samuael +marcofranssen | https://github.com/marcofranssen +140am | https://github.com/140am TaltaM | https://github.com/TaltaM dackroyd | https://github.com/dackroyd cranktakular | https://github.com/cranktakular @@ -26,10 +26,10 @@ khcchiu | https://github.com/khcchiu yangrq1018 | https://github.com/yangrq1018 woshidama323 | https://github.com/woshidama323 crackcomm | https://github.com/crackcomm -azhang | https://github.com/azhang -andreygrehov | https://github.com/andreygrehov -bretep | https://github.com/bretep -Christian-Achilli | https://github.com/Christian-Achilli +mshogin | https://github.com/mshogin +herenow | https://github.com/herenow +tk42 | https://github.com/tk42 +soxipy | https://github.com/soxipy cornelk | https://github.com/cornelk gam-phon | https://github.com/gam-phon herenow | https://github.com/herenow diff --git a/LICENSE b/LICENSE index 6a735d58c4d..3abf50b2f4f 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2014-2023 The GoCryptoTrader Developers +Copyright (c) 2014-2024 The GoCryptoTrader Developers Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 285592e8e6a..73e5fdd1f1c 100644 --- a/README.md +++ b/README.md @@ -142,25 +142,25 @@ Binaries will be published once the codebase reaches a stable condition. |User|Contribution Amount| |--|--| -| [thrasher-](https://github.com/thrasher-) | 684 | -| [shazbert](https://github.com/shazbert) | 315 | -| [dependabot[bot]](https://github.com/apps/dependabot) | 228 | -| [gloriousCode](https://github.com/gloriousCode) | 224 | +| [thrasher-](https://github.com/thrasher-) | 689 | +| [shazbert](https://github.com/shazbert) | 316 | +| [dependabot[bot]](https://github.com/apps/dependabot) | 254 | +| [gloriousCode](https://github.com/gloriousCode) | 229 | | [dependabot-preview[bot]](https://github.com/apps/dependabot-preview) | 88 | +| [gbjk](https://github.com/gbjk) | 61 | | [xtda](https://github.com/xtda) | 47 | -| [gbjk](https://github.com/gbjk) | 42 | | [lrascao](https://github.com/lrascao) | 27 | -| [Rots](https://github.com/Rots) | 15 | -| [vazha](https://github.com/vazha) | 15 | | [ydm](https://github.com/ydm) | 15 | +| [vazha](https://github.com/vazha) | 15 | +| [Rots](https://github.com/Rots) | 15 | +| [Beadko](https://github.com/Beadko) | 15 | | [ermalguni](https://github.com/ermalguni) | 14 | | [MadCozBadd](https://github.com/MadCozBadd) | 13 | -| [Beadko](https://github.com/Beadko) | 11 | | [vadimzhukck](https://github.com/vadimzhukck) | 10 | -| [140am](https://github.com/140am) | 8 | -| [marcofranssen](https://github.com/marcofranssen) | 8 | | [geseq](https://github.com/geseq) | 8 | -| [samuael](https://github.com/samuael) | 7 | +| [samuael](https://github.com/samuael) | 8 | +| [marcofranssen](https://github.com/marcofranssen) | 8 | +| [140am](https://github.com/140am) | 8 | | [TaltaM](https://github.com/TaltaM) | 6 | | [dackroyd](https://github.com/dackroyd) | 5 | | [cranktakular](https://github.com/cranktakular) | 5 | @@ -168,10 +168,10 @@ Binaries will be published once the codebase reaches a stable condition. | [yangrq1018](https://github.com/yangrq1018) | 4 | | [woshidama323](https://github.com/woshidama323) | 3 | | [crackcomm](https://github.com/crackcomm) | 3 | -| [azhang](https://github.com/azhang) | 2 | -| [andreygrehov](https://github.com/andreygrehov) | 2 | -| [bretep](https://github.com/bretep) | 2 | -| [Christian-Achilli](https://github.com/Christian-Achilli) | 2 | +| [mshogin](https://github.com/mshogin) | 2 | +| [herenow](https://github.com/herenow) | 2 | +| [tk42](https://github.com/tk42) | 2 | +| [soxipy](https://github.com/soxipy) | 2 | | [cornelk](https://github.com/cornelk) | 2 | | [gam-phon](https://github.com/gam-phon) | 2 | | [herenow](https://github.com/herenow) | 2 | diff --git a/engine/currency_state_manager.md b/engine/currency_state_manager.md index e84b08aa23d..b3ec29aa552 100644 --- a/engine/currency_state_manager.md +++ b/engine/currency_state_manager.md @@ -1,22 +1,22 @@ -# GoCryptoTrader package Currency state manager - - - - -[![Build Status](https://github.com/thrasher-corp/gocryptotrader/actions/workflows/tests.yml/badge.svg?branch=master)](https://github.com/thrasher-corp/gocryptotrader/actions/workflows/tests.yml) -[![Software License](https://img.shields.io/badge/License-MIT-orange.svg?style=flat-square)](https://github.com/thrasher-corp/gocryptotrader/blob/master/LICENSE) -[![GoDoc](https://godoc.org/github.com/thrasher-corp/gocryptotrader?status.svg)](https://godoc.org/github.com/thrasher-corp/gocryptotrader/engine/currency_state_manager) -[![Coverage Status](http://codecov.io/github/thrasher-corp/gocryptotrader/coverage.svg?branch=master)](http://codecov.io/github/thrasher-corp/gocryptotrader?branch=master) -[![Go Report Card](https://goreportcard.com/badge/github.com/thrasher-corp/gocryptotrader)](https://goreportcard.com/report/github.com/thrasher-corp/gocryptotrader) - - -This currency_state_manager package is part of the GoCryptoTrader codebase. - -## This is still in active development - -You can track ideas, planned features and what's in progress on this Trello board: [https://trello.com/b/ZAhMhpOy/gocryptotrader](https://trello.com/b/ZAhMhpOy/gocryptotrader). - -Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader Slack](https://join.slack.com/t/gocryptotrader/shared_invite/enQtNTQ5NDAxMjA2Mjc5LTc5ZDE1ZTNiOGM3ZGMyMmY1NTAxYWZhODE0MWM5N2JlZDk1NDU0YTViYzk4NTk3OTRiMDQzNGQ1YTc4YmRlMTk) +# GoCryptoTrader package Currency state manager + + + + +[![Build Status](https://github.com/thrasher-corp/gocryptotrader/actions/workflows/tests.yml/badge.svg?branch=master)](https://github.com/thrasher-corp/gocryptotrader/actions/workflows/tests.yml) +[![Software License](https://img.shields.io/badge/License-MIT-orange.svg?style=flat-square)](https://github.com/thrasher-corp/gocryptotrader/blob/master/LICENSE) +[![GoDoc](https://godoc.org/github.com/thrasher-corp/gocryptotrader?status.svg)](https://godoc.org/github.com/thrasher-corp/gocryptotrader/engine/currency_state_manager) +[![Coverage Status](http://codecov.io/github/thrasher-corp/gocryptotrader/coverage.svg?branch=master)](http://codecov.io/github/thrasher-corp/gocryptotrader?branch=master) +[![Go Report Card](https://goreportcard.com/badge/github.com/thrasher-corp/gocryptotrader)](https://goreportcard.com/report/github.com/thrasher-corp/gocryptotrader) + + +This currency_state_manager package is part of the GoCryptoTrader codebase. + +## This is still in active development + +You can track ideas, planned features and what's in progress on this Trello board: [https://trello.com/b/ZAhMhpOy/gocryptotrader](https://trello.com/b/ZAhMhpOy/gocryptotrader). + +Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader Slack](https://join.slack.com/t/gocryptotrader/shared_invite/enQtNTQ5NDAxMjA2Mjc5LTc5ZDE1ZTNiOGM3ZGMyMmY1NTAxYWZhODE0MWM5N2JlZDk1NDU0YTViYzk4NTk3OTRiMDQzNGQ1YTc4YmRlMTk) ## Current Features for Currency state manager + The state manager keeps currency states up to date, which include: @@ -27,22 +27,22 @@ Join our slack to discuss all things related to GoCryptoTrader! [GoCryptoTrader + This allows for an internal state check to compliment internal and external strategies. - -## Contribution - -Please feel free to submit any pull requests or suggest any desired features to be added. - -When submitting a PR, please abide by our coding guidelines: - -+ Code must adhere to the official Go [formatting](https://golang.org/doc/effective_go.html#formatting) guidelines (i.e. uses [gofmt](https://golang.org/cmd/gofmt/)). -+ Code must be documented adhering to the official Go [commentary](https://golang.org/doc/effective_go.html#commentary) guidelines. -+ Code must adhere to our [coding style](https://github.com/thrasher-corp/gocryptotrader/blob/master/doc/coding_style.md). -+ Pull requests need to be based on and opened against the `master` branch. - -## Donations - - - -If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to: - + +## Contribution + +Please feel free to submit any pull requests or suggest any desired features to be added. + +When submitting a PR, please abide by our coding guidelines: + ++ Code must adhere to the official Go [formatting](https://golang.org/doc/effective_go.html#formatting) guidelines (i.e. uses [gofmt](https://golang.org/cmd/gofmt/)). ++ Code must be documented adhering to the official Go [commentary](https://golang.org/doc/effective_go.html#commentary) guidelines. ++ Code must adhere to our [coding style](https://github.com/thrasher-corp/gocryptotrader/blob/master/doc/coding_style.md). ++ Pull requests need to be based on and opened against the `master` branch. + +## Donations + + + +If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to: + ***bc1qk0jareu4jytc0cfrhr5wgshsq8282awpavfahc*** From c7d7ac236a2fa5671217dfa0e13e61599aa5c3ae Mon Sep 17 00:00:00 2001 From: Samuel Reid <43227667+cranktakular@users.noreply.github.com> Date: Tue, 27 Feb 2024 15:39:44 +1100 Subject: [PATCH 31/79] Coinbase code improvements --- exchanges/asset/asset_test.go | 13 + exchanges/coinbasepro/coinbasepro.go | 1038 +++++++---------- exchanges/coinbasepro/coinbasepro_test.go | 74 +- exchanges/coinbasepro/coinbasepro_types.go | 124 +- .../coinbasepro/coinbasepro_websocket.go | 57 - exchanges/coinbasepro/coinbasepro_wrapper.go | 198 +--- 6 files changed, 574 insertions(+), 930 deletions(-) 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, From 8cdc78acc6f07882c23f3bccb5cf0b9e193bfbb4 Mon Sep 17 00:00:00 2001 From: Samuel Reid <43227667+cranktakular@users.noreply.github.com> Date: Tue, 27 Feb 2024 15:48:52 +1100 Subject: [PATCH 32/79] Providing warning about known issue --- exchanges/coinbasepro/coinbasepro_test.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/exchanges/coinbasepro/coinbasepro_test.go b/exchanges/coinbasepro/coinbasepro_test.go index 6c24075651a..5e830a08375 100644 --- a/exchanges/coinbasepro/coinbasepro_test.go +++ b/exchanges/coinbasepro/coinbasepro_test.go @@ -396,6 +396,13 @@ func TestMovePortfolioFunds(t *testing.T) { if len(portID) < 2 { t.Skip(skipInsufficientPortfolios) } + // TODO: The API's set up so that the funds on a portfolio can only be checked one portfolio at a time, + // so studiously checking for a valid portfolio to send funds from would take ages. And so, this aims to + // fail gracefully if there's not enough funds. However, as of this week, the exchange returns a 429 header, + // indicating the rate limit was hit when it wasn't, and so prompting GCT to keep ramming its head into it + // until something gives and it fails badly. I think either they'll need to fix that, we'll need to waste + // a bunch of time to fail gracefully, or we'll have to rewrite some core retry policy stuff to deal with + // cases like this. _, err = c.MovePortfolioFunds(context.Background(), testCrypto.String(), portID[0].UUID, portID[1].UUID, testAmount) if err != nil && err.Error() != errPortTransferInsufFunds { From 79ac205afcc9c2e63aac902a4351aba2c84c12d9 Mon Sep 17 00:00:00 2001 From: Samuel Reid <43227667+cranktakular@users.noreply.github.com> Date: Tue, 27 Feb 2024 15:55:40 +1100 Subject: [PATCH 33/79] Updating an error to new format --- exchanges/coinbasepro/coinbasepro_test.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/exchanges/coinbasepro/coinbasepro_test.go b/exchanges/coinbasepro/coinbasepro_test.go index 5d80fef9fd2..dffea39f764 100644 --- a/exchanges/coinbasepro/coinbasepro_test.go +++ b/exchanges/coinbasepro/coinbasepro_test.go @@ -1516,9 +1516,7 @@ func TestWsConnect(t *testing.T) { t.Error(err) } err = c.WsConnect() - if err.Error() != stream.WebsocketNotEnabled { - t.Errorf(errExpectMismatch, err, stream.WebsocketNotEnabled) - } + assert.ErrorIs(t, err, stream.ErrWebsocketNotEnabled) err = c.Websocket.Enable() assert.NoError(t, err) } From e073757f7df182a9dede840a0d64309576ed533c Mon Sep 17 00:00:00 2001 From: Samuel Reid <43227667+cranktakular@users.noreply.github.com> Date: Tue, 27 Feb 2024 16:05:00 +1100 Subject: [PATCH 34/79] Making gocritic happy --- exchanges/coinbasepro/coinbasepro_wrapper.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exchanges/coinbasepro/coinbasepro_wrapper.go b/exchanges/coinbasepro/coinbasepro_wrapper.go index 0f42e1b02c8..77b4c7a1b24 100644 --- a/exchanges/coinbasepro/coinbasepro_wrapper.go +++ b/exchanges/coinbasepro/coinbasepro_wrapper.go @@ -752,7 +752,7 @@ func (c *CoinbasePro) WithdrawCryptocurrencyFunds(ctx context.Context, withdrawR } t := time.Now().UnixNano() u := math.Float64bits(withdrawRequest.Amount) - t = t ^ int64(u) + 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, "", From a65fc38c56e6d84a1cb4d9cedd6c5f8e45546951 Mon Sep 17 00:00:00 2001 From: Samuel Reid <43227667+cranktakular@users.noreply.github.com> Date: Fri, 8 Mar 2024 09:36:08 +1100 Subject: [PATCH 35/79] Review adherence --- exchanges/coinbasepro/coinbasepro.go | 2 +- .../coinbasepro/coinbasepro_websocket.go | 22 +++++-------------- 2 files changed, 7 insertions(+), 17 deletions(-) diff --git a/exchanges/coinbasepro/coinbasepro.go b/exchanges/coinbasepro/coinbasepro.go index 588638309a6..ddbedfb43e5 100644 --- a/exchanges/coinbasepro/coinbasepro.go +++ b/exchanges/coinbasepro/coinbasepro.go @@ -1334,7 +1334,7 @@ func (p *Params) prepareDateString(startDate, endDate time.Time, labelStart, lab } p.Values.Set(labelStart, startDate.Format(time.RFC3339)) p.Values.Set(labelEnd, endDate.Format(time.RFC3339)) - return err + return nil } // PreparePagination formats pagination information in the way the exchange expects diff --git a/exchanges/coinbasepro/coinbasepro_websocket.go b/exchanges/coinbasepro/coinbasepro_websocket.go index b37bcf52550..9dc73a3edb4 100644 --- a/exchanges/coinbasepro/coinbasepro_websocket.go +++ b/exchanges/coinbasepro/coinbasepro_websocket.go @@ -454,31 +454,21 @@ func (c *CoinbasePro) sendRequest(msgType, channel string, productIDs currency.P Key: creds.Key, Timestamp: n, } - if err != nil { - return err - } - err = c.Websocket.Conn.SendJSONMessage(req) - if err != nil { - return err - } - return nil + return c.Websocket.Conn.SendJSONMessage(req) } // processBidAskArray is a helper function that turns WebsocketOrderbookDataHolder into arrays // of bids and asks func processBidAskArray(data *WebsocketOrderbookDataHolder) (bids, asks []orderbook.Item, err error) { + bids = make([]orderbook.Item, 0, len(data.Changes)) + asks = make([]orderbook.Item, 0, len(data.Changes)) for i := range data.Changes { + change := orderbook.Item{Price: data.Changes[i].PriceLevel, Amount: data.Changes[i].NewQuantity} switch data.Changes[i].Side { case "bid": - bids = append(bids, orderbook.Item{ - Price: data.Changes[i].PriceLevel, - Amount: data.Changes[i].NewQuantity, - }) + bids = append(bids, change) case "offer": - asks = append(asks, orderbook.Item{ - Price: data.Changes[i].PriceLevel, - Amount: data.Changes[i].NewQuantity, - }) + asks = append(asks, change) default: return nil, nil, errors.Errorf(errUnknownSide, data.Changes[i].Side) } From 14ab490a78db8d31428d3dc23173246d631355f6 Mon Sep 17 00:00:00 2001 From: Samuel Reid <43227667+cranktakular@users.noreply.github.com> Date: Fri, 8 Mar 2024 10:27:17 +1100 Subject: [PATCH 36/79] Endpoints moved to V3 & nil pointer fixes --- exchanges/coinbasepro/coinbasepro.go | 45 ++++++------ exchanges/coinbasepro/coinbasepro_test.go | 76 +++++++++++--------- exchanges/coinbasepro/coinbasepro_types.go | 69 ++++++++---------- exchanges/coinbasepro/coinbasepro_wrapper.go | 8 +-- 4 files changed, 95 insertions(+), 103 deletions(-) diff --git a/exchanges/coinbasepro/coinbasepro.go b/exchanges/coinbasepro/coinbasepro.go index ddbedfb43e5..451b6f73fee 100644 --- a/exchanges/coinbasepro/coinbasepro.go +++ b/exchanges/coinbasepro/coinbasepro.go @@ -54,6 +54,7 @@ const ( coinbaseConvert = "convert" coinbaseQuote = "quote" coinbaseTrade = "trade" + coinbasePaymentMethods = "payment_methods" coinbaseV2 = "/v2/" coinbaseNotifications = "notifications" coinbaseUser = "user" @@ -63,7 +64,6 @@ const ( coinbaseTransactions = "transactions" coinbaseDeposits = "deposits" coinbaseCommit = "commit" - coinbasePaymentMethods = "payment-methods" coinbaseWithdrawals = "withdrawals" coinbaseCurrencies = "currencies" coinbaseCrypto = "crypto" @@ -779,6 +779,27 @@ func (c *CoinbasePro) GetV3Time(ctx context.Context) (*ServerTimeV3, error) { coinbaseV3+coinbaseTime, nil, nil, true, &resp, nil) } +// GetAllPaymentMethods returns a list of all payment methods associated with the user's account +func (c *CoinbasePro) GetAllPaymentMethods(ctx context.Context) (*GetAllPaymentMethodsResp, error) { + var resp *GetAllPaymentMethodsResp + req := map[string]interface{}{"currency": "BTC"} + path := coinbaseV3 + coinbasePaymentMethods + return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, + path, nil, req, true, &resp, nil) +} + +// GetPaymentMethodByID returns information on a single payment method associated with the user's +// account +func (c *CoinbasePro) GetPaymentMethodByID(ctx context.Context, paymentMethodID string) (*GenPaymentMethodResp, error) { + if paymentMethodID == "" { + return nil, errPaymentMethodEmpty + } + path := coinbaseV3 + coinbasePaymentMethods + "/" + paymentMethodID + var resp *GenPaymentMethodResp + return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, + path, 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 @@ -1071,28 +1092,6 @@ func (c *CoinbasePro) GetFiatTransferByID(ctx context.Context, walletID, deposit 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 - var params Params - params.Values = url.Values{} - params.preparePagination(pag) - return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - coinbaseV2+coinbasePaymentMethods, params.Values, nil, false, &resp, nil) -} - -// GetPaymentMethodByID returns information on a single payment method associated with the user's -// account -func (c *CoinbasePro) GetPaymentMethodByID(ctx context.Context, paymentMethodID string) (*GenPaymentMethodResp, error) { - if paymentMethodID == "" { - return nil, errPaymentMethodEmpty - } - path := coinbaseV2 + coinbasePaymentMethods + "/" + paymentMethodID - var resp *GenPaymentMethodResp - return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - path, nil, nil, false, &resp, nil) -} - // GetFiatCurrencies lists currencies that Coinbase knows about func (c *CoinbasePro) GetFiatCurrencies(ctx context.Context) ([]FiatData, error) { var resp GetFiatCurrenciesResp diff --git a/exchanges/coinbasepro/coinbasepro_test.go b/exchanges/coinbasepro/coinbasepro_test.go index dffea39f764..2cdd6828586 100644 --- a/exchanges/coinbasepro/coinbasepro_test.go +++ b/exchanges/coinbasepro/coinbasepro_test.go @@ -85,6 +85,7 @@ const ( errUnrecognisedStatusType = " not recognised as status type" errFakeSide = "unknown side fakeside" errCoinbaseWSAlreadyDisabled = "websocket already disabled for exchange 'CoinbasePro'" + errInternalMessageNil = "CoinbasePro unsuccessful HTTP status code: 500 raw response: {\"error\":\"INTERNAL\",\"error_details\":\"undefined method `currency' for nil:NilClass\",\"message\":\"undefined method `currency' for nil:NilClass\"}, authenticated request failed" expectedTimestamp = "1970-01-01 00:20:34 +0000 UTC" @@ -159,7 +160,7 @@ func TestGetAccountByID(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) longResp, err := c.GetAllAccounts(context.Background(), 49, "") assert.NoError(t, err) - if len(longResp.Accounts) == 0 { + if longResp == nil || len(longResp.Accounts) == 0 { t.Fatal(errExpectedNonEmpty) } shortResp, err := c.GetAccountByID(context.Background(), longResp.Accounts[0].UUID) @@ -330,7 +331,7 @@ func TestGetFills(t *testing.T) { ordID, err := c.GetAllOrders(context.Background(), "", "", "", "", "", "", "", "", "", status, nil, 3, time.Time{}, time.Time{}) assert.NoError(t, err) - if len(ordID.Orders) == 0 { + if ordID == nil || len(ordID.Orders) == 0 { t.Skip(skipInsufficientOrders) } _, err = c.GetFills(context.Background(), ordID.Orders[0].OrderID, "", "", time.Time{}, time.Time{}, 5) @@ -344,7 +345,7 @@ func TestGetOrderByID(t *testing.T) { ordID, err := c.GetAllOrders(context.Background(), "", "", "", "", "", "", "", "", "", nil, nil, 10, time.Time{}, time.Time{}) assert.NoError(t, err) - if len(ordID.Orders) == 0 { + if ordID == nil || len(ordID.Orders) == 0 { t.Skip(skipInsufficientOrders) } resp, err := c.GetOrderByID(context.Background(), ordID.Orders[0].OrderID, ordID.Orders[0].ClientOID, @@ -591,6 +592,32 @@ func TestGetV3Time(t *testing.T) { testGetNoArgs(t, c.GetV3Time) } +func TestGetAllPaymentMethods(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + _, err := c.GetAllPaymentMethods(context.Background()) + // This endpoint appears to currently be broken for all users + if err.Error() != errInternalMessageNil { + t.Error(err) + } +} + +func TestGetPaymentMethodByID(t *testing.T) { + _, err := c.GetPaymentMethodByID(context.Background(), "") + assert.ErrorIs(t, err, errPaymentMethodEmpty) + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + // This endpoint appears to currently be broken for all users + pmID, err := c.GetAllPaymentMethods(context.Background()) + if err.Error() != errInternalMessageNil { + t.Error(err) + } + if pmID == nil || len(pmID.PaymentMethods) == 0 { + t.Skip(skipPayMethodNotFound) + } + resp, err := c.GetPaymentMethodByID(context.Background(), pmID.PaymentMethods[0].ID) + assert.NoError(t, err) + assert.NotEmpty(t, resp, errExpectedNonEmpty) +} + func TestListNotifications(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) _, err := c.ListNotifications(context.Background(), PaginationInp{}) @@ -720,7 +747,7 @@ func TestSendMoney(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) wID, err := c.GetAllWallets(context.Background(), PaginationInp{}) assert.NoError(t, err) - if len(wID.Data) < 2 { + if wID == nil || len(wID.Data) < 2 { t.Skip(skipInsufficientWallets) } var ( @@ -771,7 +798,7 @@ func TestGetTransactionByID(t *testing.T) { assert.NotEmpty(t, wID, errExpectedNonEmpty) tID, err := c.GetAllTransactions(context.Background(), wID.Data.ID, PaginationInp{}) assert.NoError(t, err) - if len(tID.Data) == 0 { + if tID == nil || len(tID.Data) == 0 { t.Skip(skipInsufficientTransactions) } resp, err := c.GetTransactionByID(context.Background(), wID.Data.ID, tID.Data[0].ID) @@ -858,7 +885,7 @@ func TestGetFiatTransferByID(t *testing.T) { assert.NotEmpty(t, wID, errExpectedNonEmpty) dID, err := c.GetAllFiatTransfers(context.Background(), wID.Data.ID, PaginationInp{}, FiatDeposit) assert.NoError(t, err) - if len(dID.Data) == 0 { + if dID == nil || len(dID.Data) == 0 { t.Skip(skipInsufficientTransactions) } resp, err := c.GetFiatTransferByID(context.Background(), wID.Data.ID, dID.Data[0].ID, FiatDeposit) @@ -869,27 +896,6 @@ func TestGetFiatTransferByID(t *testing.T) { assert.NotEmpty(t, resp, errExpectedNonEmpty) } -func TestGetAllPaymentMethods(t *testing.T) { - sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - resp, err := c.GetAllPaymentMethods(context.Background(), PaginationInp{}) - assert.NoError(t, err) - assert.NotEmpty(t, resp, errExpectedNonEmpty) -} - -func TestGetPaymentMethodByID(t *testing.T) { - _, err := c.GetPaymentMethodByID(context.Background(), "") - assert.ErrorIs(t, err, errPaymentMethodEmpty) - sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - pmID, err := c.GetAllPaymentMethods(context.Background(), PaginationInp{}) - assert.NoError(t, err) - if len(pmID.Data) == 0 { - t.Skip(skipPayMethodNotFound) - } - resp, err := c.GetPaymentMethodByID(context.Background(), pmID.Data[0].ID) - assert.NoError(t, err) - assert.NotEmpty(t, resp, errExpectedNonEmpty) -} - func TestGetFiatCurrencies(t *testing.T) { testGetNoArgs(t, c.GetFiatCurrencies) } @@ -1212,7 +1218,7 @@ func TestGetOrderInfo(t *testing.T) { if err != nil { t.Fatal(err) } - if len(ordID.Orders) == 0 { + if ordID == nil || len(ordID.Orders) == 0 { t.Skip(skipInsufficientOrders) } resp, err := c.GetOrderInfo(context.Background(), ordID.Orders[0].OrderID, testPair, asset.Spot) @@ -1243,7 +1249,7 @@ func TestWithdrawCryptocurrencyFunds(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) wallets, err := c.GetAllWallets(context.Background(), PaginationInp{}) assert.NoError(t, err) - if len(wallets.Data) == 0 { + if wallets == nil || len(wallets.Data) == 0 { t.Fatal(errExpectedNonEmpty) } for i := range wallets.Data { @@ -1690,7 +1696,7 @@ func skipTestIfLowOnFunds(t *testing.T) { t.Helper() accounts, err := c.GetAllAccounts(context.Background(), 250, "") assert.NoError(t, err) - if len(accounts.Accounts) == 0 { + if accounts == nil || len(accounts.Accounts) == 0 { t.Fatal(errExpectedNonEmpty) } var hasValidFunds bool @@ -1757,7 +1763,7 @@ func convertTestHelper(t *testing.T) (fromAccID, toAccID string) { t.Helper() accIDs, err := c.GetAllAccounts(context.Background(), 250, "") assert.NoError(t, err) - if len(accIDs.Accounts) == 0 { + if accIDs == nil || len(accIDs.Accounts) == 0 { t.Fatal(errExpectedNonEmpty) } for x := range accIDs.Accounts { @@ -1789,12 +1795,12 @@ func transferTestHelper(t *testing.T, wallets *GetAllWalletsResponse) (srcWallet if !hasValidFunds { t.Skip(skipInsufficientFunds) } - pmID, err := c.GetAllPaymentMethods(context.Background(), PaginationInp{}) + pmID, err := c.GetAllPaymentMethods(context.Background()) assert.NoError(t, err) - if len(pmID.Data) == 0 { + if pmID == nil || len(pmID.PaymentMethods) == 0 { t.Skip(skipPayMethodNotFound) } - return srcWalletID, pmID.Data[0].FiatAccount.ID + return srcWalletID, pmID.PaymentMethods[0].ID } type withdrawFiatFunc func(context.Context, *withdraw.Request) (*withdraw.ExchangeResponse, error) @@ -1827,7 +1833,7 @@ func withdrawFiatFundsHelper(t *testing.T, fn withdrawFiatFunc) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) wallets, err := c.GetAllWallets(context.Background(), PaginationInp{}) assert.NoError(t, err) - if len(wallets.Data) == 0 { + if wallets == nil || len(wallets.Data) == 0 { t.Fatal(errExpectedNonEmpty) } req.WalletID = "" diff --git a/exchanges/coinbasepro/coinbasepro_types.go b/exchanges/coinbasepro/coinbasepro_types.go index 988a91c69bc..091ed85159c 100644 --- a/exchanges/coinbasepro/coinbasepro_types.go +++ b/exchanges/coinbasepro/coinbasepro_types.go @@ -754,6 +754,34 @@ type ServerTimeV3 struct { EpochMilliseconds int64 `json:"epochMillis,string"` } +// PaymentMethodData is a sub-type that holds information on a payment method. Used in +// GetAllPaymentMethodsResp and GenPaymentMethodResp +type PaymentMethodData struct { + ID string `json:"id"` + Type string `json:"type"` + Name string `json:"name"` + Currency string `json:"currency"` + Verified bool `json:"verified"` + AllowBuy bool `json:"allow_buy"` + AllowSell bool `json:"allow_sell"` + AllowDeposit bool `json:"allow_deposit"` + AllowWithdraw bool `json:"allow_withdraw"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +// GetAllPaymentMethodsResp holds information on many payment methods. Returned by +// GetAllPaymentMethods +type GetAllPaymentMethodsResp struct { + PaymentMethods []PaymentMethodData `json:"payment_methods"` +} + +// GenPaymentMethodResp holds information on a payment method. Returned by +// GetPaymentMethodByID +type GenPaymentMethodResp struct { + PaymentMethod PaymentMethodData `json:"payment_method"` +} + // IDResource holds an ID, resource type, and associated data, used in ListNotificationsResponse, // TransactionData, DeposWithdrData, and PaymentMethodData type IDResource struct { @@ -1087,47 +1115,6 @@ type ManyDeposWithdrResp struct { Data []DeposWithdrData `json:"data"` } -// PaymentMethodData is a sub-type that holds information on a payment method. Used in -// GetAllPaymentMethodsResp and GenPaymentMethodResp -type PaymentMethodData struct { - ID string `json:"id"` - Type string `json:"type"` - Name string `json:"name"` - Currency string `json:"currency"` - PrimaryBuy bool `json:"primary_buy"` - PrimarySell bool `json:"primary_sell"` - AllowBuy bool `json:"allow_buy"` - AllowSell bool `json:"allow_sell"` - AllowDeposit bool `json:"allow_deposit"` - AllowWithdraw bool `json:"allow_withdraw"` - InstantBuy bool `json:"instant_buy"` - InstantSell bool `json:"instant_sell"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` - Resource string `json:"resource"` - ResourcePath string `json:"resource_path"` - Limits struct { - Type string `json:"type"` - Name string `json:"name"` - } `json:"limits"` - FiatAccount IDResource `json:"fiat_account"` - Verified bool `json:"verified"` - MinimumPurchaseAmount AmCur `json:"minimum_purchase_amount"` -} - -// GetAllPaymentMethodsResp holds information on many payment methods. Returned by -// GetAllPaymentMethods -type GetAllPaymentMethodsResp struct { - Pagination PaginationResp `json:"pagination"` - Data []PaymentMethodData `json:"data"` -} - -// GenPaymentMethodResp holds information on a payment method. Returned by -// GetPaymentMethodByID -type GenPaymentMethodResp struct { - Data PaymentMethodData `json:"data"` -} - // FiatData holds information on fiat currencies. Used as a sub-struct in // GetFiatCurrenciesResp, and returned by GetFiatCurrencies type FiatData struct { diff --git a/exchanges/coinbasepro/coinbasepro_wrapper.go b/exchanges/coinbasepro/coinbasepro_wrapper.go index 77b4c7a1b24..09bb1c80dea 100644 --- a/exchanges/coinbasepro/coinbasepro_wrapper.go +++ b/exchanges/coinbasepro/coinbasepro_wrapper.go @@ -772,14 +772,14 @@ func (c *CoinbasePro) WithdrawFiatFunds(ctx context.Context, withdrawRequest *wi if withdrawRequest.WalletID == "" { return nil, errWalletIDEmpty } - paymentMethods, err := c.GetAllPaymentMethods(ctx, PaginationInp{}) + paymentMethods, err := c.GetAllPaymentMethods(ctx) if err != nil { return nil, err } selectedWithdrawalMethod := PaymentMethodData{} - for i := range paymentMethods.Data { - if withdrawRequest.Fiat.Bank.BankName == paymentMethods.Data[i].Name { - selectedWithdrawalMethod = paymentMethods.Data[i] + for i := range paymentMethods.PaymentMethods { + if withdrawRequest.Fiat.Bank.BankName == paymentMethods.PaymentMethods[i].Name { + selectedWithdrawalMethod = paymentMethods.PaymentMethods[i] break } } From bee28782b7ed47c7de3ca45bff8a5bc37b984576 Mon Sep 17 00:00:00 2001 From: Samuel Reid <43227667+cranktakular@users.noreply.github.com> Date: Fri, 8 Mar 2024 12:16:20 +1100 Subject: [PATCH 37/79] Removing seemingly superfluous constant --- exchanges/coinbasepro/coinbasepro.go | 1 - 1 file changed, 1 deletion(-) diff --git a/exchanges/coinbasepro/coinbasepro.go b/exchanges/coinbasepro/coinbasepro.go index 451b6f73fee..75455b77dc7 100644 --- a/exchanges/coinbasepro/coinbasepro.go +++ b/exchanges/coinbasepro/coinbasepro.go @@ -24,7 +24,6 @@ import ( const ( coinbaseAPIURL = "https://api.coinbase.com" coinbaseproSandboxAPIURL = "https://api-public.sandbox.exchange.coinbase.com/" - coinbaseproAPIVersion = "0" coinbaseV3 = "/api/v3/brokerage/" coinbaseAccounts = "accounts" coinbaseBestBidAsk = "best_bid_ask" From 1d51a2e6ae5c98a9cd7d02cc6caadc7cc96bb93e Mon Sep 17 00:00:00 2001 From: Samuel Reid <43227667+cranktakular@users.noreply.github.com> Date: Tue, 9 Apr 2024 13:08:27 +1000 Subject: [PATCH 38/79] Glorious improvements --- exchanges/coinbasepro/coinbasepro.go | 32 +- exchanges/coinbasepro/coinbasepro_test.go | 292 +++++++++--------- .../coinbasepro/coinbasepro_websocket.go | 10 +- exchanges/coinbasepro/coinbasepro_wrapper.go | 4 +- exchanges/coinbasepro/ratelimit.go | 4 +- 5 files changed, 175 insertions(+), 167 deletions(-) diff --git a/exchanges/coinbasepro/coinbasepro.go b/exchanges/coinbasepro/coinbasepro.go index 75455b77dc7..03ab357a429 100644 --- a/exchanges/coinbasepro/coinbasepro.go +++ b/exchanges/coinbasepro/coinbasepro.go @@ -86,11 +86,7 @@ const ( startDateString = "start_date" endDateString = "end_date" - errPayMethodNotFound = "payment method '%v' not found" errIntervalNotSupported = "interval not supported" - errUnknownEndpointLimit = "unknown endpoint limit %v" - errUnknownL2DataType = "unknown l2update data type %v" - errUnknownSide = "unknown side %v" warnSequenceIssue = "Out of order sequence number. Received %v, expected %v" ) @@ -145,6 +141,13 @@ var ( errNoWalletForCurrency = errors.New("no wallet found for currency, address creation impossible") errChannelNameUnknown = errors.New("unknown channel name") errNoWalletsReturned = errors.New("no wallets returned") + errUnknownEndpointLimit = errors.New("unknown endpoint limit") + errPayMethodNotFound = errors.New("payment method not found") + errUnknownL2DataType = errors.New("unknown l2update data type") + errUnknownSide = errors.New("unknown side") + errInvalidGranularity = errors.New("invalid granularity") + errOrderFailedToCancel = errors.New("failed to cancel order") + errUnrecognisedStatusType = errors.New("unrecognised status type") ) // GetAllAccounts returns information on all trading accounts associated with the API key @@ -248,7 +251,7 @@ func (c *CoinbasePro) GetHistoricRates(ctx context.Context, productID, granulari granThirtyMin, granOneHour, granTwoHour, granSixHour, granOneDay} validGran, _ := common.InArray(granularity, allowedGranularities) if !validGran { - return nil, fmt.Errorf("invalid granularity %v, allowed granularities are: %+v", + return nil, fmt.Errorf("%w %v, allowed granularities are: %+v", errInvalidGranularity, granularity, allowedGranularities) } vals := url.Values{} @@ -481,7 +484,10 @@ func (c *CoinbasePro) PreviewOrder(ctx context.Context, productID, side, orderTy "tradable_balance": strconv.FormatFloat(tradableBalance, 'f', -1, 64), "skip_fcm_risk_check": skipFCMRiskCheck, "leverage": strconv.FormatFloat(leverage, 'f', -1, 64), - "margin_type": mt} + } + if mt != "" { + req["margin_type"] = mt + } var resp *PreviewOrderResp path := coinbaseV3 + coinbaseOrders + "/" + coinbasePreview return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, path, nil, req, true, @@ -774,8 +780,7 @@ func (c *CoinbasePro) GetConvertTradeByID(ctx context.Context, tradeID, from, to // GetV3Time returns the current server time, calling V3 of the API func (c *CoinbasePro) GetV3Time(ctx context.Context) (*ServerTimeV3, error) { var resp *ServerTimeV3 - return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - coinbaseV3+coinbaseTime, nil, nil, true, &resp, nil) + return resp, c.SendHTTPRequest(ctx, exchange.RestSpot, coinbaseV3+coinbaseTime, &resp) } // GetAllPaymentMethods returns a list of all payment methods associated with the user's account @@ -809,17 +814,6 @@ func (c *CoinbasePro) ListNotifications(ctx context.Context, pag PaginationInp) coinbaseV2+coinbaseNotifications, params.Values, nil, false, &resp, nil) } -// GetUserByID returns information about a user, given their ID -func (c *CoinbasePro) GetUserByID(ctx context.Context, userID string) (*UserResponse, error) { - if userID == "" { - return nil, errUserIDEmpty - } - path := coinbaseV2 + coinbaseUsers + "/" + userID - var resp *UserResponse - return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - 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 diff --git a/exchanges/coinbasepro/coinbasepro_test.go b/exchanges/coinbasepro/coinbasepro_test.go index 2cdd6828586..cc6f34d2405 100644 --- a/exchanges/coinbasepro/coinbasepro_test.go +++ b/exchanges/coinbasepro/coinbasepro_test.go @@ -2,7 +2,7 @@ package coinbasepro import ( "context" - "fmt" + "encoding/json" "log" "net/http" "os" @@ -31,13 +31,6 @@ import ( "github.com/thrasher-corp/gocryptotrader/portfolio/withdraw" ) -var ( - c = &CoinbasePro{} - testCrypto = currency.BTC - testFiat = currency.USD - testPair = currency.NewPairWithDelimiter(testCrypto.String(), testFiat.String(), "-") -) - // Please supply your APIKeys here for better testing const ( apiKey = "" @@ -46,10 +39,16 @@ const ( testingInSandbox = false ) +var ( + c = &CoinbasePro{} + testCrypto = currency.BTC + testFiat = currency.USD + testPair = currency.NewPairWithDelimiter(testCrypto.String(), testFiat.String(), "-") +) + // Constants used within tests const ( - // Donation address - testAddress = "bc1qk0jareu4jytc0cfrhr5wgshsq8282awpavfahc" + testAddress = "fake address" skipPayMethodNotFound = "no payment methods found, skipping" skipInsufSuitableAccs = "insufficient suitable accounts for test, skipping" @@ -60,32 +59,16 @@ const ( skipInsufficientFundsOrWallets = "insufficient funds or wallets for test, skipping" skipInsufficientTransactions = "insufficient transactions for test, skipping" - errExpectMismatch = "received: '%v' but expected: '%v'" - errExpectedNonEmpty = "expected non-empty response" - errOrder0CancelFail = "order 0 failed to cancel" - errIDNotSet = "ID not set" - errx7f = "setting proxy address error parse \"\\x7f\": net/url: invalid control character in URL" - errPortfolioNameDuplicate = `CoinbasePro unsuccessful HTTP status code: 409 raw response: {"error":"CONFLICT","error_details":"[PORTFOLIO_ERROR_CODE_ALREADY_EXISTS] the requested portfolio name already exists","message":"[PORTFOLIO_ERROR_CODE_ALREADY_EXISTS] the requested portfolio name already exists"}, authenticated request failed` - errPortTransferInsufFunds = `CoinbasePro unsuccessful HTTP status code: 429 raw response: {"error":"unknown","error_details":"[PORTFOLIO_ERROR_CODE_INSUFFICIENT_FUNDS] insufficient funds in source account","message":"[PORTFOLIO_ERROR_CODE_INSUFFICIENT_FUNDS] insufficient funds in source account"}, authenticated request failed` - errInvalidProductID = `CoinbasePro unsuccessful HTTP status code: 400 raw response: {"error":"INVALID_ARGUMENT","error_details":"valid product_id is required","message":"valid product_id is required"}, authenticated request failed` - errFeeBuilderNil = "*exchange.FeeBuilder nil pointer" - errUnsupportedAssetType = " unsupported asset type" - errUpsideUnsupported = "unsupported asset type upsideprofitcontract" - errBlorboGranularity = "invalid granularity blorbo, allowed granularities are: [ONE_MINUTE FIVE_MINUTE FIFTEEN_MINUTE THIRTY_MINUTE ONE_HOUR TWO_HOUR SIX_HOUR ONE_DAY]" - errNoEndpointPathEdgeCase3 = "no endpoint path found for the given key: EdgeCase3URL" - errJSONUnsupportedChan = "json: unsupported type: chan struct {}, authenticated request failed" - errExpectedFeeRange = "expected fee range of %v and %v, received %v" - errJSONNumberIntoString = "json: cannot unmarshal number into Go value of type string" - errParseIntValueOutOfRange = `strconv.ParseInt: parsing "922337203685477580700": value out of range` - errParseUintInvalidSyntax = `strconv.ParseUint: parsing "l": invalid syntax` - errJSONInvalidCharacter = `invalid character ':' after array element` - errL2DataMoo = "unknown l2update data type moo" - errUnrecognisedOrderType = `'' unrecognised order type` - errOrderSideInvalid = `'' order side is invalid` - errUnrecognisedStatusType = " not recognised as status type" - errFakeSide = "unknown side fakeside" - errCoinbaseWSAlreadyDisabled = "websocket already disabled for exchange 'CoinbasePro'" - errInternalMessageNil = "CoinbasePro unsuccessful HTTP status code: 500 raw response: {\"error\":\"INTERNAL\",\"error_details\":\"undefined method `currency' for nil:NilClass\",\"message\":\"undefined method `currency' for nil:NilClass\"}, authenticated request failed" + errExpectMismatch = "received: '%v' but expected: '%v'" + errExpectedNonEmpty = "expected non-empty response" + errIDNotSet = "ID not set" + errx7f = "setting proxy address error parse \"\\x7f\": net/url: invalid control character in URL" + errPortfolioNameDuplicate = `CoinbasePro unsuccessful HTTP status code: 500 raw response: {"error":"INTERNAL","error_details":"the requested portfolio name already exists","message":"the requested portfolio name already exists"}, authenticated request failed` + errPortTransferInsufFunds = `CoinbasePro unsuccessful HTTP status code: 429 raw response: {"error":"unknown","error_details":"[PORTFOLIO_ERROR_CODE_INSUFFICIENT_FUNDS] insufficient funds in source account","message":"[PORTFOLIO_ERROR_CODE_INSUFFICIENT_FUNDS] insufficient funds in source account"}, authenticated request failed` + errInvalidProductID = `CoinbasePro unsuccessful HTTP status code: 400 raw response: {"error":"INVALID_ARGUMENT","error_details":"valid product_id is required","message":"valid product_id is required"}, authenticated request failed` + errNoEndpointPathEdgeCase3 = "no endpoint path found for the given key: EdgeCase3URL" + errExpectedFeeRange = "expected fee range of %v and %v, received %v" + errUnrecognisedOrderType = `'' unrecognised order type` expectedTimestamp = "1970-01-01 00:20:34 +0000 UTC" @@ -114,7 +97,9 @@ func TestMain(m *testing.M) { err := c.API.Endpoints.SetDefaultEndpoints(map[exchange.URL]string{ exchange.RestSpot: coinbaseproSandboxAPIURL, }) - log.Fatal("failed to set sandbox endpoint", err) + if err != nil { + log.Fatal("failed to set sandbox endpoint", err) + } } cfg := config.GetConfig() err := cfg.LoadConfig("../../testdata/configtest.json", true) @@ -148,6 +133,7 @@ func TestMain(m *testing.M) { } func TestGetAllAccounts(t *testing.T) { + t.Parallel() sharedtestvalues.SkipTestIfCredentialsUnset(t, c) resp, err := c.GetAllAccounts(context.Background(), 50, "") assert.NoError(t, err) @@ -155,6 +141,7 @@ func TestGetAllAccounts(t *testing.T) { } func TestGetAccountByID(t *testing.T) { + t.Parallel() _, err := c.GetAccountByID(context.Background(), "") assert.ErrorIs(t, err, errAccountIDEmpty) sharedtestvalues.SkipTestIfCredentialsUnset(t, c) @@ -171,6 +158,7 @@ func TestGetAccountByID(t *testing.T) { } func TestGetBestBidAsk(t *testing.T) { + t.Parallel() sharedtestvalues.SkipTestIfCredentialsUnset(t, c) testPairs := []string{testPair.String(), "ETH-USD"} resp, err := c.GetBestBidAsk(context.Background(), testPairs) @@ -179,6 +167,7 @@ func TestGetBestBidAsk(t *testing.T) { } func TestGetProductBook(t *testing.T) { + t.Parallel() _, err := c.GetProductBook(context.Background(), "", 0) assert.ErrorIs(t, err, errProductIDEmpty) sharedtestvalues.SkipTestIfCredentialsUnset(t, c) @@ -188,6 +177,7 @@ func TestGetProductBook(t *testing.T) { } func TestGetAllProducts(t *testing.T) { + t.Parallel() sharedtestvalues.SkipTestIfCredentialsUnset(t, c) testPairs := []string{testPair.String(), "ETH-USD"} resp, err := c.GetAllProducts(context.Background(), 30000, 1, "SPOT", "PERPETUAL", "STATUS_ALL", @@ -197,6 +187,7 @@ func TestGetAllProducts(t *testing.T) { } func TestGetProductByID(t *testing.T) { + t.Parallel() _, err := c.GetProductByID(context.Background(), "") assert.ErrorIs(t, err, errProductIDEmpty) sharedtestvalues.SkipTestIfCredentialsUnset(t, c) @@ -206,12 +197,11 @@ func TestGetProductByID(t *testing.T) { } func TestGetHistoricRates(t *testing.T) { + t.Parallel() _, err := c.GetHistoricRates(context.Background(), "", granUnknown, time.Time{}, time.Time{}) assert.ErrorIs(t, err, errProductIDEmpty) _, err = c.GetHistoricRates(context.Background(), testPair.String(), "blorbo", time.Time{}, time.Time{}) - if err.Error() != errBlorboGranularity { - t.Errorf(errExpectMismatch, err, errBlorboGranularity) - } + assert.ErrorIs(t, err, errInvalidGranularity) sharedtestvalues.SkipTestIfCredentialsUnset(t, c) resp, err := c.GetHistoricRates(context.Background(), testPair.String(), granOneMin, time.Now().Add(-5*time.Minute), time.Now()) @@ -220,6 +210,7 @@ func TestGetHistoricRates(t *testing.T) { } func TestGetTicker(t *testing.T) { + t.Parallel() _, err := c.GetTicker(context.Background(), "", 1, time.Time{}, time.Time{}) assert.ErrorIs(t, err, errProductIDEmpty) sharedtestvalues.SkipTestIfCredentialsUnset(t, c) @@ -229,6 +220,7 @@ func TestGetTicker(t *testing.T) { } func TestPlaceOrder(t *testing.T) { + t.Parallel() _, err := c.PlaceOrder(context.Background(), "", "", "", "", "", "", "", "", 0, 0, 0, 0, false, time.Time{}) assert.ErrorIs(t, err, errClientOrderIDEmpty) _, err = c.PlaceOrder(context.Background(), "meow", "", "", "", "", "", "", "", 0, 0, 0, 0, false, time.Time{}) @@ -253,6 +245,7 @@ func TestPlaceOrder(t *testing.T) { } func TestCancelOrders(t *testing.T) { + t.Parallel() var OrderSlice []string _, err := c.CancelOrders(context.Background(), OrderSlice) assert.ErrorIs(t, err, errOrderIDEmpty) @@ -268,6 +261,7 @@ func TestCancelOrders(t *testing.T) { } func TestEditOrder(t *testing.T) { + t.Parallel() _, err := c.EditOrder(context.Background(), "", 0, 0) assert.ErrorIs(t, err, errOrderIDEmpty) _, err = c.EditOrder(context.Background(), "meow", 0, 0) @@ -285,6 +279,7 @@ func TestEditOrder(t *testing.T) { } func TestEditOrderPreview(t *testing.T) { + t.Parallel() _, err := c.EditOrderPreview(context.Background(), "", 0, 0) assert.ErrorIs(t, err, errOrderIDEmpty) _, err = c.EditOrderPreview(context.Background(), "meow", 0, 0) @@ -302,6 +297,7 @@ func TestEditOrderPreview(t *testing.T) { } func TestGetAllOrders(t *testing.T) { + t.Parallel() assets := []string{"USD"} status := make([]string, 2) _, err := c.GetAllOrders(context.Background(), "", "", "", "", "", "", "", "", "", status, assets, 0, @@ -322,6 +318,7 @@ func TestGetAllOrders(t *testing.T) { } func TestGetFills(t *testing.T) { + t.Parallel() _, err := c.GetFills(context.Background(), "", "", "", time.Unix(2, 2), time.Unix(1, 1), 0) assert.ErrorIs(t, err, common.ErrStartAfterEnd) sharedtestvalues.SkipTestIfCredentialsUnset(t, c) @@ -339,6 +336,7 @@ func TestGetFills(t *testing.T) { } func TestGetOrderByID(t *testing.T) { + t.Parallel() _, err := c.GetOrderByID(context.Background(), "", "", "") assert.ErrorIs(t, err, errOrderIDEmpty) sharedtestvalues.SkipTestIfCredentialsUnset(t, c) @@ -355,19 +353,21 @@ func TestGetOrderByID(t *testing.T) { } func TestPreviewOrder(t *testing.T) { + t.Parallel() _, err := c.PreviewOrder(context.Background(), "", "", "", "", "", 0, 0, 0, 0, 0, 0, false, false, false, time.Time{}) assert.ErrorIs(t, err, errAmountEmpty) _, err = c.PreviewOrder(context.Background(), "", "", "", "", "", 0, 1, 0, 0, 0, 0, false, false, false, time.Time{}) assert.ErrorIs(t, err, errInvalidOrderType) sharedtestvalues.SkipTestIfCredentialsUnset(t, c) skipTestIfLowOnFunds(t) - resp, err := c.PreviewOrder(context.Background(), testPair.String(), "BUY", "MARKET", "", "", 0, 1, 0, 0, 0, 0, false, - false, false, time.Time{}) + resp, err := c.PreviewOrder(context.Background(), testPair.String(), "BUY", "MARKET", "", "ISOLATED", 0, + testAmount, 0, 0, 0, 0, false, false, false, time.Time{}) assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) } func TestGetAllPortfolios(t *testing.T) { + t.Parallel() sharedtestvalues.SkipTestIfCredentialsUnset(t, c) resp, err := c.GetAllPortfolios(context.Background(), "DEFAULT") assert.NoError(t, err) @@ -375,6 +375,7 @@ func TestGetAllPortfolios(t *testing.T) { } func TestCreatePortfolio(t *testing.T) { + t.Parallel() _, err := c.CreatePortfolio(context.Background(), "") assert.ErrorIs(t, err, errNameEmpty) sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) @@ -385,6 +386,7 @@ func TestCreatePortfolio(t *testing.T) { } func TestMovePortfolioFunds(t *testing.T) { + t.Parallel() _, err := c.MovePortfolioFunds(context.Background(), "", "", "", 0) assert.ErrorIs(t, err, errPortfolioIDEmpty) _, err = c.MovePortfolioFunds(context.Background(), "", "meowPort", "woofPort", 0) @@ -412,6 +414,7 @@ func TestMovePortfolioFunds(t *testing.T) { } func TestGetPortfolioByID(t *testing.T) { + t.Parallel() _, err := c.GetPortfolioByID(context.Background(), "") assert.ErrorIs(t, err, errPortfolioIDEmpty) sharedtestvalues.SkipTestIfCredentialsUnset(t, c) @@ -428,6 +431,7 @@ func TestGetPortfolioByID(t *testing.T) { } func TestDeletePortfolio(t *testing.T) { + t.Parallel() err := c.DeletePortfolio(context.Background(), "") assert.ErrorIs(t, err, errPortfolioIDEmpty) pID := portfolioIDFromName(t, "GCT Test Portfolio To-Delete") @@ -436,6 +440,7 @@ func TestDeletePortfolio(t *testing.T) { } func TestEditPortfolio(t *testing.T) { + t.Parallel() _, err := c.EditPortfolio(context.Background(), "", "") assert.ErrorIs(t, err, errPortfolioIDEmpty) _, err = c.EditPortfolio(context.Background(), "meow", "") @@ -448,18 +453,21 @@ func TestEditPortfolio(t *testing.T) { } func TestGetFuturesBalanceSummary(t *testing.T) { + t.Parallel() sharedtestvalues.SkipTestIfCredentialsUnset(t, c) _, err := c.GetFuturesBalanceSummary(context.Background()) assert.NoError(t, err) } func TestGetAllFuturesPositions(t *testing.T) { + t.Parallel() sharedtestvalues.SkipTestIfCredentialsUnset(t, c) _, err := c.GetAllFuturesPositions(context.Background()) assert.NoError(t, err) } func TestGetFuturesPositionByID(t *testing.T) { + t.Parallel() _, err := c.GetFuturesPositionByID(context.Background(), "") assert.ErrorIs(t, err, errProductIDEmpty) sharedtestvalues.SkipTestIfCredentialsUnset(t, c) @@ -468,6 +476,7 @@ func TestGetFuturesPositionByID(t *testing.T) { } func TestScheduleFuturesSweep(t *testing.T) { + t.Parallel() sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) curSweeps, err := c.ListFuturesSweeps(context.Background()) assert.NoError(t, err) @@ -490,12 +499,14 @@ func TestScheduleFuturesSweep(t *testing.T) { } func TestListFuturesSweeps(t *testing.T) { + t.Parallel() sharedtestvalues.SkipTestIfCredentialsUnset(t, c) _, err := c.ListFuturesSweeps(context.Background()) assert.NoError(t, err) } func TestCancelPendingFuturesSweep(t *testing.T) { + t.Parallel() sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) curSweeps, err := c.ListFuturesSweeps(context.Background()) assert.NoError(t, err) @@ -518,6 +529,7 @@ func TestCancelPendingFuturesSweep(t *testing.T) { } func TestAllocatePortfolio(t *testing.T) { + t.Parallel() err := c.AllocatePortfolio(context.Background(), "", "", "", 0) assert.ErrorIs(t, err, errPortfolioIDEmpty) err = c.AllocatePortfolio(context.Background(), "meow", "", "", 0) @@ -531,6 +543,7 @@ func TestAllocatePortfolio(t *testing.T) { } func TestGetPerpetualsPortfolioSummary(t *testing.T) { + t.Parallel() _, err := c.GetPerpetualsPortfolioSummary(context.Background(), "") assert.ErrorIs(t, err, errPortfolioIDEmpty) pID := getINTXPortfolio(t) @@ -540,6 +553,7 @@ func TestGetPerpetualsPortfolioSummary(t *testing.T) { } func TestGetAllPerpetualsPositions(t *testing.T) { + t.Parallel() _, err := c.GetAllPerpetualsPositions(context.Background(), "") assert.ErrorIs(t, err, errPortfolioIDEmpty) pID := getINTXPortfolio(t) @@ -548,6 +562,7 @@ func TestGetAllPerpetualsPositions(t *testing.T) { } func TestGetPerpetualsPositionByID(t *testing.T) { + t.Parallel() _, err := c.GetPerpetualsPositionByID(context.Background(), "", "") assert.ErrorIs(t, err, errPortfolioIDEmpty) _, err = c.GetPerpetualsPositionByID(context.Background(), "meow", "") @@ -558,6 +573,7 @@ func TestGetPerpetualsPositionByID(t *testing.T) { } func TestGetTransactionSummary(t *testing.T) { + t.Parallel() _, err := c.GetTransactionSummary(context.Background(), time.Unix(2, 2), time.Unix(1, 1), "", "", "") assert.ErrorIs(t, err, common.ErrStartAfterEnd) sharedtestvalues.SkipTestIfCredentialsUnset(t, c) @@ -568,6 +584,7 @@ func TestGetTransactionSummary(t *testing.T) { } func TestCreateConvertQuote(t *testing.T) { + t.Parallel() _, err := c.CreateConvertQuote(context.Background(), "", "", "", "", 0) assert.ErrorIs(t, err, errAccountIDEmpty) _, err = c.CreateConvertQuote(context.Background(), "meow", "123", "", "", 0) @@ -588,28 +605,24 @@ func TestGetConvertTradeByID(t *testing.T) { } func TestGetV3Time(t *testing.T) { + t.Parallel() sharedtestvalues.SkipTestIfCredentialsUnset(t, c) testGetNoArgs(t, c.GetV3Time) } func TestGetAllPaymentMethods(t *testing.T) { + t.Parallel() sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - _, err := c.GetAllPaymentMethods(context.Background()) - // This endpoint appears to currently be broken for all users - if err.Error() != errInternalMessageNil { - t.Error(err) - } + testGetNoArgs(t, c.GetAllPaymentMethods) } func TestGetPaymentMethodByID(t *testing.T) { + t.Parallel() _, err := c.GetPaymentMethodByID(context.Background(), "") assert.ErrorIs(t, err, errPaymentMethodEmpty) sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - // This endpoint appears to currently be broken for all users pmID, err := c.GetAllPaymentMethods(context.Background()) - if err.Error() != errInternalMessageNil { - t.Error(err) - } + assert.NoError(t, err) if pmID == nil || len(pmID.PaymentMethods) == 0 { t.Skip(skipPayMethodNotFound) } @@ -619,36 +632,26 @@ func TestGetPaymentMethodByID(t *testing.T) { } func TestListNotifications(t *testing.T) { + t.Parallel() sharedtestvalues.SkipTestIfCredentialsUnset(t, c) _, err := c.ListNotifications(context.Background(), PaginationInp{}) assert.NoError(t, err) } -func TestGetUserByID(t *testing.T) { - _, err := c.GetUserByID(context.Background(), "") - assert.ErrorIs(t, err, errUserIDEmpty) - sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - resp, err := c.GetCurrentUser(context.Background()) - assert.NoError(t, err) - if resp == nil { - t.Fatal(errExpectedNonEmpty) - } - resp, err = c.GetUserByID(context.Background(), resp.Data.ID) - assert.NoError(t, err) - assert.NotEmpty(t, resp, errExpectedNonEmpty) -} - func TestGetCurrentUser(t *testing.T) { + t.Parallel() sharedtestvalues.SkipTestIfCredentialsUnset(t, c) testGetNoArgs(t, c.GetCurrentUser) } func TestGetAuthInfo(t *testing.T) { + t.Parallel() sharedtestvalues.SkipTestIfCredentialsUnset(t, c) testGetNoArgs(t, c.GetAuthInfo) } func TestGetAllWallets(t *testing.T) { + t.Parallel() sharedtestvalues.SkipTestIfCredentialsUnset(t, c) pagIn := PaginationInp{Limit: 2} resp, err := c.GetAllWallets(context.Background(), pagIn) @@ -664,6 +667,7 @@ func TestGetAllWallets(t *testing.T) { } func TestGetWalletByID(t *testing.T) { + t.Parallel() _, err := c.GetWalletByID(context.Background(), "", "") assert.ErrorIs(t, err, errCurrWalletConflict) sharedtestvalues.SkipTestIfCredentialsUnset(t, c) @@ -676,6 +680,7 @@ func TestGetWalletByID(t *testing.T) { } func TestCreateAddress(t *testing.T) { + t.Parallel() _, err := c.CreateAddress(context.Background(), "", "") assert.ErrorIs(t, err, errWalletIDEmpty) sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) @@ -688,6 +693,7 @@ func TestCreateAddress(t *testing.T) { } func TestGetAllAddresses(t *testing.T) { + t.Parallel() var pag PaginationInp _, err := c.GetAllAddresses(context.Background(), "", pag) assert.ErrorIs(t, err, errWalletIDEmpty) @@ -701,6 +707,7 @@ func TestGetAllAddresses(t *testing.T) { } func TestGetAddressByID(t *testing.T) { + t.Parallel() _, err := c.GetAddressByID(context.Background(), "", "") assert.ErrorIs(t, err, errWalletIDEmpty) _, err = c.GetAddressByID(context.Background(), "123", "") @@ -718,6 +725,7 @@ func TestGetAddressByID(t *testing.T) { } func TestGetAddressTransactions(t *testing.T) { + t.Parallel() _, err := c.GetAddressTransactions(context.Background(), "", "", PaginationInp{}) assert.ErrorIs(t, err, errWalletIDEmpty) _, err = c.GetAddressTransactions(context.Background(), "123", "", PaginationInp{}) @@ -734,6 +742,7 @@ func TestGetAddressTransactions(t *testing.T) { } func TestSendMoney(t *testing.T) { + t.Parallel() _, err := c.SendMoney(context.Background(), "", "", "", "", "", "", "", "", 0, false, false) assert.ErrorIs(t, err, errTransactionTypeEmpty) _, err = c.SendMoney(context.Background(), "123", "", "", "", "", "", "", "", 0, false, false) @@ -776,6 +785,7 @@ func TestSendMoney(t *testing.T) { } func TestGetAllTransactions(t *testing.T) { + t.Parallel() var pag PaginationInp _, err := c.GetAllTransactions(context.Background(), "", pag) assert.ErrorIs(t, err, errWalletIDEmpty) @@ -788,6 +798,7 @@ func TestGetAllTransactions(t *testing.T) { } func TestGetTransactionByID(t *testing.T) { + t.Parallel() _, err := c.GetTransactionByID(context.Background(), "", "") assert.ErrorIs(t, err, errWalletIDEmpty) _, err = c.GetTransactionByID(context.Background(), "123", "") @@ -807,6 +818,7 @@ func TestGetTransactionByID(t *testing.T) { } func TestFiatTransfer(t *testing.T) { + t.Parallel() _, err := c.FiatTransfer(context.Background(), "", "", "", 0, false, FiatDeposit) assert.ErrorIs(t, err, errWalletIDEmpty) _, err = c.FiatTransfer(context.Background(), "123", "", "", 0, false, FiatDeposit) @@ -829,6 +841,7 @@ func TestFiatTransfer(t *testing.T) { } func TestCommitTransfer(t *testing.T) { + t.Parallel() _, err := c.CommitTransfer(context.Background(), "", "", FiatDeposit) assert.ErrorIs(t, err, errWalletIDEmpty) _, err = c.CommitTransfer(context.Background(), "123", "", FiatDeposit) @@ -857,6 +870,7 @@ func TestCommitTransfer(t *testing.T) { } func TestGetAllFiatTransfers(t *testing.T) { + t.Parallel() var pag PaginationInp _, err := c.GetAllFiatTransfers(context.Background(), "", pag, FiatDeposit) assert.ErrorIs(t, err, errWalletIDEmpty) @@ -873,6 +887,7 @@ func TestGetAllFiatTransfers(t *testing.T) { } func TestGetFiatTransferByID(t *testing.T) { + t.Parallel() _, err := c.GetFiatTransferByID(context.Background(), "", "", FiatDeposit) assert.ErrorIs(t, err, errWalletIDEmpty) _, err = c.GetFiatTransferByID(context.Background(), "123", "", FiatDeposit) @@ -897,20 +912,24 @@ func TestGetFiatTransferByID(t *testing.T) { } func TestGetFiatCurrencies(t *testing.T) { + t.Parallel() testGetNoArgs(t, c.GetFiatCurrencies) } func TestGetCryptocurrencies(t *testing.T) { + t.Parallel() testGetNoArgs(t, c.GetCryptocurrencies) } func TestGetExchangeRates(t *testing.T) { + t.Parallel() resp, err := c.GetExchangeRates(context.Background(), "") assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) } func TestGetPrice(t *testing.T) { + t.Parallel() _, err := c.GetPrice(context.Background(), "", "") assert.ErrorIs(t, err, errInvalidPriceType) resp, err := c.GetPrice(context.Background(), testPair.String(), asset.Spot.String()) @@ -919,10 +938,12 @@ func TestGetPrice(t *testing.T) { } func TestGetV2Time(t *testing.T) { + t.Parallel() testGetNoArgs(t, c.GetV2Time) } func TestSendHTTPRequest(t *testing.T) { + t.Parallel() err := c.SendHTTPRequest(context.Background(), exchange.EdgeCase3, "", nil) if err.Error() != errNoEndpointPathEdgeCase3 { t.Errorf(errExpectMismatch, err, errNoEndpointPathEdgeCase3) @@ -930,6 +951,7 @@ func TestSendHTTPRequest(t *testing.T) { } func TestSendAuthenticatedHTTPRequest(t *testing.T) { + t.Parallel() fc := &CoinbasePro{} err := fc.SendAuthenticatedHTTPRequest(context.Background(), exchange.EdgeCase3, "", "", nil, nil, false, nil, nil) assert.ErrorIs(t, err, exchange.ErrCredentialsAreEmpty) @@ -941,16 +963,14 @@ func TestSendAuthenticatedHTTPRequest(t *testing.T) { ch := make(chan struct{}) body := map[string]interface{}{"Unmarshalable": ch} err = c.SendAuthenticatedHTTPRequest(context.Background(), exchange.RestSpot, "", "", nil, body, false, nil, nil) - if err.Error() != errJSONUnsupportedChan { - t.Errorf(errExpectMismatch, err, errJSONUnsupportedChan) - } + var targetErr *json.UnsupportedTypeError + assert.ErrorAs(t, err, &targetErr) } func TestGetFee(t *testing.T) { + t.Parallel() _, err := c.GetFee(context.Background(), nil) - if err.Error() != errFeeBuilderNil { - t.Errorf(errExpectMismatch, errFeeBuilderNil, err) - } + assert.ErrorIs(t, err, common.ErrNilPointer) feeBuilder := exchange.FeeBuilder{ FeeType: exchange.OfflineTradeFee, Amount: 1, @@ -999,6 +1019,7 @@ func TestGetFee(t *testing.T) { } func TestFetchTradablePairs(t *testing.T) { + t.Parallel() _, err := c.FetchTradablePairs(context.Background(), asset.Empty) assert.ErrorIs(t, err, asset.ErrNotSupported) sharedtestvalues.SkipTestIfCredentialsUnset(t, c) @@ -1011,12 +1032,14 @@ func TestFetchTradablePairs(t *testing.T) { } func TestUpdateTradablePairs(t *testing.T) { + t.Parallel() sharedtestvalues.SkipTestIfCredentialsUnset(t, c) err := c.UpdateTradablePairs(context.Background(), false) assert.NoError(t, err) } func TestUpdateAccountInfo(t *testing.T) { + t.Parallel() sharedtestvalues.SkipTestIfCredentialsUnset(t, c) resp, err := c.UpdateAccountInfo(context.Background(), asset.Spot) assert.NoError(t, err) @@ -1024,6 +1047,7 @@ func TestUpdateAccountInfo(t *testing.T) { } func TestFetchAccountInfo(t *testing.T) { + t.Parallel() sharedtestvalues.SkipTestIfCredentialsUnset(t, c) resp, err := c.FetchAccountInfo(context.Background(), asset.Spot) assert.NoError(t, err) @@ -1031,6 +1055,7 @@ func TestFetchAccountInfo(t *testing.T) { } func TestUpdateTickers(t *testing.T) { + t.Parallel() sharedtestvalues.SkipTestIfCredentialsUnset(t, c) err := c.UpdateTickers(context.Background(), asset.Futures) assert.NoError(t, err) @@ -1039,6 +1064,7 @@ func TestUpdateTickers(t *testing.T) { } func TestUpdateTicker(t *testing.T) { + t.Parallel() _, err := c.UpdateTicker(context.Background(), currency.Pair{}, asset.Empty) assert.ErrorIs(t, err, currency.ErrCurrencyPairEmpty) sharedtestvalues.SkipTestIfCredentialsUnset(t, c) @@ -1048,6 +1074,7 @@ func TestUpdateTicker(t *testing.T) { } func TestFetchTicker(t *testing.T) { + t.Parallel() sharedtestvalues.SkipTestIfCredentialsUnset(t, c) resp, err := c.FetchTicker(context.Background(), testPair, asset.Spot) assert.NoError(t, err) @@ -1055,6 +1082,7 @@ func TestFetchTicker(t *testing.T) { } func TestFetchOrderbook(t *testing.T) { + t.Parallel() sharedtestvalues.SkipTestIfCredentialsUnset(t, c) resp, err := c.FetchOrderbook(context.Background(), testPair, asset.Spot) assert.NoError(t, err) @@ -1062,6 +1090,7 @@ func TestFetchOrderbook(t *testing.T) { } func TestUpdateOrderbook(t *testing.T) { + t.Parallel() _, err := c.UpdateOrderbook(context.Background(), currency.Pair{}, asset.Empty) assert.ErrorIs(t, err, currency.ErrCurrencyPairEmpty) sharedtestvalues.SkipTestIfCredentialsUnset(t, c) @@ -1075,12 +1104,14 @@ func TestUpdateOrderbook(t *testing.T) { } func TestGetAccountFundingHistory(t *testing.T) { + t.Parallel() sharedtestvalues.SkipTestIfCredentialsUnset(t, c) _, err := c.GetAccountFundingHistory(context.Background()) assert.NoError(t, err) } func TestGetWithdrawalsHistory(t *testing.T) { + t.Parallel() sharedtestvalues.SkipTestIfCredentialsUnset(t, c) _, err := c.GetWithdrawalsHistory(context.Background(), currency.NewCode("meow"), asset.Spot) assert.ErrorIs(t, err, errNoMatchingWallets) @@ -1089,6 +1120,7 @@ func TestGetWithdrawalsHistory(t *testing.T) { } func TestSubmitOrder(t *testing.T) { + t.Parallel() _, err := c.SubmitOrder(context.Background(), nil) assert.ErrorIs(t, err, common.ErrNilPointer) var ord order.Submit @@ -1122,6 +1154,7 @@ func TestSubmitOrder(t *testing.T) { } func TestModifyOrder(t *testing.T) { + t.Parallel() _, err := c.ModifyOrder(context.Background(), nil) assert.ErrorIs(t, err, common.ErrNilPointer) var ord order.Modify @@ -1143,6 +1176,7 @@ func TestModifyOrder(t *testing.T) { } func TestCancelOrder(t *testing.T) { + t.Parallel() err := c.CancelOrder(context.Background(), nil) assert.ErrorIs(t, err, common.ErrNilPointer) var can order.Cancel @@ -1153,9 +1187,7 @@ func TestCancelOrder(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) can.OrderID = "0" err = c.CancelOrder(context.Background(), &can) - if err.Error() != errOrder0CancelFail { - t.Errorf(errExpectMismatch, err, errOrder0CancelFail) - } + assert.ErrorIs(t, err, errOrderFailedToCancel) skipTestIfLowOnFunds(t) resp, err := c.PlaceOrder(context.Background(), strconv.FormatInt(time.Now().UnixMilli(), 18)+"GCTCancelOrderTest", testPair.String(), order.Sell.String(), "", order.Limit.String(), "", "", "", testAmount, testPrice, 0, 9999, @@ -1169,6 +1201,7 @@ func TestCancelOrder(t *testing.T) { } func TestCancelBatchOrders(t *testing.T) { + t.Parallel() _, err := c.CancelBatchOrders(context.Background(), nil) assert.ErrorIs(t, err, errOrderIDEmpty) can := make([]order.Cancel, 1) @@ -1191,6 +1224,7 @@ func TestCancelBatchOrders(t *testing.T) { } func TestCancelAllOrders(t *testing.T) { + t.Parallel() _, err := c.CancelAllOrders(context.Background(), nil) assert.ErrorIs(t, err, common.ErrNilPointer) var can order.Cancel @@ -1212,6 +1246,7 @@ func TestCancelAllOrders(t *testing.T) { } func TestGetOrderInfo(t *testing.T) { + t.Parallel() sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) ordID, err := c.GetAllOrders(context.Background(), testPair.String(), "", "", "", "", asset.Spot.Upper(), "", "", "", nil, nil, 2, time.Time{}, time.Now()) @@ -1227,8 +1262,10 @@ func TestGetOrderInfo(t *testing.T) { } func TestGetDepositAddress(t *testing.T) { + t.Parallel() sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - _, err := c.GetDepositAddress(context.Background(), currency.NewCode("fake currency that doesn't exist"), "", "") + _, err := c.GetDepositAddress(context.Background(), currency.NewCode("fake currency that doesn't exist"), "", + "") assert.ErrorIs(t, err, errNoWalletForCurrency) resp, err := c.GetDepositAddress(context.Background(), testCrypto, "", "") assert.NoError(t, err) @@ -1236,6 +1273,7 @@ func TestGetDepositAddress(t *testing.T) { } func TestWithdrawCryptocurrencyFunds(t *testing.T) { + t.Parallel() req := withdraw.Request{} _, err := c.WithdrawCryptocurrencyFunds(context.Background(), &req) assert.ErrorIs(t, err, common.ErrExchangeNameUnset) @@ -1275,10 +1313,9 @@ func TestWithdrawFiatFundsToInternationalBank(t *testing.T) { } func TestGetFeeByType(t *testing.T) { + t.Parallel() _, err := c.GetFeeByType(context.Background(), nil) - if err.Error() != errFeeBuilderNil { - t.Errorf(errExpectMismatch, err, errFeeBuilderNil) - } + assert.ErrorIs(t, err, common.ErrNilPointer) sharedtestvalues.SkipTestIfCredentialsUnset(t, c) var feeBuilder exchange.FeeBuilder feeBuilder.FeeType = exchange.OfflineTradeFee @@ -1292,13 +1329,12 @@ func TestGetFeeByType(t *testing.T) { } func TestGetActiveOrders(t *testing.T) { + t.Parallel() _, err := c.GetActiveOrders(context.Background(), nil) assert.ErrorIs(t, err, common.ErrNilPointer) var req order.MultiOrderRequest _, err = c.GetActiveOrders(context.Background(), &req) - if err.Error() != errUnsupportedAssetType { - t.Errorf(errExpectMismatch, err, errUnsupportedAssetType) - } + assert.ErrorIs(t, err, asset.ErrNotSupported) sharedtestvalues.SkipTestIfCredentialsUnset(t, c) req.AssetType = asset.Spot req.Side = order.AnySide @@ -1311,6 +1347,7 @@ func TestGetActiveOrders(t *testing.T) { } func TestGetOrderHistory(t *testing.T) { + t.Parallel() _, err := c.GetOrderHistory(context.Background(), nil) assert.ErrorIs(t, err, order.ErrGetOrdersRequestIsNil) sharedtestvalues.SkipTestIfCredentialsUnset(t, c) @@ -1326,6 +1363,7 @@ func TestGetOrderHistory(t *testing.T) { } func TestGetHistoricCandles(t *testing.T) { + t.Parallel() _, err := c.GetHistoricCandles(context.Background(), currency.Pair{}, asset.Empty, kline.OneYear, time.Time{}, time.Time{}) assert.ErrorIs(t, err, currency.ErrCurrencyPairEmpty) @@ -1337,6 +1375,7 @@ func TestGetHistoricCandles(t *testing.T) { } func TestGetHistoricCandlesExtended(t *testing.T) { + t.Parallel() _, err := c.GetHistoricCandlesExtended(context.Background(), currency.Pair{}, asset.Empty, kline.OneYear, time.Time{}, time.Time{}) assert.ErrorIs(t, err, currency.ErrCurrencyPairEmpty) @@ -1348,24 +1387,25 @@ func TestGetHistoricCandlesExtended(t *testing.T) { } func TestValidateAPICredentials(t *testing.T) { + t.Parallel() sharedtestvalues.SkipTestIfCredentialsUnset(t, c) err := c.ValidateAPICredentials(context.Background(), asset.Spot) assert.NoError(t, err) } func TestGetServerTime(t *testing.T) { + t.Parallel() _, err := c.GetServerTime(context.Background(), 0) assert.NoError(t, err) } func TestGetLatestFundingRates(t *testing.T) { + t.Parallel() _, err := c.GetLatestFundingRates(context.Background(), nil) assert.ErrorIs(t, err, common.ErrNilPointer) req := fundingrate.LatestRateRequest{Asset: asset.UpsideProfitContract} _, err = c.GetLatestFundingRates(context.Background(), &req) - if err.Error() != errUpsideUnsupported { - t.Errorf(errExpectMismatch, err, errUpsideUnsupported) - } + assert.ErrorIs(t, err, asset.ErrNotSupported) sharedtestvalues.SkipTestIfCredentialsUnset(t, c) req.Asset = asset.Futures resp, err := c.GetLatestFundingRates(context.Background(), &req) @@ -1374,12 +1414,11 @@ func TestGetLatestFundingRates(t *testing.T) { } func TestGetFuturesContractDetails(t *testing.T) { + t.Parallel() _, err := c.GetFuturesContractDetails(context.Background(), asset.Empty) assert.ErrorIs(t, err, futures.ErrNotFuturesAsset) _, err = c.GetFuturesContractDetails(context.Background(), asset.UpsideProfitContract) - if err.Error() != errUpsideUnsupported { - t.Errorf(errExpectMismatch, err, errUpsideUnsupported) - } + assert.ErrorIs(t, err, asset.ErrNotSupported) sharedtestvalues.SkipTestIfCredentialsUnset(t, c) resp, err := c.GetFuturesContractDetails(context.Background(), asset.Futures) assert.NoError(t, err) @@ -1387,10 +1426,9 @@ func TestGetFuturesContractDetails(t *testing.T) { } func TestUpdateOrderExecutionLimits(t *testing.T) { + t.Parallel() err := c.UpdateOrderExecutionLimits(context.Background(), asset.UpsideProfitContract) - if err.Error() != errUpsideUnsupported { - t.Errorf(errExpectMismatch, err, errUpsideUnsupported) - } + assert.ErrorIs(t, err, asset.ErrNotSupported) sharedtestvalues.SkipTestIfCredentialsUnset(t, c) err = c.UpdateOrderExecutionLimits(context.Background(), asset.Futures) assert.NoError(t, err) @@ -1412,13 +1450,10 @@ func TestUnixTimestampUnmarshalJSON(t *testing.T) { t.Parallel() var u UnixTimestamp err := u.UnmarshalJSON([]byte("0")) - if err.Error() != errJSONNumberIntoString { - t.Errorf(errExpectMismatch, err, errJSONNumberIntoString) - } + var targetErr *json.UnmarshalTypeError + assert.ErrorAs(t, err, &targetErr) err = u.UnmarshalJSON([]byte("\"922337203685477580700\"")) - if err.Error() != errParseIntValueOutOfRange { - t.Errorf(errExpectMismatch, err, errParseIntValueOutOfRange) - } + assert.ErrorIs(t, err, strconv.ErrRange) err = u.UnmarshalJSON([]byte("\"1234\"")) assert.NoError(t, err) } @@ -1518,9 +1553,7 @@ func TestStringToFloatPtr(t *testing.T) { func TestWsConnect(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) err := c.Websocket.Disable() - if err != nil && err.Error() != errCoinbaseWSAlreadyDisabled { - t.Error(err) - } + assert.ErrorIs(t, err, stream.ErrAlreadyDisabled) err = c.WsConnect() assert.ErrorIs(t, err, stream.ErrWebsocketNotEnabled) err = c.Websocket.Enable() @@ -1537,9 +1570,7 @@ func TestWsHandleData(t *testing.T) { assert.ErrorIs(t, err, jsonparser.KeyPathNotFoundError) mockJSON := []byte(`{"sequence_num": "l"}`) _, err = c.wsHandleData(mockJSON, 0) - if err.Error() != errParseUintInvalidSyntax { - t.Errorf(errExpectMismatch, err, errParseUintInvalidSyntax) - } + assert.ErrorIs(t, err, strconv.ErrSyntax) mockJSON = []byte(`{"sequence_num": 1, /\\/"""}`) _, err = c.wsHandleData(mockJSON, 0) assert.ErrorIs(t, err, jsonparser.KeyPathNotFoundError) @@ -1551,9 +1582,8 @@ func TestWsHandleData(t *testing.T) { assert.ErrorIs(t, err, jsonparser.UnknownValueTypeError) mockJSON = []byte(`{"sequence_num": 0, "channel": "status", "events": ["type": 1234]}`) _, err = c.wsHandleData(mockJSON, 0) - if err.Error() != errJSONInvalidCharacter { - t.Errorf(errExpectMismatch, err, errJSONInvalidCharacter) - } + var targetErr *json.SyntaxError + assert.ErrorAs(t, err, &targetErr) mockJSON = []byte(`{"sequence_num": 0, "channel": "status", "events": [{"type": "moo"}]}`) _, err = c.wsHandleData(mockJSON, 0) assert.NoError(t, err) @@ -1562,9 +1592,7 @@ func TestWsHandleData(t *testing.T) { assert.NoError(t, err) mockJSON = []byte(`{"sequence_num": 0, "channel": "ticker", "events": ["type": ""}]}`) _, err = c.wsHandleData(mockJSON, 0) - if err.Error() != errJSONInvalidCharacter { - t.Errorf(errExpectMismatch, err, errJSONInvalidCharacter) - } + assert.ErrorAs(t, err, &targetErr) mockJSON = []byte(`{"sequence_num": 0, "channel": "ticker", "events": [{"type": "moo", "tickers": [{"price": "1.1"}]}]}`) _, err = c.wsHandleData(mockJSON, 0) assert.ErrorIs(t, err, jsonparser.KeyPathNotFoundError) @@ -1573,9 +1601,7 @@ func TestWsHandleData(t *testing.T) { assert.NoError(t, err) mockJSON = []byte(`{"sequence_num": 0, "channel": "candles", "events": ["type": ""}]}`) _, err = c.wsHandleData(mockJSON, 0) - if err.Error() != errJSONInvalidCharacter { - t.Errorf(errExpectMismatch, err, errJSONInvalidCharacter) - } + assert.ErrorAs(t, err, &targetErr) mockJSON = []byte(`{"sequence_num": 0, "channel": "candles", "events": [{"type": "moo", "candles": [{"low": "1.1"}]}]}`) _, err = c.wsHandleData(mockJSON, 0) assert.ErrorIs(t, err, jsonparser.KeyPathNotFoundError) @@ -1584,25 +1610,19 @@ func TestWsHandleData(t *testing.T) { assert.NoError(t, err) mockJSON = []byte(`{"sequence_num": 0, "channel": "market_trades", "events": ["type": ""}]}`) _, err = c.wsHandleData(mockJSON, 0) - if err.Error() != errJSONInvalidCharacter { - t.Errorf(errExpectMismatch, err, errJSONInvalidCharacter) - } + assert.ErrorAs(t, err, &targetErr) mockJSON = []byte(`{"sequence_num": 0, "channel": "market_trades", "events": [{"type": "moo", "trades": [{"price": "1.1"}]}]}`) _, err = c.wsHandleData(mockJSON, 0) assert.NoError(t, err) mockJSON = []byte(`{"sequence_num": 0, "channel": "l2_data", "events": ["type": ""}]}`) _, err = c.wsHandleData(mockJSON, 0) - if err.Error() != errJSONInvalidCharacter { - t.Errorf(errExpectMismatch, err, errJSONInvalidCharacter) - } + assert.ErrorAs(t, err, &targetErr) mockJSON = []byte(`{"sequence_num": 0, "channel": "l2_data", "events": [{"type": "moo", "updates": [{"price_level": "1.1"}]}]}`) _, err = c.wsHandleData(mockJSON, 0) assert.ErrorIs(t, err, jsonparser.KeyPathNotFoundError) mockJSON = []byte(`{"sequence_num": 0, "channel": "l2_data", "timestamp": "2006-01-02T15:04:05Z", "events": [{"type": "moo", "updates": [{"price_level": "1.1"}]}]}`) _, err = c.wsHandleData(mockJSON, 0) - if err.Error() != errL2DataMoo { - t.Errorf(errExpectMismatch, err, errL2DataMoo) - } + assert.ErrorIs(t, err, errUnknownL2DataType) mockJSON = []byte(`{"sequence_num": 0, "channel": "l2_data", "timestamp": "2006-01-02T15:04:05Z", "events": [{"type": "snapshot", "product_id": "BTC-USD", "updates": [{"side": "bid", "price_level": "1.1", "new_quantity": "2.2"}]}]}`) _, err = c.wsHandleData(mockJSON, 0) assert.NoError(t, err) @@ -1611,9 +1631,7 @@ func TestWsHandleData(t *testing.T) { assert.NoError(t, err) mockJSON = []byte(`{"sequence_num": 0, "channel": "user", "events": ["type": ""}]}`) _, err = c.wsHandleData(mockJSON, 0) - if err.Error() != errJSONInvalidCharacter { - t.Errorf(errExpectMismatch, err, errJSONInvalidCharacter) - } + assert.ErrorAs(t, err, &targetErr) mockJSON = []byte(`{"sequence_num": 0, "channel": "user", "events": [{"type": "moo", "orders": [{"total_fees": "1.1"}]}]}`) _, err = c.wsHandleData(mockJSON, 0) if err.Error() != errUnrecognisedOrderType { @@ -1621,14 +1639,10 @@ func TestWsHandleData(t *testing.T) { } mockJSON = []byte(`{"sequence_num": 0, "channel": "user", "events": [{"type": "moo", "orders": [{"total_fees": "1.1", "order_type": "ioc"}]}]}`) _, err = c.wsHandleData(mockJSON, 0) - if err.Error() != errOrderSideInvalid { - t.Errorf(errExpectMismatch, err, errOrderSideInvalid) - } + assert.ErrorIs(t, err, order.ErrSideIsInvalid) mockJSON = []byte(`{"sequence_num": 0, "channel": "user", "events": [{"type": "moo", "orders": [{"total_fees": "1.1", "order_type": "ioc", "order_side": "buy"}]}]}`) _, err = c.wsHandleData(mockJSON, 0) - if err.Error() != errUnrecognisedStatusType { - t.Errorf(errExpectMismatch, err, errUnrecognisedStatusType) - } + assert.ErrorIs(t, err, errUnrecognisedStatusType) mockJSON = []byte(`{"sequence_num": 0, "channel": "user", "events": [{"type": "moo", "orders": [{"total_fees": "1.1", "order_type": "ioc", "order_side": "buy", "status": "done"}]}]}`) _, err = c.wsHandleData(mockJSON, 0) assert.NoError(t, err) @@ -1641,13 +1655,9 @@ func TestProcessSnapshotUpdate(t *testing.T) { req := WebsocketOrderbookDataHolder{Changes: []WebsocketOrderbookData{{Side: "fakeside", PriceLevel: 1.1, NewQuantity: 2.2}}, ProductID: currency.NewBTCUSD()} err := c.ProcessSnapshot(&req, time.Time{}) - if err.Error() != errFakeSide { - t.Errorf(errExpectMismatch, err, errFakeSide) - } + assert.ErrorIs(t, err, errUnknownSide) err = c.ProcessUpdate(&req, time.Time{}) - if err.Error() != errFakeSide { - t.Errorf(errExpectMismatch, err, errFakeSide) - } + assert.ErrorIs(t, err, errUnknownSide) req.Changes[0].Side = "offer" err = c.ProcessSnapshot(&req, time.Now()) assert.NoError(t, err) @@ -1701,7 +1711,8 @@ func skipTestIfLowOnFunds(t *testing.T) { } var hasValidFunds bool for i := range accounts.Accounts { - if accounts.Accounts[i].Currency == testCrypto.String() && accounts.Accounts[i].AvailableBalance.Value > testAmount*100 { + if accounts.Accounts[i].Currency == testCrypto.String() && + accounts.Accounts[i].AvailableBalance.Value > testAmount*100 { hasValidFunds = true } } @@ -1807,6 +1818,7 @@ type withdrawFiatFunc func(context.Context, *withdraw.Request) (*withdraw.Exchan func withdrawFiatFundsHelper(t *testing.T, fn withdrawFiatFunc) { t.Helper() + t.Parallel() req := withdraw.Request{} _, err := fn(context.Background(), &req) assert.ErrorIs(t, err, common.ErrExchangeNameUnset) @@ -1825,11 +1837,8 @@ func withdrawFiatFundsHelper(t *testing.T, fn withdrawFiatFunc) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) req.WalletID = "meow" req.Fiat.Bank.BankName = "GCT's Officially Fake and Not Real Test Bank" - expectedError := fmt.Sprintf(errPayMethodNotFound, req.Fiat.Bank.BankName) _, err = fn(context.Background(), &req) - if err.Error() != expectedError { - t.Errorf(errExpectMismatch, err, expectedError) - } + assert.ErrorIs(t, err, errPayMethodNotFound) sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) wallets, err := c.GetAllWallets(context.Background(), PaginationInp{}) assert.NoError(t, err) @@ -1853,7 +1862,7 @@ func withdrawFiatFundsHelper(t *testing.T, fn withdrawFiatFunc) { } type getNoArgsResp interface { - *ServerTimeV3 | *UserResponse | *AuthResponse | []FiatData | + *ServerTimeV3 | *GetAllPaymentMethodsResp | *UserResponse | *AuthResponse | []FiatData | []CryptoData | *ServerTimeV2 } @@ -1870,6 +1879,7 @@ type genConvertTestFunc func(context.Context, string, string, string) (*ConvertR func convertTestShared(t *testing.T, f genConvertTestFunc) { t.Helper() + t.Parallel() _, err := f(context.Background(), "", "", "") assert.ErrorIs(t, err, errTransactionIDEmpty) _, err = f(context.Background(), "meow", "", "") diff --git a/exchanges/coinbasepro/coinbasepro_websocket.go b/exchanges/coinbasepro/coinbasepro_websocket.go index 9dc73a3edb4..4254aeccd1d 100644 --- a/exchanges/coinbasepro/coinbasepro_websocket.go +++ b/exchanges/coinbasepro/coinbasepro_websocket.go @@ -209,7 +209,7 @@ func (c *CoinbasePro) wsHandleData(respRaw []byte, seqCount uint64) (string, err case "update": err = c.ProcessUpdate(&wsL2[i], timestamp) default: - err = errors.Errorf(errUnknownL2DataType, wsL2[i].Type) + err = fmt.Errorf("%w %v", errUnknownL2DataType, wsL2[i].Type) } if err != nil { return warnString, err @@ -454,6 +454,10 @@ func (c *CoinbasePro) sendRequest(msgType, channel string, productIDs currency.P Key: creds.Key, Timestamp: n, } + err = c.InitiateRateLimit(context.Background(), WSRate) + if err != nil { + return fmt.Errorf("failed to rate limit HTTP request: %w", err) + } return c.Websocket.Conn.SendJSONMessage(req) } @@ -470,7 +474,7 @@ func processBidAskArray(data *WebsocketOrderbookDataHolder) (bids, asks []orderb case "offer": asks = append(asks, change) default: - return nil, nil, errors.Errorf(errUnknownSide, data.Changes[i].Side) + return nil, nil, fmt.Errorf("%w %v", errUnknownSide, data.Changes[i].Side) } } return bids, asks, nil @@ -491,7 +495,7 @@ func statusToStandardStatus(stat string) (order.Status, error) { case "change", "activate": return order.Active, nil default: - return order.UnknownStatus, fmt.Errorf("%s not recognised as status type", stat) + return order.UnknownStatus, fmt.Errorf("%w %v", errUnrecognisedStatusType, stat) } } diff --git a/exchanges/coinbasepro/coinbasepro_wrapper.go b/exchanges/coinbasepro/coinbasepro_wrapper.go index 09bb1c80dea..e1a211e75f2 100644 --- a/exchanges/coinbasepro/coinbasepro_wrapper.go +++ b/exchanges/coinbasepro/coinbasepro_wrapper.go @@ -620,7 +620,7 @@ func (c *CoinbasePro) CancelOrder(ctx context.Context, o *order.Cancel) error { return err } if resp.Status[o.OrderID] != order.Cancelled.String() { - return fmt.Errorf("order %s failed to cancel", o.OrderID) + return fmt.Errorf("%w %v", errOrderFailedToCancel, o.OrderID) } return err } @@ -784,7 +784,7 @@ func (c *CoinbasePro) WithdrawFiatFunds(ctx context.Context, withdrawRequest *wi } } if selectedWithdrawalMethod.ID == "" { - return nil, fmt.Errorf(errPayMethodNotFound, withdrawRequest.Fiat.Bank.BankName) + return nil, fmt.Errorf("%w %v", errPayMethodNotFound, withdrawRequest.Fiat.Bank.BankName) } resp, err := c.FiatTransfer(ctx, withdrawRequest.WalletID, withdrawRequest.Currency.String(), selectedWithdrawalMethod.ID, withdrawRequest.Amount, true, FiatWithdrawal) diff --git a/exchanges/coinbasepro/ratelimit.go b/exchanges/coinbasepro/ratelimit.go index 38181d951ff..f252a26cfee 100644 --- a/exchanges/coinbasepro/ratelimit.go +++ b/exchanges/coinbasepro/ratelimit.go @@ -2,9 +2,9 @@ package coinbasepro import ( "context" + "fmt" "time" - "github.com/pkg/errors" "github.com/thrasher-corp/gocryptotrader/exchanges/request" "golang.org/x/time/rate" ) @@ -45,7 +45,7 @@ func (r *RateLimit) Limit(ctx context.Context, f request.EndpointLimit) error { case WSRate: return r.RateLimWS.Wait(ctx) default: - return errors.Errorf(errUnknownEndpointLimit, f) + return fmt.Errorf("%w %v", errUnknownEndpointLimit, f) } } From 4d1afc2a107d4211a889471b479f4a0a796b29b3 Mon Sep 17 00:00:00 2001 From: Samuel Reid <43227667+cranktakular@users.noreply.github.com> Date: Mon, 15 Apr 2024 09:12:52 +1000 Subject: [PATCH 39/79] Removing unused error --- exchanges/coinbasepro/coinbasepro.go | 1 - 1 file changed, 1 deletion(-) diff --git a/exchanges/coinbasepro/coinbasepro.go b/exchanges/coinbasepro/coinbasepro.go index 03ab357a429..9a0e56e4f8c 100644 --- a/exchanges/coinbasepro/coinbasepro.go +++ b/exchanges/coinbasepro/coinbasepro.go @@ -116,7 +116,6 @@ var ( errProductIDEmpty = errors.New("product id cannot be empty") errOrderIDEmpty = errors.New("order ids cannot be empty") errOpenPairWithOtherTypes = errors.New("cannot pair open orders with other order types") - errUserIDEmpty = errors.New("user id cannot be empty") errSizeAndPriceZero = errors.New("size and price cannot both be 0") errCurrencyEmpty = errors.New("currency cannot be empty") errCurrWalletConflict = errors.New("exactly one of walletID and currency must be specified") From 55596d819a24c744ec30bcd6ecf887d142f7201f Mon Sep 17 00:00:00 2001 From: Samuel Reid <43227667+cranktakular@users.noreply.github.com> Date: Mon, 15 Apr 2024 17:26:08 +1000 Subject: [PATCH 40/79] Partial public endpoint addition --- exchanges/coinbasepro/coinbasepro.go | 248 ++++++++++++++++++- exchanges/coinbasepro/coinbasepro_test.go | 92 ++++++- exchanges/coinbasepro/coinbasepro_types.go | 186 +++++++++++++- exchanges/coinbasepro/coinbasepro_wrapper.go | 9 +- exchanges/coinbasepro/ratelimit.go | 20 +- 5 files changed, 537 insertions(+), 18 deletions(-) diff --git a/exchanges/coinbasepro/coinbasepro.go b/exchanges/coinbasepro/coinbasepro.go index 9a0e56e4f8c..1fb1db3b5d5 100644 --- a/exchanges/coinbasepro/coinbasepro.go +++ b/exchanges/coinbasepro/coinbasepro.go @@ -10,8 +10,10 @@ import ( "net/http" "net/url" "strconv" + "strings" "time" + "github.com/gofrs/uuid" "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/common/crypto" "github.com/thrasher-corp/gocryptotrader/common/key" @@ -23,6 +25,7 @@ import ( const ( coinbaseAPIURL = "https://api.coinbase.com" + coinbaseV1APIURL = "https://api.exchange.coinbase.com/" coinbaseproSandboxAPIURL = "https://api-public.sandbox.exchange.coinbase.com/" coinbaseV3 = "/api/v3/brokerage/" coinbaseAccounts = "accounts" @@ -69,6 +72,11 @@ const ( coinbaseExchangeRates = "exchange-rates" coinbasePrices = "prices" coinbaseTime = "time" + coinbaseVolumeSummary = "volume-summary" + coinbaseBook = "book" + coinbaseStats = "stats" + coinbaseTrades = "trades" + coinbaseWrappedAssets = "wrapped-assets" pageNone = "" pageBefore = "before" @@ -147,6 +155,10 @@ var ( errInvalidGranularity = errors.New("invalid granularity") errOrderFailedToCancel = errors.New("failed to cancel order") errUnrecognisedStatusType = errors.New("unrecognised status type") + errPairEmpty = errors.New("pair cannot be empty") + errStringConvert = errors.New("unable to convert into string value") + errFloatConvert = errors.New("unable to convert into float64 value") + errConvertGen = errors.New("unable to convert value") ) // GetAllAccounts returns information on all trading accounts associated with the API key @@ -186,8 +198,8 @@ func (c *CoinbasePro) GetBestBidAsk(ctx context.Context, products []string) ([]P 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) { +// GetProductBookV3 returns a list of bids/asks for a single product +func (c *CoinbasePro) GetProductBookV3(ctx context.Context, productID string, limit uint16) (*ProductBook, error) { if productID == "" { return nil, errProductIDEmpty } @@ -1127,6 +1139,232 @@ func (c *CoinbasePro) GetV2Time(ctx context.Context) (*ServerTimeV2, error) { return resp, c.SendHTTPRequest(ctx, exchange.RestSpot, coinbaseV2+coinbaseTime, &resp) } +// GetAllCurrencies returns a list of all currencies that Coinbase knows about. These aren't necessarily tradable +func (c *CoinbasePro) GetAllCurrencies(ctx context.Context) ([]CurrencyData, error) { + var resp []CurrencyData + return resp, c.SendHTTPRequest(ctx, exchange.RestSpotSupplementary, coinbaseCurrencies, &resp) +} + +// GetACurrency returns information on a single currency specified by the user +func (c *CoinbasePro) GetACurrency(ctx context.Context, currency string) (*CurrencyData, error) { + if currency == "" { + return nil, errCurrencyEmpty + } + var resp *CurrencyData + path := coinbaseCurrencies + "/" + currency + return resp, c.SendHTTPRequest(ctx, exchange.RestSpotSupplementary, path, &resp) +} + +// GetAllTradingPairs returns a list of currency pairs which are available for trading +func (c *CoinbasePro) GetAllTradingPairs(ctx context.Context, pairType string) ([]PairData, error) { + var resp []PairData + vals := url.Values{} + if pairType != "" { + vals.Set("type", pairType) + } + path := common.EncodeURLValues(coinbaseProducts, vals) + return resp, c.SendHTTPRequest(ctx, exchange.RestSpotSupplementary, path, &resp) +} + +// GetAllPairVolumes returns a list of currency pairs and their associated volumes +func (c *CoinbasePro) GetAllPairVolumes(ctx context.Context) ([]PairVolumeData, error) { + var resp []PairVolumeData + path := coinbaseProducts + "/" + coinbaseVolumeSummary + return resp, c.SendHTTPRequest(ctx, exchange.RestSpotSupplementary, path, &resp) +} + +// GetPairDetails returns information on a single currency pair +func (c *CoinbasePro) GetPairDetails(ctx context.Context, pair string) (*PairData, error) { + if pair == "" { + return nil, errPairEmpty + } + var resp *PairData + path := coinbaseProducts + "/" + pair + return resp, c.SendHTTPRequest(ctx, exchange.RestSpotSupplementary, path, &resp) +} + +// GetProductBookV1 returns the order book for the specified currency pair. Level 1 only returns the best bids and asks, +// Level 2 returns the full order book with orders at the same price aggregated, Level 3 returns the full +// non-aggregated order book. +func (c *CoinbasePro) GetProductBookV1(ctx context.Context, pair string, level uint8) (*OrderBook, error) { + if pair == "" { + return nil, errPairEmpty + } + var resp OrderBookResp + vals := url.Values{} + vals.Set("level", strconv.FormatUint(uint64(level), 10)) + path := common.EncodeURLValues(coinbaseProducts+"/"+pair+"/"+coinbaseBook, vals) + err := c.SendHTTPRequest(ctx, exchange.RestSpotSupplementary, path, &resp) + if err != nil { + return nil, err + } + ob := &OrderBook{ + Sequence: resp.Sequence, + Bids: make([]Orders, len(resp.Bids)), + Asks: make([]Orders, len(resp.Asks)), + AuctionMode: resp.AuctionMode, + Auction: resp.Auction, + Time: resp.Time, + } + for i := range resp.Bids { + tempS1, ok := resp.Bids[i][0].(string) + if !ok { + return nil, fmt.Errorf("%w, %v", errStringConvert, resp.Bids[i][0]) + } + tempF1, err := strconv.ParseFloat(tempS1, 64) + if err != nil { + return nil, err + } + tempS2, ok := resp.Bids[i][1].(string) + if !ok { + return nil, fmt.Errorf("%w, %v", errStringConvert, resp.Bids[i][1]) + } + tempF2, err := strconv.ParseFloat(tempS2, 64) + if err != nil { + return nil, err + } + switch tempV := resp.Bids[i][2].(type) { + case string: + tempU, err := uuid.FromString(tempV) + if err != nil { + return nil, err + } + ob.Bids[i] = Orders{Price: tempF1, Size: tempF2, OrderCount: 1, OrderID: tempU} + case float64: + tempU := uint64(tempV) + ob.Bids[i] = Orders{Price: tempF1, Size: tempF2, OrderCount: tempU} + } + } + for i := range resp.Asks { + tempS1, ok := resp.Asks[i][0].(string) + if !ok { + return nil, fmt.Errorf("%w, %v", errStringConvert, resp.Asks[i][0]) + } + tempF1, err := strconv.ParseFloat(tempS1, 64) + if err != nil { + return nil, err + } + tempS2, ok := resp.Asks[i][1].(string) + if !ok { + return nil, fmt.Errorf("%w, %v", errStringConvert, resp.Asks[i][1]) + } + tempF2, err := strconv.ParseFloat(tempS2, 64) + if err != nil { + return nil, err + } + switch tempV := resp.Asks[i][2].(type) { + case string: + tempU, err := uuid.FromString(tempV) + if err != nil { + return nil, err + } + ob.Asks[i] = Orders{Price: tempF1, Size: tempF2, OrderCount: 1, OrderID: tempU} + case float64: + tempU := uint64(tempV) + ob.Asks[i] = Orders{Price: tempF1, Size: tempF2, OrderCount: tempU} + } + } + return ob, nil +} + +// GetProductCandles returns historical market data for the specified currency pair. +func (c *CoinbasePro) GetProductCandles(ctx context.Context, pair string, granularity uint32, startTime, endTime time.Time) error { + if pair == "" { + return errPairEmpty + } + var params Params + params.Values = url.Values{} + params.prepareDateString(startTime, endTime, "start", "end") + if granularity != 0 { + params.Values.Set("granularity", strconv.FormatUint(uint64(granularity), 10)) + } + path := common.EncodeURLValues(coinbaseProducts+"/"+pair+"/"+coinbaseCandles, params.Values) + var resp []RawCandles + err := c.SendHTTPRequest(ctx, exchange.RestSpotSupplementary, path, &resp) + if err != nil { + return err + } + candles := make([]Candle, len(resp)) + for i := range resp { + f1, ok := resp[i][0].(float64) + if !ok { + return fmt.Errorf("%w, %v", errFloatConvert, resp[i][0]) + } + ti := int64(f1) + t := time.Unix(ti, 0) + f2, ok := resp[i][1].(float64) + if !ok { + return fmt.Errorf("%w, %v", errFloatConvert, resp[i][1]) + } + f3, ok := resp[i][2].(float64) + if !ok { + return fmt.Errorf("%w, %v", errFloatConvert, resp[i][2]) + } + f4, ok := resp[i][3].(float64) + if !ok { + return fmt.Errorf("%w, %v", errFloatConvert, resp[i][3]) + } + f5, ok := resp[i][4].(float64) + if !ok { + return fmt.Errorf("%w, %v", errFloatConvert, resp[i][4]) + } + f6, ok := resp[i][5].(float64) + if !ok { + return fmt.Errorf("%w, %v", errFloatConvert, resp[i][5]) + } + candles[i] = Candle{ + Time: t, + Low: f2, + High: f3, + Open: f4, + Close: f5, + Volume: f6, + } + } + return nil +} + +// GetProductStats returns information on a specific pair's price and volume +func (c *CoinbasePro) GetProductStats(ctx context.Context, pair string) (*ProductStats, error) { + if pair == "" { + return nil, errPairEmpty + } + path := coinbaseProducts + "/" + pair + "/" + coinbaseStats + var resp *ProductStats + return resp, c.SendHTTPRequest(ctx, exchange.RestSpotSupplementary, path, &resp) +} + +// GetProductTicker returns the ticker for the specified currency pair +func (c *CoinbasePro) GetProductTicker(ctx context.Context, pair string) (*ProductTicker, error) { + if pair == "" { + return nil, errPairEmpty + } + path := coinbaseProducts + "/" + pair + "/" + coinbaseTicker + var resp *ProductTicker + return resp, c.SendHTTPRequest(ctx, exchange.RestSpotSupplementary, path, &resp) +} + +// GetProductTrades returns a list of the latest traides for a pair +func (c *CoinbasePro) GetProductTrades(ctx context.Context, pair, step, direction string, limit int64) ([]ProductTrades, error) { + if pair == "" { + return nil, errPairEmpty + } + vals := url.Values{} + if step != "" { + vals.Set(direction, step) + } + vals.Set("limit", strconv.FormatInt(limit, 10)) + path := common.EncodeURLValues(coinbaseProducts+"/"+pair+"/"+coinbaseTrades, vals) + var resp []ProductTrades + return resp, c.SendHTTPRequest(ctx, exchange.RestSpotSupplementary, path, &resp) +} + +// GetAllWrappedAssets returns a list of supported wrapped assets +func (c *CoinbasePro) GetAllWrappedAssets(ctx context.Context) (*AllWrappedAssets, error) { + var resp *AllWrappedAssets + return resp, c.SendHTTPRequest(ctx, exchange.RestSpotSupplementary, coinbaseWrappedAssets, &resp) +} + // SendHTTPRequest sends an unauthenticated HTTP request func (c *CoinbasePro) SendHTTPRequest(ctx context.Context, ep exchange.URL, path string, result interface{}) error { endpoint, err := c.API.Endpoints.GetURL(ep) @@ -1141,7 +1379,11 @@ 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) { + rLim := PubRate + if strings.Contains(path, coinbaseV2) { + rLim = V2Rate + } + return c.SendPayload(ctx, rLim, func() (*request.Item, error) { return item, nil }, request.UnauthenticatedRequest) } diff --git a/exchanges/coinbasepro/coinbasepro_test.go b/exchanges/coinbasepro/coinbasepro_test.go index cc6f34d2405..39a91d2064d 100644 --- a/exchanges/coinbasepro/coinbasepro_test.go +++ b/exchanges/coinbasepro/coinbasepro_test.go @@ -168,10 +168,10 @@ func TestGetBestBidAsk(t *testing.T) { func TestGetProductBook(t *testing.T) { t.Parallel() - _, err := c.GetProductBook(context.Background(), "", 0) + _, err := c.GetProductBookV3(context.Background(), "", 0) assert.ErrorIs(t, err, errProductIDEmpty) sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - resp, err := c.GetProductBook(context.Background(), testPair.String(), 2) + resp, err := c.GetProductBookV3(context.Background(), testPair.String(), 2) assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) } @@ -942,6 +942,92 @@ func TestGetV2Time(t *testing.T) { testGetNoArgs(t, c.GetV2Time) } +func TestGetAllCurrencies(t *testing.T) { + t.Parallel() + testGetNoArgs(t, c.GetAllCurrencies) +} + +func TestGetACurrency(t *testing.T) { + t.Parallel() + _, err := c.GetACurrency(context.Background(), "") + assert.ErrorIs(t, err, errCurrencyEmpty) + resp, err := c.GetACurrency(context.Background(), testCrypto.String()) + assert.NoError(t, err) + assert.NotEmpty(t, resp, errExpectedNonEmpty) +} + +func TestGetAllTradingPairs(t *testing.T) { + t.Parallel() + _, err := c.GetAllTradingPairs(context.Background(), "") + assert.NoError(t, err) +} + +func TestGetAllPairVolumes(t *testing.T) { + t.Parallel() + testGetNoArgs(t, c.GetAllPairVolumes) +} + +func TestGetPairDetails(t *testing.T) { + t.Parallel() + _, err := c.GetPairDetails(context.Background(), "") + assert.ErrorIs(t, err, errPairEmpty) + resp, err := c.GetPairDetails(context.Background(), testPair.String()) + assert.NoError(t, err) + assert.NotEmpty(t, resp, errExpectedNonEmpty) +} + +func TestGetProductBookV1(t *testing.T) { + t.Parallel() + _, err := c.GetProductBookV1(context.Background(), "", 0) + assert.ErrorIs(t, err, errPairEmpty) + resp, err := c.GetProductBookV1(context.Background(), testPair.String(), 2) + assert.NoError(t, err) + assert.NotEmpty(t, resp, errExpectedNonEmpty) + resp, err = c.GetProductBookV1(context.Background(), testPair.String(), 3) + assert.NoError(t, err) + assert.NotEmpty(t, resp, errExpectedNonEmpty) +} + +func TestGetProductCandles(t *testing.T) { + t.Parallel() + err := c.GetProductCandles(context.Background(), "", 0, time.Time{}, time.Time{}) + assert.ErrorIs(t, err, errPairEmpty) + err = c.GetProductCandles(context.Background(), testPair.String(), 300, time.Time{}, time.Time{}) + assert.NoError(t, err) +} + +func TestGetProductStats(t *testing.T) { + t.Parallel() + _, err := c.GetProductStats(context.Background(), "") + assert.ErrorIs(t, err, errPairEmpty) + resp, err := c.GetProductStats(context.Background(), testPair.String()) + assert.NoError(t, err) + assert.NotEmpty(t, resp, errExpectedNonEmpty) +} + +func TestGetProductTicker(t *testing.T) { + t.Parallel() + _, err := c.GetProductTicker(context.Background(), "") + assert.ErrorIs(t, err, errPairEmpty) + resp, err := c.GetProductTicker(context.Background(), testPair.String()) + assert.NoError(t, err) + assert.NotEmpty(t, resp, errExpectedNonEmpty) +} + +func TestGetProductTrades(t *testing.T) { + t.Parallel() + _, err := c.GetProductTrades(context.Background(), "", "", "", 0) + assert.ErrorIs(t, err, errPairEmpty) + resp, err := c.GetProductTrades(context.Background(), testPair.String(), "1", "before", 0) + assert.NoError(t, err) + assert.NotEmpty(t, resp, errExpectedNonEmpty) +} + +func TestGetAllWrappedAssets(t *testing.T) { + t.Parallel() + testGetNoArgs(t, c.GetAllWrappedAssets) +} + func TestSendHTTPRequest(t *testing.T) { t.Parallel() err := c.SendHTTPRequest(context.Background(), exchange.EdgeCase3, "", nil) @@ -1863,7 +1949,7 @@ func withdrawFiatFundsHelper(t *testing.T, fn withdrawFiatFunc) { type getNoArgsResp interface { *ServerTimeV3 | *GetAllPaymentMethodsResp | *UserResponse | *AuthResponse | []FiatData | - []CryptoData | *ServerTimeV2 + []CryptoData | *ServerTimeV2 | []CurrencyData | []PairVolumeData | *AllWrappedAssets } type getNoArgsAssertNotEmpty[G getNoArgsResp] func(context.Context) (G, error) diff --git a/exchanges/coinbasepro/coinbasepro_types.go b/exchanges/coinbasepro/coinbasepro_types.go index 091ed85159c..2c7e069676c 100644 --- a/exchanges/coinbasepro/coinbasepro_types.go +++ b/exchanges/coinbasepro/coinbasepro_types.go @@ -4,6 +4,7 @@ import ( "net/url" "time" + "github.com/gofrs/uuid" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/order" @@ -75,7 +76,7 @@ type PriSiz struct { Size float64 `json:"size,string"` } -// ProductBook holds bid and ask prices for a particular product, returned by GetProductBook +// ProductBook holds bid and ask prices for a particular product, returned by GetProductBookV3 // and used as a sub-struct in the types BestBidAsk and ProductBookResponse type ProductBook struct { ProductID string `json:"product_id"` @@ -90,7 +91,7 @@ type BestBidAsk struct { Pricebooks []ProductBook `json:"pricebooks"` } -// ProductBookResponse is a temporary struct used for unmarshalling in GetProductBook +// ProductBookResponse is a temporary struct used for unmarshalling in GetProductBookV3 type ProductBookResponse struct { Pricebook ProductBook `json:"pricebook"` } @@ -1294,3 +1295,184 @@ type WebsocketOrderDataHolder struct { Type string `json:"type"` Orders []WebsocketOrderData `json:"orders"` } + +// CurrencyData contains information on known currencies, used in GetAllCurrencies and GetACurrency +type CurrencyData struct { + ID string `json:"id"` + Name string `json:"name"` + MinSize string `json:"min_size"` + Status string `json:"status"` + Message string `json:"message"` + MaxPrecision float64 `json:"max_precision,string"` + ConvertibleTo []string `json:"convertible_to"` + Details struct { + Type string `json:"type"` + Symbol string `json:"symbol"` + NetworkConfirmations int32 `json:"network_confirmations"` + SortOrder int32 `json:"sort_order"` + CryptoAddressLink string `json:"crypto_address_link"` + CryptoTransactionLink string `json:"crypto_transaction_link"` + PushPaymentMethods []string `json:"push_payment_methods"` + GroupTypes []string `json:"group_types"` + DisplayName string `json:"display_name"` + ProcessingTimeSeconds int64 `json:"processing_time_seconds"` + MinWithdrawalAmount float64 `json:"min_withdrawal_amount"` + MaxWithdrawalAmount float64 `json:"max_withdrawal_amount"` + } `json:"details"` + DefaultNetwork string `json:"default_network"` + SupportedNetworks []struct { + ID string `json:"id"` + Name string `json:"name"` + Status string `json:"status"` + ContractAddress string `json:"contract_address"` + CryptoAddressLink string `json:"crypto_address_link"` + CryptoTransactionLink string `json:"crypto_transaction_link"` + MinWithdrawalAmount float64 `json:"min_withdrawal_amount"` + MaxWithdrawalAmount float64 `json:"max_withdrawal_amount"` + NetworkConfirmations int32 `json:"network_confirmations"` + ProcessingTimeSeconds int64 `json:"processing_time_seconds"` + } `json:"supported_networks"` + DisplayName string `json:"display_name"` +} + +// PairData contains information on available trading pairs, used in GetAllTradingPairs +type PairData struct { + ID string `json:"id"` + BaseCurrency string `json:"base_currency"` + QuoteCurrency string `json:"quote_currency"` + QuoteIncrement string `json:"quote_increment"` + BaseIncrement string `json:"base_increment"` + DisplayName string `json:"display_name"` + MinMarketFunds string `json:"min_market_funds"` + MarginEnabled bool `json:"margin_enabled"` + PostOnly bool `json:"post_only"` + LimitOnly bool `json:"limit_only"` + CancelOnly bool `json:"cancel_only"` + Status string `json:"status"` + StatusMessage string `json:"status_message"` + TradingDisabled bool `json:"trading_disabled"` + FXStablecoin bool `json:"fx_stablecoin"` + MaxSlippagePercentage float64 `json:"max_slippage_percentage,string"` + AuctionMode bool `json:"auction_mode"` + HighBidLimitPercentage types.Number `json:"high_bid_limit_percentage"` +} + +// PairVolumeData contains information on trading pair volume, used in GetAllPairVolumes +type PairVolumeData struct { + ID string `json:"id"` + BaseCurrency string `json:"base_currency"` + QuoteCurrency string `json:"quote_currency"` + DisplayName string `json:"display_name"` + MarketTypes []string `json:"market_types"` + SpotVolume24Hour types.Number `json:"spot_volume_24hour"` + SpotVolume30Day types.Number `json:"spot_volume_30day"` + RFQVolume24Hour float64 `json:"rfq_volume_24hour,string"` + RFQVolume30Day float64 `json:"rfq_volume_30day,string"` + ConversionVolume24Hour float64 `json:"conversion_volume_24hour,string"` + ConversionVolume30Day float64 `json:"conversion_volume_30day,string"` +} + +// Auction holds information on an ongoing auction, used as a sub-struct in OrderBookResp and OrderBook +type Auction struct { + OpenPrice float64 `json:"open_price,string"` + OpenSize float64 `json:"open_size,string"` + BestBidPrice float64 `json:"best_bid_price,string"` + BestBidSize float64 `json:"best_bid_size,string"` + BestAskPrice float64 `json:"best_ask_price,string"` + BestAskSize float64 `json:"best_ask_size,string"` + AuctionState string `json:"auction_state"` + CanOpen string `json:"can_open"` + Time time.Time `json:"time"` +} + +// OrderBookResp holds information on bids and asks for a particular currency pair, used for unmarshalling in +// GetProductBookV1 +type OrderBookResp struct { + Bids [][3]interface{} `json:"bids"` + Asks [][3]interface{} `json:"asks"` + Sequence float64 `json:"sequence"` + AuctionMode bool `json:"auction_mode"` + Auction Auction `json:"auction"` + Time time.Time `json:"time"` +} + +// Orders holds information on orders, used as a sub-struct in OrderBook +type Orders struct { + Price float64 + Size float64 + OrderCount uint64 + OrderID uuid.UUID +} + +// OrderBook holds information on bids and asks for a particular currency pair, used in GetProductBookV1 +type OrderBook struct { + Bids []Orders + Asks []Orders + Sequence float64 + AuctionMode bool + Auction Auction + Time time.Time +} + +// RawCandles holds raw candle data, used in unmarshalling for GetProductCandles +type RawCandles [6]interface{} + +// Candle holds properly formatted candle data, returned by GetProductCandles +type Candle struct { + Time time.Time + Low float64 + High float64 + Open float64 + Close float64 + Volume float64 +} + +// ProductStats holds information on a pair's price and volume, returned by GetProductStats +type ProductStats struct { + Open float64 `json:"open,string"` + High float64 `json:"high,string"` + Low float64 `json:"low,string"` + Last float64 `json:"last,string"` + Volume float64 `json:"volume,string"` + Volume30Day float64 `json:"volume_30day,string"` + RFQVolume24Hour float64 `json:"rfq_volume_24hour,string"` + RFQVolume30Day float64 `json:"rfq_volume_30day,string"` + ConversionsVolume24Hour float64 `json:"conversions_volume_24hour,string"` + ConversionsVolume30Day float64 `json:"conversions_volume_30day,string"` +} + +// ProductTicker holds information on a pair's price and volume, returned by GetProductTicker +type ProductTicker struct { + Ask float64 `json:"ask,string"` + Bid float64 `json:"bid,string"` + Volume float64 `json:"volume,string"` + TradeID int32 `json:"trade_id"` + Price float64 `json:"price,string"` + Size float64 `json:"size,string"` + Time time.Time `json:"time"` + RFQVolume float64 `json:"rfq_volume,string"` + ConversionsVolume float64 `json:"conversions_volume,string"` +} + +// ProductTrades holds information on a pair's trades, returned by GetProductTrades +type ProductTrades struct { + TradeID int32 `json:"trade_id"` + Side string `json:"side"` + Size float64 `json:"size,string"` + Price float64 `json:"price,string"` + Time time.Time `json:"time"` +} + +// WrappedAsset holds information on a wrapped asset, used in AllWrappedAssets +type WrappedAsset struct { + ID string `json:"id"` + CirculatingSupply float64 `json:"circulating_supply,string"` + TotalSupply float64 `json:"total_supply,string"` + ConversionRate float64 `json:"conversion_rate,string"` + APY types.Number `json:"apy"` +} + +// AllWrappedAssets holds information on all wrapped assets, returned by GetAllWrappedAssets +type AllWrappedAssets struct { + WrappedAssets []WrappedAsset `json:"wrapped_assets"` +} diff --git a/exchanges/coinbasepro/coinbasepro_wrapper.go b/exchanges/coinbasepro/coinbasepro_wrapper.go index 4ba31f4acc8..8df1b48d86c 100644 --- a/exchanges/coinbasepro/coinbasepro_wrapper.go +++ b/exchanges/coinbasepro/coinbasepro_wrapper.go @@ -113,9 +113,10 @@ func (c *CoinbasePro) SetDefaults() { } c.API.Endpoints = c.NewEndpoints() err = c.API.Endpoints.SetDefaultEndpoints(map[exchange.URL]string{ - exchange.RestSpot: coinbaseAPIURL, - exchange.RestSandbox: coinbaseproSandboxAPIURL, - exchange.WebsocketSpot: coinbaseproWebsocketURL, + exchange.RestSpot: coinbaseAPIURL, + exchange.RestSandbox: coinbaseproSandboxAPIURL, + exchange.WebsocketSpot: coinbaseproWebsocketURL, + exchange.RestSpotSupplementary: coinbaseV1APIURL, }) if err != nil { log.Errorln(log.ExchangeSys, err) @@ -385,7 +386,7 @@ 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) + orderbookNew, err := c.GetProductBookV3(ctx, fPair.String(), 1000) if err != nil { return book, err } diff --git a/exchanges/coinbasepro/ratelimit.go b/exchanges/coinbasepro/ratelimit.go index f252a26cfee..835755ee424 100644 --- a/exchanges/coinbasepro/ratelimit.go +++ b/exchanges/coinbasepro/ratelimit.go @@ -19,6 +19,9 @@ const ( coinbaseWSInterval = time.Second coinbaseWSRate = 750 + + coinbasePublicInterval = time.Second + coinbasePublicRate = 10 ) // Coinbase pro rate limits @@ -26,13 +29,15 @@ const ( V2Rate request.EndpointLimit = iota V3Rate WSRate + PubRate ) // RateLimit implements the request.Limiter interface type RateLimit struct { - RateLimV3 *rate.Limiter - RateLimV2 *rate.Limiter - RateLimWS *rate.Limiter + RateLimV3 *rate.Limiter + RateLimV2 *rate.Limiter + RateLimWS *rate.Limiter + RateLimPub *rate.Limiter } // Limit limits outbound calls @@ -44,6 +49,8 @@ func (r *RateLimit) Limit(ctx context.Context, f request.EndpointLimit) error { return r.RateLimV2.Wait(ctx) case WSRate: return r.RateLimWS.Wait(ctx) + case PubRate: + return r.RateLimPub.Wait(ctx) default: return fmt.Errorf("%w %v", errUnknownEndpointLimit, f) } @@ -52,8 +59,9 @@ func (r *RateLimit) Limit(ctx context.Context, f request.EndpointLimit) error { // SetRateLimit returns the rate limit for the exchange func SetRateLimit() *RateLimit { return &RateLimit{ - RateLimWS: request.NewRateLimit(coinbaseWSInterval, coinbaseWSRate), - RateLimV3: request.NewRateLimit(coinbaseV3Interval, coinbaseV3Rate), - RateLimV2: request.NewRateLimit(coinbaseV2Interval, coinbaseV2Rate), + RateLimWS: request.NewRateLimit(coinbaseWSInterval, coinbaseWSRate), + RateLimV3: request.NewRateLimit(coinbaseV3Interval, coinbaseV3Rate), + RateLimV2: request.NewRateLimit(coinbaseV2Interval, coinbaseV2Rate), + RateLimPub: request.NewRateLimit(coinbasePublicInterval, coinbasePublicRate), } } From 343052b81632af4b39b61cea6a56267d7a9ff48b Mon Sep 17 00:00:00 2001 From: Samuel Reid <43227667+cranktakular@users.noreply.github.com> Date: Mon, 22 Apr 2024 16:25:14 +1000 Subject: [PATCH 41/79] Slight improvements --- exchanges/coinbasepro/coinbasepro.go | 2 +- exchanges/coinbasepro/coinbasepro_test.go | 211 ++++++++++-------- .../coinbasepro/coinbasepro_websocket.go | 44 ++-- exchanges/coinbasepro/ratelimit.go | 34 +-- exchanges/exchange.go | 5 +- exchanges/stream/websocket.go | 4 +- 6 files changed, 173 insertions(+), 127 deletions(-) diff --git a/exchanges/coinbasepro/coinbasepro.go b/exchanges/coinbasepro/coinbasepro.go index 1fb1db3b5d5..3b50ac8f765 100644 --- a/exchanges/coinbasepro/coinbasepro.go +++ b/exchanges/coinbasepro/coinbasepro.go @@ -158,7 +158,7 @@ var ( errPairEmpty = errors.New("pair cannot be empty") errStringConvert = errors.New("unable to convert into string value") errFloatConvert = errors.New("unable to convert into float64 value") - errConvertGen = errors.New("unable to convert value") + errNoCredsUser = errors.New("no credentials when attempting to subscribe to authenticated channel user") ) // GetAllAccounts returns information on all trading accounts associated with the API key diff --git a/exchanges/coinbasepro/coinbasepro_test.go b/exchanges/coinbasepro/coinbasepro_test.go index 39a91d2064d..50bbbf2d480 100644 --- a/exchanges/coinbasepro/coinbasepro_test.go +++ b/exchanges/coinbasepro/coinbasepro_test.go @@ -15,6 +15,7 @@ import ( "github.com/gofrs/uuid" "github.com/gorilla/websocket" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/config" "github.com/thrasher-corp/gocryptotrader/currency" @@ -49,6 +50,8 @@ var ( // Constants used within tests const ( testAddress = "fake address" + testAmount = 1e-08 + testPrice = 1e+09 skipPayMethodNotFound = "no payment methods found, skipping" skipInsufSuitableAccs = "insufficient suitable accounts for test, skipping" @@ -63,7 +66,7 @@ const ( errExpectedNonEmpty = "expected non-empty response" errIDNotSet = "ID not set" errx7f = "setting proxy address error parse \"\\x7f\": net/url: invalid control character in URL" - errPortfolioNameDuplicate = `CoinbasePro unsuccessful HTTP status code: 500 raw response: {"error":"INTERNAL","error_details":"the requested portfolio name already exists","message":"the requested portfolio name already exists"}, authenticated request failed` + errPortfolioNameDuplicate = `CoinbasePro unsuccessful HTTP status code: 409 raw response: {"error":"CONFLICT","error_details":"the requested portfolio name already exists","message":"the requested portfolio name already exists"}, authenticated request failed` errPortTransferInsufFunds = `CoinbasePro unsuccessful HTTP status code: 429 raw response: {"error":"unknown","error_details":"[PORTFOLIO_ERROR_CODE_INSUFFICIENT_FUNDS] insufficient funds in source account","message":"[PORTFOLIO_ERROR_CODE_INSUFFICIENT_FUNDS] insufficient funds in source account"}, authenticated request failed` errInvalidProductID = `CoinbasePro unsuccessful HTTP status code: 400 raw response: {"error":"INVALID_ARGUMENT","error_details":"valid product_id is required","message":"valid product_id is required"}, authenticated request failed` errNoEndpointPathEdgeCase3 = "no endpoint path found for the given key: EdgeCase3URL" @@ -71,19 +74,16 @@ const ( errUnrecognisedOrderType = `'' unrecognised order type` expectedTimestamp = "1970-01-01 00:20:34 +0000 UTC" - - testAmount = 1e-08 - testPrice = 1e+09 ) -func TestSetup(t *testing.T) { +func TestSetup2(t *testing.T) { cfg, err := c.GetStandardConfig() assert.NoError(t, err) cfg.API.AuthenticatedSupport = true cfg.API.Credentials.Key = apiKey cfg.API.Credentials.Secret = apiSecret - cfg.Enabled = false - cfg.Enabled = true + // cfg.Enabled = false + // cfg.Enabled = true cfg.ProxyAddress = string(rune(0x7f)) err = c.Setup(cfg) if err.Error() != errx7f { @@ -101,25 +101,9 @@ func TestMain(m *testing.M) { log.Fatal("failed to set sandbox endpoint", err) } } - cfg := config.GetConfig() - err := cfg.LoadConfig("../../testdata/configtest.json", true) - if err != nil { - log.Fatal("load config error", err) - } - gdxConfig, err := cfg.GetExchangeConfig("CoinbasePro") - if err != nil { - log.Fatal("init error") - } - if apiKey != "" { - gdxConfig.API.Credentials.Key = apiKey - gdxConfig.API.Credentials.Secret = apiSecret - gdxConfig.API.AuthenticatedSupport = true - gdxConfig.API.AuthenticatedWebsocketSupport = true - } - c.Websocket = sharedtestvalues.NewTestWebsocket() - err = c.Setup(gdxConfig) + err := exchangeBaseHelper(c) if err != nil { - log.Fatal("CoinbasePro setup error", err) + log.Fatal(err) } if apiKey != "" { c.GetBase().API.AuthenticatedSupport = true @@ -132,6 +116,33 @@ func TestMain(m *testing.M) { os.Exit(m.Run()) } +func TestSetup(t *testing.T) { + cfg, err := c.GetStandardConfig() + assert.NoError(t, err) + exch := &CoinbasePro{} + exch.SetDefaults() + err = exchangeBaseHelper(exch) + require.NoError(t, err) + cfg.ProxyAddress = string(rune(0x7f)) + err = exch.Setup(cfg) + assert.ErrorIs(t, err, exchange.ErrSettingProxyAddress) +} + +func TestWsConnect(t *testing.T) { + exch := &CoinbasePro{} + exch.Websocket = sharedtestvalues.NewTestWebsocket() + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + err := exch.Websocket.Disable() + assert.ErrorIs(t, err, stream.ErrAlreadyDisabled) + err = exch.WsConnect() + assert.ErrorIs(t, err, stream.ErrWebsocketNotEnabled) + exch.SetDefaults() + err = exchangeBaseHelper(exch) + require.NoError(t, err) + err = exch.Websocket.Enable() + assert.NoError(t, err) +} + func TestGetAllAccounts(t *testing.T) { t.Parallel() sharedtestvalues.SkipTestIfCredentialsUnset(t, c) @@ -475,29 +486,6 @@ func TestGetFuturesPositionByID(t *testing.T) { assert.NoError(t, err) } -func TestScheduleFuturesSweep(t *testing.T) { - t.Parallel() - sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) - curSweeps, err := c.ListFuturesSweeps(context.Background()) - assert.NoError(t, err) - preCancel := false - if len(curSweeps) > 0 { - for i := range curSweeps { - if curSweeps[i].Status == "PENDING" { - preCancel = true - } - } - } - if preCancel { - _, err = c.CancelPendingFuturesSweep(context.Background()) - if err != nil { - t.Error(err) - } - } - _, err = c.ScheduleFuturesSweep(context.Background(), 0.001337) - assert.NoError(t, err) -} - func TestListFuturesSweeps(t *testing.T) { t.Parallel() sharedtestvalues.SkipTestIfCredentialsUnset(t, c) @@ -505,29 +493,6 @@ func TestListFuturesSweeps(t *testing.T) { assert.NoError(t, err) } -func TestCancelPendingFuturesSweep(t *testing.T) { - t.Parallel() - sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) - curSweeps, err := c.ListFuturesSweeps(context.Background()) - assert.NoError(t, err) - partialSkip := false - if len(curSweeps) > 0 { - for i := range curSweeps { - if curSweeps[i].Status == "PENDING" { - partialSkip = true - } - } - } - if !partialSkip { - _, err = c.ScheduleFuturesSweep(context.Background(), 0.001337) - if err != nil { - t.Error(err) - } - } - _, err = c.CancelPendingFuturesSweep(context.Background()) - assert.NoError(t, err) -} - func TestAllocatePortfolio(t *testing.T) { t.Parallel() err := c.AllocatePortfolio(context.Background(), "", "", "", 0) @@ -1573,9 +1538,64 @@ func TestFormatExchangeKlineInterval(t *testing.T) { } } +func TestStringToFloatPtr(t *testing.T) { + t.Parallel() + err := stringToFloatPtr(nil, "") + assert.ErrorIs(t, err, errPointerNil) + var fl float64 + err = stringToFloatPtr(&fl, "") + assert.NoError(t, err) + err = stringToFloatPtr(&fl, "1.1") + assert.NoError(t, err) +} + +func TestScheduleFuturesSweep(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) + curSweeps, err := c.ListFuturesSweeps(context.Background()) + assert.NoError(t, err) + preCancel := false + if len(curSweeps) > 0 { + for i := range curSweeps { + if curSweeps[i].Status == "PENDING" { + preCancel = true + } + } + } + if preCancel { + _, err = c.CancelPendingFuturesSweep(context.Background()) + if err != nil { + t.Error(err) + } + } + _, err = c.ScheduleFuturesSweep(context.Background(), 0.001337) + assert.NoError(t, err) +} + +func TestCancelPendingFuturesSweep(t *testing.T) { + sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) + curSweeps, err := c.ListFuturesSweeps(context.Background()) + assert.NoError(t, err) + partialSkip := false + if len(curSweeps) > 0 { + for i := range curSweeps { + if curSweeps[i].Status == "PENDING" { + partialSkip = true + } + } + } + if !partialSkip { + _, err = c.ScheduleFuturesSweep(context.Background(), 0.001337) + if err != nil { + t.Error(err) + } + } + _, err = c.CancelPendingFuturesSweep(context.Background()) + assert.NoError(t, err) +} + // TestWsAuth dials websocket, sends login request. func TestWsAuth(t *testing.T) { - if !c.Websocket.IsEnabled() && !c.API.AuthenticatedWebsocketSupport || !sharedtestvalues.AreAPICredentialsSet(c) { + if c.Websocket.IsEnabled() && !c.API.AuthenticatedWebsocketSupport || !sharedtestvalues.AreAPICredentialsSet(c) { t.Skip(stream.ErrWebsocketNotEnabled.Error()) } var dialer websocket.Dialer @@ -1597,7 +1617,7 @@ func TestWsAuth(t *testing.T) { timer := time.NewTimer(sharedtestvalues.WebsocketResponseDefaultTimeout) select { case badResponse := <-c.Websocket.DataHandler: - t.Error(badResponse) + assert.IsType(t, []order.Detail{}, badResponse) case <-timer.C: } timer.Stop() @@ -1625,27 +1645,6 @@ func TestStatusToStandardStatus(t *testing.T) { } } -func TestStringToFloatPtr(t *testing.T) { - t.Parallel() - err := stringToFloatPtr(nil, "") - assert.ErrorIs(t, err, errPointerNil) - var fl float64 - err = stringToFloatPtr(&fl, "") - assert.NoError(t, err) - err = stringToFloatPtr(&fl, "1.1") - assert.NoError(t, err) -} - -func TestWsConnect(t *testing.T) { - sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - err := c.Websocket.Disable() - assert.ErrorIs(t, err, stream.ErrAlreadyDisabled) - err = c.WsConnect() - assert.ErrorIs(t, err, stream.ErrWebsocketNotEnabled) - err = c.Websocket.Enable() - assert.NoError(t, err) -} - func TestWsHandleData(t *testing.T) { go func() { for range c.Websocket.DataHandler { @@ -1788,6 +1787,30 @@ func TestGetJWT(t *testing.T) { } } +func exchangeBaseHelper(c *CoinbasePro) error { + cfg := config.GetConfig() + err := cfg.LoadConfig("../../testdata/configtest.json", true) + if err != nil { + return err + } + gdxConfig, err := cfg.GetExchangeConfig("CoinbasePro") + if err != nil { + return err + } + if apiKey != "" { + gdxConfig.API.Credentials.Key = apiKey + gdxConfig.API.Credentials.Secret = apiSecret + gdxConfig.API.AuthenticatedSupport = true + gdxConfig.API.AuthenticatedWebsocketSupport = true + } + c.Websocket = sharedtestvalues.NewTestWebsocket() + err = c.Setup(gdxConfig) + if err != nil { + return err + } + return nil +} + func skipTestIfLowOnFunds(t *testing.T) { t.Helper() accounts, err := c.GetAllAccounts(context.Background(), 250, "") diff --git a/exchanges/coinbasepro/coinbasepro_websocket.go b/exchanges/coinbasepro/coinbasepro_websocket.go index 4254aeccd1d..10c99552482 100644 --- a/exchanges/coinbasepro/coinbasepro_websocket.go +++ b/exchanges/coinbasepro/coinbasepro_websocket.go @@ -21,6 +21,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/common/crypto" "github.com/thrasher-corp/gocryptotrader/currency" + exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" @@ -221,7 +222,7 @@ func (c *CoinbasePro) wsHandleData(respRaw []byte, seqCount uint64) (string, err if err != nil { return warnString, err } - sliToSend := []order.Detail{} + var sliToSend []order.Detail for i := range wsUser { for j := range wsUser[i].Orders { var oType order.Type @@ -432,31 +433,44 @@ func getTimestamp(rawData []byte) (time.Time, error) { // sendRequest is a helper function which sends a websocket message to the Coinbase server func (c *CoinbasePro) sendRequest(msgType, channel string, productIDs currency.Pairs) error { + authenticated := true creds, err := c.GetCredentials(context.Background()) if err != nil { - return err + if errors.Is(err, exchange.ErrCredentialsAreEmpty) || + errors.Is(err, exchange.ErrAuthenticationSupportNotEnabled) { + authenticated = false + if channel == "user" { + return errNoCredsUser + } + } else { + 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(), Channel: channel, - Signature: hex.EncodeToString(hmac), - Key: creds.Key, Timestamp: n, } - err = c.InitiateRateLimit(context.Background(), WSRate) + if authenticated { + 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.Key = creds.Key + req.Signature = hex.EncodeToString(hmac) + err = c.InitiateRateLimit(context.Background(), WSAuthRate) + } else { + err = c.InitiateRateLimit(context.Background(), WSUnauthRate) + } if err != nil { - return fmt.Errorf("failed to rate limit HTTP request: %w", err) + return fmt.Errorf("failed to rate limit websocket request: %w", err) } return c.Websocket.Conn.SendJSONMessage(req) } diff --git a/exchanges/coinbasepro/ratelimit.go b/exchanges/coinbasepro/ratelimit.go index 835755ee424..1070afe0943 100644 --- a/exchanges/coinbasepro/ratelimit.go +++ b/exchanges/coinbasepro/ratelimit.go @@ -17,8 +17,11 @@ const ( coinbaseV2Interval = time.Hour coinbaseV2Rate = 10000 - coinbaseWSInterval = time.Second - coinbaseWSRate = 750 + coinbaseWSAuthInterval = time.Second + coinbaseWSAuthRate = 750 + + coinbaseWSUnauthInterval = time.Second + coinbaseWSUnauthRate = 8 coinbasePublicInterval = time.Second coinbasePublicRate = 10 @@ -28,16 +31,18 @@ const ( const ( V2Rate request.EndpointLimit = iota V3Rate - WSRate + WSAuthRate + WSUnauthRate PubRate ) // RateLimit implements the request.Limiter interface type RateLimit struct { - RateLimV3 *rate.Limiter - RateLimV2 *rate.Limiter - RateLimWS *rate.Limiter - RateLimPub *rate.Limiter + RateLimV3 *rate.Limiter + RateLimV2 *rate.Limiter + RateLimWSAuth *rate.Limiter + RateLimWSUnauth *rate.Limiter + RateLimPub *rate.Limiter } // Limit limits outbound calls @@ -47,8 +52,10 @@ func (r *RateLimit) Limit(ctx context.Context, f request.EndpointLimit) error { return r.RateLimV3.Wait(ctx) case V2Rate: return r.RateLimV2.Wait(ctx) - case WSRate: - return r.RateLimWS.Wait(ctx) + case WSAuthRate: + return r.RateLimWSAuth.Wait(ctx) + case WSUnauthRate: + return r.RateLimWSUnauth.Wait(ctx) case PubRate: return r.RateLimPub.Wait(ctx) default: @@ -59,9 +66,10 @@ func (r *RateLimit) Limit(ctx context.Context, f request.EndpointLimit) error { // SetRateLimit returns the rate limit for the exchange func SetRateLimit() *RateLimit { return &RateLimit{ - RateLimWS: request.NewRateLimit(coinbaseWSInterval, coinbaseWSRate), - RateLimV3: request.NewRateLimit(coinbaseV3Interval, coinbaseV3Rate), - RateLimV2: request.NewRateLimit(coinbaseV2Interval, coinbaseV2Rate), - RateLimPub: request.NewRateLimit(coinbasePublicInterval, coinbasePublicRate), + RateLimWSAuth: request.NewRateLimit(coinbaseWSAuthInterval, coinbaseWSAuthRate), + RateLimWSUnauth: request.NewRateLimit(coinbaseWSUnauthInterval, coinbaseWSUnauthRate), + RateLimV3: request.NewRateLimit(coinbaseV3Interval, coinbaseV3Rate), + RateLimV2: request.NewRateLimit(coinbaseV2Interval, coinbaseV2Rate), + RateLimPub: request.NewRateLimit(coinbasePublicInterval, coinbasePublicRate), } } diff --git a/exchanges/exchange.go b/exchanges/exchange.go index 4e2e34e90df..d1f5ca2c3ac 100644 --- a/exchanges/exchange.go +++ b/exchanges/exchange.go @@ -51,6 +51,7 @@ const ( var ( // ErrExchangeNameIsEmpty is returned when the exchange name is empty ErrExchangeNameIsEmpty = errors.New("exchange name is empty") + ErrSettingProxyAddress = errors.New("setting proxy address error") errEndpointStringNotFound = errors.New("endpoint string not found") errConfigPairFormatRequiresDelimiter = errors.New("config pair format requires delimiter") @@ -81,8 +82,8 @@ func (b *Base) SetClientProxyAddress(addr string) error { } proxy, err := url.Parse(addr) if err != nil { - return fmt.Errorf("setting proxy address error %s", - err) + return fmt.Errorf("%w %w", + ErrSettingProxyAddress, err) } err = b.Requester.SetProxy(proxy) diff --git a/exchanges/stream/websocket.go b/exchanges/stream/websocket.go index e7585e9449b..3c3b343c2c8 100644 --- a/exchanges/stream/websocket.go +++ b/exchanges/stream/websocket.go @@ -30,6 +30,7 @@ var ( ErrChannelInStateAlready = errors.New("channel already in state") ErrAlreadyDisabled = errors.New("websocket already disabled") ErrNotConnected = errors.New("websocket is not connected") + ErrWebsocketAlreadyEnabled = errors.New("websocket already enabled") ) // Private websocket errors @@ -40,7 +41,6 @@ var ( errWebsocketIsNil = errors.New("websocket is nil") errWebsocketSetupIsNil = errors.New("websocket setup is nil") errWebsocketAlreadyInitialised = errors.New("websocket already initialised") - errWebsocketAlreadyEnabled = errors.New("websocket already enabled") errWebsocketFeaturesIsUnset = errors.New("websocket features is unset") errConfigFeaturesIsNil = errors.New("exchange config features is nil") errDefaultURLIsEmpty = errors.New("default url is empty") @@ -331,7 +331,7 @@ func (w *Websocket) Disable() error { // Enable enables the exchange websocket protocol func (w *Websocket) Enable() error { if w.IsConnected() || w.IsEnabled() { - return fmt.Errorf("%s %w", w.exchangeName, errWebsocketAlreadyEnabled) + return fmt.Errorf("%s %w", w.exchangeName, ErrWebsocketAlreadyEnabled) } w.setEnabled(true) From ea43046064a65f676f10e29196c52d854d33fe29 Mon Sep 17 00:00:00 2001 From: Samuel Reid <43227667+cranktakular@users.noreply.github.com> Date: Tue, 30 Apr 2024 16:47:37 +1000 Subject: [PATCH 42/79] Wrapper improvements; still a few errors left in other packages --- exchanges/coinbasepro/coinbasepro.go | 45 +- exchanges/coinbasepro/coinbasepro_test.go | 277 ++++++------- exchanges/coinbasepro/coinbasepro_types.go | 14 +- exchanges/coinbasepro/coinbasepro_wrapper.go | 413 +++++++++++++------ exchanges/exchange.go | 5 +- exchanges/order/order_test.go | 6 +- exchanges/order/orders.go | 14 +- exchanges/stream/websocket_test.go | 2 +- testdata/configtest.json | 7 +- 9 files changed, 487 insertions(+), 296 deletions(-) diff --git a/exchanges/coinbasepro/coinbasepro.go b/exchanges/coinbasepro/coinbasepro.go index 3b50ac8f765..bc5843e40ce 100644 --- a/exchanges/coinbasepro/coinbasepro.go +++ b/exchanges/coinbasepro/coinbasepro.go @@ -77,6 +77,7 @@ const ( coinbaseStats = "stats" coinbaseTrades = "trades" coinbaseWrappedAssets = "wrapped-assets" + coinbaseConversionRate = "conversion-rate" pageNone = "" pageBefore = "before" @@ -109,7 +110,7 @@ const ( // here, the data returned by the GetTransactionsSummary endpoint are consistent with these worst // case scenarios. The best case scenarios are untested, and assumed to be in line with the fee pages const ( - WorstCaseTakerFee = 0.008 + WorstCaseTakerFee = 0.01 WorstCaseMakerFee = 0.006 BestCaseTakerFee = 0.0005 BestCaseMakerFee = 0 @@ -159,6 +160,8 @@ var ( errStringConvert = errors.New("unable to convert into string value") errFloatConvert = errors.New("unable to convert into float64 value") errNoCredsUser = errors.New("no credentials when attempting to subscribe to authenticated channel user") + errWrappedAssetEmpty = errors.New("wrapped asset cannot be empty") + errAuthenticationNeeded = errors.New("authentication is needed to use this endpoint") ) // GetAllAccounts returns information on all trading accounts associated with the API key @@ -1268,9 +1271,9 @@ func (c *CoinbasePro) GetProductBookV1(ctx context.Context, pair string, level u } // GetProductCandles returns historical market data for the specified currency pair. -func (c *CoinbasePro) GetProductCandles(ctx context.Context, pair string, granularity uint32, startTime, endTime time.Time) error { +func (c *CoinbasePro) GetProductCandles(ctx context.Context, pair string, granularity uint32, startTime, endTime time.Time) ([]Candle, error) { if pair == "" { - return errPairEmpty + return nil, errPairEmpty } var params Params params.Values = url.Values{} @@ -1282,35 +1285,35 @@ func (c *CoinbasePro) GetProductCandles(ctx context.Context, pair string, granul var resp []RawCandles err := c.SendHTTPRequest(ctx, exchange.RestSpotSupplementary, path, &resp) if err != nil { - return err + return nil, err } candles := make([]Candle, len(resp)) for i := range resp { f1, ok := resp[i][0].(float64) if !ok { - return fmt.Errorf("%w, %v", errFloatConvert, resp[i][0]) + return nil, fmt.Errorf("%w, %v", errFloatConvert, resp[i][0]) } ti := int64(f1) t := time.Unix(ti, 0) f2, ok := resp[i][1].(float64) if !ok { - return fmt.Errorf("%w, %v", errFloatConvert, resp[i][1]) + return nil, fmt.Errorf("%w, %v", errFloatConvert, resp[i][1]) } f3, ok := resp[i][2].(float64) if !ok { - return fmt.Errorf("%w, %v", errFloatConvert, resp[i][2]) + return nil, fmt.Errorf("%w, %v", errFloatConvert, resp[i][2]) } f4, ok := resp[i][3].(float64) if !ok { - return fmt.Errorf("%w, %v", errFloatConvert, resp[i][3]) + return nil, fmt.Errorf("%w, %v", errFloatConvert, resp[i][3]) } f5, ok := resp[i][4].(float64) if !ok { - return fmt.Errorf("%w, %v", errFloatConvert, resp[i][4]) + return nil, fmt.Errorf("%w, %v", errFloatConvert, resp[i][4]) } f6, ok := resp[i][5].(float64) if !ok { - return fmt.Errorf("%w, %v", errFloatConvert, resp[i][5]) + return nil, fmt.Errorf("%w, %v", errFloatConvert, resp[i][5]) } candles[i] = Candle{ Time: t, @@ -1321,7 +1324,7 @@ func (c *CoinbasePro) GetProductCandles(ctx context.Context, pair string, granul Volume: f6, } } - return nil + return candles, nil } // GetProductStats returns information on a specific pair's price and volume @@ -1365,6 +1368,26 @@ func (c *CoinbasePro) GetAllWrappedAssets(ctx context.Context) (*AllWrappedAsset return resp, c.SendHTTPRequest(ctx, exchange.RestSpotSupplementary, coinbaseWrappedAssets, &resp) } +// GetWrappedAssetDetails returns information on a single wrapped asset +func (c *CoinbasePro) GetWrappedAssetDetails(ctx context.Context, wrappedAsset string) (*WrappedAsset, error) { + if wrappedAsset == "" { + return nil, errWrappedAssetEmpty + } + var resp *WrappedAsset + path := coinbaseWrappedAssets + "/" + wrappedAsset + return resp, c.SendHTTPRequest(ctx, exchange.RestSpotSupplementary, path, &resp) +} + +// GetWrappedAssetConversionRate returns the conversion rate for the specified wrapped asset +func (c *CoinbasePro) GetWrappedAssetConversionRate(ctx context.Context, wrappedAsset string) (*WrappedAssetConversionRate, error) { + if wrappedAsset == "" { + return nil, errWrappedAssetEmpty + } + var resp *WrappedAssetConversionRate + path := coinbaseWrappedAssets + "/" + wrappedAsset + "/" + coinbaseConversionRate + return resp, c.SendHTTPRequest(ctx, exchange.RestSpotSupplementary, path, &resp) +} + // SendHTTPRequest sends an unauthenticated HTTP request func (c *CoinbasePro) SendHTTPRequest(ctx context.Context, ep exchange.URL, path string, result interface{}) error { endpoint, err := c.API.Endpoints.GetURL(ep) diff --git a/exchanges/coinbasepro/coinbasepro_test.go b/exchanges/coinbasepro/coinbasepro_test.go index 50bbbf2d480..52a1e349592 100644 --- a/exchanges/coinbasepro/coinbasepro_test.go +++ b/exchanges/coinbasepro/coinbasepro_test.go @@ -41,10 +41,12 @@ const ( ) var ( - c = &CoinbasePro{} - testCrypto = currency.BTC - testFiat = currency.USD - testPair = currency.NewPairWithDelimiter(testCrypto.String(), testFiat.String(), "-") + c = &CoinbasePro{} + testCrypto = currency.BTC + testFiat = currency.USD + testStable = currency.USDC + testWrappedAsset = currency.CBETH + testPair = currency.NewPairWithDelimiter(testCrypto.String(), testFiat.String(), "-") ) // Constants used within tests @@ -62,35 +64,18 @@ const ( skipInsufficientFundsOrWallets = "insufficient funds or wallets for test, skipping" skipInsufficientTransactions = "insufficient transactions for test, skipping" - errExpectMismatch = "received: '%v' but expected: '%v'" - errExpectedNonEmpty = "expected non-empty response" - errIDNotSet = "ID not set" - errx7f = "setting proxy address error parse \"\\x7f\": net/url: invalid control character in URL" - errPortfolioNameDuplicate = `CoinbasePro unsuccessful HTTP status code: 409 raw response: {"error":"CONFLICT","error_details":"the requested portfolio name already exists","message":"the requested portfolio name already exists"}, authenticated request failed` - errPortTransferInsufFunds = `CoinbasePro unsuccessful HTTP status code: 429 raw response: {"error":"unknown","error_details":"[PORTFOLIO_ERROR_CODE_INSUFFICIENT_FUNDS] insufficient funds in source account","message":"[PORTFOLIO_ERROR_CODE_INSUFFICIENT_FUNDS] insufficient funds in source account"}, authenticated request failed` - errInvalidProductID = `CoinbasePro unsuccessful HTTP status code: 400 raw response: {"error":"INVALID_ARGUMENT","error_details":"valid product_id is required","message":"valid product_id is required"}, authenticated request failed` - errNoEndpointPathEdgeCase3 = "no endpoint path found for the given key: EdgeCase3URL" - errExpectedFeeRange = "expected fee range of %v and %v, received %v" - errUnrecognisedOrderType = `'' unrecognised order type` + errExpectMismatch = "received: '%v' but expected: '%v'" + errExpectMismatch2 = "received: '%v' but expected: '%v' or '%v'" + errExpectedNonEmpty = "expected non-empty response" + errPortfolioNameDuplicate = `CoinbasePro unsuccessful HTTP status code: 409 raw response: {"error":"CONFLICT","error_details":"the requested portfolio name already exists","message":"the requested portfolio name already exists"}, authenticated request failed` + errPortTransferInsufFunds = `CoinbasePro unsuccessful HTTP status code: 429 raw response: {"error":"unknown","error_details":"[PORTFOLIO_ERROR_CODE_INSUFFICIENT_FUNDS] insufficient funds in source account","message":"[PORTFOLIO_ERROR_CODE_INSUFFICIENT_FUNDS] insufficient funds in source account"}, authenticated request failed` + errInvalidProductID = `CoinbasePro unsuccessful HTTP status code: 400 raw response: {"error":"INVALID_ARGUMENT","error_details":"valid product_id is required","message":"valid product_id is required"}, authenticated request failed` + errValidProductIDRequired = `CoinbasePro unsuccessful HTTP status code: 404 raw response: {"message":"NotFound"}` + errExpectedFeeRange = "expected fee range of %v and %v, received %v" expectedTimestamp = "1970-01-01 00:20:34 +0000 UTC" ) -func TestSetup2(t *testing.T) { - cfg, err := c.GetStandardConfig() - assert.NoError(t, err) - cfg.API.AuthenticatedSupport = true - cfg.API.Credentials.Key = apiKey - cfg.API.Credentials.Secret = apiSecret - // cfg.Enabled = false - // cfg.Enabled = true - cfg.ProxyAddress = string(rune(0x7f)) - err = c.Setup(cfg) - if err.Error() != errx7f { - t.Errorf(errExpectMismatch, err, errx7f) - } -} - func TestMain(m *testing.M) { c.SetDefaults() if testingInSandbox { @@ -113,6 +98,7 @@ func TestMain(m *testing.M) { if err != nil { log.Fatal(err) } + // c.Verbose = true os.Exit(m.Run()) } @@ -177,12 +163,12 @@ func TestGetBestBidAsk(t *testing.T) { assert.NotEmpty(t, resp, errExpectedNonEmpty) } -func TestGetProductBook(t *testing.T) { +func TestGetProductBookV3(t *testing.T) { t.Parallel() _, err := c.GetProductBookV3(context.Background(), "", 0) assert.ErrorIs(t, err, errProductIDEmpty) sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - resp, err := c.GetProductBookV3(context.Background(), testPair.String(), 2) + resp, err := c.GetProductBookV3(context.Background(), "BTC-PERP-INTX", 2) assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) } @@ -255,18 +241,26 @@ func TestPlaceOrder(t *testing.T) { assert.NotEmpty(t, resp, errExpectedNonEmpty) } +func orderTestHelper(t *testing.T, orderSide string) *GetAllOrdersResp { + t.Helper() + ordIDs, err := c.GetAllOrders(context.Background(), "", "", "", orderSide, "", "", "", "", "", []string{}, + []string{}, 1000, time.Time{}, time.Time{}) + assert.NoError(t, err) + if ordIDs == nil || len(ordIDs.Orders) == 0 { + t.Skip(skipInsufficientOrders) + } + return ordIDs +} + func TestCancelOrders(t *testing.T) { t.Parallel() - var OrderSlice []string - _, err := c.CancelOrders(context.Background(), OrderSlice) + var orderSlice []string + _, err := c.CancelOrders(context.Background(), orderSlice) assert.ErrorIs(t, err, errOrderIDEmpty) sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) - skipTestIfLowOnFunds(t) - ordID, err := c.PlaceOrder(context.Background(), "meow", testPair.String(), order.Sell.String(), "", - order.Limit.String(), "", "", "", testPrice, testAmount, 0, 9999, false, time.Time{}) - assert.NoError(t, err) - OrderSlice = append(OrderSlice, ordID.OrderID) - resp, err := c.CancelOrders(context.Background(), OrderSlice) + ordIDs := orderTestHelper(t, "") + orderSlice = append(orderSlice, ordIDs.Orders[0].OrderID) + resp, err := c.CancelOrders(context.Background(), orderSlice) assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) } @@ -278,13 +272,8 @@ func TestEditOrder(t *testing.T) { _, err = c.EditOrder(context.Background(), "meow", 0, 0) assert.ErrorIs(t, err, errSizeAndPriceZero) sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) - skipTestIfLowOnFunds(t) - id, err := uuid.NewV4() - assert.NoError(t, err) - ordID, err := c.PlaceOrder(context.Background(), id.String(), testPair.String(), order.Sell.String(), "", - order.Limit.String(), "", "", "", testAmount, testPrice, 0, 9999, false, time.Time{}) - assert.NoError(t, err) - resp, err := c.EditOrder(context.Background(), ordID.OrderID, testAmount, testPrice*10) + ordIDs := orderTestHelper(t, "SELL") + resp, err := c.EditOrder(context.Background(), ordIDs.Orders[0].OrderID, testAmount, testPrice*10) assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) } @@ -296,20 +285,15 @@ func TestEditOrderPreview(t *testing.T) { _, err = c.EditOrderPreview(context.Background(), "meow", 0, 0) assert.ErrorIs(t, err, errSizeAndPriceZero) sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - skipTestIfLowOnFunds(t) - id, err := uuid.NewV4() - assert.NoError(t, err) - ordID, err := c.PlaceOrder(context.Background(), id.String(), testPair.String(), order.Sell.String(), "", - order.Limit.String(), "", "", "", testAmount, testPrice, 0, 9999, false, time.Time{}) - assert.NoError(t, err) - resp, err := c.EditOrderPreview(context.Background(), ordID.OrderID, testAmount, testPrice*10) + ordIDs := orderTestHelper(t, "") + resp, err := c.EditOrderPreview(context.Background(), ordIDs.Orders[0].OrderID, testAmount, testPrice*10) assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) } func TestGetAllOrders(t *testing.T) { t.Parallel() - assets := []string{"USD"} + assets := []string{testFiat.String()} status := make([]string, 2) _, err := c.GetAllOrders(context.Background(), "", "", "", "", "", "", "", "", "", status, assets, 0, time.Unix(2, 2), time.Unix(1, 1)) @@ -323,8 +307,8 @@ func TestGetAllOrders(t *testing.T) { status = make([]string, 0) assets = make([]string, 1) assets[0] = testCrypto.String() - _, err = c.GetAllOrders(context.Background(), "", "USD", "LIMIT", "SELL", "", "SPOT", "RETAIL_ADVANCED", - "UNKNOWN_CONTRACT_EXPIRY_TYPE", "2", status, assets, 10, time.Time{}, time.Time{}) + _, err = c.GetAllOrders(context.Background(), "", testFiat.String(), "LIMIT", "SELL", "", "SPOT", + "RETAIL_ADVANCED", "UNKNOWN_CONTRACT_EXPIRY_TYPE", "2", status, assets, 10, time.Time{}, time.Time{}) assert.NoError(t, err) } @@ -503,7 +487,7 @@ func TestAllocatePortfolio(t *testing.T) { assert.ErrorIs(t, err, errCurrencyEmpty) sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) pID := getINTXPortfolio(t) - err = c.AllocatePortfolio(context.Background(), pID, testCrypto.String(), "USD", 0.001337) + err = c.AllocatePortfolio(context.Background(), pID, testCrypto.String(), testFiat.String(), 0.001337) assert.NoError(t, err) } @@ -683,7 +667,7 @@ func TestGetAddressByID(t *testing.T) { assert.NotEmpty(t, wID, errExpectedNonEmpty) addID, err := c.GetAllAddresses(context.Background(), wID.Data.ID, PaginationInp{}) assert.NoError(t, err) - assert.NotEmpty(t, addID, errExpectedNonEmpty) + require.NotEmpty(t, addID, errExpectedNonEmpty) resp, err := c.GetAddressByID(context.Background(), wID.Data.ID, addID.Data[0].ID) assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) @@ -701,7 +685,7 @@ func TestGetAddressTransactions(t *testing.T) { assert.NotEmpty(t, wID, errExpectedNonEmpty) addID, err := c.GetAllAddresses(context.Background(), wID.Data.ID, PaginationInp{}) assert.NoError(t, err) - assert.NotEmpty(t, addID, errExpectedNonEmpty) + require.NotEmpty(t, addID, errExpectedNonEmpty) _, err = c.GetAddressTransactions(context.Background(), wID.Data.ID, addID.Data[0].ID, PaginationInp{}) assert.NoError(t, err) } @@ -914,11 +898,7 @@ func TestGetAllCurrencies(t *testing.T) { func TestGetACurrency(t *testing.T) { t.Parallel() - _, err := c.GetACurrency(context.Background(), "") - assert.ErrorIs(t, err, errCurrencyEmpty) - resp, err := c.GetACurrency(context.Background(), testCrypto.String()) - assert.NoError(t, err) - assert.NotEmpty(t, resp, errExpectedNonEmpty) + testGetOneArg(t, c.GetACurrency, testCrypto.String(), errCurrencyEmpty) } func TestGetAllTradingPairs(t *testing.T) { @@ -934,11 +914,7 @@ func TestGetAllPairVolumes(t *testing.T) { func TestGetPairDetails(t *testing.T) { t.Parallel() - _, err := c.GetPairDetails(context.Background(), "") - assert.ErrorIs(t, err, errPairEmpty) - resp, err := c.GetPairDetails(context.Background(), testPair.String()) - assert.NoError(t, err) - assert.NotEmpty(t, resp, errExpectedNonEmpty) + testGetOneArg(t, c.GetPairDetails, testPair.String(), errPairEmpty) } func TestGetProductBookV1(t *testing.T) { @@ -955,28 +931,21 @@ func TestGetProductBookV1(t *testing.T) { func TestGetProductCandles(t *testing.T) { t.Parallel() - err := c.GetProductCandles(context.Background(), "", 0, time.Time{}, time.Time{}) + _, err := c.GetProductCandles(context.Background(), "", 0, time.Time{}, time.Time{}) assert.ErrorIs(t, err, errPairEmpty) - err = c.GetProductCandles(context.Background(), testPair.String(), 300, time.Time{}, time.Time{}) + resp, err := c.GetProductCandles(context.Background(), testPair.String(), 300, time.Time{}, time.Time{}) assert.NoError(t, err) + assert.NotEmpty(t, resp, errExpectedNonEmpty) } func TestGetProductStats(t *testing.T) { t.Parallel() - _, err := c.GetProductStats(context.Background(), "") - assert.ErrorIs(t, err, errPairEmpty) - resp, err := c.GetProductStats(context.Background(), testPair.String()) - assert.NoError(t, err) - assert.NotEmpty(t, resp, errExpectedNonEmpty) + testGetOneArg(t, c.GetProductStats, testPair.String(), errPairEmpty) } func TestGetProductTicker(t *testing.T) { t.Parallel() - _, err := c.GetProductTicker(context.Background(), "") - assert.ErrorIs(t, err, errPairEmpty) - resp, err := c.GetProductTicker(context.Background(), testPair.String()) - assert.NoError(t, err) - assert.NotEmpty(t, resp, errExpectedNonEmpty) + testGetOneArg(t, c.GetProductTicker, testPair.String(), errPairEmpty) } func TestGetProductTrades(t *testing.T) { @@ -993,12 +962,20 @@ func TestGetAllWrappedAssets(t *testing.T) { testGetNoArgs(t, c.GetAllWrappedAssets) } +func TestGetWrappedAssetDetails(t *testing.T) { + t.Parallel() + testGetOneArg(t, c.GetWrappedAssetDetails, testWrappedAsset.String(), errWrappedAssetEmpty) +} + +func TestGetWrappedAssetConversionRate(t *testing.T) { + t.Parallel() + testGetOneArg(t, c.GetWrappedAssetConversionRate, testWrappedAsset.String(), errWrappedAssetEmpty) +} + func TestSendHTTPRequest(t *testing.T) { t.Parallel() err := c.SendHTTPRequest(context.Background(), exchange.EdgeCase3, "", nil) - if err.Error() != errNoEndpointPathEdgeCase3 { - t.Errorf(errExpectMismatch, err, errNoEndpointPathEdgeCase3) - } + assert.ErrorIs(t, err, exchange.ErrEndpointPathNotFound) } func TestSendAuthenticatedHTTPRequest(t *testing.T) { @@ -1008,9 +985,7 @@ func TestSendAuthenticatedHTTPRequest(t *testing.T) { assert.ErrorIs(t, err, exchange.ErrCredentialsAreEmpty) sharedtestvalues.SkipTestIfCredentialsUnset(t, c) err = c.SendAuthenticatedHTTPRequest(context.Background(), exchange.EdgeCase3, "", "", nil, nil, false, nil, nil) - if err.Error() != errNoEndpointPathEdgeCase3 { - t.Errorf(errExpectMismatch, err, errNoEndpointPathEdgeCase3) - } + assert.ErrorIs(t, err, exchange.ErrEndpointPathNotFound) ch := make(chan struct{}) body := map[string]interface{}{"Unmarshalable": ch} err = c.SendAuthenticatedHTTPRequest(context.Background(), exchange.RestSpot, "", "", nil, body, false, nil, nil) @@ -1073,10 +1048,10 @@ func TestFetchTradablePairs(t *testing.T) { t.Parallel() _, err := c.FetchTradablePairs(context.Background(), asset.Empty) assert.ErrorIs(t, err, asset.ErrNotSupported) - sharedtestvalues.SkipTestIfCredentialsUnset(t, c) resp, err := c.FetchTradablePairs(context.Background(), asset.Spot) assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) resp, err = c.FetchTradablePairs(context.Background(), asset.Futures) assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) @@ -1084,9 +1059,12 @@ func TestFetchTradablePairs(t *testing.T) { func TestUpdateTradablePairs(t *testing.T) { t.Parallel() - sharedtestvalues.SkipTestIfCredentialsUnset(t, c) err := c.UpdateTradablePairs(context.Background(), false) - assert.NoError(t, err) + if sharedtestvalues.AreAPICredentialsSet(c) { + assert.NoError(t, err) + } else { + assert.ErrorIs(t, err, errAuthenticationNeeded) + } } func TestUpdateAccountInfo(t *testing.T) { @@ -1107,18 +1085,17 @@ func TestFetchAccountInfo(t *testing.T) { func TestUpdateTickers(t *testing.T) { t.Parallel() - sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - err := c.UpdateTickers(context.Background(), asset.Futures) + err := c.UpdateTickers(context.Background(), asset.Spot) assert.NoError(t, err) - err = c.UpdateTickers(context.Background(), asset.Spot) + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + err = c.UpdateTickers(context.Background(), asset.Futures) assert.NoError(t, err) } func TestUpdateTicker(t *testing.T) { t.Parallel() - _, err := c.UpdateTicker(context.Background(), currency.Pair{}, asset.Empty) + _, err := c.UpdateTicker(context.Background(), currency.Pair{}, asset.Spot) assert.ErrorIs(t, err, currency.ErrCurrencyPairEmpty) - sharedtestvalues.SkipTestIfCredentialsUnset(t, c) resp, err := c.UpdateTicker(context.Background(), testPair, asset.Spot) assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) @@ -1126,7 +1103,6 @@ func TestUpdateTicker(t *testing.T) { func TestFetchTicker(t *testing.T) { t.Parallel() - sharedtestvalues.SkipTestIfCredentialsUnset(t, c) resp, err := c.FetchTicker(context.Background(), testPair, asset.Spot) assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) @@ -1134,7 +1110,6 @@ func TestFetchTicker(t *testing.T) { func TestFetchOrderbook(t *testing.T) { t.Parallel() - sharedtestvalues.SkipTestIfCredentialsUnset(t, c) resp, err := c.FetchOrderbook(context.Background(), testPair, asset.Spot) assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) @@ -1144,10 +1119,10 @@ func TestUpdateOrderbook(t *testing.T) { t.Parallel() _, err := c.UpdateOrderbook(context.Background(), currency.Pair{}, asset.Empty) assert.ErrorIs(t, err, currency.ErrCurrencyPairEmpty) - sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - _, err = c.UpdateOrderbook(context.Background(), currency.NewPairWithDelimiter("meow", "woof", "-"), asset.Spot) - if err.Error() != errInvalidProductID { - t.Errorf(errExpectMismatch, err, errInvalidProductID) + _, err = c.UpdateOrderbook(context.Background(), currency.NewPairWithDelimiter("meow", "woof", "-"), + asset.Spot) + if err.Error() != errInvalidProductID && err.Error() != errValidProductIDRequired { + t.Errorf(errExpectMismatch2, err, errInvalidProductID, errValidProductIDRequired) } resp, err := c.UpdateOrderbook(context.Background(), testPair, asset.Spot) assert.NoError(t, err) @@ -1213,13 +1188,8 @@ func TestModifyOrder(t *testing.T) { assert.ErrorIs(t, err, order.ErrPairIsEmpty) sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) skipTestIfLowOnFunds(t) - resp, err := c.PlaceOrder(context.Background(), strconv.FormatInt(time.Now().UnixMilli(), 18)+"GCTModifyOrderTest", - testPair.String(), order.Sell.String(), "", order.Limit.String(), "", "", "", testAmount, testPrice, 0, 9999, - false, time.Time{}) - if err != nil { - t.Fatal(err) - } - ord.OrderID = resp.OrderID + ordIDs := orderTestHelper(t, "SELL") + ord.OrderID = ordIDs.Orders[0].OrderID ord.Price = testPrice + 1 resp2, err := c.ModifyOrder(context.Background(), &ord) assert.NoError(t, err) @@ -1232,21 +1202,13 @@ func TestCancelOrder(t *testing.T) { assert.ErrorIs(t, err, common.ErrNilPointer) var can order.Cancel err = c.CancelOrder(context.Background(), &can) - if err.Error() != errIDNotSet { - t.Errorf(errExpectMismatch, err, errIDNotSet) - } + assert.ErrorIs(t, err, order.ErrIDNotSet) sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) can.OrderID = "0" err = c.CancelOrder(context.Background(), &can) assert.ErrorIs(t, err, errOrderFailedToCancel) - skipTestIfLowOnFunds(t) - resp, err := c.PlaceOrder(context.Background(), strconv.FormatInt(time.Now().UnixMilli(), 18)+"GCTCancelOrderTest", - testPair.String(), order.Sell.String(), "", order.Limit.String(), "", "", "", testAmount, testPrice, 0, 9999, - false, time.Time{}) - if err != nil { - t.Fatal(err) - } - can.OrderID = resp.OrderID + ordIDs := orderTestHelper(t, "") + can.OrderID = ordIDs.Orders[0].OrderID err = c.CancelOrder(context.Background(), &can) assert.NoError(t, err) } @@ -1257,18 +1219,10 @@ func TestCancelBatchOrders(t *testing.T) { assert.ErrorIs(t, err, errOrderIDEmpty) can := make([]order.Cancel, 1) _, err = c.CancelBatchOrders(context.Background(), can) - if err.Error() != errIDNotSet { - t.Errorf(errExpectMismatch, err, errIDNotSet) - } + assert.ErrorIs(t, err, order.ErrIDNotSet) sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) - skipTestIfLowOnFunds(t) - resp, err := c.PlaceOrder(context.Background(), - strconv.FormatInt(time.Now().UnixMilli(), 18)+"GCTCancelBatchOrdersTest", testPair.String(), - order.Sell.String(), "", order.Limit.String(), "", "", "", testAmount, testPrice, 0, 9999, false, time.Time{}) - if err != nil { - t.Fatal(err) - } - can[0].OrderID = resp.OrderID + ordIDs := orderTestHelper(t, "") + can[0].OrderID = ordIDs.Orders[0].OrderID resp2, err := c.CancelBatchOrders(context.Background(), can) assert.NoError(t, err) assert.NotEmpty(t, resp2, errExpectedNonEmpty) @@ -1282,13 +1236,6 @@ func TestCancelAllOrders(t *testing.T) { _, err = c.CancelAllOrders(context.Background(), &can) assert.ErrorIs(t, err, order.ErrPairIsEmpty) sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) - skipTestIfLowOnFunds(t) - _, err = c.PlaceOrder(context.Background(), - strconv.FormatInt(time.Now().UnixMilli(), 18)+"GCTCancelAllOrdersTest", testPair.String(), - order.Sell.String(), "", order.Limit.String(), "", "", "", testAmount, testPrice, 0, 9999, false, time.Time{}) - if err != nil { - t.Fatal(err) - } can.Pair = testPair can.AssetType = asset.Spot resp, err := c.CancelAllOrders(context.Background(), &can) @@ -1367,7 +1314,6 @@ func TestGetFeeByType(t *testing.T) { t.Parallel() _, err := c.GetFeeByType(context.Background(), nil) assert.ErrorIs(t, err, common.ErrNilPointer) - sharedtestvalues.SkipTestIfCredentialsUnset(t, c) var feeBuilder exchange.FeeBuilder feeBuilder.FeeType = exchange.OfflineTradeFee feeBuilder.Amount = 1 @@ -1418,9 +1364,8 @@ func TestGetHistoricCandles(t *testing.T) { _, err := c.GetHistoricCandles(context.Background(), currency.Pair{}, asset.Empty, kline.OneYear, time.Time{}, time.Time{}) assert.ErrorIs(t, err, currency.ErrCurrencyPairEmpty) - sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - resp, err := c.GetHistoricCandles(context.Background(), testPair, asset.Spot, kline.ThreeHour, - time.Now().Add(-time.Hour*30), time.Now()) + resp, err := c.GetHistoricCandles(context.Background(), testPair, asset.Spot, kline.SixHour, + time.Now().Add(-time.Hour*60), time.Now()) assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) } @@ -1430,7 +1375,6 @@ func TestGetHistoricCandlesExtended(t *testing.T) { _, err := c.GetHistoricCandlesExtended(context.Background(), currency.Pair{}, asset.Empty, kline.OneYear, time.Time{}, time.Time{}) assert.ErrorIs(t, err, currency.ErrCurrencyPairEmpty) - sharedtestvalues.SkipTestIfCredentialsUnset(t, c) resp, err := c.GetHistoricCandlesExtended(context.Background(), testPair, asset.Spot, kline.OneMin, time.Now().Add(-time.Hour*9), time.Now()) assert.NoError(t, err) @@ -1480,8 +1424,7 @@ func TestUpdateOrderExecutionLimits(t *testing.T) { t.Parallel() err := c.UpdateOrderExecutionLimits(context.Background(), asset.UpsideProfitContract) assert.ErrorIs(t, err, asset.ErrNotSupported) - sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - err = c.UpdateOrderExecutionLimits(context.Background(), asset.Futures) + err = c.UpdateOrderExecutionLimits(context.Background(), asset.Spot) assert.NoError(t, err) } @@ -1520,9 +1463,10 @@ func TestUnixTimestampString(t *testing.T) { } } -func TestFormatExchangeKlineInterval(t *testing.T) { +func TestFormatExchangeKlineIntervalV3(t *testing.T) { t.Parallel() testSequence := map[kline.Interval]string{ + kline.OneMin: granOneMin, kline.FiveMin: granFiveMin, kline.FifteenMin: granFifteenMin, kline.ThirtyMin: granThirtyMin, @@ -1531,7 +1475,25 @@ func TestFormatExchangeKlineInterval(t *testing.T) { kline.OneDay: granOneDay, kline.OneWeek: errIntervalNotSupported} for k := range testSequence { - resp := formatExchangeKlineInterval(k) + resp := formatExchangeKlineIntervalV3(k) + if resp != testSequence[k] { + t.Errorf(errExpectMismatch, resp, testSequence[k]) + } + } +} + +func TestFormatExchangeKlineIntervalV1(t *testing.T) { + t.Parallel() + testSequence := map[kline.Interval]uint32{ + kline.OneMin: 60, + kline.FiveMin: 300, + kline.FifteenMin: 900, + kline.OneHour: 3600, + kline.SixHour: 21600, + kline.OneDay: 86400, + kline.OneWeek: 1<<32 - 1} + for k := range testSequence { + resp := formatExchangeKlineIntervalV1(k) if resp != testSequence[k] { t.Errorf(errExpectMismatch, resp, testSequence[k]) } @@ -1719,9 +1681,7 @@ func TestWsHandleData(t *testing.T) { assert.ErrorAs(t, err, &targetErr) mockJSON = []byte(`{"sequence_num": 0, "channel": "user", "events": [{"type": "moo", "orders": [{"total_fees": "1.1"}]}]}`) _, err = c.wsHandleData(mockJSON, 0) - if err.Error() != errUnrecognisedOrderType { - t.Errorf(errExpectMismatch, err, errUnrecognisedOrderType) - } + assert.ErrorIs(t, err, order.ErrUnrecognisedOrderType) mockJSON = []byte(`{"sequence_num": 0, "channel": "user", "events": [{"type": "moo", "orders": [{"total_fees": "1.1", "order_type": "ioc"}]}]}`) _, err = c.wsHandleData(mockJSON, 0) assert.ErrorIs(t, err, order.ErrSideIsInvalid) @@ -1887,10 +1847,10 @@ func convertTestHelper(t *testing.T) (fromAccID, toAccID string) { t.Fatal(errExpectedNonEmpty) } for x := range accIDs.Accounts { - if accIDs.Accounts[x].Currency == "USDC" { + if accIDs.Accounts[x].Currency == testStable.String() { fromAccID = accIDs.Accounts[x].UUID } - if accIDs.Accounts[x].Currency == "USD" { + if accIDs.Accounts[x].Currency == testFiat.String() { toAccID = accIDs.Accounts[x].UUID } if fromAccID != "" && toAccID != "" { @@ -2001,3 +1961,18 @@ func convertTestShared(t *testing.T, f genConvertTestFunc) { assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) } + +type getOneArgResp interface { + *CurrencyData | *PairData | *ProductStats | *ProductTicker | *WrappedAsset | *WrappedAssetConversionRate +} + +type getOneArgAssertNotEmpty[G getOneArgResp] func(context.Context, string) (G, error) + +func testGetOneArg[G getOneArgResp](t *testing.T, f getOneArgAssertNotEmpty[G], arg string, tarErr error) { + t.Helper() + _, err := f(context.Background(), "") + assert.ErrorIs(t, err, tarErr) + resp, err := f(context.Background(), arg) + assert.NoError(t, err) + assert.NotEmpty(t, resp, errExpectedNonEmpty) +} diff --git a/exchanges/coinbasepro/coinbasepro_types.go b/exchanges/coinbasepro/coinbasepro_types.go index 2c7e069676c..c30514b02cf 100644 --- a/exchanges/coinbasepro/coinbasepro_types.go +++ b/exchanges/coinbasepro/coinbasepro_types.go @@ -1340,10 +1340,10 @@ type PairData struct { ID string `json:"id"` BaseCurrency string `json:"base_currency"` QuoteCurrency string `json:"quote_currency"` - QuoteIncrement string `json:"quote_increment"` - BaseIncrement string `json:"base_increment"` + QuoteIncrement float64 `json:"quote_increment,string"` + BaseIncrement float64 `json:"base_increment,string"` DisplayName string `json:"display_name"` - MinMarketFunds string `json:"min_market_funds"` + MinMarketFunds float64 `json:"min_market_funds,string"` MarginEnabled bool `json:"margin_enabled"` PostOnly bool `json:"post_only"` LimitOnly bool `json:"limit_only"` @@ -1463,7 +1463,7 @@ type ProductTrades struct { Time time.Time `json:"time"` } -// WrappedAsset holds information on a wrapped asset, used in AllWrappedAssets +// WrappedAsset holds information on a wrapped asset, used in AllWrappedAssets and returned by GetWrappedAssetDetails type WrappedAsset struct { ID string `json:"id"` CirculatingSupply float64 `json:"circulating_supply,string"` @@ -1476,3 +1476,9 @@ type WrappedAsset struct { type AllWrappedAssets struct { WrappedAssets []WrappedAsset `json:"wrapped_assets"` } + +// WrappedAssetConversionRate holds information on a wrapped asset's conversion rate, returned by +// GetWrappedAssetConversionRate +type WrappedAssetConversionRate struct { + Amount float64 `json:"amount,string"` +} diff --git a/exchanges/coinbasepro/coinbasepro_wrapper.go b/exchanges/coinbasepro/coinbasepro_wrapper.go index 8df1b48d86c..6a0283ddfb8 100644 --- a/exchanges/coinbasepro/coinbasepro_wrapper.go +++ b/exchanges/coinbasepro/coinbasepro_wrapper.go @@ -2,6 +2,7 @@ package coinbasepro import ( "context" + "errors" "fmt" "math" "strconv" @@ -170,11 +171,22 @@ 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 err error + var pairData []PairData + unverified, err := c.verificationCheck(ctx) + if err != nil { + return nil, err + } switch a { case asset.Spot: - products, err = c.GetAllProducts(ctx, 2<<30-1, 0, asset.Spot.Upper(), "", "", nil) + if unverified { + pairData, err = c.GetAllTradingPairs(ctx, "") + } else { + products, err = c.GetAllProducts(ctx, 2<<30-1, 0, asset.Spot.Upper(), "", "", nil) + } case asset.Futures: + if unverified { + return nil, fmt.Errorf("%w for %v", errAuthenticationNeeded, asset.Futures) + } products, err = c.fetchFutures(ctx) default: err = asset.ErrNotSupported @@ -182,17 +194,29 @@ func (c *CoinbasePro) FetchTradablePairs(ctx context.Context, a asset.Item) (cur if err != nil { return nil, err } - pairs := make([]currency.Pair, 0, len(products.Products)) - for x := range products.Products { - if products.Products[x].TradingDisabled { - continue + var pairs []currency.Pair + if unverified { + for i := range pairData { + if pairData[i].TradingDisabled { + continue + } + pair, err := currency.NewPairDelimiter(pairData[i].ID, currency.DashDelimiter) + if err != nil { + return nil, err + } + pairs = append(pairs, pair) } - var pair currency.Pair - pair, err = currency.NewPairDelimiter(products.Products[x].ID, currency.DashDelimiter) - if err != nil { - return nil, err + } else { + for x := range products.Products { + if products.Products[x].TradingDisabled { + continue + } + pair, err := currency.NewPairDelimiter(products.Products[x].ID, currency.DashDelimiter) + if err != nil { + return nil, err + } + pairs = append(pairs, pair) } - pairs = append(pairs, pair) } return pairs, nil } @@ -277,31 +301,19 @@ 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 { + unverified, err := c.verificationCheck(ctx) + if err != nil { + return err + } + if unverified && assetType != asset.Spot { + return fmt.Errorf("%w for %v", errAuthenticationNeeded, 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 { - return err - } - pair, err := currency.NewPairDelimiter(products[x].String(), currency.DashDelimiter) - if err != nil { - return err - } - var last float64 - if len(tick.Trades) != 0 { - last = tick.Trades[0].Price - } - err = ticker.ProcessTicker(&ticker.Price{ - Last: last, - Bid: tick.BestBid.Float64(), - Ask: tick.BestAsk.Float64(), - Pair: pair, - ExchangeName: c.Name, - AssetType: assetType, - }) + err = c.tickerHelper(ctx, products[x].String(), assetType, unverified) if err != nil { return err } @@ -311,28 +323,20 @@ func (c *CoinbasePro) UpdateTickers(ctx context.Context, assetType asset.Item) e // UpdateTicker updates and returns the ticker for a currency pair func (c *CoinbasePro) UpdateTicker(ctx context.Context, p currency.Pair, a asset.Item) (*ticker.Price, error) { - fPair, err := c.FormatExchangeCurrency(p, a) + unverified, err := c.verificationCheck(ctx) if err != nil { return nil, err } - tick, err := c.GetTicker(ctx, fPair.String(), 1, time.Time{}, time.Time{}) + if unverified && a != asset.Spot { + return nil, fmt.Errorf("%w for %v", errAuthenticationNeeded, a) + } + fPair, err := c.FormatExchangeCurrency(p, a) 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(), - Ask: tick.BestAsk.Float64(), - Pair: p, - ExchangeName: c.Name, - AssetType: a} - err = ticker.ProcessTicker(tickerPrice) + err = c.tickerHelper(ctx, fPair.String(), a, unverified) if err != nil { - return tickerPrice, err + return nil, err } return ticker.GetTicker(c.Name, p, a) } @@ -365,7 +369,11 @@ func (c *CoinbasePro) FetchOrderbook(ctx context.Context, p currency.Pair, asset // UpdateOrderbook updates and returns the orderbook for a currency pair func (c *CoinbasePro) UpdateOrderbook(ctx context.Context, p currency.Pair, assetType asset.Item) (*orderbook.Base, error) { - p, err := c.FormatExchangeCurrency(p, assetType) + unverified, err := c.verificationCheck(ctx) + if err != nil { + return nil, err + } + p, err = c.FormatExchangeCurrency(p, assetType) if err != nil { return nil, err } @@ -376,32 +384,56 @@ func (c *CoinbasePro) UpdateOrderbook(ctx context.Context, p currency.Pair, asse if err != nil { return nil, err } + fPair, err := c.FormatExchangeCurrency(p, assetType) + if err != nil { + return nil, err + } book := &orderbook.Base{ Exchange: c.Name, Pair: p, Asset: assetType, VerifyOrderbook: c.CanVerifyOrderbook, } - fPair, err := c.FormatExchangeCurrency(p, assetType) - if err != nil { - return book, err - } - orderbookNew, err := c.GetProductBookV3(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{ - Amount: orderbookNew.Bids[x].Size, - Price: orderbookNew.Bids[x].Price, + if unverified { + if assetType != asset.Spot { + return nil, fmt.Errorf("%w for %v", errAuthenticationNeeded, assetType) } - } - book.Asks = make(orderbook.Items, len(orderbookNew.Asks)) - for x := range orderbookNew.Asks { - book.Asks[x] = orderbook.Item{ - Amount: orderbookNew.Asks[x].Size, - Price: orderbookNew.Asks[x].Price, + obN, err := c.GetProductBookV1(ctx, fPair.String(), 2) + if err != nil { + return book, err + } + book.Bids = make(orderbook.Items, len(obN.Bids)) + for x := range obN.Bids { + book.Bids[x] = orderbook.Item{ + Amount: obN.Bids[x].Size, + Price: obN.Bids[x].Price, + } + } + book.Asks = make(orderbook.Items, len(obN.Asks)) + for x := range obN.Asks { + book.Asks[x] = orderbook.Item{ + Amount: obN.Asks[x].Size, + Price: obN.Asks[x].Price, + } + } + } else { + orderbookNew, err := c.GetProductBookV3(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{ + Amount: orderbookNew.Bids[x].Size, + Price: orderbookNew.Bids[x].Price, + } + } + book.Asks = make(orderbook.Items, len(orderbookNew.Asks)) + for x := range orderbookNew.Asks { + book.Asks[x] = orderbook.Item{ + Amount: orderbookNew.Asks[x].Size, + Price: orderbookNew.Asks[x].Price, + } } } err = book.Process() @@ -790,8 +822,7 @@ func (c *CoinbasePro) GetFeeByType(ctx context.Context, feeBuilder *exchange.Fee if feeBuilder == nil { return 0, fmt.Errorf("%T %w", feeBuilder, common.ErrNilPointer) } - if !c.AreCredentialsValid(ctx) && // Todo check connection status - feeBuilder.FeeType == exchange.CryptocurrencyTradeFee { + if !c.AreCredentialsValid(ctx) && feeBuilder.FeeType == exchange.CryptocurrencyTradeFee { feeBuilder.FeeType = exchange.OfflineTradeFee } return c.GetFee(ctx, feeBuilder) @@ -891,21 +922,16 @@ 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) + unverified, err := c.verificationCheck(ctx) if err != nil { return nil, err } - timeSeries := make([]kline.Candle, len(history)) - for x := range history { - timeSeries[x] = kline.Candle{ - 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, - } + if unverified && a != asset.Spot { + return nil, fmt.Errorf("%w for %v", errAuthenticationNeeded, a) + } + timeSeries, err := c.candleHelper(ctx, req.RequestFormatted.String(), interval, start, end, unverified) + if err != nil { + return nil, err } return req.ProcessResponse(timeSeries) } @@ -916,26 +942,22 @@ func (c *CoinbasePro) GetHistoricCandlesExtended(ctx context.Context, pair curre if err != nil { return nil, err } - timeSeries := make([]kline.Candle, 0, req.Size()) + unverified, err := c.verificationCheck(ctx) + if err != nil { + return nil, err + } + if unverified && a != asset.Spot { + return nil, fmt.Errorf("%w for %v", errAuthenticationNeeded, a) + } + var timeSeries []kline.Candle for x := range req.RangeHolder.Ranges { - var history []CandleStruct - history, err := c.GetHistoricRates(ctx, req.RequestFormatted.String(), - formatExchangeKlineInterval(req.ExchangeInterval), + hist, err := c.candleHelper(ctx, req.RequestFormatted.String(), interval, req.RangeHolder.Ranges[x].Start.Time.Add(-time.Nanosecond), - req.RangeHolder.Ranges[x].End.Time.Add(-time.Nanosecond)) + req.RangeHolder.Ranges[x].End.Time.Add(-time.Nanosecond), unverified) if err != nil { return nil, err } - for i := range history { - timeSeries = append(timeSeries, kline.Candle{ - 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, - }) - } + timeSeries = append(timeSeries, hist...) } return req.ProcessResponse(timeSeries) } @@ -1028,11 +1050,22 @@ func (c *CoinbasePro) GetFuturesContractDetails(ctx context.Context, item asset. // UpdateOrderExecutionLimits updates order execution limits func (c *CoinbasePro) UpdateOrderExecutionLimits(ctx context.Context, a asset.Item) error { var data *AllProducts - var err error + var pairData []PairData + unverified, err := c.verificationCheck(ctx) + if err != nil { + return err + } switch a { case asset.Spot: - data, err = c.GetAllProducts(ctx, 2<<30-1, 0, "SPOT", "", "", nil) + if unverified { + pairData, err = c.GetAllTradingPairs(ctx, "") + } else { + data, err = c.GetAllProducts(ctx, 2<<30-1, 0, asset.Spot.Upper(), "", "", nil) + } case asset.Futures: + if unverified { + return fmt.Errorf("%w for %v", errAuthenticationNeeded, asset.Futures) + } data, err = c.fetchFutures(ctx) default: err = fmt.Errorf("%w %s", asset.ErrNotSupported, a) @@ -1040,25 +1073,44 @@ 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 + var limits []order.MinMaxLevel + if unverified { + limits = make([]order.MinMaxLevel, len(pairData)) + for i := range pairData { + pair, err := currency.NewPairFromString(pairData[i].ID) + if err != nil { + return err + } + limits[i] = order.MinMaxLevel{ + Pair: pair, + Asset: a, + PriceStepIncrementSize: pairData[i].QuoteIncrement, + AmountStepIncrementSize: pairData[i].BaseIncrement, + QuoteStepIncrementSize: pairData[i].QuoteIncrement, + MaxTotalOrders: 1000, + } } - limits[i] = order.MinMaxLevel{ - Pair: pair, - Asset: a, - MinPrice: data.Products[i].QuoteMinSize.Float64(), - MaxPrice: data.Products[i].QuoteMaxSize.Float64(), - PriceStepIncrementSize: data.Products[i].PriceIncrement.Float64(), - MinimumBaseAmount: data.Products[i].BaseMinSize.Float64(), - MaximumBaseAmount: data.Products[i].BaseMaxSize.Float64(), - MinimumQuoteAmount: data.Products[i].QuoteMinSize.Float64(), - MaximumQuoteAmount: data.Products[i].QuoteMaxSize.Float64(), - AmountStepIncrementSize: data.Products[i].BaseIncrement.Float64(), - QuoteStepIncrementSize: data.Products[i].QuoteIncrement.Float64(), - MaxTotalOrders: 1000, + } else { + 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, + MinPrice: data.Products[i].QuoteMinSize.Float64(), + MaxPrice: data.Products[i].QuoteMaxSize.Float64(), + PriceStepIncrementSize: data.Products[i].PriceIncrement.Float64(), + MinimumBaseAmount: data.Products[i].BaseMinSize.Float64(), + MaximumBaseAmount: data.Products[i].BaseMaxSize.Float64(), + MinimumQuoteAmount: data.Products[i].QuoteMinSize.Float64(), + MaximumQuoteAmount: data.Products[i].QuoteMaxSize.Float64(), + AmountStepIncrementSize: data.Products[i].BaseIncrement.Float64(), + QuoteStepIncrementSize: data.Products[i].QuoteIncrement.Float64(), + MaxTotalOrders: 1000, + } } } return c.LoadLimits(limits) @@ -1179,9 +1231,9 @@ func (c *CoinbasePro) iterativeGetAllOrders(ctx context.Context, productID, orde return resp, nil } -// formatExchangeKlineInterval is a helper function used in GetHistoricCandles and GetHistoricCandlesExtended -// to convert kline.Interval to the string format used by Coinbase -func formatExchangeKlineInterval(interval kline.Interval) string { +// formatExchangeKlineIntervalV3 is a helper function used in GetHistoricCandles and GetHistoricCandlesExtended +// to convert kline.Interval to the string format used by V3 of Coinbase's API +func formatExchangeKlineIntervalV3(interval kline.Interval) string { switch interval { case kline.OneMin: return granOneMin @@ -1203,6 +1255,26 @@ func formatExchangeKlineInterval(interval kline.Interval) string { return errIntervalNotSupported } +// formatExchangeKlineIntervalV1 is a helper function used in GetHistoricCandles and GetHistoricCandlesExtended +// to convert kline.Interval to the uint format used by V1 of Coinbase's API +func formatExchangeKlineIntervalV1(interval kline.Interval) uint32 { + switch interval { + case kline.OneMin: + return 60 + case kline.FiveMin: + return 300 + case kline.FifteenMin: + return 900 + case kline.OneHour: + return 3600 + case kline.SixHour: + return 21600 + case kline.OneDay: + return 86400 + } + return 1<<32 - 1 +} + // getOrderRespToOrderDetail is a helper function used in GetOrderInfo, GetActiveOrders, and GetOrderHistory // to convert data returned by the Coinbase API into a format suitable for the exchange package func (c *CoinbasePro) getOrderRespToOrderDetail(genOrderDetail *GetOrderResponse, pair currency.Pair, assetItem asset.Item) (*order.Detail, error) { @@ -1353,3 +1425,108 @@ func stringToFloatPtr(outgoing *float64, incoming string) error { } return nil } + +// VerificationCheck returns whether authentication support is enabled or not +func (c *CoinbasePro) verificationCheck(ctx context.Context) (bool, error) { + _, err := c.GetCredentials(ctx) + if err != nil { + if errors.Is(err, exchange.ErrAuthenticationSupportNotEnabled) { + return true, nil + } + return false, err + } + return false, nil +} + +// TickerHelper fetches the ticker for a given currency pair, used by UpdateTickers and UpdateTicker +func (c *CoinbasePro) tickerHelper(ctx context.Context, name string, assetType asset.Item, unverified bool) error { + pair, err := currency.NewPairDelimiter(name, currency.DashDelimiter) + if err != nil { + return err + } + newTick := &ticker.Price{ + Pair: pair, + ExchangeName: c.Name, + AssetType: assetType, + } + if unverified { + tick, err := c.GetProductTicker(ctx, name) + if err != nil { + return err + } + newTick.Last = tick.Price + newTick.Volume = tick.Volume + newTick.Bid = tick.Bid + newTick.Ask = tick.Ask + } else { + ticks, err := c.GetTicker(ctx, name, 1, time.Time{}, time.Time{}) + if err != nil { + return err + } + var last float64 + if len(ticks.Trades) != 0 { + last = ticks.Trades[0].Price + } + newTick.Last = last + newTick.Bid = ticks.BestBid.Float64() + newTick.Ask = ticks.BestAsk.Float64() + } + err = ticker.ProcessTicker(newTick) + if err != nil { + return err + } + return nil +} + +// This will only be valuable once it would be used four times; it's currently sitting at two. +// VerifyAssetCheck checks whether the user can access authenticated endpoints, and whether one is necessary, +// providing errors accordingly +func (c *CoinbasePro) verifyAssetCheck(ctx context.Context, assetType asset.Item) (bool, error) { + unverified, err := c.verificationCheck(ctx) + if err != nil { + return unverified, err + } + if unverified && assetType != asset.Spot { + return unverified, fmt.Errorf("%w for %v", errAuthenticationNeeded, assetType) + } + return unverified, nil +} + +// CandleHelper handles calling the correct candle function, and doing preliminary work on the data +func (c *CoinbasePro) candleHelper(ctx context.Context, pair string, granularity kline.Interval, start, end time.Time, unverified bool) ([]kline.Candle, error) { + var timeSeries []kline.Candle + if unverified { + hist, err := c.GetProductCandles(ctx, pair, formatExchangeKlineIntervalV1(granularity), start, end) + if err != nil { + return nil, err + } + timeSeries = make([]kline.Candle, len(hist)) + for x := range hist { + timeSeries[x] = kline.Candle{ + Time: hist[x].Time, + Low: hist[x].Low, + High: hist[x].High, + Open: hist[x].Open, + Close: hist[x].Close, + Volume: hist[x].Volume, + } + } + } else { + history, err := c.GetHistoricRates(ctx, pair, formatExchangeKlineIntervalV3(granularity), start, end) + if err != nil { + return nil, err + } + timeSeries = make([]kline.Candle, len(history)) + for x := range history { + timeSeries[x] = kline.Candle{ + 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 timeSeries, nil +} diff --git a/exchanges/exchange.go b/exchanges/exchange.go index d1f5ca2c3ac..d18a262b2ae 100644 --- a/exchanges/exchange.go +++ b/exchanges/exchange.go @@ -51,7 +51,10 @@ const ( var ( // ErrExchangeNameIsEmpty is returned when the exchange name is empty ErrExchangeNameIsEmpty = errors.New("exchange name is empty") + // ErrSettingProxyAddress is returned when setting a proxy address fails ErrSettingProxyAddress = errors.New("setting proxy address error") + // ErrEndpointPathNotFound is returned when an endpoint path is not found for a particular key + ErrEndpointPathNotFound = errors.New("no endpoint path found for the given key") errEndpointStringNotFound = errors.New("endpoint string not found") errConfigPairFormatRequiresDelimiter = errors.New("config pair format requires delimiter") @@ -1350,7 +1353,7 @@ func (e *Endpoints) GetURL(key URL) (string, error) { defer e.mu.RUnlock() val, ok := e.defaults[key.String()] if !ok { - return "", fmt.Errorf("no endpoint path found for the given key: %v", key) + return "", fmt.Errorf("%w %v", ErrEndpointPathNotFound, key) } return val, nil } diff --git a/exchanges/order/order_test.go b/exchanges/order/order_test.go index 8773645115f..bf3eac251ae 100644 --- a/exchanges/order/order_test.go +++ b/exchanges/order/order_test.go @@ -885,7 +885,7 @@ func TestStringToOrderType(t *testing.T) { {"tRiGgEr", Trigger, nil}, {"conDitiOnal", ConditionalStop, nil}, {"oCo", OCO, nil}, - {"woahMan", UnknownType, errUnrecognisedOrderType}, + {"woahMan", UnknownType, ErrUnrecognisedOrderType}, } for i := range cases { testData := &cases[i] @@ -1348,8 +1348,8 @@ func TestValidationOnOrderTypes(t *testing.T) { getOrders.Side = AnySide err = getOrders.Validate() - if !errors.Is(err, errUnrecognisedOrderType) { - t.Fatalf("received: '%v' but expected: '%v'", err, errUnrecognisedOrderType) + if !errors.Is(err, ErrUnrecognisedOrderType) { + t.Fatalf("received: '%v' but expected: '%v'", err, ErrUnrecognisedOrderType) } var errTestError = errors.New("test error") diff --git a/exchanges/order/orders.go b/exchanges/order/orders.go index ba2b3ec7b6c..fc3df71e347 100644 --- a/exchanges/order/orders.go +++ b/exchanges/order/orders.go @@ -36,12 +36,14 @@ var ( ErrUnableToPlaceOrder = errors.New("order not placed") // ErrOrderNotFound is returned when no order is found ErrOrderNotFound = errors.New("order not found") - - // ErrUnknownPriceType returned when price type is unknown + // ErrUnknownPriceType is returned when price type is unknown ErrUnknownPriceType = errors.New("unknown price type") + // ErrIDNotSet is returned when an order ID is not set + ErrIDNotSet = errors.New("ID not set") + // ErrUnrecognisedOrderType is returned when an order type is not recognised + ErrUnrecognisedOrderType = errors.New("unrecognised order type") errTimeInForceConflict = errors.New("multiple time in force options applied") - errUnrecognisedOrderType = errors.New("unrecognised order type") errUnrecognisedOrderStatus = errors.New("unrecognised order status") errOrderSubmitIsNil = errors.New("order submit is nil") errOrderSubmitResponseIsNil = errors.New("order submit response is nil") @@ -1115,7 +1117,7 @@ func StringToOrderType(oType string) (Type, error) { case ConditionalStop.String(): return ConditionalStop, nil default: - return UnknownType, fmt.Errorf("'%v' %w", oType, errUnrecognisedOrderType) + return UnknownType, fmt.Errorf("'%v' %w", oType, ErrUnrecognisedOrderType) } } @@ -1182,7 +1184,7 @@ func (o *ClassificationError) Error() string { func (c *Cancel) StandardCancel() validate.Checker { return validate.Check(func() error { if c.OrderID == "" { - return errors.New("ID not set") + return ErrIDNotSet } return nil }) @@ -1235,7 +1237,7 @@ func (g *MultiOrderRequest) Validate(opt ...validate.Checker) error { } if g.Type == UnknownType { - return errUnrecognisedOrderType + return ErrUnrecognisedOrderType } var errs error diff --git a/exchanges/stream/websocket_test.go b/exchanges/stream/websocket_test.go index 5e4891f8030..34efdc14ef9 100644 --- a/exchanges/stream/websocket_test.go +++ b/exchanges/stream/websocket_test.go @@ -1154,7 +1154,7 @@ func TestEnable(t *testing.T) { } require.NoError(t, w.Enable(), "Enable must not error") - assert.ErrorIs(t, w.Enable(), errWebsocketAlreadyEnabled, "Enable should error correctly") + assert.ErrorIs(t, w.Enable(), ErrWebsocketAlreadyEnabled, "Enable should error correctly") } func TestSetupNewConnection(t *testing.T) { diff --git a/testdata/configtest.json b/testdata/configtest.json index 65cca8ba31d..d9e7153a390 100644 --- a/testdata/configtest.json +++ b/testdata/configtest.json @@ -1373,12 +1373,17 @@ }, "useGlobalFormat": true, "assetTypes": [ - "spot" + "spot", + "futures" ], "pairs": { "spot": { "enabled": "BTC-USD", "available": "LTC-GBP,XLM-BTC,DASH-BTC,DAI-USDC,ZEC-USDC,XLM-EUR,ZRX-BTC,LTC-BTC,ETC-BTC,ETH-USD,XRP-EUR,BTC-USDC,REP-USD,EOS-BTC,ZEC-BTC,ETC-GBP,LINK-ETH,XRP-BTC,ZRX-USD,ETH-USDC,MANA-USDC,BTC-EUR,BCH-GBP,DNT-USDC,EOS-EUR,BCH-EUR,LTC-EUR,CVC-USDC,ETH-GBP,DASH-USD,ETH-EUR,XTZ-BTC,ZRX-EUR,BAT-ETH,BTC-GBP,ETC-USD,BAT-USDC,BCH-USD,GNT-USDC,ALGO-USD,LINK-USD,XLM-USD,ETH-BTC,EOS-USD,REP-BTC,ETH-DAI,XRP-USD,LTC-USD,ETC-EUR,BTC-USD,XTZ-USD,BCH-BTC,LOOM-USDC" + }, + "futures": { + "enabled": "BTC-PERP-INTX", + "available": "BTC-PERP-INTX" } } }, From 6dbb243adb84c16f73499860c1f693e631b04e6a Mon Sep 17 00:00:00 2001 From: Samuel Reid <43227667+cranktakular@users.noreply.github.com> Date: Tue, 7 May 2024 10:57:27 +1000 Subject: [PATCH 43/79] A lil Coinbase progress --- .../exchange_wrapper_standards_test.go | 14 +++++++++++++- exchanges/coinbasepro/coinbasepro_test.go | 11 +++++------ exchanges/coinbasepro/coinbasepro_wrapper.go | 4 ++++ exchanges/exchange.go | 3 +-- 4 files changed, 23 insertions(+), 9 deletions(-) diff --git a/cmd/exchange_wrapper_standards/exchange_wrapper_standards_test.go b/cmd/exchange_wrapper_standards/exchange_wrapper_standards_test.go index 622a7e37547..1d5749e379d 100644 --- a/cmd/exchange_wrapper_standards/exchange_wrapper_standards_test.go +++ b/cmd/exchange_wrapper_standards/exchange_wrapper_standards_test.go @@ -606,7 +606,7 @@ var blockedCIExchanges = []string{ // blockedExchanges are exchanges that are not able to be tested in general var blockedExchanges = []string{ - "coinbasepro", // coinbasepro API requires authentication for almost every endpoint + // "coinbasepro", // coinbasepro API requires authentication for almost every endpoint } // unsupportedAssets contains assets that cannot handle @@ -717,6 +717,18 @@ func isFiat(t *testing.T, c string) bool { return false } +// func getTestableAssets(t *testing.T, exch exchange.IBotExchange) []asset.Item { +// t.Helper() +// var assets []asset.Item +// if exchange == coinbrasepro && asset.Ifutures { +// continue +// } +// for x := range exch.GetBase().CurrencyPairs.GetAssetTypes(false) { +// assets = append(assets, exch.GetBase().CurrencyPairs.GetAssetTypes(false)[x]) +// } +// return assets +// } + // disruptFormatting adds in an unused delimiter and strange casing features to // ensure format currency pair is used throughout the code base. func disruptFormatting(t *testing.T, p currency.Pair) (currency.Pair, error) { diff --git a/exchanges/coinbasepro/coinbasepro_test.go b/exchanges/coinbasepro/coinbasepro_test.go index 52a1e349592..979429209cb 100644 --- a/exchanges/coinbasepro/coinbasepro_test.go +++ b/exchanges/coinbasepro/coinbasepro_test.go @@ -98,7 +98,6 @@ func TestMain(m *testing.M) { if err != nil { log.Fatal(err) } - // c.Verbose = true os.Exit(m.Run()) } @@ -1060,11 +1059,11 @@ func TestFetchTradablePairs(t *testing.T) { func TestUpdateTradablePairs(t *testing.T) { t.Parallel() err := c.UpdateTradablePairs(context.Background(), false) - if sharedtestvalues.AreAPICredentialsSet(c) { - assert.NoError(t, err) - } else { - assert.ErrorIs(t, err, errAuthenticationNeeded) - } + // if sharedtestvalues.AreAPICredentialsSet(c) { + assert.NoError(t, err) + // } else { + // assert.ErrorIs(t, err, errAuthenticationNeeded) + // } } func TestUpdateAccountInfo(t *testing.T) { diff --git a/exchanges/coinbasepro/coinbasepro_wrapper.go b/exchanges/coinbasepro/coinbasepro_wrapper.go index 6a0283ddfb8..d501c127610 100644 --- a/exchanges/coinbasepro/coinbasepro_wrapper.go +++ b/exchanges/coinbasepro/coinbasepro_wrapper.go @@ -228,6 +228,10 @@ func (c *CoinbasePro) UpdateTradablePairs(ctx context.Context, forceUpdate bool) for i := range assets { pairs, err := c.FetchTradablePairs(ctx, assets[i]) if err != nil { + if errors.Is(err, errAuthenticationNeeded) && assets[i] == asset.Futures { + log.Warnf(log.ExchangeSys, "%v", err) + continue + } return err } err = c.UpdatePairs(pairs, assets[i], false, forceUpdate) diff --git a/exchanges/exchange.go b/exchanges/exchange.go index d18a262b2ae..fcd9aa45e3c 100644 --- a/exchanges/exchange.go +++ b/exchanges/exchange.go @@ -1971,8 +1971,7 @@ func GetDefaultConfig(ctx context.Context, exch IBotExchange) (*config.Exchange, } if b.Features.Supports.RESTCapabilities.AutoPairUpdates { - err = exch.UpdateTradablePairs(ctx, true) - if err != nil { + if err = exch.UpdateTradablePairs(ctx, true); err != nil { return nil, err } } From a5a3bad3dbef40e285e1e9a7d3d6301524c941d0 Mon Sep 17 00:00:00 2001 From: Samuel Reid <43227667+cranktakular@users.noreply.github.com> Date: Tue, 7 May 2024 14:56:05 +1000 Subject: [PATCH 44/79] Json cleaning --- testdata/configtest.json | 4614 +++++++++++++++++++------------------- 1 file changed, 2307 insertions(+), 2307 deletions(-) diff --git a/testdata/configtest.json b/testdata/configtest.json index dee4902d33d..e9c76382c45 100644 --- a/testdata/configtest.json +++ b/testdata/configtest.json @@ -1,2373 +1,2373 @@ { - "name": "Skynet", - "encryptConfig": -1, - "globalHTTPTimeout": 15000000000, - "database": { - "enabled": true, - "verbose": false, - "driver": "sqlite3", - "connectionDetails": { - "host": "", - "port": 0, - "username": "", - "password": "", - "database": "gocryptotrader.db", - "sslmode": "" - } - }, - "logging": { - "enabled": true, - "level": "INFO|DEBUG|WARN|ERROR", - "output": "console", - "fileSettings": { - "filename": "log.txt", - "rotate": false, - "maxsize": 100 - }, - "advancedSettings": { - "showLogSystemName": false, - "spacer": " | ", - "timeStampFormat": " 02/01/2006 15:04:05 ", - "headers": { - "info": "[INFO]", - "warn": "[WARN]", - "debug": "[DEBUG]", - "error": "[ERROR]" - } - } - }, - "connectionMonitor": { - "preferredDNSList": [ - "8.8.8.8", - "8.8.4.4", - "1.1.1.1", - "1.0.0.1" - ], - "preferredDomainList": [ - "www.google.com", - "www.cloudflare.com", - "www.facebook.com" - ], - "checkInterval": 1000000000 - }, - "profiler": { - "enabled": false, - "mutex_profile_fraction": 0 - }, - "ntpclient": { - "enabled": 0, - "pool": [ - "0.pool.ntp.org:123", - "pool.ntp.org:123" - ], - "allowedDifference": 50000000, - "allowedNegativeDifference": 50000000 - }, - "gctscript": { - "enabled": false, - "timeout": 30000000000, - "max_virtual_machines": 10, - "allow_imports": false, - "auto_load": null, - "verbose": false - }, - "currencyConfig": { - "forexProviders": [ - { - "name": "CurrencyConverter", - "enabled": false, - "verbose": false, - "restPollingDelay": 600, - "apiKey": "Key", - "apiKeyLvl": -1, - "primaryProvider": false - }, - { - "name": "CurrencyLayer", - "enabled": false, - "verbose": false, - "restPollingDelay": 600, - "apiKey": "Key", - "apiKeyLvl": -1, - "primaryProvider": false - }, - { - "name": "Fixer", - "enabled": false, - "verbose": false, - "restPollingDelay": 600, - "apiKey": "Key", - "apiKeyLvl": -1, - "primaryProvider": false - }, - { - "name": "OpenExchangeRates", - "enabled": false, - "verbose": false, - "restPollingDelay": 600, - "apiKey": "Key", - "apiKeyLvl": -1, - "primaryProvider": false - }, - { - "name": "ExchangeRates", + "name": "Skynet", + "encryptConfig": -1, + "globalHTTPTimeout": 15000000000, + "database": { "enabled": true, "verbose": false, - "restPollingDelay": 600, - "apiKey": "Key", - "apiKeyLvl": -1, - "primaryProvider": true - } - ], - "cryptocurrencyProvider": { - "name": "CoinMarketCap", - "enabled": false, - "verbose": false, - "apiKey": "Key", - "accountPlan": "accountPlan" - }, - "cryptocurrencies": "BTC,LTC,ETH,DOGE,DASH,XRP,XMR", - "currencyPairFormat": { - "uppercase": true, - "delimiter": "-" - }, - "fiatDisplayCurrency": "USD", - "currencyFileUpdateDuration": 0, - "foreignExchangeUpdateDuration": 0 - }, - "communications": { - "slack": { - "name": "Slack", - "enabled": false, - "verbose": false, - "targetChannel": "general", - "verificationToken": "testtest" - }, - "smsGlobal": { - "name": "SMSGlobal", - "from": "Skynet", - "enabled": true, - "verbose": false, - "username": "1234", - "password": "12334", - "contacts": [ - { - "name": "StyleGherkin", - "number": "1231424", - "enabled": true + "driver": "sqlite3", + "connectionDetails": { + "host": "", + "port": 0, + "username": "", + "password": "", + "database": "gocryptotrader.db", + "sslmode": "" } - ] }, - "smtp": { - "name": "SMTP", - "enabled": false, - "verbose": false, - "host": "smtp.google.com", - "port": "537", - "accountName": "some", - "accountPassword": "password", - "from": "", - "recipientList": "lol123@gmail.com" + "logging": { + "enabled": true, + "level": "INFO|DEBUG|WARN|ERROR", + "output": "console", + "fileSettings": { + "filename": "log.txt", + "rotate": false, + "maxsize": 100 + }, + "advancedSettings": { + "showLogSystemName": false, + "spacer": " | ", + "timeStampFormat": " 02/01/2006 15:04:05 ", + "headers": { + "info": "[INFO]", + "warn": "[WARN]", + "debug": "[DEBUG]", + "error": "[ERROR]" + } + } }, - "telegram": { - "name": "Telegram", - "enabled": false, - "verbose": false, - "verificationToken": "testest", - "authorisedClients": { - "user_example": 0 - } - } - }, - "remoteControl": { - "username": "admin", - "password": "Password", - "gRPC": { - "enabled": true, - "listenAddress": "localhost:9052", - "grpcProxyEnabled": true, - "grpcProxyListenAddress": "localhost:9053" + "connectionMonitor": { + "preferredDNSList": [ + "8.8.8.8", + "8.8.4.4", + "1.1.1.1", + "1.0.0.1" + ], + "preferredDomainList": [ + "www.google.com", + "www.cloudflare.com", + "www.facebook.com" + ], + "checkInterval": 1000000000 }, - "deprecatedRPC": { - "enabled": true, - "listenAddress": "localhost:9050" + "profiler": { + "enabled": false, + "mutex_profile_fraction": 0 }, - "websocketRPC": { - "enabled": true, - "listenAddress": "localhost:9051", - "connectionLimit": 1, - "maxAuthFailures": 3, - "allowInsecureOrigin": true - } - }, - "portfolioAddresses": { - "addresses": [ - { - "Address": "1JCe8z4jJVNXSjohjM4i9Hh813dLCNx2Sy", - "CoinType": "BTC", - "Balance": 53000.01741264, - "Description": "", - "WhiteListed": false, - "ColdStorage": false, - "SupportedExchanges": "" - }, - { - "Address": "3Nxwenay9Z8Lc9JBiywExpnEFiLp6Afp8v", - "CoinType": "BTC", - "Balance": 107848.28963408, - "Description": "", - "WhiteListed": false, - "ColdStorage": false, - "SupportedExchanges": "" - }, - { - "Address": "LgY8ahfHRhvjVQC1zJnBhFMG5pCTMuKRqh", - "CoinType": "LTC", - "Balance": 0.03665026, - "Description": "", - "WhiteListed": false, - "ColdStorage": false, - "SupportedExchanges": "" - }, - { - "Address": "0xb794f5ea0ba39494ce839613fffba74279579268", - "CoinType": "ETH", - "Balance": 0.25555604051326, - "Description": "", - "WhiteListed": false, - "ColdStorage": false, - "SupportedExchanges": "" - } - ] - }, - "exchanges": [ - { - "name": "BTC Markets", - "enabled": true, - "verbose": false, - "httpTimeout": 15000000000, - "websocketResponseCheckTimeout": 30000000, - "websocketResponseMaxLimit": 7000000000, - "websocketTrafficTimeout": 30000000000, - "websocketOrderbookBufferLimit": 5, - "baseCurrencies": "AUD", - "currencyPairs": { - "requestFormat": { - "uppercase": true, - "delimiter": "-" - }, - "configFormat": { - "uppercase": true, - "delimiter": "-" - }, - "useGlobalFormat": true, - "assetTypes": [ - "spot" + "ntpclient": { + "enabled": 0, + "pool": [ + "0.pool.ntp.org:123", + "pool.ntp.org:123" ], - "pairs": { - "spot": { - "enabled": "BTC-AUD", - "available": "BTC-AUD,LTC-AUD,LTC-BTC,ETH-BTC,ETH-AUD,ETC-AUD,ETC-BTC,XRP-AUD,XRP-BTC,POWR-AUD,POWR-BTC,OMG-AUD,OMG-BTC,BCHABC-AUD,BCHABC-BTC,BCHSV-AUD,BCHSV-BTC,GNT-AUD,GNT-BTC,BAT-AUD,BAT-BTC,XLM-AUD,XLM-BTC" - } - } - }, - "api": { - "authenticatedSupport": false, - "authenticatedWebsocketApiSupport": false, - "endpoints": { - "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" - }, - "credentials": { - "key": "Key", - "secret": "Secret" - }, - "credentialsValidator": { - "requiresKey": true, - "requiresSecret": true, - "requiresBase64DecodeSecret": true - } - }, - "features": { - "supports": { - "restAPI": true, - "restCapabilities": { - "autoPairUpdates": true - }, - "websocketAPI": true, - "websocketCapabilities": {} - }, - "enabled": { - "autoPairUpdates": true, - "websocketAPI": false - } - }, - "bankAccounts": [ - { - "enabled": false, - "bankName": "", - "bankAddress": "", - "bankPostalCode": "", - "bankPostalCity": "", - "bankCountry": "", - "accountName": "", - "accountNumber": "", - "swiftCode": "", - "iban": "", - "supportedCurrencies": "" - } - ] + "allowedDifference": 50000000, + "allowedNegativeDifference": 50000000 }, - { - "name": "BTSE", - "enabled": true, - "verbose": false, - "httpTimeout": 15000000000, - "websocketResponseCheckTimeout": 30000000, - "websocketResponseMaxLimit": 7000000000, - "websocketTrafficTimeout": 30000000000, - "websocketOrderbookBufferLimit": 5, - "baseCurrencies": "USD", - "currencyPairs": { - "requestFormat": { - "uppercase": true, - "delimiter": "-" - }, - "configFormat": { - "uppercase": true, - "delimiter": "-" - }, - "useGlobalFormat": true, - "assetTypes": [ - "spot" - ], - "pairs": { - "spot": { - "enabled": "BTC-USD", - "available": "BTC-CNY,BTC-EUR,BTC-GBP,BTC-HKD,BTC-JPY,BTC-SGD,BTC-USD,ETH-CNY,ETH-EUR,ETH-GBP,ETH-HKD,ETH-JPY,ETH-SGD,ETH-USD,LTC-CNY,LTC-EUR,LTC-GBP,LTC-HKD,LTC-JPY,LTC-SGD,LTC-USD,USDT-CNY,USDT-EUR,USDT-GBP,USDT-HKD,USDT-JPY,USDT-SGD,USDT-USD,XMR-CNY,XMR-EUR,XMR-GBP,XMR-HKD,XMR-JPY,XMR-SGD,XMR-USD" - } - } - }, - "api": { - "authenticatedSupport": false, - "authenticatedWebsocketApiSupport": false, - "endpoints": { - "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" - }, - "credentials": { - "key": "Key", - "secret": "Secret" - }, - "credentialsValidator": { - "requiresKey": true, - "requiresSecret": true - } - }, - "features": { - "supports": { - "restAPI": true, - "restCapabilities": { - "autoPairUpdates": true - }, - "websocketAPI": true, - "websocketCapabilities": {} - }, - "enabled": { - "autoPairUpdates": true, - "websocketAPI": true - } - }, - "bankAccounts": [ - { - "enabled": false, - "bankName": "", - "bankAddress": "", - "bankPostalCode": "", - "bankPostalCity": "", - "bankCountry": "", - "accountName": "", - "accountNumber": "", - "swiftCode": "", - "iban": "", - "supportedCurrencies": "" - } - ] + "gctscript": { + "enabled": false, + "timeout": 30000000000, + "max_virtual_machines": 10, + "allow_imports": false, + "auto_load": null, + "verbose": false }, - { - "name": "Binance", - "enabled": true, - "verbose": false, - "httpTimeout": 15000000000, - "websocketResponseCheckTimeout": 30000000, - "websocketResponseMaxLimit": 7000000000, - "websocketTrafficTimeout": 30000000000, - "websocketOrderbookBufferLimit": 5, - "baseCurrencies": "USD", - "currencyPairs": { - "requestFormat": { - "uppercase": true - }, - "configFormat": { - "uppercase": true, - "delimiter": "-" - }, - "useGlobalFormat": true, - "assetTypes": [ - "spot" + "currencyConfig": { + "forexProviders": [ + { + "name": "CurrencyConverter", + "enabled": false, + "verbose": false, + "restPollingDelay": 600, + "apiKey": "Key", + "apiKeyLvl": -1, + "primaryProvider": false + }, + { + "name": "CurrencyLayer", + "enabled": false, + "verbose": false, + "restPollingDelay": 600, + "apiKey": "Key", + "apiKeyLvl": -1, + "primaryProvider": false + }, + { + "name": "Fixer", + "enabled": false, + "verbose": false, + "restPollingDelay": 600, + "apiKey": "Key", + "apiKeyLvl": -1, + "primaryProvider": false + }, + { + "name": "OpenExchangeRates", + "enabled": false, + "verbose": false, + "restPollingDelay": 600, + "apiKey": "Key", + "apiKeyLvl": -1, + "primaryProvider": false + }, + { + "name": "ExchangeRates", + "enabled": true, + "verbose": false, + "restPollingDelay": 600, + "apiKey": "Key", + "apiKeyLvl": -1, + "primaryProvider": true + } ], - "pairs": { - "spot": { - "enabled": "BTC-USDT,DOGE-USDT", - "available": "ETH-BTC,LTC-BTC,BNB-BTC,NEO-BTC,QTUM-ETH,EOS-ETH,SNT-ETH,BNT-ETH,GAS-BTC,BNB-ETH,BTC-USDT,ETH-USDT,OAX-ETH,DNT-ETH,MCO-ETH,MCO-BTC,WTC-BTC,WTC-ETH,LRC-BTC,LRC-ETH,QTUM-BTC,YOYO-BTC,OMG-BTC,OMG-ETH,ZRX-BTC,ZRX-ETH,STRAT-BTC,STRAT-ETH,SNGLS-BTC,BQX-BTC,BQX-ETH,KNC-BTC,KNC-ETH,FUN-BTC,FUN-ETH,SNM-BTC,SNM-ETH,NEO-ETH,IOTA-BTC,IOTA-ETH,LINK-BTC,LINK-ETH,XVG-BTC,XVG-ETH,MDA-BTC,MDA-ETH,MTL-BTC,MTL-ETH,EOS-BTC,SNT-BTC,ETC-ETH,ETC-BTC,MTH-BTC,MTH-ETH,ENG-BTC,ENG-ETH,DNT-BTC,ZEC-BTC,ZEC-ETH,BNT-BTC,AST-BTC,AST-ETH,DASH-BTC,DASH-ETH,OAX-BTC,BTG-BTC,BTG-ETH,EVX-BTC,EVX-ETH,REQ-BTC,REQ-ETH,VIB-BTC,VIB-ETH,TRX-BTC,TRX-ETH,POWR-BTC,POWR-ETH,ARK-BTC,ARK-ETH,YOYO-ETH,XRP-BTC,XRP-ETH,ENJ-BTC,ENJ-ETH,STORJ-BTC,STORJ-ETH,BNB-USDT,YOYO-BNB,POWR-BNB,KMD-BTC,KMD-ETH,NULS-BNB,RCN-BTC,RCN-ETH,RCN-BNB,NULS-BTC,NULS-ETH,RDN-BTC,RDN-ETH,RDN-BNB,XMR-BTC,XMR-ETH,DLT-BNB,WTC-BNB,DLT-BTC,DLT-ETH,AMB-BTC,AMB-ETH,AMB-BNB,BAT-BTC,BAT-ETH,BAT-BNB,BCPT-BTC,BCPT-ETH,BCPT-BNB,ARN-BTC,ARN-ETH,GVT-BTC,GVT-ETH,CDT-BTC,CDT-ETH,GXS-BTC,GXS-ETH,NEO-USDT,NEO-BNB,POE-BTC,POE-ETH,QSP-BTC,QSP-ETH,QSP-BNB,BTS-BTC,BTS-ETH,XZC-BTC,XZC-ETH,XZC-BNB,LSK-BTC,LSK-ETH,LSK-BNB,TNT-BTC,TNT-ETH,FUEL-BTC,MANA-BTC,MANA-ETH,BCD-BTC,BCD-ETH,DGD-BTC,DGD-ETH,IOTA-BNB,ADX-BTC,ADX-ETH,ADA-BTC,ADA-ETH,PPT-BTC,PPT-ETH,CMT-BTC,CMT-ETH,CMT-BNB,XLM-BTC,XLM-ETH,XLM-BNB,CND-BTC,CND-ETH,CND-BNB,LEND-BTC,LEND-ETH,WABI-BTC,WABI-ETH,WABI-BNB,LTC-ETH,LTC-USDT,LTC-BNB,TNB-BTC,TNB-ETH,WAVES-BTC,WAVES-ETH,WAVES-BNB,GTO-BTC,GTO-ETH,GTO-BNB,ICX-BTC,ICX-ETH,ICX-BNB,OST-BTC,OST-ETH,OST-BNB,ELF-BTC,ELF-ETH,AION-BTC,AION-ETH,AION-BNB,NEBL-BTC,NEBL-ETH,NEBL-BNB,BRD-BTC,BRD-ETH,BRD-BNB,MCO-BNB,EDO-BTC,EDO-ETH,NAV-BTC,LUN-BTC,APPC-BTC,APPC-ETH,APPC-BNB,VIBE-BTC,VIBE-ETH,RLC-BTC,RLC-ETH,RLC-BNB,INS-BTC,INS-ETH,PIVX-BTC,PIVX-ETH,PIVX-BNB,IOST-BTC,IOST-ETH,STEEM-BTC,STEEM-ETH,STEEM-BNB,NANO-BTC,NANO-ETH,NANO-BNB,VIA-BTC,VIA-ETH,VIA-BNB,BLZ-BTC,BLZ-ETH,BLZ-BNB,AE-BTC,AE-ETH,AE-BNB,NCASH-BTC,NCASH-ETH,POA-BTC,POA-ETH,ZIL-BTC,ZIL-ETH,ZIL-BNB,ONT-BTC,ONT-ETH,ONT-BNB,STORM-BTC,STORM-ETH,STORM-BNB,QTUM-BNB,QTUM-USDT,XEM-BTC,XEM-ETH,XEM-BNB,WAN-BTC,WAN-ETH,WAN-BNB,WPR-BTC,WPR-ETH,QLC-BTC,QLC-ETH,SYS-BTC,SYS-ETH,SYS-BNB,QLC-BNB,GRS-BTC,GRS-ETH,ADA-USDT,ADA-BNB,GNT-BTC,GNT-ETH,LOOM-BTC,LOOM-ETH,LOOM-BNB,XRP-USDT,REP-BTC,REP-ETH,BTC-TUSD,ETH-TUSD,ZEN-BTC,ZEN-ETH,ZEN-BNB,SKY-BTC,SKY-ETH,SKY-BNB,EOS-USDT,EOS-BNB,CVC-BTC,CVC-ETH,THETA-BTC,THETA-ETH,THETA-BNB,XRP-BNB,TUSD-USDT,IOTA-USDT,XLM-USDT,IOTX-BTC,IOTX-ETH,QKC-BTC,QKC-ETH,AGI-BTC,AGI-ETH,AGI-BNB,NXS-BTC,NXS-ETH,NXS-BNB,ENJ-BNB,DATA-BTC,DATA-ETH,ONT-USDT,TRX-BNB,TRX-USDT,ETC-USDT,ETC-BNB,ICX-USDT,SC-BTC,SC-ETH,SC-BNB,NPXS-ETH,KEY-BTC,KEY-ETH,NAS-BTC,NAS-ETH,NAS-BNB,MFT-BTC,MFT-ETH,MFT-BNB,DENT-ETH,ARDR-BTC,ARDR-ETH,NULS-USDT,HOT-BTC,HOT-ETH,VET-BTC,VET-ETH,VET-USDT,VET-BNB,DOCK-BTC,DOCK-ETH,POLY-BTC,POLY-BNB,HC-BTC,HC-ETH,GO-BTC,GO-BNB,PAX-USDT,RVN-BTC,RVN-BNB,DCR-BTC,DCR-BNB,MITH-BTC,MITH-BNB,BNB-PAX,BTC-PAX,ETH-PAX,XRP-PAX,EOS-PAX,XLM-PAX,REN-BTC,REN-BNB,BNB-TUSD,XRP-TUSD,EOS-TUSD,XLM-TUSD,BNB-USDC,BTC-USDC,ETH-USDC,XRP-USDC,EOS-USDC,XLM-USDC,USDC-USDT,ADA-TUSD,TRX-TUSD,NEO-TUSD,TRX-XRP,XZC-XRP,PAX-TUSD,USDC-TUSD,USDC-PAX,LINK-USDT,LINK-TUSD,LINK-PAX,LINK-USDC,WAVES-USDT,WAVES-TUSD,WAVES-USDC,LTC-TUSD,LTC-PAX,LTC-USDC,TRX-PAX,TRX-USDC,BTT-BNB,BTT-USDT,BNB-USDS,BTC-USDS,USDS-USDT,USDS-PAX,USDS-TUSD,USDS-USDC,BTT-PAX,BTT-TUSD,BTT-USDC,ONG-BNB,ONG-BTC,ONG-USDT,HOT-BNB,HOT-USDT,ZIL-USDT,ZRX-BNB,ZRX-USDT,FET-BNB,FET-BTC,FET-USDT,BAT-USDT,XMR-BNB,XMR-USDT,ZEC-BNB,ZEC-USDT,ZEC-PAX,ZEC-TUSD,ZEC-USDC,IOST-BNB,IOST-USDT,CELR-BNB,CELR-BTC,CELR-USDT,ADA-PAX,ADA-USDC,NEO-PAX,NEO-USDC,DASH-BNB,DASH-USDT,NANO-USDT,OMG-BNB,OMG-USDT,THETA-USDT,ENJ-USDT,MITH-USDT,MATIC-BNB,MATIC-BTC,MATIC-USDT,ATOM-BNB,ATOM-BTC,ATOM-USDT,ATOM-USDC,ATOM-TUSD,ETC-TUSD,BAT-USDC,BAT-PAX,BAT-TUSD,PHB-BNB,PHB-BTC,PHB-TUSD,TFUEL-BNB,TFUEL-BTC,TFUEL-USDT,ONE-BNB,ONE-BTC,ONE-USDT,ONE-USDC,FTM-BNB,FTM-BTC,FTM-USDT,FTM-USDC,ALGO-BNB,ALGO-BTC,ALGO-USDT,ALGO-TUSD,ALGO-PAX,ALGO-USDC,GTO-USDT,ERD-BNB,ERD-BTC,ERD-USDT,DOGE-BNB,DOGE-BTC,DOGE-USDT,DUSK-BNB,DUSK-BTC,DUSK-USDT,DUSK-USDC,DUSK-PAX,BGBP-USDC,ANKR-BNB,ANKR-BTC,ANKR-USDT,ONT-PAX,ONT-USDC,WIN-BNB,WIN-USDT,WIN-USDC,COS-BNB,COS-BTC,COS-USDT,NPXS-USDT,COCOS-BNB,COCOS-BTC,COCOS-USDT,MTL-USDT,TOMO-BNB,TOMO-BTC,TOMO-USDT,TOMO-USDC,PERL-BNB,PERL-BTC,PERL-USDT,DENT-USDT,MFT-USDT,KEY-USDT,STORM-USDT,DOCK-USDT,WAN-USDT,FUN-USDT,CVC-USDT,BTT-TRX,WIN-TRX,CHZ-BNB,CHZ-BTC,CHZ-USDT,BAND-BNB,BAND-BTC,BAND-USDT,BNB-BUSD,BTC-BUSD,BUSD-USDT,BEAM-BNB,BEAM-BTC,BEAM-USDT,XTZ-BNB,XTZ-BTC,XTZ-USDT,REN-USDT,RVN-USDT,HC-USDT,HBAR-BNB,HBAR-BTC,HBAR-USDT,NKN-BNB,NKN-BTC,NKN-USDT,XRP-BUSD,ETH-BUSD,LTC-BUSD,LINK-BUSD,ETC-BUSD,STX-BNB,STX-BTC,STX-USDT,KAVA-BNB,KAVA-BTC,KAVA-USDT,BUSD-NGN,BNB-NGN,BTC-NGN,ARPA-BNB,ARPA-BTC,ARPA-USDT,TRX-BUSD,EOS-BUSD,IOTX-USDT,RLC-USDT,MCO-USDT,XLM-BUSD,ADA-BUSD,CTXC-BNB,CTXC-BTC,CTXC-USDT,BCH-BNB,BCH-BTC,BCH-USDT,BCH-USDC,BCH-TUSD,BCH-PAX,BCH-BUSD,BTC-RUB,ETH-RUB,XRP-RUB,BNB-RUB,TROY-BNB,TROY-BTC,TROY-USDT,BUSD-RUB,QTUM-BUSD,VET-BUSD" - } - } - }, - "api": { - "authenticatedSupport": false, - "authenticatedWebsocketApiSupport": false, - "endpoints": { - "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" - }, - "credentials": { - "key": "Key", - "secret": "Secret" - }, - "credentialsValidator": { - "requiresKey": true, - "requiresSecret": true - } - }, - "features": { - "supports": { - "restAPI": true, - "restCapabilities": { - "tickerBatching": true, - "autoPairUpdates": true - }, - "websocketAPI": true, - "websocketCapabilities": {} - }, - "enabled": { - "autoPairUpdates": true, - "websocketAPI": true - } - }, - "bankAccounts": [ - { - "enabled": false, - "bankName": "", - "bankAddress": "", - "bankPostalCode": "", - "bankPostalCity": "", - "bankCountry": "", - "accountName": "", - "accountNumber": "", - "swiftCode": "", - "iban": "", - "supportedCurrencies": "" - } - ] + "cryptocurrencyProvider": { + "name": "CoinMarketCap", + "enabled": false, + "verbose": false, + "apiKey": "Key", + "accountPlan": "accountPlan" + }, + "cryptocurrencies": "BTC,LTC,ETH,DOGE,DASH,XRP,XMR", + "currencyPairFormat": { + "uppercase": true, + "delimiter": "-" + }, + "fiatDisplayCurrency": "USD", + "currencyFileUpdateDuration": 0, + "foreignExchangeUpdateDuration": 0 }, - { - "name": "Binanceus", - "enabled": true, - "verbose": false, - "httpTimeout": 15000000000, - "websocketResponseCheckTimeout": 30000000, - "websocketResponseMaxLimit": 7000000000, - "websocketTrafficTimeout": 30000000000, - "baseCurrencies": "USD", - "currencyPairs": { - "bypassConfigFormatUpgrades": false, - "pairs": { - "spot": { - "assetEnabled": true, - "enabled": "BTC-USDT,ETH-USDT,LTC-USDT,ADA-USDT", - "available": "BTC-USD,BCH-USD,LTC-USD,USDT-USD,BTC-USDT,ETH-USDT,BCH-USDT,LTC-USDT,BNB-USD,BNB-USDT,ETH-BTC,BNB-BTC,LTC-BTC,BCH-BTC,ADA-USD,BAT-USD,ETC-USD,XLM-USD,ZRX-USD,ADA-USDT,BAT-USDT,ETC-USDT,XLM-USDT,ZRX-USDT,LINK-USD,RVN-USD,DASH-USD,ZEC-USD,ALGO-USD,IOTA-USD,BUSD-USD,BTCB-USD,DOGE-USDT,WAVES-USD,ATOM-USDT,ATOM-USD,NEO-USDT,NEO-USD,VET-USDT,QTUM-USDT,QTUM-USD,ICX-USD,ENJ-USD,ONT-USD,ONT-USDT,ZIL-USD,ZILB-USD,VET-USD,BNBB-USD,ETHB-USD,ALGO-BUSD,XTZ-USD,XTZ-BUSD,HBAR-USD,HBAR-BUSD,OMG-USD,OMG-BUSD,MATIC-USD,MATIC-BUSD,XTZ-BTC,ADA-BTC,REP-BUSD,REP-USD,EOS-BUSD,EOS-USD,DOGE-USD,KNC-USD,KNC-USDT,VTHO-USDT,VTHO-USD,USDC-USD,COMP-USDT,COMP-USD,MANA-USD,HNT-USD,HNT-USDT,MKR-USD,MKR-USDT,DAI-USD,ONE-USDT,ONE-USD,BAND-USDT,BAND-USD,STORJ-USDT,STORJ-USD,UNI-USD,UNI-USDT,SOL-USD,SOL-USDT,LINK-BTC,VET-BTC,UNI-BTC,EGLD-USDT,EGLD-USD,PAXG-USDT,PAXG-USD,OXT-USDT,OXT-USD,ZEN-USDT,ZEN-USD,BTC-USDC,ONEB-USD,FIL-USDT,FIL-USD,AAVE-USDT,AAVE-USD,GRT-USDT,GRT-USD,SUSHI-USD,ANKR-USD,AMP-USD,SHIB-USDT,SHIB-BUSD,CRV-USDT,CRV-USD,AXS-USDT,AXS-USD,SOL-BTC,AVAX-USDT,AVAX-USD,CTSI-USDT,CTSI-USD,DOT-USDT,DOT-USD,YFI-USDT,YFI-USD,1INCH-USDT,1INCH-USD,FTM-USDT,FTM-USD,USDC-USDT,ETH-USDC,USDC-BUSD,MATIC-USDT,MANA-USDT,MANA-BUSD,ALGO-USDT,ADA-BUSD,SOL-BUSD,EOS-USDT,ENJ-USDT,NEAR-USDT,NEAR-BUSD,NEAR-USD,OMG-USDT,SUSHI-USDT,LRC-USDT,LRC-USD,LRC-BTC,KSHI-BUSD,LPT-USDT,LPT-BUSD,LPT-USD,POLY-USDT,POLY-BUSD,POLY-USD,POLY-BTC,MATIC-BTC,DOT-BTC,NMR-USDT,NMR-USD,SLP-USDT,ANT-USD,XNO-USD,CHZ-USDT,CHZ-USD,OGN-USDT,OGN-USD,GALA-USDT,GALA-USD,TLM-USDT,TLM-USD,SNX-USDT,SNX-USD,AUDIO-USDT,AUDIO-USD,ENS-USDT,MANA-BTC,ATOM-BTC,AVAX-BTC,WBTC-BTC,REQ-USDT,REQ-USD,APE-USDT,APE-USD,FLUX-USDT,FLUX-USD,TRX-BTC,TRX-BUSD,TRX-USDT,TRX-USD,COTI-USDT,COTI-USD,VOXEL-USDT,VOXEL-USD,RLC-USDT,RLC-USD,UST-USDT,UST-USD,BICO-USDT,BICO-USD,API3-USDT,API3-USD,ENS-USD,BTC-UST,BNT-USDT,BNT-USD,IMX-USDT,IMX-USD,SPELL-USDT,SPELL-USD,JASMY-USDT,JASMY-USD,FLOW-USDT,FLOW-USD,GTC-USDT,GTC-USD,BTC-BUSD,ZIL-BUSD,BNB-BUSD,ETH-BUSD,BUSD-USDT,ONE-BUSD,LINK-USDT,ZEC-USDT,SLP-USD,ANT-USDT", - "requestFormat": { - "uppercase": true - }, - "configFormat": { - "uppercase": true, - "delimiter": "-" + "communications": { + "slack": { + "name": "Slack", + "enabled": false, + "verbose": false, + "targetChannel": "general", + "verificationToken": "testtest" + }, + "smsGlobal": { + "name": "SMSGlobal", + "from": "Skynet", + "enabled": true, + "verbose": false, + "username": "1234", + "password": "12334", + "contacts": [ + { + "name": "StyleGherkin", + "number": "1231424", + "enabled": true + } + ] + }, + "smtp": { + "name": "SMTP", + "enabled": false, + "verbose": false, + "host": "smtp.google.com", + "port": "537", + "accountName": "some", + "accountPassword": "password", + "from": "", + "recipientList": "lol123@gmail.com" + }, + "telegram": { + "name": "Telegram", + "enabled": false, + "verbose": false, + "verificationToken": "testest", + "authorisedClients": { + "user_example": 0 } - } - } - }, - "api": { - "authenticatedSupport": false, - "authenticatedWebsocketApiSupport": false, - "credentials": { - "key": "", - "secret": "" - }, - "credentialsValidator": { - "requiresKey": true, - "requiresSecret": true - }, - "urlEndpoints": { - "RestSpotSupplementaryURL": "https://api.binance.us", - "RestSpotURL": "https://api.binance.us", - "WebsocketSpotSupplementaryURL": "wss://stream.binance.us:9443/stream", - "WebsocketSpotURL": "wss://stream.binance.us:9443/stream" - } - }, - "features": { - "supports": { - "restAPI": true, - "restCapabilities": { - "tickerBatching": true, - "autoPairUpdates": true - }, - "websocketAPI": true, - "websocketCapabilities": {} - }, - "enabled": { - "autoPairUpdates": true, - "websocketAPI": true, - "saveTradeData": false, - "tradeFeed": false, - "fillsFeed": false - } - }, - "bankAccounts": [ - { - "enabled": false, - "bankName": "", - "bankAddress": "", - "bankPostalCode": "", - "bankPostalCity": "", - "bankCountry": "", - "accountName": "", - "accountNumber": "", - "swiftCode": "", - "iban": "", - "supportedCurrencies": "" } - ], - "orderbook": { - "verificationBypass": false, - "websocketBufferLimit": 5, - "websocketBufferEnabled": false, - "publishPeriod": 10000000000 - } }, - { - "name": "Bitfinex", - "enabled": true, - "verbose": false, - "httpTimeout": 15000000000, - "websocketResponseCheckTimeout": 30000000, - "websocketResponseMaxLimit": 7000000000, - "websocketTrafficTimeout": 30000000000, - "websocketOrderbookBufferLimit": 5, - "baseCurrencies": "USD", - "currencyPairs": { - "requestFormat": { - "uppercase": true - }, - "configFormat": { - "uppercase": true - }, - "useGlobalFormat": true, - "assetTypes": [ - "spot" - ], - "pairs": { - "spot": { - "enabled": "BTCUSD,LTCUSD,LTCBTC,ETHUSD,ETHBTC", - "available": "BTCUSD,LTCUSD,LTCBTC,ETHUSD,ETHBTC,ETCBTC,ETCUSD,RRTUSD,RRTBTC,ZECUSD,ZECBTC,XMRUSD,XMRBTC,DSHUSD,DSHBTC,BTCEUR,BTCJPY,XRPUSD,XRPBTC,IOTUSD,IOTBTC,IOTETH,EOSUSD,EOSBTC,EOSETH,SANUSD,SANBTC,SANETH,OMGUSD,OMGBTC,OMGETH,NEOUSD,NEOBTC,NEOETH,ETPUSD,ETPBTC,ETPETH,QTMUSD,QTMBTC,QTMETH,AVTUSD,AVTBTC,AVTETH,EDOUSD,EDOBTC,EDOETH,BTGUSD,BTGBTC,DATUSD,DATBTC,DATETH,QSHUSD,QSHBTC,QSHETH,YYWUSD,YYWBTC,YYWETH,GNTUSD,GNTBTC,GNTETH,SNTUSD,SNTBTC,SNTETH,IOTEUR,BATUSD,BATBTC,BATETH,MNAUSD,MNABTC,MNAETH,FUNUSD,FUNBTC,FUNETH,ZRXUSD,ZRXBTC,ZRXETH,TNBUSD,TNBBTC,TNBETH,SPKUSD,SPKBTC,SPKETH,TRXUSD,TRXBTC,TRXETH,RCNUSD,RCNBTC,RCNETH,RLCUSD,RLCBTC,RLCETH,AIDUSD,AIDBTC,AIDETH,SNGUSD,SNGBTC,SNGETH,REPUSD,REPBTC,REPETH,ELFUSD,ELFBTC,ELFETH,NECUSD,NECBTC,NECETH,BTCGBP,ETHEUR,ETHJPY,ETHGBP,NEOEUR,NEOJPY,NEOGBP,EOSEUR,EOSJPY,EOSGBP,IOTJPY,IOTGBP,IOSUSD,IOSBTC,IOSETH,AIOUSD,AIOBTC,AIOETH,REQUSD,REQBTC,REQETH,RDNUSD,RDNBTC,RDNETH,LRCUSD,LRCBTC,LRCETH,WAXUSD,WAXBTC,WAXETH,DAIUSD,DAIBTC,DAIETH,AGIUSD,AGIBTC,AGIETH,BFTUSD,BFTBTC,BFTETH,MTNUSD,MTNBTC,MTNETH,ODEUSD,ODEBTC,ODEETH,ANTUSD,ANTBTC,ANTETH,DTHUSD,DTHBTC,DTHETH,MITUSD,MITBTC,MITETH,STJUSD,STJBTC,STJETH,XLMUSD,XLMEUR,XLMJPY,XLMGBP,XLMBTC,XLMETH,XVGUSD,XVGEUR,XVGJPY,XVGGBP,XVGBTC,XVGETH,BCIUSD,BCIBTC,MKRUSD,MKRBTC,MKRETH,KNCUSD,KNCBTC,KNCETH,POAUSD,POABTC,POAETH,EVTUSD,LYMUSD,LYMBTC,LYMETH,UTKUSD,UTKBTC,UTKETH,VEEUSD,VEEBTC,VEEETH,DADUSD,DADBTC,DADETH,ORSUSD,ORSBTC,ORSETH,AUCUSD,AUCBTC,AUCETH,POYUSD,POYBTC,POYETH,FSNUSD,FSNBTC,FSNETH,CBTUSD,CBTBTC,CBTETH,ZCNUSD,ZCNBTC,ZCNETH,SENUSD,SENBTC,SENETH,NCAUSD,NCABTC,NCAETH,CNDUSD,CNDBTC,CNDETH,CTXUSD,CTXBTC,CTXETH,PAIUSD,PAIBTC,SEEUSD,SEEBTC,SEEETH,ESSUSD,ESSBTC,ESSETH,ATMUSD,ATMBTC,ATMETH,HOTUSD,HOTBTC,HOTETH,DTAUSD,DTABTC,DTAETH,IQXUSD,IQXBTC,IQXEOS,WPRUSD,WPRBTC,WPRETH,ZILUSD,ZILBTC,ZILETH,BNTUSD,BNTBTC,BNTETH,ABSUSD,ABSETH,XRAUSD,XRAETH,MANUSD,MANETH,BBNUSD,BBNETH,NIOUSD,NIOETH,DGXUSD,DGXETH,VETUSD,VETBTC,VETETH,UTNUSD,UTNETH,TKNUSD,TKNETH,GOTUSD,GOTEUR,GOTETH,XTZUSD,XTZBTC,CNNUSD,CNNETH,BOXUSD,BOXETH,TRXEUR,TRXGBP,TRXJPY,MGOUSD,MGOETH,RTEUSD,RTEETH,YGGUSD,YGGETH,MLNUSD,MLNETH,WTCUSD,WTCETH,CSXUSD,CSXETH,OMNUSD,OMNBTC,INTUSD,INTETH,DRNUSD,DRNETH,PNKUSD,PNKETH,DGBUSD,DGBBTC,BSVUSD,BSVBTC,BABUSD,BABBTC,WLOUSD,WLOXLM,VLDUSD,VLDETH,ENJUSD,ENJETH,ONLUSD,ONLETH,RBTUSD,RBTBTC,USTUSD,EUTEUR,EUTUSD,GSDUSD,UDCUSD,TSDUSD,PAXUSD,RIFUSD,RIFBTC,PASUSD,PASETH,VSYUSD,VSYBTC,ZRXDAI,MKRDAI,OMGDAI,BTTUSD,BTTBTC,BTCUST,ETHUST,CLOUSD,CLOBTC,IMPUSD,IMPETH,LTCUST,EOSUST,BABUST,SCRUSD,SCRETH,GNOUSD,GNOETH,GENUSD,GENETH,ATOUSD,ATOBTC,ATOETH,WBTUSD,XCHUSD,EUSUSD,WBTETH,XCHETH,EUSETH,LEOUSD,LEOBTC,LEOUST,LEOEOS,LEOETH,ASTUSD,ASTETH,FOAUSD,FOAETH,UFRUSD,UFRETH,ZBTUSD,ZBTUST,OKBUSD,USKUSD,GTXUSD,KANUSD,OKBUST,OKBETH,OKBBTC,USKUST,USKETH,USKBTC,USKEOS,GTXUST,KANUST,AMPUSD,ALGUSD,ALGBTC,ALGUST,BTCXCH,SWMUSD,SWMETH,TRIUSD,TRIETH,LOOUSD,LOOETH,AMPUST,DUSK:USD,DUSK:BTC,UOSUSD,UOSBTC,RRBUSD,RRBUST,DTXUSD,DTXUST,AMPBTC,FTTUSD,FTTUST,PAXUST,UDCUST,TSDUST,BTC:CNHT,UST:CNHT,CNH:CNHT,CHZUSD,CHZUST,BTCF0:USTF0,ETHF0:USTF0" - } + "remoteControl": { + "username": "admin", + "password": "Password", + "gRPC": { + "enabled": true, + "listenAddress": "localhost:9052", + "grpcProxyEnabled": true, + "grpcProxyListenAddress": "localhost:9053" + }, + "deprecatedRPC": { + "enabled": true, + "listenAddress": "localhost:9050" + }, + "websocketRPC": { + "enabled": true, + "listenAddress": "localhost:9051", + "connectionLimit": 1, + "maxAuthFailures": 3, + "allowInsecureOrigin": true } - }, - "api": { - "authenticatedSupport": true, - "authenticatedWebsocketApiSupport": true, - "endpoints": { - "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" - }, - "credentials": { - "key": "Key", - "secret": "Secret" - }, - "credentialsValidator": { - "requiresKey": true, - "requiresSecret": true - } - }, - "features": { - "supports": { - "restAPI": true, - "restCapabilities": { - "tickerBatching": true, - "autoPairUpdates": true - }, - "websocketAPI": true, - "websocketCapabilities": {} - }, - "enabled": { - "autoPairUpdates": true, - "websocketAPI": true - } - }, - "bankAccounts": [ + }, + "portfolioAddresses": { + "addresses": [ + { + "Address": "1JCe8z4jJVNXSjohjM4i9Hh813dLCNx2Sy", + "CoinType": "BTC", + "Balance": 53000.01741264, + "Description": "", + "WhiteListed": false, + "ColdStorage": false, + "SupportedExchanges": "" + }, + { + "Address": "3Nxwenay9Z8Lc9JBiywExpnEFiLp6Afp8v", + "CoinType": "BTC", + "Balance": 107848.28963408, + "Description": "", + "WhiteListed": false, + "ColdStorage": false, + "SupportedExchanges": "" + }, + { + "Address": "LgY8ahfHRhvjVQC1zJnBhFMG5pCTMuKRqh", + "CoinType": "LTC", + "Balance": 0.03665026, + "Description": "", + "WhiteListed": false, + "ColdStorage": false, + "SupportedExchanges": "" + }, + { + "Address": "0xb794f5ea0ba39494ce839613fffba74279579268", + "CoinType": "ETH", + "Balance": 0.25555604051326, + "Description": "", + "WhiteListed": false, + "ColdStorage": false, + "SupportedExchanges": "" + } + ] + }, + "exchanges": [ { - "enabled": false, - "bankName": "Deutsche Bank Privat Und Geschaeftskunden AG", - "bankAddress": "Karlsruhe, 76125, GERMANY", - "bankPostalCode": "", - "bankPostalCity": "", - "bankCountry": "", - "accountName": "GLOBAL TRADE SOLUTIONS GmbH", - "accountNumber": "DE51660700240057016802", - "swiftCode": "DEUTDEDB660", - "iban": "DE51660700240057016802", - "supportedCurrencies": "EUR,USD" + "name": "BTC Markets", + "enabled": true, + "verbose": false, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, + "websocketOrderbookBufferLimit": 5, + "baseCurrencies": "AUD", + "currencyPairs": { + "requestFormat": { + "uppercase": true, + "delimiter": "-" + }, + "configFormat": { + "uppercase": true, + "delimiter": "-" + }, + "useGlobalFormat": true, + "assetTypes": [ + "spot" + ], + "pairs": { + "spot": { + "enabled": "BTC-AUD", + "available": "BTC-AUD,LTC-AUD,LTC-BTC,ETH-BTC,ETH-AUD,ETC-AUD,ETC-BTC,XRP-AUD,XRP-BTC,POWR-AUD,POWR-BTC,OMG-AUD,OMG-BTC,BCHABC-AUD,BCHABC-BTC,BCHSV-AUD,BCHSV-BTC,GNT-AUD,GNT-BTC,BAT-AUD,BAT-BTC,XLM-AUD,XLM-BTC" + } + } + }, + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true, + "requiresBase64DecodeSecret": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "autoPairUpdates": true + }, + "websocketAPI": true, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": false + } + }, + "bankAccounts": [ + { + "enabled": false, + "bankName": "", + "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ] }, { - "enabled": false, - "bankName": "Deutsche Bank Privat Und Geschaeftskunden AG", - "bankAddress": "Karlsruhe, 76125, GERMANY", - "bankPostalCode": "", - "bankPostalCity": "", - "bankCountry": "", - "accountName": "GLOBAL TRADE SOLUTIONS GmbH", - "accountNumber": "DE78660700240057016801", - "swiftCode": "DEUTDEDB660", - "iban": "DE78660700240057016801", - "supportedCurrencies": "JPY,GBP" - } - ] - }, - { - "name": "Bitflyer", - "enabled": true, - "verbose": false, - "httpTimeout": 15000000000, - "websocketResponseCheckTimeout": 30000000, - "websocketResponseMaxLimit": 7000000000, - "websocketTrafficTimeout": 30000000000, - "websocketOrderbookBufferLimit": 5, - "baseCurrencies": "JPY", - "currencyPairs": { - "requestFormat": { - "uppercase": true, - "delimiter": "_" - }, - "configFormat": { - "uppercase": true, - "delimiter": "_" - }, - "useGlobalFormat": true, - "lastUpdated": 1566798411, - "assetTypes": [ - "spot", - "futures" - ], - "pairs": { - "spot": { - "enabled": "BTC_JPY,ETH_BTC,BCH_BTC", - "available": "BTC_JPY,FXBTC_JPY,ETH_BTC,BCH_BTC" - } - } - }, - "api": { - "authenticatedSupport": false, - "authenticatedWebsocketApiSupport": false, - "endpoints": { - "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" - }, - "credentials": { - "key": "Key", - "secret": "Secret" - }, - "credentialsValidator": { - "requiresKey": true, - "requiresSecret": true - } - }, - "features": { - "supports": { - "restAPI": true, - "restCapabilities": { - "autoPairUpdates": true - }, - "websocketAPI": false, - "websocketCapabilities": {} + "name": "BTSE", + "enabled": true, + "verbose": false, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, + "websocketOrderbookBufferLimit": 5, + "baseCurrencies": "USD", + "currencyPairs": { + "requestFormat": { + "uppercase": true, + "delimiter": "-" + }, + "configFormat": { + "uppercase": true, + "delimiter": "-" + }, + "useGlobalFormat": true, + "assetTypes": [ + "spot" + ], + "pairs": { + "spot": { + "enabled": "BTC-USD", + "available": "BTC-CNY,BTC-EUR,BTC-GBP,BTC-HKD,BTC-JPY,BTC-SGD,BTC-USD,ETH-CNY,ETH-EUR,ETH-GBP,ETH-HKD,ETH-JPY,ETH-SGD,ETH-USD,LTC-CNY,LTC-EUR,LTC-GBP,LTC-HKD,LTC-JPY,LTC-SGD,LTC-USD,USDT-CNY,USDT-EUR,USDT-GBP,USDT-HKD,USDT-JPY,USDT-SGD,USDT-USD,XMR-CNY,XMR-EUR,XMR-GBP,XMR-HKD,XMR-JPY,XMR-SGD,XMR-USD" + } + } + }, + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "autoPairUpdates": true + }, + "websocketAPI": true, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": true + } + }, + "bankAccounts": [ + { + "enabled": false, + "bankName": "", + "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ] }, - "enabled": { - "autoPairUpdates": false, - "websocketAPI": false - } - }, - "bankAccounts": [ { - "enabled": false, - "bankName": "", - "bankAddress": "", - "bankPostalCode": "", - "bankPostalCity": "", - "bankCountry": "", - "accountName": "", - "accountNumber": "", - "swiftCode": "", - "iban": "", - "supportedCurrencies": "" - } - ] - }, - { - "name": "Bithumb", - "enabled": true, - "verbose": false, - "httpTimeout": 15000000000, - "websocketResponseCheckTimeout": 30000000, - "websocketResponseMaxLimit": 7000000000, - "websocketTrafficTimeout": 30000000000, - "baseCurrencies": "KRW", - "currencyPairs": { - "requestFormat": { - "uppercase": true, - "delimiter": "_" - }, - "configFormat": { - "uppercase": true, - "delimiter": "-" - }, - "useGlobalFormat": true, - "pairs": { - "spot": { - "assetEnabled": true, - "enabled": "USDT-KRW,QTUM-KRW,BTC-KRW,ETH-KRW,ETC-KRW,XRP-KRW,BCH-KRW,BTG-KRW,EOS-KRW", - "available": "AVAX-KRW,STRAX-KRW,KSM-KRW,RPL-KRW,ADA-KRW,ONT-KRW,EOS-KRW,STAT-KRW,APM-KRW,XPLA-KRW,STMX-KRW,FET-KRW,XVS-KRW,ROA-KRW,JOE-KRW,BNT-KRW,T-KRW,AUDIO-KRW,MIX-KRW,PUNDIX-KRW,USDC-KRW,ALGO-KRW,CTXC-KRW,IQ-KRW,RLY-KRW,GRT-KRW,NMR-KRW,FTM-KRW,WNCG-KRW,NCT-KRW,CSPR-KRW,TFUEL-KRW,EGG-KRW,MOC-KRW,BAT-KRW,ETC-KRW,TIA-KRW,GRACY-KRW,FRONT-KRW,DAI-KRW,ANKR-KRW,META-KRW,HOOK-KRW,BEL-KRW,MAGIC-KRW,ENTC-KRW,HUNT-KRW,STX-KRW,FIT-KRW,STEEM-KRW,CTSI-KRW,JUP-KRW,CAKE-KRW,DOGE-KRW,SUN-KRW,OCEAN-KRW,SOL-KRW,REQ-KRW,BNB-KRW,GAL-KRW,MBL-KRW,LRC-KRW,ILV-KRW,PEPE-KRW,IOST-KRW,XLM-KRW,CRV-KRW,NFT-KRW,PYR-KRW,TRX-KRW,TAVA-KRW,PYTH-KRW,TT-KRW,AAVE-KRW,KLAY-KRW,BAL-KRW,EVZ-KRW,FX-KRW,UMA-KRW,FLOW-KRW,ALEX-KRW,ELF-KRW,CVC-KRW,FLOKI-KRW,MASK-KRW,GAS-KRW,VIX-KRW,CELR-KRW,BLY-KRW,ARK-KRW,FNSA-KRW,OXT-KRW,VALOR-KRW,XTZ-KRW,HBAR-KRW,ONG-KRW,MTL-KRW,WAVES-KRW,ORBS-KRW,MANTA-KRW,ICX-KRW,SNX-KRW,API3-KRW,PENDLE-KRW,FLZ-KRW,APE-KRW,POWR-KRW,OGN-KRW,EDU-KRW,ARB-KRW,AXS-KRW,MBX-KRW,XRP-KRW,MATIC-KRW,USDT-KRW,1INCH-KRW,STORJ-KRW,UOS-KRW,RVN-KRW,LPT-KRW,OSMO-KRW,ALICE-KRW,LDO-KRW,TEMCO-KRW,COMP-KRW,VET-KRW,SFP-KRW,WIKEN-KRW,LBL-KRW,SHIB-KRW,GMT-KRW,AZIT-KRW,ZBCN-KRW,FLUX-KRW,ALT-KRW,AGI-KRW,SPURS-KRW,GRS-KRW,C98-KRW,ZIL-KRW,BCH-KRW,QTCON-KRW,SEI-KRW,GRND-KRW,SWAP-KRW,ETH-KRW,RSS3-KRW,STPT-KRW,FXS-KRW,SAND-KRW,MAP-KRW,MAV-KRW,LINK-KRW,MVC-KRW,QTUM-KRW,DAR-KRW,FANC-KRW,HIGH-KRW,ARKM-KRW,MANA-KRW,SUSHI-KRW,DVI-KRW,XEC-KRW,BTC-KRW,EL-KRW,THETA-KRW,CELO-KRW,KNC-KRW,POLA-KRW,LOOM-KRW,JASMY-KRW,INJ-KRW,KAVA-KRW,NEO-KRW,BIGTIME-KRW,MINA-KRW,NPT-KRW,IMX-KRW,ASM-KRW,FCT2-KRW,RLC-KRW,HIFI-KRW,CTC-KRW,DYDX-KRW,ZTX-KRW,AGIX-KRW,WEMIX-KRW,GTC-KRW,LM-KRW,OP-KRW,ONIT-KRW,ACS-KRW,LSK-KRW,REI-KRW,ATOM-KRW,WLD-KRW,GLM-KRW,COS-KRW,BTT-KRW,BFC-KRW,ACE-KRW,SC-KRW,BORA-KRW,GHX-KRW,ADP-KRW,STRK-KRW,LEVER-KRW,BOBA-KRW,BOA-KRW,HFT-KRW,RNDR-KRW,ENJ-KRW,RSR-KRW,XPR-KRW,IOTX-KRW,CYBER-KRW,WAXP-KRW,OBSR-KRW,MEV-KRW,UNI-KRW,APT-KRW,DAO-KRW,WAXL-KRW,SIX-KRW,GMX-KRW,RDNT-KRW,BTG-KRW,MNT-KRW,BLUR-KRW,XCN-KRW,YGG-KRW,MXC-KRW,ACH-KRW,RAD-KRW,MLK-KRW,DOT-KRW,JST-KRW,ZRX-KRW,STG-KRW,SOFI-KRW,WOM-KRW,TDROP-KRW,SNT-KRW,COTI-KRW,WOO-KRW,OAS-KRW,CRO-KRW,AQT-KRW,EGLD-KRW,ARPA-KRW,BSV-KRW,ASTR-KRW,AMO-KRW,AERGO-KRW,ID-KRW,SUI-KRW,GALA-KRW,CKB-KRW,BIOT-KRW,CFX-KRW,CHR-KRW,FLR-KRW,FITFI-KRW,YFI-KRW,CTK-KRW,W-KRW,MED-KRW,MKR-KRW,SXP-KRW,HIVE-KRW,CRTS-KRW,CHZ-KRW" - } - } - }, - "api": { - "authenticatedSupport": false, - "authenticatedWebsocketApiSupport": false, - "endpoints": { - "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" - }, - "credentials": { - "key": "Key", - "secret": "Secret", - "clientID": "ClientID" - }, - "credentialsValidator": { - "requiresKey": true, - "requiresSecret": true - } - }, - "features": { - "supports": { - "restAPI": true, - "restCapabilities": { - "tickerBatching": true, - "autoPairUpdates": true - }, - "websocketAPI": false, - "websocketCapabilities": {} + "name": "Binance", + "enabled": true, + "verbose": false, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, + "websocketOrderbookBufferLimit": 5, + "baseCurrencies": "USD", + "currencyPairs": { + "requestFormat": { + "uppercase": true + }, + "configFormat": { + "uppercase": true, + "delimiter": "-" + }, + "useGlobalFormat": true, + "assetTypes": [ + "spot" + ], + "pairs": { + "spot": { + "enabled": "BTC-USDT,DOGE-USDT", + "available": "ETH-BTC,LTC-BTC,BNB-BTC,NEO-BTC,QTUM-ETH,EOS-ETH,SNT-ETH,BNT-ETH,GAS-BTC,BNB-ETH,BTC-USDT,ETH-USDT,OAX-ETH,DNT-ETH,MCO-ETH,MCO-BTC,WTC-BTC,WTC-ETH,LRC-BTC,LRC-ETH,QTUM-BTC,YOYO-BTC,OMG-BTC,OMG-ETH,ZRX-BTC,ZRX-ETH,STRAT-BTC,STRAT-ETH,SNGLS-BTC,BQX-BTC,BQX-ETH,KNC-BTC,KNC-ETH,FUN-BTC,FUN-ETH,SNM-BTC,SNM-ETH,NEO-ETH,IOTA-BTC,IOTA-ETH,LINK-BTC,LINK-ETH,XVG-BTC,XVG-ETH,MDA-BTC,MDA-ETH,MTL-BTC,MTL-ETH,EOS-BTC,SNT-BTC,ETC-ETH,ETC-BTC,MTH-BTC,MTH-ETH,ENG-BTC,ENG-ETH,DNT-BTC,ZEC-BTC,ZEC-ETH,BNT-BTC,AST-BTC,AST-ETH,DASH-BTC,DASH-ETH,OAX-BTC,BTG-BTC,BTG-ETH,EVX-BTC,EVX-ETH,REQ-BTC,REQ-ETH,VIB-BTC,VIB-ETH,TRX-BTC,TRX-ETH,POWR-BTC,POWR-ETH,ARK-BTC,ARK-ETH,YOYO-ETH,XRP-BTC,XRP-ETH,ENJ-BTC,ENJ-ETH,STORJ-BTC,STORJ-ETH,BNB-USDT,YOYO-BNB,POWR-BNB,KMD-BTC,KMD-ETH,NULS-BNB,RCN-BTC,RCN-ETH,RCN-BNB,NULS-BTC,NULS-ETH,RDN-BTC,RDN-ETH,RDN-BNB,XMR-BTC,XMR-ETH,DLT-BNB,WTC-BNB,DLT-BTC,DLT-ETH,AMB-BTC,AMB-ETH,AMB-BNB,BAT-BTC,BAT-ETH,BAT-BNB,BCPT-BTC,BCPT-ETH,BCPT-BNB,ARN-BTC,ARN-ETH,GVT-BTC,GVT-ETH,CDT-BTC,CDT-ETH,GXS-BTC,GXS-ETH,NEO-USDT,NEO-BNB,POE-BTC,POE-ETH,QSP-BTC,QSP-ETH,QSP-BNB,BTS-BTC,BTS-ETH,XZC-BTC,XZC-ETH,XZC-BNB,LSK-BTC,LSK-ETH,LSK-BNB,TNT-BTC,TNT-ETH,FUEL-BTC,MANA-BTC,MANA-ETH,BCD-BTC,BCD-ETH,DGD-BTC,DGD-ETH,IOTA-BNB,ADX-BTC,ADX-ETH,ADA-BTC,ADA-ETH,PPT-BTC,PPT-ETH,CMT-BTC,CMT-ETH,CMT-BNB,XLM-BTC,XLM-ETH,XLM-BNB,CND-BTC,CND-ETH,CND-BNB,LEND-BTC,LEND-ETH,WABI-BTC,WABI-ETH,WABI-BNB,LTC-ETH,LTC-USDT,LTC-BNB,TNB-BTC,TNB-ETH,WAVES-BTC,WAVES-ETH,WAVES-BNB,GTO-BTC,GTO-ETH,GTO-BNB,ICX-BTC,ICX-ETH,ICX-BNB,OST-BTC,OST-ETH,OST-BNB,ELF-BTC,ELF-ETH,AION-BTC,AION-ETH,AION-BNB,NEBL-BTC,NEBL-ETH,NEBL-BNB,BRD-BTC,BRD-ETH,BRD-BNB,MCO-BNB,EDO-BTC,EDO-ETH,NAV-BTC,LUN-BTC,APPC-BTC,APPC-ETH,APPC-BNB,VIBE-BTC,VIBE-ETH,RLC-BTC,RLC-ETH,RLC-BNB,INS-BTC,INS-ETH,PIVX-BTC,PIVX-ETH,PIVX-BNB,IOST-BTC,IOST-ETH,STEEM-BTC,STEEM-ETH,STEEM-BNB,NANO-BTC,NANO-ETH,NANO-BNB,VIA-BTC,VIA-ETH,VIA-BNB,BLZ-BTC,BLZ-ETH,BLZ-BNB,AE-BTC,AE-ETH,AE-BNB,NCASH-BTC,NCASH-ETH,POA-BTC,POA-ETH,ZIL-BTC,ZIL-ETH,ZIL-BNB,ONT-BTC,ONT-ETH,ONT-BNB,STORM-BTC,STORM-ETH,STORM-BNB,QTUM-BNB,QTUM-USDT,XEM-BTC,XEM-ETH,XEM-BNB,WAN-BTC,WAN-ETH,WAN-BNB,WPR-BTC,WPR-ETH,QLC-BTC,QLC-ETH,SYS-BTC,SYS-ETH,SYS-BNB,QLC-BNB,GRS-BTC,GRS-ETH,ADA-USDT,ADA-BNB,GNT-BTC,GNT-ETH,LOOM-BTC,LOOM-ETH,LOOM-BNB,XRP-USDT,REP-BTC,REP-ETH,BTC-TUSD,ETH-TUSD,ZEN-BTC,ZEN-ETH,ZEN-BNB,SKY-BTC,SKY-ETH,SKY-BNB,EOS-USDT,EOS-BNB,CVC-BTC,CVC-ETH,THETA-BTC,THETA-ETH,THETA-BNB,XRP-BNB,TUSD-USDT,IOTA-USDT,XLM-USDT,IOTX-BTC,IOTX-ETH,QKC-BTC,QKC-ETH,AGI-BTC,AGI-ETH,AGI-BNB,NXS-BTC,NXS-ETH,NXS-BNB,ENJ-BNB,DATA-BTC,DATA-ETH,ONT-USDT,TRX-BNB,TRX-USDT,ETC-USDT,ETC-BNB,ICX-USDT,SC-BTC,SC-ETH,SC-BNB,NPXS-ETH,KEY-BTC,KEY-ETH,NAS-BTC,NAS-ETH,NAS-BNB,MFT-BTC,MFT-ETH,MFT-BNB,DENT-ETH,ARDR-BTC,ARDR-ETH,NULS-USDT,HOT-BTC,HOT-ETH,VET-BTC,VET-ETH,VET-USDT,VET-BNB,DOCK-BTC,DOCK-ETH,POLY-BTC,POLY-BNB,HC-BTC,HC-ETH,GO-BTC,GO-BNB,PAX-USDT,RVN-BTC,RVN-BNB,DCR-BTC,DCR-BNB,MITH-BTC,MITH-BNB,BNB-PAX,BTC-PAX,ETH-PAX,XRP-PAX,EOS-PAX,XLM-PAX,REN-BTC,REN-BNB,BNB-TUSD,XRP-TUSD,EOS-TUSD,XLM-TUSD,BNB-USDC,BTC-USDC,ETH-USDC,XRP-USDC,EOS-USDC,XLM-USDC,USDC-USDT,ADA-TUSD,TRX-TUSD,NEO-TUSD,TRX-XRP,XZC-XRP,PAX-TUSD,USDC-TUSD,USDC-PAX,LINK-USDT,LINK-TUSD,LINK-PAX,LINK-USDC,WAVES-USDT,WAVES-TUSD,WAVES-USDC,LTC-TUSD,LTC-PAX,LTC-USDC,TRX-PAX,TRX-USDC,BTT-BNB,BTT-USDT,BNB-USDS,BTC-USDS,USDS-USDT,USDS-PAX,USDS-TUSD,USDS-USDC,BTT-PAX,BTT-TUSD,BTT-USDC,ONG-BNB,ONG-BTC,ONG-USDT,HOT-BNB,HOT-USDT,ZIL-USDT,ZRX-BNB,ZRX-USDT,FET-BNB,FET-BTC,FET-USDT,BAT-USDT,XMR-BNB,XMR-USDT,ZEC-BNB,ZEC-USDT,ZEC-PAX,ZEC-TUSD,ZEC-USDC,IOST-BNB,IOST-USDT,CELR-BNB,CELR-BTC,CELR-USDT,ADA-PAX,ADA-USDC,NEO-PAX,NEO-USDC,DASH-BNB,DASH-USDT,NANO-USDT,OMG-BNB,OMG-USDT,THETA-USDT,ENJ-USDT,MITH-USDT,MATIC-BNB,MATIC-BTC,MATIC-USDT,ATOM-BNB,ATOM-BTC,ATOM-USDT,ATOM-USDC,ATOM-TUSD,ETC-TUSD,BAT-USDC,BAT-PAX,BAT-TUSD,PHB-BNB,PHB-BTC,PHB-TUSD,TFUEL-BNB,TFUEL-BTC,TFUEL-USDT,ONE-BNB,ONE-BTC,ONE-USDT,ONE-USDC,FTM-BNB,FTM-BTC,FTM-USDT,FTM-USDC,ALGO-BNB,ALGO-BTC,ALGO-USDT,ALGO-TUSD,ALGO-PAX,ALGO-USDC,GTO-USDT,ERD-BNB,ERD-BTC,ERD-USDT,DOGE-BNB,DOGE-BTC,DOGE-USDT,DUSK-BNB,DUSK-BTC,DUSK-USDT,DUSK-USDC,DUSK-PAX,BGBP-USDC,ANKR-BNB,ANKR-BTC,ANKR-USDT,ONT-PAX,ONT-USDC,WIN-BNB,WIN-USDT,WIN-USDC,COS-BNB,COS-BTC,COS-USDT,NPXS-USDT,COCOS-BNB,COCOS-BTC,COCOS-USDT,MTL-USDT,TOMO-BNB,TOMO-BTC,TOMO-USDT,TOMO-USDC,PERL-BNB,PERL-BTC,PERL-USDT,DENT-USDT,MFT-USDT,KEY-USDT,STORM-USDT,DOCK-USDT,WAN-USDT,FUN-USDT,CVC-USDT,BTT-TRX,WIN-TRX,CHZ-BNB,CHZ-BTC,CHZ-USDT,BAND-BNB,BAND-BTC,BAND-USDT,BNB-BUSD,BTC-BUSD,BUSD-USDT,BEAM-BNB,BEAM-BTC,BEAM-USDT,XTZ-BNB,XTZ-BTC,XTZ-USDT,REN-USDT,RVN-USDT,HC-USDT,HBAR-BNB,HBAR-BTC,HBAR-USDT,NKN-BNB,NKN-BTC,NKN-USDT,XRP-BUSD,ETH-BUSD,LTC-BUSD,LINK-BUSD,ETC-BUSD,STX-BNB,STX-BTC,STX-USDT,KAVA-BNB,KAVA-BTC,KAVA-USDT,BUSD-NGN,BNB-NGN,BTC-NGN,ARPA-BNB,ARPA-BTC,ARPA-USDT,TRX-BUSD,EOS-BUSD,IOTX-USDT,RLC-USDT,MCO-USDT,XLM-BUSD,ADA-BUSD,CTXC-BNB,CTXC-BTC,CTXC-USDT,BCH-BNB,BCH-BTC,BCH-USDT,BCH-USDC,BCH-TUSD,BCH-PAX,BCH-BUSD,BTC-RUB,ETH-RUB,XRP-RUB,BNB-RUB,TROY-BNB,TROY-BTC,TROY-USDT,BUSD-RUB,QTUM-BUSD,VET-BUSD" + } + } + }, + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "tickerBatching": true, + "autoPairUpdates": true + }, + "websocketAPI": true, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": true + } + }, + "bankAccounts": [ + { + "enabled": false, + "bankName": "", + "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ] }, - "enabled": { - "autoPairUpdates": true, - "websocketAPI": false - } - }, - "bankAccounts": [ { - "enabled": false, - "bankName": "", - "bankAddress": "", - "bankPostalCode": "", - "bankPostalCity": "", - "bankCountry": "", - "accountName": "", - "accountNumber": "", - "swiftCode": "", - "iban": "", - "supportedCurrencies": "" - } - ], - "orderbook": { - "verificationBypass": false, - "websocketBufferLimit": 5, - "websocketBufferEnabled": false, - "publishPeriod": 10000000000 - } - }, - { - "name": "Bitmex", - "enabled": true, - "verbose": false, - "httpTimeout": 15000000000, - "websocketResponseCheckTimeout": 30000000, - "websocketResponseMaxLimit": 7000000000, - "websocketTrafficTimeout": 30000000000, - "websocketOrderbookBufferLimit": 5, - "baseCurrencies": "USD", - "currencyPairs": { - "assetTypes": [ - "perpetualcontract", - "futures", - "downsideprofitcontract", - "upsideprofitcontract" - ], - "pairs": { - "downsideprofitcontract": { - "enabled": "XBT7D_D95", - "available": "XBT7D_D95", - "requestFormat": { - "uppercase": true, - "delimiter": "_" - }, - "configFormat": { - "uppercase": true, - "delimiter": "_" - } - }, - "futures": { - "enabled": "BCHZ19", - "available": "XRPZ19,BCHZ19,ADAZ19,EOSZ19,TRXZ19,XBTZ19,ETHZ19,LTCZ19", - "requestFormat": { - "uppercase": true - }, - "configFormat": { - "uppercase": true - } - }, - "perpetualcontract": { - "enabled": "ETHUSD", - "available": "XBTUSD,ETHUSD", - "requestFormat": { - "uppercase": true - }, - "configFormat": { - "uppercase": true - } - }, - "upsideprofitcontract": { - "enabled": "XBT7D_U105", - "available": "XBT7D_U105", - "requestFormat": { - "uppercase": true, - "delimiter": "_" - }, - "configFormat": { - "uppercase": true, - "delimiter": "_" + "name": "Binanceus", + "enabled": true, + "verbose": false, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, + "baseCurrencies": "USD", + "currencyPairs": { + "bypassConfigFormatUpgrades": false, + "pairs": { + "spot": { + "assetEnabled": true, + "enabled": "BTC-USDT,ETH-USDT,LTC-USDT,ADA-USDT", + "available": "BTC-USD,BCH-USD,LTC-USD,USDT-USD,BTC-USDT,ETH-USDT,BCH-USDT,LTC-USDT,BNB-USD,BNB-USDT,ETH-BTC,BNB-BTC,LTC-BTC,BCH-BTC,ADA-USD,BAT-USD,ETC-USD,XLM-USD,ZRX-USD,ADA-USDT,BAT-USDT,ETC-USDT,XLM-USDT,ZRX-USDT,LINK-USD,RVN-USD,DASH-USD,ZEC-USD,ALGO-USD,IOTA-USD,BUSD-USD,BTCB-USD,DOGE-USDT,WAVES-USD,ATOM-USDT,ATOM-USD,NEO-USDT,NEO-USD,VET-USDT,QTUM-USDT,QTUM-USD,ICX-USD,ENJ-USD,ONT-USD,ONT-USDT,ZIL-USD,ZILB-USD,VET-USD,BNBB-USD,ETHB-USD,ALGO-BUSD,XTZ-USD,XTZ-BUSD,HBAR-USD,HBAR-BUSD,OMG-USD,OMG-BUSD,MATIC-USD,MATIC-BUSD,XTZ-BTC,ADA-BTC,REP-BUSD,REP-USD,EOS-BUSD,EOS-USD,DOGE-USD,KNC-USD,KNC-USDT,VTHO-USDT,VTHO-USD,USDC-USD,COMP-USDT,COMP-USD,MANA-USD,HNT-USD,HNT-USDT,MKR-USD,MKR-USDT,DAI-USD,ONE-USDT,ONE-USD,BAND-USDT,BAND-USD,STORJ-USDT,STORJ-USD,UNI-USD,UNI-USDT,SOL-USD,SOL-USDT,LINK-BTC,VET-BTC,UNI-BTC,EGLD-USDT,EGLD-USD,PAXG-USDT,PAXG-USD,OXT-USDT,OXT-USD,ZEN-USDT,ZEN-USD,BTC-USDC,ONEB-USD,FIL-USDT,FIL-USD,AAVE-USDT,AAVE-USD,GRT-USDT,GRT-USD,SUSHI-USD,ANKR-USD,AMP-USD,SHIB-USDT,SHIB-BUSD,CRV-USDT,CRV-USD,AXS-USDT,AXS-USD,SOL-BTC,AVAX-USDT,AVAX-USD,CTSI-USDT,CTSI-USD,DOT-USDT,DOT-USD,YFI-USDT,YFI-USD,1INCH-USDT,1INCH-USD,FTM-USDT,FTM-USD,USDC-USDT,ETH-USDC,USDC-BUSD,MATIC-USDT,MANA-USDT,MANA-BUSD,ALGO-USDT,ADA-BUSD,SOL-BUSD,EOS-USDT,ENJ-USDT,NEAR-USDT,NEAR-BUSD,NEAR-USD,OMG-USDT,SUSHI-USDT,LRC-USDT,LRC-USD,LRC-BTC,KSHI-BUSD,LPT-USDT,LPT-BUSD,LPT-USD,POLY-USDT,POLY-BUSD,POLY-USD,POLY-BTC,MATIC-BTC,DOT-BTC,NMR-USDT,NMR-USD,SLP-USDT,ANT-USD,XNO-USD,CHZ-USDT,CHZ-USD,OGN-USDT,OGN-USD,GALA-USDT,GALA-USD,TLM-USDT,TLM-USD,SNX-USDT,SNX-USD,AUDIO-USDT,AUDIO-USD,ENS-USDT,MANA-BTC,ATOM-BTC,AVAX-BTC,WBTC-BTC,REQ-USDT,REQ-USD,APE-USDT,APE-USD,FLUX-USDT,FLUX-USD,TRX-BTC,TRX-BUSD,TRX-USDT,TRX-USD,COTI-USDT,COTI-USD,VOXEL-USDT,VOXEL-USD,RLC-USDT,RLC-USD,UST-USDT,UST-USD,BICO-USDT,BICO-USD,API3-USDT,API3-USD,ENS-USD,BTC-UST,BNT-USDT,BNT-USD,IMX-USDT,IMX-USD,SPELL-USDT,SPELL-USD,JASMY-USDT,JASMY-USD,FLOW-USDT,FLOW-USD,GTC-USDT,GTC-USD,BTC-BUSD,ZIL-BUSD,BNB-BUSD,ETH-BUSD,BUSD-USDT,ONE-BUSD,LINK-USDT,ZEC-USDT,SLP-USD,ANT-USDT", + "requestFormat": { + "uppercase": true + }, + "configFormat": { + "uppercase": true, + "delimiter": "-" + } + } + } + }, + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "credentials": { + "key": "", + "secret": "" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true + }, + "urlEndpoints": { + "RestSpotSupplementaryURL": "https://api.binance.us", + "RestSpotURL": "https://api.binance.us", + "WebsocketSpotSupplementaryURL": "wss://stream.binance.us:9443/stream", + "WebsocketSpotURL": "wss://stream.binance.us:9443/stream" + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "tickerBatching": true, + "autoPairUpdates": true + }, + "websocketAPI": true, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": true, + "saveTradeData": false, + "tradeFeed": false, + "fillsFeed": false + } + }, + "bankAccounts": [ + { + "enabled": false, + "bankName": "", + "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ], + "orderbook": { + "verificationBypass": false, + "websocketBufferLimit": 5, + "websocketBufferEnabled": false, + "publishPeriod": 10000000000 } - } - } - }, - "api": { - "authenticatedSupport": false, - "authenticatedWebsocketApiSupport": false, - "endpoints": { - "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" }, - "credentials": { - "key": "Key", - "secret": "Secret" - }, - "credentialsValidator": { - "requiresKey": true, - "requiresSecret": true - } - }, - "features": { - "supports": { - "restAPI": true, - "restCapabilities": { - "tickerBatching": true, - "autoPairUpdates": true - }, - "websocketAPI": true, - "websocketCapabilities": {} - }, - "enabled": { - "autoPairUpdates": true, - "websocketAPI": false - } - }, - "bankAccounts": [ { - "enabled": false, - "bankName": "", - "bankAddress": "", - "bankPostalCode": "", - "bankPostalCity": "", - "bankCountry": "", - "accountName": "", - "accountNumber": "", - "swiftCode": "", - "iban": "", - "supportedCurrencies": "" - } - ] - }, - { - "name": "Bitstamp", - "enabled": true, - "verbose": false, - "httpTimeout": 15000000000, - "websocketResponseCheckTimeout": 30000000, - "websocketResponseMaxLimit": 7000000000, - "websocketTrafficTimeout": 30000000000, - "websocketOrderbookBufferLimit": 5, - "baseCurrencies": "USD,EUR", - "currencyPairs": { - "requestFormat": { - "uppercase": true - }, - "configFormat": { - "uppercase": true - }, - "useGlobalFormat": true, - "assetTypes": [ - "spot" - ], - "pairs": { - "spot": { - "enabled": "BTCUSD,BTCEUR,EURUSD,XRPUSD,XRPEUR", - "available": "LTCUSD,ETHUSD,XRPEUR,BCHUSD,BCHEUR,BTCEUR,XRPBTC,EURUSD,BCHBTC,LTCEUR,BTCUSD,LTCBTC,XRPUSD,ETHBTC,ETHEUR" - } - } - }, - "api": { - "authenticatedSupport": false, - "authenticatedWebsocketApiSupport": false, - "endpoints": { - "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" - }, - "credentials": { - "key": "Key", - "secret": "Secret", - "clientID": "ClientID" + "name": "Bitfinex", + "enabled": true, + "verbose": false, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, + "websocketOrderbookBufferLimit": 5, + "baseCurrencies": "USD", + "currencyPairs": { + "requestFormat": { + "uppercase": true + }, + "configFormat": { + "uppercase": true + }, + "useGlobalFormat": true, + "assetTypes": [ + "spot" + ], + "pairs": { + "spot": { + "enabled": "BTCUSD,LTCUSD,LTCBTC,ETHUSD,ETHBTC", + "available": "BTCUSD,LTCUSD,LTCBTC,ETHUSD,ETHBTC,ETCBTC,ETCUSD,RRTUSD,RRTBTC,ZECUSD,ZECBTC,XMRUSD,XMRBTC,DSHUSD,DSHBTC,BTCEUR,BTCJPY,XRPUSD,XRPBTC,IOTUSD,IOTBTC,IOTETH,EOSUSD,EOSBTC,EOSETH,SANUSD,SANBTC,SANETH,OMGUSD,OMGBTC,OMGETH,NEOUSD,NEOBTC,NEOETH,ETPUSD,ETPBTC,ETPETH,QTMUSD,QTMBTC,QTMETH,AVTUSD,AVTBTC,AVTETH,EDOUSD,EDOBTC,EDOETH,BTGUSD,BTGBTC,DATUSD,DATBTC,DATETH,QSHUSD,QSHBTC,QSHETH,YYWUSD,YYWBTC,YYWETH,GNTUSD,GNTBTC,GNTETH,SNTUSD,SNTBTC,SNTETH,IOTEUR,BATUSD,BATBTC,BATETH,MNAUSD,MNABTC,MNAETH,FUNUSD,FUNBTC,FUNETH,ZRXUSD,ZRXBTC,ZRXETH,TNBUSD,TNBBTC,TNBETH,SPKUSD,SPKBTC,SPKETH,TRXUSD,TRXBTC,TRXETH,RCNUSD,RCNBTC,RCNETH,RLCUSD,RLCBTC,RLCETH,AIDUSD,AIDBTC,AIDETH,SNGUSD,SNGBTC,SNGETH,REPUSD,REPBTC,REPETH,ELFUSD,ELFBTC,ELFETH,NECUSD,NECBTC,NECETH,BTCGBP,ETHEUR,ETHJPY,ETHGBP,NEOEUR,NEOJPY,NEOGBP,EOSEUR,EOSJPY,EOSGBP,IOTJPY,IOTGBP,IOSUSD,IOSBTC,IOSETH,AIOUSD,AIOBTC,AIOETH,REQUSD,REQBTC,REQETH,RDNUSD,RDNBTC,RDNETH,LRCUSD,LRCBTC,LRCETH,WAXUSD,WAXBTC,WAXETH,DAIUSD,DAIBTC,DAIETH,AGIUSD,AGIBTC,AGIETH,BFTUSD,BFTBTC,BFTETH,MTNUSD,MTNBTC,MTNETH,ODEUSD,ODEBTC,ODEETH,ANTUSD,ANTBTC,ANTETH,DTHUSD,DTHBTC,DTHETH,MITUSD,MITBTC,MITETH,STJUSD,STJBTC,STJETH,XLMUSD,XLMEUR,XLMJPY,XLMGBP,XLMBTC,XLMETH,XVGUSD,XVGEUR,XVGJPY,XVGGBP,XVGBTC,XVGETH,BCIUSD,BCIBTC,MKRUSD,MKRBTC,MKRETH,KNCUSD,KNCBTC,KNCETH,POAUSD,POABTC,POAETH,EVTUSD,LYMUSD,LYMBTC,LYMETH,UTKUSD,UTKBTC,UTKETH,VEEUSD,VEEBTC,VEEETH,DADUSD,DADBTC,DADETH,ORSUSD,ORSBTC,ORSETH,AUCUSD,AUCBTC,AUCETH,POYUSD,POYBTC,POYETH,FSNUSD,FSNBTC,FSNETH,CBTUSD,CBTBTC,CBTETH,ZCNUSD,ZCNBTC,ZCNETH,SENUSD,SENBTC,SENETH,NCAUSD,NCABTC,NCAETH,CNDUSD,CNDBTC,CNDETH,CTXUSD,CTXBTC,CTXETH,PAIUSD,PAIBTC,SEEUSD,SEEBTC,SEEETH,ESSUSD,ESSBTC,ESSETH,ATMUSD,ATMBTC,ATMETH,HOTUSD,HOTBTC,HOTETH,DTAUSD,DTABTC,DTAETH,IQXUSD,IQXBTC,IQXEOS,WPRUSD,WPRBTC,WPRETH,ZILUSD,ZILBTC,ZILETH,BNTUSD,BNTBTC,BNTETH,ABSUSD,ABSETH,XRAUSD,XRAETH,MANUSD,MANETH,BBNUSD,BBNETH,NIOUSD,NIOETH,DGXUSD,DGXETH,VETUSD,VETBTC,VETETH,UTNUSD,UTNETH,TKNUSD,TKNETH,GOTUSD,GOTEUR,GOTETH,XTZUSD,XTZBTC,CNNUSD,CNNETH,BOXUSD,BOXETH,TRXEUR,TRXGBP,TRXJPY,MGOUSD,MGOETH,RTEUSD,RTEETH,YGGUSD,YGGETH,MLNUSD,MLNETH,WTCUSD,WTCETH,CSXUSD,CSXETH,OMNUSD,OMNBTC,INTUSD,INTETH,DRNUSD,DRNETH,PNKUSD,PNKETH,DGBUSD,DGBBTC,BSVUSD,BSVBTC,BABUSD,BABBTC,WLOUSD,WLOXLM,VLDUSD,VLDETH,ENJUSD,ENJETH,ONLUSD,ONLETH,RBTUSD,RBTBTC,USTUSD,EUTEUR,EUTUSD,GSDUSD,UDCUSD,TSDUSD,PAXUSD,RIFUSD,RIFBTC,PASUSD,PASETH,VSYUSD,VSYBTC,ZRXDAI,MKRDAI,OMGDAI,BTTUSD,BTTBTC,BTCUST,ETHUST,CLOUSD,CLOBTC,IMPUSD,IMPETH,LTCUST,EOSUST,BABUST,SCRUSD,SCRETH,GNOUSD,GNOETH,GENUSD,GENETH,ATOUSD,ATOBTC,ATOETH,WBTUSD,XCHUSD,EUSUSD,WBTETH,XCHETH,EUSETH,LEOUSD,LEOBTC,LEOUST,LEOEOS,LEOETH,ASTUSD,ASTETH,FOAUSD,FOAETH,UFRUSD,UFRETH,ZBTUSD,ZBTUST,OKBUSD,USKUSD,GTXUSD,KANUSD,OKBUST,OKBETH,OKBBTC,USKUST,USKETH,USKBTC,USKEOS,GTXUST,KANUST,AMPUSD,ALGUSD,ALGBTC,ALGUST,BTCXCH,SWMUSD,SWMETH,TRIUSD,TRIETH,LOOUSD,LOOETH,AMPUST,DUSK:USD,DUSK:BTC,UOSUSD,UOSBTC,RRBUSD,RRBUST,DTXUSD,DTXUST,AMPBTC,FTTUSD,FTTUST,PAXUST,UDCUST,TSDUST,BTC:CNHT,UST:CNHT,CNH:CNHT,CHZUSD,CHZUST,BTCF0:USTF0,ETHF0:USTF0" + } + } + }, + "api": { + "authenticatedSupport": true, + "authenticatedWebsocketApiSupport": true, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "tickerBatching": true, + "autoPairUpdates": true + }, + "websocketAPI": true, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": true + } + }, + "bankAccounts": [ + { + "enabled": false, + "bankName": "Deutsche Bank Privat Und Geschaeftskunden AG", + "bankAddress": "Karlsruhe, 76125, GERMANY", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", + "accountName": "GLOBAL TRADE SOLUTIONS GmbH", + "accountNumber": "DE51660700240057016802", + "swiftCode": "DEUTDEDB660", + "iban": "DE51660700240057016802", + "supportedCurrencies": "EUR,USD" + }, + { + "enabled": false, + "bankName": "Deutsche Bank Privat Und Geschaeftskunden AG", + "bankAddress": "Karlsruhe, 76125, GERMANY", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", + "accountName": "GLOBAL TRADE SOLUTIONS GmbH", + "accountNumber": "DE78660700240057016801", + "swiftCode": "DEUTDEDB660", + "iban": "DE78660700240057016801", + "supportedCurrencies": "JPY,GBP" + } + ] }, - "credentialsValidator": { - "requiresKey": true, - "requiresSecret": true, - "requiresClientID": true - } - }, - "features": { - "supports": { - "restAPI": true, - "restCapabilities": { - "autoPairUpdates": true - }, - "websocketAPI": true, - "websocketCapabilities": {} + { + "name": "Bitflyer", + "enabled": true, + "verbose": false, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, + "websocketOrderbookBufferLimit": 5, + "baseCurrencies": "JPY", + "currencyPairs": { + "requestFormat": { + "uppercase": true, + "delimiter": "_" + }, + "configFormat": { + "uppercase": true, + "delimiter": "_" + }, + "useGlobalFormat": true, + "lastUpdated": 1566798411, + "assetTypes": [ + "spot", + "futures" + ], + "pairs": { + "spot": { + "enabled": "BTC_JPY,ETH_BTC,BCH_BTC", + "available": "BTC_JPY,FXBTC_JPY,ETH_BTC,BCH_BTC" + } + } + }, + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "autoPairUpdates": true + }, + "websocketAPI": false, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": false, + "websocketAPI": false + } + }, + "bankAccounts": [ + { + "enabled": false, + "bankName": "", + "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ] }, - "enabled": { - "autoPairUpdates": true, - "websocketAPI": false - } - }, - "bankAccounts": [ { - "enabled": false, - "bankName": "", - "bankAddress": "", - "bankPostalCode": "", - "bankPostalCity": "", - "bankCountry": "", - "accountName": "", - "accountNumber": "", - "swiftCode": "", - "iban": "", - "supportedCurrencies": "" - } - ] - }, - { - "name": "Bybit", - "enabled": true, - "verbose": false, - "httpTimeout": 15000000000, - "websocketResponseCheckTimeout": 30000000, - "websocketResponseMaxLimit": 7000000000, - "websocketTrafficTimeout": 30000000000, - "websocketOrderbookBufferLimit": 5, - "baseCurrencies": "USD", - "currencyPairs": { - "assetTypes": [ - "spot", - "margin", - "coinmarginedfutures", - "usdtmarginedfutures", - "usdcmarginedfutures", - "options" - ], - "pairs": { - "spot": { - "enabled": "BTC_USDT,ETH_USDT,XRP_USDT,EOS_USDT,ETH_BTC,XRP_BTC,DOT_USDT,XLM_USDT,LTC_USDT", - "available": "BTC_USDT,ETH_USDT,XRP_USDT,EOS_USDT,ETH_BTC,XRP_BTC,DOT_USDT,XLM_USDT,LTC_USDT,DOGE_USDT,CHZ_USDT,AXS_USDT,MANA_USDT,DYDX_USDT,MKR_USDT,COMP_USDT,AAVE_USDT,YFI_USDT,LINK_USDT,SUSHI_USDT,UNI_USDT,KSM_USDT,ICP_USDT,ADA_USDT,ETC_USDT,KLAY_USDT,XTZ_USDT,BCH_USDT,SRM_USDT,QNT_USDT,USDC_USDT,GRT_USDT,SOL_USDT,FIL_USDT,OMG_USDT,TRIBE_USDT,BAT_USDT,ZRX_USDT,CRV_USDT,AGLD_USDT,ANKR_USDT,PERP_USDT,MATIC_USDT,WAVES_USDT,LUNC_USDT,SPELL_USDT,SHIB_USDT,FTM_USDT,ATOM_USDT,ALGO_USDT,ENJ_USDT,CBX_USDT,SAND_USDT,AVAX_USDT,WOO_USDT,FTT_USDT,GODS_USDT,IMX_USDT,ENS_USDT,GM_USDT,CWAR_USDT,CAKE_USDT,STETH_USDT,GALFT_USDT,LFW_USDT,SLP_USDT,C98_USDT,PSP_USDT,GENE_USDT,AVA_USDT,ONE_USDT,PTU_USDT,SHILL_USDT,XYM_USDT,BOBA_USDT,JASMY_USDT,GALA_USDT,RNDR_USDT,TRVL_USDT,WEMIX_USDT,XEM_USDT,BICO_USDT,CEL_USDT,UMA_USDT,HOT_USDT,NEXO_USDT,BNT_USDT,SNX_USDT,REN_USDT,1INCH_USDT,TEL_USDT,SIS_USDT,LRC_USDT,LDO_USDT,REAL_USDT,KRL_USDT,DEVT_USDT,ETH_USDC,BTC_USDC,1SOL_USDT,PLT_USDT,IZI_USDT,QTUM_USDT,DCR_USDT,ZEN_USDT,THETA_USDT,MX_USDT,DGB_USDT,RVN_USDT,EGLD_USDT,RUNE_USDT,XLM_BTC,XLM_USDC,SOL_USDC,XRP_USDC,ALGO_BTC,SOL_BTC,RAIN_USDT,XEC_USDT,ICX_USDT,XDC_USDT,HNT_USDT,BTG_USDT,ZIL_USDT,HBAR_USDT,FLOW_USDT,SOS_USDT,KASTA_USDT,STX_USDT,SIDUS_USDT,VPAD_USDT,GGM_USDT,LOOKS_USDT,MBS_USDT,DAI_USDT,BUSD_USDT,ACA_USDT,MV_USDT,MIX_USDT,LTC_USDC,MANA_BTC,MATIC_BTC,LTC_BTC,DOT_BTC,SAND_BTC,MANA_USDC,MATIC_USDC,SAND_USDC,DOT_USDC,LUNC_USDC,RSS3_USDT,SYNR_USDT,TAP_USDT,ERTHA_USDT,GMX_USDT,T_USDT,ACH_USDT,JST_USDT,SUN_USDT,BTT_USDT,TRX_USDT,NFT_USDT,POKT_USDT,SCRT_USDT,PSTAKE_USDT,SON_USDT,HERO_USDT,DOME_USDT,USTC_USDT,BNB_USDT,NEAR_USDT,PAXG_USDT,SD_USDT,APE_USDT,BTC3S_USDT,BTC3L_USDT,FIDA_USDT,MINA_USDT,SC_USDT,RACA_USDT,CAPS_USDT,STG_USDT,GLMR_USDT,MOVR_USDT,ZAM_USDT,ETH_DAI,BTC_DAI,WBTC_USDT,XAVA_USDT,MELOS_USDT,GMT_USDT,GST_USDT,CELO_USDT,SFUND_USDT,ELT_USDT,LGX_USDT,APEX_USDT,CTC_USDT,COT_USDT,KMON_USDT,PLY_USDT,XWG_USDT,FITFI_USDT,STRM_USDT,GAL_USDT,ETH3S_USDT,ETH3L_USDT,KOK_USDT,FAME_USDT,XRP3S_USDT,XRP3L_USDT,USDD_USDT,OP_USDT,LUNA_USDT,DFI_USDT,MOVEZ_USDT,THN_USDT,DOT3S_USDT,DOT3L_USDT,VINU_USDT,BEL_USDT,FORT_USDT,AVAX2S_USDT,AVAX2L_USDT,ADA2S_USDT,ADA2L_USDT,WLKN_USDT,KON_USDT,LTC2S_USDT,LTC2L_USDT,SAND2S_USDT,SAND2L_USDT,OBX_USDT,SEOR_USDT,MNZ_USDT,CULT_USDT,DOGE_USDC,EOS_USDC,CUSD_USDT,SLG_USDT,CMP_USDT,KUNCI_USDT,GSTS_USDT,XETA_USDT,AZY_USDT,MMC_USDT,FLOKI_USDT,BABYDOGE_USDT,STAT_USDT,SAITAMA_USDT,MATIC2S_USDT,MATIC2L_USDT,ETC2S_USDT,ETC2L_USDT,DICE_USDT,WAXP_USDT,AR_USDT,KDA_USDT,ROSE_USDT,SLG_USDC,APE2S_USDT,APE2L_USDT,GMT2S_USDT,GMT2L_USDT,DEFY_USDT,PSG_USDT,BAR_USDT,JUV_USDT,ACM_USDT,INTER_USDT,AFC_USDT,CITY_USDT,LINK2L_USDT,LINK2S_USDT,FTM2L_USDT,FTM2S_USDT,SOLO_USDT,W_BTC,AVAX_USDC,ADA_USDC,OP_USDC,DOGE2S_USDT,DOGE2L_USDT,ATOM2S_USDT,ATOM2L_USDT,APEX_USDC,TRX_USDC,ICP_USDC,LINK_USDC,GMT_USDC,CHZ_USDC,SHIB_USDC,LDO_USDC,APE_USDC,FIL_USDC,CHRP_USDT,EOS2S_USDT,EOS2L_USDT,WWY_USDT,LING_USDT,SWEAT_USDT,DLC_USDT,OKG_USDT,ETHW_USDT,INJ_USDT,MPLX_USDT,MIBR_USDT,CO_USDT,AGLA_USDT,ROND_USDT,QMALL_USDT,PUMLX_USDT,GCAKE_USDT,APT_USDT,APT_USDC,USDT_EUR,MTK_USDT,MCRT_USDT,MASK_USDT,ECOX_USDT,HFT_USDC,HFT_USDT,KCAL_USDT,PEOPLE_USDT,TWT_USDT,ORT_USDT,HOOK_USDT,PRIMAL_USDT,MCT_USDT,OAS_USDT,MAGIC_USDT,MEE_USDT,TON_USDT,BONK_USDT,FLR_USDT,TIME_USDT,3P_USDT,RPL_USDT,SSV_USDT,FXS_USDT,CORE_USDT,RDNT_USDT,BLUR_USDT,LIS_USDT,AGIX_USDT,MDAO_USDT,ACS_USDT,HVH_USDT,GNS_USDT,DPX_USDT,PIP_USDT,PRIME_USDT,EVER_USDT,VRA_USDT,GPT_USDT,FB_USDT,DZOO_USDT,ID_USDT,ARB_USDC,ARB_USDT,XCAD_USDT,MBX_USDT,AXL_USDT,CGPT_USDT,PLAY_USDT,AGI_USDT,RLTM_USDT,SUI_USDT,SUI_USDC,TAMA_USDT,MVL_USDT,PEPE_USDT,LADYS_USDT,LMWR_USDT,BOB_USDT,TOMI_USDT,KARATE_USDT,SUIA_USDT,TURBOS_USDT,FMB_USDT,CAPO_USDT,TENET_USDT,VELO_USDT,ELDA_USDT,CANDY_USDT,FON_USDT,OMN_USDT,TOMS_USDT,MTC_USDT,VELA_USDT,USDT_BRZ,BTC_BRZ,PENDLE_USDT,EGO_USDT,PEPE2_USDT,NYM_USDT,MNT_USDT,MNT_USDC,MNT_BTC,GSWIFT_USDT,SALD_USDT,ARKM_USDT,NEON_USDT,WLD_USDC,WLD_USDT,PLANET_USDT,DSRUN_USDT,SPARTA_USDT,TAVA_USDT,SEILOR_USDT,SEI_USDT,CYBER_USDT,ORDI_USDT,KAVA_USDT,VV_USDT,SAIL_USDT,PYUSD_USDT,SOL_EUR,USDC_EUR,ADA_EUR,DOGE_EUR,LTC_EUR,XRP_EUR,ETH_EUR,BTC_EUR,VEXT_USDT,CTT_USDT,NEXT_USDT,KAS_USDT,NESS_USDT,CAT_USDT,FET_USDT,LEVER_USDT,VEGA_USDT,ZTX_USDT", - "requestFormat": { - "uppercase": true - }, - "configFormat": { - "uppercase": true, - "delimiter": "_" - } - }, - "coinmarginedfutures": { - "enabled": "ADA_USD,BTC_USD,BTC_USDH24,BTC_USDZ23,DOT_USD", - "available": "ADA_USD,BTC_USD,BTC_USDH24,BTC_USDZ23,DOT_USD,EOS_USD,ETH_USD,ETH_USDH24,ETH_USDZ23,LTC_USD,MAN_AUSD,XRP_USD", - "requestFormat": { - "uppercase": true - }, - "configFormat": { - "uppercase": true, - "delimiter": "_" - } - }, - "usdcmarginedfutures": { - "enabled": "ETH-PERP,BNB-PERP,SOL-PERP,BTC-PERP", - "available": "BNB-PERP,BTC-03NOV23,BTC-20OCT23,BTC-24NOV23,BTC-27OCT23,BTC-28JUN24,BTC-29DEC23,BTC-29MAR24,BTC-PERP,ETC-PERP,ETH-03NOV23,ETH-20OCT23,ETH-24NOV23,ETH-27OCT23,ETH-28JUN24,ETH-29DEC23,ETH-29MAR24,ETH-PERP,MAT-ICPERP,OPP-ERP,SOL-PERP,XRP-PERP", - "requestFormat": { - "uppercase": true - }, - "configFormat": { - "uppercase": true, - "delimiter": "-" - } - }, - "usdtmarginedfutures": { - "enabled": "BTC_USDT,10000LADYS_USDT,IOTA_USDT,AAVE_USDT", - "available": "10000LADYS_USDT,10000NFT_USDT,1000BONK_USDT,1000BTT_USDT,1000FLOKI_USDT,1000LUNC_USDT,1000PEPE_USDT,1000XEC_USDT,1INCH_USDT,AAVE_USDT,ACH_USDT,ADA_USDT,AGIX_USDT,AGLD_USDT,AKRO_USDT,ALGO_USDT,ALICE_USDT,ALPACA_USDT,ALPHA_USDT,AMB_USDT,ANKR_USDT,ANT_USDT,APE_USDT,API3_USDT,APT_USDT,ARB_USDT,ARKM_USDT,ARK_USDT,ARPA_USDT,AR_USDT,ASTR_USDT,ATA_USDT,ATOM_USDT,AUCTION_USDT,AUDIO_USDT,AVAX_USDT,AXS_USDT,BADGER_USDT,BAKE_USDT,BAL_USDT,BAND_USDT,BAT_USDT,BCH_USDT,BEL_USDT,BICO_USDT,BIGTIME_USDT,BLUR_USDT,BLZ_USDT,BNB_USDT,BNT_USDT,BNX_USDT,BOBA_USDT,BOND_USDT,BSV_USDT,BSW_USDT,BTC_USDT,BUSD_USDT,C98_USDT,CEEK_USDT,CELO_USDT,CELR_USDT,CFX_USDT,CHR_USDT,CHZ_USDT,CKB_USDT,COMBO_USDT,COMP_USDT,CORE_USDT,COTI_USDT,CRO_USDT,CRV_USDT,CTC_USDT,CTK_USDT,CTSI_USDT,CVC_USDT,CVX_USDT,CYBER_USDT,DAR_USDT,DASH_USDT,DENT_USDT,DGB_USDT,DODO_USDT,DOGE_USDT,DOT_USDT,DUSK_USDT,DYDX_USDT,EDU_USDT,EGLD_USDT,ENJ_USDT,ENS_USDT,EOS_USDT,ETC_USDT,ETH_USDT,ETHW_USDT,FET_USDT,FIL_USDT,FITFI_USDT,FLM_USDT,FLOW_USDT,FLR_USDT,FORTH_USDT,FRONT_USDT,FTM_USDT,FXS_USDT,GALA_USDT,GAL_USDT,GFT_USDT,GLMR_USDT,GLM_USDT,GMT_USDT,GMX_USDT,GPT_USDT,GRT_USDT,GTC_USDT,HBAR_USDT,HFT_USDT,HIFI_USDT,HIGH_USDT,HNT_USDT,HOOK_USDT,HOT_USDT,ICP_USDT,ICX_USDT,IDEX_USDT,ID_USDT,ILV_USDT,IMX_USDT,INJ_USDT,IOST_USDT,IOTA_USDT,IOTX_USDT,JASMY_USDT,JOE_USDT,JST_USDT,KAS_USDT,KAVA_USDT,KDA_USDT,KEY_USDT,KLAY_USDT,KNC_USDT,KSM_USDT,LDO_USDT,LEVER_USDT,LINA_USDT,LINK_USDT,LIT_USDT,LOOKS_USDT,LOOM_USDT,LPT_USDT,LQTY_USDT,LRC_USDT,LTC_USDT,LUNA2_USDT,MAGIC_USDT,MANA_USDT,MASK_USDT,MATIC_USDT,MAV_USDT,MC_USDT,MDT_USDT,MINA_USDT,MKR_USDT,MNT_USDT,MTL_USDT,MULTI_USDT,NEAR_USDT,NEO_USDT,NKN_USDT,NMR_USDT,NTRN_USDT,OCEAN_USDT,OGN_USDT,OG_USDT,OMG_USDT,ONE_USDT,ONT_USDT,OP_USDT,ORBS_USDT,ORDI_USDT,OXT_USDT,PAXG_USDT,PENDLE_USDT,PEOPLE_USDT,PERP_USDT,PHB_USDT,PROM_USDT,QNT_USDT,QTUM_USDT,RAD_USDT,RDNT_USDT,REEF_USDT,REN_USDT,REQ_USDT,RLC_USDT,RNDR_USDT,ROSE_USDT,RPL_USDT,RSR_USDT,RSS3_USDT,RUNE_USDT,RVN_USDT,SAND_USDT,SCRT_USDT,SC_USDT,SEI_USDT,SFP_USDT,SHIB1000_USDT,SKL_USDT,SLP_USDT,SNX_USDT,SOL_USDT,SPELL_USDT,SSV_USDT,STG_USDT,STMX_USDT,STORJ_USDT,STPT_USDT,STRAX_USDT,STX_USDT,SUI_USDT,SUN_USDT,SUSHI_USDT,SWEAT_USDT,SXP_USDT,THETA_USDT,TLM_USDT,TOMI_USDT,TOMO_USDT,TON_USDT,TRB_USDT,TRU_USDT,TRX_USDT,T_USDT,TWT_USDT,UMA_USDT,UNFI_USDT,UNI_USDT,USDC_USDT,VET_USDT,VGX_USDT,VRA_USDT,WAVES_USDT,WAXP_USDT,WLD_USDT,WOO_USDT,WSM_USDT,XCN_USDT,XEM_USDT,XLM_USDT,XMR_USDT,XNO_USDT,XRP_USDT,XTZ_USDT,XVG_USDT,XVS_USDT,YFII_USDT,YFI_USDT,YGG_USDT,ZEC_USDT,ZEN_USDT,ZIL_USDT,ZRX_USDT", - "requestFormat": { - "uppercase": true - }, - "configFormat": { - "uppercase": true, - "delimiter": "_" - } - }, - "options": { - "enabled": "BTC-28JUN24-70000-C,BTC-28JUN24-70000-P,BTC-28JUN24-60000-C,BTC-28JUN24-60000-P", - "available": "BTC-28JUN24-70000-C,BTC-28JUN24-70000-P,BTC-28JUN24-60000-C,BTC-28JUN24-60000-P,BTC-28JUN24-50000-C,BTC-28JUN24-50000-P,BTC-28JUN24-40000-C,BTC-28JUN24-40000-P,BTC-28JUN24-32000-C,BTC-28JUN24-32000-P,BTC-28JUN24-30000-C,BTC-28JUN24-30000-P,BTC-28JUN24-28000-C,BTC-28JUN24-28000-P,BTC-28JUN24-25000-C,BTC-28JUN24-25000-P,BTC-28JUN24-20000-C,BTC-28JUN24-20000-P,BTC-28JUN24-10000-C,BTC-28JUN24-10000-P,BTC-29MAR24-70000-C,BTC-29MAR24-70000-P,BTC-29MAR24-60000-C,BTC-29MAR24-60000-P,BTC-29MAR24-50000-C,BTC-29MAR24-50000-P,BTC-29MAR24-45000-C,BTC-29MAR24-45000-P,BTC-29MAR24-40000-C,BTC-29MAR24-40000-P,BTC-29MAR24-36000-C,BTC-29MAR24-36000-P,BTC-29MAR24-35000-C,BTC-29MAR24-35000-P,BTC-29MAR24-33000-C,BTC-29MAR24-33000-P,BTC-29MAR24-31000-C,BTC-29MAR24-31000-P,BTC-29MAR24-30000-C,BTC-29MAR24-30000-P,BTC-29MAR24-28000-C,BTC-29MAR24-28000-P,BTC-29MAR24-27000-C,BTC-29MAR24-27000-P,BTC-29MAR24-26000-C,BTC-29MAR24-26000-P,BTC-29MAR24-24000-C,BTC-29MAR24-24000-P,BTC-29MAR24-20000-C,BTC-29MAR24-20000-P,BTC-29MAR24-10000-C,BTC-29MAR24-10000-P,BTC-29DEC23-80000-C,BTC-29DEC23-80000-P,BTC-29DEC23-70000-C,BTC-29DEC23-70000-P,BTC-29DEC23-60000-C,BTC-29DEC23-60000-P,BTC-29DEC23-50000-C,BTC-29DEC23-50000-P,BTC-29DEC23-40000-C,BTC-29DEC23-40000-P,BTC-29DEC23-36000-C,BTC-29DEC23-36000-P,BTC-29DEC23-35000-C,BTC-29DEC23-35000-P,BTC-29DEC23-34000-C,BTC-29DEC23-34000-P,BTC-29DEC23-32000-C,BTC-29DEC23-32000-P,BTC-29DEC23-31500-C,BTC-29DEC23-31500-P,BTC-29DEC23-30500-C,BTC-29DEC23-30500-P,BTC-29DEC23-30000-C,BTC-29DEC23-30000-P,BTC-29DEC23-29500-C,BTC-29DEC23-29500-P,BTC-29DEC23-29000-C,BTC-29DEC23-29000-P,BTC-29DEC23-28000-C,BTC-29DEC23-28000-P,BTC-29DEC23-27500-C,BTC-29DEC23-27500-P,BTC-29DEC23-27000-C,BTC-29DEC23-27000-P,BTC-29DEC23-26000-C,BTC-29DEC23-26000-P,BTC-29DEC23-25000-C,BTC-29DEC23-25000-P,BTC-29DEC23-24000-C,BTC-29DEC23-24000-P,BTC-29DEC23-22000-C,BTC-29DEC23-22000-P,BTC-29DEC23-20000-C,BTC-29DEC23-20000-P,BTC-29DEC23-15000-C,BTC-29DEC23-15000-P,BTC-29DEC23-10000-C,BTC-29DEC23-10000-P,BTC-24NOV23-40000-C,BTC-24NOV23-40000-P,BTC-24NOV23-38000-C,BTC-24NOV23-38000-P,BTC-24NOV23-36000-C,BTC-24NOV23-36000-P,BTC-24NOV23-34000-C,BTC-24NOV23-34000-P,BTC-24NOV23-32000-C,BTC-24NOV23-32000-P,BTC-24NOV23-31500-C,BTC-24NOV23-31500-P,BTC-24NOV23-30500-C,BTC-24NOV23-30500-P,BTC-24NOV23-30000-C,BTC-24NOV23-30000-P,BTC-24NOV23-29500-C,BTC-24NOV23-29500-P,BTC-24NOV23-29000-C,BTC-24NOV23-29000-P,BTC-24NOV23-28500-C,BTC-24NOV23-28500-P,BTC-24NOV23-28000-C,BTC-24NOV23-28000-P,BTC-24NOV23-27500-C,BTC-24NOV23-27500-P,BTC-24NOV23-27000-C,BTC-24NOV23-27000-P,BTC-24NOV23-26500-C,BTC-24NOV23-26500-P,BTC-24NOV23-26000-C,BTC-24NOV23-26000-P,BTC-24NOV23-25500-C,BTC-24NOV23-25500-P,BTC-24NOV23-25000-C,BTC-24NOV23-25000-P,BTC-24NOV23-24000-C,BTC-24NOV23-24000-P,BTC-24NOV23-23000-C,BTC-24NOV23-23000-P,BTC-24NOV23-22000-C,BTC-24NOV23-22000-P,BTC-24NOV23-20000-C,BTC-24NOV23-20000-P,BTC-24NOV23-18000-C,BTC-24NOV23-18000-P,BTC-24NOV23-16000-C,BTC-24NOV23-16000-P,BTC-3NOV23-36000-C,BTC-3NOV23-36000-P,BTC-3NOV23-34000-C,BTC-3NOV23-34000-P,BTC-3NOV23-32000-C,BTC-3NOV23-32000-P,BTC-3NOV23-30000-C,BTC-3NOV23-30000-P,BTC-3NOV23-29000-C,BTC-3NOV23-29000-P,BTC-3NOV23-28500-C,BTC-3NOV23-28500-P,BTC-3NOV23-27500-C,BTC-3NOV23-27500-P,BTC-3NOV23-27000-C,BTC-3NOV23-27000-P,BTC-3NOV23-26500-C,BTC-3NOV23-26500-P,BTC-3NOV23-26000-C,BTC-3NOV23-26000-P,BTC-3NOV23-25000-C,BTC-3NOV23-25000-P,BTC-3NOV23-24000-C,BTC-3NOV23-24000-P,BTC-3NOV23-22000-C,BTC-3NOV23-22000-P,BTC-3NOV23-20000-C,BTC-3NOV23-20000-P,BTC-3NOV23-18000-C,BTC-3NOV23-18000-P,BTC-27OCT23-44000-C,BTC-27OCT23-44000-P,BTC-27OCT23-42000-C,BTC-27OCT23-42000-P,BTC-27OCT23-40000-C,BTC-27OCT23-40000-P,BTC-27OCT23-38000-C,BTC-27OCT23-38000-P,BTC-27OCT23-37000-C,BTC-27OCT23-37000-P,BTC-27OCT23-35000-C,BTC-27OCT23-35000-P,BTC-27OCT23-34500-C,BTC-27OCT23-34500-P,BTC-27OCT23-33500-C,BTC-27OCT23-33500-P,BTC-27OCT23-32500-C,BTC-27OCT23-32500-P,BTC-27OCT23-31500-C,BTC-27OCT23-31500-P,BTC-27OCT23-31000-C,BTC-27OCT23-31000-P,BTC-27OCT23-30500-C,BTC-27OCT23-30500-P,BTC-27OCT23-30000-C,BTC-27OCT23-30000-P,BTC-27OCT23-29500-C,BTC-27OCT23-29500-P,BTC-27OCT23-29000-C,BTC-27OCT23-29000-P,BTC-27OCT23-28750-C,BTC-27OCT23-28750-P,BTC-27OCT23-28500-C,BTC-27OCT23-28500-P,BTC-27OCT23-28250-C,BTC-27OCT23-28250-P,BTC-27OCT23-28000-C,BTC-27OCT23-28000-P,BTC-27OCT23-27750-C,BTC-27OCT23-27750-P,BTC-27OCT23-27500-C,BTC-27OCT23-27500-P,BTC-27OCT23-27250-C,BTC-27OCT23-27250-P,BTC-27OCT23-27000-C,BTC-27OCT23-27000-P,BTC-27OCT23-26500-C,BTC-27OCT23-26500-P,BTC-27OCT23-26000-C,BTC-27OCT23-26000-P,BTC-27OCT23-25500-C,BTC-27OCT23-25500-P,BTC-27OCT23-25000-C,BTC-27OCT23-25000-P,BTC-27OCT23-24000-C,BTC-27OCT23-24000-P,BTC-27OCT23-23000-C,BTC-27OCT23-23000-P,BTC-27OCT23-22000-C,BTC-27OCT23-22000-P,BTC-27OCT23-20000-C,BTC-27OCT23-20000-P,BTC-27OCT23-18000-C,BTC-27OCT23-18000-P,BTC-27OCT23-16000-C,BTC-27OCT23-16000-P,BTC-20OCT23-36000-C,BTC-20OCT23-36000-P,BTC-20OCT23-34000-C,BTC-20OCT23-34000-P,BTC-20OCT23-32000-C,BTC-20OCT23-32000-P,BTC-20OCT23-31000-C,BTC-20OCT23-31000-P,BTC-20OCT23-30500-C,BTC-20OCT23-30500-P,BTC-20OCT23-30000-C,BTC-20OCT23-30000-P,BTC-20OCT23-29500-C,BTC-20OCT23-29500-P,BTC-20OCT23-29000-C,BTC-20OCT23-29000-P,BTC-20OCT23-28750-C,BTC-20OCT23-28750-P,BTC-20OCT23-28500-C,BTC-20OCT23-28500-P,BTC-20OCT23-28250-C,BTC-20OCT23-28250-P,BTC-20OCT23-28000-C,BTC-20OCT23-28000-P,BTC-20OCT23-27750-C,BTC-20OCT23-27750-P,BTC-20OCT23-27500-C,BTC-20OCT23-27500-P,BTC-20OCT23-27250-C,BTC-20OCT23-27250-P,BTC-20OCT23-27000-C,BTC-20OCT23-27000-P,BTC-20OCT23-26750-C,BTC-20OCT23-26750-P,BTC-20OCT23-26500-C,BTC-20OCT23-26500-P,BTC-20OCT23-26250-C,BTC-20OCT23-26250-P,BTC-20OCT23-26000-C,BTC-20OCT23-26000-P,BTC-20OCT23-25750-C,BTC-20OCT23-25750-P,BTC-20OCT23-25500-C,BTC-20OCT23-25500-P,BTC-20OCT23-25000-C,BTC-20OCT23-25000-P,BTC-20OCT23-24000-C,BTC-20OCT23-24000-P,BTC-20OCT23-22000-C,BTC-20OCT23-22000-P,BTC-20OCT23-20000-C,BTC-20OCT23-20000-P,BTC-20OCT23-18000-C,BTC-20OCT23-18000-P,BTC-19OCT23-29750-C,BTC-19OCT23-29750-P,BTC-19OCT23-29500-C,BTC-19OCT23-29500-P,BTC-19OCT23-29250-C,BTC-19OCT23-29250-P,BTC-19OCT23-29000-C,BTC-19OCT23-29000-P,BTC-19OCT23-28750-C,BTC-19OCT23-28750-P,BTC-19OCT23-28500-C,BTC-19OCT23-28500-P,BTC-19OCT23-28250-C,BTC-19OCT23-28250-P,BTC-19OCT23-28000-C,BTC-19OCT23-28000-P,BTC-19OCT23-27750-C,BTC-19OCT23-27750-P,BTC-19OCT23-27500-C,BTC-19OCT23-27500-P,BTC-19OCT23-27250-C,BTC-19OCT23-27250-P,BTC-19OCT23-27000-C,BTC-19OCT23-27000-P,BTC-19OCT23-26750-C,BTC-19OCT23-26750-P,BTC-19OCT23-26500-C,BTC-19OCT23-26500-P,BTC-19OCT23-26250-C,BTC-19OCT23-26250-P,BTC-19OCT23-26000-C,BTC-19OCT23-26000-P,BTC-19OCT23-25750-C,BTC-19OCT23-25750-P,BTC-18OCT23-29500-C,BTC-18OCT23-29500-P,BTC-18OCT23-29250-C,BTC-18OCT23-29250-P,BTC-18OCT23-29000-C,BTC-18OCT23-29000-P,BTC-18OCT23-28750-C,BTC-18OCT23-28750-P,BTC-18OCT23-28500-C,BTC-18OCT23-28500-P,BTC-18OCT23-28250-C,BTC-18OCT23-28250-P,BTC-18OCT23-28000-C,BTC-18OCT23-28000-P,BTC-18OCT23-27750-C,BTC-18OCT23-27750-P,BTC-18OCT23-27500-C,BTC-18OCT23-27500-P,BTC-18OCT23-27250-C,BTC-18OCT23-27250-P,BTC-18OCT23-27000-C,BTC-18OCT23-27000-P,BTC-18OCT23-26750-C,BTC-18OCT23-26750-P,BTC-18OCT23-26500-C,BTC-18OCT23-26500-P,BTC-18OCT23-26250-C,BTC-18OCT23-26250-P,BTC-18OCT23-26000-C,BTC-18OCT23-26000-P,BTC-18OCT23-25750-C,BTC-18OCT23-25750-P,BTC-18OCT23-25500-C,BTC-18OCT23-25500-P,BTC-17OCT23-29250-C,BTC-17OCT23-29250-P,BTC-17OCT23-29000-C,BTC-17OCT23-29000-P,BTC-17OCT23-28750-C,BTC-17OCT23-28750-P,BTC-17OCT23-28500-C,BTC-17OCT23-28500-P,BTC-17OCT23-28250-C,BTC-17OCT23-28250-P,BTC-17OCT23-28000-C,BTC-17OCT23-28000-P,BTC-17OCT23-27750-C,BTC-17OCT23-27750-P,BTC-17OCT23-27500-C,BTC-17OCT23-27500-P,BTC-17OCT23-27250-C,BTC-17OCT23-27250-P,BTC-17OCT23-27000-C,BTC-17OCT23-27000-P,BTC-17OCT23-26750-C,BTC-17OCT23-26750-P,BTC-17OCT23-26500-C,BTC-17OCT23-26500-P,BTC-17OCT23-26250-C,BTC-17OCT23-26250-P,BTC-17OCT23-26000-C,BTC-17OCT23-26000-P,BTC-17OCT23-25750-C,BTC-17OCT23-25750-P,BTC-17OCT23-25500-C,BTC-17OCT23-25500-P", - "requestFormat": { - "uppercase": true, - "delimiter": "-" - }, - "configFormat": { - "uppercase": true, - "delimiter": "-" + "name": "Bithumb", + "enabled": true, + "verbose": false, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, + "baseCurrencies": "KRW", + "currencyPairs": { + "requestFormat": { + "uppercase": true, + "delimiter": "_" + }, + "configFormat": { + "uppercase": true, + "delimiter": "-" + }, + "useGlobalFormat": true, + "pairs": { + "spot": { + "assetEnabled": true, + "enabled": "USDT-KRW,QTUM-KRW,BTC-KRW,ETH-KRW,ETC-KRW,XRP-KRW,BCH-KRW,BTG-KRW,EOS-KRW", + "available": "AVAX-KRW,STRAX-KRW,KSM-KRW,RPL-KRW,ADA-KRW,ONT-KRW,EOS-KRW,STAT-KRW,APM-KRW,XPLA-KRW,STMX-KRW,FET-KRW,XVS-KRW,ROA-KRW,JOE-KRW,BNT-KRW,T-KRW,AUDIO-KRW,MIX-KRW,PUNDIX-KRW,USDC-KRW,ALGO-KRW,CTXC-KRW,IQ-KRW,RLY-KRW,GRT-KRW,NMR-KRW,FTM-KRW,WNCG-KRW,NCT-KRW,CSPR-KRW,TFUEL-KRW,EGG-KRW,MOC-KRW,BAT-KRW,ETC-KRW,TIA-KRW,GRACY-KRW,FRONT-KRW,DAI-KRW,ANKR-KRW,META-KRW,HOOK-KRW,BEL-KRW,MAGIC-KRW,ENTC-KRW,HUNT-KRW,STX-KRW,FIT-KRW,STEEM-KRW,CTSI-KRW,JUP-KRW,CAKE-KRW,DOGE-KRW,SUN-KRW,OCEAN-KRW,SOL-KRW,REQ-KRW,BNB-KRW,GAL-KRW,MBL-KRW,LRC-KRW,ILV-KRW,PEPE-KRW,IOST-KRW,XLM-KRW,CRV-KRW,NFT-KRW,PYR-KRW,TRX-KRW,TAVA-KRW,PYTH-KRW,TT-KRW,AAVE-KRW,KLAY-KRW,BAL-KRW,EVZ-KRW,FX-KRW,UMA-KRW,FLOW-KRW,ALEX-KRW,ELF-KRW,CVC-KRW,FLOKI-KRW,MASK-KRW,GAS-KRW,VIX-KRW,CELR-KRW,BLY-KRW,ARK-KRW,FNSA-KRW,OXT-KRW,VALOR-KRW,XTZ-KRW,HBAR-KRW,ONG-KRW,MTL-KRW,WAVES-KRW,ORBS-KRW,MANTA-KRW,ICX-KRW,SNX-KRW,API3-KRW,PENDLE-KRW,FLZ-KRW,APE-KRW,POWR-KRW,OGN-KRW,EDU-KRW,ARB-KRW,AXS-KRW,MBX-KRW,XRP-KRW,MATIC-KRW,USDT-KRW,1INCH-KRW,STORJ-KRW,UOS-KRW,RVN-KRW,LPT-KRW,OSMO-KRW,ALICE-KRW,LDO-KRW,TEMCO-KRW,COMP-KRW,VET-KRW,SFP-KRW,WIKEN-KRW,LBL-KRW,SHIB-KRW,GMT-KRW,AZIT-KRW,ZBCN-KRW,FLUX-KRW,ALT-KRW,AGI-KRW,SPURS-KRW,GRS-KRW,C98-KRW,ZIL-KRW,BCH-KRW,QTCON-KRW,SEI-KRW,GRND-KRW,SWAP-KRW,ETH-KRW,RSS3-KRW,STPT-KRW,FXS-KRW,SAND-KRW,MAP-KRW,MAV-KRW,LINK-KRW,MVC-KRW,QTUM-KRW,DAR-KRW,FANC-KRW,HIGH-KRW,ARKM-KRW,MANA-KRW,SUSHI-KRW,DVI-KRW,XEC-KRW,BTC-KRW,EL-KRW,THETA-KRW,CELO-KRW,KNC-KRW,POLA-KRW,LOOM-KRW,JASMY-KRW,INJ-KRW,KAVA-KRW,NEO-KRW,BIGTIME-KRW,MINA-KRW,NPT-KRW,IMX-KRW,ASM-KRW,FCT2-KRW,RLC-KRW,HIFI-KRW,CTC-KRW,DYDX-KRW,ZTX-KRW,AGIX-KRW,WEMIX-KRW,GTC-KRW,LM-KRW,OP-KRW,ONIT-KRW,ACS-KRW,LSK-KRW,REI-KRW,ATOM-KRW,WLD-KRW,GLM-KRW,COS-KRW,BTT-KRW,BFC-KRW,ACE-KRW,SC-KRW,BORA-KRW,GHX-KRW,ADP-KRW,STRK-KRW,LEVER-KRW,BOBA-KRW,BOA-KRW,HFT-KRW,RNDR-KRW,ENJ-KRW,RSR-KRW,XPR-KRW,IOTX-KRW,CYBER-KRW,WAXP-KRW,OBSR-KRW,MEV-KRW,UNI-KRW,APT-KRW,DAO-KRW,WAXL-KRW,SIX-KRW,GMX-KRW,RDNT-KRW,BTG-KRW,MNT-KRW,BLUR-KRW,XCN-KRW,YGG-KRW,MXC-KRW,ACH-KRW,RAD-KRW,MLK-KRW,DOT-KRW,JST-KRW,ZRX-KRW,STG-KRW,SOFI-KRW,WOM-KRW,TDROP-KRW,SNT-KRW,COTI-KRW,WOO-KRW,OAS-KRW,CRO-KRW,AQT-KRW,EGLD-KRW,ARPA-KRW,BSV-KRW,ASTR-KRW,AMO-KRW,AERGO-KRW,ID-KRW,SUI-KRW,GALA-KRW,CKB-KRW,BIOT-KRW,CFX-KRW,CHR-KRW,FLR-KRW,FITFI-KRW,YFI-KRW,CTK-KRW,W-KRW,MED-KRW,MKR-KRW,SXP-KRW,HIVE-KRW,CRTS-KRW,CHZ-KRW" + } + } + }, + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret", + "clientID": "ClientID" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "tickerBatching": true, + "autoPairUpdates": true + }, + "websocketAPI": false, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": false + } + }, + "bankAccounts": [ + { + "enabled": false, + "bankName": "", + "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ], + "orderbook": { + "verificationBypass": false, + "websocketBufferLimit": 5, + "websocketBufferEnabled": false, + "publishPeriod": 10000000000 } - } - } - }, - "api": { - "authenticatedSupport": false, - "authenticatedWebsocketApiSupport": false, - "endpoints": { - "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" - }, - "credentials": { - "key": "Key", - "secret": "Secret" }, - "credentialsValidator": { - "requiresKey": true, - "requiresSecret": true - } - }, - "features": { - "supports": { - "restAPI": true, - "restCapabilities": { - "tickerBatching": true, - "autoPairUpdates": true - }, - "websocketAPI": true, - "websocketCapabilities": {} - }, - "enabled": { - "autoPairUpdates": true, - "websocketAPI": true - } - }, - "bankAccounts": [ { - "enabled": false, - "bankName": "", - "bankAddress": "", - "bankPostalCode": "", - "bankPostalCity": "", - "bankCountry": "", - "accountName": "", - "accountNumber": "", - "swiftCode": "", - "iban": "", - "supportedCurrencies": "" - } - ] - }, - { - "name": "COINUT", - "enabled": true, - "verbose": false, - "httpTimeout": 15000000000, - "websocketResponseCheckTimeout": 30000000, - "websocketResponseMaxLimit": 7000000000, - "websocketTrafficTimeout": 30000000000, - "websocketOrderbookBufferLimit": 5, - "baseCurrencies": "USD", - "currencyPairs": { - "requestFormat": { - "uppercase": true - }, - "configFormat": { - "uppercase": true, - "delimiter": "-" - }, - "useGlobalFormat": true, - "assetTypes": [ - "spot" - ], - "pairs": { - "spot": { - "enabled": "LTC-USDT", - "available": "LTC-CAD,LTC-SGD,USDT-USD,ETC-LTC,LTC-BTC,USDT-SGD,XMR-USDT,ZEC-SGD,ETH-USD,BTC-USDT,ETC-BTC,ETH-LTC,LTC-USD,BTC-USD,ETH-USDT,XMR-LTC,ZEC-USD,ETC-SGD,DAI-SGD,ZEC-CAD,BTC-SGD,ETH-BTC,ETH-SGD,LTC-USDT,ZEC-BTC,ZEC-USDT,BTC-CAD,XMR-BTC,ZEC-LTC,ETC-USDT,ETH-CAD" - } - } - }, - "api": { - "authenticatedSupport": false, - "authenticatedWebsocketApiSupport": false, - "endpoints": { - "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" - }, - "credentials": { - "key": "Key", - "secret": "Secret", - "clientID": "ClientID" - }, - "credentialsValidator": { - "requiresKey": true, - "requiresClientID": true - } - }, - "features": { - "supports": { - "restAPI": true, - "restCapabilities": { - "autoPairUpdates": true - }, - "websocketAPI": true, - "websocketCapabilities": {} + "name": "Bitmex", + "enabled": true, + "verbose": false, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, + "websocketOrderbookBufferLimit": 5, + "baseCurrencies": "USD", + "currencyPairs": { + "assetTypes": [ + "perpetualcontract", + "futures", + "downsideprofitcontract", + "upsideprofitcontract" + ], + "pairs": { + "downsideprofitcontract": { + "enabled": "XBT7D_D95", + "available": "XBT7D_D95", + "requestFormat": { + "uppercase": true, + "delimiter": "_" + }, + "configFormat": { + "uppercase": true, + "delimiter": "_" + } + }, + "futures": { + "enabled": "BCHZ19", + "available": "XRPZ19,BCHZ19,ADAZ19,EOSZ19,TRXZ19,XBTZ19,ETHZ19,LTCZ19", + "requestFormat": { + "uppercase": true + }, + "configFormat": { + "uppercase": true + } + }, + "perpetualcontract": { + "enabled": "ETHUSD", + "available": "XBTUSD,ETHUSD", + "requestFormat": { + "uppercase": true + }, + "configFormat": { + "uppercase": true + } + }, + "upsideprofitcontract": { + "enabled": "XBT7D_U105", + "available": "XBT7D_U105", + "requestFormat": { + "uppercase": true, + "delimiter": "_" + }, + "configFormat": { + "uppercase": true, + "delimiter": "_" + } + } + } + }, + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "tickerBatching": true, + "autoPairUpdates": true + }, + "websocketAPI": true, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": false + } + }, + "bankAccounts": [ + { + "enabled": false, + "bankName": "", + "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ] }, - "enabled": { - "autoPairUpdates": true, - "websocketAPI": false - } - }, - "bankAccounts": [ { - "enabled": false, - "bankName": "", - "bankAddress": "", - "bankPostalCode": "", - "bankPostalCity": "", - "bankCountry": "", - "accountName": "", - "accountNumber": "", - "swiftCode": "", - "iban": "", - "supportedCurrencies": "" - } - ] - }, - { - "name": "CoinbasePro", - "enabled": true, - "verbose": false, - "httpTimeout": 15000000000, - "websocketResponseCheckTimeout": 30000000, - "websocketResponseMaxLimit": 7000000000, - "websocketTrafficTimeout": 30000000000, - "websocketOrderbookBufferLimit": 5, - "baseCurrencies": "USD,GBP,EUR", - "currencyPairs": { - "requestFormat": { - "uppercase": true, - "delimiter": "-" - }, - "configFormat": { - "uppercase": true, - "delimiter": "-" - }, - "useGlobalFormat": true, - "assetTypes": [ - "spot", - "futures" - ], - "pairs": { - "spot": { - "enabled": "BTC-USD", - "available": "LTC-GBP,XLM-BTC,DASH-BTC,DAI-USDC,ZEC-USDC,XLM-EUR,ZRX-BTC,LTC-BTC,ETC-BTC,ETH-USD,XRP-EUR,BTC-USDC,REP-USD,EOS-BTC,ZEC-BTC,ETC-GBP,LINK-ETH,XRP-BTC,ZRX-USD,ETH-USDC,MANA-USDC,BTC-EUR,BCH-GBP,DNT-USDC,EOS-EUR,BCH-EUR,LTC-EUR,CVC-USDC,ETH-GBP,DASH-USD,ETH-EUR,XTZ-BTC,ZRX-EUR,BAT-ETH,BTC-GBP,ETC-USD,BAT-USDC,BCH-USD,GNT-USDC,ALGO-USD,LINK-USD,XLM-USD,ETH-BTC,EOS-USD,REP-BTC,ETH-DAI,XRP-USD,LTC-USD,ETC-EUR,BTC-USD,XTZ-USD,BCH-BTC,LOOM-USDC" - }, - "futures": { - "enabled": "BTC-PERP-INTX", - "available": "BTC-PERP-INTX" - } - } - }, - "api": { - "authenticatedSupport": false, - "authenticatedWebsocketApiSupport": false, - "endpoints": { - "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" - }, - "credentials": { - "key": "Key", - "secret": "Secret", - "clientID": "ClientID" - }, - "credentialsValidator": { - "requiresKey": true, - "requiresSecret": true, - "requiresClientID": true, - "requiresBase64DecodeSecret": true - } - }, - "features": { - "supports": { - "restAPI": true, - "restCapabilities": { - "autoPairUpdates": true - }, - "websocketAPI": true, - "websocketCapabilities": {} + "name": "Bitstamp", + "enabled": true, + "verbose": false, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, + "websocketOrderbookBufferLimit": 5, + "baseCurrencies": "USD,EUR", + "currencyPairs": { + "requestFormat": { + "uppercase": true + }, + "configFormat": { + "uppercase": true + }, + "useGlobalFormat": true, + "assetTypes": [ + "spot" + ], + "pairs": { + "spot": { + "enabled": "BTCUSD,BTCEUR,EURUSD,XRPUSD,XRPEUR", + "available": "LTCUSD,ETHUSD,XRPEUR,BCHUSD,BCHEUR,BTCEUR,XRPBTC,EURUSD,BCHBTC,LTCEUR,BTCUSD,LTCBTC,XRPUSD,ETHBTC,ETHEUR" + } + } + }, + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret", + "clientID": "ClientID" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true, + "requiresClientID": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "autoPairUpdates": true + }, + "websocketAPI": true, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": false + } + }, + "bankAccounts": [ + { + "enabled": false, + "bankName": "", + "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ] }, - "enabled": { - "autoPairUpdates": true, - "websocketAPI": false - } - }, - "bankAccounts": [ { - "enabled": false, - "bankName": "", - "bankAddress": "", - "bankPostalCode": "", - "bankPostalCity": "", - "bankCountry": "", - "accountName": "", - "accountNumber": "", - "swiftCode": "", - "iban": "", - "supportedCurrencies": "" - } - ] - }, - { - "name": "EXMO", - "enabled": true, - "verbose": false, - "httpTimeout": 15000000000, - "websocketResponseCheckTimeout": 30000000, - "websocketResponseMaxLimit": 7000000000, - "websocketTrafficTimeout": 30000000000, - "websocketOrderbookBufferLimit": 5, - "baseCurrencies": "USD,EUR,RUB,PLN,UAH", - "currencyPairs": { - "requestFormat": { - "uppercase": true, - "delimiter": "_", - "separator": "," - }, - "configFormat": { - "uppercase": true, - "delimiter": "_" - }, - "useGlobalFormat": true, - "assetTypes": [ - "spot" - ], - "pairs": { - "spot": { - "enabled": "BTC_USD,LTC_USD", - "available": "BCH_RUB,DASH_RUB,EOS_USD,ETH_TRY,GNT_ETH,LTC_USD,PTI_USDT,XRP_BTC,EXM_BTC,BTG_BTC,ETC_RUB,BTG_USD,NEO_RUB,XMR_BTC,ZRX_ETH,MNX_BTC,USDC_BTC,XRP_EUR,SMART_USD,EOS_BTC,MNX_ETH,ZEC_BTC,BCH_USD,WAVES_USD,TRX_BTC,XRP_TRY,DASH_USD,DOGE_USD,ETZ_USDT,GUSD_USD,MNC_BTC,ZEC_USD,DCR_BTC,DXT_USD,PTI_RUB,XMR_ETH,ZRX_USD,DAI_RUB,MNC_USD,XLM_TRY,DAI_BTC,BTC_EUR,LTC_EUR,OMG_BTC,PTI_EOS,SMART_RUB,XTZ_USD,HP_EXM,ADA_USD,OMG_ETH,QTUM_USD,TRX_RUB,USDC_ETH,USDC_USDT,USD_RUB,BTC_UAH,BCH_USDT,ETH_PLN,KICK_RUB,LSK_RUB,SMART_BTC,XMR_UAH,XRP_USD,GUSD_BTC,QTUM_ETH,USDT_EUR,BTC_RUB,DCR_UAH,ETH_RUB,DOGE_BTC,ETZ_BTC,INK_USD,LTC_UAH,BTT_UAH,BTC_USDT,MNC_ETH,XTZ_ETH,BTC_TRY,DXT_BTC,KICK_USDT,OMG_USD,WAVES_BTC,XLM_BTC,BTCZ_BTC,GNT_BTC,LSK_BTC,LTC_RUB,NEO_BTC,XEM_UAH,XMR_USD,ZAG_BTC,GAS_USD,LTC_BTC,TRX_UAH,XEM_EUR,XMR_RUB,XTZ_RUB,ETZ_ETH,ETC_BTC,GUSD_RUB,INK_BTC,LSK_USD,MNX_USD,SMART_EUR,VLX_BTC,BCH_ETH,XMR_EUR,ADA_ETH,QTUM_BTC,XEM_USD,ATMCASH_BTC,ADA_BTC,ETH_EUR,TRX_USD,USDC_USD,BCH_BTC,ETH_UAH,KICK_BTC,WAVES_RUB,XEM_BTC,ETH_BTC,BCH_EUR,BTT_BTC,ROOBEE_BTC,XLM_USD,XRP_ETH,ETH_USD,MKR_DAI,XTZ_BTC,DAI_USD,BCH_UAH,INK_ETH,KICK_ETH,MKR_BTC,NEO_USD,XRP_USDT,ZEC_EUR,BTC_USD,XRP_RUB,EOS_EUR,ETH_USDT,USDT_UAH,XRP_UAH,ZEC_RUB,HP_BTC,BTT_RUB,DAI_ETH,DASH_UAH,DASH_USDT,ETH_LTC,GAS_BTC,USDT_USD,BTG_ETH,XLM_RUB,WAVES_ETH,USDT_RUB,ZRX_BTC,DASH_BTC,DCR_RUB,ETC_USD,HB_BTC,PTI_BTC,BTC_PLN" + "name": "Bybit", + "enabled": true, + "verbose": false, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, + "websocketOrderbookBufferLimit": 5, + "baseCurrencies": "USD", + "currencyPairs": { + "assetTypes": [ + "spot", + "margin", + "coinmarginedfutures", + "usdtmarginedfutures", + "usdcmarginedfutures", + "options" + ], + "pairs": { + "spot": { + "enabled": "BTC_USDT,ETH_USDT,XRP_USDT,EOS_USDT,ETH_BTC,XRP_BTC,DOT_USDT,XLM_USDT,LTC_USDT", + "available": "BTC_USDT,ETH_USDT,XRP_USDT,EOS_USDT,ETH_BTC,XRP_BTC,DOT_USDT,XLM_USDT,LTC_USDT,DOGE_USDT,CHZ_USDT,AXS_USDT,MANA_USDT,DYDX_USDT,MKR_USDT,COMP_USDT,AAVE_USDT,YFI_USDT,LINK_USDT,SUSHI_USDT,UNI_USDT,KSM_USDT,ICP_USDT,ADA_USDT,ETC_USDT,KLAY_USDT,XTZ_USDT,BCH_USDT,SRM_USDT,QNT_USDT,USDC_USDT,GRT_USDT,SOL_USDT,FIL_USDT,OMG_USDT,TRIBE_USDT,BAT_USDT,ZRX_USDT,CRV_USDT,AGLD_USDT,ANKR_USDT,PERP_USDT,MATIC_USDT,WAVES_USDT,LUNC_USDT,SPELL_USDT,SHIB_USDT,FTM_USDT,ATOM_USDT,ALGO_USDT,ENJ_USDT,CBX_USDT,SAND_USDT,AVAX_USDT,WOO_USDT,FTT_USDT,GODS_USDT,IMX_USDT,ENS_USDT,GM_USDT,CWAR_USDT,CAKE_USDT,STETH_USDT,GALFT_USDT,LFW_USDT,SLP_USDT,C98_USDT,PSP_USDT,GENE_USDT,AVA_USDT,ONE_USDT,PTU_USDT,SHILL_USDT,XYM_USDT,BOBA_USDT,JASMY_USDT,GALA_USDT,RNDR_USDT,TRVL_USDT,WEMIX_USDT,XEM_USDT,BICO_USDT,CEL_USDT,UMA_USDT,HOT_USDT,NEXO_USDT,BNT_USDT,SNX_USDT,REN_USDT,1INCH_USDT,TEL_USDT,SIS_USDT,LRC_USDT,LDO_USDT,REAL_USDT,KRL_USDT,DEVT_USDT,ETH_USDC,BTC_USDC,1SOL_USDT,PLT_USDT,IZI_USDT,QTUM_USDT,DCR_USDT,ZEN_USDT,THETA_USDT,MX_USDT,DGB_USDT,RVN_USDT,EGLD_USDT,RUNE_USDT,XLM_BTC,XLM_USDC,SOL_USDC,XRP_USDC,ALGO_BTC,SOL_BTC,RAIN_USDT,XEC_USDT,ICX_USDT,XDC_USDT,HNT_USDT,BTG_USDT,ZIL_USDT,HBAR_USDT,FLOW_USDT,SOS_USDT,KASTA_USDT,STX_USDT,SIDUS_USDT,VPAD_USDT,GGM_USDT,LOOKS_USDT,MBS_USDT,DAI_USDT,BUSD_USDT,ACA_USDT,MV_USDT,MIX_USDT,LTC_USDC,MANA_BTC,MATIC_BTC,LTC_BTC,DOT_BTC,SAND_BTC,MANA_USDC,MATIC_USDC,SAND_USDC,DOT_USDC,LUNC_USDC,RSS3_USDT,SYNR_USDT,TAP_USDT,ERTHA_USDT,GMX_USDT,T_USDT,ACH_USDT,JST_USDT,SUN_USDT,BTT_USDT,TRX_USDT,NFT_USDT,POKT_USDT,SCRT_USDT,PSTAKE_USDT,SON_USDT,HERO_USDT,DOME_USDT,USTC_USDT,BNB_USDT,NEAR_USDT,PAXG_USDT,SD_USDT,APE_USDT,BTC3S_USDT,BTC3L_USDT,FIDA_USDT,MINA_USDT,SC_USDT,RACA_USDT,CAPS_USDT,STG_USDT,GLMR_USDT,MOVR_USDT,ZAM_USDT,ETH_DAI,BTC_DAI,WBTC_USDT,XAVA_USDT,MELOS_USDT,GMT_USDT,GST_USDT,CELO_USDT,SFUND_USDT,ELT_USDT,LGX_USDT,APEX_USDT,CTC_USDT,COT_USDT,KMON_USDT,PLY_USDT,XWG_USDT,FITFI_USDT,STRM_USDT,GAL_USDT,ETH3S_USDT,ETH3L_USDT,KOK_USDT,FAME_USDT,XRP3S_USDT,XRP3L_USDT,USDD_USDT,OP_USDT,LUNA_USDT,DFI_USDT,MOVEZ_USDT,THN_USDT,DOT3S_USDT,DOT3L_USDT,VINU_USDT,BEL_USDT,FORT_USDT,AVAX2S_USDT,AVAX2L_USDT,ADA2S_USDT,ADA2L_USDT,WLKN_USDT,KON_USDT,LTC2S_USDT,LTC2L_USDT,SAND2S_USDT,SAND2L_USDT,OBX_USDT,SEOR_USDT,MNZ_USDT,CULT_USDT,DOGE_USDC,EOS_USDC,CUSD_USDT,SLG_USDT,CMP_USDT,KUNCI_USDT,GSTS_USDT,XETA_USDT,AZY_USDT,MMC_USDT,FLOKI_USDT,BABYDOGE_USDT,STAT_USDT,SAITAMA_USDT,MATIC2S_USDT,MATIC2L_USDT,ETC2S_USDT,ETC2L_USDT,DICE_USDT,WAXP_USDT,AR_USDT,KDA_USDT,ROSE_USDT,SLG_USDC,APE2S_USDT,APE2L_USDT,GMT2S_USDT,GMT2L_USDT,DEFY_USDT,PSG_USDT,BAR_USDT,JUV_USDT,ACM_USDT,INTER_USDT,AFC_USDT,CITY_USDT,LINK2L_USDT,LINK2S_USDT,FTM2L_USDT,FTM2S_USDT,SOLO_USDT,W_BTC,AVAX_USDC,ADA_USDC,OP_USDC,DOGE2S_USDT,DOGE2L_USDT,ATOM2S_USDT,ATOM2L_USDT,APEX_USDC,TRX_USDC,ICP_USDC,LINK_USDC,GMT_USDC,CHZ_USDC,SHIB_USDC,LDO_USDC,APE_USDC,FIL_USDC,CHRP_USDT,EOS2S_USDT,EOS2L_USDT,WWY_USDT,LING_USDT,SWEAT_USDT,DLC_USDT,OKG_USDT,ETHW_USDT,INJ_USDT,MPLX_USDT,MIBR_USDT,CO_USDT,AGLA_USDT,ROND_USDT,QMALL_USDT,PUMLX_USDT,GCAKE_USDT,APT_USDT,APT_USDC,USDT_EUR,MTK_USDT,MCRT_USDT,MASK_USDT,ECOX_USDT,HFT_USDC,HFT_USDT,KCAL_USDT,PEOPLE_USDT,TWT_USDT,ORT_USDT,HOOK_USDT,PRIMAL_USDT,MCT_USDT,OAS_USDT,MAGIC_USDT,MEE_USDT,TON_USDT,BONK_USDT,FLR_USDT,TIME_USDT,3P_USDT,RPL_USDT,SSV_USDT,FXS_USDT,CORE_USDT,RDNT_USDT,BLUR_USDT,LIS_USDT,AGIX_USDT,MDAO_USDT,ACS_USDT,HVH_USDT,GNS_USDT,DPX_USDT,PIP_USDT,PRIME_USDT,EVER_USDT,VRA_USDT,GPT_USDT,FB_USDT,DZOO_USDT,ID_USDT,ARB_USDC,ARB_USDT,XCAD_USDT,MBX_USDT,AXL_USDT,CGPT_USDT,PLAY_USDT,AGI_USDT,RLTM_USDT,SUI_USDT,SUI_USDC,TAMA_USDT,MVL_USDT,PEPE_USDT,LADYS_USDT,LMWR_USDT,BOB_USDT,TOMI_USDT,KARATE_USDT,SUIA_USDT,TURBOS_USDT,FMB_USDT,CAPO_USDT,TENET_USDT,VELO_USDT,ELDA_USDT,CANDY_USDT,FON_USDT,OMN_USDT,TOMS_USDT,MTC_USDT,VELA_USDT,USDT_BRZ,BTC_BRZ,PENDLE_USDT,EGO_USDT,PEPE2_USDT,NYM_USDT,MNT_USDT,MNT_USDC,MNT_BTC,GSWIFT_USDT,SALD_USDT,ARKM_USDT,NEON_USDT,WLD_USDC,WLD_USDT,PLANET_USDT,DSRUN_USDT,SPARTA_USDT,TAVA_USDT,SEILOR_USDT,SEI_USDT,CYBER_USDT,ORDI_USDT,KAVA_USDT,VV_USDT,SAIL_USDT,PYUSD_USDT,SOL_EUR,USDC_EUR,ADA_EUR,DOGE_EUR,LTC_EUR,XRP_EUR,ETH_EUR,BTC_EUR,VEXT_USDT,CTT_USDT,NEXT_USDT,KAS_USDT,NESS_USDT,CAT_USDT,FET_USDT,LEVER_USDT,VEGA_USDT,ZTX_USDT", + "requestFormat": { + "uppercase": true + }, + "configFormat": { + "uppercase": true, + "delimiter": "_" + } + }, + "coinmarginedfutures": { + "enabled": "ADA_USD,BTC_USD,BTC_USDH24,BTC_USDZ23,DOT_USD", + "available": "ADA_USD,BTC_USD,BTC_USDH24,BTC_USDZ23,DOT_USD,EOS_USD,ETH_USD,ETH_USDH24,ETH_USDZ23,LTC_USD,MAN_AUSD,XRP_USD", + "requestFormat": { + "uppercase": true + }, + "configFormat": { + "uppercase": true, + "delimiter": "_" + } + }, + "usdcmarginedfutures": { + "enabled": "ETH-PERP,BNB-PERP,SOL-PERP,BTC-PERP", + "available": "BNB-PERP,BTC-03NOV23,BTC-20OCT23,BTC-24NOV23,BTC-27OCT23,BTC-28JUN24,BTC-29DEC23,BTC-29MAR24,BTC-PERP,ETC-PERP,ETH-03NOV23,ETH-20OCT23,ETH-24NOV23,ETH-27OCT23,ETH-28JUN24,ETH-29DEC23,ETH-29MAR24,ETH-PERP,MAT-ICPERP,OPP-ERP,SOL-PERP,XRP-PERP", + "requestFormat": { + "uppercase": true + }, + "configFormat": { + "uppercase": true, + "delimiter": "-" + } + }, + "usdtmarginedfutures": { + "enabled": "BTC_USDT,10000LADYS_USDT,IOTA_USDT,AAVE_USDT", + "available": "10000LADYS_USDT,10000NFT_USDT,1000BONK_USDT,1000BTT_USDT,1000FLOKI_USDT,1000LUNC_USDT,1000PEPE_USDT,1000XEC_USDT,1INCH_USDT,AAVE_USDT,ACH_USDT,ADA_USDT,AGIX_USDT,AGLD_USDT,AKRO_USDT,ALGO_USDT,ALICE_USDT,ALPACA_USDT,ALPHA_USDT,AMB_USDT,ANKR_USDT,ANT_USDT,APE_USDT,API3_USDT,APT_USDT,ARB_USDT,ARKM_USDT,ARK_USDT,ARPA_USDT,AR_USDT,ASTR_USDT,ATA_USDT,ATOM_USDT,AUCTION_USDT,AUDIO_USDT,AVAX_USDT,AXS_USDT,BADGER_USDT,BAKE_USDT,BAL_USDT,BAND_USDT,BAT_USDT,BCH_USDT,BEL_USDT,BICO_USDT,BIGTIME_USDT,BLUR_USDT,BLZ_USDT,BNB_USDT,BNT_USDT,BNX_USDT,BOBA_USDT,BOND_USDT,BSV_USDT,BSW_USDT,BTC_USDT,BUSD_USDT,C98_USDT,CEEK_USDT,CELO_USDT,CELR_USDT,CFX_USDT,CHR_USDT,CHZ_USDT,CKB_USDT,COMBO_USDT,COMP_USDT,CORE_USDT,COTI_USDT,CRO_USDT,CRV_USDT,CTC_USDT,CTK_USDT,CTSI_USDT,CVC_USDT,CVX_USDT,CYBER_USDT,DAR_USDT,DASH_USDT,DENT_USDT,DGB_USDT,DODO_USDT,DOGE_USDT,DOT_USDT,DUSK_USDT,DYDX_USDT,EDU_USDT,EGLD_USDT,ENJ_USDT,ENS_USDT,EOS_USDT,ETC_USDT,ETH_USDT,ETHW_USDT,FET_USDT,FIL_USDT,FITFI_USDT,FLM_USDT,FLOW_USDT,FLR_USDT,FORTH_USDT,FRONT_USDT,FTM_USDT,FXS_USDT,GALA_USDT,GAL_USDT,GFT_USDT,GLMR_USDT,GLM_USDT,GMT_USDT,GMX_USDT,GPT_USDT,GRT_USDT,GTC_USDT,HBAR_USDT,HFT_USDT,HIFI_USDT,HIGH_USDT,HNT_USDT,HOOK_USDT,HOT_USDT,ICP_USDT,ICX_USDT,IDEX_USDT,ID_USDT,ILV_USDT,IMX_USDT,INJ_USDT,IOST_USDT,IOTA_USDT,IOTX_USDT,JASMY_USDT,JOE_USDT,JST_USDT,KAS_USDT,KAVA_USDT,KDA_USDT,KEY_USDT,KLAY_USDT,KNC_USDT,KSM_USDT,LDO_USDT,LEVER_USDT,LINA_USDT,LINK_USDT,LIT_USDT,LOOKS_USDT,LOOM_USDT,LPT_USDT,LQTY_USDT,LRC_USDT,LTC_USDT,LUNA2_USDT,MAGIC_USDT,MANA_USDT,MASK_USDT,MATIC_USDT,MAV_USDT,MC_USDT,MDT_USDT,MINA_USDT,MKR_USDT,MNT_USDT,MTL_USDT,MULTI_USDT,NEAR_USDT,NEO_USDT,NKN_USDT,NMR_USDT,NTRN_USDT,OCEAN_USDT,OGN_USDT,OG_USDT,OMG_USDT,ONE_USDT,ONT_USDT,OP_USDT,ORBS_USDT,ORDI_USDT,OXT_USDT,PAXG_USDT,PENDLE_USDT,PEOPLE_USDT,PERP_USDT,PHB_USDT,PROM_USDT,QNT_USDT,QTUM_USDT,RAD_USDT,RDNT_USDT,REEF_USDT,REN_USDT,REQ_USDT,RLC_USDT,RNDR_USDT,ROSE_USDT,RPL_USDT,RSR_USDT,RSS3_USDT,RUNE_USDT,RVN_USDT,SAND_USDT,SCRT_USDT,SC_USDT,SEI_USDT,SFP_USDT,SHIB1000_USDT,SKL_USDT,SLP_USDT,SNX_USDT,SOL_USDT,SPELL_USDT,SSV_USDT,STG_USDT,STMX_USDT,STORJ_USDT,STPT_USDT,STRAX_USDT,STX_USDT,SUI_USDT,SUN_USDT,SUSHI_USDT,SWEAT_USDT,SXP_USDT,THETA_USDT,TLM_USDT,TOMI_USDT,TOMO_USDT,TON_USDT,TRB_USDT,TRU_USDT,TRX_USDT,T_USDT,TWT_USDT,UMA_USDT,UNFI_USDT,UNI_USDT,USDC_USDT,VET_USDT,VGX_USDT,VRA_USDT,WAVES_USDT,WAXP_USDT,WLD_USDT,WOO_USDT,WSM_USDT,XCN_USDT,XEM_USDT,XLM_USDT,XMR_USDT,XNO_USDT,XRP_USDT,XTZ_USDT,XVG_USDT,XVS_USDT,YFII_USDT,YFI_USDT,YGG_USDT,ZEC_USDT,ZEN_USDT,ZIL_USDT,ZRX_USDT", + "requestFormat": { + "uppercase": true + }, + "configFormat": { + "uppercase": true, + "delimiter": "_" + } + }, + "options": { + "enabled": "BTC-28JUN24-70000-C,BTC-28JUN24-70000-P,BTC-28JUN24-60000-C,BTC-28JUN24-60000-P", + "available": "BTC-28JUN24-70000-C,BTC-28JUN24-70000-P,BTC-28JUN24-60000-C,BTC-28JUN24-60000-P,BTC-28JUN24-50000-C,BTC-28JUN24-50000-P,BTC-28JUN24-40000-C,BTC-28JUN24-40000-P,BTC-28JUN24-32000-C,BTC-28JUN24-32000-P,BTC-28JUN24-30000-C,BTC-28JUN24-30000-P,BTC-28JUN24-28000-C,BTC-28JUN24-28000-P,BTC-28JUN24-25000-C,BTC-28JUN24-25000-P,BTC-28JUN24-20000-C,BTC-28JUN24-20000-P,BTC-28JUN24-10000-C,BTC-28JUN24-10000-P,BTC-29MAR24-70000-C,BTC-29MAR24-70000-P,BTC-29MAR24-60000-C,BTC-29MAR24-60000-P,BTC-29MAR24-50000-C,BTC-29MAR24-50000-P,BTC-29MAR24-45000-C,BTC-29MAR24-45000-P,BTC-29MAR24-40000-C,BTC-29MAR24-40000-P,BTC-29MAR24-36000-C,BTC-29MAR24-36000-P,BTC-29MAR24-35000-C,BTC-29MAR24-35000-P,BTC-29MAR24-33000-C,BTC-29MAR24-33000-P,BTC-29MAR24-31000-C,BTC-29MAR24-31000-P,BTC-29MAR24-30000-C,BTC-29MAR24-30000-P,BTC-29MAR24-28000-C,BTC-29MAR24-28000-P,BTC-29MAR24-27000-C,BTC-29MAR24-27000-P,BTC-29MAR24-26000-C,BTC-29MAR24-26000-P,BTC-29MAR24-24000-C,BTC-29MAR24-24000-P,BTC-29MAR24-20000-C,BTC-29MAR24-20000-P,BTC-29MAR24-10000-C,BTC-29MAR24-10000-P,BTC-29DEC23-80000-C,BTC-29DEC23-80000-P,BTC-29DEC23-70000-C,BTC-29DEC23-70000-P,BTC-29DEC23-60000-C,BTC-29DEC23-60000-P,BTC-29DEC23-50000-C,BTC-29DEC23-50000-P,BTC-29DEC23-40000-C,BTC-29DEC23-40000-P,BTC-29DEC23-36000-C,BTC-29DEC23-36000-P,BTC-29DEC23-35000-C,BTC-29DEC23-35000-P,BTC-29DEC23-34000-C,BTC-29DEC23-34000-P,BTC-29DEC23-32000-C,BTC-29DEC23-32000-P,BTC-29DEC23-31500-C,BTC-29DEC23-31500-P,BTC-29DEC23-30500-C,BTC-29DEC23-30500-P,BTC-29DEC23-30000-C,BTC-29DEC23-30000-P,BTC-29DEC23-29500-C,BTC-29DEC23-29500-P,BTC-29DEC23-29000-C,BTC-29DEC23-29000-P,BTC-29DEC23-28000-C,BTC-29DEC23-28000-P,BTC-29DEC23-27500-C,BTC-29DEC23-27500-P,BTC-29DEC23-27000-C,BTC-29DEC23-27000-P,BTC-29DEC23-26000-C,BTC-29DEC23-26000-P,BTC-29DEC23-25000-C,BTC-29DEC23-25000-P,BTC-29DEC23-24000-C,BTC-29DEC23-24000-P,BTC-29DEC23-22000-C,BTC-29DEC23-22000-P,BTC-29DEC23-20000-C,BTC-29DEC23-20000-P,BTC-29DEC23-15000-C,BTC-29DEC23-15000-P,BTC-29DEC23-10000-C,BTC-29DEC23-10000-P,BTC-24NOV23-40000-C,BTC-24NOV23-40000-P,BTC-24NOV23-38000-C,BTC-24NOV23-38000-P,BTC-24NOV23-36000-C,BTC-24NOV23-36000-P,BTC-24NOV23-34000-C,BTC-24NOV23-34000-P,BTC-24NOV23-32000-C,BTC-24NOV23-32000-P,BTC-24NOV23-31500-C,BTC-24NOV23-31500-P,BTC-24NOV23-30500-C,BTC-24NOV23-30500-P,BTC-24NOV23-30000-C,BTC-24NOV23-30000-P,BTC-24NOV23-29500-C,BTC-24NOV23-29500-P,BTC-24NOV23-29000-C,BTC-24NOV23-29000-P,BTC-24NOV23-28500-C,BTC-24NOV23-28500-P,BTC-24NOV23-28000-C,BTC-24NOV23-28000-P,BTC-24NOV23-27500-C,BTC-24NOV23-27500-P,BTC-24NOV23-27000-C,BTC-24NOV23-27000-P,BTC-24NOV23-26500-C,BTC-24NOV23-26500-P,BTC-24NOV23-26000-C,BTC-24NOV23-26000-P,BTC-24NOV23-25500-C,BTC-24NOV23-25500-P,BTC-24NOV23-25000-C,BTC-24NOV23-25000-P,BTC-24NOV23-24000-C,BTC-24NOV23-24000-P,BTC-24NOV23-23000-C,BTC-24NOV23-23000-P,BTC-24NOV23-22000-C,BTC-24NOV23-22000-P,BTC-24NOV23-20000-C,BTC-24NOV23-20000-P,BTC-24NOV23-18000-C,BTC-24NOV23-18000-P,BTC-24NOV23-16000-C,BTC-24NOV23-16000-P,BTC-3NOV23-36000-C,BTC-3NOV23-36000-P,BTC-3NOV23-34000-C,BTC-3NOV23-34000-P,BTC-3NOV23-32000-C,BTC-3NOV23-32000-P,BTC-3NOV23-30000-C,BTC-3NOV23-30000-P,BTC-3NOV23-29000-C,BTC-3NOV23-29000-P,BTC-3NOV23-28500-C,BTC-3NOV23-28500-P,BTC-3NOV23-27500-C,BTC-3NOV23-27500-P,BTC-3NOV23-27000-C,BTC-3NOV23-27000-P,BTC-3NOV23-26500-C,BTC-3NOV23-26500-P,BTC-3NOV23-26000-C,BTC-3NOV23-26000-P,BTC-3NOV23-25000-C,BTC-3NOV23-25000-P,BTC-3NOV23-24000-C,BTC-3NOV23-24000-P,BTC-3NOV23-22000-C,BTC-3NOV23-22000-P,BTC-3NOV23-20000-C,BTC-3NOV23-20000-P,BTC-3NOV23-18000-C,BTC-3NOV23-18000-P,BTC-27OCT23-44000-C,BTC-27OCT23-44000-P,BTC-27OCT23-42000-C,BTC-27OCT23-42000-P,BTC-27OCT23-40000-C,BTC-27OCT23-40000-P,BTC-27OCT23-38000-C,BTC-27OCT23-38000-P,BTC-27OCT23-37000-C,BTC-27OCT23-37000-P,BTC-27OCT23-35000-C,BTC-27OCT23-35000-P,BTC-27OCT23-34500-C,BTC-27OCT23-34500-P,BTC-27OCT23-33500-C,BTC-27OCT23-33500-P,BTC-27OCT23-32500-C,BTC-27OCT23-32500-P,BTC-27OCT23-31500-C,BTC-27OCT23-31500-P,BTC-27OCT23-31000-C,BTC-27OCT23-31000-P,BTC-27OCT23-30500-C,BTC-27OCT23-30500-P,BTC-27OCT23-30000-C,BTC-27OCT23-30000-P,BTC-27OCT23-29500-C,BTC-27OCT23-29500-P,BTC-27OCT23-29000-C,BTC-27OCT23-29000-P,BTC-27OCT23-28750-C,BTC-27OCT23-28750-P,BTC-27OCT23-28500-C,BTC-27OCT23-28500-P,BTC-27OCT23-28250-C,BTC-27OCT23-28250-P,BTC-27OCT23-28000-C,BTC-27OCT23-28000-P,BTC-27OCT23-27750-C,BTC-27OCT23-27750-P,BTC-27OCT23-27500-C,BTC-27OCT23-27500-P,BTC-27OCT23-27250-C,BTC-27OCT23-27250-P,BTC-27OCT23-27000-C,BTC-27OCT23-27000-P,BTC-27OCT23-26500-C,BTC-27OCT23-26500-P,BTC-27OCT23-26000-C,BTC-27OCT23-26000-P,BTC-27OCT23-25500-C,BTC-27OCT23-25500-P,BTC-27OCT23-25000-C,BTC-27OCT23-25000-P,BTC-27OCT23-24000-C,BTC-27OCT23-24000-P,BTC-27OCT23-23000-C,BTC-27OCT23-23000-P,BTC-27OCT23-22000-C,BTC-27OCT23-22000-P,BTC-27OCT23-20000-C,BTC-27OCT23-20000-P,BTC-27OCT23-18000-C,BTC-27OCT23-18000-P,BTC-27OCT23-16000-C,BTC-27OCT23-16000-P,BTC-20OCT23-36000-C,BTC-20OCT23-36000-P,BTC-20OCT23-34000-C,BTC-20OCT23-34000-P,BTC-20OCT23-32000-C,BTC-20OCT23-32000-P,BTC-20OCT23-31000-C,BTC-20OCT23-31000-P,BTC-20OCT23-30500-C,BTC-20OCT23-30500-P,BTC-20OCT23-30000-C,BTC-20OCT23-30000-P,BTC-20OCT23-29500-C,BTC-20OCT23-29500-P,BTC-20OCT23-29000-C,BTC-20OCT23-29000-P,BTC-20OCT23-28750-C,BTC-20OCT23-28750-P,BTC-20OCT23-28500-C,BTC-20OCT23-28500-P,BTC-20OCT23-28250-C,BTC-20OCT23-28250-P,BTC-20OCT23-28000-C,BTC-20OCT23-28000-P,BTC-20OCT23-27750-C,BTC-20OCT23-27750-P,BTC-20OCT23-27500-C,BTC-20OCT23-27500-P,BTC-20OCT23-27250-C,BTC-20OCT23-27250-P,BTC-20OCT23-27000-C,BTC-20OCT23-27000-P,BTC-20OCT23-26750-C,BTC-20OCT23-26750-P,BTC-20OCT23-26500-C,BTC-20OCT23-26500-P,BTC-20OCT23-26250-C,BTC-20OCT23-26250-P,BTC-20OCT23-26000-C,BTC-20OCT23-26000-P,BTC-20OCT23-25750-C,BTC-20OCT23-25750-P,BTC-20OCT23-25500-C,BTC-20OCT23-25500-P,BTC-20OCT23-25000-C,BTC-20OCT23-25000-P,BTC-20OCT23-24000-C,BTC-20OCT23-24000-P,BTC-20OCT23-22000-C,BTC-20OCT23-22000-P,BTC-20OCT23-20000-C,BTC-20OCT23-20000-P,BTC-20OCT23-18000-C,BTC-20OCT23-18000-P,BTC-19OCT23-29750-C,BTC-19OCT23-29750-P,BTC-19OCT23-29500-C,BTC-19OCT23-29500-P,BTC-19OCT23-29250-C,BTC-19OCT23-29250-P,BTC-19OCT23-29000-C,BTC-19OCT23-29000-P,BTC-19OCT23-28750-C,BTC-19OCT23-28750-P,BTC-19OCT23-28500-C,BTC-19OCT23-28500-P,BTC-19OCT23-28250-C,BTC-19OCT23-28250-P,BTC-19OCT23-28000-C,BTC-19OCT23-28000-P,BTC-19OCT23-27750-C,BTC-19OCT23-27750-P,BTC-19OCT23-27500-C,BTC-19OCT23-27500-P,BTC-19OCT23-27250-C,BTC-19OCT23-27250-P,BTC-19OCT23-27000-C,BTC-19OCT23-27000-P,BTC-19OCT23-26750-C,BTC-19OCT23-26750-P,BTC-19OCT23-26500-C,BTC-19OCT23-26500-P,BTC-19OCT23-26250-C,BTC-19OCT23-26250-P,BTC-19OCT23-26000-C,BTC-19OCT23-26000-P,BTC-19OCT23-25750-C,BTC-19OCT23-25750-P,BTC-18OCT23-29500-C,BTC-18OCT23-29500-P,BTC-18OCT23-29250-C,BTC-18OCT23-29250-P,BTC-18OCT23-29000-C,BTC-18OCT23-29000-P,BTC-18OCT23-28750-C,BTC-18OCT23-28750-P,BTC-18OCT23-28500-C,BTC-18OCT23-28500-P,BTC-18OCT23-28250-C,BTC-18OCT23-28250-P,BTC-18OCT23-28000-C,BTC-18OCT23-28000-P,BTC-18OCT23-27750-C,BTC-18OCT23-27750-P,BTC-18OCT23-27500-C,BTC-18OCT23-27500-P,BTC-18OCT23-27250-C,BTC-18OCT23-27250-P,BTC-18OCT23-27000-C,BTC-18OCT23-27000-P,BTC-18OCT23-26750-C,BTC-18OCT23-26750-P,BTC-18OCT23-26500-C,BTC-18OCT23-26500-P,BTC-18OCT23-26250-C,BTC-18OCT23-26250-P,BTC-18OCT23-26000-C,BTC-18OCT23-26000-P,BTC-18OCT23-25750-C,BTC-18OCT23-25750-P,BTC-18OCT23-25500-C,BTC-18OCT23-25500-P,BTC-17OCT23-29250-C,BTC-17OCT23-29250-P,BTC-17OCT23-29000-C,BTC-17OCT23-29000-P,BTC-17OCT23-28750-C,BTC-17OCT23-28750-P,BTC-17OCT23-28500-C,BTC-17OCT23-28500-P,BTC-17OCT23-28250-C,BTC-17OCT23-28250-P,BTC-17OCT23-28000-C,BTC-17OCT23-28000-P,BTC-17OCT23-27750-C,BTC-17OCT23-27750-P,BTC-17OCT23-27500-C,BTC-17OCT23-27500-P,BTC-17OCT23-27250-C,BTC-17OCT23-27250-P,BTC-17OCT23-27000-C,BTC-17OCT23-27000-P,BTC-17OCT23-26750-C,BTC-17OCT23-26750-P,BTC-17OCT23-26500-C,BTC-17OCT23-26500-P,BTC-17OCT23-26250-C,BTC-17OCT23-26250-P,BTC-17OCT23-26000-C,BTC-17OCT23-26000-P,BTC-17OCT23-25750-C,BTC-17OCT23-25750-P,BTC-17OCT23-25500-C,BTC-17OCT23-25500-P", + "requestFormat": { + "uppercase": true, + "delimiter": "-" + }, + "configFormat": { + "uppercase": true, + "delimiter": "-" + } } - } - }, - "api": { - "authenticatedSupport": false, - "authenticatedWebsocketApiSupport": false, - "endpoints": { - "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" - }, - "credentials": { - "key": "Key", - "secret": "Secret" - }, - "credentialsValidator": { - "requiresKey": true, - "requiresSecret": true - } - }, - "features": { - "supports": { - "restAPI": true, - "restCapabilities": { - "tickerBatching": true, - "autoPairUpdates": true - }, - "websocketAPI": false, - "websocketCapabilities": {} + } + }, + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "tickerBatching": true, + "autoPairUpdates": true + }, + "websocketAPI": true, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": true + } + }, + "bankAccounts": [ + { + "enabled": false, + "bankName": "", + "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ] }, - "enabled": { - "autoPairUpdates": true, - "websocketAPI": false - } - }, - "bankAccounts": [ { - "enabled": false, - "bankName": "", - "bankAddress": "", - "bankPostalCode": "", - "bankPostalCity": "", - "bankCountry": "", - "accountName": "", - "accountNumber": "", - "swiftCode": "", - "iban": "", - "supportedCurrencies": "" - } - ] - }, - { - "name": "GateIO", - "enabled": true, - "verbose": false, - "httpTimeout": 15000000000, - "websocketResponseCheckTimeout": 30000000, - "websocketResponseMaxLimit": 7000000000, - "websocketTrafficTimeout": 30000000000, - "websocketOrderbookBufferLimit": 5, - "baseCurrencies": "USD", - "currencyPairs": { - "requestFormat": { - "uppercase": true, - "delimiter": "_" - }, - "configFormat": { - "uppercase": true, - "delimiter": "_" - }, - "useGlobalFormat": true, - "assetTypes": [ - "spot", - "option", - "futures", - "cross_margin", - "margin", - "delivery" - ], - "pairs": { - "spot": { - "enabled": "BTC_USDT,IHT_ETH,AME_ETH,CEUR_ETH,ALEPH_USDT,OMG_TRY,BTC_TRY,OGN_USDT,ALA_USDT", - "available": "IHT_ETH,AME_ETH,CEUR_ETH,ALEPH_USDT,OMG_TRY,BTC_TRY,OGN_USDT,ALA_USDT,HC_USDT,BTC_USDT,QNT_USDT,QTUM_ETH,MAHA_ETH,XCN_ETH,POOL_USDT,KGC_USDT,MCO2_USDT,HARD_USDT,GHNY_USDT,FTT_ETH,K21_ETH,FINE_USDT,REP_USDT,SBR_USDT,SKM_ETH,QLC_ETH,GAS_BTC,ALICE3L_USDT,BAO_USDT,FALCONS_USDT,ANT_USDT,VIDYX_USDT,DXCT_ETH,SMTY_ETH,HERO_USDT,SHARE_USDT,FIN_USDT,MTV_USDT,MOO_USDT,SMTY_USDT,ORAO_USDT,AE_ETH,SUSD_USDT,MAN_USDT,UNDEAD_USDT,MC_USDT,VET_USDT,WAXP_ETH,MDA_ETH,LYXE_USDT,SPS_USDT,STX_ETH,WSIENNA_USDT,NAOS_BTC,NFTX_USDT,OPUL_USDT,ICP3L_USDT,SFI_ETH,CTT_USDT,BSV3L_USDT,DFI_USDT,DIS_ETH,FET_USDT,ARG_USDT,VELO_USDT,NSBT_BTC,GSE_ETH,HNS_BTC,DOGEDASH_ETH,BACON_USDT,DUSK_USDT,MAPE_USDT,EGLD_ETH,TDROP_USDT,C983L_USDT,FAN_ETH,CZZ_USDT,FIU_USDT,SWRV_USDT,ONT_ETH,KINE_ETH,IMX_ETH,SPAY_ETH,CFG_BTC,RACA3S_USDT,UNO_ETH,DMLG_USDT,SAKE_ETH,ASM_USDT,CUSD_ETH,SUSD_ETH,ONC_USDT,DAI_USDT,VEGA_ETH,PYM_USDT,LTC_TRY,LOKA_USDT,NIF_USDT,BNC_USDT,PERL_ETH,MATIC3S_USDT,STMX_USDT,SKL_USDT,WLKN_USDT,XYO_ETH,AMPL3S_USDT,WEX_USDT,ULU_ETH,LIKE_ETH,INSUR_ETH,CAKE_ETH,SXP_ETH,COTI_USDT,ORT_USDT,RACA3L_USDT,GASDAO_USDT,AVA_USDT,OPA_USDT,ATS_USDT,VEGA_USDT,KILT_USDT,HIT_ETH,BRISE_USDT,SAUBER_USDT,SPS_ETH,FSN_USDT,EOS_ETH,KYL_USDT,REVV_ETH,SVT_ETH,XRP_USDT,DYDX3S_USDT,MANA3S_USDT,ICP_ETH,ALICE3S_USDT,PCX_USDT,LEMO_ETH,MKR_ETH,WOO3S_USDT,CART_ETH,MATIC_USDT,UNI_USD,MOBI_BTC,ICP3S_USDT,BEAM_BTC,CRO3S_USDT,FTT_USDT,IQ_ETH,TAP_USDT,MLT_USDT,RBN_USDT,AMPL3L_USDT,KINT_ETH,HECH_USDT,GAFI_ETH,WOO3L_USDT,TAI_USDT,HERA_USDT,AST_USDT,DHV_ETH,XAVA_USDT,LSS_USDT,SNX3S_USDT,PBR_USDT,XEND_ETH,SHR_ETH,PRQ_USDT,MATIC3L_USDT,WIT_ETH,LPOOL_USDT,PSP_USDT,BXC_USDT,CBK_USDT,REVO_BTC,MANA3L_USDT,ALPINE_USDT,DEGO_USDT,SIN_USDT,OCT_USDT,KZEN_USDT,L3P_USDT,FX_ETH,ONC_ETH,AXS_USD,BORA_USDT,XTZ_ETH,NEO3L_USDT,FROG_USDT,CHAMP_USDT,XNFT_USDT,BCH3S_USDT,FORT_USDT,XLM_TRY,TRX_TRY,CRPT_USDT,ROUTE_USDT,GLM_USDT,SLRS_ETH,TIMECHRONO_USDT,VRA_USDT,ONS_USDT,ZEC3L_USDT,KFT_ETH,TFD_ETH,FRA_USDT,RDN_ETH,BLANK_USDT,IOST3L_USDT,DDD_USDT,DOGE_USD,UNQ_USDT,API33S_USDT,AKRO_ETH,GITCOIN_USDT,THG_USDT,BDX_USDT,LTO_ETH,FLY_USDT,CREDIT_USDT,RENA_USDT,ZRX_ETH,CRP_ETH,NBOT_USDT,HT3L_USDT,DORA_ETH,LLT_SNET,ASD_USDT,XMR_USDT,SSV_BTC,FTM_USDT,XELS_USDT,MTL_ETH,ADX_ETH,API33L_USDT,PIG_USDT,RUNE_ETH,QRDO_BTC,THN_USDT,BCUG_USDT,EGG_ETH,GGM_USDT,HOTCROSS_USDT,SKYRIM_USDT,BTG_USDT,POT_USDT,CS_USDT,XVS_USDT,A5T_USDT,GOD_BTC,WAVES_USDT,LSK_BTC,BTT_TRY,YIN_USDT,PEOPLE_USDT,SPELL_ETH,POLC_USDT,BZZ3L_USDT,UNO_USDT,HDV_USDT,CELL_USDT,DAR_ETH,MIR_ETH,FODL_USDT,SRM_ETH,PROS_USDT,ORN_ETH,WAG_USDT,RBC_ETH,VENT_USDT,WND_USDT,AAA_ETH,BSCS_ETH,ZEC3S_USDT,DOS_USDT,HT3S_USDT,LAND_USDT,BCD_BTC,RING_USDT,FIRO_USDT,AUDIO_USDT,KUMA_USDT,SOLO_BTC,CRBN_USDT,MM_ETH,SAKE_USDT,XMARK_USDT,SLP_USDT,F2C_USDT,LUNA_USDT,ONIT_USDT,FTM3L_USDT,POPK_USDT,RFUEL_USDT,NEO3S_USDT,MIR_USDT,ETC_BTC,STETH_ETH,MANA_TRY,ALPACA_ETH,WAXL_USDT,EGS_USDT,DAR_USDT,KSM_USDT,XMARK_ETH,QTUM_USDT,C983S_USDT,INDI_ETH,DOGE3S_USDT,RVN_USDT,NOS_USDT,ALU_ETH,ALD_ETH,LUNC_USDT,ARES_ETH,BZZ3S_USDT,TNC_ETH,ONE_USDT,SENC_ETH,FTM3S_USDT,FLUX_USDT,STORJ_ETH,MTN_ETH,MNW_USDT,BLES_ETH,STG_ETH,LIME_ETH,WAGYU_USDT,XRP_TRY,XOR_ETH,ANGLE_USDT,DOGA_USDT,JFI_USDT,USDG_USDT,GRND_USDT,BOND_ETH,DMTR_USDT,YIN_ETH,ENJ_USDT,GOLDMINER_USDT,WIT_USDT,DOGE3L_USDT,FORM_USDT,LYXE_ETH,MLK_USDT,VR_USDT,DMS_USDT,LRC_TRY,ONX_USDT,ASK_USDT,ISP_ETH,TXT_USDT,IOEN_ETH,NIIFI_USDT,VRX_USDT,DOME_USDT,CTSI_USDT,ORBS_USDT,ZLW_ETH,FIL_USDT,FTI_ETH,CTK_USDT,ASR_USDT,GBPT_BTC,CBK_BTC,MBOX_ETH,RAM_USDT,IRIS_USDT,AME_USDT,KUB_USDT,ENV_USDT,RING_ETH,COTI3S_USDT,JULD_ETH,POLK_ETH,ACH3S_USDT,HYVE_ETH,MIX_ETH,RFT_USDT,ORAO_ETH,IHT_USDT,POLYPAD_USDT,CTRC_USDT,SFUND_USDT,MXC_BTC,DDD_BTC,CHESS_ETH,SHIB_USDT,SN_USDT,NFT_USDT,ASTRO_ETH,SOLO_USDT,TSHP_USDT,AMP_USDT,BTCST_ETH,VLXPAD_USDT,GAN_USDT,O3_USDT,WBTC_TRY,TULIP_USDT,GS_ETH,DX_ETH,NYZO_ETH,TT_USDT,SHILL_USDT,RATING_ETH,DUST_USDT,PSB_USDT,BFT1_USDT,GALA_ETH,XDC_USDT,LON3L_USDT,HE_USDT,ICE_ETH,LINK_ETH,SKU_USDT,QLC_USDT,DOMI_USDT,IDEA_USDT,METO_USDT,LIFE_ETH,ACH3L_USDT,POWR_ETH,VET_ETH,ALGO_USDT,BLIN_USDT,BAO_ETH,RBLS_USDT,TORN_ETH,VRT_USDT,BLANKV2_ETH,AUCTION_ETH,OLE_USDT,NWC_BTC,DOT5S_USDT,M RCH_ETH,SUNNY_ETH,GST_USDT,ENJ_TRY,KIBA_USDT,KLAP_USDT,SNTR_ETH,CELR_ETH,CHESS_USDT,XLM3L_USDT,LIQ_USDT,TRU_ETH,CHZ_USD,EPK_USDT,MED_ETH,BSCPAD_ETH,ZCN_USDT,AIOZ_ETH,FOR_ETH,CVC3L_USDT,MNY_USDT,SALT_USDT,CSTR_USDT,MPL_USDT,PLY_ETH,FIS_USDT,CHO_USDT,BICO_ETH,STOX_ETH,HIGH_USDT,SDAO_BTC,STEP_USD,CRV_BTC,SCRT_ETH,ROSE_USDT,SKILL_ETH,FRAX_USDT,BAGS_USDT,WIKEN_BTC,WOO_USDT,BBANK_ETH,SNX3L_USDT,XRD_ETH,VTHO_USDT,OKB3L_USDT,SAFEMOON_USDT,RAD_ETH,IOI_USDT,LAMB_USDT,CHZ_USDT,FAR_ETH,OKB3S_USDT,ELU_USDT,JGN_ETH,EOS3S_USDT,DBC_USDT,ATOM_USDT,ACH_ETH,LBLOCK_USDT,WZRD_USDT,OST_ETH,MEAN_USDT,IDEX_USDT,HOT_TRY,EWT_ETH,EMON_USDT,FXS_USDT,PSY_ETH,SIDUS_USDT,ATA_USDT,CVC3S_USDT,LOOKS_ETH,ALPA_ETH,CGG_ETH,CIR_ETH,PRT_ETH,LON3S_USDT,INJ_USDT,FIRE_ETH,MAHA_USDT,IOST3S_USDT,NU_ETH,LEO_BTC,VOXEL_USDT,CRV_USDT,EQX_USDT,WHALE_USDT,INJ_ETH,GRAP_USDT,AVAX3S_USDT,TIFI_USDT,C98_USDT,ERN_ETH,SUSHI_ETH,VET3S_USDT,KPAD_USDT,CRPT_ETH,CRO_USDT,AZY_USDT,LEMD_USDT,ETH2_ETH,BASE_ETH,TT_ETH,PERL_USDT,BANK_ETH,LST_ETH,PYR_ETH,RATIO_USDT,UMB_USDT,M ETALDR_USDT,SWINGBY_ETH,WICC_ETH,NUM_USDT,SHOE_USDT,BORING_ETH,SDN_USDT,GXS_BTC,ALICE_ETH,BRKL_USDT,GF_ETH,ELEC_USDT,SFG_USDT,COFIX_USDT,TIPS_ETH,FIL_BTC,CWAR_USDT,WILD_USDT,RENBTC_USDT,BNX_USDT,TRU_USDT,SWEAT_USDT,IOST_BTC,NVIR_USDT,1EARTH_USDT,ADAPAD_USDT,PPS_USDT,CUBE_USDT,DLC_USDT,DAFI_ETH,UNISTAKE_ETH,NFTL_USDT,ATOM_TRY,SHIB3S_USDT,BNB_USD,CNAME_USDT,GTH_ETH,ZCX_USDT,DYDX3L_USDT,ASTRO_USDT,GLQ_USDT,PROPS_USDT,AART_USDT,BTRST_ETH,KFT_USDT,AERGO_USDT,RUFF_ETH,EOS3L_USDT,API3_USDT,MINA_BTC,ETHA_ETH,AXIS_ETH,LOON_USDT,AVAX3L_USDT,VET3L_USDT,AE_USDT,SHX_USDT,LYM_USDT,DCR_BTC,LBK_USDT,QTC_USDT,LAVA_USDT,XCN_USDT,BRT_USDT,RSV_USDT,KIF_USDT,PSL_USDT,AZERO_USDT,LUNA_ETH,MILO_USDT,OGN_ETH,TOTM_USDT,BYN_ETH,MINA_USDT,PUNDIX_ETH,SRT_USDT,DG_ETH,IHC_USDT,SYS_ETH,TITA_USDT,COTI3L_USDT,DAG_USDT,DOT5L_USDT,TRADE_USDT,SHPING_USDT,NU_USDT,BLANK_ETH,PCNT_ETH,SCCP_USDT,POLS_USDT,NPT_USDT,MTA_USDT,YIELD_USDT,ZCN_ETH,DVP_ETH,KART_USDT,SYLO_USDT,MCRT_USDT,SPFC_USDT,BASE_USDT,ICX_USDT,PET_USDT,GZONE_USDT,RED_ETH,SBTC_USDT,BATH_ ETH,SOL_USD,NAFT_USDT,GMX_USDT,VADER_USDT,GTC_USDT,CVP_ETH,XRPBEAR_USDT,TIME_USDT,SXP_USDT,CITY_USDT,QASH_USDT,FAST_USDT,BCD_USDT,KNIGHT_USDT,BOO_ETH,ZODI_USDT,REI_USDT,PBX_ETH,SRM_USDT,LDO_ETH,ZEC_USDT,UFT_USDT,DAG_BTC,RIDE_USDT,ERN_USDT,T_USDT,CEEK_USDT,STI_USDT,IMX3S_USDT,ELA_USDT,MNGO_ETH,EHASH_ETH,BADGER_ETH,SUPE_USDT,AR3L_USDT,AUDIO_ETH,DOCK_ETH,QSP_USDT,FLM_USDT,AAVE3S_USDT,BOND_USDT,HT_USD,TARA_USDT,TRX_USDT,SPO_USDT,DSLA_USDT,LTC_BTC,DOGE_USDT,SLIM_ETH,ALN_ETH,CFX3S_USDT,FXS_ETH,RARE_ETH,VLXPAD_ETH,ETH_USD,SDN_BTC,QUICK_USDT,UTK_USDT,XPNET_USDT,TRB_USDT,LAZIO_USDT,FTM_TRY,ALPHA_ETH,CVC_ETH,WSG_USDT,UNI_ETH,DASH3L_USDT,BTL_USDT,CPOOL_USDT,MCG_USDT,SFP_ETH,REALM_USDT,RUFF_BTC,MOB_ETH,IBFK_USDT,ALPHA3S_USDT,BLOK_USDT,WIKEN_USDT,OMG3S_USDT,UTK_ETH,BCH5S_USDT,MED_USDT,REN_USD,MAN_ETH,SLND_ETH,CGG_USDT,CRE_USDT,SOURCE_USDT,ABT_USDT,DPET_USDT,WOM_USDT,FOREX_ETH,SNFT1_USDT,RIF_USDT,BENQI_USDT,XCV_ETH,GTC_BTC,ADA_TRY,LAT_USDT,ITGR_USDT,DLTA_USDT,SMT_USDT,APYS_USDT,MFT_ETH,ABT_ETH,STOX_USDT,ZRX_BTC,GMAT_USDT,R OOM_ETH,STORJ_BTC,RAZOR_USDT,RAGE_USDT,DOCK_USDT,RDN_USDT,MTR_USDT,NKN_USDT,SWASH_USDT,FX_USDT,POR_USDT,DENT_ETH,DERI_USDT,DFND_USDT,BLES_USDT,SLND_USDT,WNXM_ETH,CRTS_USDT,BTC3S_USDT,BKC_USDT,STEPG_ETH,THETA3L_USDT,NBS_BTC,AVAX_ETH,NANO_BTC,DEFILAND_ETH,LOOKS_USDT,BCX_BTC,BCH_USD,ETH3L_USDT,QLC_BTC,BCUG_ETH,RDF_USDT,DOGEDASH_USDT,ARSW_USDT,NEAR_ETH,QTCON_USDT,BABI_USDT,MBX_USDT,PNL_USDT,ODDZ_ETH,ATOM_BTC,XRP_BTC,BTCBULL_USDT,HMT_USDT,PORTO_USDT,STND_USDT,ETHW_ETH,LPT_USDT,LTC3L_USDT,TOKAU_USDT,QI_ETH,TVK_USDT,CWS_USDT,SWOP_USDT,WBTC_USDT,INSTAR_ETH,ICX_ETH,GALA5L_USDT,XTZ_BTC,AGS_USDT,TARA_BTC,DYDX_ETH,CATGIRL_USDT,SASHIMI_ETH,EPX_ETH,GCOIN_USDT,GDAO_USDT,MARS_ETH,OMG_USD,PMON_USDT,MNGO_USDT,TVK_ETH,SLG_USDT,MSOL_USDT,POWR_USDT,UOS_USDT,USDD_USDT,SLICE_USDT,ARRR_ETH,NSBT_USDT,STR_ETH,BEAM3L_USDT,BEL_USDT,MM_USDT,AXS_ETH,WEST_ETH,FTT3L_USDT,OMI_USDT,TIPS_USDT,SLC_ETH,SQUID_USDT,FEI_USDT,GEM_USDT,UMEE_USDT,DOGE_TRY,FCD_USDT,PVU_USDT,XED_ETH,LRN_ETH,NRFB_USDT,LION_USDT,BLACK_USDT,DOGE5S_USDT,CUDOS_USDT,PCNT_USDT ,OVR_USDT,ETC3S_USDT,CHR_ETH,MER_USDT,BOBA_USDT,FUEL_USDT,BAC_USDT,ONE3S_USDT,CONV_ETH,CDT_BTC,CELL_ETH,ASM_ETH,OPIUM_USDT,JST3L_USDT,BONDLY_USDT,RAZE_USDT,LIME_BTC,NFTX_ETH,PNK_ETH,LDO_USDT,DKS_USDT,ORO_USDT,LITH_USDT,ALPHR_ETH,INK_BTC,RLY_USDT,NEAR3S_USDT,XLM3S_USDT,AR_USDT,AKT_USDT,HCT_USDT,REEF_ETH,BZZ_USDT,SRM3L_USDT,AQDC_USDT,OPIUM_ETH,BAT_TRY,EWT_USDT,ALCX_ETH,CORN_USDT,HYDRA_USDT,RUNE_USD,STEP_USDT,CKB_BTC,MATTER_USDT,STSOL_ETH,CEEK_ETH,FXF_ETH,LIKE_USDT,HIT_USDT,LEO_USDT,COMP_USDT,BAL_USDT,LMR_USDT,AQT_USDT,BUY_ETH,LINK3S_USDT,ROOK_ETH,IMX_USDT,EFI_USDT,TAUR_USDT,OKT_ETH,GALO_USDT,MOOV_USDT,RUNE_USDT,TCP_USDT,ITEM_USDT,SCLP_USDT,RBC_USDT,SPI_USDT,ETC_USDT,RENBTC_BTC,CHICKS_USDT,KNOT_USDT,XEC3L_USDT,XCV_USDT,ETC_ETH,AAVE_TRY,APT_USDT,GNX_ETH,KISHU_USDT,AE_BTC,LIEN_USDT,CREAM_USDT,ATOM3S_USDT,OP_ETH,FORTH_ETH,PYR_USDT,KTN_ETH,TKO_ETH,METAG_USDT,ACE_USDT,CIR_USDT,BEAM_ETH,TCP_ETH,SRM_USD,CEL_USD,TRIBE3S_USDT,MESA_ETH,EVA_USDT,BBANK_USDT,BLANKV2_USDT,FORM_ETH,BAL3S_USDT,VISR_ETH,REVO_ETH,ALTB_USDT,KNC_US DT,GAS_USDT,SAFEMARS_USDT,TIP_USDT,VADER_ETH,NWC_USDT,VALUE_USDT,ATA_ETH,SSX_USDT,JOE_USDT,BAS_ETH,FITFI3S_USDT,BIT_USDT,QNT_ETH,RFOX_ETH,MSU_USDT,MSOL_ETH,CRV3L_USDT,OXT_USDT,SHFT_USDT,VERA_ETH,LYM_ETH,BP_USDT,KBOX_USDT,DOGNFT_ETH,PERP_USDT,VELO_ETH,SAO_USDT,DUCK2_USDT,DEFILAND_USDT,DUCK2_ETH,GLMR3L_USDT,SERO_ETH,MTS_USDT,STX_USDT,KEX_ETH,ZIG_USDT,CARDS_USDT,ANML_USDT,GALA_USDT,RAY3S_USDT,KAVA3L_USDT,GARD_USDT,GRT3L_USDT,BFC_USDT,NIFT_USDT,ORION_USDT,CTX_USDT,ASW_USDT,CERE_USDT,COMBO_ETH,MKR_USDT,MASK_USDT,MGA_USDT,AVAX_USDT,SKL3L_USDT,FRR_USDT,MV_USDT,BMI_ETH,SFIL_USDT,TEER_USDT,KLV_USDT,DMS_ETH,LBL_USDT,MKR3L_USDT,LEDU_BTC,XLM_BTC,MIST_ETH,OIN_USDT,CAKE_USDT,RNDR_USDT,STEPG_USDT,YCT_USDT,OPS_ETH,SHR_USDT,OXY_ETH" - }, - "option": { - "enabled": "BTC_USDT-20230217-28000-P,BTC_USDT-20221028-34000-P,BTC_USDT-20221028-40000-C", - "available": "BTC_USDT-20221028-26000-C,BTC_USDT-20221028-34000-P,BTC_USDT-20221028-40000-C,BTC_USDT-20221028-28000-P,BTC_USDT-20221028-34000-C,BTC_USDT-20221028-28000-C,BTC_USDT-20221028-36000-P,BTC_USDT-20221028-50000-P,BTC_USDT-20221028-36000-C,BTC_USDT-20221028-50000-C,BTC_USDT-20221028-21000-P,BTC_USDT-20221028-38000-P,BTC_USDT-20221028-21000-C,BTC_USDT-20221028-38000-C,BTC_USDT-20221028-23000-P,BTC_USDT-20221028-17000-P,BTC_USDT-20221028-23000-C,BTC_USDT-20221028-17000-C,BTC_USDT-20221028-25000-P,BTC_USDT-20221028-19000-P,BTC_USDT-20221028-25000-C,BTC_USDT-20221028-10000-P,BTC_USDT-20221028-19000-C,BTC_USDT-20221028-27000-P,BTC_USDT-20221028-10000-C,BTC_USDT-20221028-27000-C,BTC_USDT-20221028-12000-P,BTC_USDT-20221028-12000-C,BTC_USDT-20221028-20000-P,BTC_USDT-20221028-5000-P,BTC_USDT-20221028-14000-P,BTC_USDT-20221028-20000-C,BTC_USDT-20221028-45000-P,BTC_USDT-20221028-5000-C,BTC_USDT-20221028-14000-C,BTC_USDT-20221028-22000-P,BTC_USDT-20221028-45000-C,BTC_USDT-20221028-16000-P,BTC_USDT-20221028-22000-C,BTC_USDT-20221028-30000-P,BTC_USDT-20221028-16000-C,BTC_USDT-20221028-24000-P,BTC_USDT-20221028-30000-C,BTC_USDT-20221028-18000-P,BTC_USDT-20221028-24000-C,BTC_USDT-20221028-32000-P,BTC_USDT-20221028-18000-C,BTC_USDT-20221028-26000-P,BTC_USDT-20221028-32000-C,BTC_USDT-20221028-40000-P" - }, - "futures": { - "enabled": "ETH_USD,BTC_USD,KNC_USDT,OOKI_USDT,BIT_USDT,ZEC_USDT,SC_USDT,RVN_USDT,ICX_USDT", - "available": "ETH_USD,BTC_USD,KNC_USDT,OOKI_USDT,BIT_USDT,ZEC_USDT,SC_USDT,RVN_USDT,ICX_USDT,DUSK_USDT,BEL_USDT,REEF_USDT,ALCX_USDT,ASTR_USDT,INJ_USDT,CAKE_USDT,LAZIO_USDT,ONE_USDT,CEL_USDT,ETH_USDT,KLAY_USDT,COTI_USDT,MKISHU_USDT,MANA_USDT,MOVR_USDT,OMG_USDT,UNI_USDT,LTC_USDT,AAVE_USDT,DENT_USDT,QRDO_USDT,BNB_USDT,ALPHA_USDT,RAY_USDT,APE_USDT,CERE_USDT,STMX_USDT,XCN_USDT,OGN_USDT,OKB_USDT,DOT_USDT,TLM_USDT,BTM_USDT,ADA_USDT,ANKR_USDT,ANT_USDT,TRX_USDT,MTL_USDT,YFII_USDT,SUN_USDT,SAND_USDT,MBABYDOGE_USDT,WIN_USDT,LUNC_USDT,SRM_USDT,STG_USDT,BAT_USDT,AXS_USDT,SOL_USDT,MAKITA_USDT,BNT_USDT,BLZ_USDT,PSG_USDT,IOTA_USDT,BONK_USDT,RSR_USDT,PYR_USDT,FITFI_USDT,MKR_USDT,PERP_USDT,COMP_USDT,LINK_USDT,CHR_USDT,CFX_USDT,GARI_USDT,DGB_USDT,MBOX_USDT,WEMIX_USDT,DYDX_USDT,LUNA_USDT,HT_USDT,TRB_USDT,CTK_USDT,ACA_USDT,TFUEL_USDT,OCEAN_USDT,XLM_USDT,HOT_USDT,FTM_USDT,LPT_USDT,SOS_USDT,ALGO_USDT,SHIB_USDT,BSV_USDT,PORTO_USDT,SFP_USDT,SANTOS_USDT,BADGER_USDT,DAR_USDT,DEFI_USDT,XEM_USDT,ALICE_USDT,ICP_USDT,RARE_USDT,LRC_USDT,BAKE_USDT,FLUX_USDT,CRO_USDT,CVC_USDT,MINA_USDT,LIT_USDT,AUDIO_USDT,ZIL_USDT,XMR_USDT,FRONT_USDT,CTSI_USDT,AGLD_USDT,YGG_USDT,OP_USDT,ZRX_USDT,GT_USDT,XCH_USDT,VET_USDT,MOB_USDT,BICO_USDT,SLP_USDT,ACH_USDT,AR_USDT,CLV_USDT,IMX_USDT,SPELL_USDT,UNFI_USDT,SUSHI_USDT,FTT_USDT,HIGH_USDT,HNT_USDT,ALT_USDT,YFI_USDT,NEAR_USDT,NKN_USDT,XVS_USDT,BAND_USDT,LOKA_USDT,BCH_USDT,TOMO_USDT,WAVES_USDT,FIDA_USDT,DIA_USDT,ANC_USDT,CELO_USDT,CRV_USDT,FLM_USDT,GLMR_USDT,FIL_USDT,PEOPLE_USDT,WAXP_USDT,IOTX_USDT,ATOM_USDT,RLC_USDT,HBAR_USDT,REN_USDT,GMT_USDT,KAVA_USDT,KDA_USDT,GALA_USDT,STORJ_USDT,PUNDIX_USDT,BAL_USDT,XAUG_USDT,GRIN_USDT,SXP_USDT,AKRO_USDT,NEXO_USDT,CKB_USDT,API3_USDT,NEST_USDT,ETHW_USDT,TONCOIN_USDT,THETA_USDT,CREAM_USDT,BTC_USDT,GST_USDT,BEAM_USDT,HFT_USDT,KSM_USDT,RAD_USDT,QTUM_USDT,WOO_USDT,ATA_USDT,AVAX_USDT,EOS_USDT,SNX_USDT,AUCTION_USDT,XRP_USDT,GITCOIN_USDT,MATIC_USDT,ONT_USDT,LINA_USDT,DASH_USDT,MASK_USDT,ETC_USDT,JST_USDT,BSW_USDT,CONV_USDT,SKL_USDT,GAL_USDT,DODO_USDT,GRT_USDT,TRU_USDT,STX_USDT,CVX_USDT,JASMY_USDT,HIVE_USDT,EXCH_USDT,ROSE_USDT,SUPER_USDT,SCRT_USDT,USTC_USDT,ENJ_USDT,BTS_USDT,LOOKS_USDT,QNT_USDT,HOOK_USDT,FLOW_USDT,RUNE_USDT,APT_USDT,CHZ_USDT,DOGE_USDT,1INCH_USDT,PRIV_USDT,CSPR_USDT,C98_USDT,RACA_USDT,CELR_USDT,XEC_USDT,ENS_USDT,POND_USDT,NYM_USDT,PROM_USDT,IOST_USDT,ZEN_USDT,LDO_USDT,RNDR_USDT,REQ_USDT,DEGO_USDT,VRA_USDT,QUICK_USDT,VGX_USDT,XTZ_USDT,EGLD_USDT,POLS_USDT,ARPA_USDT,NFT_USDT" - }, - "cross_margin": { - "enabled": "BTC_USDT,ERN_USDT,T_USDT,CEEK_USDT,OGN_USDT,QNT_USDT,WOZX_USDT,ZEE_USDT,FUN_USDT,FLM_USDT,BOND_USDT", - "available": "ERN_USDT,T_USDT,CEEK_USDT,OGN_USDT,QNT_USDT,WOZX_USDT,ZEE_USDT,FUN_USDT,FLM_USDT,BOND_USDT,TARA_USDT,TRX_USDT,OXY_USDT,LON_USDT,DOGE_USDT,ISP_USDT,TWT_USDT,BAO_USDT,QUACK_USDT,ANT_USDT,VGX_USDT,ARPA_USDT,QUICK_USDT,UTK_USDT,HERO_USDT,WSG_USDT,BICO_USDT,MTV_USDT,VET_USDT,GARI_USDT,BCH_USDT,KLAY_USDT,WING_USDT,BLOK_USDT,SPS_USDT,WIKEN_USDT,WSIENNA_USDT,PUNDIX_USDT,FIC_USDT,ASTR_USDT,FET_USDT,VELO_USDT,BENQI_USDT,CWEB_USDT,RIF_USDT,UNI_USDT,ONG_USDT,ERG_USDT,ALPHA_USDT,CELO_USDT,XVG_USDT,GMAT_USDT,BTS_USDT,DOCK_USDT,GMT_USDT,DIA_USDT,CSPR_USDT,NKN_USDT,STAKE_USDT,SWASH_USDT,XEC_USDT,SWRV_USDT,QRDO_USDT,BLES_USDT,EOS_USDT,GRT_USDT,ASM_USDT,FIL6_USDT,GNO_USDT,EGLD_USDT,XYM_USDT,LOOKS_USDT,LOKA_USDT,BNC_USDT,BAS_USDT,SKL_USDT,STMX_USDT,CVC_USDT,DDOS_USDT,COTI_USDT,AVA_USDT,HMT_USDT,DF_USDT,LPT_USDT,XRP_USDT,TVK_USDT,FEVR_USDT,MBL_USDT,KIN_USDT,SPELL_USDT,MATIC_USDT,FTT_USDT,NMR_USDT,PMON_USDT,BNB_USDT,USDD_USDT,LSS_USDT,MDX_USDT,PRQ_USDT,ALPINE_USDT,DEGO_USDT,OMI_USDT,TIPS_USDT,OCT_USDT,FEI_USDT,UMEE_USDT,CRP_USDT,LION_USDT,YFI_USDT,DASH_USDT,REQ_USDT,SDAO_USDT,PNT_USDT,INSUR_USDT,OOKI_USDT,SUN_USDT,CRPT_USDT,BAC_USDT,DATA_USDT,LRN_USDT,JGN_USDT,KIMCHI_USDT,SUKU_USDT,VRA_USDT,AAVE_USDT,FTI_USDT,LDO_USDT,FRA_USDT,BLANK_USDT,NEAR_USDT,ZKS_USDT,MTRG_USDT,RLY_USDT,TCT_USDT,FLY_USDT,JST_USDT,YFII_USDT,AR_USDT,POLY_USDT,JULD_USDT,SOL_USDT,BZZ_USDT,AXS_USDT,ASD_USDT,XMR_USDT,FTM_USDT,HIT_USDT,LEO_USDT,LIT_USDT,PIG_USDT,COMP_USDT,ELON_USDT,IMX_USDT,EFI_USDT,XVS_USDT,WAVES_USDT,PEOPLE_USDT,SOS_USDT,RUNE_USDT,POLC_USDT,SCLP_USDT,BABYDOGE_USDT,KONO_USDT,SPI_USDT,ETC_USDT,MDA_USDT,MTL_USDT,BCHA_USDT,KISHU_USDT,SUNNY_USDT,PYR_USDT,XTZ_USDT,TRIBE_USDT,AUDIO_USDT,FIRO_USDT,MANA_USDT,OKB_USDT,DOG_USDT,SLP_USDT,KNC_USDT,GAS_USDT,LUNA_USDT,SAFEMARS_USDT,MIR_USDT,DAR_USDT,EGS_USDT,KSM_USDT,ATP_USDT,BIT_USDT,STORJ_USDT,XEM_USDT,QTUM_USDT,AGLD_USDT,RVN_USDT,OXT_USDT,SHFT_USDT,IOTX_USDT,LUNC_USDT,NEXO_USDT,AKITA_USDT,PERP_USDT,ONE_USDT,ETH_USDT,FLUX_USDT,FLOKI_USDT,STX_USDT,ANML_USDT,XPRT_USDT,GALA_USDT,GXS_USDT,TORN_USDT,KAI_USDT,1INCH_USDT,CHR_USDT,GAL_USDT,GLMR_USDT,CTX_USDT,CERE_USDT,CART_USDT,STRAX_USDT,MASK_USDT,MKR_USDT,AVAX_USDT,ENJ_USDT,YAM_USDT,ALPACA_USDT,DODO_USDT,MFT_USDT,CAKE_USDT,RNDR_USDT,CTSI_USDT,GRIN_USDT,MXC_USDT,ONT_USDT,ANKR_USDT,SLIM_USDT,FIL_USDT,CTK_USDT,ASR_USDT,FEG_USDT,SERO_USDT,RSS3_USDT,IRIS_USDT,XCH_USDT,ZRX_USDT,BAND_USDT,BADGER_USDT,DAO_USDT,EPS_USDT,THETA_USDT,BAKE_USDT,SHIB_USDT,MBOX_USDT,NBS_USDT,SNT_USDT,DREP_USDT,NFT_USDT,AUCTION_USDT,BOSON_USDT,O3_USDT,NULS_USDT,OMG_USDT,PEARL_USDT,HAPI_USDT,STG_USDT,IDV_USDT,HORD_USDT,ZIL_USDT,SUPER_USDT,DENT_USDT,REN_USDT,RAI_USDT,ZEN_USDT,ALGO_USDT,BLZ_USDT,BOR_USDT,SC_USDT,HEGIC_USDT,MOB_USDT,DORA_USDT,FOR_USDT,FLOW_USDT,RARI_USDT,DYDX_USDT,ATLAS_USDT,GST_USDT,REEF_USDT,HT_USDT,XYO_USDT,CHESS_USDT,BAT_USDT,NYM_USDT,RAMP_USDT,USDC_USDT,ICP_USDT,EPK_USDT,EXRD_USDT,DOT_USDT,COOK_USDT,CKB_USDT,YGG_USDT,CRU_USDT,ANC_USDT,FIS_USDT,ALCX_USDT,HIGH_USDT,BEAM_USDT,BSW_USDT,STAR_USDT,ROSE_USDT,CNNS_USDT,BZRX_USDT,WOO_USDT,SAFEMOON_USDT,VTHO_USDT,OM_USDT,LAMB_USDT,CHZ_USDT,AIOZ_USDT,EDEN_USDT,POND_USDT,ATOM_USDT,UNFI_USDT,FORTH_USDT,MLN_USDT,NEO_USDT,MOVR_USDT,RLC_USDT,FXS_USDT,ENS_USDT,ATA_USDT,XPR_USDT,NEST_USDT,XLM_USDT,AUTO_USDT,SNX_USDT,OCN_USDT,RSR_USDT,MITH_USDT,KAR_USDT,INJ_USDT,PLA_USDT,CYS_USDT,WAXP_USDT,VOXEL_USDT,CRV_USDT,FITFI_USDT,WHALE_USDT,WRX_USDT,TIDAL_USDT,C98_USDT,HNT_USDT,TONCOIN_USDT,DOGGY_USDT,SYS_USDT,NPXS_USDT,CRO_USDT,LEMD_USDT,RAY_USDT,PERL_USDT,CQT_USDT,CFX_USDT,TOMO_USDT,ACA_USDT,SDN_USDT,OKT_USDT,WILD_USDT,BNX_USDT,TRU_USDT,RACA_USDT,SWEAT_USDT,ACH_USDT,AKRO_USDT,BTM_USDT,TKO_USDT,GT_USDT,OCEAN_USDT,WNCG_USDT,BSV_USDT,GHST_USDT,CELR_USDT,LINA_USDT,SAND_USDT,APE_USDT,WICC_USDT,FIDA_USDT,ADA_USDT,PROPS_USDT,METIS_USDT,KAVA_USDT,AERGO_USDT,CONV_USDT,TFUEL_USDT,FRONT_USDT,API3_USDT,FARM_USDT,AE_USDT,LRC_USDT,IOTA_USDT,RFOX_USDT,PHA_USDT,XCN_USDT,NAS_USDT,KEEP_USDT,VIDY_USDT,HOT_USDT,MINA_USDT,ETHW_USDT,ALICE_USDT,HAI_USDT,BTC_USDT,LTC_USDT,LTO_USDT,DC_USDT,NU_USDT,IOST_USDT,RAD_USDT,POLS_USDT,OP_USDT,WXT_USDT,STR_USDT,YIELD_USDT,GM_USDT,SPA_USDT,BTCST_USDT,WEMIX_USDT,CLV_USDT,ICX_USDT,PET_USDT,STARL_USDT,HBAR_USDT,REDTOKEN_USDT,BTT_USDT,LINK_USDT,TLM_USDT,ARES_USDT,GTC_USDT,SUSHI_USDT,KEY_USDT,ALN_USDT,KDA_USDT,DVI_USDT,SXP_USDT,MAPS_USDT,BCD_USDT,SRM_USDT,WIN_USDT,ZEC_USDT,JASMY_USDT" - }, - "margin": { - "enabled": "BTC_USDT,ERN_USDT,T_USDT,CEEK_USDT,OGN_USDT,QNT_USDT,WOZX_USDT,ZEE_USDT,FUN_USDT,FLM_USDT,BOND_USDT", - "available": "BTC_USDT,ERN_USDT,T_USDT,CEEK_USDT,OGN_USDT,QNT_USDT,WOZX_USDT,ZEE_USDT,FUN_USDT,FLM_USDT,BOND_USDT,TARA_USDT,TRX_USDT,OXY_USDT,LON_USDT,DOGE_USDT,ISP_USDT,TWT_USDT,BAO_USDT,QUACK_USDT,ANT_USDT,VGX_USDT,ARPA_USDT,QUICK_USDT,UTK_USDT,HERO_USDT,WSG_USDT,BICO_USDT,MTV_USDT,VET_USDT,GARI_USDT,BCH_USDT,KLAY_USDT,WING_USDT,BLOK_USDT,SPS_USDT,WIKEN_USDT,WSIENNA_USDT,PUNDIX_USDT,FIC_USDT,ASTR_USDT,FET_USDT,VELO_USDT,BENQI_USDT,CWEB_USDT,RIF_USDT,UNI_USDT,ONG_USDT,ERG_USDT,ALPHA_USDT,CELO_USDT,XVG_USDT,GMAT_USDT,BTS_USDT,DOCK_USDT,GMT_USDT,DIA_USDT,CSPR_USDT,NKN_USDT,STAKE_USDT,SWASH_USDT,XEC_USDT,SWRV_USDT,QRDO_USDT,BLES_USDT,EOS_USDT,GRT_USDT,ASM_USDT,FIL6_USDT,GNO_USDT,EGLD_USDT,XYM_USDT,LOOKS_USDT,LOKA_USDT,BNC_USDT,BAS_USDT,SKL_USDT,STMX_USDT,CVC_USDT,DDOS_USDT,COTI_USDT,AVA_USDT,HMT_USDT,DF_USDT,LPT_USDT,XRP_USDT,TVK_USDT,FEVR_USDT,MBL_USDT,KIN_USDT,SPELL_USDT,MATIC_USDT,FTT_USDT,NMR_USDT,PMON_USDT,BNB_USDT,USDD_USDT,LSS_USDT,MDX_USDT,PRQ_USDT,ALPINE_USDT,DEGO_USDT,OMI_USDT,TIPS_USDT,OCT_USDT,FEI_USDT,UMEE_USDT,CRP_USDT,LION_USDT,YFI_USDT,DASH_USDT,REQ_USDT,SDAO_USDT,PNT_USDT,INSUR_USDT,OOKI_USDT,SUN_USDT,CRPT_USDT,BAC_USDT,DATA_USDT,LRN_USDT,JGN_USDT,KIMCHI_USDT,SUKU_USDT,VRA_USDT,AAVE_USDT,FTI_USDT,LDO_USDT,FRA_USDT,BLANK_USDT,NEAR_USDT,ZKS_USDT,MTRG_USDT,RLY_USDT,TCT_USDT,FLY_USDT,JST_USDT,YFII_USDT,AR_USDT,POLY_USDT,JULD_USDT,SOL_USDT,BZZ_USDT,AXS_USDT,ASD_USDT,XMR_USDT,FTM_USDT,HIT_USDT,LEO_USDT,LIT_USDT,PIG_USDT,COMP_USDT,ELON_USDT,IMX_USDT,EFI_USDT,XVS_USDT,WAVES_USDT,PEOPLE_USDT,SOS_USDT,RUNE_USDT,POLC_USDT,SCLP_USDT,BABYDOGE_USDT,KONO_USDT,SPI_USDT,ETC_USDT,MDA_USDT,MTL_USDT,BCHA_USDT,KISHU_USDT,SUNNY_USDT,PYR_USDT,XTZ_USDT,TRIBE_USDT,AUDIO_USDT,FIRO_USDT,MANA_USDT,OKB_USDT,DOG_USDT,SLP_USDT,KNC_USDT,GAS_USDT,LUNA_USDT,SAFEMARS_USDT,MIR_USDT,DAR_USDT,EGS_USDT,KSM_USDT,ATP_USDT,BIT_USDT,STORJ_USDT,XEM_USDT,QTUM_USDT,AGLD_USDT,RVN_USDT,OXT_USDT,SHFT_USDT,IOTX_USDT,LUNC_USDT,NEXO_USDT,AKITA_USDT,PERP_USDT,ONE_USDT,ETH_USDT,FLUX_USDT,FLOKI_USDT,STX_USDT,ANML_USDT,XPRT_USDT,GALA_USDT,GXS_USDT,TORN_USDT,KAI_USDT,1INCH_USDT,CHR_USDT,GAL_USDT,GLMR_USDT,CTX_USDT,CERE_USDT,CART_USDT,STRAX_USDT,MASK_USDT,MKR_USDT,AVAX_USDT,ENJ_USDT,YAM_USDT,ALPACA_USDT,DODO_USDT,MFT_USDT,CAKE_USDT,RNDR_USDT,CTSI_USDT,GRIN_USDT,MXC_USDT,ONT_USDT,ANKR_USDT,SLIM_USDT,FIL_USDT,CTK_USDT,ASR_USDT,FEG_USDT,SERO_USDT,RSS3_USDT,IRIS_USDT,XCH_USDT,ZRX_USDT,BAND_USDT,BADGER_USDT,DAO_USDT,EPS_USDT,THETA_USDT,BAKE_USDT,SHIB_USDT,MBOX_USDT,NBS_USDT,SNT_USDT,DREP_USDT,NFT_USDT,AUCTION_USDT,BOSON_USDT,O3_USDT,NULS_USDT,OMG_USDT,PEARL_USDT,HAPI_USDT,STG_USDT,IDV_USDT,HORD_USDT,ZIL_USDT,SUPER_USDT,DENT_USDT,REN_USDT,RAI_USDT,ZEN_USDT,ALGO_USDT,BLZ_USDT,BOR_USDT,SC_USDT,HEGIC_USDT,MOB_USDT,DORA_USDT,FOR_USDT,FLOW_USDT,RARI_USDT,DYDX_USDT,ATLAS_USDT,GST_USDT,REEF_USDT,HT_USDT,XYO_USDT,CHESS_USDT,BAT_USDT,NYM_USDT,RAMP_USDT,USDC_USDT,ICP_USDT,EPK_USDT,EXRD_USDT,DOT_USDT,COOK_USDT,CKB_USDT,YGG_USDT,CRU_USDT,ANC_USDT,FIS_USDT,ALCX_USDT,HIGH_USDT,BEAM_USDT,BSW_USDT,STAR_USDT,ROSE_USDT,CNNS_USDT,BZRX_USDT,WOO_USDT,SAFEMOON_USDT,VTHO_USDT,OM_USDT,LAMB_USDT,CHZ_USDT,AIOZ_USDT,EDEN_USDT,POND_USDT,ATOM_USDT,UNFI_USDT,FORTH_USDT,MLN_USDT,NEO_USDT,MOVR_USDT,RLC_USDT,FXS_USDT,ENS_USDT,ATA_USDT,XPR_USDT,NEST_USDT,XLM_USDT,AUTO_USDT,SNX_USDT,OCN_USDT,RSR_USDT,MITH_USDT,KAR_USDT,INJ_USDT,PLA_USDT,CYS_USDT,WAXP_USDT,VOXEL_USDT,CRV_USDT,FITFI_USDT,WHALE_USDT,WRX_USDT,TIDAL_USDT,C98_USDT,HNT_USDT,TONCOIN_USDT,DOGGY_USDT,SYS_USDT,NPXS_USDT,CRO_USDT,LEMD_USDT,RAY_USDT,PERL_USDT,CQT_USDT,CFX_USDT,TOMO_USDT,ACA_USDT,SDN_USDT,OKT_USDT,WILD_USDT,BNX_USDT,TRU_USDT,RACA_USDT,SWEAT_USDT,ACH_USDT,AKRO_USDT,BTM_USDT,TKO_USDT,GT_USDT,OCEAN_USDT,WNCG_USDT,BSV_USDT,GHST_USDT,CELR_USDT,LINA_USDT,SAND_USDT,APE_USDT,WICC_USDT,FIDA_USDT,ADA_USDT,PROPS_USDT,METIS_USDT,KAVA_USDT,AERGO_USDT,CONV_USDT,TFUEL_USDT,FRONT_USDT,API3_USDT,FARM_USDT,AE_USDT,LRC_USDT,IOTA_USDT,RFOX_USDT,PHA_USDT,XCN_USDT,NAS_USDT,KEEP_USDT,VIDY_USDT,HOT_USDT,MINA_USDT,ETHW_USDT,ALICE_USDT,HAI_USDT,LTC_USDT,LTO_USDT,DC_USDT,NU_USDT,IOST_USDT,RAD_USDT,POLS_USDT,OP_USDT,WXT_USDT,STR_USDT,YIELD_USDT,GM_USDT,SPA_USDT,BTCST_USDT,WEMIX_USDT,CLV_USDT,ICX_USDT,PET_USDT,STARL_USDT,HBAR_USDT,REDTOKEN_USDT,BTT_USDT,LINK_USDT,TLM_USDT,ARES_USDT,GTC_USDT,SUSHI_USDT,KEY_USDT,ALN_USDT,KDA_USDT,DVI_USDT,SXP_USDT,MAPS_USDT,BCD_USDT,SRM_USDT,WIN_USDT,ZEC_USDT,JASMY_USDT" - }, - "delivery": { - "enabled": "BTC_USD_20230331,BTC_USD_20221230,BTC_USDT_20221021,BTC_USDT_20221014", - "available": "BTC_USD_20221021,BTC_USD_20221014,BTC_USD_20230331,BTC_USD_20221230,BTC_USDT_20221021,BTC_USDT_20221014,BTC_USDT_20230331,BTC_USDT_20221230" - } - } - }, - "api": { - "authenticatedSupport": false, - "authenticatedWebsocketApiSupport": false, - "endpoints": { - "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" - }, - "credentials": { - "key": "Key", - "secret": "Secret" - }, - "credentialsValidator": { - "requiresKey": true, - "requiresSecret": true - } - }, - "features": { - "supports": { - "restAPI": true, - "restCapabilities": { - "tickerBatching": true, - "autoPairUpdates": true - }, - "websocketAPI": true, - "websocketCapabilities": {} + "name": "COINUT", + "enabled": true, + "verbose": false, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, + "websocketOrderbookBufferLimit": 5, + "baseCurrencies": "USD", + "currencyPairs": { + "requestFormat": { + "uppercase": true + }, + "configFormat": { + "uppercase": true, + "delimiter": "-" + }, + "useGlobalFormat": true, + "assetTypes": [ + "spot" + ], + "pairs": { + "spot": { + "enabled": "LTC-USDT", + "available": "LTC-CAD,LTC-SGD,USDT-USD,ETC-LTC,LTC-BTC,USDT-SGD,XMR-USDT,ZEC-SGD,ETH-USD,BTC-USDT,ETC-BTC,ETH-LTC,LTC-USD,BTC-USD,ETH-USDT,XMR-LTC,ZEC-USD,ETC-SGD,DAI-SGD,ZEC-CAD,BTC-SGD,ETH-BTC,ETH-SGD,LTC-USDT,ZEC-BTC,ZEC-USDT,BTC-CAD,XMR-BTC,ZEC-LTC,ETC-USDT,ETH-CAD" + } + } + }, + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret", + "clientID": "ClientID" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresClientID": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "autoPairUpdates": true + }, + "websocketAPI": true, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": false + } + }, + "bankAccounts": [ + { + "enabled": false, + "bankName": "", + "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ] }, - "enabled": { - "autoPairUpdates": true, - "websocketAPI": true - } - }, - "bankAccounts": [ { - "enabled": false, - "bankName": "", - "bankAddress": "", - "bankPostalCode": "", - "bankPostalCity": "", - "bankCountry": "", - "accountName": "", - "accountNumber": "", - "swiftCode": "", - "iban": "", - "supportedCurrencies": "" - } - ] - }, - { - "name": "Gemini", - "enabled": true, - "verbose": false, - "httpTimeout": 15000000000, - "websocketResponseCheckTimeout": 30000000, - "websocketResponseMaxLimit": 7000000000, - "websocketTrafficTimeout": 30000000000, - "websocketOrderbookBufferLimit": 5, - "baseCurrencies": "USD", - "currencyPairs": { - "requestFormat": { - "uppercase": true - }, - "configFormat": { - "uppercase": true - }, - "useGlobalFormat": true, - "assetTypes": [ - "spot" - ], - "pairs": { - "spot": { - "enabled": "BTCUSD", - "available": "BTCUSD,ETHBTC,ETHUSD,BCHUSD,BCHBTC,BCHETH,LTCUSD,LTCBTC,LTCETH,LTCBCH,ZECUSD,ZECBTC,ZECETH,ZECBCH,ZECLTC" - } - } - }, - "api": { - "authenticatedSupport": false, - "authenticatedWebsocketApiSupport": false, - "endpoints": { - "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" - }, - "credentials": { - "key": "Key", - "secret": "Secret" - }, - "credentialsValidator": { - "requiresKey": true, - "requiresSecret": true - } - }, - "features": { - "supports": { - "restAPI": true, - "restCapabilities": { - "autoPairUpdates": true - }, - "websocketAPI": true, - "websocketCapabilities": {} + "name": "CoinbasePro", + "enabled": true, + "verbose": false, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, + "websocketOrderbookBufferLimit": 5, + "baseCurrencies": "USD,GBP,EUR", + "currencyPairs": { + "requestFormat": { + "uppercase": true, + "delimiter": "-" + }, + "configFormat": { + "uppercase": true, + "delimiter": "-" + }, + "useGlobalFormat": true, + "assetTypes": [ + "spot", + "futures" + ], + "pairs": { + "spot": { + "enabled": "BTC-USD", + "available": "LTC-GBP,XLM-BTC,DASH-BTC,DAI-USDC,ZEC-USDC,XLM-EUR,ZRX-BTC,LTC-BTC,ETC-BTC,ETH-USD,XRP-EUR,BTC-USDC,REP-USD,EOS-BTC,ZEC-BTC,ETC-GBP,LINK-ETH,XRP-BTC,ZRX-USD,ETH-USDC,MANA-USDC,BTC-EUR,BCH-GBP,DNT-USDC,EOS-EUR,BCH-EUR,LTC-EUR,CVC-USDC,ETH-GBP,DASH-USD,ETH-EUR,XTZ-BTC,ZRX-EUR,BAT-ETH,BTC-GBP,ETC-USD,BAT-USDC,BCH-USD,GNT-USDC,ALGO-USD,LINK-USD,XLM-USD,ETH-BTC,EOS-USD,REP-BTC,ETH-DAI,XRP-USD,LTC-USD,ETC-EUR,BTC-USD,XTZ-USD,BCH-BTC,LOOM-USDC" + }, + "futures": { + "enabled": "BTC-PERP-INTX", + "available": "BTC-PERP-INTX" + } + } + }, + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret", + "clientID": "ClientID" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true, + "requiresClientID": true, + "requiresBase64DecodeSecret": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "autoPairUpdates": true + }, + "websocketAPI": true, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": false + } + }, + "bankAccounts": [ + { + "enabled": false, + "bankName": "", + "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ] }, - "enabled": { - "autoPairUpdates": true, - "websocketAPI": false - } - }, - "bankAccounts": [ { - "enabled": false, - "bankName": "", - "bankAddress": "", - "bankPostalCode": "", - "bankPostalCity": "", - "bankCountry": "", - "accountName": "", - "accountNumber": "", - "swiftCode": "", - "iban": "", - "supportedCurrencies": "" - } - ] - }, - { - "name": "HitBTC", - "enabled": true, - "verbose": false, - "httpTimeout": 15000000000, - "websocketResponseCheckTimeout": 30000000, - "websocketResponseMaxLimit": 7000000000, - "websocketTrafficTimeout": 30000000000, - "websocketOrderbookBufferLimit": 5, - "baseCurrencies": "USD", - "currencyPairs": { - "requestFormat": { - "uppercase": true - }, - "configFormat": { - "uppercase": true, - "delimiter": "-" - }, - "useGlobalFormat": true, - "assetTypes": [ - "spot" - ], - "pairs": { - "spot": { - "enabled": "BTC-USD", - "available": "BCN-BTC,BTC-USD,DASH-BTC,DOGE-BTC,DOGE-USD,EMC-BTC,ETH-BTC,LSK-BTC,LTC-BTC,LTC-USD,NXT-BTC,SBD-BTC,SC-BTC,STEEM-BTC,XDN-BTC,XEM-BTC,XMR-BTC,ARDR-BTC,ZEC-BTC,WAVES-BTC,MAID-BTC,DGD-BTC,SNGLS-BTC,1ST-BTC,TRST-BTC,TIME-BTC,GNO-BTC,REP-BTC,XMR-USD,DASH-USD,ETH-USD,NXT-USD,ZRC-BTC,BOS-BTC,DCT-BTC,ANT-BTC,AEON-BTC,GUP-BTC,PLU-BTC,LUN-BTC,EDG-BTC,RLC-BTC,SWT-BTC,TKN-BTC,WINGS-BTC,XAUR-BTC,AE-BTC,PTOY-BTC,ZEC-USD,XEM-USD,BCN-USD,XDN-USD,MAID-USD,ETC-BTC,ETC-USD,PLBT-BTC,BNT-BTC,SNT-ETH,CVC-USD,PAY-ETH,OAX-ETH,OMG-ETH,BQX-ETH,XTZ-BTC,DICE-BTC,PTOY-ETH,1ST-ETH,XAUR-ETH,TIME-ETH,DICE-ETH,SWT-ETH,XMR-ETH,ETC-ETH,DASH-ETH,ZEC-ETH,PLU-ETH,GNO-ETH,XRP-BTC,STRAT-USD,STRAT-BTC,SNC-ETH,ADX-ETH,BET-ETH,EOS-ETH,DENT-ETH,SAN-ETH,EOS-BTC,EOS-USD,XTZ-ETH,XTZ-USD,MYB-ETH,SUR-ETH,IXT-ETH,PLR-ETH,TIX-ETH,PRO-ETH,AVT-ETH,EVX-USD,DLT-BTC,BNT-ETH,BNT-USD,MANA-USD,DNT-BTC,FYP-BTC,OPT-BTC,TNT-ETH,STX-BTC,STX-ETH,STX-USD,TNT-USD,TNT-BTC,ENG-ETH,XUC-USD,SNC-BTC,SNC-USD,OAX-USD,OAX-BTC,ZRX-BTC,ZRX-ETH,ZRX-USD,RVT-BTC,PPC-BTC,PPC-USD,QTUM-ETH,IGNIS-ETH,BMC-BTC,BMC-ETH,BMC-USD,CND-BTC,CND-ETH,CND-USD,CDT-ETH,CDT-USD,FUN-BTC,FUN-ETH,FUN-USD,HVN-BTC,HVN-ETH,POE-BTC,POE-ETH,AMB-USD,AMB-ETH,AMB-BTC,HPC-BTC,PPT-ETH,MTH-BTC,MTH-ETH,LRC-BTC,LRC-ETH,ICX-BTC,ICX-ETH,NEO-BTC,NEO-ETH,NEO-USD,CSNO-BTC,ICX-USD,IND-ETH,KICK-BTC,YOYOW-BTC,CDT-BTC,XVG-BTC,XVG-ETH,XVG-USD,DGB-BTC,DGB-ETH,DGB-USD,DCN-ETH,DCN-USD,VIBE-BTC,ENJ-BTC,ENJ-ETH,ENJ-USD,ZSC-BTC,ZSC-ETH,ZSC-USD,TRX-BTC,TRX-ETH,TRX-USD,ART-BTC,EVX-BTC,EVX-ETH,SUB-BTC,SUB-ETH,SUB-USD,WTC-BTC,BTM-BTC,BTM-ETH,BTM-USD,LIFE-BTC,VIB-BTC,VIB-ETH,VIB-USD,DRT-ETH,STU-USD,OMG-BTC,PAY-BTC,PPT-BTC,SNT-BTC,BTG-BTC,BTG-ETH,BTG-USD,SMART-BTC,SMART-ETH,SMART-USD,XUC-ETH,XUC-BTC,LA-ETH,EDO-BTC,EDO-ETH,EDO-USD,HGT-ETH,IXT-BTC,SCL-BTC,ETP-BTC,ETP-ETH,ETP-USD,NEBL-BTC,NEBL-ETH,ARN-BTC,ARN-ETH,STU-BTC,STU-ETH,GVT-ETH,BTX-BTC,LTC-ETH,BCN-ETH,MAID-ETH,NXT-ETH,STRAT-ETH,XDN-ETH,XEM-ETH,PLR-BTC,SUR-BTC,BQX-BTC,DOGE-ETH,AMM-BTC,AMM-ETH,AMM-USD,DBIX-BTC,PRE-BTC,ZAP-BTC,DOV-BTC,DOV-ETH,XRP-ETH,XRP-USD,HSR-BTC,LEND-BTC,LEND-ETH,SPF-ETH,SBTC-BTC,SBTC-ETH,LOC-BTC,LOC-ETH,LOC-USD,SWFTC-BTC,SWFTC-ETH,SWFTC-USD,STAR-ETH,SBTC-USD,STORM-BTC,DIM-ETH,DIM-USD,DIM-BTC,NGC-BTC,NGC-ETH,NGC-USD,EMC-ETH,EMC-USD,MCO-BTC,MCO-ETH,MCO-USD,MANA-ETH,MANA-BTC,CPAY-ETH,DATA-BTC,DATA-ETH,DATA-USD,UTT-BTC,UTT-ETH,UTT-USD,KMD-BTC,KMD-ETH,KMD-USD,QTUM-USD,QTUM-BTC,SNT-USD,OMG-USD,EKO-BTC,EKO-ETH,ADX-BTC,ADX-USD,LSK-ETH,LSK-USD,PLR-USD,SUR-USD,BQX-USD,DRT-USD,REP-ETH,REP-USD,WAXP-BTC,WAXP-ETH,WAXP-USD,C20-BTC,C20-ETH,IDH-BTC,IDH-ETH,IPL-BTC,COV-BTC,COV-ETH,SENT-BTC,SENT-ETH,SENT-USD,SMT-BTC,SMT-ETH,SMT-USD,CHAT-BTC,CHAT-ETH,CHAT-USD,TRAC-ETH,JNT-ETH,UTK-BTC,UTK-ETH,UTK-USD,GNX-ETH,CHSB-BTC,CHSB-ETH,DAY-BTC,DAY-ETH,DAY-USD,NEU-BTC,NEU-ETH,NEU-USD,TAU-BTC,FLP-BTC,FLP-ETH,FLP-USD,R-BTC,R-ETH,EKO-USD,BCPT-ETH,BCPT-USD,PKT-BTC,PKT-ETH,BETR-BTC,BETR-ETH,HAND-ETH,HAND-USD,CHP-ETH,BCPT-BTC,ACT-BTC,ACT-ETH,ACT-USD,ADA-BTC,ADA-ETH,ADA-USD,SIG-BTC,MTX-BTC,MTX-ETH,MTX-USD,WIZ-BTC,WIZ-ETH,WIZ-USD,DADI-BTC,DADI-ETH,BDG-ETH,DATX-BTC,DATX-ETH,TRUE-BTC,DRG-BTC,DRG-ETH,BANCA-BTC,BANCA-ETH,ZAP-ETH,ZAP-USD,AUTO-BTC,SOC-BTC,OCN-BTC,OCN-ETH,STQ-BTC,STQ-ETH,XLM-BTC,XLM-ETH,XLM-USD,IOTA-BTC,IOTA-ETH,IOTA-USD,DRT-BTC,BETR-USD,ERT-BTC,CRPT-BTC,CRPT-USD,MESH-BTC,MESH-ETH,MESH-USD,IHT-BTC,IHT-ETH,IHT-USD,SCC-BTC,YCC-BTC,DAN-BTC,TEL-BTC,TEL-ETH,NCT-BTC,NCT-ETH,NCT-USD,BMH-BTC,BANCA-USD,BERRY-BTC,BERRY-ETH,BERRY-USD,GBX-BTC,GBX-ETH,GBX-USD,SHIP-BTC,SHIP-ETH,NANO-BTC,NANO-ETH,NANO-USD,LNC-BTC,KIN-ETH,ARDR-USD,FOTA-ETH,FOTA-BTC,CVT-BTC,CVT-ETH,CVT-USD,STQ-USD,GNT-BTC,GNT-ETH,GNT-USD,GET-BTC,MITH-BTC,MITH-ETH,MITH-USD,DADI-USD,TKY-BTC,ACAT-BTC,ACAT-ETH,ACAT-USD,BTX-USD,WIKI-BTC,WIKI-ETH,WIKI-USD,ONT-BTC,ONT-ETH,ONT-USD,FTX-BTC,FTX-ETH,NAVI-BTC,VME-ETH,NAVI-ETH,LND-ETH,CSM-BTC,NANJ-BTC,NTK-BTC,NTK-ETH,NTK-USD,AUC-BTC,AUC-ETH,CMCT-BTC,CMCT-ETH,CMCT-USD,MAN-BTC,MAN-ETH,MAN-USD,PNT-BTC,PNT-ETH,FXT-BTC,NEXO-BTC,PAT-BTC,PAT-ETH,XMC-BTC,FXT-ETH,XMC-ETH,XMC-USD,FDZ-BTC,FDZ-ETH,FDZ-USD,SPD-BTC,SPD-ETH,MITX-BTC,TIV-BTC,B2G-BTC,B2G-USD,HBZ-BTC,FACE-BTC,FACE-ETH,HBZ-ETH,HBZ-USD,CPT-BTC,PAT-USD,HTML-BTC,HTML-ETH,MITX-ETH,BTS-BTC,BNK-BTC,BNK-ETH,BNK-USD,TIV-ETH,TIV-USD,CSM-ETH,CSM-USD,INK-BTC,IOST-BTC,INK-ETH,INK-USD,CBC-BTC,IOST-USD,ZIL-BTC,ABYSS-BTC,ABYSS-ETH,ZIL-USD,BCI-BTC,CBC-ETH,CBC-USD,PITCH-BTC,PITCH-ETH,HTML-USD,TDS-BTC,TDS-ETH,TDS-USD,SBD-ETH,SBD-USD,DPN-BTC,UUU-BTC,UUU-ETH,XBP-BTC,ELEC-BTC,ELEC-ETH,ELEC-USD,QNTU-BTC,QNTU-ETH,QNTU-USD,IPL-ETH,IPL-USD,CENNZ-BTC,CENNZ-ETH,SWM-BTC,SPF-USD,SPF-BTC,LCC-BTC,HGT-BTC,ETH-TUSD,BTC-TUSD,LTC-TUSD,XMR-TUSD,ZRX-TUSD,NEO-TUSD,USD-TUSD,BTC-DAI,ETH-DAI,MKR-DAI,EOS-DAI,USD-DAI,MKR-BTC,MKR-ETH,MKR-USD,TUSD-DAI,NEO-DAI,LTC-DAI,XMR-DAI,XRP-DAI,NEXO-ETH,NEXO-USD,DWS-BTC,DWS-ETH,DWS-USD,APPC-BTC,APPC-ETH,APPC-USD,BIT-ETH,SPC-BTC,SPC-ETH,SPC-USD,REX-BTC,REX-ETH,REX-USD,ELF-BTC,ELF-USD,BCD-BTC,BCD-USD,CVCOIN-BTC,CVCOIN-ETH,CVCOIN-USD,EDG-ETH,EDG-USD,NLC2-BTC,DASH-EURS,ZEC-EURS,BTC-EURS,EOS-EURS,ETH-EURS,LTC-EURS,NEO-EURS,XMR-EURS,XRP-EURS,EURS-USD,EURS-TUSD,EURS-DAI,MNX-USD,ROX-ETH,ZPR-ETH,MNX-BTC,MNX-ETH,KIND-BTC,KIND-ETH,ENGT-BTC,ENGT-ETH,PMA-BTC,PMA-ETH,TV-BTC,TV-ETH,TV-USD,BAT-BTC,BAT-ETH,BAT-USD,SRN-BTC,SRN-ETH,SRN-USD,SVD-BTC,SVD-ETH,SVD-USD,GST-BTC,GST-ETH,GST-USD,BNB-BTC,BNB-ETH,BNB-USD,DIT-BTC,DIT-ETH,POA20-BTC,PROC-BTC,POA20-ETH,POA20-USD,POA20-DAI,NIM-BTC,USE-BTC,USE-ETH,DAV-BTC,DAV-ETH,ABTC-BTC,NIM-ETH,ABA-BTC,ABA-ETH,ABA-USD,BCN-EOS,LTC-EOS,XMR-EOS,DASH-EOS,TRX-EOS,NEO-EOS,ZEC-EOS,LSK-EOS,XEM-EOS,XRP-EOS,RCN-BTC,RCN-ETH,RCN-USD,HMQ-BTC,HMQ-ETH,MYST-BTC,MYST-ETH,USD-GUSD,BTC-GUSD,ETH-GUSD,EOS-GUSD,AXPR-BTC,AXPR-ETH,DAG-BTC,DAG-ETH,BITS-BTC,BITS-ETH,BITS-USD,CDCC-BTC,CDCC-ETH,CDCC-USD,VET-BTC,VET-ETH,VET-USD,SILK-ETH,BOX-BTC,BOX-ETH,BOX-EURS,BOX-EOS,VOCO-BTC,VOCO-ETH,VOCO-USD,PASS-BTC,PASS-ETH,SLX-BTC,SLX-USD,PBTT-BTC,PMA-USD,TRAD-BTC,DGTX-BTC,DGTX-ETH,DGTX-USD,MRK-BTC,MRK-ETH,DGB-TUSD,SNBL-BTC,BCH-BTC,BCH-USD,BSV-BTC,BSV-USD,BKX-BTC,NPLC-BTC,NPLC-ETH,ETN-BTC,ETN-ETH,ETN-USD,DTR-BTC,DTR-ETH,TDP-BTC,HBT-ETH,PXG-BTC,PXG-USD,BTC-PAX,ETH-PAX,USD-PAX,BTC-USDC,ETH-USDC,USD-USDC,TUSD-USDC,DAI-USDC,EOS-PAX,CLO-BTC,CLO-ETH,CLO-USD,PETH-BTC,PETH-ETH,PETH-USD,BRD-BTC,BRD-ETH,NMR-BTC,SALT-BTC,SALT-ETH,POLY-BTC,POLY-ETH,POWR-BTC,POWR-ETH,STORJ-BTC,STORJ-ETH,STORJ-USD,MLN-BTC,MLN-ETH,BDG-BTC,POA-ETH,POA-BTC,POA-USD,POA-DAI,KIN-BTC,VEO-BTC,PLA-BTC,PLA-ETH,PLA-USD,BTT-BTC,BTT-USD,BTT-ETH,ZEN-BTC,ZEN-ETH,ZEN-USD,GRIN-BTC,GRIN-ETH,GRIN-USD,FET-BTC,HT-BTC,HT-USD,XZC-BTC,XZC-ETH,XZC-USD,VRA-BTC,VRA-ETH,BTC-KRWB,USD-KRWB,WBTC-ETH,CRO-BTC,CRO-ETH,CRO-USD,GAS-BTC,GAS-ETH,GAS-USD,ORMEUS-BTC,ORMEUS-ETH,SWM-ETH,SWM-USD,PRE-ETH,PHX-BTC,PHX-ETH,PHX-USD,BET-BTC,USD-EOSDT,BTC-EOSDT,ETH-EOSDT,EOS-EOSDT,DAI-EOSDT,NUT-BTC,NUT-EOS,NUT-USD,CUTE-BTC,CUTE-ETH,CUTE-USD,CUTE-EOS,XCON-BTC,DCR-BTC,DCR-ETH,DCR-USD,MG-BTC,MG-ETH,MG-EOS,MG-USD,GNX-BTC,PRO-BTC,EURS-EOSDT,TUSD-EOSDT,ECOIN-BTC,ECOIN-ETH,ECOIN-USD,AGI-BTC,LOOM-BTC,LOOM-ETH,BLZ-BTC,QKC-BTC,QKC-ETH,KNC-BTC,KNC-ETH,KNC-USD,KEY-BTC,KEY-ETH,ATOM-BTC,ATOM-USD,ATOM-ETH,BRDG-BTC,BRDG-ETH,BRDG-USD,MTL-BTC,MTL-ETH,EXP-BTC,BTCB-BTC,PBT-BTC,PBT-ETH,LINK-BTC,LINK-ETH,LINK-USD,USD-USDT20,PHB-BTC,BCH-ETH,BCH-DAI,BCH-TUSD,BCH-EURS,DAPP-BTC,DAPP-EOS,BTC-USDT20,DENT-BTC,DENT-USD,NJBC-BTC,NJBC-ETH,XRC-BTC,EOS-BCH,LTC-BCH,XRP-BCH,TRX-BCH,XLM-BCH,ETC-BCH,DASH-BCH,ZEC-BCH,BKX-USD,LAMB-BTC,NPXS-BTC,HBAR-BTC,HBAR-USD,ONE-BTC,RFR-BTC,RFR-USD,BUSD-USD,PAXG-BTC,PAXG-USD,REN-BTC,IGNIS-BTC,CEL-BTC,CEL-ETH,WIN-USD,ADK-BTC,PART-BTC,SOZ-BTC,SOZ-ETH,SOZ-USD,WAVES-USD,ADA-BCH,ONT-BCH,XMR-BCH,ATOM-BCH,LINK-BCH,OMG-BCH,WAVES-BCH,IOTX-BTC,HOT-BTC,SLV-BTC,HEDG-BTC,CHZ-BTC,CHZ-USD,COCOS-BTC,COCOS-USD,SEELE-BTC,SEELE-USD,MDA-BTC,LEO-USD,REM-BTC,REM-ETH,REM-USD,SCD-DAI,BTC-BUSD,RVN-BTC,BST-BTC,ERD-BTC,KRL-BTC,FTT-BTC,FTT-USD,RAISE-BTC,RAISE-ETH" - } - } - }, - "api": { - "authenticatedSupport": false, - "authenticatedWebsocketApiSupport": false, - "endpoints": { - "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" - }, - "credentials": { - "key": "Key", - "secret": "Secret" - }, - "credentialsValidator": { - "requiresKey": true, - "requiresSecret": true - } - }, - "features": { - "supports": { - "restAPI": true, - "restCapabilities": { - "tickerBatching": true, - "autoPairUpdates": true - }, - "websocketAPI": true, - "websocketCapabilities": {} + "name": "EXMO", + "enabled": true, + "verbose": false, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, + "websocketOrderbookBufferLimit": 5, + "baseCurrencies": "USD,EUR,RUB,PLN,UAH", + "currencyPairs": { + "requestFormat": { + "uppercase": true, + "delimiter": "_", + "separator": "," + }, + "configFormat": { + "uppercase": true, + "delimiter": "_" + }, + "useGlobalFormat": true, + "assetTypes": [ + "spot" + ], + "pairs": { + "spot": { + "enabled": "BTC_USD,LTC_USD", + "available": "BCH_RUB,DASH_RUB,EOS_USD,ETH_TRY,GNT_ETH,LTC_USD,PTI_USDT,XRP_BTC,EXM_BTC,BTG_BTC,ETC_RUB,BTG_USD,NEO_RUB,XMR_BTC,ZRX_ETH,MNX_BTC,USDC_BTC,XRP_EUR,SMART_USD,EOS_BTC,MNX_ETH,ZEC_BTC,BCH_USD,WAVES_USD,TRX_BTC,XRP_TRY,DASH_USD,DOGE_USD,ETZ_USDT,GUSD_USD,MNC_BTC,ZEC_USD,DCR_BTC,DXT_USD,PTI_RUB,XMR_ETH,ZRX_USD,DAI_RUB,MNC_USD,XLM_TRY,DAI_BTC,BTC_EUR,LTC_EUR,OMG_BTC,PTI_EOS,SMART_RUB,XTZ_USD,HP_EXM,ADA_USD,OMG_ETH,QTUM_USD,TRX_RUB,USDC_ETH,USDC_USDT,USD_RUB,BTC_UAH,BCH_USDT,ETH_PLN,KICK_RUB,LSK_RUB,SMART_BTC,XMR_UAH,XRP_USD,GUSD_BTC,QTUM_ETH,USDT_EUR,BTC_RUB,DCR_UAH,ETH_RUB,DOGE_BTC,ETZ_BTC,INK_USD,LTC_UAH,BTT_UAH,BTC_USDT,MNC_ETH,XTZ_ETH,BTC_TRY,DXT_BTC,KICK_USDT,OMG_USD,WAVES_BTC,XLM_BTC,BTCZ_BTC,GNT_BTC,LSK_BTC,LTC_RUB,NEO_BTC,XEM_UAH,XMR_USD,ZAG_BTC,GAS_USD,LTC_BTC,TRX_UAH,XEM_EUR,XMR_RUB,XTZ_RUB,ETZ_ETH,ETC_BTC,GUSD_RUB,INK_BTC,LSK_USD,MNX_USD,SMART_EUR,VLX_BTC,BCH_ETH,XMR_EUR,ADA_ETH,QTUM_BTC,XEM_USD,ATMCASH_BTC,ADA_BTC,ETH_EUR,TRX_USD,USDC_USD,BCH_BTC,ETH_UAH,KICK_BTC,WAVES_RUB,XEM_BTC,ETH_BTC,BCH_EUR,BTT_BTC,ROOBEE_BTC,XLM_USD,XRP_ETH,ETH_USD,MKR_DAI,XTZ_BTC,DAI_USD,BCH_UAH,INK_ETH,KICK_ETH,MKR_BTC,NEO_USD,XRP_USDT,ZEC_EUR,BTC_USD,XRP_RUB,EOS_EUR,ETH_USDT,USDT_UAH,XRP_UAH,ZEC_RUB,HP_BTC,BTT_RUB,DAI_ETH,DASH_UAH,DASH_USDT,ETH_LTC,GAS_BTC,USDT_USD,BTG_ETH,XLM_RUB,WAVES_ETH,USDT_RUB,ZRX_BTC,DASH_BTC,DCR_RUB,ETC_USD,HB_BTC,PTI_BTC,BTC_PLN" + } + } + }, + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "tickerBatching": true, + "autoPairUpdates": true + }, + "websocketAPI": false, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": false + } + }, + "bankAccounts": [ + { + "enabled": false, + "bankName": "", + "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ] }, - "enabled": { - "autoPairUpdates": true, - "websocketAPI": false - } - }, - "bankAccounts": [ { - "enabled": false, - "bankName": "", - "bankAddress": "", - "bankPostalCode": "", - "bankPostalCity": "", - "bankCountry": "", - "accountName": "", - "accountNumber": "", - "swiftCode": "", - "iban": "", - "supportedCurrencies": "" - } - ] - }, - { - "name": "Huobi", - "enabled": true, - "verbose": false, - "httpTimeout": 15000000000, - "websocketResponseCheckTimeout": 30000000, - "websocketResponseMaxLimit": 7000000000, - "websocketTrafficTimeout": 30000000000, - "websocketOrderbookBufferLimit": 5, - "baseCurrencies": "USD", - "currencyPairs": { - "requestFormat": { - "uppercase": false - }, - "configFormat": { - "uppercase": true, - "delimiter": "-" - }, - "useGlobalFormat": true, - "assetTypes": [ - "spot", - "coinmarginedfutures", - "futures" - ], - "pairs": { - "coinmarginedfutures": { - "assetEnabled": true, - "enabled": "BTC-USD", - "available": "BTC-USD,ETH-USD,LINK-USD,DOT-USD,ADA-USD,LTC-USD,XRP-USD,TRX-USD,DOGE-USD", - "requestFormat": { - "uppercase": true, - "delimiter": "-" - }, - "configFormat": { - "uppercase": true, - "delimiter": "-" - } - }, - "futures": { - "assetEnabled": true, - "enabled": "BTC-230915", - "available": "BTC-230915,BTC-230922,BTC-230929,ETH-230915,ETH-230922,ETH-230929,TRX-230915,TRX-230922", - "requestFormat": { - "uppercase": true - }, - "configFormat": { - "uppercase": true, - "delimiter": "-" - } - }, - "spot": { - "enabled": "BTC-USDT", - "available": "PROPY-ETH,IOTA-BTC,UGAS-ETH,PAI-USDT,BSV-HUSD,MTX-ETH,BCH-BTC,LTC-HT,SOC-USDT,WXT-BTC,SALT-BTC,RCN-ETH,PNT-ETH,TT-USDT,AIDOC-ETH,BIX-BTC,OCN-USDT,QTUM-ETH,KCASH-ETH,SNT-USDT,LUN-BTC,QASH-BTC,ITC-BTC,NAS-BTC,XMR-BTC,TNT-ETH,UC-ETH,FAIR-BTC,PC-ETH,YEE-BTC,PAY-ETH,XMX-BTC,CRE-USDT,BAT-ETH,BHT-USDT,CKB-HT,LAMB-HT,AE-USDT,QUN-ETH,LYM-BTC,BCH-HT,BHT-BTC,RUFF-ETH,CNN-BTC,FOR-USDT,GTC-ETH,TRX-ETH,ELA-USDT,ACT-ETH,SMT-ETH,BUT-ETH,BCH-USDT,ICX-BTC,MEET-BTC,NCC-BTC,APPC-BTC,GVE-ETH,TNB-BTC,STEEM-ETH,18C-ETH,LBA-BTC,EKO-BTC,REQ-BTC,SOC-BTC,BOX-ETH,ELF-BTC,ZRX-ETH,LET-USDT,HT-BTC,TUSD-HUSD,EGCC-BTC,WTC-BTC,ATP-USDT,DOCK-USDT,PAI-BTC,ONT-ETH,IRIS-BTC,BTT-ETH,SC-BTC,XZC-BTC,LBA-USDT,HT-USDT,VET-ETH,KMD-ETH,SHE-ETH,PORTAL-BTC,ONE-BTC,BIX-USDT,RCCC-BTC,SKM-USDT,XTZ-ETH,SWFTC-BTC,RSR-BTC,LINK-ETH,DATX-BTC,HPT-HT,GET-ETH,BLZ-ETH,CTXC-USDT,CNNS-USDT,PVT-HT,ITC-USDT,LTC-BTC,NCASH-BTC,HOT-ETH,ADA-USDT,ADX-BTC,NODE-USDT,TRIO-BTC,GXC-ETH,SNT-BTC,FOR-BTC,DBC-BTC,UUU-USDT,CVCOIN-ETH,RSR-USDT,CRO-USDT,OCN-BTC,NEW-USDT,EGT-USDT,MANA-BTC,CMT-USDT,WXT-HT,XRP-BTC,MT-ETH,PAX-HUSD,LSK-ETH,IOTA-USDT,SRN-ETH,ZIL-ETH,ELF-USDT,LXT-ETH,LAMB-BTC,CRE-HT,CKB-BTC,XVG-BTC,BSV-BTC,BFT-BTC,WPR-ETH,HT-HUSD,POWR-BTC,MANA-ETH,ENG-ETH,ZJLT-ETH,SNC-ETH,ATOM-ETH,WICC-USDT,KAN-ETH,DGD-BTC,VSYS-HT,BCD-BTC,BTM-ETH,DOGE-USDT,MEX-BTC,BTG-BTC,DAC-ETH,DAT-BTC,GRS-ETH,ADX-ETH,EM-HT,GXC-USDT,CVC-BTC,OMG-ETH,SSP-ETH,OGO-HT,CMT-ETH,POLY-ETH,XZC-USDT,THETA-USDT,XEM-USDT,LOL-USDT,BCH-HUSD,GSC-BTC,DOGE-ETH,MDS-BTC,BTS-ETH,CTXC-BTC,MCO-BTC,BCX-BTC,ZLA-ETH,EKT-USDT,MAN-BTC,BLZ-BTC,ATOM-USDT,LOL-BTC,HPT-USDT,EM-BTC,EOS-USDT,WAN-BTC,GNT-BTC,CRO-BTC,MANA-USDT,SEELE-USDT,FSN-BTC,VIDY-HT,USDC-HUSD,LTC-HUSD,XRP-USDT,VSYS-BTC,STORJ-BTC,LOOM-ETH,SKM-BTC,LINK-USDT,TT-HT,QSP-ETH,ETN-BTC,FSN-HT,NODE-BTC,HC-USDT,PHX-BTC,XLM-BTC,RCCC-ETH,LTC-USDT,UUU-BTC,SEELE-ETH,PVT-BTC,HC-ETH,REN-ETH,KAN-USDT,EOS-ETH,BSV-USDT,BTS-USDT,KMD-BTC,OGO-USDT,THETA-ETH,MUSK-BTC,CNNS-HT,ETC-BTC,COVA-BTC,BTT-TRX,XMR-USDT,MTN-ETH,QUN-BTC,NAS-USDT,ELA-ETH,HIT-ETH,BTT-USDT,EKT-ETH,TOS-BTC,GAS-ETH,DCR-USDT,ONT-BTC,NEW-HT,NEXO-BTC,ETH-USDT,WXT-USDT,FOR-HT,ADA-BTC,EVX-ETH,VET-BTC,ZEC-USDT,NANO-ETH,IOST-HT,BCV-ETH,REN-USDT,NULS-ETH,ACT-USDT,LET-ETH,BTM-USDT,MEET-ETH,AKRO-HT,ARDR-BTC,DCR-ETH,NANO-USDT,BTC-HUSD,ALGO-BTC,IIC-ETH,BHD-BTC,KNC-ETH,ATP-BTC,ZRX-BTC,ABT-BTC,18C-BTC,XMR-ETH,WAXP-BTC,CVNT-BTC,MX-USDT,OST-ETH,NKN-BTC,TOPC-BTC,GNX-BTC,FTT-USDT,ONE-HT,DGB-ETH,NULS-USDT,DASH-BTC,UIP-BTC,KCASH-HT,WICC-ETH,EKO-ETH,EGT-HT,IRIS-USDT,STK-ETH,MXC-BTC,NAS-ETH,OMG-USDT,SMT-BTC,BUT-BTC,HIT-USDT,BAT-BTC,IRIS-ETH,NKN-HT,PC-BTC,TOP-USDT,GTC-BTC,LSK-BTC,ITC-ETH,DTA-BTC,HOT-BTC,BTT-BTC,FAIR-ETH,DOCK-ETH,QTUM-BTC,ZEN-BTC,ZIL-BTC,RCN-BTC,FTI-BTC,BHD-USDT,VIDY-USDT,LUN-ETH,DBC-ETH,TOPC-ETH,IIC-BTC,STEEM-USDT,IOTA-ETH,KCASH-BTC,RUFF-BTC,APPC-ETH,MT-BTC,SOC-ETH,GT-HT,PROPY-BTC,AIDOC-BTC,ACT-BTC,LYM-ETH,CHAT-BTC,SWFTC-ETH,ETH-BTC,UIP-USDT,UGAS-BTC,XRP-HUSD,ALGO-USDT,TNT-BTC,ONT-USDT,YEE-ETH,AKRO-BTC,TRX-USDT,OCN-ETH,SRN-BTC,DASH-USDT,XMX-ETH,NANO-BTC,QASH-ETH,EOS-HT,GT-BTC,XTZ-USDT,ARPA-USDT,SALT-ETH,BKBT-ETH,MTX-BTC,SMT-USDT,GXC-BTC,VIDY-BTC,FTT-HT,LAMB-ETH,TRX-BTC,TRIO-ETH,BFT-ETH,LINK-BTC,AE-ETH,NULS-BTC,BHD-HT,AST-ETH,NEO-USDT,EDU-BTC,CVCOIN-BTC,GVE-BTC,GET-BTC,ZRX-USDT,ELF-ETH,DATX-ETH,ADA-ETH,TOP-HT,NCASH-ETH,QTUM-USDT,ETC-HT,ZIL-USDT,TNB-ETH,BIX-ETH,SHE-BTC,PNT-BTC,BTC-USDT,PORTAL-ETH,WAVES-USDT,XZC-ETH,HT-ETH,POLY-BTC,MCO-ETH,MUSK-ETH,PAI-ETH,LXT-USDT,UTK-BTC,RTE-BTC,NCC-ETH,HB10-USDT,BOX-BTC,RDN-ETH,ARPA-BTC,LBA-ETH,CNN-ETH,AAC-ETH,XTZ-BTC,IDT-BTC,AKRO-USDT,IOST-BTC,GT-USDT,WAN-ETH,ETN-ETH,PVT-USDT,NEO-BTC,WAVES-ETH,ONE-USDT,ZEC-BTC,SKM-HT,IOST-ETH,NPXS-ETH,CVC-ETH,CMT-BTC,COVA-ETH,ARDR-ETH,RDN-BTC,DCR-BTC,REN-BTC,YCC-ETH,MX-HT,NEXO-ETH,XLM-ETH,YCC-BTC,ENG-BTC,CNNS-BTC,ZLA-BTC,QSP-BTC,MAN-ETH,UUU-ETH,ETH-HUSD,RTE-ETH,ATP-HT,BTM-BTC,DAC-BTC,TOS-ETH,LAMB-USDT,DASH-HT,NPXS-BTC,NEW-BTC,FTT-BTC,EOS-HUSD,GRS-BTC,POWR-ETH,VET-USDT,AAC-BTC,MX-BTC,MTN-BTC,XVG-ETH,GNX-ETH,SSP-BTC,WAVES-BTC,EGT-BTC,CTXC-ETH,IDT-ETH,STK-BTC,WICC-BTC,UTK-ETH,CRO-HT,LXT-BTC,GSC-ETH,OMG-BTC,XRP-HT,DGB-BTC,IOST-USDT,CVNT-ETH,GAS-BTC,HIT-BTC,CKB-USDT,ARPA-HT,RUFF-USDT,HC-BTC,WTC-ETH,MDS-USDT,ABT-ETH,ALGO-ETH,BIFI-BTC,KNC-BTC,TT-BTC,LET-BTC,NKN-USDT,PAY-BTC,DTA-USDT,AE-BTC,UC-BTC,VSYS-USDT,USDT-HUSD,EOS-BTC,STEEM-BTC,DOGE-BTC,NODE-HT,MDS-ETH,CRE-BTC,GNT-USDT,UIP-ETH,AST-BTC,XEM-BTC,ZEN-ETH,EDU-ETH,MEX-ETH,EKT-BTC,CVC-USDT,WAXP-ETH,REQ-ETH,OST-BTC,STORJ-USDT,SBTC-BTC,DGD-ETH,SC-ETH,WTC-USDT,THETA-BTC,DTA-ETH,BCV-BTC,SNC-BTC,RSR-HT,KAN-BTC,ELA-BTC,ATOM-BTC,BKBT-BTC,FSN-USDT,EM-USDT,WPR-BTC,TOP-BTC,BTS-BTC,EGCC-ETH,MTL-BTC,GNT-ETH,SEELE-BTC,EVX-BTC,FTI-ETH,BAT-USDT,MT-HT,LOL-HT,ICX-ETH,LOOM-BTC,ZJLT-BTC,XLM-USDT,OGO-BTC,DOCK-BTC,CHAT-ETH,DAT-ETH,ETC-USDT,HPT-BTC,BHT-HT" - } - } - }, - "api": { - "authenticatedSupport": false, - "authenticatedWebsocketApiSupport": false, - "endpoints": { - "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" - }, - "credentials": { - "key": "Key", - "secret": "Secret" - }, - "credentialsValidator": { - "requiresKey": true, - "requiresSecret": true - } - }, - "features": { - "supports": { - "restAPI": true, - "restCapabilities": { - "tickerBatching": true, - "autoPairUpdates": true - }, - "websocketAPI": true, - "websocketCapabilities": {} + "name": "GateIO", + "enabled": true, + "verbose": false, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, + "websocketOrderbookBufferLimit": 5, + "baseCurrencies": "USD", + "currencyPairs": { + "requestFormat": { + "uppercase": true, + "delimiter": "_" + }, + "configFormat": { + "uppercase": true, + "delimiter": "_" + }, + "useGlobalFormat": true, + "assetTypes": [ + "spot", + "option", + "futures", + "cross_margin", + "margin", + "delivery" + ], + "pairs": { + "spot": { + "enabled": "BTC_USDT,IHT_ETH,AME_ETH,CEUR_ETH,ALEPH_USDT,OMG_TRY,BTC_TRY,OGN_USDT,ALA_USDT", + "available": "IHT_ETH,AME_ETH,CEUR_ETH,ALEPH_USDT,OMG_TRY,BTC_TRY,OGN_USDT,ALA_USDT,HC_USDT,BTC_USDT,QNT_USDT,QTUM_ETH,MAHA_ETH,XCN_ETH,POOL_USDT,KGC_USDT,MCO2_USDT,HARD_USDT,GHNY_USDT,FTT_ETH,K21_ETH,FINE_USDT,REP_USDT,SBR_USDT,SKM_ETH,QLC_ETH,GAS_BTC,ALICE3L_USDT,BAO_USDT,FALCONS_USDT,ANT_USDT,VIDYX_USDT,DXCT_ETH,SMTY_ETH,HERO_USDT,SHARE_USDT,FIN_USDT,MTV_USDT,MOO_USDT,SMTY_USDT,ORAO_USDT,AE_ETH,SUSD_USDT,MAN_USDT,UNDEAD_USDT,MC_USDT,VET_USDT,WAXP_ETH,MDA_ETH,LYXE_USDT,SPS_USDT,STX_ETH,WSIENNA_USDT,NAOS_BTC,NFTX_USDT,OPUL_USDT,ICP3L_USDT,SFI_ETH,CTT_USDT,BSV3L_USDT,DFI_USDT,DIS_ETH,FET_USDT,ARG_USDT,VELO_USDT,NSBT_BTC,GSE_ETH,HNS_BTC,DOGEDASH_ETH,BACON_USDT,DUSK_USDT,MAPE_USDT,EGLD_ETH,TDROP_USDT,C983L_USDT,FAN_ETH,CZZ_USDT,FIU_USDT,SWRV_USDT,ONT_ETH,KINE_ETH,IMX_ETH,SPAY_ETH,CFG_BTC,RACA3S_USDT,UNO_ETH,DMLG_USDT,SAKE_ETH,ASM_USDT,CUSD_ETH,SUSD_ETH,ONC_USDT,DAI_USDT,VEGA_ETH,PYM_USDT,LTC_TRY,LOKA_USDT,NIF_USDT,BNC_USDT,PERL_ETH,MATIC3S_USDT,STMX_USDT,SKL_USDT,WLKN_USDT,XYO_ETH,AMPL3S_USDT,WEX_USDT,ULU_ETH,LIKE_ETH,INSUR_ETH,CAKE_ETH,SXP_ETH,COTI_USDT,ORT_USDT,RACA3L_USDT,GASDAO_USDT,AVA_USDT,OPA_USDT,ATS_USDT,VEGA_USDT,KILT_USDT,HIT_ETH,BRISE_USDT,SAUBER_USDT,SPS_ETH,FSN_USDT,EOS_ETH,KYL_USDT,REVV_ETH,SVT_ETH,XRP_USDT,DYDX3S_USDT,MANA3S_USDT,ICP_ETH,ALICE3S_USDT,PCX_USDT,LEMO_ETH,MKR_ETH,WOO3S_USDT,CART_ETH,MATIC_USDT,UNI_USD,MOBI_BTC,ICP3S_USDT,BEAM_BTC,CRO3S_USDT,FTT_USDT,IQ_ETH,TAP_USDT,MLT_USDT,RBN_USDT,AMPL3L_USDT,KINT_ETH,HECH_USDT,GAFI_ETH,WOO3L_USDT,TAI_USDT,HERA_USDT,AST_USDT,DHV_ETH,XAVA_USDT,LSS_USDT,SNX3S_USDT,PBR_USDT,XEND_ETH,SHR_ETH,PRQ_USDT,MATIC3L_USDT,WIT_ETH,LPOOL_USDT,PSP_USDT,BXC_USDT,CBK_USDT,REVO_BTC,MANA3L_USDT,ALPINE_USDT,DEGO_USDT,SIN_USDT,OCT_USDT,KZEN_USDT,L3P_USDT,FX_ETH,ONC_ETH,AXS_USD,BORA_USDT,XTZ_ETH,NEO3L_USDT,FROG_USDT,CHAMP_USDT,XNFT_USDT,BCH3S_USDT,FORT_USDT,XLM_TRY,TRX_TRY,CRPT_USDT,ROUTE_USDT,GLM_USDT,SLRS_ETH,TIMECHRONO_USDT,VRA_USDT,ONS_USDT,ZEC3L_USDT,KFT_ETH,TFD_ETH,FRA_USDT,RDN_ETH,BLANK_USDT,IOST3L_USDT,DDD_USDT,DOGE_USD,UNQ_USDT,API33S_USDT,AKRO_ETH,GITCOIN_USDT,THG_USDT,BDX_USDT,LTO_ETH,FLY_USDT,CREDIT_USDT,RENA_USDT,ZRX_ETH,CRP_ETH,NBOT_USDT,HT3L_USDT,DORA_ETH,LLT_SNET,ASD_USDT,XMR_USDT,SSV_BTC,FTM_USDT,XELS_USDT,MTL_ETH,ADX_ETH,API33L_USDT,PIG_USDT,RUNE_ETH,QRDO_BTC,THN_USDT,BCUG_USDT,EGG_ETH,GGM_USDT,HOTCROSS_USDT,SKYRIM_USDT,BTG_USDT,POT_USDT,CS_USDT,XVS_USDT,A5T_USDT,GOD_BTC,WAVES_USDT,LSK_BTC,BTT_TRY,YIN_USDT,PEOPLE_USDT,SPELL_ETH,POLC_USDT,BZZ3L_USDT,UNO_USDT,HDV_USDT,CELL_USDT,DAR_ETH,MIR_ETH,FODL_USDT,SRM_ETH,PROS_USDT,ORN_ETH,WAG_USDT,RBC_ETH,VENT_USDT,WND_USDT,AAA_ETH,BSCS_ETH,ZEC3S_USDT,DOS_USDT,HT3S_USDT,LAND_USDT,BCD_BTC,RING_USDT,FIRO_USDT,AUDIO_USDT,KUMA_USDT,SOLO_BTC,CRBN_USDT,MM_ETH,SAKE_USDT,XMARK_USDT,SLP_USDT,F2C_USDT,LUNA_USDT,ONIT_USDT,FTM3L_USDT,POPK_USDT,RFUEL_USDT,NEO3S_USDT,MIR_USDT,ETC_BTC,STETH_ETH,MANA_TRY,ALPACA_ETH,WAXL_USDT,EGS_USDT,DAR_USDT,KSM_USDT,XMARK_ETH,QTUM_USDT,C983S_USDT,INDI_ETH,DOGE3S_USDT,RVN_USDT,NOS_USDT,ALU_ETH,ALD_ETH,LUNC_USDT,ARES_ETH,BZZ3S_USDT,TNC_ETH,ONE_USDT,SENC_ETH,FTM3S_USDT,FLUX_USDT,STORJ_ETH,MTN_ETH,MNW_USDT,BLES_ETH,STG_ETH,LIME_ETH,WAGYU_USDT,XRP_TRY,XOR_ETH,ANGLE_USDT,DOGA_USDT,JFI_USDT,USDG_USDT,GRND_USDT,BOND_ETH,DMTR_USDT,YIN_ETH,ENJ_USDT,GOLDMINER_USDT,WIT_USDT,DOGE3L_USDT,FORM_USDT,LYXE_ETH,MLK_USDT,VR_USDT,DMS_USDT,LRC_TRY,ONX_USDT,ASK_USDT,ISP_ETH,TXT_USDT,IOEN_ETH,NIIFI_USDT,VRX_USDT,DOME_USDT,CTSI_USDT,ORBS_USDT,ZLW_ETH,FIL_USDT,FTI_ETH,CTK_USDT,ASR_USDT,GBPT_BTC,CBK_BTC,MBOX_ETH,RAM_USDT,IRIS_USDT,AME_USDT,KUB_USDT,ENV_USDT,RING_ETH,COTI3S_USDT,JULD_ETH,POLK_ETH,ACH3S_USDT,HYVE_ETH,MIX_ETH,RFT_USDT,ORAO_ETH,IHT_USDT,POLYPAD_USDT,CTRC_USDT,SFUND_USDT,MXC_BTC,DDD_BTC,CHESS_ETH,SHIB_USDT,SN_USDT,NFT_USDT,ASTRO_ETH,SOLO_USDT,TSHP_USDT,AMP_USDT,BTCST_ETH,VLXPAD_USDT,GAN_USDT,O3_USDT,WBTC_TRY,TULIP_USDT,GS_ETH,DX_ETH,NYZO_ETH,TT_USDT,SHILL_USDT,RATING_ETH,DUST_USDT,PSB_USDT,BFT1_USDT,GALA_ETH,XDC_USDT,LON3L_USDT,HE_USDT,ICE_ETH,LINK_ETH,SKU_USDT,QLC_USDT,DOMI_USDT,IDEA_USDT,METO_USDT,LIFE_ETH,ACH3L_USDT,POWR_ETH,VET_ETH,ALGO_USDT,BLIN_USDT,BAO_ETH,RBLS_USDT,TORN_ETH,VRT_USDT,BLANKV2_ETH,AUCTION_ETH,OLE_USDT,NWC_BTC,DOT5S_USDT,M RCH_ETH,SUNNY_ETH,GST_USDT,ENJ_TRY,KIBA_USDT,KLAP_USDT,SNTR_ETH,CELR_ETH,CHESS_USDT,XLM3L_USDT,LIQ_USDT,TRU_ETH,CHZ_USD,EPK_USDT,MED_ETH,BSCPAD_ETH,ZCN_USDT,AIOZ_ETH,FOR_ETH,CVC3L_USDT,MNY_USDT,SALT_USDT,CSTR_USDT,MPL_USDT,PLY_ETH,FIS_USDT,CHO_USDT,BICO_ETH,STOX_ETH,HIGH_USDT,SDAO_BTC,STEP_USD,CRV_BTC,SCRT_ETH,ROSE_USDT,SKILL_ETH,FRAX_USDT,BAGS_USDT,WIKEN_BTC,WOO_USDT,BBANK_ETH,SNX3L_USDT,XRD_ETH,VTHO_USDT,OKB3L_USDT,SAFEMOON_USDT,RAD_ETH,IOI_USDT,LAMB_USDT,CHZ_USDT,FAR_ETH,OKB3S_USDT,ELU_USDT,JGN_ETH,EOS3S_USDT,DBC_USDT,ATOM_USDT,ACH_ETH,LBLOCK_USDT,WZRD_USDT,OST_ETH,MEAN_USDT,IDEX_USDT,HOT_TRY,EWT_ETH,EMON_USDT,FXS_USDT,PSY_ETH,SIDUS_USDT,ATA_USDT,CVC3S_USDT,LOOKS_ETH,ALPA_ETH,CGG_ETH,CIR_ETH,PRT_ETH,LON3S_USDT,INJ_USDT,FIRE_ETH,MAHA_USDT,IOST3S_USDT,NU_ETH,LEO_BTC,VOXEL_USDT,CRV_USDT,EQX_USDT,WHALE_USDT,INJ_ETH,GRAP_USDT,AVAX3S_USDT,TIFI_USDT,C98_USDT,ERN_ETH,SUSHI_ETH,VET3S_USDT,KPAD_USDT,CRPT_ETH,CRO_USDT,AZY_USDT,LEMD_USDT,ETH2_ETH,BASE_ETH,TT_ETH,PERL_USDT,BANK_ETH,LST_ETH,PYR_ETH,RATIO_USDT,UMB_USDT,M ETALDR_USDT,SWINGBY_ETH,WICC_ETH,NUM_USDT,SHOE_USDT,BORING_ETH,SDN_USDT,GXS_BTC,ALICE_ETH,BRKL_USDT,GF_ETH,ELEC_USDT,SFG_USDT,COFIX_USDT,TIPS_ETH,FIL_BTC,CWAR_USDT,WILD_USDT,RENBTC_USDT,BNX_USDT,TRU_USDT,SWEAT_USDT,IOST_BTC,NVIR_USDT,1EARTH_USDT,ADAPAD_USDT,PPS_USDT,CUBE_USDT,DLC_USDT,DAFI_ETH,UNISTAKE_ETH,NFTL_USDT,ATOM_TRY,SHIB3S_USDT,BNB_USD,CNAME_USDT,GTH_ETH,ZCX_USDT,DYDX3L_USDT,ASTRO_USDT,GLQ_USDT,PROPS_USDT,AART_USDT,BTRST_ETH,KFT_USDT,AERGO_USDT,RUFF_ETH,EOS3L_USDT,API3_USDT,MINA_BTC,ETHA_ETH,AXIS_ETH,LOON_USDT,AVAX3L_USDT,VET3L_USDT,AE_USDT,SHX_USDT,LYM_USDT,DCR_BTC,LBK_USDT,QTC_USDT,LAVA_USDT,XCN_USDT,BRT_USDT,RSV_USDT,KIF_USDT,PSL_USDT,AZERO_USDT,LUNA_ETH,MILO_USDT,OGN_ETH,TOTM_USDT,BYN_ETH,MINA_USDT,PUNDIX_ETH,SRT_USDT,DG_ETH,IHC_USDT,SYS_ETH,TITA_USDT,COTI3L_USDT,DAG_USDT,DOT5L_USDT,TRADE_USDT,SHPING_USDT,NU_USDT,BLANK_ETH,PCNT_ETH,SCCP_USDT,POLS_USDT,NPT_USDT,MTA_USDT,YIELD_USDT,ZCN_ETH,DVP_ETH,KART_USDT,SYLO_USDT,MCRT_USDT,SPFC_USDT,BASE_USDT,ICX_USDT,PET_USDT,GZONE_USDT,RED_ETH,SBTC_USDT,BATH_ ETH,SOL_USD,NAFT_USDT,GMX_USDT,VADER_USDT,GTC_USDT,CVP_ETH,XRPBEAR_USDT,TIME_USDT,SXP_USDT,CITY_USDT,QASH_USDT,FAST_USDT,BCD_USDT,KNIGHT_USDT,BOO_ETH,ZODI_USDT,REI_USDT,PBX_ETH,SRM_USDT,LDO_ETH,ZEC_USDT,UFT_USDT,DAG_BTC,RIDE_USDT,ERN_USDT,T_USDT,CEEK_USDT,STI_USDT,IMX3S_USDT,ELA_USDT,MNGO_ETH,EHASH_ETH,BADGER_ETH,SUPE_USDT,AR3L_USDT,AUDIO_ETH,DOCK_ETH,QSP_USDT,FLM_USDT,AAVE3S_USDT,BOND_USDT,HT_USD,TARA_USDT,TRX_USDT,SPO_USDT,DSLA_USDT,LTC_BTC,DOGE_USDT,SLIM_ETH,ALN_ETH,CFX3S_USDT,FXS_ETH,RARE_ETH,VLXPAD_ETH,ETH_USD,SDN_BTC,QUICK_USDT,UTK_USDT,XPNET_USDT,TRB_USDT,LAZIO_USDT,FTM_TRY,ALPHA_ETH,CVC_ETH,WSG_USDT,UNI_ETH,DASH3L_USDT,BTL_USDT,CPOOL_USDT,MCG_USDT,SFP_ETH,REALM_USDT,RUFF_BTC,MOB_ETH,IBFK_USDT,ALPHA3S_USDT,BLOK_USDT,WIKEN_USDT,OMG3S_USDT,UTK_ETH,BCH5S_USDT,MED_USDT,REN_USD,MAN_ETH,SLND_ETH,CGG_USDT,CRE_USDT,SOURCE_USDT,ABT_USDT,DPET_USDT,WOM_USDT,FOREX_ETH,SNFT1_USDT,RIF_USDT,BENQI_USDT,XCV_ETH,GTC_BTC,ADA_TRY,LAT_USDT,ITGR_USDT,DLTA_USDT,SMT_USDT,APYS_USDT,MFT_ETH,ABT_ETH,STOX_USDT,ZRX_BTC,GMAT_USDT,R OOM_ETH,STORJ_BTC,RAZOR_USDT,RAGE_USDT,DOCK_USDT,RDN_USDT,MTR_USDT,NKN_USDT,SWASH_USDT,FX_USDT,POR_USDT,DENT_ETH,DERI_USDT,DFND_USDT,BLES_USDT,SLND_USDT,WNXM_ETH,CRTS_USDT,BTC3S_USDT,BKC_USDT,STEPG_ETH,THETA3L_USDT,NBS_BTC,AVAX_ETH,NANO_BTC,DEFILAND_ETH,LOOKS_USDT,BCX_BTC,BCH_USD,ETH3L_USDT,QLC_BTC,BCUG_ETH,RDF_USDT,DOGEDASH_USDT,ARSW_USDT,NEAR_ETH,QTCON_USDT,BABI_USDT,MBX_USDT,PNL_USDT,ODDZ_ETH,ATOM_BTC,XRP_BTC,BTCBULL_USDT,HMT_USDT,PORTO_USDT,STND_USDT,ETHW_ETH,LPT_USDT,LTC3L_USDT,TOKAU_USDT,QI_ETH,TVK_USDT,CWS_USDT,SWOP_USDT,WBTC_USDT,INSTAR_ETH,ICX_ETH,GALA5L_USDT,XTZ_BTC,AGS_USDT,TARA_BTC,DYDX_ETH,CATGIRL_USDT,SASHIMI_ETH,EPX_ETH,GCOIN_USDT,GDAO_USDT,MARS_ETH,OMG_USD,PMON_USDT,MNGO_USDT,TVK_ETH,SLG_USDT,MSOL_USDT,POWR_USDT,UOS_USDT,USDD_USDT,SLICE_USDT,ARRR_ETH,NSBT_USDT,STR_ETH,BEAM3L_USDT,BEL_USDT,MM_USDT,AXS_ETH,WEST_ETH,FTT3L_USDT,OMI_USDT,TIPS_USDT,SLC_ETH,SQUID_USDT,FEI_USDT,GEM_USDT,UMEE_USDT,DOGE_TRY,FCD_USDT,PVU_USDT,XED_ETH,LRN_ETH,NRFB_USDT,LION_USDT,BLACK_USDT,DOGE5S_USDT,CUDOS_USDT,PCNT_USDT ,OVR_USDT,ETC3S_USDT,CHR_ETH,MER_USDT,BOBA_USDT,FUEL_USDT,BAC_USDT,ONE3S_USDT,CONV_ETH,CDT_BTC,CELL_ETH,ASM_ETH,OPIUM_USDT,JST3L_USDT,BONDLY_USDT,RAZE_USDT,LIME_BTC,NFTX_ETH,PNK_ETH,LDO_USDT,DKS_USDT,ORO_USDT,LITH_USDT,ALPHR_ETH,INK_BTC,RLY_USDT,NEAR3S_USDT,XLM3S_USDT,AR_USDT,AKT_USDT,HCT_USDT,REEF_ETH,BZZ_USDT,SRM3L_USDT,AQDC_USDT,OPIUM_ETH,BAT_TRY,EWT_USDT,ALCX_ETH,CORN_USDT,HYDRA_USDT,RUNE_USD,STEP_USDT,CKB_BTC,MATTER_USDT,STSOL_ETH,CEEK_ETH,FXF_ETH,LIKE_USDT,HIT_USDT,LEO_USDT,COMP_USDT,BAL_USDT,LMR_USDT,AQT_USDT,BUY_ETH,LINK3S_USDT,ROOK_ETH,IMX_USDT,EFI_USDT,TAUR_USDT,OKT_ETH,GALO_USDT,MOOV_USDT,RUNE_USDT,TCP_USDT,ITEM_USDT,SCLP_USDT,RBC_USDT,SPI_USDT,ETC_USDT,RENBTC_BTC,CHICKS_USDT,KNOT_USDT,XEC3L_USDT,XCV_USDT,ETC_ETH,AAVE_TRY,APT_USDT,GNX_ETH,KISHU_USDT,AE_BTC,LIEN_USDT,CREAM_USDT,ATOM3S_USDT,OP_ETH,FORTH_ETH,PYR_USDT,KTN_ETH,TKO_ETH,METAG_USDT,ACE_USDT,CIR_USDT,BEAM_ETH,TCP_ETH,SRM_USD,CEL_USD,TRIBE3S_USDT,MESA_ETH,EVA_USDT,BBANK_USDT,BLANKV2_USDT,FORM_ETH,BAL3S_USDT,VISR_ETH,REVO_ETH,ALTB_USDT,KNC_US DT,GAS_USDT,SAFEMARS_USDT,TIP_USDT,VADER_ETH,NWC_USDT,VALUE_USDT,ATA_ETH,SSX_USDT,JOE_USDT,BAS_ETH,FITFI3S_USDT,BIT_USDT,QNT_ETH,RFOX_ETH,MSU_USDT,MSOL_ETH,CRV3L_USDT,OXT_USDT,SHFT_USDT,VERA_ETH,LYM_ETH,BP_USDT,KBOX_USDT,DOGNFT_ETH,PERP_USDT,VELO_ETH,SAO_USDT,DUCK2_USDT,DEFILAND_USDT,DUCK2_ETH,GLMR3L_USDT,SERO_ETH,MTS_USDT,STX_USDT,KEX_ETH,ZIG_USDT,CARDS_USDT,ANML_USDT,GALA_USDT,RAY3S_USDT,KAVA3L_USDT,GARD_USDT,GRT3L_USDT,BFC_USDT,NIFT_USDT,ORION_USDT,CTX_USDT,ASW_USDT,CERE_USDT,COMBO_ETH,MKR_USDT,MASK_USDT,MGA_USDT,AVAX_USDT,SKL3L_USDT,FRR_USDT,MV_USDT,BMI_ETH,SFIL_USDT,TEER_USDT,KLV_USDT,DMS_ETH,LBL_USDT,MKR3L_USDT,LEDU_BTC,XLM_BTC,MIST_ETH,OIN_USDT,CAKE_USDT,RNDR_USDT,STEPG_USDT,YCT_USDT,OPS_ETH,SHR_USDT,OXY_ETH" + }, + "option": { + "enabled": "BTC_USDT-20230217-28000-P,BTC_USDT-20221028-34000-P,BTC_USDT-20221028-40000-C", + "available": "BTC_USDT-20221028-26000-C,BTC_USDT-20221028-34000-P,BTC_USDT-20221028-40000-C,BTC_USDT-20221028-28000-P,BTC_USDT-20221028-34000-C,BTC_USDT-20221028-28000-C,BTC_USDT-20221028-36000-P,BTC_USDT-20221028-50000-P,BTC_USDT-20221028-36000-C,BTC_USDT-20221028-50000-C,BTC_USDT-20221028-21000-P,BTC_USDT-20221028-38000-P,BTC_USDT-20221028-21000-C,BTC_USDT-20221028-38000-C,BTC_USDT-20221028-23000-P,BTC_USDT-20221028-17000-P,BTC_USDT-20221028-23000-C,BTC_USDT-20221028-17000-C,BTC_USDT-20221028-25000-P,BTC_USDT-20221028-19000-P,BTC_USDT-20221028-25000-C,BTC_USDT-20221028-10000-P,BTC_USDT-20221028-19000-C,BTC_USDT-20221028-27000-P,BTC_USDT-20221028-10000-C,BTC_USDT-20221028-27000-C,BTC_USDT-20221028-12000-P,BTC_USDT-20221028-12000-C,BTC_USDT-20221028-20000-P,BTC_USDT-20221028-5000-P,BTC_USDT-20221028-14000-P,BTC_USDT-20221028-20000-C,BTC_USDT-20221028-45000-P,BTC_USDT-20221028-5000-C,BTC_USDT-20221028-14000-C,BTC_USDT-20221028-22000-P,BTC_USDT-20221028-45000-C,BTC_USDT-20221028-16000-P,BTC_USDT-20221028-22000-C,BTC_USDT-20221028-30000-P,BTC_USDT-20221028-16000-C,BTC_USDT-20221028-24000-P,BTC_USDT-20221028-30000-C,BTC_USDT-20221028-18000-P,BTC_USDT-20221028-24000-C,BTC_USDT-20221028-32000-P,BTC_USDT-20221028-18000-C,BTC_USDT-20221028-26000-P,BTC_USDT-20221028-32000-C,BTC_USDT-20221028-40000-P" + }, + "futures": { + "enabled": "ETH_USD,BTC_USD,KNC_USDT,OOKI_USDT,BIT_USDT,ZEC_USDT,SC_USDT,RVN_USDT,ICX_USDT", + "available": "ETH_USD,BTC_USD,KNC_USDT,OOKI_USDT,BIT_USDT,ZEC_USDT,SC_USDT,RVN_USDT,ICX_USDT,DUSK_USDT,BEL_USDT,REEF_USDT,ALCX_USDT,ASTR_USDT,INJ_USDT,CAKE_USDT,LAZIO_USDT,ONE_USDT,CEL_USDT,ETH_USDT,KLAY_USDT,COTI_USDT,MKISHU_USDT,MANA_USDT,MOVR_USDT,OMG_USDT,UNI_USDT,LTC_USDT,AAVE_USDT,DENT_USDT,QRDO_USDT,BNB_USDT,ALPHA_USDT,RAY_USDT,APE_USDT,CERE_USDT,STMX_USDT,XCN_USDT,OGN_USDT,OKB_USDT,DOT_USDT,TLM_USDT,BTM_USDT,ADA_USDT,ANKR_USDT,ANT_USDT,TRX_USDT,MTL_USDT,YFII_USDT,SUN_USDT,SAND_USDT,MBABYDOGE_USDT,WIN_USDT,LUNC_USDT,SRM_USDT,STG_USDT,BAT_USDT,AXS_USDT,SOL_USDT,MAKITA_USDT,BNT_USDT,BLZ_USDT,PSG_USDT,IOTA_USDT,BONK_USDT,RSR_USDT,PYR_USDT,FITFI_USDT,MKR_USDT,PERP_USDT,COMP_USDT,LINK_USDT,CHR_USDT,CFX_USDT,GARI_USDT,DGB_USDT,MBOX_USDT,WEMIX_USDT,DYDX_USDT,LUNA_USDT,HT_USDT,TRB_USDT,CTK_USDT,ACA_USDT,TFUEL_USDT,OCEAN_USDT,XLM_USDT,HOT_USDT,FTM_USDT,LPT_USDT,SOS_USDT,ALGO_USDT,SHIB_USDT,BSV_USDT,PORTO_USDT,SFP_USDT,SANTOS_USDT,BADGER_USDT,DAR_USDT,DEFI_USDT,XEM_USDT,ALICE_USDT,ICP_USDT,RARE_USDT,LRC_USDT,BAKE_USDT,FLUX_USDT,CRO_USDT,CVC_USDT,MINA_USDT,LIT_USDT,AUDIO_USDT,ZIL_USDT,XMR_USDT,FRONT_USDT,CTSI_USDT,AGLD_USDT,YGG_USDT,OP_USDT,ZRX_USDT,GT_USDT,XCH_USDT,VET_USDT,MOB_USDT,BICO_USDT,SLP_USDT,ACH_USDT,AR_USDT,CLV_USDT,IMX_USDT,SPELL_USDT,UNFI_USDT,SUSHI_USDT,FTT_USDT,HIGH_USDT,HNT_USDT,ALT_USDT,YFI_USDT,NEAR_USDT,NKN_USDT,XVS_USDT,BAND_USDT,LOKA_USDT,BCH_USDT,TOMO_USDT,WAVES_USDT,FIDA_USDT,DIA_USDT,ANC_USDT,CELO_USDT,CRV_USDT,FLM_USDT,GLMR_USDT,FIL_USDT,PEOPLE_USDT,WAXP_USDT,IOTX_USDT,ATOM_USDT,RLC_USDT,HBAR_USDT,REN_USDT,GMT_USDT,KAVA_USDT,KDA_USDT,GALA_USDT,STORJ_USDT,PUNDIX_USDT,BAL_USDT,XAUG_USDT,GRIN_USDT,SXP_USDT,AKRO_USDT,NEXO_USDT,CKB_USDT,API3_USDT,NEST_USDT,ETHW_USDT,TONCOIN_USDT,THETA_USDT,CREAM_USDT,BTC_USDT,GST_USDT,BEAM_USDT,HFT_USDT,KSM_USDT,RAD_USDT,QTUM_USDT,WOO_USDT,ATA_USDT,AVAX_USDT,EOS_USDT,SNX_USDT,AUCTION_USDT,XRP_USDT,GITCOIN_USDT,MATIC_USDT,ONT_USDT,LINA_USDT,DASH_USDT,MASK_USDT,ETC_USDT,JST_USDT,BSW_USDT,CONV_USDT,SKL_USDT,GAL_USDT,DODO_USDT,GRT_USDT,TRU_USDT,STX_USDT,CVX_USDT,JASMY_USDT,HIVE_USDT,EXCH_USDT,ROSE_USDT,SUPER_USDT,SCRT_USDT,USTC_USDT,ENJ_USDT,BTS_USDT,LOOKS_USDT,QNT_USDT,HOOK_USDT,FLOW_USDT,RUNE_USDT,APT_USDT,CHZ_USDT,DOGE_USDT,1INCH_USDT,PRIV_USDT,CSPR_USDT,C98_USDT,RACA_USDT,CELR_USDT,XEC_USDT,ENS_USDT,POND_USDT,NYM_USDT,PROM_USDT,IOST_USDT,ZEN_USDT,LDO_USDT,RNDR_USDT,REQ_USDT,DEGO_USDT,VRA_USDT,QUICK_USDT,VGX_USDT,XTZ_USDT,EGLD_USDT,POLS_USDT,ARPA_USDT,NFT_USDT" + }, + "cross_margin": { + "enabled": "BTC_USDT,ERN_USDT,T_USDT,CEEK_USDT,OGN_USDT,QNT_USDT,WOZX_USDT,ZEE_USDT,FUN_USDT,FLM_USDT,BOND_USDT", + "available": "ERN_USDT,T_USDT,CEEK_USDT,OGN_USDT,QNT_USDT,WOZX_USDT,ZEE_USDT,FUN_USDT,FLM_USDT,BOND_USDT,TARA_USDT,TRX_USDT,OXY_USDT,LON_USDT,DOGE_USDT,ISP_USDT,TWT_USDT,BAO_USDT,QUACK_USDT,ANT_USDT,VGX_USDT,ARPA_USDT,QUICK_USDT,UTK_USDT,HERO_USDT,WSG_USDT,BICO_USDT,MTV_USDT,VET_USDT,GARI_USDT,BCH_USDT,KLAY_USDT,WING_USDT,BLOK_USDT,SPS_USDT,WIKEN_USDT,WSIENNA_USDT,PUNDIX_USDT,FIC_USDT,ASTR_USDT,FET_USDT,VELO_USDT,BENQI_USDT,CWEB_USDT,RIF_USDT,UNI_USDT,ONG_USDT,ERG_USDT,ALPHA_USDT,CELO_USDT,XVG_USDT,GMAT_USDT,BTS_USDT,DOCK_USDT,GMT_USDT,DIA_USDT,CSPR_USDT,NKN_USDT,STAKE_USDT,SWASH_USDT,XEC_USDT,SWRV_USDT,QRDO_USDT,BLES_USDT,EOS_USDT,GRT_USDT,ASM_USDT,FIL6_USDT,GNO_USDT,EGLD_USDT,XYM_USDT,LOOKS_USDT,LOKA_USDT,BNC_USDT,BAS_USDT,SKL_USDT,STMX_USDT,CVC_USDT,DDOS_USDT,COTI_USDT,AVA_USDT,HMT_USDT,DF_USDT,LPT_USDT,XRP_USDT,TVK_USDT,FEVR_USDT,MBL_USDT,KIN_USDT,SPELL_USDT,MATIC_USDT,FTT_USDT,NMR_USDT,PMON_USDT,BNB_USDT,USDD_USDT,LSS_USDT,MDX_USDT,PRQ_USDT,ALPINE_USDT,DEGO_USDT,OMI_USDT,TIPS_USDT,OCT_USDT,FEI_USDT,UMEE_USDT,CRP_USDT,LION_USDT,YFI_USDT,DASH_USDT,REQ_USDT,SDAO_USDT,PNT_USDT,INSUR_USDT,OOKI_USDT,SUN_USDT,CRPT_USDT,BAC_USDT,DATA_USDT,LRN_USDT,JGN_USDT,KIMCHI_USDT,SUKU_USDT,VRA_USDT,AAVE_USDT,FTI_USDT,LDO_USDT,FRA_USDT,BLANK_USDT,NEAR_USDT,ZKS_USDT,MTRG_USDT,RLY_USDT,TCT_USDT,FLY_USDT,JST_USDT,YFII_USDT,AR_USDT,POLY_USDT,JULD_USDT,SOL_USDT,BZZ_USDT,AXS_USDT,ASD_USDT,XMR_USDT,FTM_USDT,HIT_USDT,LEO_USDT,LIT_USDT,PIG_USDT,COMP_USDT,ELON_USDT,IMX_USDT,EFI_USDT,XVS_USDT,WAVES_USDT,PEOPLE_USDT,SOS_USDT,RUNE_USDT,POLC_USDT,SCLP_USDT,BABYDOGE_USDT,KONO_USDT,SPI_USDT,ETC_USDT,MDA_USDT,MTL_USDT,BCHA_USDT,KISHU_USDT,SUNNY_USDT,PYR_USDT,XTZ_USDT,TRIBE_USDT,AUDIO_USDT,FIRO_USDT,MANA_USDT,OKB_USDT,DOG_USDT,SLP_USDT,KNC_USDT,GAS_USDT,LUNA_USDT,SAFEMARS_USDT,MIR_USDT,DAR_USDT,EGS_USDT,KSM_USDT,ATP_USDT,BIT_USDT,STORJ_USDT,XEM_USDT,QTUM_USDT,AGLD_USDT,RVN_USDT,OXT_USDT,SHFT_USDT,IOTX_USDT,LUNC_USDT,NEXO_USDT,AKITA_USDT,PERP_USDT,ONE_USDT,ETH_USDT,FLUX_USDT,FLOKI_USDT,STX_USDT,ANML_USDT,XPRT_USDT,GALA_USDT,GXS_USDT,TORN_USDT,KAI_USDT,1INCH_USDT,CHR_USDT,GAL_USDT,GLMR_USDT,CTX_USDT,CERE_USDT,CART_USDT,STRAX_USDT,MASK_USDT,MKR_USDT,AVAX_USDT,ENJ_USDT,YAM_USDT,ALPACA_USDT,DODO_USDT,MFT_USDT,CAKE_USDT,RNDR_USDT,CTSI_USDT,GRIN_USDT,MXC_USDT,ONT_USDT,ANKR_USDT,SLIM_USDT,FIL_USDT,CTK_USDT,ASR_USDT,FEG_USDT,SERO_USDT,RSS3_USDT,IRIS_USDT,XCH_USDT,ZRX_USDT,BAND_USDT,BADGER_USDT,DAO_USDT,EPS_USDT,THETA_USDT,BAKE_USDT,SHIB_USDT,MBOX_USDT,NBS_USDT,SNT_USDT,DREP_USDT,NFT_USDT,AUCTION_USDT,BOSON_USDT,O3_USDT,NULS_USDT,OMG_USDT,PEARL_USDT,HAPI_USDT,STG_USDT,IDV_USDT,HORD_USDT,ZIL_USDT,SUPER_USDT,DENT_USDT,REN_USDT,RAI_USDT,ZEN_USDT,ALGO_USDT,BLZ_USDT,BOR_USDT,SC_USDT,HEGIC_USDT,MOB_USDT,DORA_USDT,FOR_USDT,FLOW_USDT,RARI_USDT,DYDX_USDT,ATLAS_USDT,GST_USDT,REEF_USDT,HT_USDT,XYO_USDT,CHESS_USDT,BAT_USDT,NYM_USDT,RAMP_USDT,USDC_USDT,ICP_USDT,EPK_USDT,EXRD_USDT,DOT_USDT,COOK_USDT,CKB_USDT,YGG_USDT,CRU_USDT,ANC_USDT,FIS_USDT,ALCX_USDT,HIGH_USDT,BEAM_USDT,BSW_USDT,STAR_USDT,ROSE_USDT,CNNS_USDT,BZRX_USDT,WOO_USDT,SAFEMOON_USDT,VTHO_USDT,OM_USDT,LAMB_USDT,CHZ_USDT,AIOZ_USDT,EDEN_USDT,POND_USDT,ATOM_USDT,UNFI_USDT,FORTH_USDT,MLN_USDT,NEO_USDT,MOVR_USDT,RLC_USDT,FXS_USDT,ENS_USDT,ATA_USDT,XPR_USDT,NEST_USDT,XLM_USDT,AUTO_USDT,SNX_USDT,OCN_USDT,RSR_USDT,MITH_USDT,KAR_USDT,INJ_USDT,PLA_USDT,CYS_USDT,WAXP_USDT,VOXEL_USDT,CRV_USDT,FITFI_USDT,WHALE_USDT,WRX_USDT,TIDAL_USDT,C98_USDT,HNT_USDT,TONCOIN_USDT,DOGGY_USDT,SYS_USDT,NPXS_USDT,CRO_USDT,LEMD_USDT,RAY_USDT,PERL_USDT,CQT_USDT,CFX_USDT,TOMO_USDT,ACA_USDT,SDN_USDT,OKT_USDT,WILD_USDT,BNX_USDT,TRU_USDT,RACA_USDT,SWEAT_USDT,ACH_USDT,AKRO_USDT,BTM_USDT,TKO_USDT,GT_USDT,OCEAN_USDT,WNCG_USDT,BSV_USDT,GHST_USDT,CELR_USDT,LINA_USDT,SAND_USDT,APE_USDT,WICC_USDT,FIDA_USDT,ADA_USDT,PROPS_USDT,METIS_USDT,KAVA_USDT,AERGO_USDT,CONV_USDT,TFUEL_USDT,FRONT_USDT,API3_USDT,FARM_USDT,AE_USDT,LRC_USDT,IOTA_USDT,RFOX_USDT,PHA_USDT,XCN_USDT,NAS_USDT,KEEP_USDT,VIDY_USDT,HOT_USDT,MINA_USDT,ETHW_USDT,ALICE_USDT,HAI_USDT,BTC_USDT,LTC_USDT,LTO_USDT,DC_USDT,NU_USDT,IOST_USDT,RAD_USDT,POLS_USDT,OP_USDT,WXT_USDT,STR_USDT,YIELD_USDT,GM_USDT,SPA_USDT,BTCST_USDT,WEMIX_USDT,CLV_USDT,ICX_USDT,PET_USDT,STARL_USDT,HBAR_USDT,REDTOKEN_USDT,BTT_USDT,LINK_USDT,TLM_USDT,ARES_USDT,GTC_USDT,SUSHI_USDT,KEY_USDT,ALN_USDT,KDA_USDT,DVI_USDT,SXP_USDT,MAPS_USDT,BCD_USDT,SRM_USDT,WIN_USDT,ZEC_USDT,JASMY_USDT" + }, + "margin": { + "enabled": "BTC_USDT,ERN_USDT,T_USDT,CEEK_USDT,OGN_USDT,QNT_USDT,WOZX_USDT,ZEE_USDT,FUN_USDT,FLM_USDT,BOND_USDT", + "available": "BTC_USDT,ERN_USDT,T_USDT,CEEK_USDT,OGN_USDT,QNT_USDT,WOZX_USDT,ZEE_USDT,FUN_USDT,FLM_USDT,BOND_USDT,TARA_USDT,TRX_USDT,OXY_USDT,LON_USDT,DOGE_USDT,ISP_USDT,TWT_USDT,BAO_USDT,QUACK_USDT,ANT_USDT,VGX_USDT,ARPA_USDT,QUICK_USDT,UTK_USDT,HERO_USDT,WSG_USDT,BICO_USDT,MTV_USDT,VET_USDT,GARI_USDT,BCH_USDT,KLAY_USDT,WING_USDT,BLOK_USDT,SPS_USDT,WIKEN_USDT,WSIENNA_USDT,PUNDIX_USDT,FIC_USDT,ASTR_USDT,FET_USDT,VELO_USDT,BENQI_USDT,CWEB_USDT,RIF_USDT,UNI_USDT,ONG_USDT,ERG_USDT,ALPHA_USDT,CELO_USDT,XVG_USDT,GMAT_USDT,BTS_USDT,DOCK_USDT,GMT_USDT,DIA_USDT,CSPR_USDT,NKN_USDT,STAKE_USDT,SWASH_USDT,XEC_USDT,SWRV_USDT,QRDO_USDT,BLES_USDT,EOS_USDT,GRT_USDT,ASM_USDT,FIL6_USDT,GNO_USDT,EGLD_USDT,XYM_USDT,LOOKS_USDT,LOKA_USDT,BNC_USDT,BAS_USDT,SKL_USDT,STMX_USDT,CVC_USDT,DDOS_USDT,COTI_USDT,AVA_USDT,HMT_USDT,DF_USDT,LPT_USDT,XRP_USDT,TVK_USDT,FEVR_USDT,MBL_USDT,KIN_USDT,SPELL_USDT,MATIC_USDT,FTT_USDT,NMR_USDT,PMON_USDT,BNB_USDT,USDD_USDT,LSS_USDT,MDX_USDT,PRQ_USDT,ALPINE_USDT,DEGO_USDT,OMI_USDT,TIPS_USDT,OCT_USDT,FEI_USDT,UMEE_USDT,CRP_USDT,LION_USDT,YFI_USDT,DASH_USDT,REQ_USDT,SDAO_USDT,PNT_USDT,INSUR_USDT,OOKI_USDT,SUN_USDT,CRPT_USDT,BAC_USDT,DATA_USDT,LRN_USDT,JGN_USDT,KIMCHI_USDT,SUKU_USDT,VRA_USDT,AAVE_USDT,FTI_USDT,LDO_USDT,FRA_USDT,BLANK_USDT,NEAR_USDT,ZKS_USDT,MTRG_USDT,RLY_USDT,TCT_USDT,FLY_USDT,JST_USDT,YFII_USDT,AR_USDT,POLY_USDT,JULD_USDT,SOL_USDT,BZZ_USDT,AXS_USDT,ASD_USDT,XMR_USDT,FTM_USDT,HIT_USDT,LEO_USDT,LIT_USDT,PIG_USDT,COMP_USDT,ELON_USDT,IMX_USDT,EFI_USDT,XVS_USDT,WAVES_USDT,PEOPLE_USDT,SOS_USDT,RUNE_USDT,POLC_USDT,SCLP_USDT,BABYDOGE_USDT,KONO_USDT,SPI_USDT,ETC_USDT,MDA_USDT,MTL_USDT,BCHA_USDT,KISHU_USDT,SUNNY_USDT,PYR_USDT,XTZ_USDT,TRIBE_USDT,AUDIO_USDT,FIRO_USDT,MANA_USDT,OKB_USDT,DOG_USDT,SLP_USDT,KNC_USDT,GAS_USDT,LUNA_USDT,SAFEMARS_USDT,MIR_USDT,DAR_USDT,EGS_USDT,KSM_USDT,ATP_USDT,BIT_USDT,STORJ_USDT,XEM_USDT,QTUM_USDT,AGLD_USDT,RVN_USDT,OXT_USDT,SHFT_USDT,IOTX_USDT,LUNC_USDT,NEXO_USDT,AKITA_USDT,PERP_USDT,ONE_USDT,ETH_USDT,FLUX_USDT,FLOKI_USDT,STX_USDT,ANML_USDT,XPRT_USDT,GALA_USDT,GXS_USDT,TORN_USDT,KAI_USDT,1INCH_USDT,CHR_USDT,GAL_USDT,GLMR_USDT,CTX_USDT,CERE_USDT,CART_USDT,STRAX_USDT,MASK_USDT,MKR_USDT,AVAX_USDT,ENJ_USDT,YAM_USDT,ALPACA_USDT,DODO_USDT,MFT_USDT,CAKE_USDT,RNDR_USDT,CTSI_USDT,GRIN_USDT,MXC_USDT,ONT_USDT,ANKR_USDT,SLIM_USDT,FIL_USDT,CTK_USDT,ASR_USDT,FEG_USDT,SERO_USDT,RSS3_USDT,IRIS_USDT,XCH_USDT,ZRX_USDT,BAND_USDT,BADGER_USDT,DAO_USDT,EPS_USDT,THETA_USDT,BAKE_USDT,SHIB_USDT,MBOX_USDT,NBS_USDT,SNT_USDT,DREP_USDT,NFT_USDT,AUCTION_USDT,BOSON_USDT,O3_USDT,NULS_USDT,OMG_USDT,PEARL_USDT,HAPI_USDT,STG_USDT,IDV_USDT,HORD_USDT,ZIL_USDT,SUPER_USDT,DENT_USDT,REN_USDT,RAI_USDT,ZEN_USDT,ALGO_USDT,BLZ_USDT,BOR_USDT,SC_USDT,HEGIC_USDT,MOB_USDT,DORA_USDT,FOR_USDT,FLOW_USDT,RARI_USDT,DYDX_USDT,ATLAS_USDT,GST_USDT,REEF_USDT,HT_USDT,XYO_USDT,CHESS_USDT,BAT_USDT,NYM_USDT,RAMP_USDT,USDC_USDT,ICP_USDT,EPK_USDT,EXRD_USDT,DOT_USDT,COOK_USDT,CKB_USDT,YGG_USDT,CRU_USDT,ANC_USDT,FIS_USDT,ALCX_USDT,HIGH_USDT,BEAM_USDT,BSW_USDT,STAR_USDT,ROSE_USDT,CNNS_USDT,BZRX_USDT,WOO_USDT,SAFEMOON_USDT,VTHO_USDT,OM_USDT,LAMB_USDT,CHZ_USDT,AIOZ_USDT,EDEN_USDT,POND_USDT,ATOM_USDT,UNFI_USDT,FORTH_USDT,MLN_USDT,NEO_USDT,MOVR_USDT,RLC_USDT,FXS_USDT,ENS_USDT,ATA_USDT,XPR_USDT,NEST_USDT,XLM_USDT,AUTO_USDT,SNX_USDT,OCN_USDT,RSR_USDT,MITH_USDT,KAR_USDT,INJ_USDT,PLA_USDT,CYS_USDT,WAXP_USDT,VOXEL_USDT,CRV_USDT,FITFI_USDT,WHALE_USDT,WRX_USDT,TIDAL_USDT,C98_USDT,HNT_USDT,TONCOIN_USDT,DOGGY_USDT,SYS_USDT,NPXS_USDT,CRO_USDT,LEMD_USDT,RAY_USDT,PERL_USDT,CQT_USDT,CFX_USDT,TOMO_USDT,ACA_USDT,SDN_USDT,OKT_USDT,WILD_USDT,BNX_USDT,TRU_USDT,RACA_USDT,SWEAT_USDT,ACH_USDT,AKRO_USDT,BTM_USDT,TKO_USDT,GT_USDT,OCEAN_USDT,WNCG_USDT,BSV_USDT,GHST_USDT,CELR_USDT,LINA_USDT,SAND_USDT,APE_USDT,WICC_USDT,FIDA_USDT,ADA_USDT,PROPS_USDT,METIS_USDT,KAVA_USDT,AERGO_USDT,CONV_USDT,TFUEL_USDT,FRONT_USDT,API3_USDT,FARM_USDT,AE_USDT,LRC_USDT,IOTA_USDT,RFOX_USDT,PHA_USDT,XCN_USDT,NAS_USDT,KEEP_USDT,VIDY_USDT,HOT_USDT,MINA_USDT,ETHW_USDT,ALICE_USDT,HAI_USDT,LTC_USDT,LTO_USDT,DC_USDT,NU_USDT,IOST_USDT,RAD_USDT,POLS_USDT,OP_USDT,WXT_USDT,STR_USDT,YIELD_USDT,GM_USDT,SPA_USDT,BTCST_USDT,WEMIX_USDT,CLV_USDT,ICX_USDT,PET_USDT,STARL_USDT,HBAR_USDT,REDTOKEN_USDT,BTT_USDT,LINK_USDT,TLM_USDT,ARES_USDT,GTC_USDT,SUSHI_USDT,KEY_USDT,ALN_USDT,KDA_USDT,DVI_USDT,SXP_USDT,MAPS_USDT,BCD_USDT,SRM_USDT,WIN_USDT,ZEC_USDT,JASMY_USDT" + }, + "delivery": { + "enabled": "BTC_USD_20230331,BTC_USD_20221230,BTC_USDT_20221021,BTC_USDT_20221014", + "available": "BTC_USD_20221021,BTC_USD_20221014,BTC_USD_20230331,BTC_USD_20221230,BTC_USDT_20221021,BTC_USDT_20221014,BTC_USDT_20230331,BTC_USDT_20221230" + } + } + }, + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "tickerBatching": true, + "autoPairUpdates": true + }, + "websocketAPI": true, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": true + } + }, + "bankAccounts": [ + { + "enabled": false, + "bankName": "", + "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ] }, - "enabled": { - "autoPairUpdates": true, - "websocketAPI": false - } - }, - "bankAccounts": [ { - "enabled": false, - "bankName": "", - "bankAddress": "", - "bankPostalCode": "", - "bankPostalCity": "", - "bankCountry": "", - "accountName": "", - "accountNumber": "", - "swiftCode": "", - "iban": "", - "supportedCurrencies": "" - } - ] - }, - { - "name": "Kraken", - "enabled": true, - "verbose": false, - "httpTimeout": 15000000000, - "websocketResponseCheckTimeout": 30000000, - "websocketResponseMaxLimit": 7000000000, - "websocketTrafficTimeout": 30000000000, - "websocketOrderbookBufferLimit": 5, - "baseCurrencies": "EUR,USD,CAD,GBP,JPY", - "currencyPairs": { - "requestFormat": { - "uppercase": true, - "separator": "," - }, - "configFormat": { - "uppercase": true, - "delimiter": "-", - "separator": "," - }, - "useGlobalFormat": true, - "assetTypes": [ - "spot" - ], - "pairs": { - "futures": { - "assetEnabled": true, - "enabled": "PF_XBTUSD", - "available": "PI_XBTUSD,PI_ETHUSD,PI_LTCUSD,PI_BCHUSD,PI_XRPUSD,PF_XBTUSD,PF_ETHUSD,PF_ADAUSD,PF_XRPUSD,PF_SOLUSD,PF_UNIUSD,PF_ATOMUSD,PF_BCHUSD,PF_DOTUSD,PF_EOSUSD,PF_FILUSD,PF_LINKUSD,PF_LTCUSD,PF_MATICUSD,PF_DEFIUSD,PF_AVAXUSD,PF_XMRUSD,PF_GMTUSD,PF_LUNA2USD,PF_OPUSD,PF_NEARUSD,PF_APEUSD,PF_WAVESUSD,PF_DOGEUSD,PF_FTMUSD,PF_TRXUSD,PF_MANAUSD,PF_CRVUSD,PF_AAVEUSD,PF_SNXUSD,PF_XTZUSD,PF_XLMUSD,PF_ALGOUSD,PF_SANDUSD,PF_OMGUSD,PF_ENJUSD,PF_COMPUSD,PF_YFIUSD,PF_CHZUSD,PF_LPTUSD,PF_BATUSD,PF_MKRUSD,PF_AXSUSD,PF_GALAUSD,PF_ETCUSD,PF_KAVAUSD,PF_LRCUSD,PF_KSMUSD,PF_GRTUSD,PF_FLOWUSD,PF_ZECUSD,PF_QTUMUSD,PF_DASHUSD,PF_1INCHUSD,PF_KNCUSD,PF_OGNUSD,PF_SUSHIUSD,PF_STORJUSD,PF_RUNEUSD,PF_EGLDUSD,PF_DYDXUSD,PF_RENUSD,PF_ANKRUSD,PF_ICPUSD,PF_ETHWUSD,PF_OCEANUSD,PF_BANDUSD,PF_BALUSD,PF_ALICEUSD,PF_ICXUSD,PF_ENSUSD,PF_AUDIOUSD,PF_ANTUSD,PF_SCUSD,PF_MINAUSD,PF_GLMRUSD,PF_THETAUSD,PF_QNTUSD,PF_IMXUSD,PF_APTUSD,PF_FLRUSD,PF_BLURUSD,PF_GMXUSD,PF_MASKUSD,PF_LDOUSD,PF_USDCUSD,PF_USDTUSD,PF_ARBUSD,PF_FETUSD,PF_STXUSD,PF_RNDRUSD,PF_CVXUSD,PF_WOOUSD,PF_JASMYUSD,PF_INJUSD,PF_ZRXUSD,PF_RLCUSD,PF_GALUSD,PF_SUIUSD,PF_PEPEUSD,FI_XBTUSD_231229,FI_ETHUSD_231229,PF_SHIBUSD,PF_TUSDUSD,PF_SXPUSD,PF_ARPAUSD,PF_ALPHAUSD,PF_STGUSD,PF_HFTUSD,PF_ACHUSD,PF_WLDUSD,PF_MOONUSD,PF_LINAUSD,PF_CFXUSD,PF_PAXGUSD,PF_AGLDUSD,FF_ETHUSD_231229,FF_XBTUSD_231229,FI_ETHUSD_240329,FI_XBTUSD_240329,FI_XRPUSD_231229,FI_BCHUSD_231229,FI_LTCUSD_231229,PF_FXSUSD,PF_SEIUSD,FF_XBTUSD_231027,FF_ETHUSD_231027,FI_XBTUSD_231027,FI_ETHUSD_231027,FI_XRPUSD_231027,FI_BCHUSD_231027,FI_LTCUSD_231027", - "requestFormat": { - "uppercase": true, - "delimiter": "_" - }, - "configFormat": { - "uppercase": true, - "delimiter": "_" - } - }, - "spot": { - "enabled": "XBT-USD", - "available": "ETH-GBP,XRP-USD,DAI-EUR,LSK-USD,BAT-EUR,BCH-EUR,EOS-ETH,GNO-EUR,ETH-CAD,XRP-JPY,ADA-ETH,DAI-USD,DASH-EUR,GNO-USD,LSK-XBT,ETH-EUR,ZEC-EUR,DASH-XBT,EOS-EUR,ETH-CHF,SC-ETH,SC-USD,WAVES-EUR,XBT-USD,ADA-EUR,LINK-USD,NANO-EUR,PAXG-USD,SC-EUR,WAVES-ETH,REP-USD,EOS-XBT,ETC-ETH,XMR-USD,LTC-USD,MLN-XBT,XTZ-CAD,XBT-GBP,ADA-CAD,XTZ-EUR,ETH-JPY,XTZ-USD,XDG-XBT,XLM-EUR,ATOM-USD,ATOM-XBT,OMG-EUR,ZEC-JPY,ADA-XBT,GNO-ETH,LINK-XBT,ETC-EUR,BCH-XBT,QTUM-ETH,XBT-CHF,LTC-EUR,ETH-DAI,LSK-EUR,NANO-USD,QTUM-XBT,XRP-XBT,ZEC-USD,BAT-ETH,LINK-ETH,XBT-CAD,BAT-USD,GNO-XBT,ICX-XBT,PAXG-ETH,DAI-USDT,NANO-ETH,OMG-ETH,WAVES-XBT,ZEC-XBT,BAT-XBT,NANO-XBT,XBT-JPY,DASH-USD,ICX-ETH,LSK-ETH,QTUM-CAD,REP-XBT,XMR-XBT,XRP-EUR,ATOM-CAD,OMG-USD,LTC-XBT,MLN-ETH,XTZ-ETH,EOS-USD,ICX-EUR,SC-XBT,ETC-USD,BCH-USD,ICX-USD,QTUM-USD,ETH-XBT,ETH-USD,OMG-XBT,PAXG-EUR,REP-EUR,ADA-USD,USDT-USD,XMR-EUR,XRP-CAD,ATOM-EUR,ETC-XBT,XBT-EUR,XLM-USD,ATOM-ETH,LINK-EUR,PAXG-XBT,WAVES-USD,REP-ETH,XLM-XBT,QTUM-EUR,XTZ-XBT" - } - } - }, - "api": { - "authenticatedSupport": false, - "authenticatedWebsocketApiSupport": false, - "endpoints": { - "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" - }, - "credentials": { - "key": "Key", - "secret": "Secret" - }, - "credentialsValidator": { - "requiresKey": true, - "requiresSecret": true, - "requiresBase64DecodeSecret": true - } - }, - "features": { - "supports": { - "restAPI": true, - "restCapabilities": { - "tickerBatching": true, - "autoPairUpdates": true - }, - "websocketAPI": true, - "websocketCapabilities": {} + "name": "Gemini", + "enabled": true, + "verbose": false, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, + "websocketOrderbookBufferLimit": 5, + "baseCurrencies": "USD", + "currencyPairs": { + "requestFormat": { + "uppercase": true + }, + "configFormat": { + "uppercase": true + }, + "useGlobalFormat": true, + "assetTypes": [ + "spot" + ], + "pairs": { + "spot": { + "enabled": "BTCUSD", + "available": "BTCUSD,ETHBTC,ETHUSD,BCHUSD,BCHBTC,BCHETH,LTCUSD,LTCBTC,LTCETH,LTCBCH,ZECUSD,ZECBTC,ZECETH,ZECBCH,ZECLTC" + } + } + }, + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "autoPairUpdates": true + }, + "websocketAPI": true, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": false + } + }, + "bankAccounts": [ + { + "enabled": false, + "bankName": "", + "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ] }, - "enabled": { - "autoPairUpdates": true, - "websocketAPI": true - } - }, - "bankAccounts": [ { - "enabled": false, - "bankName": "", - "bankAddress": "", - "bankPostalCode": "", - "bankPostalCity": "", - "bankCountry": "", - "accountName": "", - "accountNumber": "", - "swiftCode": "", - "iban": "", - "supportedCurrencies": "" - } - ] - }, - { - "name": "Kucoin", - "enabled": true, - "verbose": false, - "httpTimeout": 15000000000, - "websocketResponseCheckTimeout": 30000000, - "websocketResponseMaxLimit": 7000000000, - "websocketTrafficTimeout": 30000000000, - "websocketOrderbookBufferLimit": 5, - "baseCurrencies": "USD", - "currencyPairs": { - "assetTypes": [ - "spot", - "margin", - "futures" - ], - "pairs": { - "spot": { - "assetEnabled": true, - "enabled": "BTC-USDT,ETH-BTC,ETH-USDT,LTC-USDT", - "available": "BTC-USDT,MHC-ETH,MHC-BTC,OXEN-BTC,OXEN-ETH,NRG-BTC,AVA-USDT,FET-BTC,FET-ETH,ANKR-BTC,MHC-USDT,XMR-BTC,XMR-ETH,RIF-BTC,MTV-BTC,MTV-ETH,CRO-BTC,MTV-USDT,KMD-BTC,KMD-USDT,RFOX-USDT,TEL-USDT,TT-USDT,AERGO-USDT,XMR-USDT,TRX-KCS,ATOM-BTC,ATOM-ETH,ATOM-USDT,ATOM-KCS,ETN-USDT,FTM-USDT,TOMO-USDT,VSYS-USDT,OCEAN-BTC,OCEAN-ETH,CHR-BTC,CHR-USDT,FX-BTC,FX-ETH,NIM-BTC,NIM-ETH,COTI-BTC,COTI-USDT,NRG-ETH,BNB-BTC,BNB-USDT,JAR-BTC,JAR-USDT,ALGO-BTC,ALGO-ETH,ALGO-USDT,XEM-BTC,XEM-USDT,CIX100-USDT,XTZ-BTC,XTZ-USDT,ZEC-BTC,ZEC-USDT,ADA-BTC,ADA-USDT,REV-USDT,WXT-BTC,WXT-USDT,FORESTPLUS-BTC,FORESTPLUS-USDT,BOLT-BTC,BOLT-USDT,ARPA-USDT,CHZ-BTC,CHZ-USDT,DAPPT-BTC,DAPPT-USDT,NOIA-BTC,NOIA-USDT,WIN-BTC,WIN-USDT,DERO-BTC,DERO-USDT,BTT-USDT,EOSC-USDT,ENQ-BTC,ENQ-USDT,ONE-BTC,ONE-USDT,TOKO-BTC,TOKO-USDT,VID-BTC,VID-USDT,LUNA-USDT,SXP-BTC,SXP-USDT,AKRO-BTC,AKRO-USDT,ROOBEE-BTC,WIN-TRX,MAP-BTC,MAP-USDT,AMPL-BTC,AMPL-USDT,DAG-USDT,POL-USDT,ARX-USDT,NWC-BTC,NWC-USDT,BEPRO-BTC,BEPRO-USDT,VRA-BTC,VRA-USDT,KSM-BTC,KSM-USDT,DASH-USDT,SUTER-USDT,ACOIN-USDT,SUTER-BTC,SENSO-USDT,PRE-BTC,XDB-USDT,SYLO-USDT,WOM-USDT,SENSO-BTC,DGB-USDT,LYXE-USDT,LYXE-ETH,XDB-BTC,STX-BTC,STX-USDT,XSR-USDT,COMP-USDT,CRO-USDT,KAI-USDT,KAI-BTC,WEST-BTC,WEST-USDT,EWT-BTC,WAVES-USDT,WAVES-BTC,ORN-USDT,AMPL-ETH,BNS-USDT,MKR-USDT,SUKU-BTC,MLK-BTC,MLK-USDT,JST-USDT,KAI-ETH,SUKU-USDT,DIA-USDT,DIA-BTC,LINK-BTC,LINK-USDT,DOT-USDT,DOT-BTC,SHA-BTC,SHA-USDT,EWT-USDT,USDJ-USDT,EFX-BTC,CKB-BTC,CKB-USDT,UMA-USDT,ALEPH-USDT,VELO-USDT,SUN-USDT,BUY-USDT,YFI-USDT,OXEN-USDT,UNI-USDT,UOS-USDT,UOS-BTC,NIM-USDT,DEGO-USDT,DEGO-ETH,UDOO-ETH,RFUEL-USDT,FIL-USDT,UBX-ETH,REAP-USDT,AAVE-USDT,AAVE-BTC,TONE-BTC,TONE-ETH,ELF-ETH,AERGO-BTC,IOST-ETH,KCS-USDT,SNX-ETH,TOMO-ETH,KCS-ETH,DRGN-BTC,WAN-ETH,NULS-ETH,AXPR-ETH,POWR-BTC,QTUM-BTC,MANA-BTC,TEL-BTC,XYO-ETH,AXPR-BTC,ETN-BTC,COV-ETH,VET-BTC,KCS-BTC,CAPP-ETH,ONT-BTC,DRGN-ETH,DAG-ETH,TOMO-BTC,WAN-BTC,KNC-ETH,CRPT-ETH,LTC-USDT,BAX-ETH,BSV-USDT,DENT-ETH,AION-ETH,LYM-ETH,TRAC-ETH,ENJ-BTC,WAXP-BTC,DGB-BTC,ELA-BTC,ZIL-BTC,BSV-BTC,XLM-USDT,IOTX-ETH,SOUL-BTC,DOCK-BTC,AMB-ETH,TRX-BTC,XRP-TUSD,NULS-BTC,ETH-DAI,LSK-BTC,GMB-ETH,GMB-BTC,NEO-ETH,OMG-ETH,BTC-TUSD,KAT-USDT,KNC-BTC,ELF-BTC,MANA-ETH,ETC-USDT,ONT-ETH,MKR-BTC,KAT-BTC,XRP-USDC,XYO-BTC,SNT-ETH,ZRX-BTC,LOOM-ETH,AION-BTC,POWR-ETH,OLT-ETH,OLT-BTC,SNT-BTC,TRAC-BTC,XLM-ETH,ETH-USDT,BSV-ETH,TRX-ETH,ETN-ETH,AOA-USDT,BCD-BTC,DENT-BTC,DOCK-ETH,KEY-BTC,EOS-KCS,XLM-BTC,ADB-ETH,TIME-ETH,CVC-BTC,LSK-ETH,QKC-BTC,AMB-BTC,USDT-TUSD,ETC-ETH,XRP-BTC,NEO-KCS,SNX-USDT,CRPT-BTC,IOTX-BTC,LTC-ETH,XRP-KCS,ADB-BTC,LTC-KCS,TEL-ETH,DCR-ETH,LYM-USDT,USDT-USDC,ETH-USDC,DAG-BTC,AVA-BTC,BTC-USDT,WAXP-ETH,XRP-USDT,KEY-ETH,VET-ETH,FTM-BTC,USDT-DAI,QKC-ETH,ETH-BTC,MAN-BTC,CPC-ETH,TRX-USDT,BTC-DAI,ONT-USDT,DASH-ETH,BAX-BTC,AVA-ETH,LOOM-BTC,MVP-BTC,MKR-ETH,COV-BTC,CPC-BTC,REQ-ETH,EOS-BTC,LTC-BTC,XRP-ETH,CAPP-BTC,FTM-ETH,BCD-ETH,ZRX-ETH,DGB-ETH,VET-USDT,REQ-BTC,UTK-BTC,PLAY-BTC,UTK-ETH,SNX-BTC,MVP-ETH,NEO-BTC,SOUL-ETH,NEO-USDT,ELA-ETH,OMG-BTC,TIME-BTC,AOA-BTC,ETC-BTC,DCR-BTC,BTC-USDC,ENJ-ETH,IOST-BTC,DASH-BTC,EOS-USDT,EOS-ETH,ZIL-ETH,ETH-TUSD,GAS-BTC,LYM-BTC,BCH-BTC,VSYS-BTC,BCH-USDT,MKR-DAI,SOLVE-BTC,GRIN-BTC,GRIN-USDT,UQC-BTC,UQC-ETH,OPCT-BTC,OPCT-ETH,PRE-USDT,SHR-BTC,SHR-USDT,UBXT-USDT,ROSE-USDT,USDC-USDT,CTI-USDT,CTI-ETH,ETH2-ETH,BUX-BTC,XHV-USDT,PLU-USDT,GRT-USDT,CAS-BTC,CAS-USDT,MSWAP-BTC,MSWAP-USDT,GOM2-BTC,GOM2-USDT,REVV-BTC,REVV-USDT,LON-USDT,1INCH-USDT,LOC-USDT,API3-USDT,UNFI-USDT,HTR-USDT,FRONT-USDT,FRONT-BTC,WBTC-BTC,WBTC-ETH,MIR-USDT,LTC-USDC,BCH-USDC,HYDRA-USDT,DFI-USDT,DFI-BTC,CRV-USDT,SUSHI-USDT,FRM-USDT,EOS-USDC,BSV-USDC,ZEN-USDT,CUDOS-USDT,ADA-USDC,REN-USDT,LRC-USDT,LINK-USDC,KLV-USDT,KLV-BTC,BOA-USDT,THETA-USDT,QNT-USDT,BAT-USDT,DOGE-USDT,DOGE-USDC,DAO-USDT,STRONG-USDT,TRIAS-USDT,TRIAS-BTC,DOGE-BTC,MITX-BTC,MITX-USDT,CAKE-USDT,ORAI-USDT,ZEE-USDT,LTX-USDT,LTX-BTC,MASK-USDT,KLV-TRX,IDEA-USDT,PHA-USDT,PHA-ETH,BCH-KCS,SRK-USDT,SRK-BTC,ADA-KCS,HTR-BTC,BSV-KCS,DOT-KCS,LINK-KCS,MIR-KCS,BNB-KCS,XLM-KCS,VET-KCS,SWINGBY-USDT,SWINGBY-BTC,XHV-BTC,DASH-KCS,UNI-KCS,AAVE-KCS,DOGE-KCS,ZEC-KCS,XTZ-KCS,GRT-KCS,ALGO-KCS,EWT-KCS,GAS-USDT,AVAX-USDT,AVAX-BTC,KRL-BTC,KRL-USDT,POLK-USDT,POLK-BTC,ENJ-USDT,MANA-USDT,RNDR-USDT,RNDR-BTC,RLY-USDT,ANC-USDT,SKEY-USDT,LAYER-USDT,TARA-USDT,TARA-ETH,IOST-USDT,DYP-USDT,DYP-ETH,XYM-USDT,XYM-BTC,PCX-USDT,PCX-BTC,ORBS-USDT,ORBS-BTC,BTC3L-USDT,BTC3S-USDT,ETH3L-USDT,ETH3S-USDT,ANKR-USDT,DSLA-USDT,DSLA-BTC,SAND-USDT,VAI-USDT,XCUR-USDT,XCUR-BTC,FLUX-USDT,OMG-USDT,ZIL-USDT,DODO-USDT,MAN-USDT,BAX-USDT,BOSON-USDT,BOSON-ETH,PUNDIX-USDT,PUNDIX-BTC,WAXP-USDT,HT-USDT,PDEX-USDT,LABS-USDT,LABS-ETH,GMB-USDT,PHNX-USDT,PHNX-BTC,HAI-USDT,EQZ-USDT,FORTH-USDT,HORD-USDT,CGG-USDT,UBX-USDT,GHX-USDT,TCP-USDT,STND-USDT,STND-ETH,TOWER-USDT,TOWER-BTC,ACE-USDT,LOCG-USDT,CARD-USDT,FLY-USDT,CWS-USDT,XDC-USDT,XDC-ETH,STRK-BTC,STRK-ETH,SHIB-USDT,POLX-USDT,KDA-USDT,KDA-BTC,ICP-USDT,ICP-BTC,STC-USDT,STC-BTC,GOVI-USDT,GOVI-BTC,FKX-USDT,CELO-USDT,CELO-BTC,CUSD-USDT,CUSD-BTC,FCL-USDT,MATIC-USDT,MATIC-BTC,ELA-USDT,CRPT-USDT,OPCT-USDT,OGN-USDT,OGN-BTC,OUSD-USDT,OUSD-BTC,TLOS-USDT,TLOS-BTC,YOP-USDT,YOP-ETH,GLQ-USDT,GLQ-BTC,MXC-USDT,ERSDL-USDT,HOTCROSS-USDT,ADA3L-USDT,ADA3S-USDT,HYVE-USDT,HYVE-BTC,DAPPX-USDT,KONO-USDT,PRQ-USDT,MAHA-USDT,MAHA-BTC,FEAR-USDT,PYR-USDT,PYR-BTC,PROM-USDT,PROM-BTC,GLCH-USDT,UNO-USDT,ALBT-USDT,ALBT-ETH,XCAD-USDT,EOS3L-USDT,EOS3S-USDT,BCH3L-USDT,BCH3S-USDT,ELON-USDT,APL-USDT,FCL-ETH,VEED-USDT,VEED-BTC,DIVI-USDT,PDEX-BTC,JUP-USDT,JUP-ETH,POLS-USDT,POLS-BTC,LPOOL-USDT,LPOOL-BTC,LSS-USDT,VET3L-USDT,VET3S-USDT,LTC3L-USDT,LTC3S-USDT,ABBC-USDT,ABBC-BTC,KOK-USDT,ROSN-USDT,DORA-USDT,DORA-BTC,ZCX-USDT,ZCX-BTC,NORD-USDT,GMEE-USDT,SFUND-USDT,XAVA-USDT,AI-USDT,ALPACA-USDT,IOI-USDT,NFT-USDT,NFT-TRX,MNST-USDT,MEM-USDT,AGIX-USDT,AGIX-BTC,AGIX-ETH,CQT-USDT,AIOZ-USDT,MARSH-USDT,HAPI-USDT,MODEFI-USDT,MODEFI-BTC,YFDAI-USDT,YFDAI-BTC,GENS-USDT,FORM-USDT,ARRR-USDT,ARRR-BTC,TOKO-KCS,EXRD-USDT,NGM-USDT,LPT-USDT,STMX-USDT,ASD-USDT,BOND-USDT,HAI-BTC,SOUL-USDT,2CRZ-USDT,NEAR-USDT,NEAR-BTC,DFYN-USDT,OOE-USDT,CFG-USDT,CFG-BTC,AXS-USDT,CLV-USDT,ROUTE-USDT,KAR-USDT,EFX-USDT,XDC-BTC,SHFT-USDT,PMON-USDT,DPET-USDT,ERG-USDT,ERG-BTC,SOL-USDT,SLP-USDT,LITH-USDT,LITH-ETH,XCH-USDT,HAKA-USDT,LAYER-BTC,MTL-USDT,MTL-BTC,IOTX-USDT,GALA-USDT,REQ-USDT,TXA-USDT,TXA-USDC,CIRUS-USDT,QI-USDT,QI-BTC,ODDZ-USDT,PNT-USDT,PNT-BTC,XPR-USDT,XPR-BTC,TRIBE-USDT,SHFT-BTC,MOVR-USDT,MOVR-ETH,WOO-USDT,WILD-USDT,QRDO-USDT,QRDO-ETH,SDN-USDT,SDN-ETH,MAKI-USDT,MAKI-BTC,REP-USDT,REP-BTC,REP-ETH,BNT-USDT,BNT-BTC,BNT-ETH,OXT-USDT,OXT-BTC,OXT-ETH,BAL-USDT,BAL-BTC,BAL-ETH,STORJ-USDT,STORJ-BTC,STORJ-ETH,YGG-USDT,NDAU-USDT,SDAO-USDT,SDAO-ETH,XRP3L-USDT,XRP3S-USDT,SKL-USDT,SKL-BTC,NMR-USDT,NMR-BTC,IXS-USDT,TRB-USDT,TRB-BTC,DYDX-USDT,XYO-USDT,GTC-USDT,GTC-BTC,EQX-USDT,EQX-BTC,RLC-USDT,RLC-BTC,XPRT-USDT,EGLD-USDT,EGLD-BTC,HBAR-USDT,HBAR-BTC,DOGE3L-USDT,DOGE3S-USDT,FLOW-USDT,FLOW-BTC,NKN-USDT,NKN-BTC,PBX-USDT,SOL3L-USDT,SOL3S-USDT,MLN-USDT,MLN-BTC,XNL-USDT,SOLVE-USDT,WNCG-USDT,WNCG-BTC,DMTR-USDT,LINK3L-USDT,LINK3S-USDT,DOT3L-USDT,DOT3S-USDT,CTSI-USDT,CTSI-BTC,ALICE-USDT,ALICE-BTC,ALICE-ETH,OPUL-USDT,ILV-USDT,BAND-USDT,BAND-BTC,FTT-USDT,FTT-BTC,DVPN-USDT,SKU-USDT,SKU-BTC,EDG-USDT,SLIM-USDT,TLM-USDT,TLM-BTC,TLM-ETH,DEXE-USDT,DEXE-BTC,DEXE-ETH,MATTER-USDT,CUDOS-BTC,RUNE-USDT,RUNE-BTC,RMRK-USDT,BMON-USDT,C98-USDT,BLOK-USDT,SOLR-USDT,ATOM3L-USDT,ATOM3S-USDT,UNI3L-USDT,UNI3S-USDT,WSIENNA-USDT,PUSH-USDT,PUSH-BTC,FORM-ETH,NTVRK-USDT,NTVRK-USDC,AXS3L-USDT,AXS3S-USDT,FTM3L-USDT,FTM3S-USDT,FLAME-USDT,AGLD-USDT,NAKA-USDT,YLD-USDT,TONE-USDT,REEF-USDT,REEF-BTC,TIDAL-USDT,TVK-USDT,TVK-BTC,INJ-USDT,INJ-BTC,BNB3L-USDT,BNB3S-USDT,MATIC3L-USDT,MATIC3S-USDT,NFTB-USDT,VEGA-USDT,VEGA-ETH,ALPHA-USDT,ALPHA-BTC,BADGER-USDT,BADGER-BTC,UNO-BTC,ZKT-USDT,AR-USDT,AR-BTC,XVS-USDT,XVS-BTC,JASMY-USDT,PERP-USDT,PERP-BTC,GHST-USDT,GHST-BTC,SCLP-USDT,SCLP-BTC,SUPER-USDT,SUPER-BTC,CPOOL-USDT,HERO-USDT,BASIC-USDT,XED-USDT,XED-BTC,AURY-USDT,SWASH-USDT,LTO-USDT,LTO-BTC,BUX-USDT,MTRG-USDT,DREAMS-USDT,SHIB-DOGE,QUICK-USDT,QUICK-BTC,TRU-USDT,TRU-BTC,WRX-USDT,WRX-BTC,TKO-USDT,TKO-BTC,SUSHI3L-USDT,SUSHI3S-USDT,NEAR3L-USDT,NEAR3S-USDT,DATA-USDT,DATA-BTC,NORD-BTC,ISP-USDT,CERE-USDT,SHILL-USDT,HEGIC-USDT,HEGIC-BTC,ERN-USDT,ERN-BTC,FTG-USDT,PAXG-USDT,PAXG-BTC,AUDIO-USDT,AUDIO-BTC,ENS-USDT,AAVE3L-USDT,AAVE3S-USDT,SAND3L-USDT,SAND3S-USDT,XTM-USDT,MNW-USDT,FXS-USDT,FXS-BTC,ATA-USDT,ATA-BTC,VXV-USDT,LRC-BTC,LRC-ETH,DPR-USDT,CWAR-USDT,CWAR-BTC,FLUX-BTC,EDG-BTC,PBR-USDT,WNXM-USDT,WNXM-BTC,ANT-USDT,ANT-BTC,COV-USDT,SWP-USDT,TWT-USDT,TWT-BTC,OM-USDT,OM-BTC,ADX-USDT,AVAX3L-USDT,AVAX3S-USDT,MANA3L-USDT,MANA3S-USDT,GLM-USDT,GLM-BTC,BAKE-USDT,BAKE-BTC,BAKE-ETH,NUM-USDT,VLX-USDT,VLX-BTC,TRADE-USDT,TRADE-BTC,1EARTH-USDT,MONI-USDT,LIKE-USDT,MFT-USDT,MFT-BTC,LIT-USDT,LIT-BTC,KAVA-USDT,SFP-USDT,SFP-BTC,BURGER-USDT,BURGER-BTC,ILA-USDT,CREAM-USDT,CREAM-BTC,RSR-USDT,RSR-BTC,BUY-BTC,IMX-USDT,GODS-USDT,KMA-USDT,SRM-USDT,SRM-BTC,POLC-USDT,XTAG-USDT,MNET-USDT,NGC-USDT,HARD-USDT,GALAX3L-USDT,GALAX3S-USDT,UNIC-USDT,POND-USDT,POND-BTC,VR-USDT,EPIK-USDT,NGL-USDT,NGL-BTC,KDON-USDT,PEL-USDT,CIRUS-ETH,LINA-USDT,LINA-BTC,KLAY-USDT,KLAY-BTC,CREDI-USDT,TRVL-USDT,LACE-USDT,LACE-ETH,ARKER-USDT,BONDLY-USDT,BONDLY-ETH,XEC-USDT,HEART-USDT,HEART-BTC,UNB-USDT,GAFI-USDT,KOL-USDT,KOL-ETH,H3RO3S-USDT,FALCONS-USDT,UFO-USDT,CHMB-USDT,GEEQ-USDT,ORC-USDT,RACEFI-USDT,PEOPLE-USDT,ADS-USDT,ADS-BTC,OCEAN-USDT,SOS-USDT,WHALE-USDT,TIME-USDT,CWEB-USDT,IOTA-USDT,IOTA-BTC,OOKI-USDT,OOKI-BTC,HNT-USDT,HNT-BTC,GGG-USDT,POWR-USDT,REVU-USDT,CLH-USDT,PLGR-USDT,GLMR-USDT,GLMR-BTC,LOVE-USDT,CTC-USDT,CTC-BTC,GARI-USDT,FRR-USDT,ASTR-USDT,ASTR-BTC,ERTHA-USDT,FCON-USDT,ACA-USDT,ACA-BTC,MTS-USDT,ROAR-USDT,HBB-USDT,SURV-USDT,CVX-USDT,AMP-USDT,ACT-USDT,MJT-USDT,MJT-KCS,SHX-USDT,SHX-BTC,STARLY-USDT,ONSTON-USDT,RANKER-USDT,WMT-USDT,XNO-USDT,XNO-BTC,MARS4-USDT,TFUEL-USDT,TFUEL-BTC,METIS-USDT,LAVAX-USDT,WAL-USDT,BULL-USDT,SON-USDT,MELOS-USDT,APE-USDT,GMT-USDT,BICO-USDT,STG-USDT,LMR-USDT,LMR-BTC,LOKA-USDT,URUS-USDT,JAM-USDT,JAM-ETH,BNC-USDT,LBP-USDT,CFX-USDT,LOOKS-USDT,XCN-USDT,XCN-BTC,KP3R-USDT,TITAN-USDT,INDI-USDT,UPO-USDT,SPELL-USDT,SLCL-USDT,CEEK-USDT,VEMP-USDT,BETA-USDT,NHCT-USDT,ARNM-USDT,FRA-USDT,VISION-USDT,COCOS-USDT,ALPINE-USDT,BNX-USDT,ZBC-USDT,WOOP-USDT,T-USDT,NYM-USDT,VOXEL-USDT,VOXEL-ETH,PSTAKE-USDT,SPA-USDT,SPA-ETH,SYNR-USDT,DAR-USDT,DAR-BTC,MV-USDT,XDEFI-USDT,RACA-USDT,XWG-USDT,HAWK-USDT,TRVL-BTC,SWFTC-USDT,IDEX-USDT,BRWL-USDT,PLATO-USDT,TAUM-USDT,CELR-USDT,AURORA-USDT,POSI-USDT,COOHA-USDT,KNC-USDT,EPK-USDT,PLD-USDT,PSL-USDT,PKF-USDT,OVR-USDT,SYS-USDT,SYS-BTC,BRISE-USDT,DG-USDT,EPX-USDT,GST-USDT,PLY-USDT,GAL-USDT,BSW-USDT,FITFI-USDT,FSN-USDT,H2O-USDT,GMM-USDT,AKT-USDT,SIN-USDT,AUSD-USDT,BOBA-USDT,KARA-USDT,BFC-USDT,BIFI-USDT,DFA-USDT,KYL-USDT,FCD-USDT,MBL-USDT,CELT-USDT,DUSK-USDT,USDD-USDT,USDD-USDC,FITFI-USDC,MBOX-USDT,MBOX-BTC,APE-USDC,AVAX-USDC,SHIB-USDC,XCN-USDC,TRX-USDC,NEAR-USDC,MATIC-USDC,FTM-USDC,ZIL-USDC,SOL-USDC,MLS-USDT,AFK-USDT,AFK-USDC,ACH-USDT,SCRT-USDT,SCRT-BTC,APE3L-USDT,APE3S-USDT,STORE-USDT,STORE-ETH,GMT3L-USDT,GMT3S-USDT,CCD-USDT,DOSE-USDC,LUNC-USDT,LUNC-USDC,USTC-USDT,USTC-USDC,GMT-USDC,VRA-USDC,DOT-USDC,RUNE-USDC,ATOM-USDC,BNB-USDC,JASMY-USDC,KCS-USDC,KDA-USDC,ALGO-USDC,LUNA-USDC,OP-USDT,OP-USDC,JASMY3L-USDT,JASMY3S-USDT,EVER-USDT,MOOV-USDT,IHC-USDT,ICX-USDT,ICX-ETH,BTC-BRL,ETH-BRL,USDT-BRL,WELL-USDT,FORT-USDT,USDP-USDT,USDD-TRX,CSPR-USDT,CSPR-ETH,WEMIX-USDT,REV3L-USDT,OLE-USDT,LDO-USDT,LDO-USDC,CULT-USDT,SWFTC-USDC,FIDA-USDT,BUSD-USDT,RBP-USDT,SRBP-USDT,HIBAYC-USDT,BUSD-USDC,OGV-USDT,WOMBAT-USDT,HIPUNKS-USDT,FT-USDT,ETC-USDC,HIENS4-USDT,EGAME-USDT,EGAME-BTC,STEPWATCH-USDT,HISAND33-USDT,DC-USDT,NEER-USDT,RVN-USDT,HIENS3-USDT,MC-USDT,PEEL-USDT,PEEL-BTC,SDL-USDT,SDL-BTC,SWEAT-USDT,HIODBS-USDT,CMP-USDT,PIX-USDT,MPLX-USDT,HIDOODLES-USDT,ETHW-USDT,QUARTZ-USDT,ACQ-USDT,ACQ-USDC,AOG-USDT,HIMAYC-USDT,PRMX-USDT,RED-USDT,PUMLX-USDT,XETA-USDT,GEM-USDT,DERC-USDT,P00LS-USDT,P00LS-USDC,KICKS-USDT,TRIBL-USDT,GMX-USDT,HIOD-USDT,POKT-USDT,EFI-USDT,APT-USDT,BBC-USDT,EUL-USDT,TON-USDT,PIAS-USDT,HIMEEBITS-USDT,HISQUIGGLE-USDT,XCV-USDT,HFT-USDT,HFT-USDC,ECOX-USDT,AMB-USDT,AZERO-USDT,HIFIDENZA-USDT,BEAT-USDT", - "requestFormat": { - "uppercase": true, - "delimiter": "-" - }, - "configFormat": { - "uppercase": true, - "delimiter": "-" - } - }, - "margin": { - "assetEnabled": true, - "enabled": "ETH-BTC,TRX-BTC,LTC-USDT,SOL-USDC", - "available": "BTC-USDT,MHC-ETH,MHC-BTC,OXEN-BTC,OXEN-ETH,NRG-BTC,AVA-USDT,FET-BTC,FET-ETH,ANKR-BTC,MHC-USDT,XMR-BTC,XMR-ETH,RIF-BTC,MTV-BTC,MTV-ETH,CRO-BTC,MTV-USDT,KMD-BTC,KMD-USDT,RFOX-USDT,TEL-USDT,TT-USDT,AERGO-USDT,XMR-USDT,TRX-KCS,ATOM-BTC,ATOM-ETH,ATOM-USDT,ATOM-KCS,ETN-USDT,FTM-USDT,TOMO-USDT,VSYS-USDT,OCEAN-BTC,OCEAN-ETH,CHR-BTC,CHR-USDT,FX-BTC,FX-ETH,NIM-BTC,NIM-ETH,COTI-BTC,COTI-USDT,NRG-ETH,BNB-BTC,BNB-USDT,JAR-BTC,JAR-USDT,ALGO-BTC,ALGO-ETH,ALGO-USDT,XEM-BTC,XEM-USDT,CIX100-USDT,XTZ-BTC,XTZ-USDT,ZEC-BTC,ZEC-USDT,ADA-BTC,ADA-USDT,REV-USDT,WXT-BTC,WXT-USDT,FORESTPLUS-BTC,FORESTPLUS-USDT,BOLT-BTC,BOLT-USDT,ARPA-USDT,CHZ-BTC,CHZ-USDT,DAPPT-BTC,DAPPT-USDT,NOIA-BTC,NOIA-USDT,WIN-BTC,WIN-USDT,DERO-BTC,DERO-USDT,BTT-USDT,EOSC-USDT,ENQ-BTC,ENQ-USDT,ONE-BTC,ONE-USDT,TOKO-BTC,TOKO-USDT,VID-BTC,VID-USDT,LUNA-USDT,SXP-BTC,SXP-USDT,AKRO-BTC,AKRO-USDT,ROOBEE-BTC,WIN-TRX,MAP-BTC,MAP-USDT,AMPL-BTC,AMPL-USDT,DAG-USDT,POL-USDT,ARX-USDT,NWC-BTC,NWC-USDT,BEPRO-BTC,BEPRO-USDT,VRA-BTC,VRA-USDT,KSM-BTC,KSM-USDT,DASH-USDT,SUTER-USDT,ACOIN-USDT,SUTER-BTC,SENSO-USDT,PRE-BTC,XDB-USDT,SYLO-USDT,WOM-USDT,SENSO-BTC,DGB-USDT,LYXE-USDT,LYXE-ETH,XDB-BTC,STX-BTC,STX-USDT,XSR-USDT,COMP-USDT,CRO-USDT,KAI-USDT,KAI-BTC,WEST-BTC,WEST-USDT,EWT-BTC,WAVES-USDT,WAVES-BTC,ORN-USDT,AMPL-ETH,BNS-USDT,MKR-USDT,SUKU-BTC,MLK-BTC,MLK-USDT,JST-USDT,KAI-ETH,SUKU-USDT,DIA-USDT,DIA-BTC,LINK-BTC,LINK-USDT,DOT-USDT,DOT-BTC,SHA-BTC,SHA-USDT,EWT-USDT,USDJ-USDT,EFX-BTC,CKB-BTC,CKB-USDT,UMA-USDT,ALEPH-USDT,VELO-USDT,SUN-USDT,BUY-USDT,YFI-USDT,OXEN-USDT,UNI-USDT,UOS-USDT,UOS-BTC,NIM-USDT,DEGO-USDT,DEGO-ETH,UDOO-ETH,RFUEL-USDT,FIL-USDT,UBX-ETH,REAP-USDT,AAVE-USDT,AAVE-BTC,TONE-BTC,TONE-ETH,ELF-ETH,AERGO-BTC,IOST-ETH,KCS-USDT,SNX-ETH,TOMO-ETH,KCS-ETH,DRGN-BTC,WAN-ETH,NULS-ETH,AXPR-ETH,POWR-BTC,QTUM-BTC,MANA-BTC,TEL-BTC,XYO-ETH,AXPR-BTC,ETN-BTC,COV-ETH,VET-BTC,KCS-BTC,CAPP-ETH,ONT-BTC,DRGN-ETH,DAG-ETH,TOMO-BTC,WAN-BTC,KNC-ETH,CRPT-ETH,LTC-USDT,BAX-ETH,BSV-USDT,DENT-ETH,AION-ETH,LYM-ETH,TRAC-ETH,ENJ-BTC,WAXP-BTC,DGB-BTC,ELA-BTC,ZIL-BTC,BSV-BTC,XLM-USDT,IOTX-ETH,SOUL-BTC,DOCK-BTC,AMB-ETH,TRX-BTC,XRP-TUSD,NULS-BTC,ETH-DAI,LSK-BTC,GMB-ETH,GMB-BTC,NEO-ETH,OMG-ETH,BTC-TUSD,KAT-USDT,KNC-BTC,ELF-BTC,MANA-ETH,ETC-USDT,ONT-ETH,MKR-BTC,KAT-BTC,XRP-USDC,XYO-BTC,SNT-ETH,ZRX-BTC,LOOM-ETH,AION-BTC,POWR-ETH,OLT-ETH,OLT-BTC,SNT-BTC,TRAC-BTC,XLM-ETH,ETH-USDT,BSV-ETH,TRX-ETH,ETN-ETH,AOA-USDT,BCD-BTC,DENT-BTC,DOCK-ETH,KEY-BTC,EOS-KCS,XLM-BTC,ADB-ETH,TIME-ETH,CVC-BTC,LSK-ETH,QKC-BTC,AMB-BTC,USDT-TUSD,ETC-ETH,XRP-BTC,NEO-KCS,SNX-USDT,CRPT-BTC,IOTX-BTC,LTC-ETH,XRP-KCS,ADB-BTC,LTC-KCS,TEL-ETH,DCR-ETH,LYM-USDT,USDT-USDC,ETH-USDC,DAG-BTC,AVA-BTC,BTC-USDT,WAXP-ETH,XRP-USDT,KEY-ETH,VET-ETH,FTM-BTC,USDT-DAI,QKC-ETH,ETH-BTC,MAN-BTC,CPC-ETH,TRX-USDT,BTC-DAI,ONT-USDT,DASH-ETH,BAX-BTC,AVA-ETH,LOOM-BTC,MVP-BTC,MKR-ETH,COV-BTC,CPC-BTC,REQ-ETH,EOS-BTC,LTC-BTC,XRP-ETH,CAPP-BTC,FTM-ETH,BCD-ETH,ZRX-ETH,DGB-ETH,VET-USDT,REQ-BTC,UTK-BTC,PLAY-BTC,UTK-ETH,SNX-BTC,MVP-ETH,NEO-BTC,SOUL-ETH,NEO-USDT,ELA-ETH,OMG-BTC,TIME-BTC,AOA-BTC,ETC-BTC,DCR-BTC,BTC-USDC,ENJ-ETH,IOST-BTC,DASH-BTC,EOS-USDT,EOS-ETH,ZIL-ETH,ETH-TUSD,GAS-BTC,LYM-BTC,BCH-BTC,VSYS-BTC,BCH-USDT,MKR-DAI,SOLVE-BTC,GRIN-BTC,GRIN-USDT,UQC-BTC,UQC-ETH,OPCT-BTC,OPCT-ETH,PRE-USDT,SHR-BTC,SHR-USDT,UBXT-USDT,ROSE-USDT,USDC-USDT,CTI-USDT,CTI-ETH,ETH2-ETH,BUX-BTC,XHV-USDT,PLU-USDT,GRT-USDT,CAS-BTC,CAS-USDT,MSWAP-BTC,MSWAP-USDT,GOM2-BTC,GOM2-USDT,REVV-BTC,REVV-USDT,LON-USDT,1INCH-USDT,LOC-USDT,API3-USDT,UNFI-USDT,HTR-USDT,FRONT-USDT,FRONT-BTC,WBTC-BTC,WBTC-ETH,MIR-USDT,LTC-USDC,BCH-USDC,HYDRA-USDT,DFI-USDT,DFI-BTC,CRV-USDT,SUSHI-USDT,FRM-USDT,EOS-USDC,BSV-USDC,ZEN-USDT,CUDOS-USDT,ADA-USDC,REN-USDT,LRC-USDT,LINK-USDC,KLV-USDT,KLV-BTC,BOA-USDT,THETA-USDT,QNT-USDT,BAT-USDT,DOGE-USDT,DOGE-USDC,DAO-USDT,STRONG-USDT,TRIAS-USDT,TRIAS-BTC,DOGE-BTC,MITX-BTC,MITX-USDT,CAKE-USDT,ORAI-USDT,ZEE-USDT,LTX-USDT,LTX-BTC,MASK-USDT,KLV-TRX,IDEA-USDT,PHA-USDT,PHA-ETH,BCH-KCS,SRK-USDT,SRK-BTC,ADA-KCS,HTR-BTC,BSV-KCS,DOT-KCS,LINK-KCS,MIR-KCS,BNB-KCS,XLM-KCS,VET-KCS,SWINGBY-USDT,SWINGBY-BTC,XHV-BTC,DASH-KCS,UNI-KCS,AAVE-KCS,DOGE-KCS,ZEC-KCS,XTZ-KCS,GRT-KCS,ALGO-KCS,EWT-KCS,GAS-USDT,AVAX-USDT,AVAX-BTC,KRL-BTC,KRL-USDT,POLK-USDT,POLK-BTC,ENJ-USDT,MANA-USDT,RNDR-USDT,RNDR-BTC,RLY-USDT,ANC-USDT,SKEY-USDT,LAYER-USDT,TARA-USDT,TARA-ETH,IOST-USDT,DYP-USDT,DYP-ETH,XYM-USDT,XYM-BTC,PCX-USDT,PCX-BTC,ORBS-USDT,ORBS-BTC,BTC3L-USDT,BTC3S-USDT,ETH3L-USDT,ETH3S-USDT,ANKR-USDT,DSLA-USDT,DSLA-BTC,SAND-USDT,VAI-USDT,XCUR-USDT,XCUR-BTC,FLUX-USDT,OMG-USDT,ZIL-USDT,DODO-USDT,MAN-USDT,BAX-USDT,BOSON-USDT,BOSON-ETH,PUNDIX-USDT,PUNDIX-BTC,WAXP-USDT,HT-USDT,PDEX-USDT,LABS-USDT,LABS-ETH,GMB-USDT,PHNX-USDT,PHNX-BTC,HAI-USDT,EQZ-USDT,FORTH-USDT,HORD-USDT,CGG-USDT,UBX-USDT,GHX-USDT,TCP-USDT,STND-USDT,STND-ETH,TOWER-USDT,TOWER-BTC,ACE-USDT,LOCG-USDT,CARD-USDT,FLY-USDT,CWS-USDT,XDC-USDT,XDC-ETH,STRK-BTC,STRK-ETH,SHIB-USDT,POLX-USDT,KDA-USDT,KDA-BTC,ICP-USDT,ICP-BTC,STC-USDT,STC-BTC,GOVI-USDT,GOVI-BTC,FKX-USDT,CELO-USDT,CELO-BTC,CUSD-USDT,CUSD-BTC,FCL-USDT,MATIC-USDT,MATIC-BTC,ELA-USDT,CRPT-USDT,OPCT-USDT,OGN-USDT,OGN-BTC,OUSD-USDT,OUSD-BTC,TLOS-USDT,TLOS-BTC,YOP-USDT,YOP-ETH,GLQ-USDT,GLQ-BTC,MXC-USDT,ERSDL-USDT,HOTCROSS-USDT,ADA3L-USDT,ADA3S-USDT,HYVE-USDT,HYVE-BTC,DAPPX-USDT,KONO-USDT,PRQ-USDT,MAHA-USDT,MAHA-BTC,FEAR-USDT,PYR-USDT,PYR-BTC,PROM-USDT,PROM-BTC,GLCH-USDT,UNO-USDT,ALBT-USDT,ALBT-ETH,XCAD-USDT,EOS3L-USDT,EOS3S-USDT,BCH3L-USDT,BCH3S-USDT,ELON-USDT,APL-USDT,FCL-ETH,VEED-USDT,VEED-BTC,DIVI-USDT,PDEX-BTC,JUP-USDT,JUP-ETH,POLS-USDT,POLS-BTC,LPOOL-USDT,LPOOL-BTC,LSS-USDT,VET3L-USDT,VET3S-USDT,LTC3L-USDT,LTC3S-USDT,ABBC-USDT,ABBC-BTC,KOK-USDT,ROSN-USDT,DORA-USDT,DORA-BTC,ZCX-USDT,ZCX-BTC,NORD-USDT,GMEE-USDT,SFUND-USDT,XAVA-USDT,AI-USDT,ALPACA-USDT,IOI-USDT,NFT-USDT,NFT-TRX,MNST-USDT,MEM-USDT,AGIX-USDT,AGIX-BTC,AGIX-ETH,CQT-USDT,AIOZ-USDT,MARSH-USDT,HAPI-USDT,MODEFI-USDT,MODEFI-BTC,YFDAI-USDT,YFDAI-BTC,GENS-USDT,FORM-USDT,ARRR-USDT,ARRR-BTC,TOKO-KCS,EXRD-USDT,NGM-USDT,LPT-USDT,STMX-USDT,ASD-USDT,BOND-USDT,HAI-BTC,SOUL-USDT,2CRZ-USDT,NEAR-USDT,NEAR-BTC,DFYN-USDT,OOE-USDT,CFG-USDT,CFG-BTC,AXS-USDT,CLV-USDT,ROUTE-USDT,KAR-USDT,EFX-USDT,XDC-BTC,SHFT-USDT,PMON-USDT,DPET-USDT,ERG-USDT,ERG-BTC,SOL-USDT,SLP-USDT,LITH-USDT,LITH-ETH,XCH-USDT,HAKA-USDT,LAYER-BTC,MTL-USDT,MTL-BTC,IOTX-USDT,GALA-USDT,REQ-USDT,TXA-USDT,TXA-USDC,CIRUS-USDT,QI-USDT,QI-BTC,ODDZ-USDT,PNT-USDT,PNT-BTC,XPR-USDT,XPR-BTC,TRIBE-USDT,SHFT-BTC,MOVR-USDT,MOVR-ETH,WOO-USDT,WILD-USDT,QRDO-USDT,QRDO-ETH,SDN-USDT,SDN-ETH,MAKI-USDT,MAKI-BTC,REP-USDT,REP-BTC,REP-ETH,BNT-USDT,BNT-BTC,BNT-ETH,OXT-USDT,OXT-BTC,OXT-ETH,BAL-USDT,BAL-BTC,BAL-ETH,STORJ-USDT,STORJ-BTC,STORJ-ETH,YGG-USDT,NDAU-USDT,SDAO-USDT,SDAO-ETH,XRP3L-USDT,XRP3S-USDT,SKL-USDT,SKL-BTC,NMR-USDT,NMR-BTC,IXS-USDT,TRB-USDT,TRB-BTC,DYDX-USDT,XYO-USDT,GTC-USDT,GTC-BTC,EQX-USDT,EQX-BTC,RLC-USDT,RLC-BTC,XPRT-USDT,EGLD-USDT,EGLD-BTC,HBAR-USDT,HBAR-BTC,DOGE3L-USDT,DOGE3S-USDT,FLOW-USDT,FLOW-BTC,NKN-USDT,NKN-BTC,PBX-USDT,SOL3L-USDT,SOL3S-USDT,MLN-USDT,MLN-BTC,XNL-USDT,SOLVE-USDT,WNCG-USDT,WNCG-BTC,DMTR-USDT,LINK3L-USDT,LINK3S-USDT,DOT3L-USDT,DOT3S-USDT,CTSI-USDT,CTSI-BTC,ALICE-USDT,ALICE-BTC,ALICE-ETH,OPUL-USDT,ILV-USDT,BAND-USDT,BAND-BTC,FTT-USDT,FTT-BTC,DVPN-USDT,SKU-USDT,SKU-BTC,EDG-USDT,SLIM-USDT,TLM-USDT,TLM-BTC,TLM-ETH,DEXE-USDT,DEXE-BTC,DEXE-ETH,MATTER-USDT,CUDOS-BTC,RUNE-USDT,RUNE-BTC,RMRK-USDT,BMON-USDT,C98-USDT,BLOK-USDT,SOLR-USDT,ATOM3L-USDT,ATOM3S-USDT,UNI3L-USDT,UNI3S-USDT,WSIENNA-USDT,PUSH-USDT,PUSH-BTC,FORM-ETH,NTVRK-USDT,NTVRK-USDC,AXS3L-USDT,AXS3S-USDT,FTM3L-USDT,FTM3S-USDT,FLAME-USDT,AGLD-USDT,NAKA-USDT,YLD-USDT,TONE-USDT,REEF-USDT,REEF-BTC,TIDAL-USDT,TVK-USDT,TVK-BTC,INJ-USDT,INJ-BTC,BNB3L-USDT,BNB3S-USDT,MATIC3L-USDT,MATIC3S-USDT,NFTB-USDT,VEGA-USDT,VEGA-ETH,ALPHA-USDT,ALPHA-BTC,BADGER-USDT,BADGER-BTC,UNO-BTC,ZKT-USDT,AR-USDT,AR-BTC,XVS-USDT,XVS-BTC,JASMY-USDT,PERP-USDT,PERP-BTC,GHST-USDT,GHST-BTC,SCLP-USDT,SCLP-BTC,SUPER-USDT,SUPER-BTC,CPOOL-USDT,HERO-USDT,BASIC-USDT,XED-USDT,XED-BTC,AURY-USDT,SWASH-USDT,LTO-USDT,LTO-BTC,BUX-USDT,MTRG-USDT,DREAMS-USDT,SHIB-DOGE,QUICK-USDT,QUICK-BTC,TRU-USDT,TRU-BTC,WRX-USDT,WRX-BTC,TKO-USDT,TKO-BTC,SUSHI3L-USDT,SUSHI3S-USDT,NEAR3L-USDT,NEAR3S-USDT,DATA-USDT,DATA-BTC,NORD-BTC,ISP-USDT,CERE-USDT,SHILL-USDT,HEGIC-USDT,HEGIC-BTC,ERN-USDT,ERN-BTC,FTG-USDT,PAXG-USDT,PAXG-BTC,AUDIO-USDT,AUDIO-BTC,ENS-USDT,AAVE3L-USDT,AAVE3S-USDT,SAND3L-USDT,SAND3S-USDT,XTM-USDT,MNW-USDT,FXS-USDT,FXS-BTC,ATA-USDT,ATA-BTC,VXV-USDT,LRC-BTC,LRC-ETH,DPR-USDT,CWAR-USDT,CWAR-BTC,FLUX-BTC,EDG-BTC,PBR-USDT,WNXM-USDT,WNXM-BTC,ANT-USDT,ANT-BTC,COV-USDT,SWP-USDT,TWT-USDT,TWT-BTC,OM-USDT,OM-BTC,ADX-USDT,AVAX3L-USDT,AVAX3S-USDT,MANA3L-USDT,MANA3S-USDT,GLM-USDT,GLM-BTC,BAKE-USDT,BAKE-BTC,BAKE-ETH,NUM-USDT,VLX-USDT,VLX-BTC,TRADE-USDT,TRADE-BTC,1EARTH-USDT,MONI-USDT,LIKE-USDT,MFT-USDT,MFT-BTC,LIT-USDT,LIT-BTC,KAVA-USDT,SFP-USDT,SFP-BTC,BURGER-USDT,BURGER-BTC,ILA-USDT,CREAM-USDT,CREAM-BTC,RSR-USDT,RSR-BTC,BUY-BTC,IMX-USDT,GODS-USDT,KMA-USDT,SRM-USDT,SRM-BTC,POLC-USDT,XTAG-USDT,MNET-USDT,NGC-USDT,HARD-USDT,GALAX3L-USDT,GALAX3S-USDT,UNIC-USDT,POND-USDT,POND-BTC,VR-USDT,EPIK-USDT,NGL-USDT,NGL-BTC,KDON-USDT,PEL-USDT,CIRUS-ETH,LINA-USDT,LINA-BTC,KLAY-USDT,KLAY-BTC,CREDI-USDT,TRVL-USDT,LACE-USDT,LACE-ETH,ARKER-USDT,BONDLY-USDT,BONDLY-ETH,XEC-USDT,HEART-USDT,HEART-BTC,UNB-USDT,GAFI-USDT,KOL-USDT,KOL-ETH,H3RO3S-USDT,FALCONS-USDT,UFO-USDT,CHMB-USDT,GEEQ-USDT,ORC-USDT,RACEFI-USDT,PEOPLE-USDT,ADS-USDT,ADS-BTC,OCEAN-USDT,SOS-USDT,WHALE-USDT,TIME-USDT,CWEB-USDT,IOTA-USDT,IOTA-BTC,OOKI-USDT,OOKI-BTC,HNT-USDT,HNT-BTC,GGG-USDT,POWR-USDT,REVU-USDT,CLH-USDT,PLGR-USDT,GLMR-USDT,GLMR-BTC,LOVE-USDT,CTC-USDT,CTC-BTC,GARI-USDT,FRR-USDT,ASTR-USDT,ASTR-BTC,ERTHA-USDT,FCON-USDT,ACA-USDT,ACA-BTC,MTS-USDT,ROAR-USDT,HBB-USDT,SURV-USDT,CVX-USDT,AMP-USDT,ACT-USDT,MJT-USDT,MJT-KCS,SHX-USDT,SHX-BTC,STARLY-USDT,ONSTON-USDT,RANKER-USDT,WMT-USDT,XNO-USDT,XNO-BTC,MARS4-USDT,TFUEL-USDT,TFUEL-BTC,METIS-USDT,LAVAX-USDT,WAL-USDT,BULL-USDT,SON-USDT,MELOS-USDT,APE-USDT,GMT-USDT,BICO-USDT,STG-USDT,LMR-USDT,LMR-BTC,LOKA-USDT,URUS-USDT,JAM-USDT,JAM-ETH,BNC-USDT,LBP-USDT,CFX-USDT,LOOKS-USDT,XCN-USDT,XCN-BTC,KP3R-USDT,TITAN-USDT,INDI-USDT,UPO-USDT,SPELL-USDT,SLCL-USDT,CEEK-USDT,VEMP-USDT,BETA-USDT,NHCT-USDT,ARNM-USDT,FRA-USDT,VISION-USDT,COCOS-USDT,ALPINE-USDT,BNX-USDT,ZBC-USDT,WOOP-USDT,T-USDT,NYM-USDT,VOXEL-USDT,VOXEL-ETH,PSTAKE-USDT,SPA-USDT,SPA-ETH,SYNR-USDT,DAR-USDT,DAR-BTC,MV-USDT,XDEFI-USDT,RACA-USDT,XWG-USDT,HAWK-USDT,TRVL-BTC,SWFTC-USDT,IDEX-USDT,BRWL-USDT,PLATO-USDT,TAUM-USDT,CELR-USDT,AURORA-USDT,POSI-USDT,COOHA-USDT,KNC-USDT,EPK-USDT,PLD-USDT,PSL-USDT,PKF-USDT,OVR-USDT,SYS-USDT,SYS-BTC,BRISE-USDT,DG-USDT,EPX-USDT,GST-USDT,PLY-USDT,GAL-USDT,BSW-USDT,FITFI-USDT,FSN-USDT,H2O-USDT,GMM-USDT,AKT-USDT,SIN-USDT,AUSD-USDT,BOBA-USDT,KARA-USDT,BFC-USDT,BIFI-USDT,DFA-USDT,KYL-USDT,FCD-USDT,MBL-USDT,CELT-USDT,DUSK-USDT,USDD-USDT,USDD-USDC,FITFI-USDC,MBOX-USDT,MBOX-BTC,APE-USDC,AVAX-USDC,SHIB-USDC,XCN-USDC,TRX-USDC,NEAR-USDC,MATIC-USDC,FTM-USDC,ZIL-USDC,SOL-USDC,MLS-USDT,AFK-USDT,AFK-USDC,ACH-USDT,SCRT-USDT,SCRT-BTC,APE3L-USDT,APE3S-USDT,STORE-USDT,STORE-ETH,GMT3L-USDT,GMT3S-USDT,CCD-USDT,DOSE-USDC,LUNC-USDT,LUNC-USDC,USTC-USDT,USTC-USDC,GMT-USDC,VRA-USDC,DOT-USDC,RUNE-USDC,ATOM-USDC,BNB-USDC,JASMY-USDC,KCS-USDC,KDA-USDC,ALGO-USDC,LUNA-USDC,OP-USDT,OP-USDC,JASMY3L-USDT,JASMY3S-USDT,EVER-USDT,MOOV-USDT,IHC-USDT,ICX-USDT,ICX-ETH,BTC-BRL,ETH-BRL,USDT-BRL,WELL-USDT,FORT-USDT,USDP-USDT,USDD-TRX,CSPR-USDT,CSPR-ETH,WEMIX-USDT,REV3L-USDT,OLE-USDT,LDO-USDT,LDO-USDC,CULT-USDT,SWFTC-USDC,FIDA-USDT,BUSD-USDT,RBP-USDT,SRBP-USDT,HIBAYC-USDT,BUSD-USDC,OGV-USDT,WOMBAT-USDT,HIPUNKS-USDT,FT-USDT,ETC-USDC,HIENS4-USDT,EGAME-USDT,EGAME-BTC,STEPWATCH-USDT,HISAND33-USDT,DC-USDT,NEER-USDT,RVN-USDT,HIENS3-USDT,MC-USDT,PEEL-USDT,PEEL-BTC,SDL-USDT,SDL-BTC,SWEAT-USDT,HIODBS-USDT,CMP-USDT,PIX-USDT,MPLX-USDT,HIDOODLES-USDT,ETHW-USDT,QUARTZ-USDT,ACQ-USDT,ACQ-USDC,AOG-USDT,HIMAYC-USDT,PRMX-USDT,RED-USDT,PUMLX-USDT,XETA-USDT,GEM-USDT,DERC-USDT,P00LS-USDT,P00LS-USDC,KICKS-USDT,TRIBL-USDT,GMX-USDT,HIOD-USDT,POKT-USDT,EFI-USDT,APT-USDT,BBC-USDT,EUL-USDT,TON-USDT,PIAS-USDT,HIMEEBITS-USDT,HISQUIGGLE-USDT,XCV-USDT,HFT-USDT,HFT-USDC,ECOX-USDT,AMB-USDT,AZERO-USDT,HIFIDENZA-USDT,BEAT-USDT", - "requestFormat": { - "uppercase": true, - "delimiter": "-" - }, - "configFormat": { - "uppercase": true, - "delimiter": "-" - } - }, - "futures": { - "assetEnabled": true, - "enabled": "ETH_USDCM,XBT_USDCM,SOL_USDTM", - "available": "XBT_USDTM,XBT_USDM,ETH_USDTM,BCH_USDTM,BSV_USDTM,LINK_USDTM,UNI_USDTM,YFI_USDTM,EOS_USDTM,DOT_USDTM,FIL_USDTM,ADA_USDTM,XRP_USDTM,LTC_USDTM,ETH_USDM,TRX_USDTM,GRT_USDTM,SUSHI_USDTM,XLM_USDTM,1INCH_USDTM,ZEC_USDTM,DASH_USDTM,DOT_USDM,XRP_USDM,AAVE_USDTM,KSM_USDTM,DOGE_USDTM,VET_USDTM,BNB_USDTM,SXP_USDTM,SOL_USDTM,CRV_USDTM,ALGO_USDTM,AVAX_USDTM,FTM_USDTM,MATIC_USDTM,THETA_USDTM,ATOM_USDTM,CHZ_USDTM,ENJ_USDTM,MANA_USDTM,DENT_USDTM,OCEAN_USDTM,BAT_USDTM,XEM_USDTM,QTUM_USDTM,XTZ_USDTM,SNX_USDTM,NEO_USDTM,ONT_USDTM,XMR_USDTM,COMP_USDTM,ETC_USDTM,WAVES_USDTM,BAND_USDTM,MKR_USDTM,RVN_USDTM,DGB_USDTM,SHIB_USDTM,ICP_USDTM,DYDX_USDTM,AXS_USDTM,HBAR_USDTM,EGLD_USDTM,ALICE_USDTM,YGG_USDTM,NEAR_USDTM,SAND_USDTM,C98_USDTM,ONE_USDTM,VRA_USDTM,GALA_USDTM,CHR_USDTM,LRC_USDTM,FLOW_USDTM,RNDR_USDTM,IOTX_USDTM,CRO_USDTM,WAXP_USDTM,PEOPLE_USDTM,OMG_USDTM,LINA_USDTM,IMX_USDTM,CELR_USDTM,ENS_USDTM,CELO_USDTM,CTSI_USDTM,ARPA_USDTM,KNC_USDTM,ROSE_USDTM,AGLD_USDTM,APE_USDTM,JASMY_USDTM,ZIL_USDTM,GMT_USDTM,RUNE_USDTM,LOOKS_USDTM,AUDIO_USDTM,KDA_USDTM,KAVA_USDTM,BAL_USDTM,GAL_USDTM,LUNA_USDTM,LUNC_USDTM,OP_USDTM,XCN_USDTM,UNFI_USDTM,LIT_USDTM,DUSK_USDTM,STORJ_USDTM,RSR_USDTM,OGN_USDTM,TRB_USDTM,PERP_USDTM,KLAY_USDTM,ANKR_USDTM,LDO_USDTM,WOO_USDTM,REN_USDTM,CVC_USDTM,INJ_USDTM,APT_USDTM,MASK_USDTM,REEF_USDTM,TON_USDTM,MAGIC_USDTM,CFX_USDTM,AGIX_USDTM,FXS_USDTM,FET_USDTM,AR_USDTM,GMX_USDTM,BLUR_USDTM,ASTR_USDTM,HIGH_USDTM,ACH_USDTM,STX_USDTM,SSV_USDTM,FLOKI_USDTM,CKB_USDTM,TRU_USDTM,QNT_USDTM,ETH_USDCM,MINA_USDTM,USDC_USDTM,T_USDTM,LQTY_USDTM,ARB_USDTM,DAR_USDTM,ID_USDTM,STG_USDTM,JOE_USDTM,RDNT_USDTM,DODO_USDTM,PAXG_USDTM,ZRX_USDTM,ICX_USDTM,HFT_USDTM,NKN_USDTM,HOOK_USDTM,ANT_USDTM,DC_USDTM,BEL_USDTM,SUI_USDTM,PEPE_USDTM,IDEX_USDTM,GNS_USDTM,CETUS_USDTM,KAS_USDTM,ORDI_USDTM,WOJAK_USDTM,POGAI_USDTM,UMA_USDTM,RAD_USDTM,XBT_USDCM,PHB_USDTM,FTT_USDTM,10000LADYS_USDTM,LEVER_USDTM,TURBO_USDTM,TOMO_USDTM,BOB_USDTM,KEY_USDTM,EDU_USDTM,MTL_USDTM,FLUX_USDTM,COMBO_USDTM,AMB_USDTM,ALPHA_USDTM,SFP_USDTM,MAV_USDTM,MDT_USDTM,XEC_USDTM,XVG_USDTM,1000PEPE2_USDTM,PENDLE_USDTM,STMX_USDTM,WLD_USDTM,LPT_USDTM,GTC_USDTM,BNT_USDTM,OXT_USDTM,BLZ_USDTM,SEI_USDTM,BAKE_USDTM,CYBER_USDTM,NMR_USDTM,FLM_USDTM,SPELL_USDTM,ARK_USDTM,XBT_MU23,XBT_MZ23", - "requestFormat": { - "uppercase": true, - "delimiter": "" - }, - "configFormat": { - "uppercase": true, - "delimiter": "_" - } - } - } - }, - "api": { - "authenticatedSupport": true, - "authenticatedWebsocketApiSupport": true, - "endpoints": { - "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" - }, - "credentials": { - "key": "Key", - "secret": "Secret", - "clientID": "ClientID" - }, - "credentialsValidator": { - "requiresKey": true, - "requiresSecret": true - } - }, - "features": { - "supports": { - "restAPI": true, - "restCapabilities": { - "tickerBatching": true, - "autoPairUpdates": true - }, - "websocketAPI": true, - "websocketCapabilities": {} + "name": "HitBTC", + "enabled": true, + "verbose": false, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, + "websocketOrderbookBufferLimit": 5, + "baseCurrencies": "USD", + "currencyPairs": { + "requestFormat": { + "uppercase": true + }, + "configFormat": { + "uppercase": true, + "delimiter": "-" + }, + "useGlobalFormat": true, + "assetTypes": [ + "spot" + ], + "pairs": { + "spot": { + "enabled": "BTC-USD", + "available": "BCN-BTC,BTC-USD,DASH-BTC,DOGE-BTC,DOGE-USD,EMC-BTC,ETH-BTC,LSK-BTC,LTC-BTC,LTC-USD,NXT-BTC,SBD-BTC,SC-BTC,STEEM-BTC,XDN-BTC,XEM-BTC,XMR-BTC,ARDR-BTC,ZEC-BTC,WAVES-BTC,MAID-BTC,DGD-BTC,SNGLS-BTC,1ST-BTC,TRST-BTC,TIME-BTC,GNO-BTC,REP-BTC,XMR-USD,DASH-USD,ETH-USD,NXT-USD,ZRC-BTC,BOS-BTC,DCT-BTC,ANT-BTC,AEON-BTC,GUP-BTC,PLU-BTC,LUN-BTC,EDG-BTC,RLC-BTC,SWT-BTC,TKN-BTC,WINGS-BTC,XAUR-BTC,AE-BTC,PTOY-BTC,ZEC-USD,XEM-USD,BCN-USD,XDN-USD,MAID-USD,ETC-BTC,ETC-USD,PLBT-BTC,BNT-BTC,SNT-ETH,CVC-USD,PAY-ETH,OAX-ETH,OMG-ETH,BQX-ETH,XTZ-BTC,DICE-BTC,PTOY-ETH,1ST-ETH,XAUR-ETH,TIME-ETH,DICE-ETH,SWT-ETH,XMR-ETH,ETC-ETH,DASH-ETH,ZEC-ETH,PLU-ETH,GNO-ETH,XRP-BTC,STRAT-USD,STRAT-BTC,SNC-ETH,ADX-ETH,BET-ETH,EOS-ETH,DENT-ETH,SAN-ETH,EOS-BTC,EOS-USD,XTZ-ETH,XTZ-USD,MYB-ETH,SUR-ETH,IXT-ETH,PLR-ETH,TIX-ETH,PRO-ETH,AVT-ETH,EVX-USD,DLT-BTC,BNT-ETH,BNT-USD,MANA-USD,DNT-BTC,FYP-BTC,OPT-BTC,TNT-ETH,STX-BTC,STX-ETH,STX-USD,TNT-USD,TNT-BTC,ENG-ETH,XUC-USD,SNC-BTC,SNC-USD,OAX-USD,OAX-BTC,ZRX-BTC,ZRX-ETH,ZRX-USD,RVT-BTC,PPC-BTC,PPC-USD,QTUM-ETH,IGNIS-ETH,BMC-BTC,BMC-ETH,BMC-USD,CND-BTC,CND-ETH,CND-USD,CDT-ETH,CDT-USD,FUN-BTC,FUN-ETH,FUN-USD,HVN-BTC,HVN-ETH,POE-BTC,POE-ETH,AMB-USD,AMB-ETH,AMB-BTC,HPC-BTC,PPT-ETH,MTH-BTC,MTH-ETH,LRC-BTC,LRC-ETH,ICX-BTC,ICX-ETH,NEO-BTC,NEO-ETH,NEO-USD,CSNO-BTC,ICX-USD,IND-ETH,KICK-BTC,YOYOW-BTC,CDT-BTC,XVG-BTC,XVG-ETH,XVG-USD,DGB-BTC,DGB-ETH,DGB-USD,DCN-ETH,DCN-USD,VIBE-BTC,ENJ-BTC,ENJ-ETH,ENJ-USD,ZSC-BTC,ZSC-ETH,ZSC-USD,TRX-BTC,TRX-ETH,TRX-USD,ART-BTC,EVX-BTC,EVX-ETH,SUB-BTC,SUB-ETH,SUB-USD,WTC-BTC,BTM-BTC,BTM-ETH,BTM-USD,LIFE-BTC,VIB-BTC,VIB-ETH,VIB-USD,DRT-ETH,STU-USD,OMG-BTC,PAY-BTC,PPT-BTC,SNT-BTC,BTG-BTC,BTG-ETH,BTG-USD,SMART-BTC,SMART-ETH,SMART-USD,XUC-ETH,XUC-BTC,LA-ETH,EDO-BTC,EDO-ETH,EDO-USD,HGT-ETH,IXT-BTC,SCL-BTC,ETP-BTC,ETP-ETH,ETP-USD,NEBL-BTC,NEBL-ETH,ARN-BTC,ARN-ETH,STU-BTC,STU-ETH,GVT-ETH,BTX-BTC,LTC-ETH,BCN-ETH,MAID-ETH,NXT-ETH,STRAT-ETH,XDN-ETH,XEM-ETH,PLR-BTC,SUR-BTC,BQX-BTC,DOGE-ETH,AMM-BTC,AMM-ETH,AMM-USD,DBIX-BTC,PRE-BTC,ZAP-BTC,DOV-BTC,DOV-ETH,XRP-ETH,XRP-USD,HSR-BTC,LEND-BTC,LEND-ETH,SPF-ETH,SBTC-BTC,SBTC-ETH,LOC-BTC,LOC-ETH,LOC-USD,SWFTC-BTC,SWFTC-ETH,SWFTC-USD,STAR-ETH,SBTC-USD,STORM-BTC,DIM-ETH,DIM-USD,DIM-BTC,NGC-BTC,NGC-ETH,NGC-USD,EMC-ETH,EMC-USD,MCO-BTC,MCO-ETH,MCO-USD,MANA-ETH,MANA-BTC,CPAY-ETH,DATA-BTC,DATA-ETH,DATA-USD,UTT-BTC,UTT-ETH,UTT-USD,KMD-BTC,KMD-ETH,KMD-USD,QTUM-USD,QTUM-BTC,SNT-USD,OMG-USD,EKO-BTC,EKO-ETH,ADX-BTC,ADX-USD,LSK-ETH,LSK-USD,PLR-USD,SUR-USD,BQX-USD,DRT-USD,REP-ETH,REP-USD,WAXP-BTC,WAXP-ETH,WAXP-USD,C20-BTC,C20-ETH,IDH-BTC,IDH-ETH,IPL-BTC,COV-BTC,COV-ETH,SENT-BTC,SENT-ETH,SENT-USD,SMT-BTC,SMT-ETH,SMT-USD,CHAT-BTC,CHAT-ETH,CHAT-USD,TRAC-ETH,JNT-ETH,UTK-BTC,UTK-ETH,UTK-USD,GNX-ETH,CHSB-BTC,CHSB-ETH,DAY-BTC,DAY-ETH,DAY-USD,NEU-BTC,NEU-ETH,NEU-USD,TAU-BTC,FLP-BTC,FLP-ETH,FLP-USD,R-BTC,R-ETH,EKO-USD,BCPT-ETH,BCPT-USD,PKT-BTC,PKT-ETH,BETR-BTC,BETR-ETH,HAND-ETH,HAND-USD,CHP-ETH,BCPT-BTC,ACT-BTC,ACT-ETH,ACT-USD,ADA-BTC,ADA-ETH,ADA-USD,SIG-BTC,MTX-BTC,MTX-ETH,MTX-USD,WIZ-BTC,WIZ-ETH,WIZ-USD,DADI-BTC,DADI-ETH,BDG-ETH,DATX-BTC,DATX-ETH,TRUE-BTC,DRG-BTC,DRG-ETH,BANCA-BTC,BANCA-ETH,ZAP-ETH,ZAP-USD,AUTO-BTC,SOC-BTC,OCN-BTC,OCN-ETH,STQ-BTC,STQ-ETH,XLM-BTC,XLM-ETH,XLM-USD,IOTA-BTC,IOTA-ETH,IOTA-USD,DRT-BTC,BETR-USD,ERT-BTC,CRPT-BTC,CRPT-USD,MESH-BTC,MESH-ETH,MESH-USD,IHT-BTC,IHT-ETH,IHT-USD,SCC-BTC,YCC-BTC,DAN-BTC,TEL-BTC,TEL-ETH,NCT-BTC,NCT-ETH,NCT-USD,BMH-BTC,BANCA-USD,BERRY-BTC,BERRY-ETH,BERRY-USD,GBX-BTC,GBX-ETH,GBX-USD,SHIP-BTC,SHIP-ETH,NANO-BTC,NANO-ETH,NANO-USD,LNC-BTC,KIN-ETH,ARDR-USD,FOTA-ETH,FOTA-BTC,CVT-BTC,CVT-ETH,CVT-USD,STQ-USD,GNT-BTC,GNT-ETH,GNT-USD,GET-BTC,MITH-BTC,MITH-ETH,MITH-USD,DADI-USD,TKY-BTC,ACAT-BTC,ACAT-ETH,ACAT-USD,BTX-USD,WIKI-BTC,WIKI-ETH,WIKI-USD,ONT-BTC,ONT-ETH,ONT-USD,FTX-BTC,FTX-ETH,NAVI-BTC,VME-ETH,NAVI-ETH,LND-ETH,CSM-BTC,NANJ-BTC,NTK-BTC,NTK-ETH,NTK-USD,AUC-BTC,AUC-ETH,CMCT-BTC,CMCT-ETH,CMCT-USD,MAN-BTC,MAN-ETH,MAN-USD,PNT-BTC,PNT-ETH,FXT-BTC,NEXO-BTC,PAT-BTC,PAT-ETH,XMC-BTC,FXT-ETH,XMC-ETH,XMC-USD,FDZ-BTC,FDZ-ETH,FDZ-USD,SPD-BTC,SPD-ETH,MITX-BTC,TIV-BTC,B2G-BTC,B2G-USD,HBZ-BTC,FACE-BTC,FACE-ETH,HBZ-ETH,HBZ-USD,CPT-BTC,PAT-USD,HTML-BTC,HTML-ETH,MITX-ETH,BTS-BTC,BNK-BTC,BNK-ETH,BNK-USD,TIV-ETH,TIV-USD,CSM-ETH,CSM-USD,INK-BTC,IOST-BTC,INK-ETH,INK-USD,CBC-BTC,IOST-USD,ZIL-BTC,ABYSS-BTC,ABYSS-ETH,ZIL-USD,BCI-BTC,CBC-ETH,CBC-USD,PITCH-BTC,PITCH-ETH,HTML-USD,TDS-BTC,TDS-ETH,TDS-USD,SBD-ETH,SBD-USD,DPN-BTC,UUU-BTC,UUU-ETH,XBP-BTC,ELEC-BTC,ELEC-ETH,ELEC-USD,QNTU-BTC,QNTU-ETH,QNTU-USD,IPL-ETH,IPL-USD,CENNZ-BTC,CENNZ-ETH,SWM-BTC,SPF-USD,SPF-BTC,LCC-BTC,HGT-BTC,ETH-TUSD,BTC-TUSD,LTC-TUSD,XMR-TUSD,ZRX-TUSD,NEO-TUSD,USD-TUSD,BTC-DAI,ETH-DAI,MKR-DAI,EOS-DAI,USD-DAI,MKR-BTC,MKR-ETH,MKR-USD,TUSD-DAI,NEO-DAI,LTC-DAI,XMR-DAI,XRP-DAI,NEXO-ETH,NEXO-USD,DWS-BTC,DWS-ETH,DWS-USD,APPC-BTC,APPC-ETH,APPC-USD,BIT-ETH,SPC-BTC,SPC-ETH,SPC-USD,REX-BTC,REX-ETH,REX-USD,ELF-BTC,ELF-USD,BCD-BTC,BCD-USD,CVCOIN-BTC,CVCOIN-ETH,CVCOIN-USD,EDG-ETH,EDG-USD,NLC2-BTC,DASH-EURS,ZEC-EURS,BTC-EURS,EOS-EURS,ETH-EURS,LTC-EURS,NEO-EURS,XMR-EURS,XRP-EURS,EURS-USD,EURS-TUSD,EURS-DAI,MNX-USD,ROX-ETH,ZPR-ETH,MNX-BTC,MNX-ETH,KIND-BTC,KIND-ETH,ENGT-BTC,ENGT-ETH,PMA-BTC,PMA-ETH,TV-BTC,TV-ETH,TV-USD,BAT-BTC,BAT-ETH,BAT-USD,SRN-BTC,SRN-ETH,SRN-USD,SVD-BTC,SVD-ETH,SVD-USD,GST-BTC,GST-ETH,GST-USD,BNB-BTC,BNB-ETH,BNB-USD,DIT-BTC,DIT-ETH,POA20-BTC,PROC-BTC,POA20-ETH,POA20-USD,POA20-DAI,NIM-BTC,USE-BTC,USE-ETH,DAV-BTC,DAV-ETH,ABTC-BTC,NIM-ETH,ABA-BTC,ABA-ETH,ABA-USD,BCN-EOS,LTC-EOS,XMR-EOS,DASH-EOS,TRX-EOS,NEO-EOS,ZEC-EOS,LSK-EOS,XEM-EOS,XRP-EOS,RCN-BTC,RCN-ETH,RCN-USD,HMQ-BTC,HMQ-ETH,MYST-BTC,MYST-ETH,USD-GUSD,BTC-GUSD,ETH-GUSD,EOS-GUSD,AXPR-BTC,AXPR-ETH,DAG-BTC,DAG-ETH,BITS-BTC,BITS-ETH,BITS-USD,CDCC-BTC,CDCC-ETH,CDCC-USD,VET-BTC,VET-ETH,VET-USD,SILK-ETH,BOX-BTC,BOX-ETH,BOX-EURS,BOX-EOS,VOCO-BTC,VOCO-ETH,VOCO-USD,PASS-BTC,PASS-ETH,SLX-BTC,SLX-USD,PBTT-BTC,PMA-USD,TRAD-BTC,DGTX-BTC,DGTX-ETH,DGTX-USD,MRK-BTC,MRK-ETH,DGB-TUSD,SNBL-BTC,BCH-BTC,BCH-USD,BSV-BTC,BSV-USD,BKX-BTC,NPLC-BTC,NPLC-ETH,ETN-BTC,ETN-ETH,ETN-USD,DTR-BTC,DTR-ETH,TDP-BTC,HBT-ETH,PXG-BTC,PXG-USD,BTC-PAX,ETH-PAX,USD-PAX,BTC-USDC,ETH-USDC,USD-USDC,TUSD-USDC,DAI-USDC,EOS-PAX,CLO-BTC,CLO-ETH,CLO-USD,PETH-BTC,PETH-ETH,PETH-USD,BRD-BTC,BRD-ETH,NMR-BTC,SALT-BTC,SALT-ETH,POLY-BTC,POLY-ETH,POWR-BTC,POWR-ETH,STORJ-BTC,STORJ-ETH,STORJ-USD,MLN-BTC,MLN-ETH,BDG-BTC,POA-ETH,POA-BTC,POA-USD,POA-DAI,KIN-BTC,VEO-BTC,PLA-BTC,PLA-ETH,PLA-USD,BTT-BTC,BTT-USD,BTT-ETH,ZEN-BTC,ZEN-ETH,ZEN-USD,GRIN-BTC,GRIN-ETH,GRIN-USD,FET-BTC,HT-BTC,HT-USD,XZC-BTC,XZC-ETH,XZC-USD,VRA-BTC,VRA-ETH,BTC-KRWB,USD-KRWB,WBTC-ETH,CRO-BTC,CRO-ETH,CRO-USD,GAS-BTC,GAS-ETH,GAS-USD,ORMEUS-BTC,ORMEUS-ETH,SWM-ETH,SWM-USD,PRE-ETH,PHX-BTC,PHX-ETH,PHX-USD,BET-BTC,USD-EOSDT,BTC-EOSDT,ETH-EOSDT,EOS-EOSDT,DAI-EOSDT,NUT-BTC,NUT-EOS,NUT-USD,CUTE-BTC,CUTE-ETH,CUTE-USD,CUTE-EOS,XCON-BTC,DCR-BTC,DCR-ETH,DCR-USD,MG-BTC,MG-ETH,MG-EOS,MG-USD,GNX-BTC,PRO-BTC,EURS-EOSDT,TUSD-EOSDT,ECOIN-BTC,ECOIN-ETH,ECOIN-USD,AGI-BTC,LOOM-BTC,LOOM-ETH,BLZ-BTC,QKC-BTC,QKC-ETH,KNC-BTC,KNC-ETH,KNC-USD,KEY-BTC,KEY-ETH,ATOM-BTC,ATOM-USD,ATOM-ETH,BRDG-BTC,BRDG-ETH,BRDG-USD,MTL-BTC,MTL-ETH,EXP-BTC,BTCB-BTC,PBT-BTC,PBT-ETH,LINK-BTC,LINK-ETH,LINK-USD,USD-USDT20,PHB-BTC,BCH-ETH,BCH-DAI,BCH-TUSD,BCH-EURS,DAPP-BTC,DAPP-EOS,BTC-USDT20,DENT-BTC,DENT-USD,NJBC-BTC,NJBC-ETH,XRC-BTC,EOS-BCH,LTC-BCH,XRP-BCH,TRX-BCH,XLM-BCH,ETC-BCH,DASH-BCH,ZEC-BCH,BKX-USD,LAMB-BTC,NPXS-BTC,HBAR-BTC,HBAR-USD,ONE-BTC,RFR-BTC,RFR-USD,BUSD-USD,PAXG-BTC,PAXG-USD,REN-BTC,IGNIS-BTC,CEL-BTC,CEL-ETH,WIN-USD,ADK-BTC,PART-BTC,SOZ-BTC,SOZ-ETH,SOZ-USD,WAVES-USD,ADA-BCH,ONT-BCH,XMR-BCH,ATOM-BCH,LINK-BCH,OMG-BCH,WAVES-BCH,IOTX-BTC,HOT-BTC,SLV-BTC,HEDG-BTC,CHZ-BTC,CHZ-USD,COCOS-BTC,COCOS-USD,SEELE-BTC,SEELE-USD,MDA-BTC,LEO-USD,REM-BTC,REM-ETH,REM-USD,SCD-DAI,BTC-BUSD,RVN-BTC,BST-BTC,ERD-BTC,KRL-BTC,FTT-BTC,FTT-USD,RAISE-BTC,RAISE-ETH" + } + } + }, + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "tickerBatching": true, + "autoPairUpdates": true + }, + "websocketAPI": true, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": false + } + }, + "bankAccounts": [ + { + "enabled": false, + "bankName": "", + "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ] }, - "enabled": { - "autoPairUpdates": true, - "websocketAPI": true - } - }, - "bankAccounts": [ { - "enabled": false, - "bankName": "", - "bankAddress": "", - "bankPostalCode": "", - "bankPostalCity": "", - "bankCountry": "", - "accountName": "", - "accountNumber": "", - "swiftCode": "", - "iban": "", - "supportedCurrencies": "" - } - ] - }, - { - "name": "LBank", - "enabled": true, - "verbose": false, - "httpTimeout": 15000000000, - "websocketResponseCheckTimeout": 30000000, - "websocketResponseMaxLimit": 7000000000, - "websocketTrafficTimeout": 30000000000, - "websocketOrderbookBufferLimit": 5, - "baseCurrencies": "USD", - "currencyPairs": { - "requestFormat": { - "uppercase": false, - "delimiter": "_" - }, - "configFormat": { - "uppercase": false, - "delimiter": "_" - }, - "useGlobalFormat": true, - "assetTypes": [ - "spot" - ], - "pairs": { - "spot": { - "enabled": "eth_btc", - "available": "FBC_USDT,GALT_USDT,IOEX_USDT,OATH_USDT,BLOC_USDT,BTC_USDT,ETH_USDT,ETH_BTC,ABBC_BTC,KISC_ETH,BXA_USDT,ATP_USDT,MAT_USDT,SKY_BTC,RNT_USDT,VENA_USDT,GRIN_USDT,IDA_USDT,PNT_USDT,OPX_USDT,VTHO_BTC,AMO_ETH,UBEX_BTC,EOS_BTC,UBEX_USDT,TNS_BTC,SAIT_ETH,DAX_BTC,DAX_ETH,DALI_USDT,VET_USDT,BCH_BTC,BCH_USDT,NEO_USDT,QTUM_USDT,ZEC_USDT,VET_BTC,PAI_BTC,PNT_BTC,NEO_BTC,DASH_BTC,LTC_BTC,ETC_BTC,QTUM_BTC,ZEC_BTC,SC_BTC,BTS_BTC,CPX_BTC,XWC_BTC,FIL6_BTC,FIL12_BTC,FIL36_BTC,EOS_USDT,UT_ETH,ELA_ETH,VET_ETH,VTHO_ETH,PAI_ETH,HER_ETH,PTT_ETH,TAC_ETH,IDHUB_ETH,SSC_ETH,SKM_ETH,PLY_ETH,EXT_ETH,EOS_ETH,YOYOW_ETH,TRX_ETH,QTUM_ETH,ZEC_ETH,BTS_ETH,BTM_ETH,MITH_ETH,NAS_ETH,MAN_ETH,DBC_ETH,BTO_ETH,DDD_ETH,CPX_ETH,CS_ETH,IHT_ETH,OCN_ETH,EKO_ETH,XWC_ETH,PUT_ETH,PNT_ETH,AAC_ETH,FIL6_ETH,FIL12_ETH,FIL36_ETH,SEER_ETH,BSB_ETH,CDC_ETH,GRAMS_ETH,DDMX_ETH,EAI_ETH,BNB_USDT,HT_USDT,KBC_BTC,KBC_USDT,MAI_USDT,PHV_USDT,GT_USDT,VOKEN_USDT,CYE_USDT,BRC_USDT,BTC_AUSD,DDMX_USDT,SEAL_USDT,SEOS_BTC,BTY_USDT,FO_USDT,DLX_USDT,BFC_USDT,LBK_USDT,SERO_USDT,MTV_USDT,CKB_USDT,ARPA_USDT,ZIP_USDT,AT_USDT,DOT_USDT,DILI_USDT,DUO_USDT,TEP_USDT,BIKI_USDT,MX_USDT,DNS_USDT,OKB_USDT,FLDT_USDT,CCTC_USDT,WIN_USDT,BTT_USDT,TRX_USDT,GRS_BTC,GST_USDT,GST_ETH,ABBC_USDT,UTK_USDT,GKI_USDT,BPX_USDT,SUTER_USDT,LT_USDT,LM_USDT,HTDF_USDT" - } - } - }, - "api": { - "authenticatedSupport": false, - "authenticatedWebsocketApiSupport": false, - "endpoints": { - "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" - }, - "credentials": { - "key": "Key", - "secret": "Secret" - }, - "credentialsValidator": { - "requiresKey": true, - "requiresSecret": true - } - }, - "features": { - "supports": { - "restAPI": true, - "restCapabilities": { - "tickerBatching": true, - "autoPairUpdates": true - }, - "websocketAPI": false, - "websocketCapabilities": {} + "name": "Huobi", + "enabled": true, + "verbose": false, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, + "websocketOrderbookBufferLimit": 5, + "baseCurrencies": "USD", + "currencyPairs": { + "requestFormat": { + "uppercase": false + }, + "configFormat": { + "uppercase": true, + "delimiter": "-" + }, + "useGlobalFormat": true, + "assetTypes": [ + "spot", + "coinmarginedfutures", + "futures" + ], + "pairs": { + "coinmarginedfutures": { + "assetEnabled": true, + "enabled": "BTC-USD", + "available": "BTC-USD,ETH-USD,LINK-USD,DOT-USD,ADA-USD,LTC-USD,XRP-USD,TRX-USD,DOGE-USD", + "requestFormat": { + "uppercase": true, + "delimiter": "-" + }, + "configFormat": { + "uppercase": true, + "delimiter": "-" + } + }, + "futures": { + "assetEnabled": true, + "enabled": "BTC-230915", + "available": "BTC-230915,BTC-230922,BTC-230929,ETH-230915,ETH-230922,ETH-230929,TRX-230915,TRX-230922", + "requestFormat": { + "uppercase": true + }, + "configFormat": { + "uppercase": true, + "delimiter": "-" + } + }, + "spot": { + "enabled": "BTC-USDT", + "available": "PROPY-ETH,IOTA-BTC,UGAS-ETH,PAI-USDT,BSV-HUSD,MTX-ETH,BCH-BTC,LTC-HT,SOC-USDT,WXT-BTC,SALT-BTC,RCN-ETH,PNT-ETH,TT-USDT,AIDOC-ETH,BIX-BTC,OCN-USDT,QTUM-ETH,KCASH-ETH,SNT-USDT,LUN-BTC,QASH-BTC,ITC-BTC,NAS-BTC,XMR-BTC,TNT-ETH,UC-ETH,FAIR-BTC,PC-ETH,YEE-BTC,PAY-ETH,XMX-BTC,CRE-USDT,BAT-ETH,BHT-USDT,CKB-HT,LAMB-HT,AE-USDT,QUN-ETH,LYM-BTC,BCH-HT,BHT-BTC,RUFF-ETH,CNN-BTC,FOR-USDT,GTC-ETH,TRX-ETH,ELA-USDT,ACT-ETH,SMT-ETH,BUT-ETH,BCH-USDT,ICX-BTC,MEET-BTC,NCC-BTC,APPC-BTC,GVE-ETH,TNB-BTC,STEEM-ETH,18C-ETH,LBA-BTC,EKO-BTC,REQ-BTC,SOC-BTC,BOX-ETH,ELF-BTC,ZRX-ETH,LET-USDT,HT-BTC,TUSD-HUSD,EGCC-BTC,WTC-BTC,ATP-USDT,DOCK-USDT,PAI-BTC,ONT-ETH,IRIS-BTC,BTT-ETH,SC-BTC,XZC-BTC,LBA-USDT,HT-USDT,VET-ETH,KMD-ETH,SHE-ETH,PORTAL-BTC,ONE-BTC,BIX-USDT,RCCC-BTC,SKM-USDT,XTZ-ETH,SWFTC-BTC,RSR-BTC,LINK-ETH,DATX-BTC,HPT-HT,GET-ETH,BLZ-ETH,CTXC-USDT,CNNS-USDT,PVT-HT,ITC-USDT,LTC-BTC,NCASH-BTC,HOT-ETH,ADA-USDT,ADX-BTC,NODE-USDT,TRIO-BTC,GXC-ETH,SNT-BTC,FOR-BTC,DBC-BTC,UUU-USDT,CVCOIN-ETH,RSR-USDT,CRO-USDT,OCN-BTC,NEW-USDT,EGT-USDT,MANA-BTC,CMT-USDT,WXT-HT,XRP-BTC,MT-ETH,PAX-HUSD,LSK-ETH,IOTA-USDT,SRN-ETH,ZIL-ETH,ELF-USDT,LXT-ETH,LAMB-BTC,CRE-HT,CKB-BTC,XVG-BTC,BSV-BTC,BFT-BTC,WPR-ETH,HT-HUSD,POWR-BTC,MANA-ETH,ENG-ETH,ZJLT-ETH,SNC-ETH,ATOM-ETH,WICC-USDT,KAN-ETH,DGD-BTC,VSYS-HT,BCD-BTC,BTM-ETH,DOGE-USDT,MEX-BTC,BTG-BTC,DAC-ETH,DAT-BTC,GRS-ETH,ADX-ETH,EM-HT,GXC-USDT,CVC-BTC,OMG-ETH,SSP-ETH,OGO-HT,CMT-ETH,POLY-ETH,XZC-USDT,THETA-USDT,XEM-USDT,LOL-USDT,BCH-HUSD,GSC-BTC,DOGE-ETH,MDS-BTC,BTS-ETH,CTXC-BTC,MCO-BTC,BCX-BTC,ZLA-ETH,EKT-USDT,MAN-BTC,BLZ-BTC,ATOM-USDT,LOL-BTC,HPT-USDT,EM-BTC,EOS-USDT,WAN-BTC,GNT-BTC,CRO-BTC,MANA-USDT,SEELE-USDT,FSN-BTC,VIDY-HT,USDC-HUSD,LTC-HUSD,XRP-USDT,VSYS-BTC,STORJ-BTC,LOOM-ETH,SKM-BTC,LINK-USDT,TT-HT,QSP-ETH,ETN-BTC,FSN-HT,NODE-BTC,HC-USDT,PHX-BTC,XLM-BTC,RCCC-ETH,LTC-USDT,UUU-BTC,SEELE-ETH,PVT-BTC,HC-ETH,REN-ETH,KAN-USDT,EOS-ETH,BSV-USDT,BTS-USDT,KMD-BTC,OGO-USDT,THETA-ETH,MUSK-BTC,CNNS-HT,ETC-BTC,COVA-BTC,BTT-TRX,XMR-USDT,MTN-ETH,QUN-BTC,NAS-USDT,ELA-ETH,HIT-ETH,BTT-USDT,EKT-ETH,TOS-BTC,GAS-ETH,DCR-USDT,ONT-BTC,NEW-HT,NEXO-BTC,ETH-USDT,WXT-USDT,FOR-HT,ADA-BTC,EVX-ETH,VET-BTC,ZEC-USDT,NANO-ETH,IOST-HT,BCV-ETH,REN-USDT,NULS-ETH,ACT-USDT,LET-ETH,BTM-USDT,MEET-ETH,AKRO-HT,ARDR-BTC,DCR-ETH,NANO-USDT,BTC-HUSD,ALGO-BTC,IIC-ETH,BHD-BTC,KNC-ETH,ATP-BTC,ZRX-BTC,ABT-BTC,18C-BTC,XMR-ETH,WAXP-BTC,CVNT-BTC,MX-USDT,OST-ETH,NKN-BTC,TOPC-BTC,GNX-BTC,FTT-USDT,ONE-HT,DGB-ETH,NULS-USDT,DASH-BTC,UIP-BTC,KCASH-HT,WICC-ETH,EKO-ETH,EGT-HT,IRIS-USDT,STK-ETH,MXC-BTC,NAS-ETH,OMG-USDT,SMT-BTC,BUT-BTC,HIT-USDT,BAT-BTC,IRIS-ETH,NKN-HT,PC-BTC,TOP-USDT,GTC-BTC,LSK-BTC,ITC-ETH,DTA-BTC,HOT-BTC,BTT-BTC,FAIR-ETH,DOCK-ETH,QTUM-BTC,ZEN-BTC,ZIL-BTC,RCN-BTC,FTI-BTC,BHD-USDT,VIDY-USDT,LUN-ETH,DBC-ETH,TOPC-ETH,IIC-BTC,STEEM-USDT,IOTA-ETH,KCASH-BTC,RUFF-BTC,APPC-ETH,MT-BTC,SOC-ETH,GT-HT,PROPY-BTC,AIDOC-BTC,ACT-BTC,LYM-ETH,CHAT-BTC,SWFTC-ETH,ETH-BTC,UIP-USDT,UGAS-BTC,XRP-HUSD,ALGO-USDT,TNT-BTC,ONT-USDT,YEE-ETH,AKRO-BTC,TRX-USDT,OCN-ETH,SRN-BTC,DASH-USDT,XMX-ETH,NANO-BTC,QASH-ETH,EOS-HT,GT-BTC,XTZ-USDT,ARPA-USDT,SALT-ETH,BKBT-ETH,MTX-BTC,SMT-USDT,GXC-BTC,VIDY-BTC,FTT-HT,LAMB-ETH,TRX-BTC,TRIO-ETH,BFT-ETH,LINK-BTC,AE-ETH,NULS-BTC,BHD-HT,AST-ETH,NEO-USDT,EDU-BTC,CVCOIN-BTC,GVE-BTC,GET-BTC,ZRX-USDT,ELF-ETH,DATX-ETH,ADA-ETH,TOP-HT,NCASH-ETH,QTUM-USDT,ETC-HT,ZIL-USDT,TNB-ETH,BIX-ETH,SHE-BTC,PNT-BTC,BTC-USDT,PORTAL-ETH,WAVES-USDT,XZC-ETH,HT-ETH,POLY-BTC,MCO-ETH,MUSK-ETH,PAI-ETH,LXT-USDT,UTK-BTC,RTE-BTC,NCC-ETH,HB10-USDT,BOX-BTC,RDN-ETH,ARPA-BTC,LBA-ETH,CNN-ETH,AAC-ETH,XTZ-BTC,IDT-BTC,AKRO-USDT,IOST-BTC,GT-USDT,WAN-ETH,ETN-ETH,PVT-USDT,NEO-BTC,WAVES-ETH,ONE-USDT,ZEC-BTC,SKM-HT,IOST-ETH,NPXS-ETH,CVC-ETH,CMT-BTC,COVA-ETH,ARDR-ETH,RDN-BTC,DCR-BTC,REN-BTC,YCC-ETH,MX-HT,NEXO-ETH,XLM-ETH,YCC-BTC,ENG-BTC,CNNS-BTC,ZLA-BTC,QSP-BTC,MAN-ETH,UUU-ETH,ETH-HUSD,RTE-ETH,ATP-HT,BTM-BTC,DAC-BTC,TOS-ETH,LAMB-USDT,DASH-HT,NPXS-BTC,NEW-BTC,FTT-BTC,EOS-HUSD,GRS-BTC,POWR-ETH,VET-USDT,AAC-BTC,MX-BTC,MTN-BTC,XVG-ETH,GNX-ETH,SSP-BTC,WAVES-BTC,EGT-BTC,CTXC-ETH,IDT-ETH,STK-BTC,WICC-BTC,UTK-ETH,CRO-HT,LXT-BTC,GSC-ETH,OMG-BTC,XRP-HT,DGB-BTC,IOST-USDT,CVNT-ETH,GAS-BTC,HIT-BTC,CKB-USDT,ARPA-HT,RUFF-USDT,HC-BTC,WTC-ETH,MDS-USDT,ABT-ETH,ALGO-ETH,BIFI-BTC,KNC-BTC,TT-BTC,LET-BTC,NKN-USDT,PAY-BTC,DTA-USDT,AE-BTC,UC-BTC,VSYS-USDT,USDT-HUSD,EOS-BTC,STEEM-BTC,DOGE-BTC,NODE-HT,MDS-ETH,CRE-BTC,GNT-USDT,UIP-ETH,AST-BTC,XEM-BTC,ZEN-ETH,EDU-ETH,MEX-ETH,EKT-BTC,CVC-USDT,WAXP-ETH,REQ-ETH,OST-BTC,STORJ-USDT,SBTC-BTC,DGD-ETH,SC-ETH,WTC-USDT,THETA-BTC,DTA-ETH,BCV-BTC,SNC-BTC,RSR-HT,KAN-BTC,ELA-BTC,ATOM-BTC,BKBT-BTC,FSN-USDT,EM-USDT,WPR-BTC,TOP-BTC,BTS-BTC,EGCC-ETH,MTL-BTC,GNT-ETH,SEELE-BTC,EVX-BTC,FTI-ETH,BAT-USDT,MT-HT,LOL-HT,ICX-ETH,LOOM-BTC,ZJLT-BTC,XLM-USDT,OGO-BTC,DOCK-BTC,CHAT-ETH,DAT-ETH,ETC-USDT,HPT-BTC,BHT-HT" + } + } + }, + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "tickerBatching": true, + "autoPairUpdates": true + }, + "websocketAPI": true, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": false + } + }, + "bankAccounts": [ + { + "enabled": false, + "bankName": "", + "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ] }, - "enabled": { - "autoPairUpdates": true, - "websocketAPI": false - } - }, - "bankAccounts": [ { - "enabled": false, - "bankName": "", - "bankAddress": "", - "bankPostalCode": "", - "bankPostalCity": "", - "bankCountry": "", - "accountName": "", - "accountNumber": "", - "swiftCode": "", - "iban": "", - "supportedCurrencies": "" - } - ] - }, - { - "name": "Okcoin", - "enabled": true, - "verbose": false, - "httpTimeout": 15000000000, - "websocketResponseCheckTimeout": 30000000, - "websocketResponseMaxLimit": 7000000000, - "websocketTrafficTimeout": 30000000000, - "websocketOrderbookBufferLimit": 5, - "baseCurrencies": "USD", - "currencyPairs": { - "requestFormat": { - "uppercase": true, - "delimiter": "-" - }, - "configFormat": { - "uppercase": true, - "delimiter": "-" - }, - "useGlobalFormat": true, - "assetTypes": [ - "spot" - ], - "pairs": { - "spot": { - "enabled": "BTC-USD", - "available": "BTC-USD,LTC-USD,ETH-USD,ETC-USD,TUSD-USD,BCH-USD,EOS-USD,XRP-USD,TRX-USD,BSV-USD,USDT-USD,USDK-USD,XLM-USD,ADA-USD,BAT-USD,DCR-USD,EURS-USD,HBAR-USD,PAX-USD,USDC-USD,ZEC-USD,BTC-USDT,BTC-SGD,ETH-SGD,BTC-EUR,BTC-EURS,ETH-EUR,BCH-EUR,EURS-EUR" - }, - "margin": { - "enabled": "BTC-USD", - "available": "BTC-USD" - } - } - }, - "api": { - "authenticatedSupport": false, - "authenticatedWebsocketApiSupport": false, - "endpoints": { - "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" - }, - "credentials": { - "key": "Key", - "secret": "Secret" - }, - "credentialsValidator": { - "requiresKey": true, - "requiresSecret": true, - "requiresClientID": true - } - }, - "features": { - "supports": { - "restAPI": true, - "restCapabilities": { - "tickerBatching": true, - "autoPairUpdates": true - }, - "websocketAPI": true, - "websocketCapabilities": {} + "name": "Kraken", + "enabled": true, + "verbose": false, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, + "websocketOrderbookBufferLimit": 5, + "baseCurrencies": "EUR,USD,CAD,GBP,JPY", + "currencyPairs": { + "requestFormat": { + "uppercase": true, + "separator": "," + }, + "configFormat": { + "uppercase": true, + "delimiter": "-", + "separator": "," + }, + "useGlobalFormat": true, + "assetTypes": [ + "spot" + ], + "pairs": { + "futures": { + "assetEnabled": true, + "enabled": "PF_XBTUSD", + "available": "PI_XBTUSD,PI_ETHUSD,PI_LTCUSD,PI_BCHUSD,PI_XRPUSD,PF_XBTUSD,PF_ETHUSD,PF_ADAUSD,PF_XRPUSD,PF_SOLUSD,PF_UNIUSD,PF_ATOMUSD,PF_BCHUSD,PF_DOTUSD,PF_EOSUSD,PF_FILUSD,PF_LINKUSD,PF_LTCUSD,PF_MATICUSD,PF_DEFIUSD,PF_AVAXUSD,PF_XMRUSD,PF_GMTUSD,PF_LUNA2USD,PF_OPUSD,PF_NEARUSD,PF_APEUSD,PF_WAVESUSD,PF_DOGEUSD,PF_FTMUSD,PF_TRXUSD,PF_MANAUSD,PF_CRVUSD,PF_AAVEUSD,PF_SNXUSD,PF_XTZUSD,PF_XLMUSD,PF_ALGOUSD,PF_SANDUSD,PF_OMGUSD,PF_ENJUSD,PF_COMPUSD,PF_YFIUSD,PF_CHZUSD,PF_LPTUSD,PF_BATUSD,PF_MKRUSD,PF_AXSUSD,PF_GALAUSD,PF_ETCUSD,PF_KAVAUSD,PF_LRCUSD,PF_KSMUSD,PF_GRTUSD,PF_FLOWUSD,PF_ZECUSD,PF_QTUMUSD,PF_DASHUSD,PF_1INCHUSD,PF_KNCUSD,PF_OGNUSD,PF_SUSHIUSD,PF_STORJUSD,PF_RUNEUSD,PF_EGLDUSD,PF_DYDXUSD,PF_RENUSD,PF_ANKRUSD,PF_ICPUSD,PF_ETHWUSD,PF_OCEANUSD,PF_BANDUSD,PF_BALUSD,PF_ALICEUSD,PF_ICXUSD,PF_ENSUSD,PF_AUDIOUSD,PF_ANTUSD,PF_SCUSD,PF_MINAUSD,PF_GLMRUSD,PF_THETAUSD,PF_QNTUSD,PF_IMXUSD,PF_APTUSD,PF_FLRUSD,PF_BLURUSD,PF_GMXUSD,PF_MASKUSD,PF_LDOUSD,PF_USDCUSD,PF_USDTUSD,PF_ARBUSD,PF_FETUSD,PF_STXUSD,PF_RNDRUSD,PF_CVXUSD,PF_WOOUSD,PF_JASMYUSD,PF_INJUSD,PF_ZRXUSD,PF_RLCUSD,PF_GALUSD,PF_SUIUSD,PF_PEPEUSD,FI_XBTUSD_231229,FI_ETHUSD_231229,PF_SHIBUSD,PF_TUSDUSD,PF_SXPUSD,PF_ARPAUSD,PF_ALPHAUSD,PF_STGUSD,PF_HFTUSD,PF_ACHUSD,PF_WLDUSD,PF_MOONUSD,PF_LINAUSD,PF_CFXUSD,PF_PAXGUSD,PF_AGLDUSD,FF_ETHUSD_231229,FF_XBTUSD_231229,FI_ETHUSD_240329,FI_XBTUSD_240329,FI_XRPUSD_231229,FI_BCHUSD_231229,FI_LTCUSD_231229,PF_FXSUSD,PF_SEIUSD,FF_XBTUSD_231027,FF_ETHUSD_231027,FI_XBTUSD_231027,FI_ETHUSD_231027,FI_XRPUSD_231027,FI_BCHUSD_231027,FI_LTCUSD_231027", + "requestFormat": { + "uppercase": true, + "delimiter": "_" + }, + "configFormat": { + "uppercase": true, + "delimiter": "_" + } + }, + "spot": { + "enabled": "XBT-USD", + "available": "ETH-GBP,XRP-USD,DAI-EUR,LSK-USD,BAT-EUR,BCH-EUR,EOS-ETH,GNO-EUR,ETH-CAD,XRP-JPY,ADA-ETH,DAI-USD,DASH-EUR,GNO-USD,LSK-XBT,ETH-EUR,ZEC-EUR,DASH-XBT,EOS-EUR,ETH-CHF,SC-ETH,SC-USD,WAVES-EUR,XBT-USD,ADA-EUR,LINK-USD,NANO-EUR,PAXG-USD,SC-EUR,WAVES-ETH,REP-USD,EOS-XBT,ETC-ETH,XMR-USD,LTC-USD,MLN-XBT,XTZ-CAD,XBT-GBP,ADA-CAD,XTZ-EUR,ETH-JPY,XTZ-USD,XDG-XBT,XLM-EUR,ATOM-USD,ATOM-XBT,OMG-EUR,ZEC-JPY,ADA-XBT,GNO-ETH,LINK-XBT,ETC-EUR,BCH-XBT,QTUM-ETH,XBT-CHF,LTC-EUR,ETH-DAI,LSK-EUR,NANO-USD,QTUM-XBT,XRP-XBT,ZEC-USD,BAT-ETH,LINK-ETH,XBT-CAD,BAT-USD,GNO-XBT,ICX-XBT,PAXG-ETH,DAI-USDT,NANO-ETH,OMG-ETH,WAVES-XBT,ZEC-XBT,BAT-XBT,NANO-XBT,XBT-JPY,DASH-USD,ICX-ETH,LSK-ETH,QTUM-CAD,REP-XBT,XMR-XBT,XRP-EUR,ATOM-CAD,OMG-USD,LTC-XBT,MLN-ETH,XTZ-ETH,EOS-USD,ICX-EUR,SC-XBT,ETC-USD,BCH-USD,ICX-USD,QTUM-USD,ETH-XBT,ETH-USD,OMG-XBT,PAXG-EUR,REP-EUR,ADA-USD,USDT-USD,XMR-EUR,XRP-CAD,ATOM-EUR,ETC-XBT,XBT-EUR,XLM-USD,ATOM-ETH,LINK-EUR,PAXG-XBT,WAVES-USD,REP-ETH,XLM-XBT,QTUM-EUR,XTZ-XBT" + } + } + }, + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true, + "requiresBase64DecodeSecret": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "tickerBatching": true, + "autoPairUpdates": true + }, + "websocketAPI": true, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": true + } + }, + "bankAccounts": [ + { + "enabled": false, + "bankName": "", + "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ] }, - "enabled": { - "autoPairUpdates": true, - "websocketAPI": true - } - }, - "bankAccounts": [ { - "enabled": false, - "bankName": "", - "bankAddress": "", - "bankPostalCode": "", - "bankPostalCity": "", - "bankCountry": "", - "accountName": "", - "accountNumber": "", - "swiftCode": "", - "iban": "", - "supportedCurrencies": "" - } - ] - }, - { - "name": "Okx", - "enabled": true, - "verbose": false, - "httpTimeout": 15000000000, - "websocketResponseCheckTimeout": 30000000, - "websocketResponseMaxLimit": 7000000000, - "websocketTrafficTimeout": 30000000000, - "baseCurrencies": "USD", - "currencyPairs": { - "bypassConfigFormatUpgrades": false, - "requestFormat": { - "uppercase": true, - "delimiter": "-" - }, - "configFormat": { - "uppercase": true, - "delimiter": "-" - }, - "useGlobalFormat": true, - "assetTypes": [ - "futures", - "margin", - "option", - "perpetualswap", - "spot" - ], - "pairs": { - "futures": { - "assetEnabled": true, - "enabled": "BTC-USD-221007,BTC-USD-221014", - "available": "BTC-USD-221230,BTC-USD-230331,ETH-USD-221007,ETH-USD-221014,ETH-USD-221230,ETH-USD-230331,LTC-USD-221007,LTC-USD-221014,LTC-USD-221230,LTC-USD-230331,DOT-USD-221007,DOT-USD-221014,DOT-USD-221230,DOT-USD-230331,FIL-USD-221007,FIL-USD-221014,FIL-USD-221230,FIL-USD-230331,ADA-USD-221007,ADA-USD-221014,ADA-USD-221230,ADA-USD-230331,AVAX-USD-221007,AVAX-USD-221014,AVAX-USD-221230,BTC-USD-221007,BTC-USD-221014,BTC-USD-221230,BTC-USD-230331,ETH-USD-221007,ETH-USD-221014,ETH-USD-221230,ETH-USD-230331,LTC-USD-221007,LTC-USD-221014,LTC-USD-221230,LTC-USD-230331,DOT-USD-221007,DOT-USD-221014,DOT-USD-221230,DOT-USD-230331,FIL-USD-221007,FIL-USD-221014,FIL-USD-221230,FIL-USD-230331,ADA-USD-221007,ADA-USD-221014,ADA-USD-221230,ADA-USD-230331,AVAX-USD-221007,AVAX-USD-221014,AVAX-USD-221230,AVAX-USD-230331,BCH-USD-221007,BCH-USD-221014,BCH-USD-221230,BCH-USD-230331,EOS-USD-221007,EOS-USD-221014,EOS-USD-221230,EOS-USD-230331,ETC-USD-221007,ETC-USD-221014,ETC-USD-221230,ETC-USD-230331,LINK-USD-221007,LINK-USD-221014,LINK-USD-221230,LINK-USD-230331,SOL-USD-221007,SOL-USD-221014,SOL-USD-221230,SOL-USD-230331,TRX-USD-221007,TRX-USD-221014,TRX-USD-221230,TRX-USD-230331,XRP-USD-221007,XRP-USD-221014,XRP-USD-221230,XRP-USD-230331,BTC-USDT-221007,BTC-USDT-221014,BTC-USDT-221230,BTC-USDT-230331,ETH-USDT-221007,ETH-USDT-221014,ETH-USDT-221230,ETH-USDT-230331,LTC-USDT-221007,LTC-USDT-221014,LTC-USDT-221230,LTC-USDT-230331,DOT-USDT-221007,DOT-USDT-221014,DOT-USDT-221230,DOT-USDT-230331,FIL-USDT-221007,FIL-USDT-221014,FIL-USDT-221230,FIL-USDT-230331,ADA-USDT-221007,ADA-USDT-221014,ADA-USDT-221230,ADA-USDT-230331,BCH-USDT-221007,BCH-USDT-221014,BCH-USDT-221230,BCH-USDT-230331,EOS-USDT-221007,EOS-USDT-221014,EOS-USDT-221230,EOS-USDT-230331,ETC-USDT-221007,ETC-USDT-221014,ETC-USDT-221230,ETC-USDT-230331,LINK-USDT-221007,LINK-USDT-221014,LINK-USDT-221230,LINK-USDT-230331,TRX-USDT-221007,TRX-USDT-221014,TRX-USDT-221230,TRX-USDT-230331,XRP-USDT-221007,XRP-USDT-221014,XRP-USDT-221230,XRP-USDT-230331" - }, - "margin": { - "assetEnabled": true, - "enabled": "BTC-USDT,ETH-USDT,OKB-USDT", - "available": "LTC-USDT,DOT-USDT,DOGE-USDT,LUNC-USDT,ETHW-USDT,LUNA-USDT,FIL-USDT,XRP-USDT,1INCH-USDT,AAVE-USDT,ADA-USDT,AGLD-USDT,AKITA-USDT,ALGO-USDT,ALPHA-USDT,BTC-USDT,ETH-USDT,OKB-USDT,LTC-USDT,DOT-USDT,DOGE-USDT,LUNC-USDT,ETHW-USDT,LUNA-USDT,FIL-USDT,XRP-USDT,1INCH-USDT,AAVE-USDT,ADA-USDT,AGLD-USDT,AKITA-USDT,ALGO-USDT,ALPHA-USDT,ANT-USDT,APE-USDT,API3-USDT,ASTR-USDT,ATOM-USDT,AVAX-USDT,AXS-USDT,BABYDOGE-USDT,BADGER-USDT,BAL-USDT,BAND-USDT,BAT-USDT,BCH-USDT,BICO-USDT,BNT-USDT,BSV-USDT,BTM-USDT,BTT-USDT,BZZ-USDT,CELO-USDT,CEL-USDT,CELR-USDT,CFX-USDT,CHZ-USDT,CLV-USDT,COMP-USDT,CONV-USDT,CQT-USDT,CRO-USDT,CRV-USDT,CSPR-USDT,CVC-USDT,DASH-USDT,DOME-USDT,DORA-USDT,DYDX-USDT,EFI-USDT,EGLD-USDT,ELF-USDT,ENJ-USDT,ENS-USDT,EOS-USDT,ETC-USDT,FITFI-USDT,FLM-USDT,FLOW-USDT,FTM-USDT,GALA-USDT,GLMR-USDT,GMT-USDT,GODS-USDT,GRT-USDT,HBAR-USDT,HC-USDT,ICP-USDT,IMX-USDT,IOST-USDT,IOTA-USDT,JST-USDT,KAR-USDT,KISHU-USDT,KNC-USDT,KSM-USDT,LAMB-USDT,LAT-USDT,LINK-USDT,LON-USDT,LOOKS-USDT,LPT-USDT,LRC-USDT,MANA-USDT,MASK-USDT,MATIC-USDT,MINA-USDT,MKR-USDT,NAS-USDT,NEAR-USDT,NEO-USDT,NFT-USDT,NYM-USDT,OMG-USDT,ONT-USDT,OP-USDT,PEOPLE-USDT,PERP-USDT,QTUM-USDT,REN-USDT,RSR-USDT,RSS3-USDT,RVN-USDT,SAND-USDT,SC-USDT,SHIB-USDT,SKL-USDT,SLP-USDT,SNT-USDT,SNX-USDT,SOL-USDT,SOS-USDT,SRM-USDT,STARL-USDT,STORJ-USDT,SUSHI-USDT,SWEAT-USDT,SWRV-USDT,THETA-USDT,TORN-USDT,TRB-USDT,TRX-USDT,UMA-USDT,UMEE-USDT,UNI-USDT,USDC-USDT,VSYS-USDT,WAVES-USDT,WNCG-USDT,WNXM-USDT,XCH-USDT,XEM-USDT,XLM-USDT,XMR-USDT,XTZ-USDT,YFI-USDT,YFII-USDT,YGG-USDT,ZEC-USDT,ZEN-USDT,ZIL-USDT,ZRX-USDT,BTC-USDC,ETH-BTC,LTC-USDC,DOT-USDC,DOGE-USDC,LUNC-USDC,LUNA-USDC,XRP-USDC,ADA-USDC,ATOM-USDC,AVAX-USDC,NEAR-USDC,OP-USDC,SOL-USDC,OKB-BTC,LTC-BTC,DOT-BTC,DOGE-BTC,FIL-BTC,XRP-BTC,AAVE-BTC,ADA-BTC,ALGO-BTC,ANT-BTC,ATOM-BTC,AVAX-BTC,BADGER-BTC,BAT-BTC,BCH-BTC,BNT-BTC,BSV-BTC,BTM-BTC,CHZ-BTC,COMP-BTC,CRO-BTC,CRV-BTC,CVC-BTC,DASH-BTC,EGLD-BTC,ELF-BTC,ENJ-BTC,EOS-BTC,ETC-BTC,GRT-BTC,HBAR-BTC,HC-BTC,ICP-BTC,IOST-BTC,IOTA-BTC,KNC-BTC,KSM-BTC,LINK-BTC,LRC-BTC,MANA-BTC,MKR-BTC,NAS-BTC,NEAR-BTC,NEO-BTC,OMG-BTC,ONT-BTC,QTUM-BTC,REN-BTC,RSR-BTC,RVN-BTC,SNT-BTC,SOL-BTC,SRM-BTC,THETA-BTC,TRX-BTC,UNI-BTC,VSYS-BTC,WAVES-BTC,XCH-BTC,XEM-BTC,XLM-BTC,XMR-BTC,XTZ-BTC,ZEC-BTC,ZIL-BTC,ZRX-BTC" - }, - "option": { - "assetEnabled": true, - "enabled": "BTC-USD-220930-28000-P,BTC-USD-220930-30000-C", - "available": "BTC-USD-220930-30000-P,BTC-USD-220930-32000-C,BTC-USD-220930-32000-P,BTC-USD-220930-34000-C,BTC-USD-220930-34000-P,BTC-USD-220930-35000-C,BTC-USD-220930-35000-P,BTC-USD-220930-36000-C,BTC-USD-220930-36000-P,BTC-USD-220930-40000-C,BTC-USD-220930-40000-P,BTC-USD-220930-45000-C,BTC-USD-220930-45000-P,BTC-USD-220930-50000-C,BTC-USD-220930-50000-P,BTC-USD-220930-55000-C,BTC-USD-220930-55000-P,BTC-USD-220930-60000-C,BTC-USD-220930-60000-P,BTC-USD-220930-65000-C,BTC-USD-220930-65000-P,BTC-USD-220930-70000-C,BTC-USD-220930-70000-P,BTC-USD-220930-80000-C,BTC-USD-220930-80000-P,BTC-USD-220930-90000-C,BTC-USD-220930-90000-P,BTC-USD-220930-100000-C,BTC-USD-220930-100000-P,BTC-USD-220930-120000-C,BTC-USD-220930-120000-P,BTC-USD-221007-13000-C,BTC-USD-221007-13000-P,BTC-USD-221007-14000-C,BTC-USD-221007-14000-P,BTC-USD-221007-15000-C,BTC-USD-221007-15000-P,BTC-USD-221007-16000-C,BTC-USD-221007-16000-P,BTC-USD-221007-17000-C,BTC-USD-221007-17000-P,BTC-USD-221007-18000-C,BTC-USD-221007-18000-P,BTC-USD-221007-18500-C,BTC-USD-221007-18500-P,BTC-USD-221007-19000-C,BTC-USD-221007-19000-P,BTC-USD-221007-20000-C,BTC-USD-221007-20000-P,BTC-USD-221007-21000-C,BTC-USD-221007-21000-P,BTC-USD-221007-22000-C,BTC-USD-221007-22000-P,BTC-USD-221007-23000-C,BTC-USD-221007-23000-P,BTC-USD-221007-24000-C,BTC-USD-221007-24000-P,BTC-USD-221007-26000-C,BTC-USD-221007-26000-P,BTC-USD-221007-28000-C,BTC-USD-221007-28000-P,BTC-USD-221007-30000-C,BTC-USD-221007-30000-P,BTC-USD-221014-14000-C,BTC-USD-221014-14000-P,BTC-USD-221014-15000-C,BTC-USD-221014-15000-P,BTC-USD-221014-16000-C,BTC-USD-221014-16000-P,BTC-USD-221014-17000-C,BTC-USD-221014-17000-P,BTC-USD-221014-18000-C,BTC-USD-221014-18000-P,BTC-USD-221014-19000-C,BTC-USD-221014-19000-P,BTC-USD-221014-20000-C,BTC-USD-221014-20000-P,BTC-USD-221014-21000-C,BTC-USD-221014-21000-P,BTC-USD-221014-22000-C,BTC-USD-221014-22000-P,BTC-USD-221014-23000-C,BTC-USD-221014-23000-P,BTC-USD-221014-24000-C,BTC-USD-221014-24000-P,BTC-USD-221014-26000-C,BTC-USD-221014-26000-P,BTC-USD-221014-28000-C,BTC-USD-221014-28000-P,BTC-USD-221014-30000-C,BTC-USD-221014-30000-P,BTC-USD-221028-10000-C,BTC-USD-221028-10000-P,BTC-USD-221028-12000-C,BTC-USD-221028-12000-P,BTC-USD-221028-14000-C,BTC-USD-221028-14000-P,BTC-USD-221028-16000-C,BTC-USD-221028-16000-P,BTC-USD-221028-17000-C,BTC-USD-221028-17000-P,BTC-USD-221028-18000-C,BTC-USD-221028-18000-P,BTC-USD-221028-19000-C,BTC-USD-221028-19000-P,BTC-USD-221028-20000-C,BTC-USD-221028-20000-P,BTC-USD-221028-21000-C,BTC-USD-221028-21000-P,BTC-USD-221028-22000-C,BTC-USD-221028-22000-P,BTC-USD-221028-24000-C,BTC-USD-221028-24000-P,BTC-USD-221028-26000-C,BTC-USD-221028-26000-P,BTC-USD-221028-28000-C,BTC-USD-221028-28000-P,BTC-USD-221028-30000-C,BTC-USD-221028-30000-P,BTC-USD-221028-32000-C,BTC-USD-221028-32000-P,BTC-USD-221028-35000-C,BTC-USD-220927-17000-C,BTC-USD-220927-17000-P,BTC-USD-220927-17500-C,BTC-USD-220927-17500-P,BTC-USD-220927-18000-C,BTC-USD-220927-18000-P,BTC-USD-220927-18200-C,BTC-USD-220927-18200-P,BTC-USD-220927-18400-C,BTC-USD-220927-18400-P,BTC-USD-220927-18600-C,BTC-USD-220927-18600-P,BTC-USD-220927-18800-C,BTC-USD-220927-18800-P,BTC-USD-220927-19000-C,BTC-USD-220927-19000-P,BTC-USD-220927-19200-C,BTC-USD-220927-19200-P,BTC-USD-220927-19400-C,BTC-USD-220927-19400-P,BTC-USD-220927-19600-C,BTC-USD-220927-19600-P,BTC-USD-220927-19800-C,BTC-USD-220927-19800-P,BTC-USD-220927-20000-C,BTC-USD-220927-20000-P,BTC-USD-220927-20500-C,BTC-USD-220927-20500-P,BTC-USD-220927-21000-C,BTC-USD-220927-21000-P,BTC-USD-220927-21500-C,BTC-USD-220927-21500-P,BTC-USD-220928-16500-C,BTC-USD-220928-16500-P,BTC-USD-220928-17000-C,BTC-USD-220928-17000-P,BTC-USD-220928-17500-C,BTC-USD-220928-17500-P,BTC-USD-220928-18000-C,BTC-USD-220928-18000-P,BTC-USD-220928-18200-C,BTC-USD-220928-18200-P,BTC-USD-220928-18400-C,BTC-USD-220928-18400-P,BTC-USD-220928-18600-C,BTC-USD-220928-18600-P,BTC-USD-220928-18800-C,BTC-USD-220928-18800-P,BTC-USD-220928-19000-C,BTC-USD-220928-19000-P,BTC-USD-220928-19200-C,BTC-USD-220928-19200-P,BTC-USD-220928-19400-C,BTC-USD-220928-19400-P,BTC-USD-220928-19600-C,BTC-USD-220928-19600-P,BTC-USD-220928-20000-C,BTC-USD-220928-20000-P,BTC-USD-220928-20500-C,BTC-USD-220928-20500-P,BTC-USD-220928-21000-C,BTC-USD-220928-21000-P,BTC-USD-220928-21500-C,BTC-USD-220928-21500-P,BTC-USD-220930-5000-C,BTC-USD-220930-5000-P,BTC-USD-220930-10000-C,BTC-USD-220930-10000-P,BTC-USD-220930-12000-C,BTC-USD-220930-12000-P,BTC-USD-220930-15000-C,BTC-USD-220930-15000-P,BTC-USD-220930-16000-C,BTC-USD-220930-16000-P,BTC-USD-220930-17000-C,BTC-USD-220930-17000-P,BTC-USD-220930-17500-C,BTC-USD-220930-17500-P,BTC-USD-220930-18000-C,BTC-USD-220930-18000-P,BTC-USD-220930-18500-C,BTC-USD-220930-18500-P,BTC-USD-220930-19000-C,BTC-USD-220930-19000-P,BTC-USD-220930-19500-C,BTC-USD-220930-19500-P,BTC-USD-220930-20000-C,BTC-USD-220930-20000-P,BTC-USD-220930-20500-C,BTC-USD-220930-20500-P,BTC-USD-220930-21000-C,BTC-USD-220930-21000-P,BTC-USD-220930-22000-C,BTC-USD-220930-22000-P,BTC-USD-220930-23000-C,BTC-USD-220930-23000-P,BTC-USD-220930-24000-C,BTC-USD-220930-24000-P,BTC-USD-220930-25000-C,BTC-USD-220930-25000-P,BTC-USD-220930-26000-C,BTC-USD-220930-26000-P,BTC-USD-220930-28000-C,BTC-USD-220930-28000-P,BTC-USD-220930-30000-C,BTC-USD-220930-30000-P,BTC-USD-220930-32000-C,BTC-USD-220930-32000-P,BTC-USD-220930-34000-C,BTC-USD-220930-34000-P,BTC-USD-220930-35000-C,BTC-USD-220930-35000-P,BTC-USD-220930-36000-C,BTC-USD-220930-36000-P,BTC-USD-220930-40000-C,BTC-USD-220930-40000-P,BTC-USD-220930-45000-C,BTC-USD-220930-45000-P,BTC-USD-220930-50000-C,BTC-USD-220930-50000-P,BTC-USD-220930-55000-C,BTC-USD-220930-55000-P,BTC-USD-220930-60000-C,BTC-USD-220930-60000-P,BTC-USD-220930-65000-C,BTC-USD-220930-65000-P,BTC-USD-220930-70000-C,BTC-USD-220930-70000-P,BTC-USD-220930-80000-C,BTC-USD-220930-80000-P,BTC-USD-220930-90000-C,BTC-USD-220930-90000-P,BTC-USD-220930-100000-C,BTC-USD-220930-100000-P,BTC-USD-220930-120000-C,BTC-USD-220930-120000-P,BTC-USD-221007-13000-C,BTC-USD-221007-13000-P,BTC-USD-221007-14000-C,BTC-USD-221007-14000-P,BTC-USD-221007-15000-C,BTC-USD-221007-15000-P,BTC-USD-221007-16000-C,BTC-USD-221007-16000-P,BTC-USD-221007-17000-C,BTC-USD-221007-17000-P,BTC-USD-221007-18000-C,BTC-USD-221007-18000-P,BTC-USD-221007-18500-C,BTC-USD-221007-18500-P,BTC-USD-221007-19000-C,BTC-USD-221007-19000-P,BTC-USD-221007-20000-C,BTC-USD-221007-20000-P,BTC-USD-221007-21000-C,BTC-USD-221007-21000-P,BTC-USD-221007-22000-C,BTC-USD-221007-22000-P,BTC-USD-221007-23000-C,BTC-USD-221007-23000-P,BTC-USD-221007-24000-C,BTC-USD-221007-24000-P,BTC-USD-221007-26000-C,BTC-USD-221007-26000-P,BTC-USD-221007-28000-C,BTC-USD-221007-28000-P,BTC-USD-221007-30000-C,BTC-USD-221007-30000-P,BTC-USD-221014-14000-C,BTC-USD-221014-14000-P,BTC-USD-221014-15000-C,BTC-USD-221014-15000-P,BTC-USD-221014-16000-C,BTC-USD-221014-16000-P,BTC-USD-221014-17000-C,BTC-USD-221014-17000-P,BTC-USD-221014-18000-C,BTC-USD-221014-18000-P,BTC-USD-221014-19000-C,BTC-USD-221014-19000-P,BTC-USD-221014-20000-C,BTC-USD-221014-20000-P,BTC-USD-221014-21000-C,BTC-USD-221014-21000-P,BTC-USD-221014-22000-C,BTC-USD-221014-22000-P,BTC-USD-221014-23000-C,BTC-USD-221014-23000-P,BTC-USD-221014-24000-C,BTC-USD-221014-24000-P,BTC-USD-221014-26000-C,BTC-USD-221014-26000-P,BTC-USD-221014-28000-C,BTC-USD-221014-28000-P,BTC-USD-221014-30000-C,BTC-USD-221014-30000-P,BTC-USD-221028-10000-C,BTC-USD-221028-10000-P,BTC-USD-221028-12000-C,BTC-USD-221028-12000-P,BTC-USD-221028-14000-C,BTC-USD-221028-14000-P,BTC-USD-221028-16000-C,BTC-USD-221028-16000-P,BTC-USD-221028-17000-C,BTC-USD-221028-17000-P,BTC-USD-221028-18000-C,BTC-USD-221028-18000-P,BTC-USD-221028-19000-C,BTC-USD-221028-19000-P,BTC-USD-221028-20000-C,BTC-USD-221028-20000-P,BTC-USD-221028-21000-C,BTC-USD-221028-21000-P,BTC-USD-221028-22000-C,BTC-USD-221028-22000-P,BTC-USD-221028-24000-C,BTC-USD-221028-24000-P,BTC-USD-221028-26000-C,BTC-USD-221028-26000-P,BTC-USD-221028-28000-C,BTC-USD-221028-28000-P,BTC-USD-221028-30000-C,BTC-USD-221028-30000-P,BTC-USD-221028-32000-C,BTC-USD-221028-32000-P,BTC-USD-221028-35000-C,BTC-USD-221028-35000-P,BTC-USD-221028-40000-C,BTC-USD-221028-40000-P,BTC-USD-221028-50000-C,BTC-USD-221028-50000-P,BTC-USD-221028-60000-C,BTC-USD-221028-60000-P,BTC-USD-221028-70000-C,BTC-USD-221028-70000-P,BTC-USD-221125-5000-C,BTC-USD-221125-5000-P,BTC-USD-221125-10000-C,BTC-USD-221125-10000-P,BTC-USD-221125-12000-C,BTC-USD-221125-12000-P,BTC-USD-221125-15000-C,BTC-USD-221125-15000-P,BTC-USD-221125-16000-C,BTC-USD-221125-16000-P,BTC-USD-221125-17000-C,BTC-USD-221125-17000-P,BTC-USD-221125-18000-C,BTC-USD-221125-18000-P,BTC-USD-221125-20000-C,BTC-USD-221125-20000-P,BTC-USD-221125-22000-C,BTC-USD-221125-22000-P,BTC-USD-221125-24000-C,BTC-USD-221125-24000-P,BTC-USD-221125-26000-C,BTC-USD-221125-26000-P,BTC-USD-221125-28000-C,BTC-USD-221125-28000-P,BTC-USD-221125-30000-C,BTC-USD-221125-30000-P,BTC-USD-221125-32000-C,BTC-USD-221125-32000-P,BTC-USD-221125-35000-C,BTC-USD-221125-35000-P,BTC-USD-221125-40000-C,BTC-USD-221125-40000-P,BTC-USD-221125-50000-C,BTC-USD-221125-50000-P,BTC-USD-221125-60000-C,BTC-USD-221125-60000-P,BTC-USD-221125-70000-C,BTC-USD-221125-70000-P,BTC-USD-221230-5000-C,BTC-USD-221230-5000-P,BTC-USD-221230-10000-C,BTC-USD-221230-10000-P,BTC-USD-221230-12000-C,BTC-USD-221230-12000-P,BTC-USD-221230-13000-C,BTC-USD-221230-13000-P,BTC-USD-221230-15000-C,BTC-USD-221230-15000-P,BTC-USD-221230-16000-C,BTC-USD-221230-16000-P,BTC-USD-221230-17000-C,BTC-USD-221230-17000-P,BTC-USD-221230-18000-C,BTC-USD-221230-18000-P,BTC-USD-221230-19000-C,BTC-USD-221230-19000-P,BTC-USD-221230-20000-C,BTC-USD-221230-20000-P,BTC-USD-221230-21000-C,BTC-USD-221230-21000-P,BTC-USD-221230-22000-C,BTC-USD-221230-22000-P,BTC-USD-221230-23000-C,BTC-USD-221230-23000-P,BTC-USD-221230-24000-C,BTC-USD-221230-24000-P,BTC-USD-221230-25000-C,BTC-USD-221230-25000-P,BTC-USD-221230-26000-C,BTC-USD-221230-26000-P,BTC-USD-221230-28000-C,BTC-USD-221230-28000-P,BTC-USD-221230-30000-C,BTC-USD-221230-30000-P,BTC-USD-221230-32000-C,BTC-USD-221230-32000-P,BTC-USD-221230-35000-C,BTC-USD-221230-35000-P" - }, - "perpetualswap": { - "assetEnabled": true, - "enabled": "BTC-USD-SWAP,ETH-USD-SWAP", - "available": "LTC-USD-SWAP,DOT-USD-SWAP,DOGE-USD-SWAP,MASK-USDT-SWAP,MATIC-USDT-SWAP,MINA-USDT-SWAP,MKR-USDT-SWAP,NEAR-USDT-SWAP,NEO-USDT-SWAP,NFT-USDT-SWAP,NYM-USDT-SWAP,OMG-USDT-SWAP,ONT-USDT-SWAP,OP-USDT-SWAP,PEOPLE-USDT-SWAP,BTC-USD-SWAP,ETH-USD-SWAP,LTC-USD-SWAP,DOT-USD-SWAP,DOGE-USD-SWAP,FIL-USD-SWAP,XRP-USD-SWAP,1INCH-USD-SWAP,ADA-USD-SWAP,ALGO-USD-SWAP,ATOM-USD-SWAP,AVAX-USD-SWAP,BCH-USD-SWAP,BSV-USD-SWAP,CRV-USD-SWAP,DASH-USD-SWAP,EOS-USD-SWAP,ETC-USD-SWAP,GRT-USD-SWAP,IOST-USD-SWAP,IOTA-USD-SWAP,KNC-USD-SWAP,KSM-USD-SWAP,LINK-USD-SWAP,MANA-USD-SWAP,NEO-USD-SWAP,ONT-USD-SWAP,QTUM-USD-SWAP,SAND-USD-SWAP,SOL-USD-SWAP,SUSHI-USD-SWAP,THETA-USD-SWAP,TRX-USD-SWAP,UNI-USD-SWAP,XLM-USD-SWAP,XMR-USD-SWAP,XTZ-USD-SWAP,YFI-USD-SWAP,YFII-USD-SWAP,ZEC-USD-SWAP,BTC-USDT-SWAP,ETH-USDT-SWAP,LTC-USDT-SWAP,DOT-USDT-SWAP,DOGE-USDT-SWAP,LUNC-USDT-SWAP,ETHW-USDT-SWAP,LUNA-USDT-SWAP,FIL-USDT-SWAP,XRP-USDT-SWAP,1INCH-USDT-SWAP,AAVE-USDT-SWAP,ADA-USDT-SWAP,AGLD-USDT-SWAP,ALGO-USDT-SWAP,ALPHA-USDT-SWAP,ANT-USDT-SWAP,APE-USDT-SWAP,API3-USDT-SWAP,ASTR-USDT-SWAP,ATOM-USDT-SWAP,AVAX-USDT-SWAP,AXS-USDT-SWAP,BABYDOGE-USDT-SWAP,BADGER-USDT-SWAP,BAL-USDT-SWAP,BAND-USDT-SWAP,BAT-USDT-SWAP,BCH-USDT-SWAP,BICO-USDT-SWAP,BNT-USDT-SWAP,BSV-USDT-SWAP,BTT-USDT-SWAP,CELO-USDT-SWAP,CEL-USDT-SWAP,CFX-USDT-SWAP,CHZ-USDT-SWAP,COMP-USDT-SWAP,CRO-USDT-SWAP,CRV-USDT-SWAP,CSPR-USDT-SWAP,CVC-USDT-SWAP,DASH-USDT-SWAP,DOME-USDT-SWAP,DORA-USDT-SWAP,DYDX-USDT-SWAP,EGLD-USDT-SWAP,ELON-USDT-SWAP,ENJ-USDT-SWAP,ENS-USDT-SWAP,EOS-USDT-SWAP,ETC-USDT-SWAP,FITFI-USDT-SWAP,FLM-USDT-SWAP,FTM-USDT-SWAP,GALA-USDT-SWAP,GMT-USDT-SWAP,GODS-USDT-SWAP,GRT-USDT-SWAP,ICP-USDT-SWAP,IMX-USDT-SWAP,IOST-USDT-SWAP,IOTA-USDT-SWAP,JST-USDT-SWAP,KISHU-USDT-SWAP,KNC-USDT-SWAP,KSM-USDT-SWAP,LINK-USDT-SWAP,LOOKS-USDT-SWAP,LPT-USDT-SWAP,LRC-USDT-SWAP,MANA-USDT-SWAP,MASK-USDT-SWAP,MATIC-USDT-SWAP,MINA-USDT-SWAP,MKR-USDT-SWAP,NEAR-USDT-SWAP,NEO-USDT-SWAP,NFT-USDT-SWAP,NYM-USDT-SWAP,OMG-USDT-SWAP,ONT-USDT-SWAP,OP-USDT-SWAP,PEOPLE-USDT-SWAP,PERP-USDT-SWAP,QTUM-USDT-SWAP,REN-USDT-SWAP,RSR-USDT-SWAP,RVN-USDT-SWAP,SAND-USDT-SWAP,SC-USDT-SWAP,SHIB-USDT-SWAP,SLP-USDT-SWAP,SNX-USDT-SWAP,SOL-USDT-SWAP,SOS-USDT-SWAP,SRM-USDT-SWAP,STARL-USDT-SWAP,STORJ-USDT-SWAP,SUSHI-USDT-SWAP,SWEAT-USDT-SWAP,THETA-USDT-SWAP,TRB-USDT-SWAP,TRX-USDT-SWAP,UMA-USDT-SWAP,UMEE-USDT-SWAP,UNI-USDT-SWAP,WAVES-USDT-SWAP,WNXM-USDT-SWAP,XCH-USDT-SWAP,XEM-USDT-SWAP,XLM-USDT-SWAP,XMR-USDT-SWAP,XTZ-USDT-SWAP,YFI-USDT-SWAP,YFII-USDT-SWAP,YGG-USDT-SWAP,ZEC-USDT-SWAP,ZEN-USDT-SWAP,ZIL-USDT-SWAP,ZRX-USDT-SWAP" - }, - "spot": { - "assetEnabled": true, - "enabled": "BTC-USDT,ETH-USDT,OKB-USDT", - "available": "OKT-USDT,LTC-USDT,DOT-USDT,DOGE-USDT,LUNC-USDT,ETHW-USDT,LUNA-USDT,FIL-USDT,XRP-USDT,CITY-USDT,MENGO-USDT,ARG-USDT,POR-USDT,1INCH-USDT,AAVE-USDT,ABT-USDT,ACA-USDT,ADA-USDT,AERGO-USDT,AGLD-USDT,XMR-ETH,YFI-ETH,ZEC-ETH,LTC-OKB,XRP-OKB,ETC-OKB,BTC-USDT,ETH-USDT,OKB-USDT,OKT-USDT,LTC-USDT,DOT-USDT,DOGE-USDT,LUNC-USDT,ETHW-USDT,LUNA-USDT,FIL-USDT,XRP-USDT,CITY-USDT,MENGO-USDT,ARG-USDT,POR-USDT,1INCH-USDT,AAVE-USDT,ABT-USDT,ACA-USDT,ADA-USDT,AERGO-USDT,AGLD-USDT,AKITA-USDT,ALCX-USDT,ALGO-USDT,ALPHA-USDT,ANC-USDT,ANT-USDT,ANW-USDT,APE-USDT,APIX-USDT,API3-USDT,APM-USDT,AR-USDT,ARK-USDT,AST-USDT,ASTR-USDT,ATOM-USDT,AUCTION-USDT,AVAX-USDT,AXS-USDT,AZY-USDT,BABYDOGE-USDT,BADGER-USDT,BAL-USDT,BAND-USDT,BAT-USDT,BCD-USDT,BCH-USDT,BETH-USDT,BHP-USDT,BICO-USDT,BLOK-USDT,BNT-USDT,BORING-USDT,BORA-USDT,BRWL-USDT,BSV-USDT,BTG-USDT,BTM-USDT,BTT-USDT,BZZ-USDT,CELO-USDT,CEL-USDT,CELR-USDT,CELT-USDT,CFG-USDT,CFX-USDT,CGS-USDT,CHAT-USDT,CHE-USDT,CHZ-USDT,CLV-USDT,CMT-USDT,CNTM-USDT,COMP-USDT,CONV-USDT,COVER-USDT,CQT-USDT,CRO-USDT,CRV-USDT,CSPR-USDT,CTC-USDT,CTXC-USDT,CVC-USDT,CVP-USDT,CVT-USDT,CVX-USDT,DAI-USDT,DAO-USDT,DASH-USDT,DCR-USDT,DEP-USDT,DEVT-USDT,DGB-USDT,DHT-USDT,DIA-USDT,DMD-USDT,DNA-USDT,DOME-USDT,DORA-USDT,DOSE-USDT,DYDX-USDT,EC-USDT,EDEN-USDT,EFI-USDT,EGLD-USDT,EGT-USDT,ELF-USDT,ELON-USDT,ELT-USDT,EM-USDT,ENJ-USDT,ENS-USDT,EOS-USDT,ERN-USDT,ETC-USDT,EURT-USDT,FAIR-USDT,FAME-USDT,FITFI-USDT,FLM-USDT,FLOW-USDT,FODL-USDT,FORTH-USDT,FRONT-USDT,FSN-USDT,FTM-USDT,GALA-USDT,GALFT-USDT,GARI-USDT,GAS-USDT,GF-USDT,GHST-USDT,GLM-USDT,GLMR-USDT,GM-USDT,GMT-USDT,GODS-USDT,GOG-USDT,GRT-USDT,GTO-USDT,GUSD-USDT,HBAR-USDT,HC-USDT,HDAO-USDT,HEGIC-USDT,HYC-USDT,ICP-USDT,ICX-USDT,ILV-USDT,IMX-USDT,INT-USDT,INX-USDT,IOST-USDT,IOTA-USDT,IQ-USDT,JFI-USDT,JOE-USDT,JST-USDT,KAN-USDT,KAR-USDT,KCASH-USDT,KDA-USDT,KINE-USDT,KISHU-USDT,KLAY-USDT,KNC-USDT,KOL-USDT,KONO-USDT,KP3R-USDT,KSM-USDT,LAMB-USDT,LAT-USDT,LBA-USDT,LDN-USDT,LDO-USDT,LEASH-USDT,LEO-USDT,LET-USDT,LINK-USDT,LING-USDT,LITH-USDT,LON-USDT,LOON-USDT,LOOKS-USDT,LPT-USDT,LRC-USDT,LSK-USDT,MAGIC-USDT,MANA-USDT,MASK-USDT,MATIC-USDT,MCO-USDT,MDA-USDT,MDT-USDT,MEME-USDT,METIS-USDT,MILO-USDT,MINA-USDT,MIR-USDT,MITH-USDT,MKR-USDT,MLN-USDT,MOF-USDT,MON-USDT,MOVR-USDT,MOVEZ-USDT,MXC-USDT,MXT-USDT,NAS-USDT,NEAR-USDT,NEO-USDT,NFT-USDT,NMR-USDT,NULS-USDT,NYM-USDT,OM-USDT,OMG-USDT,OMI-USDT,ONE-USDT,ONT-USDT,OP-USDT,ORBS-USDT,ORB-USDT,ORS-USDT,OXT-USDT,PAY-USDT,PCI-USDT,PEOPLE-USDT,PERP-USDT,PHA-USDT,PICKLE-USDT,PIT-USDT,PLG-USDT,PNK-USDT,POLS-USDT,POLYDOGE-USDT,PPT-USDT,PRQ-USDT,PST-USDT,PSTAKE-USDT,QOM-USDT,QTUM-USDT,RACA-USDT,RAY-USDT,REN-USDT,REP-USDT,REVV-USDT,RFUEL-USDT,RIO-USDT,RNT-USDT,ROAD-USDT,RON-USDT,RSR-USDT,RSS3-USDT,RVN-USDT,SAITAMA-USDT,SAMO-USDT,SAND-USDT,SC-USDT,SD-USDT,SFG-USDT,SHIB-USDT,SIS-USDT,SKEB-USDT,SKL-USDT,SLP-USDT,SNT-USDT,SNX-USDT,SOC-USDT,SOL-USDT,SOS-USDT,SPELL-USDT,SRM-USDT,STARL-USDT,STC-USDT,STORJ-USDT,STRK-USDT,STX-USDT,SUN-USDT,SUSHI-USDT,SWEAT-USDT,SWFTC-USDT,SWRV-USDT,T-USDT,TAI-USDT,TAKI-USDT,TCT-USDT,THETA-USDT,THG-USDT,TON-USDT,TOPC-USDT,TORN-USDT,TOWN-USDT,TRADE-USDT,TRA-USDT,TRB-USDT,TRUE-USDT,TRX-USDT,TUP-USDT,TUSD-USDT,UMA-USDT,UMEE-USDT,UNI-USDT,USDC-USDT,USDP-USDT,USTC-USDT,UTK-USDT,VALUE-USDT,VELO-USDT,VRA-USDT,VSYS-USDT,WAVES-USDT,WAXP-USDT,WBTC-USDT,WEMIX-USDT,WGRT-USDT,WING-USDT,WIN-USDT,WNCG-USDT,WNXM-USDT,WOO-USDT,WSB-USDT,WXT-USDT,XAUT-USDT,XCH-USDT,XEC-USDT,XEM-USDT,XETA-USDT,XLM-USDT,XMR-USDT,XNO-USDT,XPR-USDT,XTZ-USDT,YEE-USDT,YFI-USDT,YFII-USDT,YGG-USDT,YOU-USDT,YOYO-USDT,ZBC-USDT,ZEC-USDT,ZEN-USDT,ZIL-USDT,ZKS-USDT,ZRX-USDT,ZYRO-USDT,BTC-USDC,ETH-USDC,ETH-BTC,OKB-USDC,OKT-USDC,LTC-USDC,DOT-USDC,DOGE-USDC,LUNC-USDC,ETHW-USDC,LUNA-USDC,FIL-USDC,XRP-USDC,1INCH-USDC,AAVE-USDC,ADA-USDC,AGLD-USDC,ALGO-USDC,ANC-USDC,ANT-USDC,APE-USDC,API3-USDC,AR-USDC,ASTR-USDC,ATOM-USDC,AVAX-USDC,AXS-USDC,AZY-USDC,BABYDOGE-USDC,BAT-USDC,BCH-USDC,BICO-USDC,BSV-USDC,CEL-USDC,CELO-USDC,CELT-USDC,CHZ-USDC,COMP-USDC,CRO-USDC,CRV-USDC,CSPR-USDC,DASH-USDC,DEP-USDC,DOME-USDC,DYDX-USDC,EGLD-USDC,ELT-USDC,ENS-USDC,EOS-USDC,ETC-USDC,FITFI-USDC,FLM-USDC,FLOW-USDC,FTM-USDC,GALA-USDC,GALFT-USDC,GARI-USDC,GLMR-USDC,GMT-USDC,GODS-USDC,GRT-USDC,HBAR-USDC,ICP-USDC,IMX-USDC,IOST-USDC,JST-USDC,KISHU-USDC,KLAY-USDC,KNC-USDC,KSM-USDC,LINK-USDC,LOOKS-USDC,LRC-USDC,MANA-USDC,MASK-USDC,MATIC-USDC,MINA-USDC,MKR-USDC,MOF-USDC,MOVEZ-USDC,MXC-USDC,NEAR-USDC,NFT-USDC,NMR-USDC,NYM-USDC,OMG-USDC,OP-USDC,PEOPLE-USDC,PERP-USDC,RACA-USDC,RSR-USDC,SAITAMA-USDC,SAND-USDC,SHIB-USDC,SLP-USDC,SNX-USDC,SOC-USDC,SOL-USDC,SOS-USDC,SRM-USDC,STARL-USDC,STC-USDC,STORJ-USDC,STX-USDC,SUSHI-USDC,SWFTC-USDC,THETA-USDC,TON-USDC,TORN-USDC,TRB-USDC,TRX-USDC,UNI-USDC,USDP-USDC,USTC-USDC,VRA-USDC,WAVES-USDC,XCH-USDC,XEM-USDC,XLM-USDC,XMR-USDC,XNO-USDC,XTZ-USDC,YFI-USDC,YFII-USDC,YGG-USDC,ZEC-USDC,ZIL-USDC,BTC-DAI,ETH-DAI,BTC-USDK,ETH-USDK,USDT-USDK,OKB-BTC,OKT-BTC,LTC-BTC,DOT-BTC,DOGE-BTC,FIL-BTC,XRP-BTC,AAVE-BTC,ADA-BTC,ALGO-BTC,ALPHA-BTC,ANT-BTC,ATOM-BTC,AVAX-BTC,BADGER-BTC,BAT-BTC,BCD-BTC,BCH-BTC,BNT-BTC,BSV-BTC,BTG-BTC,BTM-BTC,CELO-BTC,CELT-BTC,CHZ-BTC,COMP-BTC,CQT-BTC,CRO-BTC,CRV-BTC,CTC-BTC,CVC-BTC,DASH-BTC,DCR-BTC,DGB-BTC,EGLD-BTC,ELF-BTC,ENJ-BTC,EOS-BTC,ETC-BTC,FLOW-BTC,GAS-BTC,GRT-BTC,GTO-BTC,HBAR-BTC,HC-BTC,ICP-BTC,ICX-BTC,INT-BTC,IOST-BTC,IOTA-BTC,KLAY-BTC,KNC-BTC,KSM-BTC,LINK-BTC,LRC-BTC,LSK-BTC,MANA-BTC,MITH-BTC,MKR-BTC,NAS-BTC,NEAR-BTC,NEO-BTC,NULS-BTC,OMG-BTC,ONT-BTC,PST-BTC,QTUM-BTC,REN-BTC,RSR-BTC,RVN-BTC,SC-BTC,SNT-BTC,SOL-BTC,SRM-BTC,STX-BTC,SWFTC-BTC,THETA-BTC,TRUE-BTC,TRX-BTC,UNI-BTC,VSYS-BTC,WAVES-BTC,WBTC-BTC,WXT-BTC,XCH-BTC,XEM-BTC,XLM-BTC,XMR-BTC,XTZ-BTC,YFI-BTC,YOU-BTC,ZEC-BTC,ZEN-BTC,ZIL-BTC,ZRX-BTC,OKB-ETH,OKT-ETH,LTC-ETH,DOT-ETH,DOGE-ETH,FIL-ETH,XRP-ETH,AAVE-ETH,ADA-ETH,API3-ETH,ATOM-ETH,AVAX-ETH,BETH-ETH,CRV-ETH,DASH-ETH,EOS-ETH,ETC-ETH,FLOW-ETH,GAS-ETH,GHST-ETH,HEGIC-ETH,INT-ETH,IOST-ETH,KSM-ETH,LINK-ETH,MANA-ETH,MKR-ETH,NEAR-ETH,NEO-ETH,NULS-ETH,OM-ETH,QTUM-ETH,SNX-ETH,SOL-ETH,SUSHI-ETH,SWFTC-ETH,TRX-ETH,UNI-ETH,WBTC-ETH,XLM-ETH,XMR-ETH,YFI-ETH,ZEC-ETH,LTC-OKB,XRP-OKB,ETC-OKB,OKDOT1-DOT,OKDOT2-DOT,BTC-EURT,ETH-EURT" - } - } - }, - "api": { - "authenticatedSupport": false, - "authenticatedWebsocketApiSupport": false, - "credentials": { - "key": "", - "secret": "", - "clientID": "" - }, - "credentialsValidator": { - "requiresKey": true, - "requiresSecret": true, - "requiresClientID": true - }, - "urlEndpoints": { - "RestSpotURL": "https://www.okx.com/api/v5/", - "WebsocketSpotURL": "wss://ws.okx.com:8443/ws/v5/public" - } - }, - "features": { - "supports": { - "restAPI": true, - "restCapabilities": { - "autoPairUpdates": true - }, - "websocketAPI": true, - "websocketCapabilities": {} + "name": "Kucoin", + "enabled": true, + "verbose": false, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, + "websocketOrderbookBufferLimit": 5, + "baseCurrencies": "USD", + "currencyPairs": { + "assetTypes": [ + "spot", + "margin", + "futures" + ], + "pairs": { + "spot": { + "assetEnabled": true, + "enabled": "BTC-USDT,ETH-BTC,ETH-USDT,LTC-USDT", + "available": "BTC-USDT,MHC-ETH,MHC-BTC,OXEN-BTC,OXEN-ETH,NRG-BTC,AVA-USDT,FET-BTC,FET-ETH,ANKR-BTC,MHC-USDT,XMR-BTC,XMR-ETH,RIF-BTC,MTV-BTC,MTV-ETH,CRO-BTC,MTV-USDT,KMD-BTC,KMD-USDT,RFOX-USDT,TEL-USDT,TT-USDT,AERGO-USDT,XMR-USDT,TRX-KCS,ATOM-BTC,ATOM-ETH,ATOM-USDT,ATOM-KCS,ETN-USDT,FTM-USDT,TOMO-USDT,VSYS-USDT,OCEAN-BTC,OCEAN-ETH,CHR-BTC,CHR-USDT,FX-BTC,FX-ETH,NIM-BTC,NIM-ETH,COTI-BTC,COTI-USDT,NRG-ETH,BNB-BTC,BNB-USDT,JAR-BTC,JAR-USDT,ALGO-BTC,ALGO-ETH,ALGO-USDT,XEM-BTC,XEM-USDT,CIX100-USDT,XTZ-BTC,XTZ-USDT,ZEC-BTC,ZEC-USDT,ADA-BTC,ADA-USDT,REV-USDT,WXT-BTC,WXT-USDT,FORESTPLUS-BTC,FORESTPLUS-USDT,BOLT-BTC,BOLT-USDT,ARPA-USDT,CHZ-BTC,CHZ-USDT,DAPPT-BTC,DAPPT-USDT,NOIA-BTC,NOIA-USDT,WIN-BTC,WIN-USDT,DERO-BTC,DERO-USDT,BTT-USDT,EOSC-USDT,ENQ-BTC,ENQ-USDT,ONE-BTC,ONE-USDT,TOKO-BTC,TOKO-USDT,VID-BTC,VID-USDT,LUNA-USDT,SXP-BTC,SXP-USDT,AKRO-BTC,AKRO-USDT,ROOBEE-BTC,WIN-TRX,MAP-BTC,MAP-USDT,AMPL-BTC,AMPL-USDT,DAG-USDT,POL-USDT,ARX-USDT,NWC-BTC,NWC-USDT,BEPRO-BTC,BEPRO-USDT,VRA-BTC,VRA-USDT,KSM-BTC,KSM-USDT,DASH-USDT,SUTER-USDT,ACOIN-USDT,SUTER-BTC,SENSO-USDT,PRE-BTC,XDB-USDT,SYLO-USDT,WOM-USDT,SENSO-BTC,DGB-USDT,LYXE-USDT,LYXE-ETH,XDB-BTC,STX-BTC,STX-USDT,XSR-USDT,COMP-USDT,CRO-USDT,KAI-USDT,KAI-BTC,WEST-BTC,WEST-USDT,EWT-BTC,WAVES-USDT,WAVES-BTC,ORN-USDT,AMPL-ETH,BNS-USDT,MKR-USDT,SUKU-BTC,MLK-BTC,MLK-USDT,JST-USDT,KAI-ETH,SUKU-USDT,DIA-USDT,DIA-BTC,LINK-BTC,LINK-USDT,DOT-USDT,DOT-BTC,SHA-BTC,SHA-USDT,EWT-USDT,USDJ-USDT,EFX-BTC,CKB-BTC,CKB-USDT,UMA-USDT,ALEPH-USDT,VELO-USDT,SUN-USDT,BUY-USDT,YFI-USDT,OXEN-USDT,UNI-USDT,UOS-USDT,UOS-BTC,NIM-USDT,DEGO-USDT,DEGO-ETH,UDOO-ETH,RFUEL-USDT,FIL-USDT,UBX-ETH,REAP-USDT,AAVE-USDT,AAVE-BTC,TONE-BTC,TONE-ETH,ELF-ETH,AERGO-BTC,IOST-ETH,KCS-USDT,SNX-ETH,TOMO-ETH,KCS-ETH,DRGN-BTC,WAN-ETH,NULS-ETH,AXPR-ETH,POWR-BTC,QTUM-BTC,MANA-BTC,TEL-BTC,XYO-ETH,AXPR-BTC,ETN-BTC,COV-ETH,VET-BTC,KCS-BTC,CAPP-ETH,ONT-BTC,DRGN-ETH,DAG-ETH,TOMO-BTC,WAN-BTC,KNC-ETH,CRPT-ETH,LTC-USDT,BAX-ETH,BSV-USDT,DENT-ETH,AION-ETH,LYM-ETH,TRAC-ETH,ENJ-BTC,WAXP-BTC,DGB-BTC,ELA-BTC,ZIL-BTC,BSV-BTC,XLM-USDT,IOTX-ETH,SOUL-BTC,DOCK-BTC,AMB-ETH,TRX-BTC,XRP-TUSD,NULS-BTC,ETH-DAI,LSK-BTC,GMB-ETH,GMB-BTC,NEO-ETH,OMG-ETH,BTC-TUSD,KAT-USDT,KNC-BTC,ELF-BTC,MANA-ETH,ETC-USDT,ONT-ETH,MKR-BTC,KAT-BTC,XRP-USDC,XYO-BTC,SNT-ETH,ZRX-BTC,LOOM-ETH,AION-BTC,POWR-ETH,OLT-ETH,OLT-BTC,SNT-BTC,TRAC-BTC,XLM-ETH,ETH-USDT,BSV-ETH,TRX-ETH,ETN-ETH,AOA-USDT,BCD-BTC,DENT-BTC,DOCK-ETH,KEY-BTC,EOS-KCS,XLM-BTC,ADB-ETH,TIME-ETH,CVC-BTC,LSK-ETH,QKC-BTC,AMB-BTC,USDT-TUSD,ETC-ETH,XRP-BTC,NEO-KCS,SNX-USDT,CRPT-BTC,IOTX-BTC,LTC-ETH,XRP-KCS,ADB-BTC,LTC-KCS,TEL-ETH,DCR-ETH,LYM-USDT,USDT-USDC,ETH-USDC,DAG-BTC,AVA-BTC,BTC-USDT,WAXP-ETH,XRP-USDT,KEY-ETH,VET-ETH,FTM-BTC,USDT-DAI,QKC-ETH,ETH-BTC,MAN-BTC,CPC-ETH,TRX-USDT,BTC-DAI,ONT-USDT,DASH-ETH,BAX-BTC,AVA-ETH,LOOM-BTC,MVP-BTC,MKR-ETH,COV-BTC,CPC-BTC,REQ-ETH,EOS-BTC,LTC-BTC,XRP-ETH,CAPP-BTC,FTM-ETH,BCD-ETH,ZRX-ETH,DGB-ETH,VET-USDT,REQ-BTC,UTK-BTC,PLAY-BTC,UTK-ETH,SNX-BTC,MVP-ETH,NEO-BTC,SOUL-ETH,NEO-USDT,ELA-ETH,OMG-BTC,TIME-BTC,AOA-BTC,ETC-BTC,DCR-BTC,BTC-USDC,ENJ-ETH,IOST-BTC,DASH-BTC,EOS-USDT,EOS-ETH,ZIL-ETH,ETH-TUSD,GAS-BTC,LYM-BTC,BCH-BTC,VSYS-BTC,BCH-USDT,MKR-DAI,SOLVE-BTC,GRIN-BTC,GRIN-USDT,UQC-BTC,UQC-ETH,OPCT-BTC,OPCT-ETH,PRE-USDT,SHR-BTC,SHR-USDT,UBXT-USDT,ROSE-USDT,USDC-USDT,CTI-USDT,CTI-ETH,ETH2-ETH,BUX-BTC,XHV-USDT,PLU-USDT,GRT-USDT,CAS-BTC,CAS-USDT,MSWAP-BTC,MSWAP-USDT,GOM2-BTC,GOM2-USDT,REVV-BTC,REVV-USDT,LON-USDT,1INCH-USDT,LOC-USDT,API3-USDT,UNFI-USDT,HTR-USDT,FRONT-USDT,FRONT-BTC,WBTC-BTC,WBTC-ETH,MIR-USDT,LTC-USDC,BCH-USDC,HYDRA-USDT,DFI-USDT,DFI-BTC,CRV-USDT,SUSHI-USDT,FRM-USDT,EOS-USDC,BSV-USDC,ZEN-USDT,CUDOS-USDT,ADA-USDC,REN-USDT,LRC-USDT,LINK-USDC,KLV-USDT,KLV-BTC,BOA-USDT,THETA-USDT,QNT-USDT,BAT-USDT,DOGE-USDT,DOGE-USDC,DAO-USDT,STRONG-USDT,TRIAS-USDT,TRIAS-BTC,DOGE-BTC,MITX-BTC,MITX-USDT,CAKE-USDT,ORAI-USDT,ZEE-USDT,LTX-USDT,LTX-BTC,MASK-USDT,KLV-TRX,IDEA-USDT,PHA-USDT,PHA-ETH,BCH-KCS,SRK-USDT,SRK-BTC,ADA-KCS,HTR-BTC,BSV-KCS,DOT-KCS,LINK-KCS,MIR-KCS,BNB-KCS,XLM-KCS,VET-KCS,SWINGBY-USDT,SWINGBY-BTC,XHV-BTC,DASH-KCS,UNI-KCS,AAVE-KCS,DOGE-KCS,ZEC-KCS,XTZ-KCS,GRT-KCS,ALGO-KCS,EWT-KCS,GAS-USDT,AVAX-USDT,AVAX-BTC,KRL-BTC,KRL-USDT,POLK-USDT,POLK-BTC,ENJ-USDT,MANA-USDT,RNDR-USDT,RNDR-BTC,RLY-USDT,ANC-USDT,SKEY-USDT,LAYER-USDT,TARA-USDT,TARA-ETH,IOST-USDT,DYP-USDT,DYP-ETH,XYM-USDT,XYM-BTC,PCX-USDT,PCX-BTC,ORBS-USDT,ORBS-BTC,BTC3L-USDT,BTC3S-USDT,ETH3L-USDT,ETH3S-USDT,ANKR-USDT,DSLA-USDT,DSLA-BTC,SAND-USDT,VAI-USDT,XCUR-USDT,XCUR-BTC,FLUX-USDT,OMG-USDT,ZIL-USDT,DODO-USDT,MAN-USDT,BAX-USDT,BOSON-USDT,BOSON-ETH,PUNDIX-USDT,PUNDIX-BTC,WAXP-USDT,HT-USDT,PDEX-USDT,LABS-USDT,LABS-ETH,GMB-USDT,PHNX-USDT,PHNX-BTC,HAI-USDT,EQZ-USDT,FORTH-USDT,HORD-USDT,CGG-USDT,UBX-USDT,GHX-USDT,TCP-USDT,STND-USDT,STND-ETH,TOWER-USDT,TOWER-BTC,ACE-USDT,LOCG-USDT,CARD-USDT,FLY-USDT,CWS-USDT,XDC-USDT,XDC-ETH,STRK-BTC,STRK-ETH,SHIB-USDT,POLX-USDT,KDA-USDT,KDA-BTC,ICP-USDT,ICP-BTC,STC-USDT,STC-BTC,GOVI-USDT,GOVI-BTC,FKX-USDT,CELO-USDT,CELO-BTC,CUSD-USDT,CUSD-BTC,FCL-USDT,MATIC-USDT,MATIC-BTC,ELA-USDT,CRPT-USDT,OPCT-USDT,OGN-USDT,OGN-BTC,OUSD-USDT,OUSD-BTC,TLOS-USDT,TLOS-BTC,YOP-USDT,YOP-ETH,GLQ-USDT,GLQ-BTC,MXC-USDT,ERSDL-USDT,HOTCROSS-USDT,ADA3L-USDT,ADA3S-USDT,HYVE-USDT,HYVE-BTC,DAPPX-USDT,KONO-USDT,PRQ-USDT,MAHA-USDT,MAHA-BTC,FEAR-USDT,PYR-USDT,PYR-BTC,PROM-USDT,PROM-BTC,GLCH-USDT,UNO-USDT,ALBT-USDT,ALBT-ETH,XCAD-USDT,EOS3L-USDT,EOS3S-USDT,BCH3L-USDT,BCH3S-USDT,ELON-USDT,APL-USDT,FCL-ETH,VEED-USDT,VEED-BTC,DIVI-USDT,PDEX-BTC,JUP-USDT,JUP-ETH,POLS-USDT,POLS-BTC,LPOOL-USDT,LPOOL-BTC,LSS-USDT,VET3L-USDT,VET3S-USDT,LTC3L-USDT,LTC3S-USDT,ABBC-USDT,ABBC-BTC,KOK-USDT,ROSN-USDT,DORA-USDT,DORA-BTC,ZCX-USDT,ZCX-BTC,NORD-USDT,GMEE-USDT,SFUND-USDT,XAVA-USDT,AI-USDT,ALPACA-USDT,IOI-USDT,NFT-USDT,NFT-TRX,MNST-USDT,MEM-USDT,AGIX-USDT,AGIX-BTC,AGIX-ETH,CQT-USDT,AIOZ-USDT,MARSH-USDT,HAPI-USDT,MODEFI-USDT,MODEFI-BTC,YFDAI-USDT,YFDAI-BTC,GENS-USDT,FORM-USDT,ARRR-USDT,ARRR-BTC,TOKO-KCS,EXRD-USDT,NGM-USDT,LPT-USDT,STMX-USDT,ASD-USDT,BOND-USDT,HAI-BTC,SOUL-USDT,2CRZ-USDT,NEAR-USDT,NEAR-BTC,DFYN-USDT,OOE-USDT,CFG-USDT,CFG-BTC,AXS-USDT,CLV-USDT,ROUTE-USDT,KAR-USDT,EFX-USDT,XDC-BTC,SHFT-USDT,PMON-USDT,DPET-USDT,ERG-USDT,ERG-BTC,SOL-USDT,SLP-USDT,LITH-USDT,LITH-ETH,XCH-USDT,HAKA-USDT,LAYER-BTC,MTL-USDT,MTL-BTC,IOTX-USDT,GALA-USDT,REQ-USDT,TXA-USDT,TXA-USDC,CIRUS-USDT,QI-USDT,QI-BTC,ODDZ-USDT,PNT-USDT,PNT-BTC,XPR-USDT,XPR-BTC,TRIBE-USDT,SHFT-BTC,MOVR-USDT,MOVR-ETH,WOO-USDT,WILD-USDT,QRDO-USDT,QRDO-ETH,SDN-USDT,SDN-ETH,MAKI-USDT,MAKI-BTC,REP-USDT,REP-BTC,REP-ETH,BNT-USDT,BNT-BTC,BNT-ETH,OXT-USDT,OXT-BTC,OXT-ETH,BAL-USDT,BAL-BTC,BAL-ETH,STORJ-USDT,STORJ-BTC,STORJ-ETH,YGG-USDT,NDAU-USDT,SDAO-USDT,SDAO-ETH,XRP3L-USDT,XRP3S-USDT,SKL-USDT,SKL-BTC,NMR-USDT,NMR-BTC,IXS-USDT,TRB-USDT,TRB-BTC,DYDX-USDT,XYO-USDT,GTC-USDT,GTC-BTC,EQX-USDT,EQX-BTC,RLC-USDT,RLC-BTC,XPRT-USDT,EGLD-USDT,EGLD-BTC,HBAR-USDT,HBAR-BTC,DOGE3L-USDT,DOGE3S-USDT,FLOW-USDT,FLOW-BTC,NKN-USDT,NKN-BTC,PBX-USDT,SOL3L-USDT,SOL3S-USDT,MLN-USDT,MLN-BTC,XNL-USDT,SOLVE-USDT,WNCG-USDT,WNCG-BTC,DMTR-USDT,LINK3L-USDT,LINK3S-USDT,DOT3L-USDT,DOT3S-USDT,CTSI-USDT,CTSI-BTC,ALICE-USDT,ALICE-BTC,ALICE-ETH,OPUL-USDT,ILV-USDT,BAND-USDT,BAND-BTC,FTT-USDT,FTT-BTC,DVPN-USDT,SKU-USDT,SKU-BTC,EDG-USDT,SLIM-USDT,TLM-USDT,TLM-BTC,TLM-ETH,DEXE-USDT,DEXE-BTC,DEXE-ETH,MATTER-USDT,CUDOS-BTC,RUNE-USDT,RUNE-BTC,RMRK-USDT,BMON-USDT,C98-USDT,BLOK-USDT,SOLR-USDT,ATOM3L-USDT,ATOM3S-USDT,UNI3L-USDT,UNI3S-USDT,WSIENNA-USDT,PUSH-USDT,PUSH-BTC,FORM-ETH,NTVRK-USDT,NTVRK-USDC,AXS3L-USDT,AXS3S-USDT,FTM3L-USDT,FTM3S-USDT,FLAME-USDT,AGLD-USDT,NAKA-USDT,YLD-USDT,TONE-USDT,REEF-USDT,REEF-BTC,TIDAL-USDT,TVK-USDT,TVK-BTC,INJ-USDT,INJ-BTC,BNB3L-USDT,BNB3S-USDT,MATIC3L-USDT,MATIC3S-USDT,NFTB-USDT,VEGA-USDT,VEGA-ETH,ALPHA-USDT,ALPHA-BTC,BADGER-USDT,BADGER-BTC,UNO-BTC,ZKT-USDT,AR-USDT,AR-BTC,XVS-USDT,XVS-BTC,JASMY-USDT,PERP-USDT,PERP-BTC,GHST-USDT,GHST-BTC,SCLP-USDT,SCLP-BTC,SUPER-USDT,SUPER-BTC,CPOOL-USDT,HERO-USDT,BASIC-USDT,XED-USDT,XED-BTC,AURY-USDT,SWASH-USDT,LTO-USDT,LTO-BTC,BUX-USDT,MTRG-USDT,DREAMS-USDT,SHIB-DOGE,QUICK-USDT,QUICK-BTC,TRU-USDT,TRU-BTC,WRX-USDT,WRX-BTC,TKO-USDT,TKO-BTC,SUSHI3L-USDT,SUSHI3S-USDT,NEAR3L-USDT,NEAR3S-USDT,DATA-USDT,DATA-BTC,NORD-BTC,ISP-USDT,CERE-USDT,SHILL-USDT,HEGIC-USDT,HEGIC-BTC,ERN-USDT,ERN-BTC,FTG-USDT,PAXG-USDT,PAXG-BTC,AUDIO-USDT,AUDIO-BTC,ENS-USDT,AAVE3L-USDT,AAVE3S-USDT,SAND3L-USDT,SAND3S-USDT,XTM-USDT,MNW-USDT,FXS-USDT,FXS-BTC,ATA-USDT,ATA-BTC,VXV-USDT,LRC-BTC,LRC-ETH,DPR-USDT,CWAR-USDT,CWAR-BTC,FLUX-BTC,EDG-BTC,PBR-USDT,WNXM-USDT,WNXM-BTC,ANT-USDT,ANT-BTC,COV-USDT,SWP-USDT,TWT-USDT,TWT-BTC,OM-USDT,OM-BTC,ADX-USDT,AVAX3L-USDT,AVAX3S-USDT,MANA3L-USDT,MANA3S-USDT,GLM-USDT,GLM-BTC,BAKE-USDT,BAKE-BTC,BAKE-ETH,NUM-USDT,VLX-USDT,VLX-BTC,TRADE-USDT,TRADE-BTC,1EARTH-USDT,MONI-USDT,LIKE-USDT,MFT-USDT,MFT-BTC,LIT-USDT,LIT-BTC,KAVA-USDT,SFP-USDT,SFP-BTC,BURGER-USDT,BURGER-BTC,ILA-USDT,CREAM-USDT,CREAM-BTC,RSR-USDT,RSR-BTC,BUY-BTC,IMX-USDT,GODS-USDT,KMA-USDT,SRM-USDT,SRM-BTC,POLC-USDT,XTAG-USDT,MNET-USDT,NGC-USDT,HARD-USDT,GALAX3L-USDT,GALAX3S-USDT,UNIC-USDT,POND-USDT,POND-BTC,VR-USDT,EPIK-USDT,NGL-USDT,NGL-BTC,KDON-USDT,PEL-USDT,CIRUS-ETH,LINA-USDT,LINA-BTC,KLAY-USDT,KLAY-BTC,CREDI-USDT,TRVL-USDT,LACE-USDT,LACE-ETH,ARKER-USDT,BONDLY-USDT,BONDLY-ETH,XEC-USDT,HEART-USDT,HEART-BTC,UNB-USDT,GAFI-USDT,KOL-USDT,KOL-ETH,H3RO3S-USDT,FALCONS-USDT,UFO-USDT,CHMB-USDT,GEEQ-USDT,ORC-USDT,RACEFI-USDT,PEOPLE-USDT,ADS-USDT,ADS-BTC,OCEAN-USDT,SOS-USDT,WHALE-USDT,TIME-USDT,CWEB-USDT,IOTA-USDT,IOTA-BTC,OOKI-USDT,OOKI-BTC,HNT-USDT,HNT-BTC,GGG-USDT,POWR-USDT,REVU-USDT,CLH-USDT,PLGR-USDT,GLMR-USDT,GLMR-BTC,LOVE-USDT,CTC-USDT,CTC-BTC,GARI-USDT,FRR-USDT,ASTR-USDT,ASTR-BTC,ERTHA-USDT,FCON-USDT,ACA-USDT,ACA-BTC,MTS-USDT,ROAR-USDT,HBB-USDT,SURV-USDT,CVX-USDT,AMP-USDT,ACT-USDT,MJT-USDT,MJT-KCS,SHX-USDT,SHX-BTC,STARLY-USDT,ONSTON-USDT,RANKER-USDT,WMT-USDT,XNO-USDT,XNO-BTC,MARS4-USDT,TFUEL-USDT,TFUEL-BTC,METIS-USDT,LAVAX-USDT,WAL-USDT,BULL-USDT,SON-USDT,MELOS-USDT,APE-USDT,GMT-USDT,BICO-USDT,STG-USDT,LMR-USDT,LMR-BTC,LOKA-USDT,URUS-USDT,JAM-USDT,JAM-ETH,BNC-USDT,LBP-USDT,CFX-USDT,LOOKS-USDT,XCN-USDT,XCN-BTC,KP3R-USDT,TITAN-USDT,INDI-USDT,UPO-USDT,SPELL-USDT,SLCL-USDT,CEEK-USDT,VEMP-USDT,BETA-USDT,NHCT-USDT,ARNM-USDT,FRA-USDT,VISION-USDT,COCOS-USDT,ALPINE-USDT,BNX-USDT,ZBC-USDT,WOOP-USDT,T-USDT,NYM-USDT,VOXEL-USDT,VOXEL-ETH,PSTAKE-USDT,SPA-USDT,SPA-ETH,SYNR-USDT,DAR-USDT,DAR-BTC,MV-USDT,XDEFI-USDT,RACA-USDT,XWG-USDT,HAWK-USDT,TRVL-BTC,SWFTC-USDT,IDEX-USDT,BRWL-USDT,PLATO-USDT,TAUM-USDT,CELR-USDT,AURORA-USDT,POSI-USDT,COOHA-USDT,KNC-USDT,EPK-USDT,PLD-USDT,PSL-USDT,PKF-USDT,OVR-USDT,SYS-USDT,SYS-BTC,BRISE-USDT,DG-USDT,EPX-USDT,GST-USDT,PLY-USDT,GAL-USDT,BSW-USDT,FITFI-USDT,FSN-USDT,H2O-USDT,GMM-USDT,AKT-USDT,SIN-USDT,AUSD-USDT,BOBA-USDT,KARA-USDT,BFC-USDT,BIFI-USDT,DFA-USDT,KYL-USDT,FCD-USDT,MBL-USDT,CELT-USDT,DUSK-USDT,USDD-USDT,USDD-USDC,FITFI-USDC,MBOX-USDT,MBOX-BTC,APE-USDC,AVAX-USDC,SHIB-USDC,XCN-USDC,TRX-USDC,NEAR-USDC,MATIC-USDC,FTM-USDC,ZIL-USDC,SOL-USDC,MLS-USDT,AFK-USDT,AFK-USDC,ACH-USDT,SCRT-USDT,SCRT-BTC,APE3L-USDT,APE3S-USDT,STORE-USDT,STORE-ETH,GMT3L-USDT,GMT3S-USDT,CCD-USDT,DOSE-USDC,LUNC-USDT,LUNC-USDC,USTC-USDT,USTC-USDC,GMT-USDC,VRA-USDC,DOT-USDC,RUNE-USDC,ATOM-USDC,BNB-USDC,JASMY-USDC,KCS-USDC,KDA-USDC,ALGO-USDC,LUNA-USDC,OP-USDT,OP-USDC,JASMY3L-USDT,JASMY3S-USDT,EVER-USDT,MOOV-USDT,IHC-USDT,ICX-USDT,ICX-ETH,BTC-BRL,ETH-BRL,USDT-BRL,WELL-USDT,FORT-USDT,USDP-USDT,USDD-TRX,CSPR-USDT,CSPR-ETH,WEMIX-USDT,REV3L-USDT,OLE-USDT,LDO-USDT,LDO-USDC,CULT-USDT,SWFTC-USDC,FIDA-USDT,BUSD-USDT,RBP-USDT,SRBP-USDT,HIBAYC-USDT,BUSD-USDC,OGV-USDT,WOMBAT-USDT,HIPUNKS-USDT,FT-USDT,ETC-USDC,HIENS4-USDT,EGAME-USDT,EGAME-BTC,STEPWATCH-USDT,HISAND33-USDT,DC-USDT,NEER-USDT,RVN-USDT,HIENS3-USDT,MC-USDT,PEEL-USDT,PEEL-BTC,SDL-USDT,SDL-BTC,SWEAT-USDT,HIODBS-USDT,CMP-USDT,PIX-USDT,MPLX-USDT,HIDOODLES-USDT,ETHW-USDT,QUARTZ-USDT,ACQ-USDT,ACQ-USDC,AOG-USDT,HIMAYC-USDT,PRMX-USDT,RED-USDT,PUMLX-USDT,XETA-USDT,GEM-USDT,DERC-USDT,P00LS-USDT,P00LS-USDC,KICKS-USDT,TRIBL-USDT,GMX-USDT,HIOD-USDT,POKT-USDT,EFI-USDT,APT-USDT,BBC-USDT,EUL-USDT,TON-USDT,PIAS-USDT,HIMEEBITS-USDT,HISQUIGGLE-USDT,XCV-USDT,HFT-USDT,HFT-USDC,ECOX-USDT,AMB-USDT,AZERO-USDT,HIFIDENZA-USDT,BEAT-USDT", + "requestFormat": { + "uppercase": true, + "delimiter": "-" + }, + "configFormat": { + "uppercase": true, + "delimiter": "-" + } + }, + "margin": { + "assetEnabled": true, + "enabled": "ETH-BTC,TRX-BTC,LTC-USDT,SOL-USDC", + "available": "BTC-USDT,MHC-ETH,MHC-BTC,OXEN-BTC,OXEN-ETH,NRG-BTC,AVA-USDT,FET-BTC,FET-ETH,ANKR-BTC,MHC-USDT,XMR-BTC,XMR-ETH,RIF-BTC,MTV-BTC,MTV-ETH,CRO-BTC,MTV-USDT,KMD-BTC,KMD-USDT,RFOX-USDT,TEL-USDT,TT-USDT,AERGO-USDT,XMR-USDT,TRX-KCS,ATOM-BTC,ATOM-ETH,ATOM-USDT,ATOM-KCS,ETN-USDT,FTM-USDT,TOMO-USDT,VSYS-USDT,OCEAN-BTC,OCEAN-ETH,CHR-BTC,CHR-USDT,FX-BTC,FX-ETH,NIM-BTC,NIM-ETH,COTI-BTC,COTI-USDT,NRG-ETH,BNB-BTC,BNB-USDT,JAR-BTC,JAR-USDT,ALGO-BTC,ALGO-ETH,ALGO-USDT,XEM-BTC,XEM-USDT,CIX100-USDT,XTZ-BTC,XTZ-USDT,ZEC-BTC,ZEC-USDT,ADA-BTC,ADA-USDT,REV-USDT,WXT-BTC,WXT-USDT,FORESTPLUS-BTC,FORESTPLUS-USDT,BOLT-BTC,BOLT-USDT,ARPA-USDT,CHZ-BTC,CHZ-USDT,DAPPT-BTC,DAPPT-USDT,NOIA-BTC,NOIA-USDT,WIN-BTC,WIN-USDT,DERO-BTC,DERO-USDT,BTT-USDT,EOSC-USDT,ENQ-BTC,ENQ-USDT,ONE-BTC,ONE-USDT,TOKO-BTC,TOKO-USDT,VID-BTC,VID-USDT,LUNA-USDT,SXP-BTC,SXP-USDT,AKRO-BTC,AKRO-USDT,ROOBEE-BTC,WIN-TRX,MAP-BTC,MAP-USDT,AMPL-BTC,AMPL-USDT,DAG-USDT,POL-USDT,ARX-USDT,NWC-BTC,NWC-USDT,BEPRO-BTC,BEPRO-USDT,VRA-BTC,VRA-USDT,KSM-BTC,KSM-USDT,DASH-USDT,SUTER-USDT,ACOIN-USDT,SUTER-BTC,SENSO-USDT,PRE-BTC,XDB-USDT,SYLO-USDT,WOM-USDT,SENSO-BTC,DGB-USDT,LYXE-USDT,LYXE-ETH,XDB-BTC,STX-BTC,STX-USDT,XSR-USDT,COMP-USDT,CRO-USDT,KAI-USDT,KAI-BTC,WEST-BTC,WEST-USDT,EWT-BTC,WAVES-USDT,WAVES-BTC,ORN-USDT,AMPL-ETH,BNS-USDT,MKR-USDT,SUKU-BTC,MLK-BTC,MLK-USDT,JST-USDT,KAI-ETH,SUKU-USDT,DIA-USDT,DIA-BTC,LINK-BTC,LINK-USDT,DOT-USDT,DOT-BTC,SHA-BTC,SHA-USDT,EWT-USDT,USDJ-USDT,EFX-BTC,CKB-BTC,CKB-USDT,UMA-USDT,ALEPH-USDT,VELO-USDT,SUN-USDT,BUY-USDT,YFI-USDT,OXEN-USDT,UNI-USDT,UOS-USDT,UOS-BTC,NIM-USDT,DEGO-USDT,DEGO-ETH,UDOO-ETH,RFUEL-USDT,FIL-USDT,UBX-ETH,REAP-USDT,AAVE-USDT,AAVE-BTC,TONE-BTC,TONE-ETH,ELF-ETH,AERGO-BTC,IOST-ETH,KCS-USDT,SNX-ETH,TOMO-ETH,KCS-ETH,DRGN-BTC,WAN-ETH,NULS-ETH,AXPR-ETH,POWR-BTC,QTUM-BTC,MANA-BTC,TEL-BTC,XYO-ETH,AXPR-BTC,ETN-BTC,COV-ETH,VET-BTC,KCS-BTC,CAPP-ETH,ONT-BTC,DRGN-ETH,DAG-ETH,TOMO-BTC,WAN-BTC,KNC-ETH,CRPT-ETH,LTC-USDT,BAX-ETH,BSV-USDT,DENT-ETH,AION-ETH,LYM-ETH,TRAC-ETH,ENJ-BTC,WAXP-BTC,DGB-BTC,ELA-BTC,ZIL-BTC,BSV-BTC,XLM-USDT,IOTX-ETH,SOUL-BTC,DOCK-BTC,AMB-ETH,TRX-BTC,XRP-TUSD,NULS-BTC,ETH-DAI,LSK-BTC,GMB-ETH,GMB-BTC,NEO-ETH,OMG-ETH,BTC-TUSD,KAT-USDT,KNC-BTC,ELF-BTC,MANA-ETH,ETC-USDT,ONT-ETH,MKR-BTC,KAT-BTC,XRP-USDC,XYO-BTC,SNT-ETH,ZRX-BTC,LOOM-ETH,AION-BTC,POWR-ETH,OLT-ETH,OLT-BTC,SNT-BTC,TRAC-BTC,XLM-ETH,ETH-USDT,BSV-ETH,TRX-ETH,ETN-ETH,AOA-USDT,BCD-BTC,DENT-BTC,DOCK-ETH,KEY-BTC,EOS-KCS,XLM-BTC,ADB-ETH,TIME-ETH,CVC-BTC,LSK-ETH,QKC-BTC,AMB-BTC,USDT-TUSD,ETC-ETH,XRP-BTC,NEO-KCS,SNX-USDT,CRPT-BTC,IOTX-BTC,LTC-ETH,XRP-KCS,ADB-BTC,LTC-KCS,TEL-ETH,DCR-ETH,LYM-USDT,USDT-USDC,ETH-USDC,DAG-BTC,AVA-BTC,BTC-USDT,WAXP-ETH,XRP-USDT,KEY-ETH,VET-ETH,FTM-BTC,USDT-DAI,QKC-ETH,ETH-BTC,MAN-BTC,CPC-ETH,TRX-USDT,BTC-DAI,ONT-USDT,DASH-ETH,BAX-BTC,AVA-ETH,LOOM-BTC,MVP-BTC,MKR-ETH,COV-BTC,CPC-BTC,REQ-ETH,EOS-BTC,LTC-BTC,XRP-ETH,CAPP-BTC,FTM-ETH,BCD-ETH,ZRX-ETH,DGB-ETH,VET-USDT,REQ-BTC,UTK-BTC,PLAY-BTC,UTK-ETH,SNX-BTC,MVP-ETH,NEO-BTC,SOUL-ETH,NEO-USDT,ELA-ETH,OMG-BTC,TIME-BTC,AOA-BTC,ETC-BTC,DCR-BTC,BTC-USDC,ENJ-ETH,IOST-BTC,DASH-BTC,EOS-USDT,EOS-ETH,ZIL-ETH,ETH-TUSD,GAS-BTC,LYM-BTC,BCH-BTC,VSYS-BTC,BCH-USDT,MKR-DAI,SOLVE-BTC,GRIN-BTC,GRIN-USDT,UQC-BTC,UQC-ETH,OPCT-BTC,OPCT-ETH,PRE-USDT,SHR-BTC,SHR-USDT,UBXT-USDT,ROSE-USDT,USDC-USDT,CTI-USDT,CTI-ETH,ETH2-ETH,BUX-BTC,XHV-USDT,PLU-USDT,GRT-USDT,CAS-BTC,CAS-USDT,MSWAP-BTC,MSWAP-USDT,GOM2-BTC,GOM2-USDT,REVV-BTC,REVV-USDT,LON-USDT,1INCH-USDT,LOC-USDT,API3-USDT,UNFI-USDT,HTR-USDT,FRONT-USDT,FRONT-BTC,WBTC-BTC,WBTC-ETH,MIR-USDT,LTC-USDC,BCH-USDC,HYDRA-USDT,DFI-USDT,DFI-BTC,CRV-USDT,SUSHI-USDT,FRM-USDT,EOS-USDC,BSV-USDC,ZEN-USDT,CUDOS-USDT,ADA-USDC,REN-USDT,LRC-USDT,LINK-USDC,KLV-USDT,KLV-BTC,BOA-USDT,THETA-USDT,QNT-USDT,BAT-USDT,DOGE-USDT,DOGE-USDC,DAO-USDT,STRONG-USDT,TRIAS-USDT,TRIAS-BTC,DOGE-BTC,MITX-BTC,MITX-USDT,CAKE-USDT,ORAI-USDT,ZEE-USDT,LTX-USDT,LTX-BTC,MASK-USDT,KLV-TRX,IDEA-USDT,PHA-USDT,PHA-ETH,BCH-KCS,SRK-USDT,SRK-BTC,ADA-KCS,HTR-BTC,BSV-KCS,DOT-KCS,LINK-KCS,MIR-KCS,BNB-KCS,XLM-KCS,VET-KCS,SWINGBY-USDT,SWINGBY-BTC,XHV-BTC,DASH-KCS,UNI-KCS,AAVE-KCS,DOGE-KCS,ZEC-KCS,XTZ-KCS,GRT-KCS,ALGO-KCS,EWT-KCS,GAS-USDT,AVAX-USDT,AVAX-BTC,KRL-BTC,KRL-USDT,POLK-USDT,POLK-BTC,ENJ-USDT,MANA-USDT,RNDR-USDT,RNDR-BTC,RLY-USDT,ANC-USDT,SKEY-USDT,LAYER-USDT,TARA-USDT,TARA-ETH,IOST-USDT,DYP-USDT,DYP-ETH,XYM-USDT,XYM-BTC,PCX-USDT,PCX-BTC,ORBS-USDT,ORBS-BTC,BTC3L-USDT,BTC3S-USDT,ETH3L-USDT,ETH3S-USDT,ANKR-USDT,DSLA-USDT,DSLA-BTC,SAND-USDT,VAI-USDT,XCUR-USDT,XCUR-BTC,FLUX-USDT,OMG-USDT,ZIL-USDT,DODO-USDT,MAN-USDT,BAX-USDT,BOSON-USDT,BOSON-ETH,PUNDIX-USDT,PUNDIX-BTC,WAXP-USDT,HT-USDT,PDEX-USDT,LABS-USDT,LABS-ETH,GMB-USDT,PHNX-USDT,PHNX-BTC,HAI-USDT,EQZ-USDT,FORTH-USDT,HORD-USDT,CGG-USDT,UBX-USDT,GHX-USDT,TCP-USDT,STND-USDT,STND-ETH,TOWER-USDT,TOWER-BTC,ACE-USDT,LOCG-USDT,CARD-USDT,FLY-USDT,CWS-USDT,XDC-USDT,XDC-ETH,STRK-BTC,STRK-ETH,SHIB-USDT,POLX-USDT,KDA-USDT,KDA-BTC,ICP-USDT,ICP-BTC,STC-USDT,STC-BTC,GOVI-USDT,GOVI-BTC,FKX-USDT,CELO-USDT,CELO-BTC,CUSD-USDT,CUSD-BTC,FCL-USDT,MATIC-USDT,MATIC-BTC,ELA-USDT,CRPT-USDT,OPCT-USDT,OGN-USDT,OGN-BTC,OUSD-USDT,OUSD-BTC,TLOS-USDT,TLOS-BTC,YOP-USDT,YOP-ETH,GLQ-USDT,GLQ-BTC,MXC-USDT,ERSDL-USDT,HOTCROSS-USDT,ADA3L-USDT,ADA3S-USDT,HYVE-USDT,HYVE-BTC,DAPPX-USDT,KONO-USDT,PRQ-USDT,MAHA-USDT,MAHA-BTC,FEAR-USDT,PYR-USDT,PYR-BTC,PROM-USDT,PROM-BTC,GLCH-USDT,UNO-USDT,ALBT-USDT,ALBT-ETH,XCAD-USDT,EOS3L-USDT,EOS3S-USDT,BCH3L-USDT,BCH3S-USDT,ELON-USDT,APL-USDT,FCL-ETH,VEED-USDT,VEED-BTC,DIVI-USDT,PDEX-BTC,JUP-USDT,JUP-ETH,POLS-USDT,POLS-BTC,LPOOL-USDT,LPOOL-BTC,LSS-USDT,VET3L-USDT,VET3S-USDT,LTC3L-USDT,LTC3S-USDT,ABBC-USDT,ABBC-BTC,KOK-USDT,ROSN-USDT,DORA-USDT,DORA-BTC,ZCX-USDT,ZCX-BTC,NORD-USDT,GMEE-USDT,SFUND-USDT,XAVA-USDT,AI-USDT,ALPACA-USDT,IOI-USDT,NFT-USDT,NFT-TRX,MNST-USDT,MEM-USDT,AGIX-USDT,AGIX-BTC,AGIX-ETH,CQT-USDT,AIOZ-USDT,MARSH-USDT,HAPI-USDT,MODEFI-USDT,MODEFI-BTC,YFDAI-USDT,YFDAI-BTC,GENS-USDT,FORM-USDT,ARRR-USDT,ARRR-BTC,TOKO-KCS,EXRD-USDT,NGM-USDT,LPT-USDT,STMX-USDT,ASD-USDT,BOND-USDT,HAI-BTC,SOUL-USDT,2CRZ-USDT,NEAR-USDT,NEAR-BTC,DFYN-USDT,OOE-USDT,CFG-USDT,CFG-BTC,AXS-USDT,CLV-USDT,ROUTE-USDT,KAR-USDT,EFX-USDT,XDC-BTC,SHFT-USDT,PMON-USDT,DPET-USDT,ERG-USDT,ERG-BTC,SOL-USDT,SLP-USDT,LITH-USDT,LITH-ETH,XCH-USDT,HAKA-USDT,LAYER-BTC,MTL-USDT,MTL-BTC,IOTX-USDT,GALA-USDT,REQ-USDT,TXA-USDT,TXA-USDC,CIRUS-USDT,QI-USDT,QI-BTC,ODDZ-USDT,PNT-USDT,PNT-BTC,XPR-USDT,XPR-BTC,TRIBE-USDT,SHFT-BTC,MOVR-USDT,MOVR-ETH,WOO-USDT,WILD-USDT,QRDO-USDT,QRDO-ETH,SDN-USDT,SDN-ETH,MAKI-USDT,MAKI-BTC,REP-USDT,REP-BTC,REP-ETH,BNT-USDT,BNT-BTC,BNT-ETH,OXT-USDT,OXT-BTC,OXT-ETH,BAL-USDT,BAL-BTC,BAL-ETH,STORJ-USDT,STORJ-BTC,STORJ-ETH,YGG-USDT,NDAU-USDT,SDAO-USDT,SDAO-ETH,XRP3L-USDT,XRP3S-USDT,SKL-USDT,SKL-BTC,NMR-USDT,NMR-BTC,IXS-USDT,TRB-USDT,TRB-BTC,DYDX-USDT,XYO-USDT,GTC-USDT,GTC-BTC,EQX-USDT,EQX-BTC,RLC-USDT,RLC-BTC,XPRT-USDT,EGLD-USDT,EGLD-BTC,HBAR-USDT,HBAR-BTC,DOGE3L-USDT,DOGE3S-USDT,FLOW-USDT,FLOW-BTC,NKN-USDT,NKN-BTC,PBX-USDT,SOL3L-USDT,SOL3S-USDT,MLN-USDT,MLN-BTC,XNL-USDT,SOLVE-USDT,WNCG-USDT,WNCG-BTC,DMTR-USDT,LINK3L-USDT,LINK3S-USDT,DOT3L-USDT,DOT3S-USDT,CTSI-USDT,CTSI-BTC,ALICE-USDT,ALICE-BTC,ALICE-ETH,OPUL-USDT,ILV-USDT,BAND-USDT,BAND-BTC,FTT-USDT,FTT-BTC,DVPN-USDT,SKU-USDT,SKU-BTC,EDG-USDT,SLIM-USDT,TLM-USDT,TLM-BTC,TLM-ETH,DEXE-USDT,DEXE-BTC,DEXE-ETH,MATTER-USDT,CUDOS-BTC,RUNE-USDT,RUNE-BTC,RMRK-USDT,BMON-USDT,C98-USDT,BLOK-USDT,SOLR-USDT,ATOM3L-USDT,ATOM3S-USDT,UNI3L-USDT,UNI3S-USDT,WSIENNA-USDT,PUSH-USDT,PUSH-BTC,FORM-ETH,NTVRK-USDT,NTVRK-USDC,AXS3L-USDT,AXS3S-USDT,FTM3L-USDT,FTM3S-USDT,FLAME-USDT,AGLD-USDT,NAKA-USDT,YLD-USDT,TONE-USDT,REEF-USDT,REEF-BTC,TIDAL-USDT,TVK-USDT,TVK-BTC,INJ-USDT,INJ-BTC,BNB3L-USDT,BNB3S-USDT,MATIC3L-USDT,MATIC3S-USDT,NFTB-USDT,VEGA-USDT,VEGA-ETH,ALPHA-USDT,ALPHA-BTC,BADGER-USDT,BADGER-BTC,UNO-BTC,ZKT-USDT,AR-USDT,AR-BTC,XVS-USDT,XVS-BTC,JASMY-USDT,PERP-USDT,PERP-BTC,GHST-USDT,GHST-BTC,SCLP-USDT,SCLP-BTC,SUPER-USDT,SUPER-BTC,CPOOL-USDT,HERO-USDT,BASIC-USDT,XED-USDT,XED-BTC,AURY-USDT,SWASH-USDT,LTO-USDT,LTO-BTC,BUX-USDT,MTRG-USDT,DREAMS-USDT,SHIB-DOGE,QUICK-USDT,QUICK-BTC,TRU-USDT,TRU-BTC,WRX-USDT,WRX-BTC,TKO-USDT,TKO-BTC,SUSHI3L-USDT,SUSHI3S-USDT,NEAR3L-USDT,NEAR3S-USDT,DATA-USDT,DATA-BTC,NORD-BTC,ISP-USDT,CERE-USDT,SHILL-USDT,HEGIC-USDT,HEGIC-BTC,ERN-USDT,ERN-BTC,FTG-USDT,PAXG-USDT,PAXG-BTC,AUDIO-USDT,AUDIO-BTC,ENS-USDT,AAVE3L-USDT,AAVE3S-USDT,SAND3L-USDT,SAND3S-USDT,XTM-USDT,MNW-USDT,FXS-USDT,FXS-BTC,ATA-USDT,ATA-BTC,VXV-USDT,LRC-BTC,LRC-ETH,DPR-USDT,CWAR-USDT,CWAR-BTC,FLUX-BTC,EDG-BTC,PBR-USDT,WNXM-USDT,WNXM-BTC,ANT-USDT,ANT-BTC,COV-USDT,SWP-USDT,TWT-USDT,TWT-BTC,OM-USDT,OM-BTC,ADX-USDT,AVAX3L-USDT,AVAX3S-USDT,MANA3L-USDT,MANA3S-USDT,GLM-USDT,GLM-BTC,BAKE-USDT,BAKE-BTC,BAKE-ETH,NUM-USDT,VLX-USDT,VLX-BTC,TRADE-USDT,TRADE-BTC,1EARTH-USDT,MONI-USDT,LIKE-USDT,MFT-USDT,MFT-BTC,LIT-USDT,LIT-BTC,KAVA-USDT,SFP-USDT,SFP-BTC,BURGER-USDT,BURGER-BTC,ILA-USDT,CREAM-USDT,CREAM-BTC,RSR-USDT,RSR-BTC,BUY-BTC,IMX-USDT,GODS-USDT,KMA-USDT,SRM-USDT,SRM-BTC,POLC-USDT,XTAG-USDT,MNET-USDT,NGC-USDT,HARD-USDT,GALAX3L-USDT,GALAX3S-USDT,UNIC-USDT,POND-USDT,POND-BTC,VR-USDT,EPIK-USDT,NGL-USDT,NGL-BTC,KDON-USDT,PEL-USDT,CIRUS-ETH,LINA-USDT,LINA-BTC,KLAY-USDT,KLAY-BTC,CREDI-USDT,TRVL-USDT,LACE-USDT,LACE-ETH,ARKER-USDT,BONDLY-USDT,BONDLY-ETH,XEC-USDT,HEART-USDT,HEART-BTC,UNB-USDT,GAFI-USDT,KOL-USDT,KOL-ETH,H3RO3S-USDT,FALCONS-USDT,UFO-USDT,CHMB-USDT,GEEQ-USDT,ORC-USDT,RACEFI-USDT,PEOPLE-USDT,ADS-USDT,ADS-BTC,OCEAN-USDT,SOS-USDT,WHALE-USDT,TIME-USDT,CWEB-USDT,IOTA-USDT,IOTA-BTC,OOKI-USDT,OOKI-BTC,HNT-USDT,HNT-BTC,GGG-USDT,POWR-USDT,REVU-USDT,CLH-USDT,PLGR-USDT,GLMR-USDT,GLMR-BTC,LOVE-USDT,CTC-USDT,CTC-BTC,GARI-USDT,FRR-USDT,ASTR-USDT,ASTR-BTC,ERTHA-USDT,FCON-USDT,ACA-USDT,ACA-BTC,MTS-USDT,ROAR-USDT,HBB-USDT,SURV-USDT,CVX-USDT,AMP-USDT,ACT-USDT,MJT-USDT,MJT-KCS,SHX-USDT,SHX-BTC,STARLY-USDT,ONSTON-USDT,RANKER-USDT,WMT-USDT,XNO-USDT,XNO-BTC,MARS4-USDT,TFUEL-USDT,TFUEL-BTC,METIS-USDT,LAVAX-USDT,WAL-USDT,BULL-USDT,SON-USDT,MELOS-USDT,APE-USDT,GMT-USDT,BICO-USDT,STG-USDT,LMR-USDT,LMR-BTC,LOKA-USDT,URUS-USDT,JAM-USDT,JAM-ETH,BNC-USDT,LBP-USDT,CFX-USDT,LOOKS-USDT,XCN-USDT,XCN-BTC,KP3R-USDT,TITAN-USDT,INDI-USDT,UPO-USDT,SPELL-USDT,SLCL-USDT,CEEK-USDT,VEMP-USDT,BETA-USDT,NHCT-USDT,ARNM-USDT,FRA-USDT,VISION-USDT,COCOS-USDT,ALPINE-USDT,BNX-USDT,ZBC-USDT,WOOP-USDT,T-USDT,NYM-USDT,VOXEL-USDT,VOXEL-ETH,PSTAKE-USDT,SPA-USDT,SPA-ETH,SYNR-USDT,DAR-USDT,DAR-BTC,MV-USDT,XDEFI-USDT,RACA-USDT,XWG-USDT,HAWK-USDT,TRVL-BTC,SWFTC-USDT,IDEX-USDT,BRWL-USDT,PLATO-USDT,TAUM-USDT,CELR-USDT,AURORA-USDT,POSI-USDT,COOHA-USDT,KNC-USDT,EPK-USDT,PLD-USDT,PSL-USDT,PKF-USDT,OVR-USDT,SYS-USDT,SYS-BTC,BRISE-USDT,DG-USDT,EPX-USDT,GST-USDT,PLY-USDT,GAL-USDT,BSW-USDT,FITFI-USDT,FSN-USDT,H2O-USDT,GMM-USDT,AKT-USDT,SIN-USDT,AUSD-USDT,BOBA-USDT,KARA-USDT,BFC-USDT,BIFI-USDT,DFA-USDT,KYL-USDT,FCD-USDT,MBL-USDT,CELT-USDT,DUSK-USDT,USDD-USDT,USDD-USDC,FITFI-USDC,MBOX-USDT,MBOX-BTC,APE-USDC,AVAX-USDC,SHIB-USDC,XCN-USDC,TRX-USDC,NEAR-USDC,MATIC-USDC,FTM-USDC,ZIL-USDC,SOL-USDC,MLS-USDT,AFK-USDT,AFK-USDC,ACH-USDT,SCRT-USDT,SCRT-BTC,APE3L-USDT,APE3S-USDT,STORE-USDT,STORE-ETH,GMT3L-USDT,GMT3S-USDT,CCD-USDT,DOSE-USDC,LUNC-USDT,LUNC-USDC,USTC-USDT,USTC-USDC,GMT-USDC,VRA-USDC,DOT-USDC,RUNE-USDC,ATOM-USDC,BNB-USDC,JASMY-USDC,KCS-USDC,KDA-USDC,ALGO-USDC,LUNA-USDC,OP-USDT,OP-USDC,JASMY3L-USDT,JASMY3S-USDT,EVER-USDT,MOOV-USDT,IHC-USDT,ICX-USDT,ICX-ETH,BTC-BRL,ETH-BRL,USDT-BRL,WELL-USDT,FORT-USDT,USDP-USDT,USDD-TRX,CSPR-USDT,CSPR-ETH,WEMIX-USDT,REV3L-USDT,OLE-USDT,LDO-USDT,LDO-USDC,CULT-USDT,SWFTC-USDC,FIDA-USDT,BUSD-USDT,RBP-USDT,SRBP-USDT,HIBAYC-USDT,BUSD-USDC,OGV-USDT,WOMBAT-USDT,HIPUNKS-USDT,FT-USDT,ETC-USDC,HIENS4-USDT,EGAME-USDT,EGAME-BTC,STEPWATCH-USDT,HISAND33-USDT,DC-USDT,NEER-USDT,RVN-USDT,HIENS3-USDT,MC-USDT,PEEL-USDT,PEEL-BTC,SDL-USDT,SDL-BTC,SWEAT-USDT,HIODBS-USDT,CMP-USDT,PIX-USDT,MPLX-USDT,HIDOODLES-USDT,ETHW-USDT,QUARTZ-USDT,ACQ-USDT,ACQ-USDC,AOG-USDT,HIMAYC-USDT,PRMX-USDT,RED-USDT,PUMLX-USDT,XETA-USDT,GEM-USDT,DERC-USDT,P00LS-USDT,P00LS-USDC,KICKS-USDT,TRIBL-USDT,GMX-USDT,HIOD-USDT,POKT-USDT,EFI-USDT,APT-USDT,BBC-USDT,EUL-USDT,TON-USDT,PIAS-USDT,HIMEEBITS-USDT,HISQUIGGLE-USDT,XCV-USDT,HFT-USDT,HFT-USDC,ECOX-USDT,AMB-USDT,AZERO-USDT,HIFIDENZA-USDT,BEAT-USDT", + "requestFormat": { + "uppercase": true, + "delimiter": "-" + }, + "configFormat": { + "uppercase": true, + "delimiter": "-" + } + }, + "futures": { + "assetEnabled": true, + "enabled": "ETH_USDCM,XBT_USDCM,SOL_USDTM", + "available": "XBT_USDTM,XBT_USDM,ETH_USDTM,BCH_USDTM,BSV_USDTM,LINK_USDTM,UNI_USDTM,YFI_USDTM,EOS_USDTM,DOT_USDTM,FIL_USDTM,ADA_USDTM,XRP_USDTM,LTC_USDTM,ETH_USDM,TRX_USDTM,GRT_USDTM,SUSHI_USDTM,XLM_USDTM,1INCH_USDTM,ZEC_USDTM,DASH_USDTM,DOT_USDM,XRP_USDM,AAVE_USDTM,KSM_USDTM,DOGE_USDTM,VET_USDTM,BNB_USDTM,SXP_USDTM,SOL_USDTM,CRV_USDTM,ALGO_USDTM,AVAX_USDTM,FTM_USDTM,MATIC_USDTM,THETA_USDTM,ATOM_USDTM,CHZ_USDTM,ENJ_USDTM,MANA_USDTM,DENT_USDTM,OCEAN_USDTM,BAT_USDTM,XEM_USDTM,QTUM_USDTM,XTZ_USDTM,SNX_USDTM,NEO_USDTM,ONT_USDTM,XMR_USDTM,COMP_USDTM,ETC_USDTM,WAVES_USDTM,BAND_USDTM,MKR_USDTM,RVN_USDTM,DGB_USDTM,SHIB_USDTM,ICP_USDTM,DYDX_USDTM,AXS_USDTM,HBAR_USDTM,EGLD_USDTM,ALICE_USDTM,YGG_USDTM,NEAR_USDTM,SAND_USDTM,C98_USDTM,ONE_USDTM,VRA_USDTM,GALA_USDTM,CHR_USDTM,LRC_USDTM,FLOW_USDTM,RNDR_USDTM,IOTX_USDTM,CRO_USDTM,WAXP_USDTM,PEOPLE_USDTM,OMG_USDTM,LINA_USDTM,IMX_USDTM,CELR_USDTM,ENS_USDTM,CELO_USDTM,CTSI_USDTM,ARPA_USDTM,KNC_USDTM,ROSE_USDTM,AGLD_USDTM,APE_USDTM,JASMY_USDTM,ZIL_USDTM,GMT_USDTM,RUNE_USDTM,LOOKS_USDTM,AUDIO_USDTM,KDA_USDTM,KAVA_USDTM,BAL_USDTM,GAL_USDTM,LUNA_USDTM,LUNC_USDTM,OP_USDTM,XCN_USDTM,UNFI_USDTM,LIT_USDTM,DUSK_USDTM,STORJ_USDTM,RSR_USDTM,OGN_USDTM,TRB_USDTM,PERP_USDTM,KLAY_USDTM,ANKR_USDTM,LDO_USDTM,WOO_USDTM,REN_USDTM,CVC_USDTM,INJ_USDTM,APT_USDTM,MASK_USDTM,REEF_USDTM,TON_USDTM,MAGIC_USDTM,CFX_USDTM,AGIX_USDTM,FXS_USDTM,FET_USDTM,AR_USDTM,GMX_USDTM,BLUR_USDTM,ASTR_USDTM,HIGH_USDTM,ACH_USDTM,STX_USDTM,SSV_USDTM,FLOKI_USDTM,CKB_USDTM,TRU_USDTM,QNT_USDTM,ETH_USDCM,MINA_USDTM,USDC_USDTM,T_USDTM,LQTY_USDTM,ARB_USDTM,DAR_USDTM,ID_USDTM,STG_USDTM,JOE_USDTM,RDNT_USDTM,DODO_USDTM,PAXG_USDTM,ZRX_USDTM,ICX_USDTM,HFT_USDTM,NKN_USDTM,HOOK_USDTM,ANT_USDTM,DC_USDTM,BEL_USDTM,SUI_USDTM,PEPE_USDTM,IDEX_USDTM,GNS_USDTM,CETUS_USDTM,KAS_USDTM,ORDI_USDTM,WOJAK_USDTM,POGAI_USDTM,UMA_USDTM,RAD_USDTM,XBT_USDCM,PHB_USDTM,FTT_USDTM,10000LADYS_USDTM,LEVER_USDTM,TURBO_USDTM,TOMO_USDTM,BOB_USDTM,KEY_USDTM,EDU_USDTM,MTL_USDTM,FLUX_USDTM,COMBO_USDTM,AMB_USDTM,ALPHA_USDTM,SFP_USDTM,MAV_USDTM,MDT_USDTM,XEC_USDTM,XVG_USDTM,1000PEPE2_USDTM,PENDLE_USDTM,STMX_USDTM,WLD_USDTM,LPT_USDTM,GTC_USDTM,BNT_USDTM,OXT_USDTM,BLZ_USDTM,SEI_USDTM,BAKE_USDTM,CYBER_USDTM,NMR_USDTM,FLM_USDTM,SPELL_USDTM,ARK_USDTM,XBT_MU23,XBT_MZ23", + "requestFormat": { + "uppercase": true, + "delimiter": "" + }, + "configFormat": { + "uppercase": true, + "delimiter": "_" + } + } + } + }, + "api": { + "authenticatedSupport": true, + "authenticatedWebsocketApiSupport": true, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret", + "clientID": "ClientID" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "tickerBatching": true, + "autoPairUpdates": true + }, + "websocketAPI": true, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": true + } + }, + "bankAccounts": [ + { + "enabled": false, + "bankName": "", + "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ] }, - "enabled": { - "autoPairUpdates": true, - "websocketAPI": true, - "saveTradeData": false, - "tradeFeed": false, - "fillsFeed": false - } - }, - "bankAccounts": [ { - "enabled": false, - "bankName": "", - "bankAddress": "", - "bankPostalCode": "", - "bankPostalCity": "", - "bankCountry": "", - "accountName": "", - "accountNumber": "", - "swiftCode": "", - "iban": "", - "supportedCurrencies": "" - } - ], - "orderbook": { - "verificationBypass": false, - "websocketBufferLimit": 5, - "websocketBufferEnabled": false, - "publishPeriod": 10000000000 - } - }, - { - "name": "Poloniex", - "enabled": true, - "verbose": false, - "httpTimeout": 15000000000, - "websocketResponseCheckTimeout": 30000000, - "websocketResponseMaxLimit": 7000000000, - "websocketTrafficTimeout": 30000000000, - "websocketOrderbookBufferLimit": 5, - "baseCurrencies": "USD", - "currencyPairs": { - "requestFormat": { - "uppercase": true, - "delimiter": "_" - }, - "configFormat": { - "uppercase": true, - "delimiter": "_" - }, - "useGlobalFormat": true, - "assetTypes": [ - "spot" - ], - "pairs": { - "spot": { - "enabled": "BTC_LTC,BTC_ETH,BTC_DOGE,BTC_DASH,BTC_XRP", - "available": "USDC_GRIN,BTC_BCN,BTC_DGB,BTC_XMR,USDT_STR,BTC_SC,BTC_ZRX,USDC_XMR,BTC_TRX,BTC_STR,BTC_SNT,USDT_QTUM,USDC_BTC,BTC_NMR,BTC_DASH,BTC_NXT,USDT_LTC,BTC_DCR,USDT_ZRX,USDC_ZEC,USDT_REP,USDT_BAT,BTC_MANA,USDC_BCHABC,USDC_STR,BTC_XRP,USDT_ETH,BTC_REP,USDT_EOS,USDC_ATOM,USDT_XRP,BTC_ETH,USDT_LSK,USDT_SC,USDT_MANA,USDC_ETC,USDC_ETH,BTC_BTS,BTC_LTC,BTC_ETC,BTC_OMG,BTC_STORJ,USDC_XRP,USDT_GRIN,BTC_QTUM,BTC_MAID,BTC_XEM,USDT_BTC,USDT_DASH,ETH_REP,BTC_ZEC,BTC_STRAT,USDC_LTC,BTC_FOAM,USDC_TRX,BTC_DOGE,BTC_VIA,BTC_VTC,ETH_ETC,USDT_ETC,ETH_EOS,USDC_BCHSV,USDT_NXT,USDT_XMR,BTC_ARDR,BTC_CVC,ETH_BAT,USDC_DOGE,BTC_XPM,BTC_LOOM,BTC_LPT,USDC_EOS,USDT_DGB,USDT_BCHSV,BTC_OMNI,ETH_ZEC,BTC_EOS,BTC_KNC,BTC_BCHSV,BTC_POLY,USDC_DASH,USDT_GNT,BTC_BCHABC,BTC_GRIN,BTC_ATOM,USDT_ATOM,USDT_BCHABC,BTC_LSK,ETH_ZRX,BTC_GAS,BTC_BAT,BTC_BNT,USDT_TRX,BTC_FCT,USDT_ZEC,BTC_GNT,USDT_DOGE,USDC_USDT" - } - } - }, - "api": { - "authenticatedSupport": false, - "authenticatedWebsocketApiSupport": false, - "endpoints": { - "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" - }, - "credentials": { - "key": "Key", - "secret": "Secret" - }, - "credentialsValidator": { - "requiresKey": true, - "requiresSecret": true - } - }, - "features": { - "supports": { - "restAPI": true, - "restCapabilities": { - "tickerBatching": true, - "autoPairUpdates": true - }, - "websocketAPI": true, - "websocketCapabilities": {} + "name": "LBank", + "enabled": true, + "verbose": false, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, + "websocketOrderbookBufferLimit": 5, + "baseCurrencies": "USD", + "currencyPairs": { + "requestFormat": { + "uppercase": false, + "delimiter": "_" + }, + "configFormat": { + "uppercase": false, + "delimiter": "_" + }, + "useGlobalFormat": true, + "assetTypes": [ + "spot" + ], + "pairs": { + "spot": { + "enabled": "eth_btc", + "available": "FBC_USDT,GALT_USDT,IOEX_USDT,OATH_USDT,BLOC_USDT,BTC_USDT,ETH_USDT,ETH_BTC,ABBC_BTC,KISC_ETH,BXA_USDT,ATP_USDT,MAT_USDT,SKY_BTC,RNT_USDT,VENA_USDT,GRIN_USDT,IDA_USDT,PNT_USDT,OPX_USDT,VTHO_BTC,AMO_ETH,UBEX_BTC,EOS_BTC,UBEX_USDT,TNS_BTC,SAIT_ETH,DAX_BTC,DAX_ETH,DALI_USDT,VET_USDT,BCH_BTC,BCH_USDT,NEO_USDT,QTUM_USDT,ZEC_USDT,VET_BTC,PAI_BTC,PNT_BTC,NEO_BTC,DASH_BTC,LTC_BTC,ETC_BTC,QTUM_BTC,ZEC_BTC,SC_BTC,BTS_BTC,CPX_BTC,XWC_BTC,FIL6_BTC,FIL12_BTC,FIL36_BTC,EOS_USDT,UT_ETH,ELA_ETH,VET_ETH,VTHO_ETH,PAI_ETH,HER_ETH,PTT_ETH,TAC_ETH,IDHUB_ETH,SSC_ETH,SKM_ETH,PLY_ETH,EXT_ETH,EOS_ETH,YOYOW_ETH,TRX_ETH,QTUM_ETH,ZEC_ETH,BTS_ETH,BTM_ETH,MITH_ETH,NAS_ETH,MAN_ETH,DBC_ETH,BTO_ETH,DDD_ETH,CPX_ETH,CS_ETH,IHT_ETH,OCN_ETH,EKO_ETH,XWC_ETH,PUT_ETH,PNT_ETH,AAC_ETH,FIL6_ETH,FIL12_ETH,FIL36_ETH,SEER_ETH,BSB_ETH,CDC_ETH,GRAMS_ETH,DDMX_ETH,EAI_ETH,BNB_USDT,HT_USDT,KBC_BTC,KBC_USDT,MAI_USDT,PHV_USDT,GT_USDT,VOKEN_USDT,CYE_USDT,BRC_USDT,BTC_AUSD,DDMX_USDT,SEAL_USDT,SEOS_BTC,BTY_USDT,FO_USDT,DLX_USDT,BFC_USDT,LBK_USDT,SERO_USDT,MTV_USDT,CKB_USDT,ARPA_USDT,ZIP_USDT,AT_USDT,DOT_USDT,DILI_USDT,DUO_USDT,TEP_USDT,BIKI_USDT,MX_USDT,DNS_USDT,OKB_USDT,FLDT_USDT,CCTC_USDT,WIN_USDT,BTT_USDT,TRX_USDT,GRS_BTC,GST_USDT,GST_ETH,ABBC_USDT,UTK_USDT,GKI_USDT,BPX_USDT,SUTER_USDT,LT_USDT,LM_USDT,HTDF_USDT" + } + } + }, + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "tickerBatching": true, + "autoPairUpdates": true + }, + "websocketAPI": false, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": false + } + }, + "bankAccounts": [ + { + "enabled": false, + "bankName": "", + "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ] }, - "enabled": { - "autoPairUpdates": true, - "websocketAPI": false - } - }, - "bankAccounts": [ { - "enabled": false, - "bankName": "", - "bankAddress": "", - "bankPostalCode": "", - "bankPostalCity": "", - "bankCountry": "", - "accountName": "", - "accountNumber": "", - "swiftCode": "", - "iban": "", - "supportedCurrencies": "" - } - ] - }, - { - "name": "Yobit", - "enabled": true, - "verbose": false, - "httpTimeout": 15000000000, - "websocketResponseCheckTimeout": 30000000, - "websocketResponseMaxLimit": 7000000000, - "websocketTrafficTimeout": 30000000000, - "websocketOrderbookBufferLimit": 5, - "baseCurrencies": "USD", - "currencyPairs": { - "requestFormat": { - "uppercase": false, - "delimiter": "_", - "separator": "-" - }, - "configFormat": { - "uppercase": true, - "delimiter": "_" - }, - "useGlobalFormat": true, - "lastUpdated": 1566798411, - "assetTypes": [ - "spot" - ], - "pairs": { - "spot": { - "enabled": "LTC_BTC,ETH_BTC,BTC_USD,DASH_BTC", - "available": "DASH_BTC,WAVES_BTC,LSK_BTC,LIZA_BTC,BCC_BTC,ETH_BTC,LTC_BTC,TRX_BTC,DOGE_BTC,VNTX_BTC,SW_BTC,ZEC_BTC,DASH_ETH,WAVES_ETH,LSK_ETH,LIZA_ETH,BCC_ETH,LTC_ETH,TRX_ETH,DOGE_ETH,VNTX_ETH,SW_ETH,ZEC_ETH,DASH_DOGE,WAVES_DOGE,LSK_DOGE,LIZA_DOGE,BCC_DOGE,LTC_DOGE,TRX_DOGE,VNTX_DOGE,SW_DOGE,ZEC_DOGE,DASH_USD,WAVES_USD,LSK_USD,LIZA_USD,BCC_USD,LTC_USD,TRX_USD,VNTX_USD,SW_USD,ZEC_USD,ETH_USD,BTC_USD,DASH_RUR,WAVES_BTC,WAVES_RUR,LSK_RUR,LIZA_RUR,BCC_RUR,LTC_RUR,TRX_RUR,VNTX_RUR,SW_RUR,ETH_RUR,ZEC_RUR" - } - } - }, - "api": { - "authenticatedSupport": false, - "authenticatedWebsocketApiSupport": false, - "endpoints": { - "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + "name": "Okcoin", + "enabled": true, + "verbose": false, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, + "websocketOrderbookBufferLimit": 5, + "baseCurrencies": "USD", + "currencyPairs": { + "requestFormat": { + "uppercase": true, + "delimiter": "-" + }, + "configFormat": { + "uppercase": true, + "delimiter": "-" + }, + "useGlobalFormat": true, + "assetTypes": [ + "spot" + ], + "pairs": { + "spot": { + "enabled": "BTC-USD", + "available": "BTC-USD,LTC-USD,ETH-USD,ETC-USD,TUSD-USD,BCH-USD,EOS-USD,XRP-USD,TRX-USD,BSV-USD,USDT-USD,USDK-USD,XLM-USD,ADA-USD,BAT-USD,DCR-USD,EURS-USD,HBAR-USD,PAX-USD,USDC-USD,ZEC-USD,BTC-USDT,BTC-SGD,ETH-SGD,BTC-EUR,BTC-EURS,ETH-EUR,BCH-EUR,EURS-EUR" + }, + "margin": { + "enabled": "BTC-USD", + "available": "BTC-USD" + } + } + }, + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true, + "requiresClientID": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "tickerBatching": true, + "autoPairUpdates": true + }, + "websocketAPI": true, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": true + } + }, + "bankAccounts": [ + { + "enabled": false, + "bankName": "", + "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ] }, - "credentials": { - "key": "Key", - "secret": "Secret" + { + "name": "Okx", + "enabled": true, + "verbose": false, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, + "baseCurrencies": "USD", + "currencyPairs": { + "bypassConfigFormatUpgrades": false, + "requestFormat": { + "uppercase": true, + "delimiter": "-" + }, + "configFormat": { + "uppercase": true, + "delimiter": "-" + }, + "useGlobalFormat": true, + "assetTypes": [ + "futures", + "margin", + "option", + "perpetualswap", + "spot" + ], + "pairs": { + "futures": { + "assetEnabled": true, + "enabled": "BTC-USD-221007,BTC-USD-221014", + "available": "BTC-USD-221230,BTC-USD-230331,ETH-USD-221007,ETH-USD-221014,ETH-USD-221230,ETH-USD-230331,LTC-USD-221007,LTC-USD-221014,LTC-USD-221230,LTC-USD-230331,DOT-USD-221007,DOT-USD-221014,DOT-USD-221230,DOT-USD-230331,FIL-USD-221007,FIL-USD-221014,FIL-USD-221230,FIL-USD-230331,ADA-USD-221007,ADA-USD-221014,ADA-USD-221230,ADA-USD-230331,AVAX-USD-221007,AVAX-USD-221014,AVAX-USD-221230,BTC-USD-221007,BTC-USD-221014,BTC-USD-221230,BTC-USD-230331,ETH-USD-221007,ETH-USD-221014,ETH-USD-221230,ETH-USD-230331,LTC-USD-221007,LTC-USD-221014,LTC-USD-221230,LTC-USD-230331,DOT-USD-221007,DOT-USD-221014,DOT-USD-221230,DOT-USD-230331,FIL-USD-221007,FIL-USD-221014,FIL-USD-221230,FIL-USD-230331,ADA-USD-221007,ADA-USD-221014,ADA-USD-221230,ADA-USD-230331,AVAX-USD-221007,AVAX-USD-221014,AVAX-USD-221230,AVAX-USD-230331,BCH-USD-221007,BCH-USD-221014,BCH-USD-221230,BCH-USD-230331,EOS-USD-221007,EOS-USD-221014,EOS-USD-221230,EOS-USD-230331,ETC-USD-221007,ETC-USD-221014,ETC-USD-221230,ETC-USD-230331,LINK-USD-221007,LINK-USD-221014,LINK-USD-221230,LINK-USD-230331,SOL-USD-221007,SOL-USD-221014,SOL-USD-221230,SOL-USD-230331,TRX-USD-221007,TRX-USD-221014,TRX-USD-221230,TRX-USD-230331,XRP-USD-221007,XRP-USD-221014,XRP-USD-221230,XRP-USD-230331,BTC-USDT-221007,BTC-USDT-221014,BTC-USDT-221230,BTC-USDT-230331,ETH-USDT-221007,ETH-USDT-221014,ETH-USDT-221230,ETH-USDT-230331,LTC-USDT-221007,LTC-USDT-221014,LTC-USDT-221230,LTC-USDT-230331,DOT-USDT-221007,DOT-USDT-221014,DOT-USDT-221230,DOT-USDT-230331,FIL-USDT-221007,FIL-USDT-221014,FIL-USDT-221230,FIL-USDT-230331,ADA-USDT-221007,ADA-USDT-221014,ADA-USDT-221230,ADA-USDT-230331,BCH-USDT-221007,BCH-USDT-221014,BCH-USDT-221230,BCH-USDT-230331,EOS-USDT-221007,EOS-USDT-221014,EOS-USDT-221230,EOS-USDT-230331,ETC-USDT-221007,ETC-USDT-221014,ETC-USDT-221230,ETC-USDT-230331,LINK-USDT-221007,LINK-USDT-221014,LINK-USDT-221230,LINK-USDT-230331,TRX-USDT-221007,TRX-USDT-221014,TRX-USDT-221230,TRX-USDT-230331,XRP-USDT-221007,XRP-USDT-221014,XRP-USDT-221230,XRP-USDT-230331" + }, + "margin": { + "assetEnabled": true, + "enabled": "BTC-USDT,ETH-USDT,OKB-USDT", + "available": "LTC-USDT,DOT-USDT,DOGE-USDT,LUNC-USDT,ETHW-USDT,LUNA-USDT,FIL-USDT,XRP-USDT,1INCH-USDT,AAVE-USDT,ADA-USDT,AGLD-USDT,AKITA-USDT,ALGO-USDT,ALPHA-USDT,BTC-USDT,ETH-USDT,OKB-USDT,LTC-USDT,DOT-USDT,DOGE-USDT,LUNC-USDT,ETHW-USDT,LUNA-USDT,FIL-USDT,XRP-USDT,1INCH-USDT,AAVE-USDT,ADA-USDT,AGLD-USDT,AKITA-USDT,ALGO-USDT,ALPHA-USDT,ANT-USDT,APE-USDT,API3-USDT,ASTR-USDT,ATOM-USDT,AVAX-USDT,AXS-USDT,BABYDOGE-USDT,BADGER-USDT,BAL-USDT,BAND-USDT,BAT-USDT,BCH-USDT,BICO-USDT,BNT-USDT,BSV-USDT,BTM-USDT,BTT-USDT,BZZ-USDT,CELO-USDT,CEL-USDT,CELR-USDT,CFX-USDT,CHZ-USDT,CLV-USDT,COMP-USDT,CONV-USDT,CQT-USDT,CRO-USDT,CRV-USDT,CSPR-USDT,CVC-USDT,DASH-USDT,DOME-USDT,DORA-USDT,DYDX-USDT,EFI-USDT,EGLD-USDT,ELF-USDT,ENJ-USDT,ENS-USDT,EOS-USDT,ETC-USDT,FITFI-USDT,FLM-USDT,FLOW-USDT,FTM-USDT,GALA-USDT,GLMR-USDT,GMT-USDT,GODS-USDT,GRT-USDT,HBAR-USDT,HC-USDT,ICP-USDT,IMX-USDT,IOST-USDT,IOTA-USDT,JST-USDT,KAR-USDT,KISHU-USDT,KNC-USDT,KSM-USDT,LAMB-USDT,LAT-USDT,LINK-USDT,LON-USDT,LOOKS-USDT,LPT-USDT,LRC-USDT,MANA-USDT,MASK-USDT,MATIC-USDT,MINA-USDT,MKR-USDT,NAS-USDT,NEAR-USDT,NEO-USDT,NFT-USDT,NYM-USDT,OMG-USDT,ONT-USDT,OP-USDT,PEOPLE-USDT,PERP-USDT,QTUM-USDT,REN-USDT,RSR-USDT,RSS3-USDT,RVN-USDT,SAND-USDT,SC-USDT,SHIB-USDT,SKL-USDT,SLP-USDT,SNT-USDT,SNX-USDT,SOL-USDT,SOS-USDT,SRM-USDT,STARL-USDT,STORJ-USDT,SUSHI-USDT,SWEAT-USDT,SWRV-USDT,THETA-USDT,TORN-USDT,TRB-USDT,TRX-USDT,UMA-USDT,UMEE-USDT,UNI-USDT,USDC-USDT,VSYS-USDT,WAVES-USDT,WNCG-USDT,WNXM-USDT,XCH-USDT,XEM-USDT,XLM-USDT,XMR-USDT,XTZ-USDT,YFI-USDT,YFII-USDT,YGG-USDT,ZEC-USDT,ZEN-USDT,ZIL-USDT,ZRX-USDT,BTC-USDC,ETH-BTC,LTC-USDC,DOT-USDC,DOGE-USDC,LUNC-USDC,LUNA-USDC,XRP-USDC,ADA-USDC,ATOM-USDC,AVAX-USDC,NEAR-USDC,OP-USDC,SOL-USDC,OKB-BTC,LTC-BTC,DOT-BTC,DOGE-BTC,FIL-BTC,XRP-BTC,AAVE-BTC,ADA-BTC,ALGO-BTC,ANT-BTC,ATOM-BTC,AVAX-BTC,BADGER-BTC,BAT-BTC,BCH-BTC,BNT-BTC,BSV-BTC,BTM-BTC,CHZ-BTC,COMP-BTC,CRO-BTC,CRV-BTC,CVC-BTC,DASH-BTC,EGLD-BTC,ELF-BTC,ENJ-BTC,EOS-BTC,ETC-BTC,GRT-BTC,HBAR-BTC,HC-BTC,ICP-BTC,IOST-BTC,IOTA-BTC,KNC-BTC,KSM-BTC,LINK-BTC,LRC-BTC,MANA-BTC,MKR-BTC,NAS-BTC,NEAR-BTC,NEO-BTC,OMG-BTC,ONT-BTC,QTUM-BTC,REN-BTC,RSR-BTC,RVN-BTC,SNT-BTC,SOL-BTC,SRM-BTC,THETA-BTC,TRX-BTC,UNI-BTC,VSYS-BTC,WAVES-BTC,XCH-BTC,XEM-BTC,XLM-BTC,XMR-BTC,XTZ-BTC,ZEC-BTC,ZIL-BTC,ZRX-BTC" + }, + "option": { + "assetEnabled": true, + "enabled": "BTC-USD-220930-28000-P,BTC-USD-220930-30000-C", + "available": "BTC-USD-220930-30000-P,BTC-USD-220930-32000-C,BTC-USD-220930-32000-P,BTC-USD-220930-34000-C,BTC-USD-220930-34000-P,BTC-USD-220930-35000-C,BTC-USD-220930-35000-P,BTC-USD-220930-36000-C,BTC-USD-220930-36000-P,BTC-USD-220930-40000-C,BTC-USD-220930-40000-P,BTC-USD-220930-45000-C,BTC-USD-220930-45000-P,BTC-USD-220930-50000-C,BTC-USD-220930-50000-P,BTC-USD-220930-55000-C,BTC-USD-220930-55000-P,BTC-USD-220930-60000-C,BTC-USD-220930-60000-P,BTC-USD-220930-65000-C,BTC-USD-220930-65000-P,BTC-USD-220930-70000-C,BTC-USD-220930-70000-P,BTC-USD-220930-80000-C,BTC-USD-220930-80000-P,BTC-USD-220930-90000-C,BTC-USD-220930-90000-P,BTC-USD-220930-100000-C,BTC-USD-220930-100000-P,BTC-USD-220930-120000-C,BTC-USD-220930-120000-P,BTC-USD-221007-13000-C,BTC-USD-221007-13000-P,BTC-USD-221007-14000-C,BTC-USD-221007-14000-P,BTC-USD-221007-15000-C,BTC-USD-221007-15000-P,BTC-USD-221007-16000-C,BTC-USD-221007-16000-P,BTC-USD-221007-17000-C,BTC-USD-221007-17000-P,BTC-USD-221007-18000-C,BTC-USD-221007-18000-P,BTC-USD-221007-18500-C,BTC-USD-221007-18500-P,BTC-USD-221007-19000-C,BTC-USD-221007-19000-P,BTC-USD-221007-20000-C,BTC-USD-221007-20000-P,BTC-USD-221007-21000-C,BTC-USD-221007-21000-P,BTC-USD-221007-22000-C,BTC-USD-221007-22000-P,BTC-USD-221007-23000-C,BTC-USD-221007-23000-P,BTC-USD-221007-24000-C,BTC-USD-221007-24000-P,BTC-USD-221007-26000-C,BTC-USD-221007-26000-P,BTC-USD-221007-28000-C,BTC-USD-221007-28000-P,BTC-USD-221007-30000-C,BTC-USD-221007-30000-P,BTC-USD-221014-14000-C,BTC-USD-221014-14000-P,BTC-USD-221014-15000-C,BTC-USD-221014-15000-P,BTC-USD-221014-16000-C,BTC-USD-221014-16000-P,BTC-USD-221014-17000-C,BTC-USD-221014-17000-P,BTC-USD-221014-18000-C,BTC-USD-221014-18000-P,BTC-USD-221014-19000-C,BTC-USD-221014-19000-P,BTC-USD-221014-20000-C,BTC-USD-221014-20000-P,BTC-USD-221014-21000-C,BTC-USD-221014-21000-P,BTC-USD-221014-22000-C,BTC-USD-221014-22000-P,BTC-USD-221014-23000-C,BTC-USD-221014-23000-P,BTC-USD-221014-24000-C,BTC-USD-221014-24000-P,BTC-USD-221014-26000-C,BTC-USD-221014-26000-P,BTC-USD-221014-28000-C,BTC-USD-221014-28000-P,BTC-USD-221014-30000-C,BTC-USD-221014-30000-P,BTC-USD-221028-10000-C,BTC-USD-221028-10000-P,BTC-USD-221028-12000-C,BTC-USD-221028-12000-P,BTC-USD-221028-14000-C,BTC-USD-221028-14000-P,BTC-USD-221028-16000-C,BTC-USD-221028-16000-P,BTC-USD-221028-17000-C,BTC-USD-221028-17000-P,BTC-USD-221028-18000-C,BTC-USD-221028-18000-P,BTC-USD-221028-19000-C,BTC-USD-221028-19000-P,BTC-USD-221028-20000-C,BTC-USD-221028-20000-P,BTC-USD-221028-21000-C,BTC-USD-221028-21000-P,BTC-USD-221028-22000-C,BTC-USD-221028-22000-P,BTC-USD-221028-24000-C,BTC-USD-221028-24000-P,BTC-USD-221028-26000-C,BTC-USD-221028-26000-P,BTC-USD-221028-28000-C,BTC-USD-221028-28000-P,BTC-USD-221028-30000-C,BTC-USD-221028-30000-P,BTC-USD-221028-32000-C,BTC-USD-221028-32000-P,BTC-USD-221028-35000-C,BTC-USD-220927-17000-C,BTC-USD-220927-17000-P,BTC-USD-220927-17500-C,BTC-USD-220927-17500-P,BTC-USD-220927-18000-C,BTC-USD-220927-18000-P,BTC-USD-220927-18200-C,BTC-USD-220927-18200-P,BTC-USD-220927-18400-C,BTC-USD-220927-18400-P,BTC-USD-220927-18600-C,BTC-USD-220927-18600-P,BTC-USD-220927-18800-C,BTC-USD-220927-18800-P,BTC-USD-220927-19000-C,BTC-USD-220927-19000-P,BTC-USD-220927-19200-C,BTC-USD-220927-19200-P,BTC-USD-220927-19400-C,BTC-USD-220927-19400-P,BTC-USD-220927-19600-C,BTC-USD-220927-19600-P,BTC-USD-220927-19800-C,BTC-USD-220927-19800-P,BTC-USD-220927-20000-C,BTC-USD-220927-20000-P,BTC-USD-220927-20500-C,BTC-USD-220927-20500-P,BTC-USD-220927-21000-C,BTC-USD-220927-21000-P,BTC-USD-220927-21500-C,BTC-USD-220927-21500-P,BTC-USD-220928-16500-C,BTC-USD-220928-16500-P,BTC-USD-220928-17000-C,BTC-USD-220928-17000-P,BTC-USD-220928-17500-C,BTC-USD-220928-17500-P,BTC-USD-220928-18000-C,BTC-USD-220928-18000-P,BTC-USD-220928-18200-C,BTC-USD-220928-18200-P,BTC-USD-220928-18400-C,BTC-USD-220928-18400-P,BTC-USD-220928-18600-C,BTC-USD-220928-18600-P,BTC-USD-220928-18800-C,BTC-USD-220928-18800-P,BTC-USD-220928-19000-C,BTC-USD-220928-19000-P,BTC-USD-220928-19200-C,BTC-USD-220928-19200-P,BTC-USD-220928-19400-C,BTC-USD-220928-19400-P,BTC-USD-220928-19600-C,BTC-USD-220928-19600-P,BTC-USD-220928-20000-C,BTC-USD-220928-20000-P,BTC-USD-220928-20500-C,BTC-USD-220928-20500-P,BTC-USD-220928-21000-C,BTC-USD-220928-21000-P,BTC-USD-220928-21500-C,BTC-USD-220928-21500-P,BTC-USD-220930-5000-C,BTC-USD-220930-5000-P,BTC-USD-220930-10000-C,BTC-USD-220930-10000-P,BTC-USD-220930-12000-C,BTC-USD-220930-12000-P,BTC-USD-220930-15000-C,BTC-USD-220930-15000-P,BTC-USD-220930-16000-C,BTC-USD-220930-16000-P,BTC-USD-220930-17000-C,BTC-USD-220930-17000-P,BTC-USD-220930-17500-C,BTC-USD-220930-17500-P,BTC-USD-220930-18000-C,BTC-USD-220930-18000-P,BTC-USD-220930-18500-C,BTC-USD-220930-18500-P,BTC-USD-220930-19000-C,BTC-USD-220930-19000-P,BTC-USD-220930-19500-C,BTC-USD-220930-19500-P,BTC-USD-220930-20000-C,BTC-USD-220930-20000-P,BTC-USD-220930-20500-C,BTC-USD-220930-20500-P,BTC-USD-220930-21000-C,BTC-USD-220930-21000-P,BTC-USD-220930-22000-C,BTC-USD-220930-22000-P,BTC-USD-220930-23000-C,BTC-USD-220930-23000-P,BTC-USD-220930-24000-C,BTC-USD-220930-24000-P,BTC-USD-220930-25000-C,BTC-USD-220930-25000-P,BTC-USD-220930-26000-C,BTC-USD-220930-26000-P,BTC-USD-220930-28000-C,BTC-USD-220930-28000-P,BTC-USD-220930-30000-C,BTC-USD-220930-30000-P,BTC-USD-220930-32000-C,BTC-USD-220930-32000-P,BTC-USD-220930-34000-C,BTC-USD-220930-34000-P,BTC-USD-220930-35000-C,BTC-USD-220930-35000-P,BTC-USD-220930-36000-C,BTC-USD-220930-36000-P,BTC-USD-220930-40000-C,BTC-USD-220930-40000-P,BTC-USD-220930-45000-C,BTC-USD-220930-45000-P,BTC-USD-220930-50000-C,BTC-USD-220930-50000-P,BTC-USD-220930-55000-C,BTC-USD-220930-55000-P,BTC-USD-220930-60000-C,BTC-USD-220930-60000-P,BTC-USD-220930-65000-C,BTC-USD-220930-65000-P,BTC-USD-220930-70000-C,BTC-USD-220930-70000-P,BTC-USD-220930-80000-C,BTC-USD-220930-80000-P,BTC-USD-220930-90000-C,BTC-USD-220930-90000-P,BTC-USD-220930-100000-C,BTC-USD-220930-100000-P,BTC-USD-220930-120000-C,BTC-USD-220930-120000-P,BTC-USD-221007-13000-C,BTC-USD-221007-13000-P,BTC-USD-221007-14000-C,BTC-USD-221007-14000-P,BTC-USD-221007-15000-C,BTC-USD-221007-15000-P,BTC-USD-221007-16000-C,BTC-USD-221007-16000-P,BTC-USD-221007-17000-C,BTC-USD-221007-17000-P,BTC-USD-221007-18000-C,BTC-USD-221007-18000-P,BTC-USD-221007-18500-C,BTC-USD-221007-18500-P,BTC-USD-221007-19000-C,BTC-USD-221007-19000-P,BTC-USD-221007-20000-C,BTC-USD-221007-20000-P,BTC-USD-221007-21000-C,BTC-USD-221007-21000-P,BTC-USD-221007-22000-C,BTC-USD-221007-22000-P,BTC-USD-221007-23000-C,BTC-USD-221007-23000-P,BTC-USD-221007-24000-C,BTC-USD-221007-24000-P,BTC-USD-221007-26000-C,BTC-USD-221007-26000-P,BTC-USD-221007-28000-C,BTC-USD-221007-28000-P,BTC-USD-221007-30000-C,BTC-USD-221007-30000-P,BTC-USD-221014-14000-C,BTC-USD-221014-14000-P,BTC-USD-221014-15000-C,BTC-USD-221014-15000-P,BTC-USD-221014-16000-C,BTC-USD-221014-16000-P,BTC-USD-221014-17000-C,BTC-USD-221014-17000-P,BTC-USD-221014-18000-C,BTC-USD-221014-18000-P,BTC-USD-221014-19000-C,BTC-USD-221014-19000-P,BTC-USD-221014-20000-C,BTC-USD-221014-20000-P,BTC-USD-221014-21000-C,BTC-USD-221014-21000-P,BTC-USD-221014-22000-C,BTC-USD-221014-22000-P,BTC-USD-221014-23000-C,BTC-USD-221014-23000-P,BTC-USD-221014-24000-C,BTC-USD-221014-24000-P,BTC-USD-221014-26000-C,BTC-USD-221014-26000-P,BTC-USD-221014-28000-C,BTC-USD-221014-28000-P,BTC-USD-221014-30000-C,BTC-USD-221014-30000-P,BTC-USD-221028-10000-C,BTC-USD-221028-10000-P,BTC-USD-221028-12000-C,BTC-USD-221028-12000-P,BTC-USD-221028-14000-C,BTC-USD-221028-14000-P,BTC-USD-221028-16000-C,BTC-USD-221028-16000-P,BTC-USD-221028-17000-C,BTC-USD-221028-17000-P,BTC-USD-221028-18000-C,BTC-USD-221028-18000-P,BTC-USD-221028-19000-C,BTC-USD-221028-19000-P,BTC-USD-221028-20000-C,BTC-USD-221028-20000-P,BTC-USD-221028-21000-C,BTC-USD-221028-21000-P,BTC-USD-221028-22000-C,BTC-USD-221028-22000-P,BTC-USD-221028-24000-C,BTC-USD-221028-24000-P,BTC-USD-221028-26000-C,BTC-USD-221028-26000-P,BTC-USD-221028-28000-C,BTC-USD-221028-28000-P,BTC-USD-221028-30000-C,BTC-USD-221028-30000-P,BTC-USD-221028-32000-C,BTC-USD-221028-32000-P,BTC-USD-221028-35000-C,BTC-USD-221028-35000-P,BTC-USD-221028-40000-C,BTC-USD-221028-40000-P,BTC-USD-221028-50000-C,BTC-USD-221028-50000-P,BTC-USD-221028-60000-C,BTC-USD-221028-60000-P,BTC-USD-221028-70000-C,BTC-USD-221028-70000-P,BTC-USD-221125-5000-C,BTC-USD-221125-5000-P,BTC-USD-221125-10000-C,BTC-USD-221125-10000-P,BTC-USD-221125-12000-C,BTC-USD-221125-12000-P,BTC-USD-221125-15000-C,BTC-USD-221125-15000-P,BTC-USD-221125-16000-C,BTC-USD-221125-16000-P,BTC-USD-221125-17000-C,BTC-USD-221125-17000-P,BTC-USD-221125-18000-C,BTC-USD-221125-18000-P,BTC-USD-221125-20000-C,BTC-USD-221125-20000-P,BTC-USD-221125-22000-C,BTC-USD-221125-22000-P,BTC-USD-221125-24000-C,BTC-USD-221125-24000-P,BTC-USD-221125-26000-C,BTC-USD-221125-26000-P,BTC-USD-221125-28000-C,BTC-USD-221125-28000-P,BTC-USD-221125-30000-C,BTC-USD-221125-30000-P,BTC-USD-221125-32000-C,BTC-USD-221125-32000-P,BTC-USD-221125-35000-C,BTC-USD-221125-35000-P,BTC-USD-221125-40000-C,BTC-USD-221125-40000-P,BTC-USD-221125-50000-C,BTC-USD-221125-50000-P,BTC-USD-221125-60000-C,BTC-USD-221125-60000-P,BTC-USD-221125-70000-C,BTC-USD-221125-70000-P,BTC-USD-221230-5000-C,BTC-USD-221230-5000-P,BTC-USD-221230-10000-C,BTC-USD-221230-10000-P,BTC-USD-221230-12000-C,BTC-USD-221230-12000-P,BTC-USD-221230-13000-C,BTC-USD-221230-13000-P,BTC-USD-221230-15000-C,BTC-USD-221230-15000-P,BTC-USD-221230-16000-C,BTC-USD-221230-16000-P,BTC-USD-221230-17000-C,BTC-USD-221230-17000-P,BTC-USD-221230-18000-C,BTC-USD-221230-18000-P,BTC-USD-221230-19000-C,BTC-USD-221230-19000-P,BTC-USD-221230-20000-C,BTC-USD-221230-20000-P,BTC-USD-221230-21000-C,BTC-USD-221230-21000-P,BTC-USD-221230-22000-C,BTC-USD-221230-22000-P,BTC-USD-221230-23000-C,BTC-USD-221230-23000-P,BTC-USD-221230-24000-C,BTC-USD-221230-24000-P,BTC-USD-221230-25000-C,BTC-USD-221230-25000-P,BTC-USD-221230-26000-C,BTC-USD-221230-26000-P,BTC-USD-221230-28000-C,BTC-USD-221230-28000-P,BTC-USD-221230-30000-C,BTC-USD-221230-30000-P,BTC-USD-221230-32000-C,BTC-USD-221230-32000-P,BTC-USD-221230-35000-C,BTC-USD-221230-35000-P" + }, + "perpetualswap": { + "assetEnabled": true, + "enabled": "BTC-USD-SWAP,ETH-USD-SWAP", + "available": "LTC-USD-SWAP,DOT-USD-SWAP,DOGE-USD-SWAP,MASK-USDT-SWAP,MATIC-USDT-SWAP,MINA-USDT-SWAP,MKR-USDT-SWAP,NEAR-USDT-SWAP,NEO-USDT-SWAP,NFT-USDT-SWAP,NYM-USDT-SWAP,OMG-USDT-SWAP,ONT-USDT-SWAP,OP-USDT-SWAP,PEOPLE-USDT-SWAP,BTC-USD-SWAP,ETH-USD-SWAP,LTC-USD-SWAP,DOT-USD-SWAP,DOGE-USD-SWAP,FIL-USD-SWAP,XRP-USD-SWAP,1INCH-USD-SWAP,ADA-USD-SWAP,ALGO-USD-SWAP,ATOM-USD-SWAP,AVAX-USD-SWAP,BCH-USD-SWAP,BSV-USD-SWAP,CRV-USD-SWAP,DASH-USD-SWAP,EOS-USD-SWAP,ETC-USD-SWAP,GRT-USD-SWAP,IOST-USD-SWAP,IOTA-USD-SWAP,KNC-USD-SWAP,KSM-USD-SWAP,LINK-USD-SWAP,MANA-USD-SWAP,NEO-USD-SWAP,ONT-USD-SWAP,QTUM-USD-SWAP,SAND-USD-SWAP,SOL-USD-SWAP,SUSHI-USD-SWAP,THETA-USD-SWAP,TRX-USD-SWAP,UNI-USD-SWAP,XLM-USD-SWAP,XMR-USD-SWAP,XTZ-USD-SWAP,YFI-USD-SWAP,YFII-USD-SWAP,ZEC-USD-SWAP,BTC-USDT-SWAP,ETH-USDT-SWAP,LTC-USDT-SWAP,DOT-USDT-SWAP,DOGE-USDT-SWAP,LUNC-USDT-SWAP,ETHW-USDT-SWAP,LUNA-USDT-SWAP,FIL-USDT-SWAP,XRP-USDT-SWAP,1INCH-USDT-SWAP,AAVE-USDT-SWAP,ADA-USDT-SWAP,AGLD-USDT-SWAP,ALGO-USDT-SWAP,ALPHA-USDT-SWAP,ANT-USDT-SWAP,APE-USDT-SWAP,API3-USDT-SWAP,ASTR-USDT-SWAP,ATOM-USDT-SWAP,AVAX-USDT-SWAP,AXS-USDT-SWAP,BABYDOGE-USDT-SWAP,BADGER-USDT-SWAP,BAL-USDT-SWAP,BAND-USDT-SWAP,BAT-USDT-SWAP,BCH-USDT-SWAP,BICO-USDT-SWAP,BNT-USDT-SWAP,BSV-USDT-SWAP,BTT-USDT-SWAP,CELO-USDT-SWAP,CEL-USDT-SWAP,CFX-USDT-SWAP,CHZ-USDT-SWAP,COMP-USDT-SWAP,CRO-USDT-SWAP,CRV-USDT-SWAP,CSPR-USDT-SWAP,CVC-USDT-SWAP,DASH-USDT-SWAP,DOME-USDT-SWAP,DORA-USDT-SWAP,DYDX-USDT-SWAP,EGLD-USDT-SWAP,ELON-USDT-SWAP,ENJ-USDT-SWAP,ENS-USDT-SWAP,EOS-USDT-SWAP,ETC-USDT-SWAP,FITFI-USDT-SWAP,FLM-USDT-SWAP,FTM-USDT-SWAP,GALA-USDT-SWAP,GMT-USDT-SWAP,GODS-USDT-SWAP,GRT-USDT-SWAP,ICP-USDT-SWAP,IMX-USDT-SWAP,IOST-USDT-SWAP,IOTA-USDT-SWAP,JST-USDT-SWAP,KISHU-USDT-SWAP,KNC-USDT-SWAP,KSM-USDT-SWAP,LINK-USDT-SWAP,LOOKS-USDT-SWAP,LPT-USDT-SWAP,LRC-USDT-SWAP,MANA-USDT-SWAP,MASK-USDT-SWAP,MATIC-USDT-SWAP,MINA-USDT-SWAP,MKR-USDT-SWAP,NEAR-USDT-SWAP,NEO-USDT-SWAP,NFT-USDT-SWAP,NYM-USDT-SWAP,OMG-USDT-SWAP,ONT-USDT-SWAP,OP-USDT-SWAP,PEOPLE-USDT-SWAP,PERP-USDT-SWAP,QTUM-USDT-SWAP,REN-USDT-SWAP,RSR-USDT-SWAP,RVN-USDT-SWAP,SAND-USDT-SWAP,SC-USDT-SWAP,SHIB-USDT-SWAP,SLP-USDT-SWAP,SNX-USDT-SWAP,SOL-USDT-SWAP,SOS-USDT-SWAP,SRM-USDT-SWAP,STARL-USDT-SWAP,STORJ-USDT-SWAP,SUSHI-USDT-SWAP,SWEAT-USDT-SWAP,THETA-USDT-SWAP,TRB-USDT-SWAP,TRX-USDT-SWAP,UMA-USDT-SWAP,UMEE-USDT-SWAP,UNI-USDT-SWAP,WAVES-USDT-SWAP,WNXM-USDT-SWAP,XCH-USDT-SWAP,XEM-USDT-SWAP,XLM-USDT-SWAP,XMR-USDT-SWAP,XTZ-USDT-SWAP,YFI-USDT-SWAP,YFII-USDT-SWAP,YGG-USDT-SWAP,ZEC-USDT-SWAP,ZEN-USDT-SWAP,ZIL-USDT-SWAP,ZRX-USDT-SWAP" + }, + "spot": { + "assetEnabled": true, + "enabled": "BTC-USDT,ETH-USDT,OKB-USDT", + "available": "OKT-USDT,LTC-USDT,DOT-USDT,DOGE-USDT,LUNC-USDT,ETHW-USDT,LUNA-USDT,FIL-USDT,XRP-USDT,CITY-USDT,MENGO-USDT,ARG-USDT,POR-USDT,1INCH-USDT,AAVE-USDT,ABT-USDT,ACA-USDT,ADA-USDT,AERGO-USDT,AGLD-USDT,XMR-ETH,YFI-ETH,ZEC-ETH,LTC-OKB,XRP-OKB,ETC-OKB,BTC-USDT,ETH-USDT,OKB-USDT,OKT-USDT,LTC-USDT,DOT-USDT,DOGE-USDT,LUNC-USDT,ETHW-USDT,LUNA-USDT,FIL-USDT,XRP-USDT,CITY-USDT,MENGO-USDT,ARG-USDT,POR-USDT,1INCH-USDT,AAVE-USDT,ABT-USDT,ACA-USDT,ADA-USDT,AERGO-USDT,AGLD-USDT,AKITA-USDT,ALCX-USDT,ALGO-USDT,ALPHA-USDT,ANC-USDT,ANT-USDT,ANW-USDT,APE-USDT,APIX-USDT,API3-USDT,APM-USDT,AR-USDT,ARK-USDT,AST-USDT,ASTR-USDT,ATOM-USDT,AUCTION-USDT,AVAX-USDT,AXS-USDT,AZY-USDT,BABYDOGE-USDT,BADGER-USDT,BAL-USDT,BAND-USDT,BAT-USDT,BCD-USDT,BCH-USDT,BETH-USDT,BHP-USDT,BICO-USDT,BLOK-USDT,BNT-USDT,BORING-USDT,BORA-USDT,BRWL-USDT,BSV-USDT,BTG-USDT,BTM-USDT,BTT-USDT,BZZ-USDT,CELO-USDT,CEL-USDT,CELR-USDT,CELT-USDT,CFG-USDT,CFX-USDT,CGS-USDT,CHAT-USDT,CHE-USDT,CHZ-USDT,CLV-USDT,CMT-USDT,CNTM-USDT,COMP-USDT,CONV-USDT,COVER-USDT,CQT-USDT,CRO-USDT,CRV-USDT,CSPR-USDT,CTC-USDT,CTXC-USDT,CVC-USDT,CVP-USDT,CVT-USDT,CVX-USDT,DAI-USDT,DAO-USDT,DASH-USDT,DCR-USDT,DEP-USDT,DEVT-USDT,DGB-USDT,DHT-USDT,DIA-USDT,DMD-USDT,DNA-USDT,DOME-USDT,DORA-USDT,DOSE-USDT,DYDX-USDT,EC-USDT,EDEN-USDT,EFI-USDT,EGLD-USDT,EGT-USDT,ELF-USDT,ELON-USDT,ELT-USDT,EM-USDT,ENJ-USDT,ENS-USDT,EOS-USDT,ERN-USDT,ETC-USDT,EURT-USDT,FAIR-USDT,FAME-USDT,FITFI-USDT,FLM-USDT,FLOW-USDT,FODL-USDT,FORTH-USDT,FRONT-USDT,FSN-USDT,FTM-USDT,GALA-USDT,GALFT-USDT,GARI-USDT,GAS-USDT,GF-USDT,GHST-USDT,GLM-USDT,GLMR-USDT,GM-USDT,GMT-USDT,GODS-USDT,GOG-USDT,GRT-USDT,GTO-USDT,GUSD-USDT,HBAR-USDT,HC-USDT,HDAO-USDT,HEGIC-USDT,HYC-USDT,ICP-USDT,ICX-USDT,ILV-USDT,IMX-USDT,INT-USDT,INX-USDT,IOST-USDT,IOTA-USDT,IQ-USDT,JFI-USDT,JOE-USDT,JST-USDT,KAN-USDT,KAR-USDT,KCASH-USDT,KDA-USDT,KINE-USDT,KISHU-USDT,KLAY-USDT,KNC-USDT,KOL-USDT,KONO-USDT,KP3R-USDT,KSM-USDT,LAMB-USDT,LAT-USDT,LBA-USDT,LDN-USDT,LDO-USDT,LEASH-USDT,LEO-USDT,LET-USDT,LINK-USDT,LING-USDT,LITH-USDT,LON-USDT,LOON-USDT,LOOKS-USDT,LPT-USDT,LRC-USDT,LSK-USDT,MAGIC-USDT,MANA-USDT,MASK-USDT,MATIC-USDT,MCO-USDT,MDA-USDT,MDT-USDT,MEME-USDT,METIS-USDT,MILO-USDT,MINA-USDT,MIR-USDT,MITH-USDT,MKR-USDT,MLN-USDT,MOF-USDT,MON-USDT,MOVR-USDT,MOVEZ-USDT,MXC-USDT,MXT-USDT,NAS-USDT,NEAR-USDT,NEO-USDT,NFT-USDT,NMR-USDT,NULS-USDT,NYM-USDT,OM-USDT,OMG-USDT,OMI-USDT,ONE-USDT,ONT-USDT,OP-USDT,ORBS-USDT,ORB-USDT,ORS-USDT,OXT-USDT,PAY-USDT,PCI-USDT,PEOPLE-USDT,PERP-USDT,PHA-USDT,PICKLE-USDT,PIT-USDT,PLG-USDT,PNK-USDT,POLS-USDT,POLYDOGE-USDT,PPT-USDT,PRQ-USDT,PST-USDT,PSTAKE-USDT,QOM-USDT,QTUM-USDT,RACA-USDT,RAY-USDT,REN-USDT,REP-USDT,REVV-USDT,RFUEL-USDT,RIO-USDT,RNT-USDT,ROAD-USDT,RON-USDT,RSR-USDT,RSS3-USDT,RVN-USDT,SAITAMA-USDT,SAMO-USDT,SAND-USDT,SC-USDT,SD-USDT,SFG-USDT,SHIB-USDT,SIS-USDT,SKEB-USDT,SKL-USDT,SLP-USDT,SNT-USDT,SNX-USDT,SOC-USDT,SOL-USDT,SOS-USDT,SPELL-USDT,SRM-USDT,STARL-USDT,STC-USDT,STORJ-USDT,STRK-USDT,STX-USDT,SUN-USDT,SUSHI-USDT,SWEAT-USDT,SWFTC-USDT,SWRV-USDT,T-USDT,TAI-USDT,TAKI-USDT,TCT-USDT,THETA-USDT,THG-USDT,TON-USDT,TOPC-USDT,TORN-USDT,TOWN-USDT,TRADE-USDT,TRA-USDT,TRB-USDT,TRUE-USDT,TRX-USDT,TUP-USDT,TUSD-USDT,UMA-USDT,UMEE-USDT,UNI-USDT,USDC-USDT,USDP-USDT,USTC-USDT,UTK-USDT,VALUE-USDT,VELO-USDT,VRA-USDT,VSYS-USDT,WAVES-USDT,WAXP-USDT,WBTC-USDT,WEMIX-USDT,WGRT-USDT,WING-USDT,WIN-USDT,WNCG-USDT,WNXM-USDT,WOO-USDT,WSB-USDT,WXT-USDT,XAUT-USDT,XCH-USDT,XEC-USDT,XEM-USDT,XETA-USDT,XLM-USDT,XMR-USDT,XNO-USDT,XPR-USDT,XTZ-USDT,YEE-USDT,YFI-USDT,YFII-USDT,YGG-USDT,YOU-USDT,YOYO-USDT,ZBC-USDT,ZEC-USDT,ZEN-USDT,ZIL-USDT,ZKS-USDT,ZRX-USDT,ZYRO-USDT,BTC-USDC,ETH-USDC,ETH-BTC,OKB-USDC,OKT-USDC,LTC-USDC,DOT-USDC,DOGE-USDC,LUNC-USDC,ETHW-USDC,LUNA-USDC,FIL-USDC,XRP-USDC,1INCH-USDC,AAVE-USDC,ADA-USDC,AGLD-USDC,ALGO-USDC,ANC-USDC,ANT-USDC,APE-USDC,API3-USDC,AR-USDC,ASTR-USDC,ATOM-USDC,AVAX-USDC,AXS-USDC,AZY-USDC,BABYDOGE-USDC,BAT-USDC,BCH-USDC,BICO-USDC,BSV-USDC,CEL-USDC,CELO-USDC,CELT-USDC,CHZ-USDC,COMP-USDC,CRO-USDC,CRV-USDC,CSPR-USDC,DASH-USDC,DEP-USDC,DOME-USDC,DYDX-USDC,EGLD-USDC,ELT-USDC,ENS-USDC,EOS-USDC,ETC-USDC,FITFI-USDC,FLM-USDC,FLOW-USDC,FTM-USDC,GALA-USDC,GALFT-USDC,GARI-USDC,GLMR-USDC,GMT-USDC,GODS-USDC,GRT-USDC,HBAR-USDC,ICP-USDC,IMX-USDC,IOST-USDC,JST-USDC,KISHU-USDC,KLAY-USDC,KNC-USDC,KSM-USDC,LINK-USDC,LOOKS-USDC,LRC-USDC,MANA-USDC,MASK-USDC,MATIC-USDC,MINA-USDC,MKR-USDC,MOF-USDC,MOVEZ-USDC,MXC-USDC,NEAR-USDC,NFT-USDC,NMR-USDC,NYM-USDC,OMG-USDC,OP-USDC,PEOPLE-USDC,PERP-USDC,RACA-USDC,RSR-USDC,SAITAMA-USDC,SAND-USDC,SHIB-USDC,SLP-USDC,SNX-USDC,SOC-USDC,SOL-USDC,SOS-USDC,SRM-USDC,STARL-USDC,STC-USDC,STORJ-USDC,STX-USDC,SUSHI-USDC,SWFTC-USDC,THETA-USDC,TON-USDC,TORN-USDC,TRB-USDC,TRX-USDC,UNI-USDC,USDP-USDC,USTC-USDC,VRA-USDC,WAVES-USDC,XCH-USDC,XEM-USDC,XLM-USDC,XMR-USDC,XNO-USDC,XTZ-USDC,YFI-USDC,YFII-USDC,YGG-USDC,ZEC-USDC,ZIL-USDC,BTC-DAI,ETH-DAI,BTC-USDK,ETH-USDK,USDT-USDK,OKB-BTC,OKT-BTC,LTC-BTC,DOT-BTC,DOGE-BTC,FIL-BTC,XRP-BTC,AAVE-BTC,ADA-BTC,ALGO-BTC,ALPHA-BTC,ANT-BTC,ATOM-BTC,AVAX-BTC,BADGER-BTC,BAT-BTC,BCD-BTC,BCH-BTC,BNT-BTC,BSV-BTC,BTG-BTC,BTM-BTC,CELO-BTC,CELT-BTC,CHZ-BTC,COMP-BTC,CQT-BTC,CRO-BTC,CRV-BTC,CTC-BTC,CVC-BTC,DASH-BTC,DCR-BTC,DGB-BTC,EGLD-BTC,ELF-BTC,ENJ-BTC,EOS-BTC,ETC-BTC,FLOW-BTC,GAS-BTC,GRT-BTC,GTO-BTC,HBAR-BTC,HC-BTC,ICP-BTC,ICX-BTC,INT-BTC,IOST-BTC,IOTA-BTC,KLAY-BTC,KNC-BTC,KSM-BTC,LINK-BTC,LRC-BTC,LSK-BTC,MANA-BTC,MITH-BTC,MKR-BTC,NAS-BTC,NEAR-BTC,NEO-BTC,NULS-BTC,OMG-BTC,ONT-BTC,PST-BTC,QTUM-BTC,REN-BTC,RSR-BTC,RVN-BTC,SC-BTC,SNT-BTC,SOL-BTC,SRM-BTC,STX-BTC,SWFTC-BTC,THETA-BTC,TRUE-BTC,TRX-BTC,UNI-BTC,VSYS-BTC,WAVES-BTC,WBTC-BTC,WXT-BTC,XCH-BTC,XEM-BTC,XLM-BTC,XMR-BTC,XTZ-BTC,YFI-BTC,YOU-BTC,ZEC-BTC,ZEN-BTC,ZIL-BTC,ZRX-BTC,OKB-ETH,OKT-ETH,LTC-ETH,DOT-ETH,DOGE-ETH,FIL-ETH,XRP-ETH,AAVE-ETH,ADA-ETH,API3-ETH,ATOM-ETH,AVAX-ETH,BETH-ETH,CRV-ETH,DASH-ETH,EOS-ETH,ETC-ETH,FLOW-ETH,GAS-ETH,GHST-ETH,HEGIC-ETH,INT-ETH,IOST-ETH,KSM-ETH,LINK-ETH,MANA-ETH,MKR-ETH,NEAR-ETH,NEO-ETH,NULS-ETH,OM-ETH,QTUM-ETH,SNX-ETH,SOL-ETH,SUSHI-ETH,SWFTC-ETH,TRX-ETH,UNI-ETH,WBTC-ETH,XLM-ETH,XMR-ETH,YFI-ETH,ZEC-ETH,LTC-OKB,XRP-OKB,ETC-OKB,OKDOT1-DOT,OKDOT2-DOT,BTC-EURT,ETH-EURT" + } + } + }, + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "credentials": { + "key": "", + "secret": "", + "clientID": "" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true, + "requiresClientID": true + }, + "urlEndpoints": { + "RestSpotURL": "https://www.okx.com/api/v5/", + "WebsocketSpotURL": "wss://ws.okx.com:8443/ws/v5/public" + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "autoPairUpdates": true + }, + "websocketAPI": true, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": true, + "saveTradeData": false, + "tradeFeed": false, + "fillsFeed": false + } + }, + "bankAccounts": [ + { + "enabled": false, + "bankName": "", + "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ], + "orderbook": { + "verificationBypass": false, + "websocketBufferLimit": 5, + "websocketBufferEnabled": false, + "publishPeriod": 10000000000 + } }, - "credentialsValidator": { - "requiresKey": true, - "requiresSecret": true - } - }, - "features": { - "supports": { - "restAPI": true, - "restCapabilities": { - "tickerBatching": true, - "autoPairUpdates": true - }, - "websocketAPI": false, - "websocketCapabilities": {} + { + "name": "Poloniex", + "enabled": true, + "verbose": false, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, + "websocketOrderbookBufferLimit": 5, + "baseCurrencies": "USD", + "currencyPairs": { + "requestFormat": { + "uppercase": true, + "delimiter": "_" + }, + "configFormat": { + "uppercase": true, + "delimiter": "_" + }, + "useGlobalFormat": true, + "assetTypes": [ + "spot" + ], + "pairs": { + "spot": { + "enabled": "BTC_LTC,BTC_ETH,BTC_DOGE,BTC_DASH,BTC_XRP", + "available": "USDC_GRIN,BTC_BCN,BTC_DGB,BTC_XMR,USDT_STR,BTC_SC,BTC_ZRX,USDC_XMR,BTC_TRX,BTC_STR,BTC_SNT,USDT_QTUM,USDC_BTC,BTC_NMR,BTC_DASH,BTC_NXT,USDT_LTC,BTC_DCR,USDT_ZRX,USDC_ZEC,USDT_REP,USDT_BAT,BTC_MANA,USDC_BCHABC,USDC_STR,BTC_XRP,USDT_ETH,BTC_REP,USDT_EOS,USDC_ATOM,USDT_XRP,BTC_ETH,USDT_LSK,USDT_SC,USDT_MANA,USDC_ETC,USDC_ETH,BTC_BTS,BTC_LTC,BTC_ETC,BTC_OMG,BTC_STORJ,USDC_XRP,USDT_GRIN,BTC_QTUM,BTC_MAID,BTC_XEM,USDT_BTC,USDT_DASH,ETH_REP,BTC_ZEC,BTC_STRAT,USDC_LTC,BTC_FOAM,USDC_TRX,BTC_DOGE,BTC_VIA,BTC_VTC,ETH_ETC,USDT_ETC,ETH_EOS,USDC_BCHSV,USDT_NXT,USDT_XMR,BTC_ARDR,BTC_CVC,ETH_BAT,USDC_DOGE,BTC_XPM,BTC_LOOM,BTC_LPT,USDC_EOS,USDT_DGB,USDT_BCHSV,BTC_OMNI,ETH_ZEC,BTC_EOS,BTC_KNC,BTC_BCHSV,BTC_POLY,USDC_DASH,USDT_GNT,BTC_BCHABC,BTC_GRIN,BTC_ATOM,USDT_ATOM,USDT_BCHABC,BTC_LSK,ETH_ZRX,BTC_GAS,BTC_BAT,BTC_BNT,USDT_TRX,BTC_FCT,USDT_ZEC,BTC_GNT,USDT_DOGE,USDC_USDT" + } + } + }, + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "tickerBatching": true, + "autoPairUpdates": true + }, + "websocketAPI": true, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": false + } + }, + "bankAccounts": [ + { + "enabled": false, + "bankName": "", + "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ] }, - "enabled": { - "autoPairUpdates": false, - "websocketAPI": false + { + "name": "Yobit", + "enabled": true, + "verbose": false, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, + "websocketOrderbookBufferLimit": 5, + "baseCurrencies": "USD", + "currencyPairs": { + "requestFormat": { + "uppercase": false, + "delimiter": "_", + "separator": "-" + }, + "configFormat": { + "uppercase": true, + "delimiter": "_" + }, + "useGlobalFormat": true, + "lastUpdated": 1566798411, + "assetTypes": [ + "spot" + ], + "pairs": { + "spot": { + "enabled": "LTC_BTC,ETH_BTC,BTC_USD,DASH_BTC", + "available": "DASH_BTC,WAVES_BTC,LSK_BTC,LIZA_BTC,BCC_BTC,ETH_BTC,LTC_BTC,TRX_BTC,DOGE_BTC,VNTX_BTC,SW_BTC,ZEC_BTC,DASH_ETH,WAVES_ETH,LSK_ETH,LIZA_ETH,BCC_ETH,LTC_ETH,TRX_ETH,DOGE_ETH,VNTX_ETH,SW_ETH,ZEC_ETH,DASH_DOGE,WAVES_DOGE,LSK_DOGE,LIZA_DOGE,BCC_DOGE,LTC_DOGE,TRX_DOGE,VNTX_DOGE,SW_DOGE,ZEC_DOGE,DASH_USD,WAVES_USD,LSK_USD,LIZA_USD,BCC_USD,LTC_USD,TRX_USD,VNTX_USD,SW_USD,ZEC_USD,ETH_USD,BTC_USD,DASH_RUR,WAVES_BTC,WAVES_RUR,LSK_RUR,LIZA_RUR,BCC_RUR,LTC_RUR,TRX_RUR,VNTX_RUR,SW_RUR,ETH_RUR,ZEC_RUR" + } + } + }, + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "tickerBatching": true, + "autoPairUpdates": true + }, + "websocketAPI": false, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": false, + "websocketAPI": false + } + }, + "bankAccounts": [ + { + "enabled": false, + "bankName": "", + "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ] } - }, - "bankAccounts": [ + ], + "bankAccounts": [ { - "enabled": false, - "bankName": "", - "bankAddress": "", - "bankPostalCode": "", - "bankPostalCity": "", - "bankCountry": "", - "accountName": "", - "accountNumber": "", - "swiftCode": "", - "iban": "", - "supportedCurrencies": "" + "enabled": false, + "bankName": "test", + "bankAddress": "test", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", + "accountName": "TestAccount", + "accountNumber": "0234", + "swiftCode": "91272837", + "iban": "98218738671897", + "supportedCurrencies": "USD", + "supportedExchanges": "Kraken,Bitstamp" } - ] - } - ], - "bankAccounts": [ - { - "enabled": false, - "bankName": "test", - "bankAddress": "test", - "bankPostalCode": "", - "bankPostalCity": "", - "bankCountry": "", - "accountName": "TestAccount", - "accountNumber": "0234", - "swiftCode": "91272837", - "iban": "98218738671897", - "supportedCurrencies": "USD", - "supportedExchanges": "Kraken,Bitstamp" - } - ] -} + ] +} \ No newline at end of file From 742b21ec11f52ff6cb77fde1652762f2218550aa Mon Sep 17 00:00:00 2001 From: Samuel Reid <43227667+cranktakular@users.noreply.github.com> Date: Tue, 7 May 2024 15:15:26 +1000 Subject: [PATCH 45/79] Lint appeasement --- .../exchange_wrapper_standards_test.go | 6 ++-- exchanges/coinbasepro/coinbasepro.go | 8 ++++-- .../coinbasepro/coinbasepro_websocket.go | 3 +- exchanges/coinbasepro/coinbasepro_wrapper.go | 28 ++++++------------- exchanges/exchange.go | 3 +- exchanges/lbank/lbank.go | 1 + 6 files changed, 22 insertions(+), 27 deletions(-) diff --git a/cmd/exchange_wrapper_standards/exchange_wrapper_standards_test.go b/cmd/exchange_wrapper_standards/exchange_wrapper_standards_test.go index 31dcd436d62..b459390aea9 100644 --- a/cmd/exchange_wrapper_standards/exchange_wrapper_standards_test.go +++ b/cmd/exchange_wrapper_standards/exchange_wrapper_standards_test.go @@ -105,9 +105,9 @@ func setupExchange(ctx context.Context, t *testing.T, name string, cfg *config.C b.API.SetSubAccount(creds.Subaccount) // Lbank usually runs this during setup, but if keys aren't set then, it will fail, so we have to manually // recreate that here - switch theExch := exch.(type) { - case *lbank.Lbank: - err = theExch.LoadPrivKey(ctx) + lbankExch, ok := exch.(*lbank.Lbank) + if ok { + err = lbankExch.LoadPrivKey(ctx) if err != nil { t.Fatalf("Cannot setup %v LoadPrivKey %v", name, err) } diff --git a/exchanges/coinbasepro/coinbasepro.go b/exchanges/coinbasepro/coinbasepro.go index e853f8e7e50..b0b3dbbd1fc 100644 --- a/exchanges/coinbasepro/coinbasepro.go +++ b/exchanges/coinbasepro/coinbasepro.go @@ -141,7 +141,6 @@ var ( errInvalidOrderType = errors.New("order type must be market, limit, or stop") errNoMatchingWallets = errors.New("no matching wallets returned") errOrderModFailNoRet = errors.New("order modification failed but no error returned") - errNoMatchingOrders = errors.New("no matching orders returned") errPointerNil = errors.New("relevant pointer is nil") errNameEmpty = errors.New("name cannot be empty") errPortfolioIDEmpty = errors.New("portfolio id cannot be empty") @@ -1270,13 +1269,16 @@ func (c *CoinbasePro) GetProductCandles(ctx context.Context, pair string, granul } var params Params params.Values = url.Values{} - params.prepareDateString(startTime, endTime, "start", "end") + err := params.prepareDateString(startTime, endTime, "start", "end") + if err != nil { + return nil, err + } if granularity != 0 { params.Values.Set("granularity", strconv.FormatUint(uint64(granularity), 10)) } path := common.EncodeURLValues(coinbaseProducts+"/"+pair+"/"+coinbaseCandles, params.Values) var resp []RawCandles - err := c.SendHTTPRequest(ctx, exchange.RestSpotSupplementary, path, &resp) + err = c.SendHTTPRequest(ctx, exchange.RestSpotSupplementary, path, &resp) if err != nil { return nil, err } diff --git a/exchanges/coinbasepro/coinbasepro_websocket.go b/exchanges/coinbasepro/coinbasepro_websocket.go index 10c99552482..7a9800a7552 100644 --- a/exchanges/coinbasepro/coinbasepro_websocket.go +++ b/exchanges/coinbasepro/coinbasepro_websocket.go @@ -455,7 +455,8 @@ func (c *CoinbasePro) sendRequest(msgType, channel string, productIDs currency.P } if authenticated { message := n + channel + productIDs.Join() - hmac, err := crypto.GetHMAC(crypto.HashSHA256, + var hmac []byte + hmac, err = crypto.GetHMAC(crypto.HashSHA256, []byte(message), []byte(creds.Secret)) if err != nil { diff --git a/exchanges/coinbasepro/coinbasepro_wrapper.go b/exchanges/coinbasepro/coinbasepro_wrapper.go index 94d2b1b3cf4..9291a326164 100644 --- a/exchanges/coinbasepro/coinbasepro_wrapper.go +++ b/exchanges/coinbasepro/coinbasepro_wrapper.go @@ -402,7 +402,8 @@ func (c *CoinbasePro) UpdateOrderbook(ctx context.Context, p currency.Pair, asse if assetType != asset.Spot { return nil, fmt.Errorf("%w for %v", errAuthenticationNeeded, assetType) } - obN, err := c.GetProductBookV1(ctx, fPair.String(), 2) + var obN *OrderBook + obN, err = c.GetProductBookV1(ctx, fPair.String(), 2) if err != nil { return book, err } @@ -421,7 +422,8 @@ func (c *CoinbasePro) UpdateOrderbook(ctx context.Context, p currency.Pair, asse } } } else { - orderbookNew, err := c.GetProductBookV3(ctx, fPair.String(), 1000) + var orderbookNew *ProductBook + orderbookNew, err = c.GetProductBookV3(ctx, fPair.String(), 1000) if err != nil { return book, err } @@ -654,7 +656,7 @@ func (c *CoinbasePro) CancelBatchOrders(ctx context.Context, o []order.Cancel) ( } // CancelAllOrders cancels all orders associated with a currency pair -func (c *CoinbasePro) CancelAllOrders(ctx context.Context, can *order.Cancel) (order.CancelAllResponse, error) { +func (c *CoinbasePro) CancelAllOrders(_ context.Context, _ *order.Cancel) (order.CancelAllResponse, error) { return order.CancelAllResponse{}, common.ErrFunctionNotSupported } @@ -1428,7 +1430,8 @@ func (c *CoinbasePro) tickerHelper(ctx context.Context, name string, assetType a AssetType: assetType, } if unverified { - tick, err := c.GetProductTicker(ctx, name) + var tick *ProductTicker + tick, err = c.GetProductTicker(ctx, name) if err != nil { return err } @@ -1437,7 +1440,8 @@ func (c *CoinbasePro) tickerHelper(ctx context.Context, name string, assetType a newTick.Bid = tick.Bid newTick.Ask = tick.Ask } else { - ticks, err := c.GetTicker(ctx, name, 1, time.Time{}, time.Time{}) + var ticks *Ticker + ticks, err = c.GetTicker(ctx, name, 1, time.Time{}, time.Time{}) if err != nil { return err } @@ -1456,20 +1460,6 @@ func (c *CoinbasePro) tickerHelper(ctx context.Context, name string, assetType a return nil } -// This will only be valuable once it would be used four times; it's currently sitting at two. -// VerifyAssetCheck checks whether the user can access authenticated endpoints, and whether one is necessary, -// providing errors accordingly -func (c *CoinbasePro) verifyAssetCheck(ctx context.Context, assetType asset.Item) (bool, error) { - unverified, err := c.verificationCheck(ctx) - if err != nil { - return unverified, err - } - if unverified && assetType != asset.Spot { - return unverified, fmt.Errorf("%w for %v", errAuthenticationNeeded, assetType) - } - return unverified, nil -} - // CandleHelper handles calling the correct candle function, and doing preliminary work on the data func (c *CoinbasePro) candleHelper(ctx context.Context, pair string, granularity kline.Interval, start, end time.Time, unverified bool) ([]kline.Candle, error) { var timeSeries []kline.Candle diff --git a/exchanges/exchange.go b/exchanges/exchange.go index 9dd1e36db9c..c6fc0ea382e 100644 --- a/exchanges/exchange.go +++ b/exchanges/exchange.go @@ -1972,7 +1972,8 @@ func GetDefaultConfig(ctx context.Context, exch IBotExchange) (*config.Exchange, } if b.Features.Supports.RESTCapabilities.AutoPairUpdates { - if err = exch.UpdateTradablePairs(ctx, true); err != nil { + err = exch.UpdateTradablePairs(ctx, true) + if err != nil { return nil, err } } diff --git a/exchanges/lbank/lbank.go b/exchanges/lbank/lbank.go index e0c59efdf42..f07ccd7d20d 100644 --- a/exchanges/lbank/lbank.go +++ b/exchanges/lbank/lbank.go @@ -495,6 +495,7 @@ func (l *Lbank) SendHTTPRequest(ctx context.Context, ep exchange.URL, path strin }, request.UnauthenticatedRequest) } +// LoadPrivKey loads the private key func (l *Lbank) LoadPrivKey(ctx context.Context) error { creds, err := l.GetCredentials(ctx) if err != nil { From 71e0fa5436c32a8bb858b3011e14af560e8ab27b Mon Sep 17 00:00:00 2001 From: Samuel Reid <43227667+cranktakular@users.noreply.github.com> Date: Tue, 7 May 2024 15:25:20 +1000 Subject: [PATCH 46/79] Config repair --- testdata/configtest.json | 4619 +++++++++++++++++++------------------- 1 file changed, 2310 insertions(+), 2309 deletions(-) diff --git a/testdata/configtest.json b/testdata/configtest.json index e9c76382c45..cf508c9bc3d 100644 --- a/testdata/configtest.json +++ b/testdata/configtest.json @@ -3,2371 +3,2372 @@ "encryptConfig": -1, "globalHTTPTimeout": 15000000000, "database": { - "enabled": true, - "verbose": false, - "driver": "sqlite3", - "connectionDetails": { - "host": "", - "port": 0, - "username": "", - "password": "", - "database": "gocryptotrader.db", - "sslmode": "" - } + "enabled": true, + "verbose": false, + "driver": "sqlite3", + "connectionDetails": { + "host": "", + "port": 0, + "username": "", + "password": "", + "database": "gocryptotrader.db", + "sslmode": "" + } }, "logging": { - "enabled": true, - "level": "INFO|DEBUG|WARN|ERROR", - "output": "console", - "fileSettings": { - "filename": "log.txt", - "rotate": false, - "maxsize": 100 - }, - "advancedSettings": { - "showLogSystemName": false, - "spacer": " | ", - "timeStampFormat": " 02/01/2006 15:04:05 ", - "headers": { - "info": "[INFO]", - "warn": "[WARN]", - "debug": "[DEBUG]", - "error": "[ERROR]" - } + "enabled": true, + "level": "INFO|DEBUG|WARN|ERROR", + "output": "console", + "fileSettings": { + "filename": "log.txt", + "rotate": false, + "maxsize": 100 + }, + "advancedSettings": { + "showLogSystemName": false, + "spacer": " | ", + "timeStampFormat": " 02/01/2006 15:04:05 ", + "headers": { + "info": "[INFO]", + "warn": "[WARN]", + "debug": "[DEBUG]", + "error": "[ERROR]" } + } }, "connectionMonitor": { - "preferredDNSList": [ - "8.8.8.8", - "8.8.4.4", - "1.1.1.1", - "1.0.0.1" - ], - "preferredDomainList": [ - "www.google.com", - "www.cloudflare.com", - "www.facebook.com" - ], - "checkInterval": 1000000000 + "preferredDNSList": [ + "8.8.8.8", + "8.8.4.4", + "1.1.1.1", + "1.0.0.1" + ], + "preferredDomainList": [ + "www.google.com", + "www.cloudflare.com", + "www.facebook.com" + ], + "checkInterval": 1000000000 }, "profiler": { - "enabled": false, - "mutex_profile_fraction": 0 + "enabled": false, + "mutex_profile_fraction": 0 }, "ntpclient": { - "enabled": 0, - "pool": [ - "0.pool.ntp.org:123", - "pool.ntp.org:123" - ], - "allowedDifference": 50000000, - "allowedNegativeDifference": 50000000 + "enabled": 0, + "pool": [ + "0.pool.ntp.org:123", + "pool.ntp.org:123" + ], + "allowedDifference": 50000000, + "allowedNegativeDifference": 50000000 }, "gctscript": { - "enabled": false, - "timeout": 30000000000, - "max_virtual_machines": 10, - "allow_imports": false, - "auto_load": null, - "verbose": false + "enabled": false, + "timeout": 30000000000, + "max_virtual_machines": 10, + "allow_imports": false, + "auto_load": null, + "verbose": false }, "currencyConfig": { - "forexProviders": [ - { - "name": "CurrencyConverter", - "enabled": false, - "verbose": false, - "restPollingDelay": 600, - "apiKey": "Key", - "apiKeyLvl": -1, - "primaryProvider": false - }, - { - "name": "CurrencyLayer", - "enabled": false, - "verbose": false, - "restPollingDelay": 600, - "apiKey": "Key", - "apiKeyLvl": -1, - "primaryProvider": false - }, - { - "name": "Fixer", - "enabled": false, - "verbose": false, - "restPollingDelay": 600, - "apiKey": "Key", - "apiKeyLvl": -1, - "primaryProvider": false - }, - { - "name": "OpenExchangeRates", - "enabled": false, - "verbose": false, - "restPollingDelay": 600, - "apiKey": "Key", - "apiKeyLvl": -1, - "primaryProvider": false - }, - { - "name": "ExchangeRates", - "enabled": true, - "verbose": false, - "restPollingDelay": 600, - "apiKey": "Key", - "apiKeyLvl": -1, - "primaryProvider": true - } - ], - "cryptocurrencyProvider": { - "name": "CoinMarketCap", - "enabled": false, - "verbose": false, - "apiKey": "Key", - "accountPlan": "accountPlan" + "forexProviders": [ + { + "name": "CurrencyConverter", + "enabled": false, + "verbose": false, + "restPollingDelay": 600, + "apiKey": "Key", + "apiKeyLvl": -1, + "primaryProvider": false }, - "cryptocurrencies": "BTC,LTC,ETH,DOGE,DASH,XRP,XMR", - "currencyPairFormat": { - "uppercase": true, - "delimiter": "-" + { + "name": "CurrencyLayer", + "enabled": false, + "verbose": false, + "restPollingDelay": 600, + "apiKey": "Key", + "apiKeyLvl": -1, + "primaryProvider": false }, - "fiatDisplayCurrency": "USD", - "currencyFileUpdateDuration": 0, - "foreignExchangeUpdateDuration": 0 + { + "name": "Fixer", + "enabled": false, + "verbose": false, + "restPollingDelay": 600, + "apiKey": "Key", + "apiKeyLvl": -1, + "primaryProvider": false + }, + { + "name": "OpenExchangeRates", + "enabled": false, + "verbose": false, + "restPollingDelay": 600, + "apiKey": "Key", + "apiKeyLvl": -1, + "primaryProvider": false + }, + { + "name": "ExchangeRates", + "enabled": true, + "verbose": false, + "restPollingDelay": 600, + "apiKey": "Key", + "apiKeyLvl": -1, + "primaryProvider": true + } + ], + "cryptocurrencyProvider": { + "name": "CoinMarketCap", + "enabled": false, + "verbose": false, + "apiKey": "Key", + "accountPlan": "accountPlan" + }, + "cryptocurrencies": "BTC,LTC,ETH,DOGE,DASH,XRP,XMR", + "currencyPairFormat": { + "uppercase": true, + "delimiter": "-" + }, + "fiatDisplayCurrency": "USD", + "currencyFileUpdateDuration": 0, + "foreignExchangeUpdateDuration": 0 }, "communications": { - "slack": { - "name": "Slack", - "enabled": false, - "verbose": false, - "targetChannel": "general", - "verificationToken": "testtest" - }, - "smsGlobal": { - "name": "SMSGlobal", - "from": "Skynet", - "enabled": true, - "verbose": false, - "username": "1234", - "password": "12334", - "contacts": [ - { - "name": "StyleGherkin", - "number": "1231424", - "enabled": true - } - ] - }, - "smtp": { - "name": "SMTP", - "enabled": false, - "verbose": false, - "host": "smtp.google.com", - "port": "537", - "accountName": "some", - "accountPassword": "password", - "from": "", - "recipientList": "lol123@gmail.com" - }, - "telegram": { - "name": "Telegram", - "enabled": false, - "verbose": false, - "verificationToken": "testest", - "authorisedClients": { - "user_example": 0 - } + "slack": { + "name": "Slack", + "enabled": false, + "verbose": false, + "targetChannel": "general", + "verificationToken": "testtest" + }, + "smsGlobal": { + "name": "SMSGlobal", + "from": "Skynet", + "enabled": true, + "verbose": false, + "username": "1234", + "password": "12334", + "contacts": [ + { + "name": "StyleGherkin", + "number": "1231424", + "enabled": true + } + ] + }, + "smtp": { + "name": "SMTP", + "enabled": false, + "verbose": false, + "host": "smtp.google.com", + "port": "537", + "accountName": "some", + "accountPassword": "password", + "from": "", + "recipientList": "lol123@gmail.com" + }, + "telegram": { + "name": "Telegram", + "enabled": false, + "verbose": false, + "verificationToken": "testest", + "authorisedClients": { + "user_example": 0 } + } }, "remoteControl": { - "username": "admin", - "password": "Password", - "gRPC": { - "enabled": true, - "listenAddress": "localhost:9052", - "grpcProxyEnabled": true, - "grpcProxyListenAddress": "localhost:9053" - }, - "deprecatedRPC": { - "enabled": true, - "listenAddress": "localhost:9050" - }, - "websocketRPC": { - "enabled": true, - "listenAddress": "localhost:9051", - "connectionLimit": 1, - "maxAuthFailures": 3, - "allowInsecureOrigin": true - } + "username": "admin", + "password": "Password", + "gRPC": { + "enabled": true, + "listenAddress": "localhost:9052", + "grpcProxyEnabled": true, + "grpcProxyListenAddress": "localhost:9053" + }, + "deprecatedRPC": { + "enabled": true, + "listenAddress": "localhost:9050" + }, + "websocketRPC": { + "enabled": true, + "listenAddress": "localhost:9051", + "connectionLimit": 1, + "maxAuthFailures": 3, + "allowInsecureOrigin": true + } }, "portfolioAddresses": { - "addresses": [ - { - "Address": "1JCe8z4jJVNXSjohjM4i9Hh813dLCNx2Sy", - "CoinType": "BTC", - "Balance": 53000.01741264, - "Description": "", - "WhiteListed": false, - "ColdStorage": false, - "SupportedExchanges": "" - }, - { - "Address": "3Nxwenay9Z8Lc9JBiywExpnEFiLp6Afp8v", - "CoinType": "BTC", - "Balance": 107848.28963408, - "Description": "", - "WhiteListed": false, - "ColdStorage": false, - "SupportedExchanges": "" - }, - { - "Address": "LgY8ahfHRhvjVQC1zJnBhFMG5pCTMuKRqh", - "CoinType": "LTC", - "Balance": 0.03665026, - "Description": "", - "WhiteListed": false, - "ColdStorage": false, - "SupportedExchanges": "" - }, - { - "Address": "0xb794f5ea0ba39494ce839613fffba74279579268", - "CoinType": "ETH", - "Balance": 0.25555604051326, - "Description": "", - "WhiteListed": false, - "ColdStorage": false, - "SupportedExchanges": "" - } - ] - }, - "exchanges": [ + "addresses": [ { - "name": "BTC Markets", - "enabled": true, - "verbose": false, - "httpTimeout": 15000000000, - "websocketResponseCheckTimeout": 30000000, - "websocketResponseMaxLimit": 7000000000, - "websocketTrafficTimeout": 30000000000, - "websocketOrderbookBufferLimit": 5, - "baseCurrencies": "AUD", - "currencyPairs": { - "requestFormat": { - "uppercase": true, - "delimiter": "-" - }, - "configFormat": { - "uppercase": true, - "delimiter": "-" - }, - "useGlobalFormat": true, - "assetTypes": [ - "spot" - ], - "pairs": { - "spot": { - "enabled": "BTC-AUD", - "available": "BTC-AUD,LTC-AUD,LTC-BTC,ETH-BTC,ETH-AUD,ETC-AUD,ETC-BTC,XRP-AUD,XRP-BTC,POWR-AUD,POWR-BTC,OMG-AUD,OMG-BTC,BCHABC-AUD,BCHABC-BTC,BCHSV-AUD,BCHSV-BTC,GNT-AUD,GNT-BTC,BAT-AUD,BAT-BTC,XLM-AUD,XLM-BTC" - } - } - }, - "api": { - "authenticatedSupport": false, - "authenticatedWebsocketApiSupport": false, - "endpoints": { - "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" - }, - "credentials": { - "key": "Key", - "secret": "Secret" - }, - "credentialsValidator": { - "requiresKey": true, - "requiresSecret": true, - "requiresBase64DecodeSecret": true - } - }, - "features": { - "supports": { - "restAPI": true, - "restCapabilities": { - "autoPairUpdates": true - }, - "websocketAPI": true, - "websocketCapabilities": {} - }, - "enabled": { - "autoPairUpdates": true, - "websocketAPI": false - } - }, - "bankAccounts": [ - { - "enabled": false, - "bankName": "", - "bankAddress": "", - "bankPostalCode": "", - "bankPostalCity": "", - "bankCountry": "", - "accountName": "", - "accountNumber": "", - "swiftCode": "", - "iban": "", - "supportedCurrencies": "" - } - ] + "Address": "1JCe8z4jJVNXSjohjM4i9Hh813dLCNx2Sy", + "CoinType": "BTC", + "Balance": 53000.01741264, + "Description": "", + "WhiteListed": false, + "ColdStorage": false, + "SupportedExchanges": "" }, { - "name": "BTSE", - "enabled": true, - "verbose": false, - "httpTimeout": 15000000000, - "websocketResponseCheckTimeout": 30000000, - "websocketResponseMaxLimit": 7000000000, - "websocketTrafficTimeout": 30000000000, - "websocketOrderbookBufferLimit": 5, - "baseCurrencies": "USD", - "currencyPairs": { - "requestFormat": { - "uppercase": true, - "delimiter": "-" - }, - "configFormat": { - "uppercase": true, - "delimiter": "-" - }, - "useGlobalFormat": true, - "assetTypes": [ - "spot" - ], - "pairs": { - "spot": { - "enabled": "BTC-USD", - "available": "BTC-CNY,BTC-EUR,BTC-GBP,BTC-HKD,BTC-JPY,BTC-SGD,BTC-USD,ETH-CNY,ETH-EUR,ETH-GBP,ETH-HKD,ETH-JPY,ETH-SGD,ETH-USD,LTC-CNY,LTC-EUR,LTC-GBP,LTC-HKD,LTC-JPY,LTC-SGD,LTC-USD,USDT-CNY,USDT-EUR,USDT-GBP,USDT-HKD,USDT-JPY,USDT-SGD,USDT-USD,XMR-CNY,XMR-EUR,XMR-GBP,XMR-HKD,XMR-JPY,XMR-SGD,XMR-USD" - } - } - }, - "api": { - "authenticatedSupport": false, - "authenticatedWebsocketApiSupport": false, - "endpoints": { - "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" - }, - "credentials": { - "key": "Key", - "secret": "Secret" - }, - "credentialsValidator": { - "requiresKey": true, - "requiresSecret": true - } - }, - "features": { - "supports": { - "restAPI": true, - "restCapabilities": { - "autoPairUpdates": true - }, - "websocketAPI": true, - "websocketCapabilities": {} - }, - "enabled": { - "autoPairUpdates": true, - "websocketAPI": true - } - }, - "bankAccounts": [ - { - "enabled": false, - "bankName": "", - "bankAddress": "", - "bankPostalCode": "", - "bankPostalCity": "", - "bankCountry": "", - "accountName": "", - "accountNumber": "", - "swiftCode": "", - "iban": "", - "supportedCurrencies": "" - } - ] + "Address": "3Nxwenay9Z8Lc9JBiywExpnEFiLp6Afp8v", + "CoinType": "BTC", + "Balance": 107848.28963408, + "Description": "", + "WhiteListed": false, + "ColdStorage": false, + "SupportedExchanges": "" }, { - "name": "Binance", - "enabled": true, - "verbose": false, - "httpTimeout": 15000000000, - "websocketResponseCheckTimeout": 30000000, - "websocketResponseMaxLimit": 7000000000, - "websocketTrafficTimeout": 30000000000, - "websocketOrderbookBufferLimit": 5, - "baseCurrencies": "USD", - "currencyPairs": { - "requestFormat": { - "uppercase": true - }, - "configFormat": { - "uppercase": true, - "delimiter": "-" - }, - "useGlobalFormat": true, - "assetTypes": [ - "spot" - ], - "pairs": { - "spot": { - "enabled": "BTC-USDT,DOGE-USDT", - "available": "ETH-BTC,LTC-BTC,BNB-BTC,NEO-BTC,QTUM-ETH,EOS-ETH,SNT-ETH,BNT-ETH,GAS-BTC,BNB-ETH,BTC-USDT,ETH-USDT,OAX-ETH,DNT-ETH,MCO-ETH,MCO-BTC,WTC-BTC,WTC-ETH,LRC-BTC,LRC-ETH,QTUM-BTC,YOYO-BTC,OMG-BTC,OMG-ETH,ZRX-BTC,ZRX-ETH,STRAT-BTC,STRAT-ETH,SNGLS-BTC,BQX-BTC,BQX-ETH,KNC-BTC,KNC-ETH,FUN-BTC,FUN-ETH,SNM-BTC,SNM-ETH,NEO-ETH,IOTA-BTC,IOTA-ETH,LINK-BTC,LINK-ETH,XVG-BTC,XVG-ETH,MDA-BTC,MDA-ETH,MTL-BTC,MTL-ETH,EOS-BTC,SNT-BTC,ETC-ETH,ETC-BTC,MTH-BTC,MTH-ETH,ENG-BTC,ENG-ETH,DNT-BTC,ZEC-BTC,ZEC-ETH,BNT-BTC,AST-BTC,AST-ETH,DASH-BTC,DASH-ETH,OAX-BTC,BTG-BTC,BTG-ETH,EVX-BTC,EVX-ETH,REQ-BTC,REQ-ETH,VIB-BTC,VIB-ETH,TRX-BTC,TRX-ETH,POWR-BTC,POWR-ETH,ARK-BTC,ARK-ETH,YOYO-ETH,XRP-BTC,XRP-ETH,ENJ-BTC,ENJ-ETH,STORJ-BTC,STORJ-ETH,BNB-USDT,YOYO-BNB,POWR-BNB,KMD-BTC,KMD-ETH,NULS-BNB,RCN-BTC,RCN-ETH,RCN-BNB,NULS-BTC,NULS-ETH,RDN-BTC,RDN-ETH,RDN-BNB,XMR-BTC,XMR-ETH,DLT-BNB,WTC-BNB,DLT-BTC,DLT-ETH,AMB-BTC,AMB-ETH,AMB-BNB,BAT-BTC,BAT-ETH,BAT-BNB,BCPT-BTC,BCPT-ETH,BCPT-BNB,ARN-BTC,ARN-ETH,GVT-BTC,GVT-ETH,CDT-BTC,CDT-ETH,GXS-BTC,GXS-ETH,NEO-USDT,NEO-BNB,POE-BTC,POE-ETH,QSP-BTC,QSP-ETH,QSP-BNB,BTS-BTC,BTS-ETH,XZC-BTC,XZC-ETH,XZC-BNB,LSK-BTC,LSK-ETH,LSK-BNB,TNT-BTC,TNT-ETH,FUEL-BTC,MANA-BTC,MANA-ETH,BCD-BTC,BCD-ETH,DGD-BTC,DGD-ETH,IOTA-BNB,ADX-BTC,ADX-ETH,ADA-BTC,ADA-ETH,PPT-BTC,PPT-ETH,CMT-BTC,CMT-ETH,CMT-BNB,XLM-BTC,XLM-ETH,XLM-BNB,CND-BTC,CND-ETH,CND-BNB,LEND-BTC,LEND-ETH,WABI-BTC,WABI-ETH,WABI-BNB,LTC-ETH,LTC-USDT,LTC-BNB,TNB-BTC,TNB-ETH,WAVES-BTC,WAVES-ETH,WAVES-BNB,GTO-BTC,GTO-ETH,GTO-BNB,ICX-BTC,ICX-ETH,ICX-BNB,OST-BTC,OST-ETH,OST-BNB,ELF-BTC,ELF-ETH,AION-BTC,AION-ETH,AION-BNB,NEBL-BTC,NEBL-ETH,NEBL-BNB,BRD-BTC,BRD-ETH,BRD-BNB,MCO-BNB,EDO-BTC,EDO-ETH,NAV-BTC,LUN-BTC,APPC-BTC,APPC-ETH,APPC-BNB,VIBE-BTC,VIBE-ETH,RLC-BTC,RLC-ETH,RLC-BNB,INS-BTC,INS-ETH,PIVX-BTC,PIVX-ETH,PIVX-BNB,IOST-BTC,IOST-ETH,STEEM-BTC,STEEM-ETH,STEEM-BNB,NANO-BTC,NANO-ETH,NANO-BNB,VIA-BTC,VIA-ETH,VIA-BNB,BLZ-BTC,BLZ-ETH,BLZ-BNB,AE-BTC,AE-ETH,AE-BNB,NCASH-BTC,NCASH-ETH,POA-BTC,POA-ETH,ZIL-BTC,ZIL-ETH,ZIL-BNB,ONT-BTC,ONT-ETH,ONT-BNB,STORM-BTC,STORM-ETH,STORM-BNB,QTUM-BNB,QTUM-USDT,XEM-BTC,XEM-ETH,XEM-BNB,WAN-BTC,WAN-ETH,WAN-BNB,WPR-BTC,WPR-ETH,QLC-BTC,QLC-ETH,SYS-BTC,SYS-ETH,SYS-BNB,QLC-BNB,GRS-BTC,GRS-ETH,ADA-USDT,ADA-BNB,GNT-BTC,GNT-ETH,LOOM-BTC,LOOM-ETH,LOOM-BNB,XRP-USDT,REP-BTC,REP-ETH,BTC-TUSD,ETH-TUSD,ZEN-BTC,ZEN-ETH,ZEN-BNB,SKY-BTC,SKY-ETH,SKY-BNB,EOS-USDT,EOS-BNB,CVC-BTC,CVC-ETH,THETA-BTC,THETA-ETH,THETA-BNB,XRP-BNB,TUSD-USDT,IOTA-USDT,XLM-USDT,IOTX-BTC,IOTX-ETH,QKC-BTC,QKC-ETH,AGI-BTC,AGI-ETH,AGI-BNB,NXS-BTC,NXS-ETH,NXS-BNB,ENJ-BNB,DATA-BTC,DATA-ETH,ONT-USDT,TRX-BNB,TRX-USDT,ETC-USDT,ETC-BNB,ICX-USDT,SC-BTC,SC-ETH,SC-BNB,NPXS-ETH,KEY-BTC,KEY-ETH,NAS-BTC,NAS-ETH,NAS-BNB,MFT-BTC,MFT-ETH,MFT-BNB,DENT-ETH,ARDR-BTC,ARDR-ETH,NULS-USDT,HOT-BTC,HOT-ETH,VET-BTC,VET-ETH,VET-USDT,VET-BNB,DOCK-BTC,DOCK-ETH,POLY-BTC,POLY-BNB,HC-BTC,HC-ETH,GO-BTC,GO-BNB,PAX-USDT,RVN-BTC,RVN-BNB,DCR-BTC,DCR-BNB,MITH-BTC,MITH-BNB,BNB-PAX,BTC-PAX,ETH-PAX,XRP-PAX,EOS-PAX,XLM-PAX,REN-BTC,REN-BNB,BNB-TUSD,XRP-TUSD,EOS-TUSD,XLM-TUSD,BNB-USDC,BTC-USDC,ETH-USDC,XRP-USDC,EOS-USDC,XLM-USDC,USDC-USDT,ADA-TUSD,TRX-TUSD,NEO-TUSD,TRX-XRP,XZC-XRP,PAX-TUSD,USDC-TUSD,USDC-PAX,LINK-USDT,LINK-TUSD,LINK-PAX,LINK-USDC,WAVES-USDT,WAVES-TUSD,WAVES-USDC,LTC-TUSD,LTC-PAX,LTC-USDC,TRX-PAX,TRX-USDC,BTT-BNB,BTT-USDT,BNB-USDS,BTC-USDS,USDS-USDT,USDS-PAX,USDS-TUSD,USDS-USDC,BTT-PAX,BTT-TUSD,BTT-USDC,ONG-BNB,ONG-BTC,ONG-USDT,HOT-BNB,HOT-USDT,ZIL-USDT,ZRX-BNB,ZRX-USDT,FET-BNB,FET-BTC,FET-USDT,BAT-USDT,XMR-BNB,XMR-USDT,ZEC-BNB,ZEC-USDT,ZEC-PAX,ZEC-TUSD,ZEC-USDC,IOST-BNB,IOST-USDT,CELR-BNB,CELR-BTC,CELR-USDT,ADA-PAX,ADA-USDC,NEO-PAX,NEO-USDC,DASH-BNB,DASH-USDT,NANO-USDT,OMG-BNB,OMG-USDT,THETA-USDT,ENJ-USDT,MITH-USDT,MATIC-BNB,MATIC-BTC,MATIC-USDT,ATOM-BNB,ATOM-BTC,ATOM-USDT,ATOM-USDC,ATOM-TUSD,ETC-TUSD,BAT-USDC,BAT-PAX,BAT-TUSD,PHB-BNB,PHB-BTC,PHB-TUSD,TFUEL-BNB,TFUEL-BTC,TFUEL-USDT,ONE-BNB,ONE-BTC,ONE-USDT,ONE-USDC,FTM-BNB,FTM-BTC,FTM-USDT,FTM-USDC,ALGO-BNB,ALGO-BTC,ALGO-USDT,ALGO-TUSD,ALGO-PAX,ALGO-USDC,GTO-USDT,ERD-BNB,ERD-BTC,ERD-USDT,DOGE-BNB,DOGE-BTC,DOGE-USDT,DUSK-BNB,DUSK-BTC,DUSK-USDT,DUSK-USDC,DUSK-PAX,BGBP-USDC,ANKR-BNB,ANKR-BTC,ANKR-USDT,ONT-PAX,ONT-USDC,WIN-BNB,WIN-USDT,WIN-USDC,COS-BNB,COS-BTC,COS-USDT,NPXS-USDT,COCOS-BNB,COCOS-BTC,COCOS-USDT,MTL-USDT,TOMO-BNB,TOMO-BTC,TOMO-USDT,TOMO-USDC,PERL-BNB,PERL-BTC,PERL-USDT,DENT-USDT,MFT-USDT,KEY-USDT,STORM-USDT,DOCK-USDT,WAN-USDT,FUN-USDT,CVC-USDT,BTT-TRX,WIN-TRX,CHZ-BNB,CHZ-BTC,CHZ-USDT,BAND-BNB,BAND-BTC,BAND-USDT,BNB-BUSD,BTC-BUSD,BUSD-USDT,BEAM-BNB,BEAM-BTC,BEAM-USDT,XTZ-BNB,XTZ-BTC,XTZ-USDT,REN-USDT,RVN-USDT,HC-USDT,HBAR-BNB,HBAR-BTC,HBAR-USDT,NKN-BNB,NKN-BTC,NKN-USDT,XRP-BUSD,ETH-BUSD,LTC-BUSD,LINK-BUSD,ETC-BUSD,STX-BNB,STX-BTC,STX-USDT,KAVA-BNB,KAVA-BTC,KAVA-USDT,BUSD-NGN,BNB-NGN,BTC-NGN,ARPA-BNB,ARPA-BTC,ARPA-USDT,TRX-BUSD,EOS-BUSD,IOTX-USDT,RLC-USDT,MCO-USDT,XLM-BUSD,ADA-BUSD,CTXC-BNB,CTXC-BTC,CTXC-USDT,BCH-BNB,BCH-BTC,BCH-USDT,BCH-USDC,BCH-TUSD,BCH-PAX,BCH-BUSD,BTC-RUB,ETH-RUB,XRP-RUB,BNB-RUB,TROY-BNB,TROY-BTC,TROY-USDT,BUSD-RUB,QTUM-BUSD,VET-BUSD" - } - } - }, - "api": { - "authenticatedSupport": false, - "authenticatedWebsocketApiSupport": false, - "endpoints": { - "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" - }, - "credentials": { - "key": "Key", - "secret": "Secret" - }, - "credentialsValidator": { - "requiresKey": true, - "requiresSecret": true - } - }, - "features": { - "supports": { - "restAPI": true, - "restCapabilities": { - "tickerBatching": true, - "autoPairUpdates": true - }, - "websocketAPI": true, - "websocketCapabilities": {} - }, - "enabled": { - "autoPairUpdates": true, - "websocketAPI": true - } - }, - "bankAccounts": [ - { - "enabled": false, - "bankName": "", - "bankAddress": "", - "bankPostalCode": "", - "bankPostalCity": "", - "bankCountry": "", - "accountName": "", - "accountNumber": "", - "swiftCode": "", - "iban": "", - "supportedCurrencies": "" - } - ] + "Address": "LgY8ahfHRhvjVQC1zJnBhFMG5pCTMuKRqh", + "CoinType": "LTC", + "Balance": 0.03665026, + "Description": "", + "WhiteListed": false, + "ColdStorage": false, + "SupportedExchanges": "" }, { - "name": "Binanceus", - "enabled": true, - "verbose": false, - "httpTimeout": 15000000000, - "websocketResponseCheckTimeout": 30000000, - "websocketResponseMaxLimit": 7000000000, - "websocketTrafficTimeout": 30000000000, - "baseCurrencies": "USD", - "currencyPairs": { - "bypassConfigFormatUpgrades": false, - "pairs": { - "spot": { - "assetEnabled": true, - "enabled": "BTC-USDT,ETH-USDT,LTC-USDT,ADA-USDT", - "available": "BTC-USD,BCH-USD,LTC-USD,USDT-USD,BTC-USDT,ETH-USDT,BCH-USDT,LTC-USDT,BNB-USD,BNB-USDT,ETH-BTC,BNB-BTC,LTC-BTC,BCH-BTC,ADA-USD,BAT-USD,ETC-USD,XLM-USD,ZRX-USD,ADA-USDT,BAT-USDT,ETC-USDT,XLM-USDT,ZRX-USDT,LINK-USD,RVN-USD,DASH-USD,ZEC-USD,ALGO-USD,IOTA-USD,BUSD-USD,BTCB-USD,DOGE-USDT,WAVES-USD,ATOM-USDT,ATOM-USD,NEO-USDT,NEO-USD,VET-USDT,QTUM-USDT,QTUM-USD,ICX-USD,ENJ-USD,ONT-USD,ONT-USDT,ZIL-USD,ZILB-USD,VET-USD,BNBB-USD,ETHB-USD,ALGO-BUSD,XTZ-USD,XTZ-BUSD,HBAR-USD,HBAR-BUSD,OMG-USD,OMG-BUSD,MATIC-USD,MATIC-BUSD,XTZ-BTC,ADA-BTC,REP-BUSD,REP-USD,EOS-BUSD,EOS-USD,DOGE-USD,KNC-USD,KNC-USDT,VTHO-USDT,VTHO-USD,USDC-USD,COMP-USDT,COMP-USD,MANA-USD,HNT-USD,HNT-USDT,MKR-USD,MKR-USDT,DAI-USD,ONE-USDT,ONE-USD,BAND-USDT,BAND-USD,STORJ-USDT,STORJ-USD,UNI-USD,UNI-USDT,SOL-USD,SOL-USDT,LINK-BTC,VET-BTC,UNI-BTC,EGLD-USDT,EGLD-USD,PAXG-USDT,PAXG-USD,OXT-USDT,OXT-USD,ZEN-USDT,ZEN-USD,BTC-USDC,ONEB-USD,FIL-USDT,FIL-USD,AAVE-USDT,AAVE-USD,GRT-USDT,GRT-USD,SUSHI-USD,ANKR-USD,AMP-USD,SHIB-USDT,SHIB-BUSD,CRV-USDT,CRV-USD,AXS-USDT,AXS-USD,SOL-BTC,AVAX-USDT,AVAX-USD,CTSI-USDT,CTSI-USD,DOT-USDT,DOT-USD,YFI-USDT,YFI-USD,1INCH-USDT,1INCH-USD,FTM-USDT,FTM-USD,USDC-USDT,ETH-USDC,USDC-BUSD,MATIC-USDT,MANA-USDT,MANA-BUSD,ALGO-USDT,ADA-BUSD,SOL-BUSD,EOS-USDT,ENJ-USDT,NEAR-USDT,NEAR-BUSD,NEAR-USD,OMG-USDT,SUSHI-USDT,LRC-USDT,LRC-USD,LRC-BTC,KSHI-BUSD,LPT-USDT,LPT-BUSD,LPT-USD,POLY-USDT,POLY-BUSD,POLY-USD,POLY-BTC,MATIC-BTC,DOT-BTC,NMR-USDT,NMR-USD,SLP-USDT,ANT-USD,XNO-USD,CHZ-USDT,CHZ-USD,OGN-USDT,OGN-USD,GALA-USDT,GALA-USD,TLM-USDT,TLM-USD,SNX-USDT,SNX-USD,AUDIO-USDT,AUDIO-USD,ENS-USDT,MANA-BTC,ATOM-BTC,AVAX-BTC,WBTC-BTC,REQ-USDT,REQ-USD,APE-USDT,APE-USD,FLUX-USDT,FLUX-USD,TRX-BTC,TRX-BUSD,TRX-USDT,TRX-USD,COTI-USDT,COTI-USD,VOXEL-USDT,VOXEL-USD,RLC-USDT,RLC-USD,UST-USDT,UST-USD,BICO-USDT,BICO-USD,API3-USDT,API3-USD,ENS-USD,BTC-UST,BNT-USDT,BNT-USD,IMX-USDT,IMX-USD,SPELL-USDT,SPELL-USD,JASMY-USDT,JASMY-USD,FLOW-USDT,FLOW-USD,GTC-USDT,GTC-USD,BTC-BUSD,ZIL-BUSD,BNB-BUSD,ETH-BUSD,BUSD-USDT,ONE-BUSD,LINK-USDT,ZEC-USDT,SLP-USD,ANT-USDT", - "requestFormat": { - "uppercase": true - }, - "configFormat": { - "uppercase": true, - "delimiter": "-" - } - } - } - }, - "api": { - "authenticatedSupport": false, - "authenticatedWebsocketApiSupport": false, - "credentials": { - "key": "", - "secret": "" - }, - "credentialsValidator": { - "requiresKey": true, - "requiresSecret": true - }, - "urlEndpoints": { - "RestSpotSupplementaryURL": "https://api.binance.us", - "RestSpotURL": "https://api.binance.us", - "WebsocketSpotSupplementaryURL": "wss://stream.binance.us:9443/stream", - "WebsocketSpotURL": "wss://stream.binance.us:9443/stream" - } - }, - "features": { - "supports": { - "restAPI": true, - "restCapabilities": { - "tickerBatching": true, - "autoPairUpdates": true - }, - "websocketAPI": true, - "websocketCapabilities": {} - }, - "enabled": { - "autoPairUpdates": true, - "websocketAPI": true, - "saveTradeData": false, - "tradeFeed": false, - "fillsFeed": false - } - }, - "bankAccounts": [ - { - "enabled": false, - "bankName": "", - "bankAddress": "", - "bankPostalCode": "", - "bankPostalCity": "", - "bankCountry": "", - "accountName": "", - "accountNumber": "", - "swiftCode": "", - "iban": "", - "supportedCurrencies": "" - } - ], - "orderbook": { - "verificationBypass": false, - "websocketBufferLimit": 5, - "websocketBufferEnabled": false, - "publishPeriod": 10000000000 + "Address": "0xb794f5ea0ba39494ce839613fffba74279579268", + "CoinType": "ETH", + "Balance": 0.25555604051326, + "Description": "", + "WhiteListed": false, + "ColdStorage": false, + "SupportedExchanges": "" + } + ] + }, + "exchanges": [ + { + "name": "BTC Markets", + "enabled": true, + "verbose": false, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, + "websocketOrderbookBufferLimit": 5, + "baseCurrencies": "AUD", + "currencyPairs": { + "requestFormat": { + "uppercase": true, + "delimiter": "-" + }, + "configFormat": { + "uppercase": true, + "delimiter": "-" + }, + "useGlobalFormat": true, + "assetTypes": [ + "spot" + ], + "pairs": { + "spot": { + "enabled": "BTC-AUD", + "available": "BTC-AUD,LTC-AUD,LTC-BTC,ETH-BTC,ETH-AUD,ETC-AUD,ETC-BTC,XRP-AUD,XRP-BTC,POWR-AUD,POWR-BTC,OMG-AUD,OMG-BTC,BCHABC-AUD,BCHABC-BTC,BCHSV-AUD,BCHSV-BTC,GNT-AUD,GNT-BTC,BAT-AUD,BAT-BTC,XLM-AUD,XLM-BTC" } + } }, - { - "name": "Bitfinex", - "enabled": true, - "verbose": false, - "httpTimeout": 15000000000, - "websocketResponseCheckTimeout": 30000000, - "websocketResponseMaxLimit": 7000000000, - "websocketTrafficTimeout": 30000000000, - "websocketOrderbookBufferLimit": 5, - "baseCurrencies": "USD", - "currencyPairs": { - "requestFormat": { - "uppercase": true - }, - "configFormat": { - "uppercase": true - }, - "useGlobalFormat": true, - "assetTypes": [ - "spot" - ], - "pairs": { - "spot": { - "enabled": "BTCUSD,LTCUSD,LTCBTC,ETHUSD,ETHBTC", - "available": "BTCUSD,LTCUSD,LTCBTC,ETHUSD,ETHBTC,ETCBTC,ETCUSD,RRTUSD,RRTBTC,ZECUSD,ZECBTC,XMRUSD,XMRBTC,DSHUSD,DSHBTC,BTCEUR,BTCJPY,XRPUSD,XRPBTC,IOTUSD,IOTBTC,IOTETH,EOSUSD,EOSBTC,EOSETH,SANUSD,SANBTC,SANETH,OMGUSD,OMGBTC,OMGETH,NEOUSD,NEOBTC,NEOETH,ETPUSD,ETPBTC,ETPETH,QTMUSD,QTMBTC,QTMETH,AVTUSD,AVTBTC,AVTETH,EDOUSD,EDOBTC,EDOETH,BTGUSD,BTGBTC,DATUSD,DATBTC,DATETH,QSHUSD,QSHBTC,QSHETH,YYWUSD,YYWBTC,YYWETH,GNTUSD,GNTBTC,GNTETH,SNTUSD,SNTBTC,SNTETH,IOTEUR,BATUSD,BATBTC,BATETH,MNAUSD,MNABTC,MNAETH,FUNUSD,FUNBTC,FUNETH,ZRXUSD,ZRXBTC,ZRXETH,TNBUSD,TNBBTC,TNBETH,SPKUSD,SPKBTC,SPKETH,TRXUSD,TRXBTC,TRXETH,RCNUSD,RCNBTC,RCNETH,RLCUSD,RLCBTC,RLCETH,AIDUSD,AIDBTC,AIDETH,SNGUSD,SNGBTC,SNGETH,REPUSD,REPBTC,REPETH,ELFUSD,ELFBTC,ELFETH,NECUSD,NECBTC,NECETH,BTCGBP,ETHEUR,ETHJPY,ETHGBP,NEOEUR,NEOJPY,NEOGBP,EOSEUR,EOSJPY,EOSGBP,IOTJPY,IOTGBP,IOSUSD,IOSBTC,IOSETH,AIOUSD,AIOBTC,AIOETH,REQUSD,REQBTC,REQETH,RDNUSD,RDNBTC,RDNETH,LRCUSD,LRCBTC,LRCETH,WAXUSD,WAXBTC,WAXETH,DAIUSD,DAIBTC,DAIETH,AGIUSD,AGIBTC,AGIETH,BFTUSD,BFTBTC,BFTETH,MTNUSD,MTNBTC,MTNETH,ODEUSD,ODEBTC,ODEETH,ANTUSD,ANTBTC,ANTETH,DTHUSD,DTHBTC,DTHETH,MITUSD,MITBTC,MITETH,STJUSD,STJBTC,STJETH,XLMUSD,XLMEUR,XLMJPY,XLMGBP,XLMBTC,XLMETH,XVGUSD,XVGEUR,XVGJPY,XVGGBP,XVGBTC,XVGETH,BCIUSD,BCIBTC,MKRUSD,MKRBTC,MKRETH,KNCUSD,KNCBTC,KNCETH,POAUSD,POABTC,POAETH,EVTUSD,LYMUSD,LYMBTC,LYMETH,UTKUSD,UTKBTC,UTKETH,VEEUSD,VEEBTC,VEEETH,DADUSD,DADBTC,DADETH,ORSUSD,ORSBTC,ORSETH,AUCUSD,AUCBTC,AUCETH,POYUSD,POYBTC,POYETH,FSNUSD,FSNBTC,FSNETH,CBTUSD,CBTBTC,CBTETH,ZCNUSD,ZCNBTC,ZCNETH,SENUSD,SENBTC,SENETH,NCAUSD,NCABTC,NCAETH,CNDUSD,CNDBTC,CNDETH,CTXUSD,CTXBTC,CTXETH,PAIUSD,PAIBTC,SEEUSD,SEEBTC,SEEETH,ESSUSD,ESSBTC,ESSETH,ATMUSD,ATMBTC,ATMETH,HOTUSD,HOTBTC,HOTETH,DTAUSD,DTABTC,DTAETH,IQXUSD,IQXBTC,IQXEOS,WPRUSD,WPRBTC,WPRETH,ZILUSD,ZILBTC,ZILETH,BNTUSD,BNTBTC,BNTETH,ABSUSD,ABSETH,XRAUSD,XRAETH,MANUSD,MANETH,BBNUSD,BBNETH,NIOUSD,NIOETH,DGXUSD,DGXETH,VETUSD,VETBTC,VETETH,UTNUSD,UTNETH,TKNUSD,TKNETH,GOTUSD,GOTEUR,GOTETH,XTZUSD,XTZBTC,CNNUSD,CNNETH,BOXUSD,BOXETH,TRXEUR,TRXGBP,TRXJPY,MGOUSD,MGOETH,RTEUSD,RTEETH,YGGUSD,YGGETH,MLNUSD,MLNETH,WTCUSD,WTCETH,CSXUSD,CSXETH,OMNUSD,OMNBTC,INTUSD,INTETH,DRNUSD,DRNETH,PNKUSD,PNKETH,DGBUSD,DGBBTC,BSVUSD,BSVBTC,BABUSD,BABBTC,WLOUSD,WLOXLM,VLDUSD,VLDETH,ENJUSD,ENJETH,ONLUSD,ONLETH,RBTUSD,RBTBTC,USTUSD,EUTEUR,EUTUSD,GSDUSD,UDCUSD,TSDUSD,PAXUSD,RIFUSD,RIFBTC,PASUSD,PASETH,VSYUSD,VSYBTC,ZRXDAI,MKRDAI,OMGDAI,BTTUSD,BTTBTC,BTCUST,ETHUST,CLOUSD,CLOBTC,IMPUSD,IMPETH,LTCUST,EOSUST,BABUST,SCRUSD,SCRETH,GNOUSD,GNOETH,GENUSD,GENETH,ATOUSD,ATOBTC,ATOETH,WBTUSD,XCHUSD,EUSUSD,WBTETH,XCHETH,EUSETH,LEOUSD,LEOBTC,LEOUST,LEOEOS,LEOETH,ASTUSD,ASTETH,FOAUSD,FOAETH,UFRUSD,UFRETH,ZBTUSD,ZBTUST,OKBUSD,USKUSD,GTXUSD,KANUSD,OKBUST,OKBETH,OKBBTC,USKUST,USKETH,USKBTC,USKEOS,GTXUST,KANUST,AMPUSD,ALGUSD,ALGBTC,ALGUST,BTCXCH,SWMUSD,SWMETH,TRIUSD,TRIETH,LOOUSD,LOOETH,AMPUST,DUSK:USD,DUSK:BTC,UOSUSD,UOSBTC,RRBUSD,RRBUST,DTXUSD,DTXUST,AMPBTC,FTTUSD,FTTUST,PAXUST,UDCUST,TSDUST,BTC:CNHT,UST:CNHT,CNH:CNHT,CHZUSD,CHZUST,BTCF0:USTF0,ETHF0:USTF0" - } - } - }, - "api": { - "authenticatedSupport": true, - "authenticatedWebsocketApiSupport": true, - "endpoints": { - "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" - }, - "credentials": { - "key": "Key", - "secret": "Secret" - }, - "credentialsValidator": { - "requiresKey": true, - "requiresSecret": true - } - }, - "features": { - "supports": { - "restAPI": true, - "restCapabilities": { - "tickerBatching": true, - "autoPairUpdates": true - }, - "websocketAPI": true, - "websocketCapabilities": {} - }, - "enabled": { - "autoPairUpdates": true, - "websocketAPI": true - } - }, - "bankAccounts": [ - { - "enabled": false, - "bankName": "Deutsche Bank Privat Und Geschaeftskunden AG", - "bankAddress": "Karlsruhe, 76125, GERMANY", - "bankPostalCode": "", - "bankPostalCity": "", - "bankCountry": "", - "accountName": "GLOBAL TRADE SOLUTIONS GmbH", - "accountNumber": "DE51660700240057016802", - "swiftCode": "DEUTDEDB660", - "iban": "DE51660700240057016802", - "supportedCurrencies": "EUR,USD" - }, - { - "enabled": false, - "bankName": "Deutsche Bank Privat Und Geschaeftskunden AG", - "bankAddress": "Karlsruhe, 76125, GERMANY", - "bankPostalCode": "", - "bankPostalCity": "", - "bankCountry": "", - "accountName": "GLOBAL TRADE SOLUTIONS GmbH", - "accountNumber": "DE78660700240057016801", - "swiftCode": "DEUTDEDB660", - "iban": "DE78660700240057016801", - "supportedCurrencies": "JPY,GBP" - } - ] + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true, + "requiresBase64DecodeSecret": true + } }, - { - "name": "Bitflyer", - "enabled": true, - "verbose": false, - "httpTimeout": 15000000000, - "websocketResponseCheckTimeout": 30000000, - "websocketResponseMaxLimit": 7000000000, - "websocketTrafficTimeout": 30000000000, - "websocketOrderbookBufferLimit": 5, - "baseCurrencies": "JPY", - "currencyPairs": { - "requestFormat": { - "uppercase": true, - "delimiter": "_" - }, - "configFormat": { - "uppercase": true, - "delimiter": "_" - }, - "useGlobalFormat": true, - "lastUpdated": 1566798411, - "assetTypes": [ - "spot", - "futures" - ], - "pairs": { - "spot": { - "enabled": "BTC_JPY,ETH_BTC,BCH_BTC", - "available": "BTC_JPY,FXBTC_JPY,ETH_BTC,BCH_BTC" - } - } - }, - "api": { - "authenticatedSupport": false, - "authenticatedWebsocketApiSupport": false, - "endpoints": { - "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" - }, - "credentials": { - "key": "Key", - "secret": "Secret" - }, - "credentialsValidator": { - "requiresKey": true, - "requiresSecret": true - } - }, - "features": { - "supports": { - "restAPI": true, - "restCapabilities": { - "autoPairUpdates": true - }, - "websocketAPI": false, - "websocketCapabilities": {} - }, - "enabled": { - "autoPairUpdates": false, - "websocketAPI": false - } - }, - "bankAccounts": [ - { - "enabled": false, - "bankName": "", - "bankAddress": "", - "bankPostalCode": "", - "bankPostalCity": "", - "bankCountry": "", - "accountName": "", - "accountNumber": "", - "swiftCode": "", - "iban": "", - "supportedCurrencies": "" - } - ] + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "autoPairUpdates": true + }, + "websocketAPI": true, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": false + } }, - { - "name": "Bithumb", - "enabled": true, - "verbose": false, - "httpTimeout": 15000000000, - "websocketResponseCheckTimeout": 30000000, - "websocketResponseMaxLimit": 7000000000, - "websocketTrafficTimeout": 30000000000, - "baseCurrencies": "KRW", - "currencyPairs": { - "requestFormat": { - "uppercase": true, - "delimiter": "_" - }, - "configFormat": { - "uppercase": true, - "delimiter": "-" - }, - "useGlobalFormat": true, - "pairs": { - "spot": { - "assetEnabled": true, - "enabled": "USDT-KRW,QTUM-KRW,BTC-KRW,ETH-KRW,ETC-KRW,XRP-KRW,BCH-KRW,BTG-KRW,EOS-KRW", - "available": "AVAX-KRW,STRAX-KRW,KSM-KRW,RPL-KRW,ADA-KRW,ONT-KRW,EOS-KRW,STAT-KRW,APM-KRW,XPLA-KRW,STMX-KRW,FET-KRW,XVS-KRW,ROA-KRW,JOE-KRW,BNT-KRW,T-KRW,AUDIO-KRW,MIX-KRW,PUNDIX-KRW,USDC-KRW,ALGO-KRW,CTXC-KRW,IQ-KRW,RLY-KRW,GRT-KRW,NMR-KRW,FTM-KRW,WNCG-KRW,NCT-KRW,CSPR-KRW,TFUEL-KRW,EGG-KRW,MOC-KRW,BAT-KRW,ETC-KRW,TIA-KRW,GRACY-KRW,FRONT-KRW,DAI-KRW,ANKR-KRW,META-KRW,HOOK-KRW,BEL-KRW,MAGIC-KRW,ENTC-KRW,HUNT-KRW,STX-KRW,FIT-KRW,STEEM-KRW,CTSI-KRW,JUP-KRW,CAKE-KRW,DOGE-KRW,SUN-KRW,OCEAN-KRW,SOL-KRW,REQ-KRW,BNB-KRW,GAL-KRW,MBL-KRW,LRC-KRW,ILV-KRW,PEPE-KRW,IOST-KRW,XLM-KRW,CRV-KRW,NFT-KRW,PYR-KRW,TRX-KRW,TAVA-KRW,PYTH-KRW,TT-KRW,AAVE-KRW,KLAY-KRW,BAL-KRW,EVZ-KRW,FX-KRW,UMA-KRW,FLOW-KRW,ALEX-KRW,ELF-KRW,CVC-KRW,FLOKI-KRW,MASK-KRW,GAS-KRW,VIX-KRW,CELR-KRW,BLY-KRW,ARK-KRW,FNSA-KRW,OXT-KRW,VALOR-KRW,XTZ-KRW,HBAR-KRW,ONG-KRW,MTL-KRW,WAVES-KRW,ORBS-KRW,MANTA-KRW,ICX-KRW,SNX-KRW,API3-KRW,PENDLE-KRW,FLZ-KRW,APE-KRW,POWR-KRW,OGN-KRW,EDU-KRW,ARB-KRW,AXS-KRW,MBX-KRW,XRP-KRW,MATIC-KRW,USDT-KRW,1INCH-KRW,STORJ-KRW,UOS-KRW,RVN-KRW,LPT-KRW,OSMO-KRW,ALICE-KRW,LDO-KRW,TEMCO-KRW,COMP-KRW,VET-KRW,SFP-KRW,WIKEN-KRW,LBL-KRW,SHIB-KRW,GMT-KRW,AZIT-KRW,ZBCN-KRW,FLUX-KRW,ALT-KRW,AGI-KRW,SPURS-KRW,GRS-KRW,C98-KRW,ZIL-KRW,BCH-KRW,QTCON-KRW,SEI-KRW,GRND-KRW,SWAP-KRW,ETH-KRW,RSS3-KRW,STPT-KRW,FXS-KRW,SAND-KRW,MAP-KRW,MAV-KRW,LINK-KRW,MVC-KRW,QTUM-KRW,DAR-KRW,FANC-KRW,HIGH-KRW,ARKM-KRW,MANA-KRW,SUSHI-KRW,DVI-KRW,XEC-KRW,BTC-KRW,EL-KRW,THETA-KRW,CELO-KRW,KNC-KRW,POLA-KRW,LOOM-KRW,JASMY-KRW,INJ-KRW,KAVA-KRW,NEO-KRW,BIGTIME-KRW,MINA-KRW,NPT-KRW,IMX-KRW,ASM-KRW,FCT2-KRW,RLC-KRW,HIFI-KRW,CTC-KRW,DYDX-KRW,ZTX-KRW,AGIX-KRW,WEMIX-KRW,GTC-KRW,LM-KRW,OP-KRW,ONIT-KRW,ACS-KRW,LSK-KRW,REI-KRW,ATOM-KRW,WLD-KRW,GLM-KRW,COS-KRW,BTT-KRW,BFC-KRW,ACE-KRW,SC-KRW,BORA-KRW,GHX-KRW,ADP-KRW,STRK-KRW,LEVER-KRW,BOBA-KRW,BOA-KRW,HFT-KRW,RNDR-KRW,ENJ-KRW,RSR-KRW,XPR-KRW,IOTX-KRW,CYBER-KRW,WAXP-KRW,OBSR-KRW,MEV-KRW,UNI-KRW,APT-KRW,DAO-KRW,WAXL-KRW,SIX-KRW,GMX-KRW,RDNT-KRW,BTG-KRW,MNT-KRW,BLUR-KRW,XCN-KRW,YGG-KRW,MXC-KRW,ACH-KRW,RAD-KRW,MLK-KRW,DOT-KRW,JST-KRW,ZRX-KRW,STG-KRW,SOFI-KRW,WOM-KRW,TDROP-KRW,SNT-KRW,COTI-KRW,WOO-KRW,OAS-KRW,CRO-KRW,AQT-KRW,EGLD-KRW,ARPA-KRW,BSV-KRW,ASTR-KRW,AMO-KRW,AERGO-KRW,ID-KRW,SUI-KRW,GALA-KRW,CKB-KRW,BIOT-KRW,CFX-KRW,CHR-KRW,FLR-KRW,FITFI-KRW,YFI-KRW,CTK-KRW,W-KRW,MED-KRW,MKR-KRW,SXP-KRW,HIVE-KRW,CRTS-KRW,CHZ-KRW" - } - } - }, - "api": { - "authenticatedSupport": false, - "authenticatedWebsocketApiSupport": false, - "endpoints": { - "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" - }, - "credentials": { - "key": "Key", - "secret": "Secret", - "clientID": "ClientID" - }, - "credentialsValidator": { - "requiresKey": true, - "requiresSecret": true - } - }, - "features": { - "supports": { - "restAPI": true, - "restCapabilities": { - "tickerBatching": true, - "autoPairUpdates": true - }, - "websocketAPI": false, - "websocketCapabilities": {} - }, - "enabled": { - "autoPairUpdates": true, - "websocketAPI": false - } - }, - "bankAccounts": [ - { - "enabled": false, - "bankName": "", - "bankAddress": "", - "bankPostalCode": "", - "bankPostalCity": "", - "bankCountry": "", - "accountName": "", - "accountNumber": "", - "swiftCode": "", - "iban": "", - "supportedCurrencies": "" - } - ], - "orderbook": { - "verificationBypass": false, - "websocketBufferLimit": 5, - "websocketBufferEnabled": false, - "publishPeriod": 10000000000 + "bankAccounts": [ + { + "enabled": false, + "bankName": "", + "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ] + }, + { + "name": "BTSE", + "enabled": true, + "verbose": false, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, + "websocketOrderbookBufferLimit": 5, + "baseCurrencies": "USD", + "currencyPairs": { + "requestFormat": { + "uppercase": true, + "delimiter": "-" + }, + "configFormat": { + "uppercase": true, + "delimiter": "-" + }, + "useGlobalFormat": true, + "assetTypes": [ + "spot" + ], + "pairs": { + "spot": { + "enabled": "BTC-USD", + "available": "BTC-CNY,BTC-EUR,BTC-GBP,BTC-HKD,BTC-JPY,BTC-SGD,BTC-USD,ETH-CNY,ETH-EUR,ETH-GBP,ETH-HKD,ETH-JPY,ETH-SGD,ETH-USD,LTC-CNY,LTC-EUR,LTC-GBP,LTC-HKD,LTC-JPY,LTC-SGD,LTC-USD,USDT-CNY,USDT-EUR,USDT-GBP,USDT-HKD,USDT-JPY,USDT-SGD,USDT-USD,XMR-CNY,XMR-EUR,XMR-GBP,XMR-HKD,XMR-JPY,XMR-SGD,XMR-USD" } + } }, - { - "name": "Bitmex", - "enabled": true, - "verbose": false, - "httpTimeout": 15000000000, - "websocketResponseCheckTimeout": 30000000, - "websocketResponseMaxLimit": 7000000000, - "websocketTrafficTimeout": 30000000000, - "websocketOrderbookBufferLimit": 5, - "baseCurrencies": "USD", - "currencyPairs": { - "assetTypes": [ - "perpetualcontract", - "futures", - "downsideprofitcontract", - "upsideprofitcontract" - ], - "pairs": { - "downsideprofitcontract": { - "enabled": "XBT7D_D95", - "available": "XBT7D_D95", - "requestFormat": { - "uppercase": true, - "delimiter": "_" - }, - "configFormat": { - "uppercase": true, - "delimiter": "_" - } - }, - "futures": { - "enabled": "BCHZ19", - "available": "XRPZ19,BCHZ19,ADAZ19,EOSZ19,TRXZ19,XBTZ19,ETHZ19,LTCZ19", - "requestFormat": { - "uppercase": true - }, - "configFormat": { - "uppercase": true - } - }, - "perpetualcontract": { - "enabled": "ETHUSD", - "available": "XBTUSD,ETHUSD", - "requestFormat": { - "uppercase": true - }, - "configFormat": { - "uppercase": true - } - }, - "upsideprofitcontract": { - "enabled": "XBT7D_U105", - "available": "XBT7D_U105", - "requestFormat": { - "uppercase": true, - "delimiter": "_" - }, - "configFormat": { - "uppercase": true, - "delimiter": "_" - } - } - } - }, - "api": { - "authenticatedSupport": false, - "authenticatedWebsocketApiSupport": false, - "endpoints": { - "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" - }, - "credentials": { - "key": "Key", - "secret": "Secret" - }, - "credentialsValidator": { - "requiresKey": true, - "requiresSecret": true - } - }, - "features": { - "supports": { - "restAPI": true, - "restCapabilities": { - "tickerBatching": true, - "autoPairUpdates": true - }, - "websocketAPI": true, - "websocketCapabilities": {} - }, - "enabled": { - "autoPairUpdates": true, - "websocketAPI": false - } - }, - "bankAccounts": [ - { - "enabled": false, - "bankName": "", - "bankAddress": "", - "bankPostalCode": "", - "bankPostalCity": "", - "bankCountry": "", - "accountName": "", - "accountNumber": "", - "swiftCode": "", - "iban": "", - "supportedCurrencies": "" - } - ] + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true + } }, - { - "name": "Bitstamp", - "enabled": true, - "verbose": false, - "httpTimeout": 15000000000, - "websocketResponseCheckTimeout": 30000000, - "websocketResponseMaxLimit": 7000000000, - "websocketTrafficTimeout": 30000000000, - "websocketOrderbookBufferLimit": 5, - "baseCurrencies": "USD,EUR", - "currencyPairs": { - "requestFormat": { - "uppercase": true - }, - "configFormat": { - "uppercase": true - }, - "useGlobalFormat": true, - "assetTypes": [ - "spot" - ], - "pairs": { - "spot": { - "enabled": "BTCUSD,BTCEUR,EURUSD,XRPUSD,XRPEUR", - "available": "LTCUSD,ETHUSD,XRPEUR,BCHUSD,BCHEUR,BTCEUR,XRPBTC,EURUSD,BCHBTC,LTCEUR,BTCUSD,LTCBTC,XRPUSD,ETHBTC,ETHEUR" - } - } - }, - "api": { - "authenticatedSupport": false, - "authenticatedWebsocketApiSupport": false, - "endpoints": { - "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" - }, - "credentials": { - "key": "Key", - "secret": "Secret", - "clientID": "ClientID" - }, - "credentialsValidator": { - "requiresKey": true, - "requiresSecret": true, - "requiresClientID": true - } - }, - "features": { - "supports": { - "restAPI": true, - "restCapabilities": { - "autoPairUpdates": true - }, - "websocketAPI": true, - "websocketCapabilities": {} - }, - "enabled": { - "autoPairUpdates": true, - "websocketAPI": false - } - }, - "bankAccounts": [ - { - "enabled": false, - "bankName": "", - "bankAddress": "", - "bankPostalCode": "", - "bankPostalCity": "", - "bankCountry": "", - "accountName": "", - "accountNumber": "", - "swiftCode": "", - "iban": "", - "supportedCurrencies": "" - } - ] + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "autoPairUpdates": true + }, + "websocketAPI": true, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": true + } }, - { - "name": "Bybit", - "enabled": true, - "verbose": false, - "httpTimeout": 15000000000, - "websocketResponseCheckTimeout": 30000000, - "websocketResponseMaxLimit": 7000000000, - "websocketTrafficTimeout": 30000000000, - "websocketOrderbookBufferLimit": 5, - "baseCurrencies": "USD", - "currencyPairs": { - "assetTypes": [ - "spot", - "margin", - "coinmarginedfutures", - "usdtmarginedfutures", - "usdcmarginedfutures", - "options" - ], - "pairs": { - "spot": { - "enabled": "BTC_USDT,ETH_USDT,XRP_USDT,EOS_USDT,ETH_BTC,XRP_BTC,DOT_USDT,XLM_USDT,LTC_USDT", - "available": "BTC_USDT,ETH_USDT,XRP_USDT,EOS_USDT,ETH_BTC,XRP_BTC,DOT_USDT,XLM_USDT,LTC_USDT,DOGE_USDT,CHZ_USDT,AXS_USDT,MANA_USDT,DYDX_USDT,MKR_USDT,COMP_USDT,AAVE_USDT,YFI_USDT,LINK_USDT,SUSHI_USDT,UNI_USDT,KSM_USDT,ICP_USDT,ADA_USDT,ETC_USDT,KLAY_USDT,XTZ_USDT,BCH_USDT,SRM_USDT,QNT_USDT,USDC_USDT,GRT_USDT,SOL_USDT,FIL_USDT,OMG_USDT,TRIBE_USDT,BAT_USDT,ZRX_USDT,CRV_USDT,AGLD_USDT,ANKR_USDT,PERP_USDT,MATIC_USDT,WAVES_USDT,LUNC_USDT,SPELL_USDT,SHIB_USDT,FTM_USDT,ATOM_USDT,ALGO_USDT,ENJ_USDT,CBX_USDT,SAND_USDT,AVAX_USDT,WOO_USDT,FTT_USDT,GODS_USDT,IMX_USDT,ENS_USDT,GM_USDT,CWAR_USDT,CAKE_USDT,STETH_USDT,GALFT_USDT,LFW_USDT,SLP_USDT,C98_USDT,PSP_USDT,GENE_USDT,AVA_USDT,ONE_USDT,PTU_USDT,SHILL_USDT,XYM_USDT,BOBA_USDT,JASMY_USDT,GALA_USDT,RNDR_USDT,TRVL_USDT,WEMIX_USDT,XEM_USDT,BICO_USDT,CEL_USDT,UMA_USDT,HOT_USDT,NEXO_USDT,BNT_USDT,SNX_USDT,REN_USDT,1INCH_USDT,TEL_USDT,SIS_USDT,LRC_USDT,LDO_USDT,REAL_USDT,KRL_USDT,DEVT_USDT,ETH_USDC,BTC_USDC,1SOL_USDT,PLT_USDT,IZI_USDT,QTUM_USDT,DCR_USDT,ZEN_USDT,THETA_USDT,MX_USDT,DGB_USDT,RVN_USDT,EGLD_USDT,RUNE_USDT,XLM_BTC,XLM_USDC,SOL_USDC,XRP_USDC,ALGO_BTC,SOL_BTC,RAIN_USDT,XEC_USDT,ICX_USDT,XDC_USDT,HNT_USDT,BTG_USDT,ZIL_USDT,HBAR_USDT,FLOW_USDT,SOS_USDT,KASTA_USDT,STX_USDT,SIDUS_USDT,VPAD_USDT,GGM_USDT,LOOKS_USDT,MBS_USDT,DAI_USDT,BUSD_USDT,ACA_USDT,MV_USDT,MIX_USDT,LTC_USDC,MANA_BTC,MATIC_BTC,LTC_BTC,DOT_BTC,SAND_BTC,MANA_USDC,MATIC_USDC,SAND_USDC,DOT_USDC,LUNC_USDC,RSS3_USDT,SYNR_USDT,TAP_USDT,ERTHA_USDT,GMX_USDT,T_USDT,ACH_USDT,JST_USDT,SUN_USDT,BTT_USDT,TRX_USDT,NFT_USDT,POKT_USDT,SCRT_USDT,PSTAKE_USDT,SON_USDT,HERO_USDT,DOME_USDT,USTC_USDT,BNB_USDT,NEAR_USDT,PAXG_USDT,SD_USDT,APE_USDT,BTC3S_USDT,BTC3L_USDT,FIDA_USDT,MINA_USDT,SC_USDT,RACA_USDT,CAPS_USDT,STG_USDT,GLMR_USDT,MOVR_USDT,ZAM_USDT,ETH_DAI,BTC_DAI,WBTC_USDT,XAVA_USDT,MELOS_USDT,GMT_USDT,GST_USDT,CELO_USDT,SFUND_USDT,ELT_USDT,LGX_USDT,APEX_USDT,CTC_USDT,COT_USDT,KMON_USDT,PLY_USDT,XWG_USDT,FITFI_USDT,STRM_USDT,GAL_USDT,ETH3S_USDT,ETH3L_USDT,KOK_USDT,FAME_USDT,XRP3S_USDT,XRP3L_USDT,USDD_USDT,OP_USDT,LUNA_USDT,DFI_USDT,MOVEZ_USDT,THN_USDT,DOT3S_USDT,DOT3L_USDT,VINU_USDT,BEL_USDT,FORT_USDT,AVAX2S_USDT,AVAX2L_USDT,ADA2S_USDT,ADA2L_USDT,WLKN_USDT,KON_USDT,LTC2S_USDT,LTC2L_USDT,SAND2S_USDT,SAND2L_USDT,OBX_USDT,SEOR_USDT,MNZ_USDT,CULT_USDT,DOGE_USDC,EOS_USDC,CUSD_USDT,SLG_USDT,CMP_USDT,KUNCI_USDT,GSTS_USDT,XETA_USDT,AZY_USDT,MMC_USDT,FLOKI_USDT,BABYDOGE_USDT,STAT_USDT,SAITAMA_USDT,MATIC2S_USDT,MATIC2L_USDT,ETC2S_USDT,ETC2L_USDT,DICE_USDT,WAXP_USDT,AR_USDT,KDA_USDT,ROSE_USDT,SLG_USDC,APE2S_USDT,APE2L_USDT,GMT2S_USDT,GMT2L_USDT,DEFY_USDT,PSG_USDT,BAR_USDT,JUV_USDT,ACM_USDT,INTER_USDT,AFC_USDT,CITY_USDT,LINK2L_USDT,LINK2S_USDT,FTM2L_USDT,FTM2S_USDT,SOLO_USDT,W_BTC,AVAX_USDC,ADA_USDC,OP_USDC,DOGE2S_USDT,DOGE2L_USDT,ATOM2S_USDT,ATOM2L_USDT,APEX_USDC,TRX_USDC,ICP_USDC,LINK_USDC,GMT_USDC,CHZ_USDC,SHIB_USDC,LDO_USDC,APE_USDC,FIL_USDC,CHRP_USDT,EOS2S_USDT,EOS2L_USDT,WWY_USDT,LING_USDT,SWEAT_USDT,DLC_USDT,OKG_USDT,ETHW_USDT,INJ_USDT,MPLX_USDT,MIBR_USDT,CO_USDT,AGLA_USDT,ROND_USDT,QMALL_USDT,PUMLX_USDT,GCAKE_USDT,APT_USDT,APT_USDC,USDT_EUR,MTK_USDT,MCRT_USDT,MASK_USDT,ECOX_USDT,HFT_USDC,HFT_USDT,KCAL_USDT,PEOPLE_USDT,TWT_USDT,ORT_USDT,HOOK_USDT,PRIMAL_USDT,MCT_USDT,OAS_USDT,MAGIC_USDT,MEE_USDT,TON_USDT,BONK_USDT,FLR_USDT,TIME_USDT,3P_USDT,RPL_USDT,SSV_USDT,FXS_USDT,CORE_USDT,RDNT_USDT,BLUR_USDT,LIS_USDT,AGIX_USDT,MDAO_USDT,ACS_USDT,HVH_USDT,GNS_USDT,DPX_USDT,PIP_USDT,PRIME_USDT,EVER_USDT,VRA_USDT,GPT_USDT,FB_USDT,DZOO_USDT,ID_USDT,ARB_USDC,ARB_USDT,XCAD_USDT,MBX_USDT,AXL_USDT,CGPT_USDT,PLAY_USDT,AGI_USDT,RLTM_USDT,SUI_USDT,SUI_USDC,TAMA_USDT,MVL_USDT,PEPE_USDT,LADYS_USDT,LMWR_USDT,BOB_USDT,TOMI_USDT,KARATE_USDT,SUIA_USDT,TURBOS_USDT,FMB_USDT,CAPO_USDT,TENET_USDT,VELO_USDT,ELDA_USDT,CANDY_USDT,FON_USDT,OMN_USDT,TOMS_USDT,MTC_USDT,VELA_USDT,USDT_BRZ,BTC_BRZ,PENDLE_USDT,EGO_USDT,PEPE2_USDT,NYM_USDT,MNT_USDT,MNT_USDC,MNT_BTC,GSWIFT_USDT,SALD_USDT,ARKM_USDT,NEON_USDT,WLD_USDC,WLD_USDT,PLANET_USDT,DSRUN_USDT,SPARTA_USDT,TAVA_USDT,SEILOR_USDT,SEI_USDT,CYBER_USDT,ORDI_USDT,KAVA_USDT,VV_USDT,SAIL_USDT,PYUSD_USDT,SOL_EUR,USDC_EUR,ADA_EUR,DOGE_EUR,LTC_EUR,XRP_EUR,ETH_EUR,BTC_EUR,VEXT_USDT,CTT_USDT,NEXT_USDT,KAS_USDT,NESS_USDT,CAT_USDT,FET_USDT,LEVER_USDT,VEGA_USDT,ZTX_USDT", - "requestFormat": { - "uppercase": true - }, - "configFormat": { - "uppercase": true, - "delimiter": "_" - } - }, - "coinmarginedfutures": { - "enabled": "ADA_USD,BTC_USD,BTC_USDH24,BTC_USDZ23,DOT_USD", - "available": "ADA_USD,BTC_USD,BTC_USDH24,BTC_USDZ23,DOT_USD,EOS_USD,ETH_USD,ETH_USDH24,ETH_USDZ23,LTC_USD,MAN_AUSD,XRP_USD", - "requestFormat": { - "uppercase": true - }, - "configFormat": { - "uppercase": true, - "delimiter": "_" - } - }, - "usdcmarginedfutures": { - "enabled": "ETH-PERP,BNB-PERP,SOL-PERP,BTC-PERP", - "available": "BNB-PERP,BTC-03NOV23,BTC-20OCT23,BTC-24NOV23,BTC-27OCT23,BTC-28JUN24,BTC-29DEC23,BTC-29MAR24,BTC-PERP,ETC-PERP,ETH-03NOV23,ETH-20OCT23,ETH-24NOV23,ETH-27OCT23,ETH-28JUN24,ETH-29DEC23,ETH-29MAR24,ETH-PERP,MAT-ICPERP,OPP-ERP,SOL-PERP,XRP-PERP", - "requestFormat": { - "uppercase": true - }, - "configFormat": { - "uppercase": true, - "delimiter": "-" - } - }, - "usdtmarginedfutures": { - "enabled": "BTC_USDT,10000LADYS_USDT,IOTA_USDT,AAVE_USDT", - "available": "10000LADYS_USDT,10000NFT_USDT,1000BONK_USDT,1000BTT_USDT,1000FLOKI_USDT,1000LUNC_USDT,1000PEPE_USDT,1000XEC_USDT,1INCH_USDT,AAVE_USDT,ACH_USDT,ADA_USDT,AGIX_USDT,AGLD_USDT,AKRO_USDT,ALGO_USDT,ALICE_USDT,ALPACA_USDT,ALPHA_USDT,AMB_USDT,ANKR_USDT,ANT_USDT,APE_USDT,API3_USDT,APT_USDT,ARB_USDT,ARKM_USDT,ARK_USDT,ARPA_USDT,AR_USDT,ASTR_USDT,ATA_USDT,ATOM_USDT,AUCTION_USDT,AUDIO_USDT,AVAX_USDT,AXS_USDT,BADGER_USDT,BAKE_USDT,BAL_USDT,BAND_USDT,BAT_USDT,BCH_USDT,BEL_USDT,BICO_USDT,BIGTIME_USDT,BLUR_USDT,BLZ_USDT,BNB_USDT,BNT_USDT,BNX_USDT,BOBA_USDT,BOND_USDT,BSV_USDT,BSW_USDT,BTC_USDT,BUSD_USDT,C98_USDT,CEEK_USDT,CELO_USDT,CELR_USDT,CFX_USDT,CHR_USDT,CHZ_USDT,CKB_USDT,COMBO_USDT,COMP_USDT,CORE_USDT,COTI_USDT,CRO_USDT,CRV_USDT,CTC_USDT,CTK_USDT,CTSI_USDT,CVC_USDT,CVX_USDT,CYBER_USDT,DAR_USDT,DASH_USDT,DENT_USDT,DGB_USDT,DODO_USDT,DOGE_USDT,DOT_USDT,DUSK_USDT,DYDX_USDT,EDU_USDT,EGLD_USDT,ENJ_USDT,ENS_USDT,EOS_USDT,ETC_USDT,ETH_USDT,ETHW_USDT,FET_USDT,FIL_USDT,FITFI_USDT,FLM_USDT,FLOW_USDT,FLR_USDT,FORTH_USDT,FRONT_USDT,FTM_USDT,FXS_USDT,GALA_USDT,GAL_USDT,GFT_USDT,GLMR_USDT,GLM_USDT,GMT_USDT,GMX_USDT,GPT_USDT,GRT_USDT,GTC_USDT,HBAR_USDT,HFT_USDT,HIFI_USDT,HIGH_USDT,HNT_USDT,HOOK_USDT,HOT_USDT,ICP_USDT,ICX_USDT,IDEX_USDT,ID_USDT,ILV_USDT,IMX_USDT,INJ_USDT,IOST_USDT,IOTA_USDT,IOTX_USDT,JASMY_USDT,JOE_USDT,JST_USDT,KAS_USDT,KAVA_USDT,KDA_USDT,KEY_USDT,KLAY_USDT,KNC_USDT,KSM_USDT,LDO_USDT,LEVER_USDT,LINA_USDT,LINK_USDT,LIT_USDT,LOOKS_USDT,LOOM_USDT,LPT_USDT,LQTY_USDT,LRC_USDT,LTC_USDT,LUNA2_USDT,MAGIC_USDT,MANA_USDT,MASK_USDT,MATIC_USDT,MAV_USDT,MC_USDT,MDT_USDT,MINA_USDT,MKR_USDT,MNT_USDT,MTL_USDT,MULTI_USDT,NEAR_USDT,NEO_USDT,NKN_USDT,NMR_USDT,NTRN_USDT,OCEAN_USDT,OGN_USDT,OG_USDT,OMG_USDT,ONE_USDT,ONT_USDT,OP_USDT,ORBS_USDT,ORDI_USDT,OXT_USDT,PAXG_USDT,PENDLE_USDT,PEOPLE_USDT,PERP_USDT,PHB_USDT,PROM_USDT,QNT_USDT,QTUM_USDT,RAD_USDT,RDNT_USDT,REEF_USDT,REN_USDT,REQ_USDT,RLC_USDT,RNDR_USDT,ROSE_USDT,RPL_USDT,RSR_USDT,RSS3_USDT,RUNE_USDT,RVN_USDT,SAND_USDT,SCRT_USDT,SC_USDT,SEI_USDT,SFP_USDT,SHIB1000_USDT,SKL_USDT,SLP_USDT,SNX_USDT,SOL_USDT,SPELL_USDT,SSV_USDT,STG_USDT,STMX_USDT,STORJ_USDT,STPT_USDT,STRAX_USDT,STX_USDT,SUI_USDT,SUN_USDT,SUSHI_USDT,SWEAT_USDT,SXP_USDT,THETA_USDT,TLM_USDT,TOMI_USDT,TOMO_USDT,TON_USDT,TRB_USDT,TRU_USDT,TRX_USDT,T_USDT,TWT_USDT,UMA_USDT,UNFI_USDT,UNI_USDT,USDC_USDT,VET_USDT,VGX_USDT,VRA_USDT,WAVES_USDT,WAXP_USDT,WLD_USDT,WOO_USDT,WSM_USDT,XCN_USDT,XEM_USDT,XLM_USDT,XMR_USDT,XNO_USDT,XRP_USDT,XTZ_USDT,XVG_USDT,XVS_USDT,YFII_USDT,YFI_USDT,YGG_USDT,ZEC_USDT,ZEN_USDT,ZIL_USDT,ZRX_USDT", - "requestFormat": { - "uppercase": true - }, - "configFormat": { - "uppercase": true, - "delimiter": "_" - } - }, - "options": { - "enabled": "BTC-28JUN24-70000-C,BTC-28JUN24-70000-P,BTC-28JUN24-60000-C,BTC-28JUN24-60000-P", - "available": "BTC-28JUN24-70000-C,BTC-28JUN24-70000-P,BTC-28JUN24-60000-C,BTC-28JUN24-60000-P,BTC-28JUN24-50000-C,BTC-28JUN24-50000-P,BTC-28JUN24-40000-C,BTC-28JUN24-40000-P,BTC-28JUN24-32000-C,BTC-28JUN24-32000-P,BTC-28JUN24-30000-C,BTC-28JUN24-30000-P,BTC-28JUN24-28000-C,BTC-28JUN24-28000-P,BTC-28JUN24-25000-C,BTC-28JUN24-25000-P,BTC-28JUN24-20000-C,BTC-28JUN24-20000-P,BTC-28JUN24-10000-C,BTC-28JUN24-10000-P,BTC-29MAR24-70000-C,BTC-29MAR24-70000-P,BTC-29MAR24-60000-C,BTC-29MAR24-60000-P,BTC-29MAR24-50000-C,BTC-29MAR24-50000-P,BTC-29MAR24-45000-C,BTC-29MAR24-45000-P,BTC-29MAR24-40000-C,BTC-29MAR24-40000-P,BTC-29MAR24-36000-C,BTC-29MAR24-36000-P,BTC-29MAR24-35000-C,BTC-29MAR24-35000-P,BTC-29MAR24-33000-C,BTC-29MAR24-33000-P,BTC-29MAR24-31000-C,BTC-29MAR24-31000-P,BTC-29MAR24-30000-C,BTC-29MAR24-30000-P,BTC-29MAR24-28000-C,BTC-29MAR24-28000-P,BTC-29MAR24-27000-C,BTC-29MAR24-27000-P,BTC-29MAR24-26000-C,BTC-29MAR24-26000-P,BTC-29MAR24-24000-C,BTC-29MAR24-24000-P,BTC-29MAR24-20000-C,BTC-29MAR24-20000-P,BTC-29MAR24-10000-C,BTC-29MAR24-10000-P,BTC-29DEC23-80000-C,BTC-29DEC23-80000-P,BTC-29DEC23-70000-C,BTC-29DEC23-70000-P,BTC-29DEC23-60000-C,BTC-29DEC23-60000-P,BTC-29DEC23-50000-C,BTC-29DEC23-50000-P,BTC-29DEC23-40000-C,BTC-29DEC23-40000-P,BTC-29DEC23-36000-C,BTC-29DEC23-36000-P,BTC-29DEC23-35000-C,BTC-29DEC23-35000-P,BTC-29DEC23-34000-C,BTC-29DEC23-34000-P,BTC-29DEC23-32000-C,BTC-29DEC23-32000-P,BTC-29DEC23-31500-C,BTC-29DEC23-31500-P,BTC-29DEC23-30500-C,BTC-29DEC23-30500-P,BTC-29DEC23-30000-C,BTC-29DEC23-30000-P,BTC-29DEC23-29500-C,BTC-29DEC23-29500-P,BTC-29DEC23-29000-C,BTC-29DEC23-29000-P,BTC-29DEC23-28000-C,BTC-29DEC23-28000-P,BTC-29DEC23-27500-C,BTC-29DEC23-27500-P,BTC-29DEC23-27000-C,BTC-29DEC23-27000-P,BTC-29DEC23-26000-C,BTC-29DEC23-26000-P,BTC-29DEC23-25000-C,BTC-29DEC23-25000-P,BTC-29DEC23-24000-C,BTC-29DEC23-24000-P,BTC-29DEC23-22000-C,BTC-29DEC23-22000-P,BTC-29DEC23-20000-C,BTC-29DEC23-20000-P,BTC-29DEC23-15000-C,BTC-29DEC23-15000-P,BTC-29DEC23-10000-C,BTC-29DEC23-10000-P,BTC-24NOV23-40000-C,BTC-24NOV23-40000-P,BTC-24NOV23-38000-C,BTC-24NOV23-38000-P,BTC-24NOV23-36000-C,BTC-24NOV23-36000-P,BTC-24NOV23-34000-C,BTC-24NOV23-34000-P,BTC-24NOV23-32000-C,BTC-24NOV23-32000-P,BTC-24NOV23-31500-C,BTC-24NOV23-31500-P,BTC-24NOV23-30500-C,BTC-24NOV23-30500-P,BTC-24NOV23-30000-C,BTC-24NOV23-30000-P,BTC-24NOV23-29500-C,BTC-24NOV23-29500-P,BTC-24NOV23-29000-C,BTC-24NOV23-29000-P,BTC-24NOV23-28500-C,BTC-24NOV23-28500-P,BTC-24NOV23-28000-C,BTC-24NOV23-28000-P,BTC-24NOV23-27500-C,BTC-24NOV23-27500-P,BTC-24NOV23-27000-C,BTC-24NOV23-27000-P,BTC-24NOV23-26500-C,BTC-24NOV23-26500-P,BTC-24NOV23-26000-C,BTC-24NOV23-26000-P,BTC-24NOV23-25500-C,BTC-24NOV23-25500-P,BTC-24NOV23-25000-C,BTC-24NOV23-25000-P,BTC-24NOV23-24000-C,BTC-24NOV23-24000-P,BTC-24NOV23-23000-C,BTC-24NOV23-23000-P,BTC-24NOV23-22000-C,BTC-24NOV23-22000-P,BTC-24NOV23-20000-C,BTC-24NOV23-20000-P,BTC-24NOV23-18000-C,BTC-24NOV23-18000-P,BTC-24NOV23-16000-C,BTC-24NOV23-16000-P,BTC-3NOV23-36000-C,BTC-3NOV23-36000-P,BTC-3NOV23-34000-C,BTC-3NOV23-34000-P,BTC-3NOV23-32000-C,BTC-3NOV23-32000-P,BTC-3NOV23-30000-C,BTC-3NOV23-30000-P,BTC-3NOV23-29000-C,BTC-3NOV23-29000-P,BTC-3NOV23-28500-C,BTC-3NOV23-28500-P,BTC-3NOV23-27500-C,BTC-3NOV23-27500-P,BTC-3NOV23-27000-C,BTC-3NOV23-27000-P,BTC-3NOV23-26500-C,BTC-3NOV23-26500-P,BTC-3NOV23-26000-C,BTC-3NOV23-26000-P,BTC-3NOV23-25000-C,BTC-3NOV23-25000-P,BTC-3NOV23-24000-C,BTC-3NOV23-24000-P,BTC-3NOV23-22000-C,BTC-3NOV23-22000-P,BTC-3NOV23-20000-C,BTC-3NOV23-20000-P,BTC-3NOV23-18000-C,BTC-3NOV23-18000-P,BTC-27OCT23-44000-C,BTC-27OCT23-44000-P,BTC-27OCT23-42000-C,BTC-27OCT23-42000-P,BTC-27OCT23-40000-C,BTC-27OCT23-40000-P,BTC-27OCT23-38000-C,BTC-27OCT23-38000-P,BTC-27OCT23-37000-C,BTC-27OCT23-37000-P,BTC-27OCT23-35000-C,BTC-27OCT23-35000-P,BTC-27OCT23-34500-C,BTC-27OCT23-34500-P,BTC-27OCT23-33500-C,BTC-27OCT23-33500-P,BTC-27OCT23-32500-C,BTC-27OCT23-32500-P,BTC-27OCT23-31500-C,BTC-27OCT23-31500-P,BTC-27OCT23-31000-C,BTC-27OCT23-31000-P,BTC-27OCT23-30500-C,BTC-27OCT23-30500-P,BTC-27OCT23-30000-C,BTC-27OCT23-30000-P,BTC-27OCT23-29500-C,BTC-27OCT23-29500-P,BTC-27OCT23-29000-C,BTC-27OCT23-29000-P,BTC-27OCT23-28750-C,BTC-27OCT23-28750-P,BTC-27OCT23-28500-C,BTC-27OCT23-28500-P,BTC-27OCT23-28250-C,BTC-27OCT23-28250-P,BTC-27OCT23-28000-C,BTC-27OCT23-28000-P,BTC-27OCT23-27750-C,BTC-27OCT23-27750-P,BTC-27OCT23-27500-C,BTC-27OCT23-27500-P,BTC-27OCT23-27250-C,BTC-27OCT23-27250-P,BTC-27OCT23-27000-C,BTC-27OCT23-27000-P,BTC-27OCT23-26500-C,BTC-27OCT23-26500-P,BTC-27OCT23-26000-C,BTC-27OCT23-26000-P,BTC-27OCT23-25500-C,BTC-27OCT23-25500-P,BTC-27OCT23-25000-C,BTC-27OCT23-25000-P,BTC-27OCT23-24000-C,BTC-27OCT23-24000-P,BTC-27OCT23-23000-C,BTC-27OCT23-23000-P,BTC-27OCT23-22000-C,BTC-27OCT23-22000-P,BTC-27OCT23-20000-C,BTC-27OCT23-20000-P,BTC-27OCT23-18000-C,BTC-27OCT23-18000-P,BTC-27OCT23-16000-C,BTC-27OCT23-16000-P,BTC-20OCT23-36000-C,BTC-20OCT23-36000-P,BTC-20OCT23-34000-C,BTC-20OCT23-34000-P,BTC-20OCT23-32000-C,BTC-20OCT23-32000-P,BTC-20OCT23-31000-C,BTC-20OCT23-31000-P,BTC-20OCT23-30500-C,BTC-20OCT23-30500-P,BTC-20OCT23-30000-C,BTC-20OCT23-30000-P,BTC-20OCT23-29500-C,BTC-20OCT23-29500-P,BTC-20OCT23-29000-C,BTC-20OCT23-29000-P,BTC-20OCT23-28750-C,BTC-20OCT23-28750-P,BTC-20OCT23-28500-C,BTC-20OCT23-28500-P,BTC-20OCT23-28250-C,BTC-20OCT23-28250-P,BTC-20OCT23-28000-C,BTC-20OCT23-28000-P,BTC-20OCT23-27750-C,BTC-20OCT23-27750-P,BTC-20OCT23-27500-C,BTC-20OCT23-27500-P,BTC-20OCT23-27250-C,BTC-20OCT23-27250-P,BTC-20OCT23-27000-C,BTC-20OCT23-27000-P,BTC-20OCT23-26750-C,BTC-20OCT23-26750-P,BTC-20OCT23-26500-C,BTC-20OCT23-26500-P,BTC-20OCT23-26250-C,BTC-20OCT23-26250-P,BTC-20OCT23-26000-C,BTC-20OCT23-26000-P,BTC-20OCT23-25750-C,BTC-20OCT23-25750-P,BTC-20OCT23-25500-C,BTC-20OCT23-25500-P,BTC-20OCT23-25000-C,BTC-20OCT23-25000-P,BTC-20OCT23-24000-C,BTC-20OCT23-24000-P,BTC-20OCT23-22000-C,BTC-20OCT23-22000-P,BTC-20OCT23-20000-C,BTC-20OCT23-20000-P,BTC-20OCT23-18000-C,BTC-20OCT23-18000-P,BTC-19OCT23-29750-C,BTC-19OCT23-29750-P,BTC-19OCT23-29500-C,BTC-19OCT23-29500-P,BTC-19OCT23-29250-C,BTC-19OCT23-29250-P,BTC-19OCT23-29000-C,BTC-19OCT23-29000-P,BTC-19OCT23-28750-C,BTC-19OCT23-28750-P,BTC-19OCT23-28500-C,BTC-19OCT23-28500-P,BTC-19OCT23-28250-C,BTC-19OCT23-28250-P,BTC-19OCT23-28000-C,BTC-19OCT23-28000-P,BTC-19OCT23-27750-C,BTC-19OCT23-27750-P,BTC-19OCT23-27500-C,BTC-19OCT23-27500-P,BTC-19OCT23-27250-C,BTC-19OCT23-27250-P,BTC-19OCT23-27000-C,BTC-19OCT23-27000-P,BTC-19OCT23-26750-C,BTC-19OCT23-26750-P,BTC-19OCT23-26500-C,BTC-19OCT23-26500-P,BTC-19OCT23-26250-C,BTC-19OCT23-26250-P,BTC-19OCT23-26000-C,BTC-19OCT23-26000-P,BTC-19OCT23-25750-C,BTC-19OCT23-25750-P,BTC-18OCT23-29500-C,BTC-18OCT23-29500-P,BTC-18OCT23-29250-C,BTC-18OCT23-29250-P,BTC-18OCT23-29000-C,BTC-18OCT23-29000-P,BTC-18OCT23-28750-C,BTC-18OCT23-28750-P,BTC-18OCT23-28500-C,BTC-18OCT23-28500-P,BTC-18OCT23-28250-C,BTC-18OCT23-28250-P,BTC-18OCT23-28000-C,BTC-18OCT23-28000-P,BTC-18OCT23-27750-C,BTC-18OCT23-27750-P,BTC-18OCT23-27500-C,BTC-18OCT23-27500-P,BTC-18OCT23-27250-C,BTC-18OCT23-27250-P,BTC-18OCT23-27000-C,BTC-18OCT23-27000-P,BTC-18OCT23-26750-C,BTC-18OCT23-26750-P,BTC-18OCT23-26500-C,BTC-18OCT23-26500-P,BTC-18OCT23-26250-C,BTC-18OCT23-26250-P,BTC-18OCT23-26000-C,BTC-18OCT23-26000-P,BTC-18OCT23-25750-C,BTC-18OCT23-25750-P,BTC-18OCT23-25500-C,BTC-18OCT23-25500-P,BTC-17OCT23-29250-C,BTC-17OCT23-29250-P,BTC-17OCT23-29000-C,BTC-17OCT23-29000-P,BTC-17OCT23-28750-C,BTC-17OCT23-28750-P,BTC-17OCT23-28500-C,BTC-17OCT23-28500-P,BTC-17OCT23-28250-C,BTC-17OCT23-28250-P,BTC-17OCT23-28000-C,BTC-17OCT23-28000-P,BTC-17OCT23-27750-C,BTC-17OCT23-27750-P,BTC-17OCT23-27500-C,BTC-17OCT23-27500-P,BTC-17OCT23-27250-C,BTC-17OCT23-27250-P,BTC-17OCT23-27000-C,BTC-17OCT23-27000-P,BTC-17OCT23-26750-C,BTC-17OCT23-26750-P,BTC-17OCT23-26500-C,BTC-17OCT23-26500-P,BTC-17OCT23-26250-C,BTC-17OCT23-26250-P,BTC-17OCT23-26000-C,BTC-17OCT23-26000-P,BTC-17OCT23-25750-C,BTC-17OCT23-25750-P,BTC-17OCT23-25500-C,BTC-17OCT23-25500-P", - "requestFormat": { - "uppercase": true, - "delimiter": "-" - }, - "configFormat": { - "uppercase": true, - "delimiter": "-" - } - } - } - }, - "api": { - "authenticatedSupport": false, - "authenticatedWebsocketApiSupport": false, - "endpoints": { - "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" - }, - "credentials": { - "key": "Key", - "secret": "Secret" - }, - "credentialsValidator": { - "requiresKey": true, - "requiresSecret": true - } - }, - "features": { - "supports": { - "restAPI": true, - "restCapabilities": { - "tickerBatching": true, - "autoPairUpdates": true - }, - "websocketAPI": true, - "websocketCapabilities": {} - }, - "enabled": { - "autoPairUpdates": true, - "websocketAPI": true - } - }, - "bankAccounts": [ - { - "enabled": false, - "bankName": "", - "bankAddress": "", - "bankPostalCode": "", - "bankPostalCity": "", - "bankCountry": "", - "accountName": "", - "accountNumber": "", - "swiftCode": "", - "iban": "", - "supportedCurrencies": "" - } - ] + "bankAccounts": [ + { + "enabled": false, + "bankName": "", + "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ] + }, + { + "name": "Binance", + "enabled": true, + "verbose": false, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, + "websocketOrderbookBufferLimit": 5, + "baseCurrencies": "USD", + "currencyPairs": { + "requestFormat": { + "uppercase": true + }, + "configFormat": { + "uppercase": true, + "delimiter": "-" + }, + "useGlobalFormat": true, + "assetTypes": [ + "spot" + ], + "pairs": { + "spot": { + "enabled": "BTC-USDT,DOGE-USDT", + "available": "ETH-BTC,LTC-BTC,BNB-BTC,NEO-BTC,QTUM-ETH,EOS-ETH,SNT-ETH,BNT-ETH,GAS-BTC,BNB-ETH,BTC-USDT,ETH-USDT,OAX-ETH,DNT-ETH,MCO-ETH,MCO-BTC,WTC-BTC,WTC-ETH,LRC-BTC,LRC-ETH,QTUM-BTC,YOYO-BTC,OMG-BTC,OMG-ETH,ZRX-BTC,ZRX-ETH,STRAT-BTC,STRAT-ETH,SNGLS-BTC,BQX-BTC,BQX-ETH,KNC-BTC,KNC-ETH,FUN-BTC,FUN-ETH,SNM-BTC,SNM-ETH,NEO-ETH,IOTA-BTC,IOTA-ETH,LINK-BTC,LINK-ETH,XVG-BTC,XVG-ETH,MDA-BTC,MDA-ETH,MTL-BTC,MTL-ETH,EOS-BTC,SNT-BTC,ETC-ETH,ETC-BTC,MTH-BTC,MTH-ETH,ENG-BTC,ENG-ETH,DNT-BTC,ZEC-BTC,ZEC-ETH,BNT-BTC,AST-BTC,AST-ETH,DASH-BTC,DASH-ETH,OAX-BTC,BTG-BTC,BTG-ETH,EVX-BTC,EVX-ETH,REQ-BTC,REQ-ETH,VIB-BTC,VIB-ETH,TRX-BTC,TRX-ETH,POWR-BTC,POWR-ETH,ARK-BTC,ARK-ETH,YOYO-ETH,XRP-BTC,XRP-ETH,ENJ-BTC,ENJ-ETH,STORJ-BTC,STORJ-ETH,BNB-USDT,YOYO-BNB,POWR-BNB,KMD-BTC,KMD-ETH,NULS-BNB,RCN-BTC,RCN-ETH,RCN-BNB,NULS-BTC,NULS-ETH,RDN-BTC,RDN-ETH,RDN-BNB,XMR-BTC,XMR-ETH,DLT-BNB,WTC-BNB,DLT-BTC,DLT-ETH,AMB-BTC,AMB-ETH,AMB-BNB,BAT-BTC,BAT-ETH,BAT-BNB,BCPT-BTC,BCPT-ETH,BCPT-BNB,ARN-BTC,ARN-ETH,GVT-BTC,GVT-ETH,CDT-BTC,CDT-ETH,GXS-BTC,GXS-ETH,NEO-USDT,NEO-BNB,POE-BTC,POE-ETH,QSP-BTC,QSP-ETH,QSP-BNB,BTS-BTC,BTS-ETH,XZC-BTC,XZC-ETH,XZC-BNB,LSK-BTC,LSK-ETH,LSK-BNB,TNT-BTC,TNT-ETH,FUEL-BTC,MANA-BTC,MANA-ETH,BCD-BTC,BCD-ETH,DGD-BTC,DGD-ETH,IOTA-BNB,ADX-BTC,ADX-ETH,ADA-BTC,ADA-ETH,PPT-BTC,PPT-ETH,CMT-BTC,CMT-ETH,CMT-BNB,XLM-BTC,XLM-ETH,XLM-BNB,CND-BTC,CND-ETH,CND-BNB,LEND-BTC,LEND-ETH,WABI-BTC,WABI-ETH,WABI-BNB,LTC-ETH,LTC-USDT,LTC-BNB,TNB-BTC,TNB-ETH,WAVES-BTC,WAVES-ETH,WAVES-BNB,GTO-BTC,GTO-ETH,GTO-BNB,ICX-BTC,ICX-ETH,ICX-BNB,OST-BTC,OST-ETH,OST-BNB,ELF-BTC,ELF-ETH,AION-BTC,AION-ETH,AION-BNB,NEBL-BTC,NEBL-ETH,NEBL-BNB,BRD-BTC,BRD-ETH,BRD-BNB,MCO-BNB,EDO-BTC,EDO-ETH,NAV-BTC,LUN-BTC,APPC-BTC,APPC-ETH,APPC-BNB,VIBE-BTC,VIBE-ETH,RLC-BTC,RLC-ETH,RLC-BNB,INS-BTC,INS-ETH,PIVX-BTC,PIVX-ETH,PIVX-BNB,IOST-BTC,IOST-ETH,STEEM-BTC,STEEM-ETH,STEEM-BNB,NANO-BTC,NANO-ETH,NANO-BNB,VIA-BTC,VIA-ETH,VIA-BNB,BLZ-BTC,BLZ-ETH,BLZ-BNB,AE-BTC,AE-ETH,AE-BNB,NCASH-BTC,NCASH-ETH,POA-BTC,POA-ETH,ZIL-BTC,ZIL-ETH,ZIL-BNB,ONT-BTC,ONT-ETH,ONT-BNB,STORM-BTC,STORM-ETH,STORM-BNB,QTUM-BNB,QTUM-USDT,XEM-BTC,XEM-ETH,XEM-BNB,WAN-BTC,WAN-ETH,WAN-BNB,WPR-BTC,WPR-ETH,QLC-BTC,QLC-ETH,SYS-BTC,SYS-ETH,SYS-BNB,QLC-BNB,GRS-BTC,GRS-ETH,ADA-USDT,ADA-BNB,GNT-BTC,GNT-ETH,LOOM-BTC,LOOM-ETH,LOOM-BNB,XRP-USDT,REP-BTC,REP-ETH,BTC-TUSD,ETH-TUSD,ZEN-BTC,ZEN-ETH,ZEN-BNB,SKY-BTC,SKY-ETH,SKY-BNB,EOS-USDT,EOS-BNB,CVC-BTC,CVC-ETH,THETA-BTC,THETA-ETH,THETA-BNB,XRP-BNB,TUSD-USDT,IOTA-USDT,XLM-USDT,IOTX-BTC,IOTX-ETH,QKC-BTC,QKC-ETH,AGI-BTC,AGI-ETH,AGI-BNB,NXS-BTC,NXS-ETH,NXS-BNB,ENJ-BNB,DATA-BTC,DATA-ETH,ONT-USDT,TRX-BNB,TRX-USDT,ETC-USDT,ETC-BNB,ICX-USDT,SC-BTC,SC-ETH,SC-BNB,NPXS-ETH,KEY-BTC,KEY-ETH,NAS-BTC,NAS-ETH,NAS-BNB,MFT-BTC,MFT-ETH,MFT-BNB,DENT-ETH,ARDR-BTC,ARDR-ETH,NULS-USDT,HOT-BTC,HOT-ETH,VET-BTC,VET-ETH,VET-USDT,VET-BNB,DOCK-BTC,DOCK-ETH,POLY-BTC,POLY-BNB,HC-BTC,HC-ETH,GO-BTC,GO-BNB,PAX-USDT,RVN-BTC,RVN-BNB,DCR-BTC,DCR-BNB,MITH-BTC,MITH-BNB,BNB-PAX,BTC-PAX,ETH-PAX,XRP-PAX,EOS-PAX,XLM-PAX,REN-BTC,REN-BNB,BNB-TUSD,XRP-TUSD,EOS-TUSD,XLM-TUSD,BNB-USDC,BTC-USDC,ETH-USDC,XRP-USDC,EOS-USDC,XLM-USDC,USDC-USDT,ADA-TUSD,TRX-TUSD,NEO-TUSD,TRX-XRP,XZC-XRP,PAX-TUSD,USDC-TUSD,USDC-PAX,LINK-USDT,LINK-TUSD,LINK-PAX,LINK-USDC,WAVES-USDT,WAVES-TUSD,WAVES-USDC,LTC-TUSD,LTC-PAX,LTC-USDC,TRX-PAX,TRX-USDC,BTT-BNB,BTT-USDT,BNB-USDS,BTC-USDS,USDS-USDT,USDS-PAX,USDS-TUSD,USDS-USDC,BTT-PAX,BTT-TUSD,BTT-USDC,ONG-BNB,ONG-BTC,ONG-USDT,HOT-BNB,HOT-USDT,ZIL-USDT,ZRX-BNB,ZRX-USDT,FET-BNB,FET-BTC,FET-USDT,BAT-USDT,XMR-BNB,XMR-USDT,ZEC-BNB,ZEC-USDT,ZEC-PAX,ZEC-TUSD,ZEC-USDC,IOST-BNB,IOST-USDT,CELR-BNB,CELR-BTC,CELR-USDT,ADA-PAX,ADA-USDC,NEO-PAX,NEO-USDC,DASH-BNB,DASH-USDT,NANO-USDT,OMG-BNB,OMG-USDT,THETA-USDT,ENJ-USDT,MITH-USDT,MATIC-BNB,MATIC-BTC,MATIC-USDT,ATOM-BNB,ATOM-BTC,ATOM-USDT,ATOM-USDC,ATOM-TUSD,ETC-TUSD,BAT-USDC,BAT-PAX,BAT-TUSD,PHB-BNB,PHB-BTC,PHB-TUSD,TFUEL-BNB,TFUEL-BTC,TFUEL-USDT,ONE-BNB,ONE-BTC,ONE-USDT,ONE-USDC,FTM-BNB,FTM-BTC,FTM-USDT,FTM-USDC,ALGO-BNB,ALGO-BTC,ALGO-USDT,ALGO-TUSD,ALGO-PAX,ALGO-USDC,GTO-USDT,ERD-BNB,ERD-BTC,ERD-USDT,DOGE-BNB,DOGE-BTC,DOGE-USDT,DUSK-BNB,DUSK-BTC,DUSK-USDT,DUSK-USDC,DUSK-PAX,BGBP-USDC,ANKR-BNB,ANKR-BTC,ANKR-USDT,ONT-PAX,ONT-USDC,WIN-BNB,WIN-USDT,WIN-USDC,COS-BNB,COS-BTC,COS-USDT,NPXS-USDT,COCOS-BNB,COCOS-BTC,COCOS-USDT,MTL-USDT,TOMO-BNB,TOMO-BTC,TOMO-USDT,TOMO-USDC,PERL-BNB,PERL-BTC,PERL-USDT,DENT-USDT,MFT-USDT,KEY-USDT,STORM-USDT,DOCK-USDT,WAN-USDT,FUN-USDT,CVC-USDT,BTT-TRX,WIN-TRX,CHZ-BNB,CHZ-BTC,CHZ-USDT,BAND-BNB,BAND-BTC,BAND-USDT,BNB-BUSD,BTC-BUSD,BUSD-USDT,BEAM-BNB,BEAM-BTC,BEAM-USDT,XTZ-BNB,XTZ-BTC,XTZ-USDT,REN-USDT,RVN-USDT,HC-USDT,HBAR-BNB,HBAR-BTC,HBAR-USDT,NKN-BNB,NKN-BTC,NKN-USDT,XRP-BUSD,ETH-BUSD,LTC-BUSD,LINK-BUSD,ETC-BUSD,STX-BNB,STX-BTC,STX-USDT,KAVA-BNB,KAVA-BTC,KAVA-USDT,BUSD-NGN,BNB-NGN,BTC-NGN,ARPA-BNB,ARPA-BTC,ARPA-USDT,TRX-BUSD,EOS-BUSD,IOTX-USDT,RLC-USDT,MCO-USDT,XLM-BUSD,ADA-BUSD,CTXC-BNB,CTXC-BTC,CTXC-USDT,BCH-BNB,BCH-BTC,BCH-USDT,BCH-USDC,BCH-TUSD,BCH-PAX,BCH-BUSD,BTC-RUB,ETH-RUB,XRP-RUB,BNB-RUB,TROY-BNB,TROY-BTC,TROY-USDT,BUSD-RUB,QTUM-BUSD,VET-BUSD" + } + } }, - { - "name": "COINUT", - "enabled": true, - "verbose": false, - "httpTimeout": 15000000000, - "websocketResponseCheckTimeout": 30000000, - "websocketResponseMaxLimit": 7000000000, - "websocketTrafficTimeout": 30000000000, - "websocketOrderbookBufferLimit": 5, - "baseCurrencies": "USD", - "currencyPairs": { - "requestFormat": { - "uppercase": true - }, - "configFormat": { - "uppercase": true, - "delimiter": "-" - }, - "useGlobalFormat": true, - "assetTypes": [ - "spot" - ], - "pairs": { - "spot": { - "enabled": "LTC-USDT", - "available": "LTC-CAD,LTC-SGD,USDT-USD,ETC-LTC,LTC-BTC,USDT-SGD,XMR-USDT,ZEC-SGD,ETH-USD,BTC-USDT,ETC-BTC,ETH-LTC,LTC-USD,BTC-USD,ETH-USDT,XMR-LTC,ZEC-USD,ETC-SGD,DAI-SGD,ZEC-CAD,BTC-SGD,ETH-BTC,ETH-SGD,LTC-USDT,ZEC-BTC,ZEC-USDT,BTC-CAD,XMR-BTC,ZEC-LTC,ETC-USDT,ETH-CAD" - } - } - }, - "api": { - "authenticatedSupport": false, - "authenticatedWebsocketApiSupport": false, - "endpoints": { - "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" - }, - "credentials": { - "key": "Key", - "secret": "Secret", - "clientID": "ClientID" - }, - "credentialsValidator": { - "requiresKey": true, - "requiresClientID": true - } - }, - "features": { - "supports": { - "restAPI": true, - "restCapabilities": { - "autoPairUpdates": true - }, - "websocketAPI": true, - "websocketCapabilities": {} - }, - "enabled": { - "autoPairUpdates": true, - "websocketAPI": false - } - }, - "bankAccounts": [ - { - "enabled": false, - "bankName": "", - "bankAddress": "", - "bankPostalCode": "", - "bankPostalCity": "", - "bankCountry": "", - "accountName": "", - "accountNumber": "", - "swiftCode": "", - "iban": "", - "supportedCurrencies": "" - } - ] + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true + } }, - { - "name": "CoinbasePro", - "enabled": true, - "verbose": false, - "httpTimeout": 15000000000, - "websocketResponseCheckTimeout": 30000000, - "websocketResponseMaxLimit": 7000000000, - "websocketTrafficTimeout": 30000000000, - "websocketOrderbookBufferLimit": 5, - "baseCurrencies": "USD,GBP,EUR", - "currencyPairs": { - "requestFormat": { - "uppercase": true, - "delimiter": "-" - }, - "configFormat": { - "uppercase": true, - "delimiter": "-" - }, - "useGlobalFormat": true, - "assetTypes": [ - "spot", - "futures" - ], - "pairs": { - "spot": { - "enabled": "BTC-USD", - "available": "LTC-GBP,XLM-BTC,DASH-BTC,DAI-USDC,ZEC-USDC,XLM-EUR,ZRX-BTC,LTC-BTC,ETC-BTC,ETH-USD,XRP-EUR,BTC-USDC,REP-USD,EOS-BTC,ZEC-BTC,ETC-GBP,LINK-ETH,XRP-BTC,ZRX-USD,ETH-USDC,MANA-USDC,BTC-EUR,BCH-GBP,DNT-USDC,EOS-EUR,BCH-EUR,LTC-EUR,CVC-USDC,ETH-GBP,DASH-USD,ETH-EUR,XTZ-BTC,ZRX-EUR,BAT-ETH,BTC-GBP,ETC-USD,BAT-USDC,BCH-USD,GNT-USDC,ALGO-USD,LINK-USD,XLM-USD,ETH-BTC,EOS-USD,REP-BTC,ETH-DAI,XRP-USD,LTC-USD,ETC-EUR,BTC-USD,XTZ-USD,BCH-BTC,LOOM-USDC" - }, - "futures": { - "enabled": "BTC-PERP-INTX", - "available": "BTC-PERP-INTX" - } - } - }, - "api": { - "authenticatedSupport": false, - "authenticatedWebsocketApiSupport": false, - "endpoints": { - "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" - }, - "credentials": { - "key": "Key", - "secret": "Secret", - "clientID": "ClientID" - }, - "credentialsValidator": { - "requiresKey": true, - "requiresSecret": true, - "requiresClientID": true, - "requiresBase64DecodeSecret": true - } - }, - "features": { - "supports": { - "restAPI": true, - "restCapabilities": { - "autoPairUpdates": true - }, - "websocketAPI": true, - "websocketCapabilities": {} - }, - "enabled": { - "autoPairUpdates": true, - "websocketAPI": false - } - }, - "bankAccounts": [ - { - "enabled": false, - "bankName": "", - "bankAddress": "", - "bankPostalCode": "", - "bankPostalCity": "", - "bankCountry": "", - "accountName": "", - "accountNumber": "", - "swiftCode": "", - "iban": "", - "supportedCurrencies": "" - } - ] + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "tickerBatching": true, + "autoPairUpdates": true + }, + "websocketAPI": true, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": true + } }, - { - "name": "EXMO", - "enabled": true, - "verbose": false, - "httpTimeout": 15000000000, - "websocketResponseCheckTimeout": 30000000, - "websocketResponseMaxLimit": 7000000000, - "websocketTrafficTimeout": 30000000000, - "websocketOrderbookBufferLimit": 5, - "baseCurrencies": "USD,EUR,RUB,PLN,UAH", - "currencyPairs": { - "requestFormat": { - "uppercase": true, - "delimiter": "_", - "separator": "," - }, - "configFormat": { - "uppercase": true, - "delimiter": "_" - }, - "useGlobalFormat": true, - "assetTypes": [ - "spot" - ], - "pairs": { - "spot": { - "enabled": "BTC_USD,LTC_USD", - "available": "BCH_RUB,DASH_RUB,EOS_USD,ETH_TRY,GNT_ETH,LTC_USD,PTI_USDT,XRP_BTC,EXM_BTC,BTG_BTC,ETC_RUB,BTG_USD,NEO_RUB,XMR_BTC,ZRX_ETH,MNX_BTC,USDC_BTC,XRP_EUR,SMART_USD,EOS_BTC,MNX_ETH,ZEC_BTC,BCH_USD,WAVES_USD,TRX_BTC,XRP_TRY,DASH_USD,DOGE_USD,ETZ_USDT,GUSD_USD,MNC_BTC,ZEC_USD,DCR_BTC,DXT_USD,PTI_RUB,XMR_ETH,ZRX_USD,DAI_RUB,MNC_USD,XLM_TRY,DAI_BTC,BTC_EUR,LTC_EUR,OMG_BTC,PTI_EOS,SMART_RUB,XTZ_USD,HP_EXM,ADA_USD,OMG_ETH,QTUM_USD,TRX_RUB,USDC_ETH,USDC_USDT,USD_RUB,BTC_UAH,BCH_USDT,ETH_PLN,KICK_RUB,LSK_RUB,SMART_BTC,XMR_UAH,XRP_USD,GUSD_BTC,QTUM_ETH,USDT_EUR,BTC_RUB,DCR_UAH,ETH_RUB,DOGE_BTC,ETZ_BTC,INK_USD,LTC_UAH,BTT_UAH,BTC_USDT,MNC_ETH,XTZ_ETH,BTC_TRY,DXT_BTC,KICK_USDT,OMG_USD,WAVES_BTC,XLM_BTC,BTCZ_BTC,GNT_BTC,LSK_BTC,LTC_RUB,NEO_BTC,XEM_UAH,XMR_USD,ZAG_BTC,GAS_USD,LTC_BTC,TRX_UAH,XEM_EUR,XMR_RUB,XTZ_RUB,ETZ_ETH,ETC_BTC,GUSD_RUB,INK_BTC,LSK_USD,MNX_USD,SMART_EUR,VLX_BTC,BCH_ETH,XMR_EUR,ADA_ETH,QTUM_BTC,XEM_USD,ATMCASH_BTC,ADA_BTC,ETH_EUR,TRX_USD,USDC_USD,BCH_BTC,ETH_UAH,KICK_BTC,WAVES_RUB,XEM_BTC,ETH_BTC,BCH_EUR,BTT_BTC,ROOBEE_BTC,XLM_USD,XRP_ETH,ETH_USD,MKR_DAI,XTZ_BTC,DAI_USD,BCH_UAH,INK_ETH,KICK_ETH,MKR_BTC,NEO_USD,XRP_USDT,ZEC_EUR,BTC_USD,XRP_RUB,EOS_EUR,ETH_USDT,USDT_UAH,XRP_UAH,ZEC_RUB,HP_BTC,BTT_RUB,DAI_ETH,DASH_UAH,DASH_USDT,ETH_LTC,GAS_BTC,USDT_USD,BTG_ETH,XLM_RUB,WAVES_ETH,USDT_RUB,ZRX_BTC,DASH_BTC,DCR_RUB,ETC_USD,HB_BTC,PTI_BTC,BTC_PLN" - } - } - }, - "api": { - "authenticatedSupport": false, - "authenticatedWebsocketApiSupport": false, - "endpoints": { - "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" - }, - "credentials": { - "key": "Key", - "secret": "Secret" - }, - "credentialsValidator": { - "requiresKey": true, - "requiresSecret": true - } - }, - "features": { - "supports": { - "restAPI": true, - "restCapabilities": { - "tickerBatching": true, - "autoPairUpdates": true - }, - "websocketAPI": false, - "websocketCapabilities": {} - }, - "enabled": { - "autoPairUpdates": true, - "websocketAPI": false - } - }, - "bankAccounts": [ - { - "enabled": false, - "bankName": "", - "bankAddress": "", - "bankPostalCode": "", - "bankPostalCity": "", - "bankCountry": "", - "accountName": "", - "accountNumber": "", - "swiftCode": "", - "iban": "", - "supportedCurrencies": "" - } - ] + "bankAccounts": [ + { + "enabled": false, + "bankName": "", + "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ] + }, + { + "name": "Binanceus", + "enabled": true, + "verbose": false, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, + "baseCurrencies": "USD", + "currencyPairs": { + "bypassConfigFormatUpgrades": false, + "pairs": { + "spot": { + "assetEnabled": true, + "enabled": "BTC-USDT,ETH-USDT,LTC-USDT,ADA-USDT", + "available": "BTC-USD,BCH-USD,LTC-USD,USDT-USD,BTC-USDT,ETH-USDT,BCH-USDT,LTC-USDT,BNB-USD,BNB-USDT,ETH-BTC,BNB-BTC,LTC-BTC,BCH-BTC,ADA-USD,BAT-USD,ETC-USD,XLM-USD,ZRX-USD,ADA-USDT,BAT-USDT,ETC-USDT,XLM-USDT,ZRX-USDT,LINK-USD,RVN-USD,DASH-USD,ZEC-USD,ALGO-USD,IOTA-USD,BUSD-USD,BTCB-USD,DOGE-USDT,WAVES-USD,ATOM-USDT,ATOM-USD,NEO-USDT,NEO-USD,VET-USDT,QTUM-USDT,QTUM-USD,ICX-USD,ENJ-USD,ONT-USD,ONT-USDT,ZIL-USD,ZILB-USD,VET-USD,BNBB-USD,ETHB-USD,ALGO-BUSD,XTZ-USD,XTZ-BUSD,HBAR-USD,HBAR-BUSD,OMG-USD,OMG-BUSD,MATIC-USD,MATIC-BUSD,XTZ-BTC,ADA-BTC,REP-BUSD,REP-USD,EOS-BUSD,EOS-USD,DOGE-USD,KNC-USD,KNC-USDT,VTHO-USDT,VTHO-USD,USDC-USD,COMP-USDT,COMP-USD,MANA-USD,HNT-USD,HNT-USDT,MKR-USD,MKR-USDT,DAI-USD,ONE-USDT,ONE-USD,BAND-USDT,BAND-USD,STORJ-USDT,STORJ-USD,UNI-USD,UNI-USDT,SOL-USD,SOL-USDT,LINK-BTC,VET-BTC,UNI-BTC,EGLD-USDT,EGLD-USD,PAXG-USDT,PAXG-USD,OXT-USDT,OXT-USD,ZEN-USDT,ZEN-USD,BTC-USDC,ONEB-USD,FIL-USDT,FIL-USD,AAVE-USDT,AAVE-USD,GRT-USDT,GRT-USD,SUSHI-USD,ANKR-USD,AMP-USD,SHIB-USDT,SHIB-BUSD,CRV-USDT,CRV-USD,AXS-USDT,AXS-USD,SOL-BTC,AVAX-USDT,AVAX-USD,CTSI-USDT,CTSI-USD,DOT-USDT,DOT-USD,YFI-USDT,YFI-USD,1INCH-USDT,1INCH-USD,FTM-USDT,FTM-USD,USDC-USDT,ETH-USDC,USDC-BUSD,MATIC-USDT,MANA-USDT,MANA-BUSD,ALGO-USDT,ADA-BUSD,SOL-BUSD,EOS-USDT,ENJ-USDT,NEAR-USDT,NEAR-BUSD,NEAR-USD,OMG-USDT,SUSHI-USDT,LRC-USDT,LRC-USD,LRC-BTC,KSHI-BUSD,LPT-USDT,LPT-BUSD,LPT-USD,POLY-USDT,POLY-BUSD,POLY-USD,POLY-BTC,MATIC-BTC,DOT-BTC,NMR-USDT,NMR-USD,SLP-USDT,ANT-USD,XNO-USD,CHZ-USDT,CHZ-USD,OGN-USDT,OGN-USD,GALA-USDT,GALA-USD,TLM-USDT,TLM-USD,SNX-USDT,SNX-USD,AUDIO-USDT,AUDIO-USD,ENS-USDT,MANA-BTC,ATOM-BTC,AVAX-BTC,WBTC-BTC,REQ-USDT,REQ-USD,APE-USDT,APE-USD,FLUX-USDT,FLUX-USD,TRX-BTC,TRX-BUSD,TRX-USDT,TRX-USD,COTI-USDT,COTI-USD,VOXEL-USDT,VOXEL-USD,RLC-USDT,RLC-USD,UST-USDT,UST-USD,BICO-USDT,BICO-USD,API3-USDT,API3-USD,ENS-USD,BTC-UST,BNT-USDT,BNT-USD,IMX-USDT,IMX-USD,SPELL-USDT,SPELL-USD,JASMY-USDT,JASMY-USD,FLOW-USDT,FLOW-USD,GTC-USDT,GTC-USD,BTC-BUSD,ZIL-BUSD,BNB-BUSD,ETH-BUSD,BUSD-USDT,ONE-BUSD,LINK-USDT,ZEC-USDT,SLP-USD,ANT-USDT", + "requestFormat": { + "uppercase": true + }, + "configFormat": { + "uppercase": true, + "delimiter": "-" + } + } + } }, - { - "name": "GateIO", - "enabled": true, - "verbose": false, - "httpTimeout": 15000000000, - "websocketResponseCheckTimeout": 30000000, - "websocketResponseMaxLimit": 7000000000, - "websocketTrafficTimeout": 30000000000, - "websocketOrderbookBufferLimit": 5, - "baseCurrencies": "USD", - "currencyPairs": { - "requestFormat": { - "uppercase": true, - "delimiter": "_" - }, - "configFormat": { - "uppercase": true, - "delimiter": "_" - }, - "useGlobalFormat": true, - "assetTypes": [ - "spot", - "option", - "futures", - "cross_margin", - "margin", - "delivery" - ], - "pairs": { - "spot": { - "enabled": "BTC_USDT,IHT_ETH,AME_ETH,CEUR_ETH,ALEPH_USDT,OMG_TRY,BTC_TRY,OGN_USDT,ALA_USDT", - "available": "IHT_ETH,AME_ETH,CEUR_ETH,ALEPH_USDT,OMG_TRY,BTC_TRY,OGN_USDT,ALA_USDT,HC_USDT,BTC_USDT,QNT_USDT,QTUM_ETH,MAHA_ETH,XCN_ETH,POOL_USDT,KGC_USDT,MCO2_USDT,HARD_USDT,GHNY_USDT,FTT_ETH,K21_ETH,FINE_USDT,REP_USDT,SBR_USDT,SKM_ETH,QLC_ETH,GAS_BTC,ALICE3L_USDT,BAO_USDT,FALCONS_USDT,ANT_USDT,VIDYX_USDT,DXCT_ETH,SMTY_ETH,HERO_USDT,SHARE_USDT,FIN_USDT,MTV_USDT,MOO_USDT,SMTY_USDT,ORAO_USDT,AE_ETH,SUSD_USDT,MAN_USDT,UNDEAD_USDT,MC_USDT,VET_USDT,WAXP_ETH,MDA_ETH,LYXE_USDT,SPS_USDT,STX_ETH,WSIENNA_USDT,NAOS_BTC,NFTX_USDT,OPUL_USDT,ICP3L_USDT,SFI_ETH,CTT_USDT,BSV3L_USDT,DFI_USDT,DIS_ETH,FET_USDT,ARG_USDT,VELO_USDT,NSBT_BTC,GSE_ETH,HNS_BTC,DOGEDASH_ETH,BACON_USDT,DUSK_USDT,MAPE_USDT,EGLD_ETH,TDROP_USDT,C983L_USDT,FAN_ETH,CZZ_USDT,FIU_USDT,SWRV_USDT,ONT_ETH,KINE_ETH,IMX_ETH,SPAY_ETH,CFG_BTC,RACA3S_USDT,UNO_ETH,DMLG_USDT,SAKE_ETH,ASM_USDT,CUSD_ETH,SUSD_ETH,ONC_USDT,DAI_USDT,VEGA_ETH,PYM_USDT,LTC_TRY,LOKA_USDT,NIF_USDT,BNC_USDT,PERL_ETH,MATIC3S_USDT,STMX_USDT,SKL_USDT,WLKN_USDT,XYO_ETH,AMPL3S_USDT,WEX_USDT,ULU_ETH,LIKE_ETH,INSUR_ETH,CAKE_ETH,SXP_ETH,COTI_USDT,ORT_USDT,RACA3L_USDT,GASDAO_USDT,AVA_USDT,OPA_USDT,ATS_USDT,VEGA_USDT,KILT_USDT,HIT_ETH,BRISE_USDT,SAUBER_USDT,SPS_ETH,FSN_USDT,EOS_ETH,KYL_USDT,REVV_ETH,SVT_ETH,XRP_USDT,DYDX3S_USDT,MANA3S_USDT,ICP_ETH,ALICE3S_USDT,PCX_USDT,LEMO_ETH,MKR_ETH,WOO3S_USDT,CART_ETH,MATIC_USDT,UNI_USD,MOBI_BTC,ICP3S_USDT,BEAM_BTC,CRO3S_USDT,FTT_USDT,IQ_ETH,TAP_USDT,MLT_USDT,RBN_USDT,AMPL3L_USDT,KINT_ETH,HECH_USDT,GAFI_ETH,WOO3L_USDT,TAI_USDT,HERA_USDT,AST_USDT,DHV_ETH,XAVA_USDT,LSS_USDT,SNX3S_USDT,PBR_USDT,XEND_ETH,SHR_ETH,PRQ_USDT,MATIC3L_USDT,WIT_ETH,LPOOL_USDT,PSP_USDT,BXC_USDT,CBK_USDT,REVO_BTC,MANA3L_USDT,ALPINE_USDT,DEGO_USDT,SIN_USDT,OCT_USDT,KZEN_USDT,L3P_USDT,FX_ETH,ONC_ETH,AXS_USD,BORA_USDT,XTZ_ETH,NEO3L_USDT,FROG_USDT,CHAMP_USDT,XNFT_USDT,BCH3S_USDT,FORT_USDT,XLM_TRY,TRX_TRY,CRPT_USDT,ROUTE_USDT,GLM_USDT,SLRS_ETH,TIMECHRONO_USDT,VRA_USDT,ONS_USDT,ZEC3L_USDT,KFT_ETH,TFD_ETH,FRA_USDT,RDN_ETH,BLANK_USDT,IOST3L_USDT,DDD_USDT,DOGE_USD,UNQ_USDT,API33S_USDT,AKRO_ETH,GITCOIN_USDT,THG_USDT,BDX_USDT,LTO_ETH,FLY_USDT,CREDIT_USDT,RENA_USDT,ZRX_ETH,CRP_ETH,NBOT_USDT,HT3L_USDT,DORA_ETH,LLT_SNET,ASD_USDT,XMR_USDT,SSV_BTC,FTM_USDT,XELS_USDT,MTL_ETH,ADX_ETH,API33L_USDT,PIG_USDT,RUNE_ETH,QRDO_BTC,THN_USDT,BCUG_USDT,EGG_ETH,GGM_USDT,HOTCROSS_USDT,SKYRIM_USDT,BTG_USDT,POT_USDT,CS_USDT,XVS_USDT,A5T_USDT,GOD_BTC,WAVES_USDT,LSK_BTC,BTT_TRY,YIN_USDT,PEOPLE_USDT,SPELL_ETH,POLC_USDT,BZZ3L_USDT,UNO_USDT,HDV_USDT,CELL_USDT,DAR_ETH,MIR_ETH,FODL_USDT,SRM_ETH,PROS_USDT,ORN_ETH,WAG_USDT,RBC_ETH,VENT_USDT,WND_USDT,AAA_ETH,BSCS_ETH,ZEC3S_USDT,DOS_USDT,HT3S_USDT,LAND_USDT,BCD_BTC,RING_USDT,FIRO_USDT,AUDIO_USDT,KUMA_USDT,SOLO_BTC,CRBN_USDT,MM_ETH,SAKE_USDT,XMARK_USDT,SLP_USDT,F2C_USDT,LUNA_USDT,ONIT_USDT,FTM3L_USDT,POPK_USDT,RFUEL_USDT,NEO3S_USDT,MIR_USDT,ETC_BTC,STETH_ETH,MANA_TRY,ALPACA_ETH,WAXL_USDT,EGS_USDT,DAR_USDT,KSM_USDT,XMARK_ETH,QTUM_USDT,C983S_USDT,INDI_ETH,DOGE3S_USDT,RVN_USDT,NOS_USDT,ALU_ETH,ALD_ETH,LUNC_USDT,ARES_ETH,BZZ3S_USDT,TNC_ETH,ONE_USDT,SENC_ETH,FTM3S_USDT,FLUX_USDT,STORJ_ETH,MTN_ETH,MNW_USDT,BLES_ETH,STG_ETH,LIME_ETH,WAGYU_USDT,XRP_TRY,XOR_ETH,ANGLE_USDT,DOGA_USDT,JFI_USDT,USDG_USDT,GRND_USDT,BOND_ETH,DMTR_USDT,YIN_ETH,ENJ_USDT,GOLDMINER_USDT,WIT_USDT,DOGE3L_USDT,FORM_USDT,LYXE_ETH,MLK_USDT,VR_USDT,DMS_USDT,LRC_TRY,ONX_USDT,ASK_USDT,ISP_ETH,TXT_USDT,IOEN_ETH,NIIFI_USDT,VRX_USDT,DOME_USDT,CTSI_USDT,ORBS_USDT,ZLW_ETH,FIL_USDT,FTI_ETH,CTK_USDT,ASR_USDT,GBPT_BTC,CBK_BTC,MBOX_ETH,RAM_USDT,IRIS_USDT,AME_USDT,KUB_USDT,ENV_USDT,RING_ETH,COTI3S_USDT,JULD_ETH,POLK_ETH,ACH3S_USDT,HYVE_ETH,MIX_ETH,RFT_USDT,ORAO_ETH,IHT_USDT,POLYPAD_USDT,CTRC_USDT,SFUND_USDT,MXC_BTC,DDD_BTC,CHESS_ETH,SHIB_USDT,SN_USDT,NFT_USDT,ASTRO_ETH,SOLO_USDT,TSHP_USDT,AMP_USDT,BTCST_ETH,VLXPAD_USDT,GAN_USDT,O3_USDT,WBTC_TRY,TULIP_USDT,GS_ETH,DX_ETH,NYZO_ETH,TT_USDT,SHILL_USDT,RATING_ETH,DUST_USDT,PSB_USDT,BFT1_USDT,GALA_ETH,XDC_USDT,LON3L_USDT,HE_USDT,ICE_ETH,LINK_ETH,SKU_USDT,QLC_USDT,DOMI_USDT,IDEA_USDT,METO_USDT,LIFE_ETH,ACH3L_USDT,POWR_ETH,VET_ETH,ALGO_USDT,BLIN_USDT,BAO_ETH,RBLS_USDT,TORN_ETH,VRT_USDT,BLANKV2_ETH,AUCTION_ETH,OLE_USDT,NWC_BTC,DOT5S_USDT,M RCH_ETH,SUNNY_ETH,GST_USDT,ENJ_TRY,KIBA_USDT,KLAP_USDT,SNTR_ETH,CELR_ETH,CHESS_USDT,XLM3L_USDT,LIQ_USDT,TRU_ETH,CHZ_USD,EPK_USDT,MED_ETH,BSCPAD_ETH,ZCN_USDT,AIOZ_ETH,FOR_ETH,CVC3L_USDT,MNY_USDT,SALT_USDT,CSTR_USDT,MPL_USDT,PLY_ETH,FIS_USDT,CHO_USDT,BICO_ETH,STOX_ETH,HIGH_USDT,SDAO_BTC,STEP_USD,CRV_BTC,SCRT_ETH,ROSE_USDT,SKILL_ETH,FRAX_USDT,BAGS_USDT,WIKEN_BTC,WOO_USDT,BBANK_ETH,SNX3L_USDT,XRD_ETH,VTHO_USDT,OKB3L_USDT,SAFEMOON_USDT,RAD_ETH,IOI_USDT,LAMB_USDT,CHZ_USDT,FAR_ETH,OKB3S_USDT,ELU_USDT,JGN_ETH,EOS3S_USDT,DBC_USDT,ATOM_USDT,ACH_ETH,LBLOCK_USDT,WZRD_USDT,OST_ETH,MEAN_USDT,IDEX_USDT,HOT_TRY,EWT_ETH,EMON_USDT,FXS_USDT,PSY_ETH,SIDUS_USDT,ATA_USDT,CVC3S_USDT,LOOKS_ETH,ALPA_ETH,CGG_ETH,CIR_ETH,PRT_ETH,LON3S_USDT,INJ_USDT,FIRE_ETH,MAHA_USDT,IOST3S_USDT,NU_ETH,LEO_BTC,VOXEL_USDT,CRV_USDT,EQX_USDT,WHALE_USDT,INJ_ETH,GRAP_USDT,AVAX3S_USDT,TIFI_USDT,C98_USDT,ERN_ETH,SUSHI_ETH,VET3S_USDT,KPAD_USDT,CRPT_ETH,CRO_USDT,AZY_USDT,LEMD_USDT,ETH2_ETH,BASE_ETH,TT_ETH,PERL_USDT,BANK_ETH,LST_ETH,PYR_ETH,RATIO_USDT,UMB_USDT,M ETALDR_USDT,SWINGBY_ETH,WICC_ETH,NUM_USDT,SHOE_USDT,BORING_ETH,SDN_USDT,GXS_BTC,ALICE_ETH,BRKL_USDT,GF_ETH,ELEC_USDT,SFG_USDT,COFIX_USDT,TIPS_ETH,FIL_BTC,CWAR_USDT,WILD_USDT,RENBTC_USDT,BNX_USDT,TRU_USDT,SWEAT_USDT,IOST_BTC,NVIR_USDT,1EARTH_USDT,ADAPAD_USDT,PPS_USDT,CUBE_USDT,DLC_USDT,DAFI_ETH,UNISTAKE_ETH,NFTL_USDT,ATOM_TRY,SHIB3S_USDT,BNB_USD,CNAME_USDT,GTH_ETH,ZCX_USDT,DYDX3L_USDT,ASTRO_USDT,GLQ_USDT,PROPS_USDT,AART_USDT,BTRST_ETH,KFT_USDT,AERGO_USDT,RUFF_ETH,EOS3L_USDT,API3_USDT,MINA_BTC,ETHA_ETH,AXIS_ETH,LOON_USDT,AVAX3L_USDT,VET3L_USDT,AE_USDT,SHX_USDT,LYM_USDT,DCR_BTC,LBK_USDT,QTC_USDT,LAVA_USDT,XCN_USDT,BRT_USDT,RSV_USDT,KIF_USDT,PSL_USDT,AZERO_USDT,LUNA_ETH,MILO_USDT,OGN_ETH,TOTM_USDT,BYN_ETH,MINA_USDT,PUNDIX_ETH,SRT_USDT,DG_ETH,IHC_USDT,SYS_ETH,TITA_USDT,COTI3L_USDT,DAG_USDT,DOT5L_USDT,TRADE_USDT,SHPING_USDT,NU_USDT,BLANK_ETH,PCNT_ETH,SCCP_USDT,POLS_USDT,NPT_USDT,MTA_USDT,YIELD_USDT,ZCN_ETH,DVP_ETH,KART_USDT,SYLO_USDT,MCRT_USDT,SPFC_USDT,BASE_USDT,ICX_USDT,PET_USDT,GZONE_USDT,RED_ETH,SBTC_USDT,BATH_ ETH,SOL_USD,NAFT_USDT,GMX_USDT,VADER_USDT,GTC_USDT,CVP_ETH,XRPBEAR_USDT,TIME_USDT,SXP_USDT,CITY_USDT,QASH_USDT,FAST_USDT,BCD_USDT,KNIGHT_USDT,BOO_ETH,ZODI_USDT,REI_USDT,PBX_ETH,SRM_USDT,LDO_ETH,ZEC_USDT,UFT_USDT,DAG_BTC,RIDE_USDT,ERN_USDT,T_USDT,CEEK_USDT,STI_USDT,IMX3S_USDT,ELA_USDT,MNGO_ETH,EHASH_ETH,BADGER_ETH,SUPE_USDT,AR3L_USDT,AUDIO_ETH,DOCK_ETH,QSP_USDT,FLM_USDT,AAVE3S_USDT,BOND_USDT,HT_USD,TARA_USDT,TRX_USDT,SPO_USDT,DSLA_USDT,LTC_BTC,DOGE_USDT,SLIM_ETH,ALN_ETH,CFX3S_USDT,FXS_ETH,RARE_ETH,VLXPAD_ETH,ETH_USD,SDN_BTC,QUICK_USDT,UTK_USDT,XPNET_USDT,TRB_USDT,LAZIO_USDT,FTM_TRY,ALPHA_ETH,CVC_ETH,WSG_USDT,UNI_ETH,DASH3L_USDT,BTL_USDT,CPOOL_USDT,MCG_USDT,SFP_ETH,REALM_USDT,RUFF_BTC,MOB_ETH,IBFK_USDT,ALPHA3S_USDT,BLOK_USDT,WIKEN_USDT,OMG3S_USDT,UTK_ETH,BCH5S_USDT,MED_USDT,REN_USD,MAN_ETH,SLND_ETH,CGG_USDT,CRE_USDT,SOURCE_USDT,ABT_USDT,DPET_USDT,WOM_USDT,FOREX_ETH,SNFT1_USDT,RIF_USDT,BENQI_USDT,XCV_ETH,GTC_BTC,ADA_TRY,LAT_USDT,ITGR_USDT,DLTA_USDT,SMT_USDT,APYS_USDT,MFT_ETH,ABT_ETH,STOX_USDT,ZRX_BTC,GMAT_USDT,R OOM_ETH,STORJ_BTC,RAZOR_USDT,RAGE_USDT,DOCK_USDT,RDN_USDT,MTR_USDT,NKN_USDT,SWASH_USDT,FX_USDT,POR_USDT,DENT_ETH,DERI_USDT,DFND_USDT,BLES_USDT,SLND_USDT,WNXM_ETH,CRTS_USDT,BTC3S_USDT,BKC_USDT,STEPG_ETH,THETA3L_USDT,NBS_BTC,AVAX_ETH,NANO_BTC,DEFILAND_ETH,LOOKS_USDT,BCX_BTC,BCH_USD,ETH3L_USDT,QLC_BTC,BCUG_ETH,RDF_USDT,DOGEDASH_USDT,ARSW_USDT,NEAR_ETH,QTCON_USDT,BABI_USDT,MBX_USDT,PNL_USDT,ODDZ_ETH,ATOM_BTC,XRP_BTC,BTCBULL_USDT,HMT_USDT,PORTO_USDT,STND_USDT,ETHW_ETH,LPT_USDT,LTC3L_USDT,TOKAU_USDT,QI_ETH,TVK_USDT,CWS_USDT,SWOP_USDT,WBTC_USDT,INSTAR_ETH,ICX_ETH,GALA5L_USDT,XTZ_BTC,AGS_USDT,TARA_BTC,DYDX_ETH,CATGIRL_USDT,SASHIMI_ETH,EPX_ETH,GCOIN_USDT,GDAO_USDT,MARS_ETH,OMG_USD,PMON_USDT,MNGO_USDT,TVK_ETH,SLG_USDT,MSOL_USDT,POWR_USDT,UOS_USDT,USDD_USDT,SLICE_USDT,ARRR_ETH,NSBT_USDT,STR_ETH,BEAM3L_USDT,BEL_USDT,MM_USDT,AXS_ETH,WEST_ETH,FTT3L_USDT,OMI_USDT,TIPS_USDT,SLC_ETH,SQUID_USDT,FEI_USDT,GEM_USDT,UMEE_USDT,DOGE_TRY,FCD_USDT,PVU_USDT,XED_ETH,LRN_ETH,NRFB_USDT,LION_USDT,BLACK_USDT,DOGE5S_USDT,CUDOS_USDT,PCNT_USDT ,OVR_USDT,ETC3S_USDT,CHR_ETH,MER_USDT,BOBA_USDT,FUEL_USDT,BAC_USDT,ONE3S_USDT,CONV_ETH,CDT_BTC,CELL_ETH,ASM_ETH,OPIUM_USDT,JST3L_USDT,BONDLY_USDT,RAZE_USDT,LIME_BTC,NFTX_ETH,PNK_ETH,LDO_USDT,DKS_USDT,ORO_USDT,LITH_USDT,ALPHR_ETH,INK_BTC,RLY_USDT,NEAR3S_USDT,XLM3S_USDT,AR_USDT,AKT_USDT,HCT_USDT,REEF_ETH,BZZ_USDT,SRM3L_USDT,AQDC_USDT,OPIUM_ETH,BAT_TRY,EWT_USDT,ALCX_ETH,CORN_USDT,HYDRA_USDT,RUNE_USD,STEP_USDT,CKB_BTC,MATTER_USDT,STSOL_ETH,CEEK_ETH,FXF_ETH,LIKE_USDT,HIT_USDT,LEO_USDT,COMP_USDT,BAL_USDT,LMR_USDT,AQT_USDT,BUY_ETH,LINK3S_USDT,ROOK_ETH,IMX_USDT,EFI_USDT,TAUR_USDT,OKT_ETH,GALO_USDT,MOOV_USDT,RUNE_USDT,TCP_USDT,ITEM_USDT,SCLP_USDT,RBC_USDT,SPI_USDT,ETC_USDT,RENBTC_BTC,CHICKS_USDT,KNOT_USDT,XEC3L_USDT,XCV_USDT,ETC_ETH,AAVE_TRY,APT_USDT,GNX_ETH,KISHU_USDT,AE_BTC,LIEN_USDT,CREAM_USDT,ATOM3S_USDT,OP_ETH,FORTH_ETH,PYR_USDT,KTN_ETH,TKO_ETH,METAG_USDT,ACE_USDT,CIR_USDT,BEAM_ETH,TCP_ETH,SRM_USD,CEL_USD,TRIBE3S_USDT,MESA_ETH,EVA_USDT,BBANK_USDT,BLANKV2_USDT,FORM_ETH,BAL3S_USDT,VISR_ETH,REVO_ETH,ALTB_USDT,KNC_US DT,GAS_USDT,SAFEMARS_USDT,TIP_USDT,VADER_ETH,NWC_USDT,VALUE_USDT,ATA_ETH,SSX_USDT,JOE_USDT,BAS_ETH,FITFI3S_USDT,BIT_USDT,QNT_ETH,RFOX_ETH,MSU_USDT,MSOL_ETH,CRV3L_USDT,OXT_USDT,SHFT_USDT,VERA_ETH,LYM_ETH,BP_USDT,KBOX_USDT,DOGNFT_ETH,PERP_USDT,VELO_ETH,SAO_USDT,DUCK2_USDT,DEFILAND_USDT,DUCK2_ETH,GLMR3L_USDT,SERO_ETH,MTS_USDT,STX_USDT,KEX_ETH,ZIG_USDT,CARDS_USDT,ANML_USDT,GALA_USDT,RAY3S_USDT,KAVA3L_USDT,GARD_USDT,GRT3L_USDT,BFC_USDT,NIFT_USDT,ORION_USDT,CTX_USDT,ASW_USDT,CERE_USDT,COMBO_ETH,MKR_USDT,MASK_USDT,MGA_USDT,AVAX_USDT,SKL3L_USDT,FRR_USDT,MV_USDT,BMI_ETH,SFIL_USDT,TEER_USDT,KLV_USDT,DMS_ETH,LBL_USDT,MKR3L_USDT,LEDU_BTC,XLM_BTC,MIST_ETH,OIN_USDT,CAKE_USDT,RNDR_USDT,STEPG_USDT,YCT_USDT,OPS_ETH,SHR_USDT,OXY_ETH" - }, - "option": { - "enabled": "BTC_USDT-20230217-28000-P,BTC_USDT-20221028-34000-P,BTC_USDT-20221028-40000-C", - "available": "BTC_USDT-20221028-26000-C,BTC_USDT-20221028-34000-P,BTC_USDT-20221028-40000-C,BTC_USDT-20221028-28000-P,BTC_USDT-20221028-34000-C,BTC_USDT-20221028-28000-C,BTC_USDT-20221028-36000-P,BTC_USDT-20221028-50000-P,BTC_USDT-20221028-36000-C,BTC_USDT-20221028-50000-C,BTC_USDT-20221028-21000-P,BTC_USDT-20221028-38000-P,BTC_USDT-20221028-21000-C,BTC_USDT-20221028-38000-C,BTC_USDT-20221028-23000-P,BTC_USDT-20221028-17000-P,BTC_USDT-20221028-23000-C,BTC_USDT-20221028-17000-C,BTC_USDT-20221028-25000-P,BTC_USDT-20221028-19000-P,BTC_USDT-20221028-25000-C,BTC_USDT-20221028-10000-P,BTC_USDT-20221028-19000-C,BTC_USDT-20221028-27000-P,BTC_USDT-20221028-10000-C,BTC_USDT-20221028-27000-C,BTC_USDT-20221028-12000-P,BTC_USDT-20221028-12000-C,BTC_USDT-20221028-20000-P,BTC_USDT-20221028-5000-P,BTC_USDT-20221028-14000-P,BTC_USDT-20221028-20000-C,BTC_USDT-20221028-45000-P,BTC_USDT-20221028-5000-C,BTC_USDT-20221028-14000-C,BTC_USDT-20221028-22000-P,BTC_USDT-20221028-45000-C,BTC_USDT-20221028-16000-P,BTC_USDT-20221028-22000-C,BTC_USDT-20221028-30000-P,BTC_USDT-20221028-16000-C,BTC_USDT-20221028-24000-P,BTC_USDT-20221028-30000-C,BTC_USDT-20221028-18000-P,BTC_USDT-20221028-24000-C,BTC_USDT-20221028-32000-P,BTC_USDT-20221028-18000-C,BTC_USDT-20221028-26000-P,BTC_USDT-20221028-32000-C,BTC_USDT-20221028-40000-P" - }, - "futures": { - "enabled": "ETH_USD,BTC_USD,KNC_USDT,OOKI_USDT,BIT_USDT,ZEC_USDT,SC_USDT,RVN_USDT,ICX_USDT", - "available": "ETH_USD,BTC_USD,KNC_USDT,OOKI_USDT,BIT_USDT,ZEC_USDT,SC_USDT,RVN_USDT,ICX_USDT,DUSK_USDT,BEL_USDT,REEF_USDT,ALCX_USDT,ASTR_USDT,INJ_USDT,CAKE_USDT,LAZIO_USDT,ONE_USDT,CEL_USDT,ETH_USDT,KLAY_USDT,COTI_USDT,MKISHU_USDT,MANA_USDT,MOVR_USDT,OMG_USDT,UNI_USDT,LTC_USDT,AAVE_USDT,DENT_USDT,QRDO_USDT,BNB_USDT,ALPHA_USDT,RAY_USDT,APE_USDT,CERE_USDT,STMX_USDT,XCN_USDT,OGN_USDT,OKB_USDT,DOT_USDT,TLM_USDT,BTM_USDT,ADA_USDT,ANKR_USDT,ANT_USDT,TRX_USDT,MTL_USDT,YFII_USDT,SUN_USDT,SAND_USDT,MBABYDOGE_USDT,WIN_USDT,LUNC_USDT,SRM_USDT,STG_USDT,BAT_USDT,AXS_USDT,SOL_USDT,MAKITA_USDT,BNT_USDT,BLZ_USDT,PSG_USDT,IOTA_USDT,BONK_USDT,RSR_USDT,PYR_USDT,FITFI_USDT,MKR_USDT,PERP_USDT,COMP_USDT,LINK_USDT,CHR_USDT,CFX_USDT,GARI_USDT,DGB_USDT,MBOX_USDT,WEMIX_USDT,DYDX_USDT,LUNA_USDT,HT_USDT,TRB_USDT,CTK_USDT,ACA_USDT,TFUEL_USDT,OCEAN_USDT,XLM_USDT,HOT_USDT,FTM_USDT,LPT_USDT,SOS_USDT,ALGO_USDT,SHIB_USDT,BSV_USDT,PORTO_USDT,SFP_USDT,SANTOS_USDT,BADGER_USDT,DAR_USDT,DEFI_USDT,XEM_USDT,ALICE_USDT,ICP_USDT,RARE_USDT,LRC_USDT,BAKE_USDT,FLUX_USDT,CRO_USDT,CVC_USDT,MINA_USDT,LIT_USDT,AUDIO_USDT,ZIL_USDT,XMR_USDT,FRONT_USDT,CTSI_USDT,AGLD_USDT,YGG_USDT,OP_USDT,ZRX_USDT,GT_USDT,XCH_USDT,VET_USDT,MOB_USDT,BICO_USDT,SLP_USDT,ACH_USDT,AR_USDT,CLV_USDT,IMX_USDT,SPELL_USDT,UNFI_USDT,SUSHI_USDT,FTT_USDT,HIGH_USDT,HNT_USDT,ALT_USDT,YFI_USDT,NEAR_USDT,NKN_USDT,XVS_USDT,BAND_USDT,LOKA_USDT,BCH_USDT,TOMO_USDT,WAVES_USDT,FIDA_USDT,DIA_USDT,ANC_USDT,CELO_USDT,CRV_USDT,FLM_USDT,GLMR_USDT,FIL_USDT,PEOPLE_USDT,WAXP_USDT,IOTX_USDT,ATOM_USDT,RLC_USDT,HBAR_USDT,REN_USDT,GMT_USDT,KAVA_USDT,KDA_USDT,GALA_USDT,STORJ_USDT,PUNDIX_USDT,BAL_USDT,XAUG_USDT,GRIN_USDT,SXP_USDT,AKRO_USDT,NEXO_USDT,CKB_USDT,API3_USDT,NEST_USDT,ETHW_USDT,TONCOIN_USDT,THETA_USDT,CREAM_USDT,BTC_USDT,GST_USDT,BEAM_USDT,HFT_USDT,KSM_USDT,RAD_USDT,QTUM_USDT,WOO_USDT,ATA_USDT,AVAX_USDT,EOS_USDT,SNX_USDT,AUCTION_USDT,XRP_USDT,GITCOIN_USDT,MATIC_USDT,ONT_USDT,LINA_USDT,DASH_USDT,MASK_USDT,ETC_USDT,JST_USDT,BSW_USDT,CONV_USDT,SKL_USDT,GAL_USDT,DODO_USDT,GRT_USDT,TRU_USDT,STX_USDT,CVX_USDT,JASMY_USDT,HIVE_USDT,EXCH_USDT,ROSE_USDT,SUPER_USDT,SCRT_USDT,USTC_USDT,ENJ_USDT,BTS_USDT,LOOKS_USDT,QNT_USDT,HOOK_USDT,FLOW_USDT,RUNE_USDT,APT_USDT,CHZ_USDT,DOGE_USDT,1INCH_USDT,PRIV_USDT,CSPR_USDT,C98_USDT,RACA_USDT,CELR_USDT,XEC_USDT,ENS_USDT,POND_USDT,NYM_USDT,PROM_USDT,IOST_USDT,ZEN_USDT,LDO_USDT,RNDR_USDT,REQ_USDT,DEGO_USDT,VRA_USDT,QUICK_USDT,VGX_USDT,XTZ_USDT,EGLD_USDT,POLS_USDT,ARPA_USDT,NFT_USDT" - }, - "cross_margin": { - "enabled": "BTC_USDT,ERN_USDT,T_USDT,CEEK_USDT,OGN_USDT,QNT_USDT,WOZX_USDT,ZEE_USDT,FUN_USDT,FLM_USDT,BOND_USDT", - "available": "ERN_USDT,T_USDT,CEEK_USDT,OGN_USDT,QNT_USDT,WOZX_USDT,ZEE_USDT,FUN_USDT,FLM_USDT,BOND_USDT,TARA_USDT,TRX_USDT,OXY_USDT,LON_USDT,DOGE_USDT,ISP_USDT,TWT_USDT,BAO_USDT,QUACK_USDT,ANT_USDT,VGX_USDT,ARPA_USDT,QUICK_USDT,UTK_USDT,HERO_USDT,WSG_USDT,BICO_USDT,MTV_USDT,VET_USDT,GARI_USDT,BCH_USDT,KLAY_USDT,WING_USDT,BLOK_USDT,SPS_USDT,WIKEN_USDT,WSIENNA_USDT,PUNDIX_USDT,FIC_USDT,ASTR_USDT,FET_USDT,VELO_USDT,BENQI_USDT,CWEB_USDT,RIF_USDT,UNI_USDT,ONG_USDT,ERG_USDT,ALPHA_USDT,CELO_USDT,XVG_USDT,GMAT_USDT,BTS_USDT,DOCK_USDT,GMT_USDT,DIA_USDT,CSPR_USDT,NKN_USDT,STAKE_USDT,SWASH_USDT,XEC_USDT,SWRV_USDT,QRDO_USDT,BLES_USDT,EOS_USDT,GRT_USDT,ASM_USDT,FIL6_USDT,GNO_USDT,EGLD_USDT,XYM_USDT,LOOKS_USDT,LOKA_USDT,BNC_USDT,BAS_USDT,SKL_USDT,STMX_USDT,CVC_USDT,DDOS_USDT,COTI_USDT,AVA_USDT,HMT_USDT,DF_USDT,LPT_USDT,XRP_USDT,TVK_USDT,FEVR_USDT,MBL_USDT,KIN_USDT,SPELL_USDT,MATIC_USDT,FTT_USDT,NMR_USDT,PMON_USDT,BNB_USDT,USDD_USDT,LSS_USDT,MDX_USDT,PRQ_USDT,ALPINE_USDT,DEGO_USDT,OMI_USDT,TIPS_USDT,OCT_USDT,FEI_USDT,UMEE_USDT,CRP_USDT,LION_USDT,YFI_USDT,DASH_USDT,REQ_USDT,SDAO_USDT,PNT_USDT,INSUR_USDT,OOKI_USDT,SUN_USDT,CRPT_USDT,BAC_USDT,DATA_USDT,LRN_USDT,JGN_USDT,KIMCHI_USDT,SUKU_USDT,VRA_USDT,AAVE_USDT,FTI_USDT,LDO_USDT,FRA_USDT,BLANK_USDT,NEAR_USDT,ZKS_USDT,MTRG_USDT,RLY_USDT,TCT_USDT,FLY_USDT,JST_USDT,YFII_USDT,AR_USDT,POLY_USDT,JULD_USDT,SOL_USDT,BZZ_USDT,AXS_USDT,ASD_USDT,XMR_USDT,FTM_USDT,HIT_USDT,LEO_USDT,LIT_USDT,PIG_USDT,COMP_USDT,ELON_USDT,IMX_USDT,EFI_USDT,XVS_USDT,WAVES_USDT,PEOPLE_USDT,SOS_USDT,RUNE_USDT,POLC_USDT,SCLP_USDT,BABYDOGE_USDT,KONO_USDT,SPI_USDT,ETC_USDT,MDA_USDT,MTL_USDT,BCHA_USDT,KISHU_USDT,SUNNY_USDT,PYR_USDT,XTZ_USDT,TRIBE_USDT,AUDIO_USDT,FIRO_USDT,MANA_USDT,OKB_USDT,DOG_USDT,SLP_USDT,KNC_USDT,GAS_USDT,LUNA_USDT,SAFEMARS_USDT,MIR_USDT,DAR_USDT,EGS_USDT,KSM_USDT,ATP_USDT,BIT_USDT,STORJ_USDT,XEM_USDT,QTUM_USDT,AGLD_USDT,RVN_USDT,OXT_USDT,SHFT_USDT,IOTX_USDT,LUNC_USDT,NEXO_USDT,AKITA_USDT,PERP_USDT,ONE_USDT,ETH_USDT,FLUX_USDT,FLOKI_USDT,STX_USDT,ANML_USDT,XPRT_USDT,GALA_USDT,GXS_USDT,TORN_USDT,KAI_USDT,1INCH_USDT,CHR_USDT,GAL_USDT,GLMR_USDT,CTX_USDT,CERE_USDT,CART_USDT,STRAX_USDT,MASK_USDT,MKR_USDT,AVAX_USDT,ENJ_USDT,YAM_USDT,ALPACA_USDT,DODO_USDT,MFT_USDT,CAKE_USDT,RNDR_USDT,CTSI_USDT,GRIN_USDT,MXC_USDT,ONT_USDT,ANKR_USDT,SLIM_USDT,FIL_USDT,CTK_USDT,ASR_USDT,FEG_USDT,SERO_USDT,RSS3_USDT,IRIS_USDT,XCH_USDT,ZRX_USDT,BAND_USDT,BADGER_USDT,DAO_USDT,EPS_USDT,THETA_USDT,BAKE_USDT,SHIB_USDT,MBOX_USDT,NBS_USDT,SNT_USDT,DREP_USDT,NFT_USDT,AUCTION_USDT,BOSON_USDT,O3_USDT,NULS_USDT,OMG_USDT,PEARL_USDT,HAPI_USDT,STG_USDT,IDV_USDT,HORD_USDT,ZIL_USDT,SUPER_USDT,DENT_USDT,REN_USDT,RAI_USDT,ZEN_USDT,ALGO_USDT,BLZ_USDT,BOR_USDT,SC_USDT,HEGIC_USDT,MOB_USDT,DORA_USDT,FOR_USDT,FLOW_USDT,RARI_USDT,DYDX_USDT,ATLAS_USDT,GST_USDT,REEF_USDT,HT_USDT,XYO_USDT,CHESS_USDT,BAT_USDT,NYM_USDT,RAMP_USDT,USDC_USDT,ICP_USDT,EPK_USDT,EXRD_USDT,DOT_USDT,COOK_USDT,CKB_USDT,YGG_USDT,CRU_USDT,ANC_USDT,FIS_USDT,ALCX_USDT,HIGH_USDT,BEAM_USDT,BSW_USDT,STAR_USDT,ROSE_USDT,CNNS_USDT,BZRX_USDT,WOO_USDT,SAFEMOON_USDT,VTHO_USDT,OM_USDT,LAMB_USDT,CHZ_USDT,AIOZ_USDT,EDEN_USDT,POND_USDT,ATOM_USDT,UNFI_USDT,FORTH_USDT,MLN_USDT,NEO_USDT,MOVR_USDT,RLC_USDT,FXS_USDT,ENS_USDT,ATA_USDT,XPR_USDT,NEST_USDT,XLM_USDT,AUTO_USDT,SNX_USDT,OCN_USDT,RSR_USDT,MITH_USDT,KAR_USDT,INJ_USDT,PLA_USDT,CYS_USDT,WAXP_USDT,VOXEL_USDT,CRV_USDT,FITFI_USDT,WHALE_USDT,WRX_USDT,TIDAL_USDT,C98_USDT,HNT_USDT,TONCOIN_USDT,DOGGY_USDT,SYS_USDT,NPXS_USDT,CRO_USDT,LEMD_USDT,RAY_USDT,PERL_USDT,CQT_USDT,CFX_USDT,TOMO_USDT,ACA_USDT,SDN_USDT,OKT_USDT,WILD_USDT,BNX_USDT,TRU_USDT,RACA_USDT,SWEAT_USDT,ACH_USDT,AKRO_USDT,BTM_USDT,TKO_USDT,GT_USDT,OCEAN_USDT,WNCG_USDT,BSV_USDT,GHST_USDT,CELR_USDT,LINA_USDT,SAND_USDT,APE_USDT,WICC_USDT,FIDA_USDT,ADA_USDT,PROPS_USDT,METIS_USDT,KAVA_USDT,AERGO_USDT,CONV_USDT,TFUEL_USDT,FRONT_USDT,API3_USDT,FARM_USDT,AE_USDT,LRC_USDT,IOTA_USDT,RFOX_USDT,PHA_USDT,XCN_USDT,NAS_USDT,KEEP_USDT,VIDY_USDT,HOT_USDT,MINA_USDT,ETHW_USDT,ALICE_USDT,HAI_USDT,BTC_USDT,LTC_USDT,LTO_USDT,DC_USDT,NU_USDT,IOST_USDT,RAD_USDT,POLS_USDT,OP_USDT,WXT_USDT,STR_USDT,YIELD_USDT,GM_USDT,SPA_USDT,BTCST_USDT,WEMIX_USDT,CLV_USDT,ICX_USDT,PET_USDT,STARL_USDT,HBAR_USDT,REDTOKEN_USDT,BTT_USDT,LINK_USDT,TLM_USDT,ARES_USDT,GTC_USDT,SUSHI_USDT,KEY_USDT,ALN_USDT,KDA_USDT,DVI_USDT,SXP_USDT,MAPS_USDT,BCD_USDT,SRM_USDT,WIN_USDT,ZEC_USDT,JASMY_USDT" - }, - "margin": { - "enabled": "BTC_USDT,ERN_USDT,T_USDT,CEEK_USDT,OGN_USDT,QNT_USDT,WOZX_USDT,ZEE_USDT,FUN_USDT,FLM_USDT,BOND_USDT", - "available": "BTC_USDT,ERN_USDT,T_USDT,CEEK_USDT,OGN_USDT,QNT_USDT,WOZX_USDT,ZEE_USDT,FUN_USDT,FLM_USDT,BOND_USDT,TARA_USDT,TRX_USDT,OXY_USDT,LON_USDT,DOGE_USDT,ISP_USDT,TWT_USDT,BAO_USDT,QUACK_USDT,ANT_USDT,VGX_USDT,ARPA_USDT,QUICK_USDT,UTK_USDT,HERO_USDT,WSG_USDT,BICO_USDT,MTV_USDT,VET_USDT,GARI_USDT,BCH_USDT,KLAY_USDT,WING_USDT,BLOK_USDT,SPS_USDT,WIKEN_USDT,WSIENNA_USDT,PUNDIX_USDT,FIC_USDT,ASTR_USDT,FET_USDT,VELO_USDT,BENQI_USDT,CWEB_USDT,RIF_USDT,UNI_USDT,ONG_USDT,ERG_USDT,ALPHA_USDT,CELO_USDT,XVG_USDT,GMAT_USDT,BTS_USDT,DOCK_USDT,GMT_USDT,DIA_USDT,CSPR_USDT,NKN_USDT,STAKE_USDT,SWASH_USDT,XEC_USDT,SWRV_USDT,QRDO_USDT,BLES_USDT,EOS_USDT,GRT_USDT,ASM_USDT,FIL6_USDT,GNO_USDT,EGLD_USDT,XYM_USDT,LOOKS_USDT,LOKA_USDT,BNC_USDT,BAS_USDT,SKL_USDT,STMX_USDT,CVC_USDT,DDOS_USDT,COTI_USDT,AVA_USDT,HMT_USDT,DF_USDT,LPT_USDT,XRP_USDT,TVK_USDT,FEVR_USDT,MBL_USDT,KIN_USDT,SPELL_USDT,MATIC_USDT,FTT_USDT,NMR_USDT,PMON_USDT,BNB_USDT,USDD_USDT,LSS_USDT,MDX_USDT,PRQ_USDT,ALPINE_USDT,DEGO_USDT,OMI_USDT,TIPS_USDT,OCT_USDT,FEI_USDT,UMEE_USDT,CRP_USDT,LION_USDT,YFI_USDT,DASH_USDT,REQ_USDT,SDAO_USDT,PNT_USDT,INSUR_USDT,OOKI_USDT,SUN_USDT,CRPT_USDT,BAC_USDT,DATA_USDT,LRN_USDT,JGN_USDT,KIMCHI_USDT,SUKU_USDT,VRA_USDT,AAVE_USDT,FTI_USDT,LDO_USDT,FRA_USDT,BLANK_USDT,NEAR_USDT,ZKS_USDT,MTRG_USDT,RLY_USDT,TCT_USDT,FLY_USDT,JST_USDT,YFII_USDT,AR_USDT,POLY_USDT,JULD_USDT,SOL_USDT,BZZ_USDT,AXS_USDT,ASD_USDT,XMR_USDT,FTM_USDT,HIT_USDT,LEO_USDT,LIT_USDT,PIG_USDT,COMP_USDT,ELON_USDT,IMX_USDT,EFI_USDT,XVS_USDT,WAVES_USDT,PEOPLE_USDT,SOS_USDT,RUNE_USDT,POLC_USDT,SCLP_USDT,BABYDOGE_USDT,KONO_USDT,SPI_USDT,ETC_USDT,MDA_USDT,MTL_USDT,BCHA_USDT,KISHU_USDT,SUNNY_USDT,PYR_USDT,XTZ_USDT,TRIBE_USDT,AUDIO_USDT,FIRO_USDT,MANA_USDT,OKB_USDT,DOG_USDT,SLP_USDT,KNC_USDT,GAS_USDT,LUNA_USDT,SAFEMARS_USDT,MIR_USDT,DAR_USDT,EGS_USDT,KSM_USDT,ATP_USDT,BIT_USDT,STORJ_USDT,XEM_USDT,QTUM_USDT,AGLD_USDT,RVN_USDT,OXT_USDT,SHFT_USDT,IOTX_USDT,LUNC_USDT,NEXO_USDT,AKITA_USDT,PERP_USDT,ONE_USDT,ETH_USDT,FLUX_USDT,FLOKI_USDT,STX_USDT,ANML_USDT,XPRT_USDT,GALA_USDT,GXS_USDT,TORN_USDT,KAI_USDT,1INCH_USDT,CHR_USDT,GAL_USDT,GLMR_USDT,CTX_USDT,CERE_USDT,CART_USDT,STRAX_USDT,MASK_USDT,MKR_USDT,AVAX_USDT,ENJ_USDT,YAM_USDT,ALPACA_USDT,DODO_USDT,MFT_USDT,CAKE_USDT,RNDR_USDT,CTSI_USDT,GRIN_USDT,MXC_USDT,ONT_USDT,ANKR_USDT,SLIM_USDT,FIL_USDT,CTK_USDT,ASR_USDT,FEG_USDT,SERO_USDT,RSS3_USDT,IRIS_USDT,XCH_USDT,ZRX_USDT,BAND_USDT,BADGER_USDT,DAO_USDT,EPS_USDT,THETA_USDT,BAKE_USDT,SHIB_USDT,MBOX_USDT,NBS_USDT,SNT_USDT,DREP_USDT,NFT_USDT,AUCTION_USDT,BOSON_USDT,O3_USDT,NULS_USDT,OMG_USDT,PEARL_USDT,HAPI_USDT,STG_USDT,IDV_USDT,HORD_USDT,ZIL_USDT,SUPER_USDT,DENT_USDT,REN_USDT,RAI_USDT,ZEN_USDT,ALGO_USDT,BLZ_USDT,BOR_USDT,SC_USDT,HEGIC_USDT,MOB_USDT,DORA_USDT,FOR_USDT,FLOW_USDT,RARI_USDT,DYDX_USDT,ATLAS_USDT,GST_USDT,REEF_USDT,HT_USDT,XYO_USDT,CHESS_USDT,BAT_USDT,NYM_USDT,RAMP_USDT,USDC_USDT,ICP_USDT,EPK_USDT,EXRD_USDT,DOT_USDT,COOK_USDT,CKB_USDT,YGG_USDT,CRU_USDT,ANC_USDT,FIS_USDT,ALCX_USDT,HIGH_USDT,BEAM_USDT,BSW_USDT,STAR_USDT,ROSE_USDT,CNNS_USDT,BZRX_USDT,WOO_USDT,SAFEMOON_USDT,VTHO_USDT,OM_USDT,LAMB_USDT,CHZ_USDT,AIOZ_USDT,EDEN_USDT,POND_USDT,ATOM_USDT,UNFI_USDT,FORTH_USDT,MLN_USDT,NEO_USDT,MOVR_USDT,RLC_USDT,FXS_USDT,ENS_USDT,ATA_USDT,XPR_USDT,NEST_USDT,XLM_USDT,AUTO_USDT,SNX_USDT,OCN_USDT,RSR_USDT,MITH_USDT,KAR_USDT,INJ_USDT,PLA_USDT,CYS_USDT,WAXP_USDT,VOXEL_USDT,CRV_USDT,FITFI_USDT,WHALE_USDT,WRX_USDT,TIDAL_USDT,C98_USDT,HNT_USDT,TONCOIN_USDT,DOGGY_USDT,SYS_USDT,NPXS_USDT,CRO_USDT,LEMD_USDT,RAY_USDT,PERL_USDT,CQT_USDT,CFX_USDT,TOMO_USDT,ACA_USDT,SDN_USDT,OKT_USDT,WILD_USDT,BNX_USDT,TRU_USDT,RACA_USDT,SWEAT_USDT,ACH_USDT,AKRO_USDT,BTM_USDT,TKO_USDT,GT_USDT,OCEAN_USDT,WNCG_USDT,BSV_USDT,GHST_USDT,CELR_USDT,LINA_USDT,SAND_USDT,APE_USDT,WICC_USDT,FIDA_USDT,ADA_USDT,PROPS_USDT,METIS_USDT,KAVA_USDT,AERGO_USDT,CONV_USDT,TFUEL_USDT,FRONT_USDT,API3_USDT,FARM_USDT,AE_USDT,LRC_USDT,IOTA_USDT,RFOX_USDT,PHA_USDT,XCN_USDT,NAS_USDT,KEEP_USDT,VIDY_USDT,HOT_USDT,MINA_USDT,ETHW_USDT,ALICE_USDT,HAI_USDT,LTC_USDT,LTO_USDT,DC_USDT,NU_USDT,IOST_USDT,RAD_USDT,POLS_USDT,OP_USDT,WXT_USDT,STR_USDT,YIELD_USDT,GM_USDT,SPA_USDT,BTCST_USDT,WEMIX_USDT,CLV_USDT,ICX_USDT,PET_USDT,STARL_USDT,HBAR_USDT,REDTOKEN_USDT,BTT_USDT,LINK_USDT,TLM_USDT,ARES_USDT,GTC_USDT,SUSHI_USDT,KEY_USDT,ALN_USDT,KDA_USDT,DVI_USDT,SXP_USDT,MAPS_USDT,BCD_USDT,SRM_USDT,WIN_USDT,ZEC_USDT,JASMY_USDT" - }, - "delivery": { - "enabled": "BTC_USD_20230331,BTC_USD_20221230,BTC_USDT_20221021,BTC_USDT_20221014", - "available": "BTC_USD_20221021,BTC_USD_20221014,BTC_USD_20230331,BTC_USD_20221230,BTC_USDT_20221021,BTC_USDT_20221014,BTC_USDT_20230331,BTC_USDT_20221230" - } - } - }, - "api": { - "authenticatedSupport": false, - "authenticatedWebsocketApiSupport": false, - "endpoints": { - "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" - }, - "credentials": { - "key": "Key", - "secret": "Secret" - }, - "credentialsValidator": { - "requiresKey": true, - "requiresSecret": true - } - }, - "features": { - "supports": { - "restAPI": true, - "restCapabilities": { - "tickerBatching": true, - "autoPairUpdates": true - }, - "websocketAPI": true, - "websocketCapabilities": {} - }, - "enabled": { - "autoPairUpdates": true, - "websocketAPI": true - } - }, - "bankAccounts": [ - { - "enabled": false, - "bankName": "", - "bankAddress": "", - "bankPostalCode": "", - "bankPostalCity": "", - "bankCountry": "", - "accountName": "", - "accountNumber": "", - "swiftCode": "", - "iban": "", - "supportedCurrencies": "" - } - ] + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "credentials": { + "key": "", + "secret": "" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true + }, + "urlEndpoints": { + "RestSpotSupplementaryURL": "https://api.binance.us", + "RestSpotURL": "https://api.binance.us", + "WebsocketSpotSupplementaryURL": "wss://stream.binance.us:9443/stream", + "WebsocketSpotURL": "wss://stream.binance.us:9443/stream" + } }, - { - "name": "Gemini", - "enabled": true, - "verbose": false, - "httpTimeout": 15000000000, - "websocketResponseCheckTimeout": 30000000, - "websocketResponseMaxLimit": 7000000000, - "websocketTrafficTimeout": 30000000000, - "websocketOrderbookBufferLimit": 5, - "baseCurrencies": "USD", - "currencyPairs": { - "requestFormat": { - "uppercase": true - }, - "configFormat": { - "uppercase": true - }, - "useGlobalFormat": true, - "assetTypes": [ - "spot" - ], - "pairs": { - "spot": { - "enabled": "BTCUSD", - "available": "BTCUSD,ETHBTC,ETHUSD,BCHUSD,BCHBTC,BCHETH,LTCUSD,LTCBTC,LTCETH,LTCBCH,ZECUSD,ZECBTC,ZECETH,ZECBCH,ZECLTC" - } - } - }, - "api": { - "authenticatedSupport": false, - "authenticatedWebsocketApiSupport": false, - "endpoints": { - "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" - }, - "credentials": { - "key": "Key", - "secret": "Secret" - }, - "credentialsValidator": { - "requiresKey": true, - "requiresSecret": true - } - }, - "features": { - "supports": { - "restAPI": true, - "restCapabilities": { - "autoPairUpdates": true - }, - "websocketAPI": true, - "websocketCapabilities": {} - }, - "enabled": { - "autoPairUpdates": true, - "websocketAPI": false - } - }, - "bankAccounts": [ - { - "enabled": false, - "bankName": "", - "bankAddress": "", - "bankPostalCode": "", - "bankPostalCity": "", - "bankCountry": "", - "accountName": "", - "accountNumber": "", - "swiftCode": "", - "iban": "", - "supportedCurrencies": "" - } - ] + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "tickerBatching": true, + "autoPairUpdates": true + }, + "websocketAPI": true, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": true, + "saveTradeData": false, + "tradeFeed": false, + "fillsFeed": false + } }, - { - "name": "HitBTC", - "enabled": true, - "verbose": false, - "httpTimeout": 15000000000, - "websocketResponseCheckTimeout": 30000000, - "websocketResponseMaxLimit": 7000000000, - "websocketTrafficTimeout": 30000000000, - "websocketOrderbookBufferLimit": 5, - "baseCurrencies": "USD", - "currencyPairs": { - "requestFormat": { - "uppercase": true - }, - "configFormat": { - "uppercase": true, - "delimiter": "-" - }, - "useGlobalFormat": true, - "assetTypes": [ - "spot" - ], - "pairs": { - "spot": { - "enabled": "BTC-USD", - "available": "BCN-BTC,BTC-USD,DASH-BTC,DOGE-BTC,DOGE-USD,EMC-BTC,ETH-BTC,LSK-BTC,LTC-BTC,LTC-USD,NXT-BTC,SBD-BTC,SC-BTC,STEEM-BTC,XDN-BTC,XEM-BTC,XMR-BTC,ARDR-BTC,ZEC-BTC,WAVES-BTC,MAID-BTC,DGD-BTC,SNGLS-BTC,1ST-BTC,TRST-BTC,TIME-BTC,GNO-BTC,REP-BTC,XMR-USD,DASH-USD,ETH-USD,NXT-USD,ZRC-BTC,BOS-BTC,DCT-BTC,ANT-BTC,AEON-BTC,GUP-BTC,PLU-BTC,LUN-BTC,EDG-BTC,RLC-BTC,SWT-BTC,TKN-BTC,WINGS-BTC,XAUR-BTC,AE-BTC,PTOY-BTC,ZEC-USD,XEM-USD,BCN-USD,XDN-USD,MAID-USD,ETC-BTC,ETC-USD,PLBT-BTC,BNT-BTC,SNT-ETH,CVC-USD,PAY-ETH,OAX-ETH,OMG-ETH,BQX-ETH,XTZ-BTC,DICE-BTC,PTOY-ETH,1ST-ETH,XAUR-ETH,TIME-ETH,DICE-ETH,SWT-ETH,XMR-ETH,ETC-ETH,DASH-ETH,ZEC-ETH,PLU-ETH,GNO-ETH,XRP-BTC,STRAT-USD,STRAT-BTC,SNC-ETH,ADX-ETH,BET-ETH,EOS-ETH,DENT-ETH,SAN-ETH,EOS-BTC,EOS-USD,XTZ-ETH,XTZ-USD,MYB-ETH,SUR-ETH,IXT-ETH,PLR-ETH,TIX-ETH,PRO-ETH,AVT-ETH,EVX-USD,DLT-BTC,BNT-ETH,BNT-USD,MANA-USD,DNT-BTC,FYP-BTC,OPT-BTC,TNT-ETH,STX-BTC,STX-ETH,STX-USD,TNT-USD,TNT-BTC,ENG-ETH,XUC-USD,SNC-BTC,SNC-USD,OAX-USD,OAX-BTC,ZRX-BTC,ZRX-ETH,ZRX-USD,RVT-BTC,PPC-BTC,PPC-USD,QTUM-ETH,IGNIS-ETH,BMC-BTC,BMC-ETH,BMC-USD,CND-BTC,CND-ETH,CND-USD,CDT-ETH,CDT-USD,FUN-BTC,FUN-ETH,FUN-USD,HVN-BTC,HVN-ETH,POE-BTC,POE-ETH,AMB-USD,AMB-ETH,AMB-BTC,HPC-BTC,PPT-ETH,MTH-BTC,MTH-ETH,LRC-BTC,LRC-ETH,ICX-BTC,ICX-ETH,NEO-BTC,NEO-ETH,NEO-USD,CSNO-BTC,ICX-USD,IND-ETH,KICK-BTC,YOYOW-BTC,CDT-BTC,XVG-BTC,XVG-ETH,XVG-USD,DGB-BTC,DGB-ETH,DGB-USD,DCN-ETH,DCN-USD,VIBE-BTC,ENJ-BTC,ENJ-ETH,ENJ-USD,ZSC-BTC,ZSC-ETH,ZSC-USD,TRX-BTC,TRX-ETH,TRX-USD,ART-BTC,EVX-BTC,EVX-ETH,SUB-BTC,SUB-ETH,SUB-USD,WTC-BTC,BTM-BTC,BTM-ETH,BTM-USD,LIFE-BTC,VIB-BTC,VIB-ETH,VIB-USD,DRT-ETH,STU-USD,OMG-BTC,PAY-BTC,PPT-BTC,SNT-BTC,BTG-BTC,BTG-ETH,BTG-USD,SMART-BTC,SMART-ETH,SMART-USD,XUC-ETH,XUC-BTC,LA-ETH,EDO-BTC,EDO-ETH,EDO-USD,HGT-ETH,IXT-BTC,SCL-BTC,ETP-BTC,ETP-ETH,ETP-USD,NEBL-BTC,NEBL-ETH,ARN-BTC,ARN-ETH,STU-BTC,STU-ETH,GVT-ETH,BTX-BTC,LTC-ETH,BCN-ETH,MAID-ETH,NXT-ETH,STRAT-ETH,XDN-ETH,XEM-ETH,PLR-BTC,SUR-BTC,BQX-BTC,DOGE-ETH,AMM-BTC,AMM-ETH,AMM-USD,DBIX-BTC,PRE-BTC,ZAP-BTC,DOV-BTC,DOV-ETH,XRP-ETH,XRP-USD,HSR-BTC,LEND-BTC,LEND-ETH,SPF-ETH,SBTC-BTC,SBTC-ETH,LOC-BTC,LOC-ETH,LOC-USD,SWFTC-BTC,SWFTC-ETH,SWFTC-USD,STAR-ETH,SBTC-USD,STORM-BTC,DIM-ETH,DIM-USD,DIM-BTC,NGC-BTC,NGC-ETH,NGC-USD,EMC-ETH,EMC-USD,MCO-BTC,MCO-ETH,MCO-USD,MANA-ETH,MANA-BTC,CPAY-ETH,DATA-BTC,DATA-ETH,DATA-USD,UTT-BTC,UTT-ETH,UTT-USD,KMD-BTC,KMD-ETH,KMD-USD,QTUM-USD,QTUM-BTC,SNT-USD,OMG-USD,EKO-BTC,EKO-ETH,ADX-BTC,ADX-USD,LSK-ETH,LSK-USD,PLR-USD,SUR-USD,BQX-USD,DRT-USD,REP-ETH,REP-USD,WAXP-BTC,WAXP-ETH,WAXP-USD,C20-BTC,C20-ETH,IDH-BTC,IDH-ETH,IPL-BTC,COV-BTC,COV-ETH,SENT-BTC,SENT-ETH,SENT-USD,SMT-BTC,SMT-ETH,SMT-USD,CHAT-BTC,CHAT-ETH,CHAT-USD,TRAC-ETH,JNT-ETH,UTK-BTC,UTK-ETH,UTK-USD,GNX-ETH,CHSB-BTC,CHSB-ETH,DAY-BTC,DAY-ETH,DAY-USD,NEU-BTC,NEU-ETH,NEU-USD,TAU-BTC,FLP-BTC,FLP-ETH,FLP-USD,R-BTC,R-ETH,EKO-USD,BCPT-ETH,BCPT-USD,PKT-BTC,PKT-ETH,BETR-BTC,BETR-ETH,HAND-ETH,HAND-USD,CHP-ETH,BCPT-BTC,ACT-BTC,ACT-ETH,ACT-USD,ADA-BTC,ADA-ETH,ADA-USD,SIG-BTC,MTX-BTC,MTX-ETH,MTX-USD,WIZ-BTC,WIZ-ETH,WIZ-USD,DADI-BTC,DADI-ETH,BDG-ETH,DATX-BTC,DATX-ETH,TRUE-BTC,DRG-BTC,DRG-ETH,BANCA-BTC,BANCA-ETH,ZAP-ETH,ZAP-USD,AUTO-BTC,SOC-BTC,OCN-BTC,OCN-ETH,STQ-BTC,STQ-ETH,XLM-BTC,XLM-ETH,XLM-USD,IOTA-BTC,IOTA-ETH,IOTA-USD,DRT-BTC,BETR-USD,ERT-BTC,CRPT-BTC,CRPT-USD,MESH-BTC,MESH-ETH,MESH-USD,IHT-BTC,IHT-ETH,IHT-USD,SCC-BTC,YCC-BTC,DAN-BTC,TEL-BTC,TEL-ETH,NCT-BTC,NCT-ETH,NCT-USD,BMH-BTC,BANCA-USD,BERRY-BTC,BERRY-ETH,BERRY-USD,GBX-BTC,GBX-ETH,GBX-USD,SHIP-BTC,SHIP-ETH,NANO-BTC,NANO-ETH,NANO-USD,LNC-BTC,KIN-ETH,ARDR-USD,FOTA-ETH,FOTA-BTC,CVT-BTC,CVT-ETH,CVT-USD,STQ-USD,GNT-BTC,GNT-ETH,GNT-USD,GET-BTC,MITH-BTC,MITH-ETH,MITH-USD,DADI-USD,TKY-BTC,ACAT-BTC,ACAT-ETH,ACAT-USD,BTX-USD,WIKI-BTC,WIKI-ETH,WIKI-USD,ONT-BTC,ONT-ETH,ONT-USD,FTX-BTC,FTX-ETH,NAVI-BTC,VME-ETH,NAVI-ETH,LND-ETH,CSM-BTC,NANJ-BTC,NTK-BTC,NTK-ETH,NTK-USD,AUC-BTC,AUC-ETH,CMCT-BTC,CMCT-ETH,CMCT-USD,MAN-BTC,MAN-ETH,MAN-USD,PNT-BTC,PNT-ETH,FXT-BTC,NEXO-BTC,PAT-BTC,PAT-ETH,XMC-BTC,FXT-ETH,XMC-ETH,XMC-USD,FDZ-BTC,FDZ-ETH,FDZ-USD,SPD-BTC,SPD-ETH,MITX-BTC,TIV-BTC,B2G-BTC,B2G-USD,HBZ-BTC,FACE-BTC,FACE-ETH,HBZ-ETH,HBZ-USD,CPT-BTC,PAT-USD,HTML-BTC,HTML-ETH,MITX-ETH,BTS-BTC,BNK-BTC,BNK-ETH,BNK-USD,TIV-ETH,TIV-USD,CSM-ETH,CSM-USD,INK-BTC,IOST-BTC,INK-ETH,INK-USD,CBC-BTC,IOST-USD,ZIL-BTC,ABYSS-BTC,ABYSS-ETH,ZIL-USD,BCI-BTC,CBC-ETH,CBC-USD,PITCH-BTC,PITCH-ETH,HTML-USD,TDS-BTC,TDS-ETH,TDS-USD,SBD-ETH,SBD-USD,DPN-BTC,UUU-BTC,UUU-ETH,XBP-BTC,ELEC-BTC,ELEC-ETH,ELEC-USD,QNTU-BTC,QNTU-ETH,QNTU-USD,IPL-ETH,IPL-USD,CENNZ-BTC,CENNZ-ETH,SWM-BTC,SPF-USD,SPF-BTC,LCC-BTC,HGT-BTC,ETH-TUSD,BTC-TUSD,LTC-TUSD,XMR-TUSD,ZRX-TUSD,NEO-TUSD,USD-TUSD,BTC-DAI,ETH-DAI,MKR-DAI,EOS-DAI,USD-DAI,MKR-BTC,MKR-ETH,MKR-USD,TUSD-DAI,NEO-DAI,LTC-DAI,XMR-DAI,XRP-DAI,NEXO-ETH,NEXO-USD,DWS-BTC,DWS-ETH,DWS-USD,APPC-BTC,APPC-ETH,APPC-USD,BIT-ETH,SPC-BTC,SPC-ETH,SPC-USD,REX-BTC,REX-ETH,REX-USD,ELF-BTC,ELF-USD,BCD-BTC,BCD-USD,CVCOIN-BTC,CVCOIN-ETH,CVCOIN-USD,EDG-ETH,EDG-USD,NLC2-BTC,DASH-EURS,ZEC-EURS,BTC-EURS,EOS-EURS,ETH-EURS,LTC-EURS,NEO-EURS,XMR-EURS,XRP-EURS,EURS-USD,EURS-TUSD,EURS-DAI,MNX-USD,ROX-ETH,ZPR-ETH,MNX-BTC,MNX-ETH,KIND-BTC,KIND-ETH,ENGT-BTC,ENGT-ETH,PMA-BTC,PMA-ETH,TV-BTC,TV-ETH,TV-USD,BAT-BTC,BAT-ETH,BAT-USD,SRN-BTC,SRN-ETH,SRN-USD,SVD-BTC,SVD-ETH,SVD-USD,GST-BTC,GST-ETH,GST-USD,BNB-BTC,BNB-ETH,BNB-USD,DIT-BTC,DIT-ETH,POA20-BTC,PROC-BTC,POA20-ETH,POA20-USD,POA20-DAI,NIM-BTC,USE-BTC,USE-ETH,DAV-BTC,DAV-ETH,ABTC-BTC,NIM-ETH,ABA-BTC,ABA-ETH,ABA-USD,BCN-EOS,LTC-EOS,XMR-EOS,DASH-EOS,TRX-EOS,NEO-EOS,ZEC-EOS,LSK-EOS,XEM-EOS,XRP-EOS,RCN-BTC,RCN-ETH,RCN-USD,HMQ-BTC,HMQ-ETH,MYST-BTC,MYST-ETH,USD-GUSD,BTC-GUSD,ETH-GUSD,EOS-GUSD,AXPR-BTC,AXPR-ETH,DAG-BTC,DAG-ETH,BITS-BTC,BITS-ETH,BITS-USD,CDCC-BTC,CDCC-ETH,CDCC-USD,VET-BTC,VET-ETH,VET-USD,SILK-ETH,BOX-BTC,BOX-ETH,BOX-EURS,BOX-EOS,VOCO-BTC,VOCO-ETH,VOCO-USD,PASS-BTC,PASS-ETH,SLX-BTC,SLX-USD,PBTT-BTC,PMA-USD,TRAD-BTC,DGTX-BTC,DGTX-ETH,DGTX-USD,MRK-BTC,MRK-ETH,DGB-TUSD,SNBL-BTC,BCH-BTC,BCH-USD,BSV-BTC,BSV-USD,BKX-BTC,NPLC-BTC,NPLC-ETH,ETN-BTC,ETN-ETH,ETN-USD,DTR-BTC,DTR-ETH,TDP-BTC,HBT-ETH,PXG-BTC,PXG-USD,BTC-PAX,ETH-PAX,USD-PAX,BTC-USDC,ETH-USDC,USD-USDC,TUSD-USDC,DAI-USDC,EOS-PAX,CLO-BTC,CLO-ETH,CLO-USD,PETH-BTC,PETH-ETH,PETH-USD,BRD-BTC,BRD-ETH,NMR-BTC,SALT-BTC,SALT-ETH,POLY-BTC,POLY-ETH,POWR-BTC,POWR-ETH,STORJ-BTC,STORJ-ETH,STORJ-USD,MLN-BTC,MLN-ETH,BDG-BTC,POA-ETH,POA-BTC,POA-USD,POA-DAI,KIN-BTC,VEO-BTC,PLA-BTC,PLA-ETH,PLA-USD,BTT-BTC,BTT-USD,BTT-ETH,ZEN-BTC,ZEN-ETH,ZEN-USD,GRIN-BTC,GRIN-ETH,GRIN-USD,FET-BTC,HT-BTC,HT-USD,XZC-BTC,XZC-ETH,XZC-USD,VRA-BTC,VRA-ETH,BTC-KRWB,USD-KRWB,WBTC-ETH,CRO-BTC,CRO-ETH,CRO-USD,GAS-BTC,GAS-ETH,GAS-USD,ORMEUS-BTC,ORMEUS-ETH,SWM-ETH,SWM-USD,PRE-ETH,PHX-BTC,PHX-ETH,PHX-USD,BET-BTC,USD-EOSDT,BTC-EOSDT,ETH-EOSDT,EOS-EOSDT,DAI-EOSDT,NUT-BTC,NUT-EOS,NUT-USD,CUTE-BTC,CUTE-ETH,CUTE-USD,CUTE-EOS,XCON-BTC,DCR-BTC,DCR-ETH,DCR-USD,MG-BTC,MG-ETH,MG-EOS,MG-USD,GNX-BTC,PRO-BTC,EURS-EOSDT,TUSD-EOSDT,ECOIN-BTC,ECOIN-ETH,ECOIN-USD,AGI-BTC,LOOM-BTC,LOOM-ETH,BLZ-BTC,QKC-BTC,QKC-ETH,KNC-BTC,KNC-ETH,KNC-USD,KEY-BTC,KEY-ETH,ATOM-BTC,ATOM-USD,ATOM-ETH,BRDG-BTC,BRDG-ETH,BRDG-USD,MTL-BTC,MTL-ETH,EXP-BTC,BTCB-BTC,PBT-BTC,PBT-ETH,LINK-BTC,LINK-ETH,LINK-USD,USD-USDT20,PHB-BTC,BCH-ETH,BCH-DAI,BCH-TUSD,BCH-EURS,DAPP-BTC,DAPP-EOS,BTC-USDT20,DENT-BTC,DENT-USD,NJBC-BTC,NJBC-ETH,XRC-BTC,EOS-BCH,LTC-BCH,XRP-BCH,TRX-BCH,XLM-BCH,ETC-BCH,DASH-BCH,ZEC-BCH,BKX-USD,LAMB-BTC,NPXS-BTC,HBAR-BTC,HBAR-USD,ONE-BTC,RFR-BTC,RFR-USD,BUSD-USD,PAXG-BTC,PAXG-USD,REN-BTC,IGNIS-BTC,CEL-BTC,CEL-ETH,WIN-USD,ADK-BTC,PART-BTC,SOZ-BTC,SOZ-ETH,SOZ-USD,WAVES-USD,ADA-BCH,ONT-BCH,XMR-BCH,ATOM-BCH,LINK-BCH,OMG-BCH,WAVES-BCH,IOTX-BTC,HOT-BTC,SLV-BTC,HEDG-BTC,CHZ-BTC,CHZ-USD,COCOS-BTC,COCOS-USD,SEELE-BTC,SEELE-USD,MDA-BTC,LEO-USD,REM-BTC,REM-ETH,REM-USD,SCD-DAI,BTC-BUSD,RVN-BTC,BST-BTC,ERD-BTC,KRL-BTC,FTT-BTC,FTT-USD,RAISE-BTC,RAISE-ETH" - } - } - }, - "api": { - "authenticatedSupport": false, - "authenticatedWebsocketApiSupport": false, - "endpoints": { - "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" - }, - "credentials": { - "key": "Key", - "secret": "Secret" - }, - "credentialsValidator": { - "requiresKey": true, - "requiresSecret": true - } - }, - "features": { - "supports": { - "restAPI": true, - "restCapabilities": { - "tickerBatching": true, - "autoPairUpdates": true - }, - "websocketAPI": true, - "websocketCapabilities": {} - }, - "enabled": { - "autoPairUpdates": true, - "websocketAPI": false - } - }, - "bankAccounts": [ - { - "enabled": false, - "bankName": "", - "bankAddress": "", - "bankPostalCode": "", - "bankPostalCity": "", - "bankCountry": "", - "accountName": "", - "accountNumber": "", - "swiftCode": "", - "iban": "", - "supportedCurrencies": "" - } - ] + "bankAccounts": [ + { + "enabled": false, + "bankName": "", + "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ], + "orderbook": { + "verificationBypass": false, + "websocketBufferLimit": 5, + "websocketBufferEnabled": false, + "publishPeriod": 10000000000 + } + }, + { + "name": "Bitfinex", + "enabled": true, + "verbose": false, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, + "websocketOrderbookBufferLimit": 5, + "baseCurrencies": "USD", + "currencyPairs": { + "requestFormat": { + "uppercase": true + }, + "configFormat": { + "uppercase": true + }, + "useGlobalFormat": true, + "assetTypes": [ + "spot" + ], + "pairs": { + "spot": { + "enabled": "BTCUSD,LTCUSD,LTCBTC,ETHUSD,ETHBTC", + "available": "BTCUSD,LTCUSD,LTCBTC,ETHUSD,ETHBTC,ETCBTC,ETCUSD,RRTUSD,RRTBTC,ZECUSD,ZECBTC,XMRUSD,XMRBTC,DSHUSD,DSHBTC,BTCEUR,BTCJPY,XRPUSD,XRPBTC,IOTUSD,IOTBTC,IOTETH,EOSUSD,EOSBTC,EOSETH,SANUSD,SANBTC,SANETH,OMGUSD,OMGBTC,OMGETH,NEOUSD,NEOBTC,NEOETH,ETPUSD,ETPBTC,ETPETH,QTMUSD,QTMBTC,QTMETH,AVTUSD,AVTBTC,AVTETH,EDOUSD,EDOBTC,EDOETH,BTGUSD,BTGBTC,DATUSD,DATBTC,DATETH,QSHUSD,QSHBTC,QSHETH,YYWUSD,YYWBTC,YYWETH,GNTUSD,GNTBTC,GNTETH,SNTUSD,SNTBTC,SNTETH,IOTEUR,BATUSD,BATBTC,BATETH,MNAUSD,MNABTC,MNAETH,FUNUSD,FUNBTC,FUNETH,ZRXUSD,ZRXBTC,ZRXETH,TNBUSD,TNBBTC,TNBETH,SPKUSD,SPKBTC,SPKETH,TRXUSD,TRXBTC,TRXETH,RCNUSD,RCNBTC,RCNETH,RLCUSD,RLCBTC,RLCETH,AIDUSD,AIDBTC,AIDETH,SNGUSD,SNGBTC,SNGETH,REPUSD,REPBTC,REPETH,ELFUSD,ELFBTC,ELFETH,NECUSD,NECBTC,NECETH,BTCGBP,ETHEUR,ETHJPY,ETHGBP,NEOEUR,NEOJPY,NEOGBP,EOSEUR,EOSJPY,EOSGBP,IOTJPY,IOTGBP,IOSUSD,IOSBTC,IOSETH,AIOUSD,AIOBTC,AIOETH,REQUSD,REQBTC,REQETH,RDNUSD,RDNBTC,RDNETH,LRCUSD,LRCBTC,LRCETH,WAXUSD,WAXBTC,WAXETH,DAIUSD,DAIBTC,DAIETH,AGIUSD,AGIBTC,AGIETH,BFTUSD,BFTBTC,BFTETH,MTNUSD,MTNBTC,MTNETH,ODEUSD,ODEBTC,ODEETH,ANTUSD,ANTBTC,ANTETH,DTHUSD,DTHBTC,DTHETH,MITUSD,MITBTC,MITETH,STJUSD,STJBTC,STJETH,XLMUSD,XLMEUR,XLMJPY,XLMGBP,XLMBTC,XLMETH,XVGUSD,XVGEUR,XVGJPY,XVGGBP,XVGBTC,XVGETH,BCIUSD,BCIBTC,MKRUSD,MKRBTC,MKRETH,KNCUSD,KNCBTC,KNCETH,POAUSD,POABTC,POAETH,EVTUSD,LYMUSD,LYMBTC,LYMETH,UTKUSD,UTKBTC,UTKETH,VEEUSD,VEEBTC,VEEETH,DADUSD,DADBTC,DADETH,ORSUSD,ORSBTC,ORSETH,AUCUSD,AUCBTC,AUCETH,POYUSD,POYBTC,POYETH,FSNUSD,FSNBTC,FSNETH,CBTUSD,CBTBTC,CBTETH,ZCNUSD,ZCNBTC,ZCNETH,SENUSD,SENBTC,SENETH,NCAUSD,NCABTC,NCAETH,CNDUSD,CNDBTC,CNDETH,CTXUSD,CTXBTC,CTXETH,PAIUSD,PAIBTC,SEEUSD,SEEBTC,SEEETH,ESSUSD,ESSBTC,ESSETH,ATMUSD,ATMBTC,ATMETH,HOTUSD,HOTBTC,HOTETH,DTAUSD,DTABTC,DTAETH,IQXUSD,IQXBTC,IQXEOS,WPRUSD,WPRBTC,WPRETH,ZILUSD,ZILBTC,ZILETH,BNTUSD,BNTBTC,BNTETH,ABSUSD,ABSETH,XRAUSD,XRAETH,MANUSD,MANETH,BBNUSD,BBNETH,NIOUSD,NIOETH,DGXUSD,DGXETH,VETUSD,VETBTC,VETETH,UTNUSD,UTNETH,TKNUSD,TKNETH,GOTUSD,GOTEUR,GOTETH,XTZUSD,XTZBTC,CNNUSD,CNNETH,BOXUSD,BOXETH,TRXEUR,TRXGBP,TRXJPY,MGOUSD,MGOETH,RTEUSD,RTEETH,YGGUSD,YGGETH,MLNUSD,MLNETH,WTCUSD,WTCETH,CSXUSD,CSXETH,OMNUSD,OMNBTC,INTUSD,INTETH,DRNUSD,DRNETH,PNKUSD,PNKETH,DGBUSD,DGBBTC,BSVUSD,BSVBTC,BABUSD,BABBTC,WLOUSD,WLOXLM,VLDUSD,VLDETH,ENJUSD,ENJETH,ONLUSD,ONLETH,RBTUSD,RBTBTC,USTUSD,EUTEUR,EUTUSD,GSDUSD,UDCUSD,TSDUSD,PAXUSD,RIFUSD,RIFBTC,PASUSD,PASETH,VSYUSD,VSYBTC,ZRXDAI,MKRDAI,OMGDAI,BTTUSD,BTTBTC,BTCUST,ETHUST,CLOUSD,CLOBTC,IMPUSD,IMPETH,LTCUST,EOSUST,BABUST,SCRUSD,SCRETH,GNOUSD,GNOETH,GENUSD,GENETH,ATOUSD,ATOBTC,ATOETH,WBTUSD,XCHUSD,EUSUSD,WBTETH,XCHETH,EUSETH,LEOUSD,LEOBTC,LEOUST,LEOEOS,LEOETH,ASTUSD,ASTETH,FOAUSD,FOAETH,UFRUSD,UFRETH,ZBTUSD,ZBTUST,OKBUSD,USKUSD,GTXUSD,KANUSD,OKBUST,OKBETH,OKBBTC,USKUST,USKETH,USKBTC,USKEOS,GTXUST,KANUST,AMPUSD,ALGUSD,ALGBTC,ALGUST,BTCXCH,SWMUSD,SWMETH,TRIUSD,TRIETH,LOOUSD,LOOETH,AMPUST,DUSK:USD,DUSK:BTC,UOSUSD,UOSBTC,RRBUSD,RRBUST,DTXUSD,DTXUST,AMPBTC,FTTUSD,FTTUST,PAXUST,UDCUST,TSDUST,BTC:CNHT,UST:CNHT,CNH:CNHT,CHZUSD,CHZUST,BTCF0:USTF0,ETHF0:USTF0" + } + } }, - { - "name": "Huobi", - "enabled": true, - "verbose": false, - "httpTimeout": 15000000000, - "websocketResponseCheckTimeout": 30000000, - "websocketResponseMaxLimit": 7000000000, - "websocketTrafficTimeout": 30000000000, - "websocketOrderbookBufferLimit": 5, - "baseCurrencies": "USD", - "currencyPairs": { - "requestFormat": { - "uppercase": false - }, - "configFormat": { - "uppercase": true, - "delimiter": "-" - }, - "useGlobalFormat": true, - "assetTypes": [ - "spot", - "coinmarginedfutures", - "futures" - ], - "pairs": { - "coinmarginedfutures": { - "assetEnabled": true, - "enabled": "BTC-USD", - "available": "BTC-USD,ETH-USD,LINK-USD,DOT-USD,ADA-USD,LTC-USD,XRP-USD,TRX-USD,DOGE-USD", - "requestFormat": { - "uppercase": true, - "delimiter": "-" - }, - "configFormat": { - "uppercase": true, - "delimiter": "-" - } - }, - "futures": { - "assetEnabled": true, - "enabled": "BTC-230915", - "available": "BTC-230915,BTC-230922,BTC-230929,ETH-230915,ETH-230922,ETH-230929,TRX-230915,TRX-230922", - "requestFormat": { - "uppercase": true - }, - "configFormat": { - "uppercase": true, - "delimiter": "-" - } - }, - "spot": { - "enabled": "BTC-USDT", - "available": "PROPY-ETH,IOTA-BTC,UGAS-ETH,PAI-USDT,BSV-HUSD,MTX-ETH,BCH-BTC,LTC-HT,SOC-USDT,WXT-BTC,SALT-BTC,RCN-ETH,PNT-ETH,TT-USDT,AIDOC-ETH,BIX-BTC,OCN-USDT,QTUM-ETH,KCASH-ETH,SNT-USDT,LUN-BTC,QASH-BTC,ITC-BTC,NAS-BTC,XMR-BTC,TNT-ETH,UC-ETH,FAIR-BTC,PC-ETH,YEE-BTC,PAY-ETH,XMX-BTC,CRE-USDT,BAT-ETH,BHT-USDT,CKB-HT,LAMB-HT,AE-USDT,QUN-ETH,LYM-BTC,BCH-HT,BHT-BTC,RUFF-ETH,CNN-BTC,FOR-USDT,GTC-ETH,TRX-ETH,ELA-USDT,ACT-ETH,SMT-ETH,BUT-ETH,BCH-USDT,ICX-BTC,MEET-BTC,NCC-BTC,APPC-BTC,GVE-ETH,TNB-BTC,STEEM-ETH,18C-ETH,LBA-BTC,EKO-BTC,REQ-BTC,SOC-BTC,BOX-ETH,ELF-BTC,ZRX-ETH,LET-USDT,HT-BTC,TUSD-HUSD,EGCC-BTC,WTC-BTC,ATP-USDT,DOCK-USDT,PAI-BTC,ONT-ETH,IRIS-BTC,BTT-ETH,SC-BTC,XZC-BTC,LBA-USDT,HT-USDT,VET-ETH,KMD-ETH,SHE-ETH,PORTAL-BTC,ONE-BTC,BIX-USDT,RCCC-BTC,SKM-USDT,XTZ-ETH,SWFTC-BTC,RSR-BTC,LINK-ETH,DATX-BTC,HPT-HT,GET-ETH,BLZ-ETH,CTXC-USDT,CNNS-USDT,PVT-HT,ITC-USDT,LTC-BTC,NCASH-BTC,HOT-ETH,ADA-USDT,ADX-BTC,NODE-USDT,TRIO-BTC,GXC-ETH,SNT-BTC,FOR-BTC,DBC-BTC,UUU-USDT,CVCOIN-ETH,RSR-USDT,CRO-USDT,OCN-BTC,NEW-USDT,EGT-USDT,MANA-BTC,CMT-USDT,WXT-HT,XRP-BTC,MT-ETH,PAX-HUSD,LSK-ETH,IOTA-USDT,SRN-ETH,ZIL-ETH,ELF-USDT,LXT-ETH,LAMB-BTC,CRE-HT,CKB-BTC,XVG-BTC,BSV-BTC,BFT-BTC,WPR-ETH,HT-HUSD,POWR-BTC,MANA-ETH,ENG-ETH,ZJLT-ETH,SNC-ETH,ATOM-ETH,WICC-USDT,KAN-ETH,DGD-BTC,VSYS-HT,BCD-BTC,BTM-ETH,DOGE-USDT,MEX-BTC,BTG-BTC,DAC-ETH,DAT-BTC,GRS-ETH,ADX-ETH,EM-HT,GXC-USDT,CVC-BTC,OMG-ETH,SSP-ETH,OGO-HT,CMT-ETH,POLY-ETH,XZC-USDT,THETA-USDT,XEM-USDT,LOL-USDT,BCH-HUSD,GSC-BTC,DOGE-ETH,MDS-BTC,BTS-ETH,CTXC-BTC,MCO-BTC,BCX-BTC,ZLA-ETH,EKT-USDT,MAN-BTC,BLZ-BTC,ATOM-USDT,LOL-BTC,HPT-USDT,EM-BTC,EOS-USDT,WAN-BTC,GNT-BTC,CRO-BTC,MANA-USDT,SEELE-USDT,FSN-BTC,VIDY-HT,USDC-HUSD,LTC-HUSD,XRP-USDT,VSYS-BTC,STORJ-BTC,LOOM-ETH,SKM-BTC,LINK-USDT,TT-HT,QSP-ETH,ETN-BTC,FSN-HT,NODE-BTC,HC-USDT,PHX-BTC,XLM-BTC,RCCC-ETH,LTC-USDT,UUU-BTC,SEELE-ETH,PVT-BTC,HC-ETH,REN-ETH,KAN-USDT,EOS-ETH,BSV-USDT,BTS-USDT,KMD-BTC,OGO-USDT,THETA-ETH,MUSK-BTC,CNNS-HT,ETC-BTC,COVA-BTC,BTT-TRX,XMR-USDT,MTN-ETH,QUN-BTC,NAS-USDT,ELA-ETH,HIT-ETH,BTT-USDT,EKT-ETH,TOS-BTC,GAS-ETH,DCR-USDT,ONT-BTC,NEW-HT,NEXO-BTC,ETH-USDT,WXT-USDT,FOR-HT,ADA-BTC,EVX-ETH,VET-BTC,ZEC-USDT,NANO-ETH,IOST-HT,BCV-ETH,REN-USDT,NULS-ETH,ACT-USDT,LET-ETH,BTM-USDT,MEET-ETH,AKRO-HT,ARDR-BTC,DCR-ETH,NANO-USDT,BTC-HUSD,ALGO-BTC,IIC-ETH,BHD-BTC,KNC-ETH,ATP-BTC,ZRX-BTC,ABT-BTC,18C-BTC,XMR-ETH,WAXP-BTC,CVNT-BTC,MX-USDT,OST-ETH,NKN-BTC,TOPC-BTC,GNX-BTC,FTT-USDT,ONE-HT,DGB-ETH,NULS-USDT,DASH-BTC,UIP-BTC,KCASH-HT,WICC-ETH,EKO-ETH,EGT-HT,IRIS-USDT,STK-ETH,MXC-BTC,NAS-ETH,OMG-USDT,SMT-BTC,BUT-BTC,HIT-USDT,BAT-BTC,IRIS-ETH,NKN-HT,PC-BTC,TOP-USDT,GTC-BTC,LSK-BTC,ITC-ETH,DTA-BTC,HOT-BTC,BTT-BTC,FAIR-ETH,DOCK-ETH,QTUM-BTC,ZEN-BTC,ZIL-BTC,RCN-BTC,FTI-BTC,BHD-USDT,VIDY-USDT,LUN-ETH,DBC-ETH,TOPC-ETH,IIC-BTC,STEEM-USDT,IOTA-ETH,KCASH-BTC,RUFF-BTC,APPC-ETH,MT-BTC,SOC-ETH,GT-HT,PROPY-BTC,AIDOC-BTC,ACT-BTC,LYM-ETH,CHAT-BTC,SWFTC-ETH,ETH-BTC,UIP-USDT,UGAS-BTC,XRP-HUSD,ALGO-USDT,TNT-BTC,ONT-USDT,YEE-ETH,AKRO-BTC,TRX-USDT,OCN-ETH,SRN-BTC,DASH-USDT,XMX-ETH,NANO-BTC,QASH-ETH,EOS-HT,GT-BTC,XTZ-USDT,ARPA-USDT,SALT-ETH,BKBT-ETH,MTX-BTC,SMT-USDT,GXC-BTC,VIDY-BTC,FTT-HT,LAMB-ETH,TRX-BTC,TRIO-ETH,BFT-ETH,LINK-BTC,AE-ETH,NULS-BTC,BHD-HT,AST-ETH,NEO-USDT,EDU-BTC,CVCOIN-BTC,GVE-BTC,GET-BTC,ZRX-USDT,ELF-ETH,DATX-ETH,ADA-ETH,TOP-HT,NCASH-ETH,QTUM-USDT,ETC-HT,ZIL-USDT,TNB-ETH,BIX-ETH,SHE-BTC,PNT-BTC,BTC-USDT,PORTAL-ETH,WAVES-USDT,XZC-ETH,HT-ETH,POLY-BTC,MCO-ETH,MUSK-ETH,PAI-ETH,LXT-USDT,UTK-BTC,RTE-BTC,NCC-ETH,HB10-USDT,BOX-BTC,RDN-ETH,ARPA-BTC,LBA-ETH,CNN-ETH,AAC-ETH,XTZ-BTC,IDT-BTC,AKRO-USDT,IOST-BTC,GT-USDT,WAN-ETH,ETN-ETH,PVT-USDT,NEO-BTC,WAVES-ETH,ONE-USDT,ZEC-BTC,SKM-HT,IOST-ETH,NPXS-ETH,CVC-ETH,CMT-BTC,COVA-ETH,ARDR-ETH,RDN-BTC,DCR-BTC,REN-BTC,YCC-ETH,MX-HT,NEXO-ETH,XLM-ETH,YCC-BTC,ENG-BTC,CNNS-BTC,ZLA-BTC,QSP-BTC,MAN-ETH,UUU-ETH,ETH-HUSD,RTE-ETH,ATP-HT,BTM-BTC,DAC-BTC,TOS-ETH,LAMB-USDT,DASH-HT,NPXS-BTC,NEW-BTC,FTT-BTC,EOS-HUSD,GRS-BTC,POWR-ETH,VET-USDT,AAC-BTC,MX-BTC,MTN-BTC,XVG-ETH,GNX-ETH,SSP-BTC,WAVES-BTC,EGT-BTC,CTXC-ETH,IDT-ETH,STK-BTC,WICC-BTC,UTK-ETH,CRO-HT,LXT-BTC,GSC-ETH,OMG-BTC,XRP-HT,DGB-BTC,IOST-USDT,CVNT-ETH,GAS-BTC,HIT-BTC,CKB-USDT,ARPA-HT,RUFF-USDT,HC-BTC,WTC-ETH,MDS-USDT,ABT-ETH,ALGO-ETH,BIFI-BTC,KNC-BTC,TT-BTC,LET-BTC,NKN-USDT,PAY-BTC,DTA-USDT,AE-BTC,UC-BTC,VSYS-USDT,USDT-HUSD,EOS-BTC,STEEM-BTC,DOGE-BTC,NODE-HT,MDS-ETH,CRE-BTC,GNT-USDT,UIP-ETH,AST-BTC,XEM-BTC,ZEN-ETH,EDU-ETH,MEX-ETH,EKT-BTC,CVC-USDT,WAXP-ETH,REQ-ETH,OST-BTC,STORJ-USDT,SBTC-BTC,DGD-ETH,SC-ETH,WTC-USDT,THETA-BTC,DTA-ETH,BCV-BTC,SNC-BTC,RSR-HT,KAN-BTC,ELA-BTC,ATOM-BTC,BKBT-BTC,FSN-USDT,EM-USDT,WPR-BTC,TOP-BTC,BTS-BTC,EGCC-ETH,MTL-BTC,GNT-ETH,SEELE-BTC,EVX-BTC,FTI-ETH,BAT-USDT,MT-HT,LOL-HT,ICX-ETH,LOOM-BTC,ZJLT-BTC,XLM-USDT,OGO-BTC,DOCK-BTC,CHAT-ETH,DAT-ETH,ETC-USDT,HPT-BTC,BHT-HT" - } - } - }, - "api": { - "authenticatedSupport": false, - "authenticatedWebsocketApiSupport": false, - "endpoints": { - "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" - }, - "credentials": { - "key": "Key", - "secret": "Secret" - }, - "credentialsValidator": { - "requiresKey": true, - "requiresSecret": true - } - }, - "features": { - "supports": { - "restAPI": true, - "restCapabilities": { - "tickerBatching": true, - "autoPairUpdates": true - }, - "websocketAPI": true, - "websocketCapabilities": {} - }, - "enabled": { - "autoPairUpdates": true, - "websocketAPI": false - } - }, - "bankAccounts": [ - { - "enabled": false, - "bankName": "", - "bankAddress": "", - "bankPostalCode": "", - "bankPostalCity": "", - "bankCountry": "", - "accountName": "", - "accountNumber": "", - "swiftCode": "", - "iban": "", - "supportedCurrencies": "" - } - ] + "api": { + "authenticatedSupport": true, + "authenticatedWebsocketApiSupport": true, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true + } }, - { - "name": "Kraken", - "enabled": true, - "verbose": false, - "httpTimeout": 15000000000, - "websocketResponseCheckTimeout": 30000000, - "websocketResponseMaxLimit": 7000000000, - "websocketTrafficTimeout": 30000000000, - "websocketOrderbookBufferLimit": 5, - "baseCurrencies": "EUR,USD,CAD,GBP,JPY", - "currencyPairs": { - "requestFormat": { - "uppercase": true, - "separator": "," - }, - "configFormat": { - "uppercase": true, - "delimiter": "-", - "separator": "," - }, - "useGlobalFormat": true, - "assetTypes": [ - "spot" - ], - "pairs": { - "futures": { - "assetEnabled": true, - "enabled": "PF_XBTUSD", - "available": "PI_XBTUSD,PI_ETHUSD,PI_LTCUSD,PI_BCHUSD,PI_XRPUSD,PF_XBTUSD,PF_ETHUSD,PF_ADAUSD,PF_XRPUSD,PF_SOLUSD,PF_UNIUSD,PF_ATOMUSD,PF_BCHUSD,PF_DOTUSD,PF_EOSUSD,PF_FILUSD,PF_LINKUSD,PF_LTCUSD,PF_MATICUSD,PF_DEFIUSD,PF_AVAXUSD,PF_XMRUSD,PF_GMTUSD,PF_LUNA2USD,PF_OPUSD,PF_NEARUSD,PF_APEUSD,PF_WAVESUSD,PF_DOGEUSD,PF_FTMUSD,PF_TRXUSD,PF_MANAUSD,PF_CRVUSD,PF_AAVEUSD,PF_SNXUSD,PF_XTZUSD,PF_XLMUSD,PF_ALGOUSD,PF_SANDUSD,PF_OMGUSD,PF_ENJUSD,PF_COMPUSD,PF_YFIUSD,PF_CHZUSD,PF_LPTUSD,PF_BATUSD,PF_MKRUSD,PF_AXSUSD,PF_GALAUSD,PF_ETCUSD,PF_KAVAUSD,PF_LRCUSD,PF_KSMUSD,PF_GRTUSD,PF_FLOWUSD,PF_ZECUSD,PF_QTUMUSD,PF_DASHUSD,PF_1INCHUSD,PF_KNCUSD,PF_OGNUSD,PF_SUSHIUSD,PF_STORJUSD,PF_RUNEUSD,PF_EGLDUSD,PF_DYDXUSD,PF_RENUSD,PF_ANKRUSD,PF_ICPUSD,PF_ETHWUSD,PF_OCEANUSD,PF_BANDUSD,PF_BALUSD,PF_ALICEUSD,PF_ICXUSD,PF_ENSUSD,PF_AUDIOUSD,PF_ANTUSD,PF_SCUSD,PF_MINAUSD,PF_GLMRUSD,PF_THETAUSD,PF_QNTUSD,PF_IMXUSD,PF_APTUSD,PF_FLRUSD,PF_BLURUSD,PF_GMXUSD,PF_MASKUSD,PF_LDOUSD,PF_USDCUSD,PF_USDTUSD,PF_ARBUSD,PF_FETUSD,PF_STXUSD,PF_RNDRUSD,PF_CVXUSD,PF_WOOUSD,PF_JASMYUSD,PF_INJUSD,PF_ZRXUSD,PF_RLCUSD,PF_GALUSD,PF_SUIUSD,PF_PEPEUSD,FI_XBTUSD_231229,FI_ETHUSD_231229,PF_SHIBUSD,PF_TUSDUSD,PF_SXPUSD,PF_ARPAUSD,PF_ALPHAUSD,PF_STGUSD,PF_HFTUSD,PF_ACHUSD,PF_WLDUSD,PF_MOONUSD,PF_LINAUSD,PF_CFXUSD,PF_PAXGUSD,PF_AGLDUSD,FF_ETHUSD_231229,FF_XBTUSD_231229,FI_ETHUSD_240329,FI_XBTUSD_240329,FI_XRPUSD_231229,FI_BCHUSD_231229,FI_LTCUSD_231229,PF_FXSUSD,PF_SEIUSD,FF_XBTUSD_231027,FF_ETHUSD_231027,FI_XBTUSD_231027,FI_ETHUSD_231027,FI_XRPUSD_231027,FI_BCHUSD_231027,FI_LTCUSD_231027", - "requestFormat": { - "uppercase": true, - "delimiter": "_" - }, - "configFormat": { - "uppercase": true, - "delimiter": "_" - } - }, - "spot": { - "enabled": "XBT-USD", - "available": "ETH-GBP,XRP-USD,DAI-EUR,LSK-USD,BAT-EUR,BCH-EUR,EOS-ETH,GNO-EUR,ETH-CAD,XRP-JPY,ADA-ETH,DAI-USD,DASH-EUR,GNO-USD,LSK-XBT,ETH-EUR,ZEC-EUR,DASH-XBT,EOS-EUR,ETH-CHF,SC-ETH,SC-USD,WAVES-EUR,XBT-USD,ADA-EUR,LINK-USD,NANO-EUR,PAXG-USD,SC-EUR,WAVES-ETH,REP-USD,EOS-XBT,ETC-ETH,XMR-USD,LTC-USD,MLN-XBT,XTZ-CAD,XBT-GBP,ADA-CAD,XTZ-EUR,ETH-JPY,XTZ-USD,XDG-XBT,XLM-EUR,ATOM-USD,ATOM-XBT,OMG-EUR,ZEC-JPY,ADA-XBT,GNO-ETH,LINK-XBT,ETC-EUR,BCH-XBT,QTUM-ETH,XBT-CHF,LTC-EUR,ETH-DAI,LSK-EUR,NANO-USD,QTUM-XBT,XRP-XBT,ZEC-USD,BAT-ETH,LINK-ETH,XBT-CAD,BAT-USD,GNO-XBT,ICX-XBT,PAXG-ETH,DAI-USDT,NANO-ETH,OMG-ETH,WAVES-XBT,ZEC-XBT,BAT-XBT,NANO-XBT,XBT-JPY,DASH-USD,ICX-ETH,LSK-ETH,QTUM-CAD,REP-XBT,XMR-XBT,XRP-EUR,ATOM-CAD,OMG-USD,LTC-XBT,MLN-ETH,XTZ-ETH,EOS-USD,ICX-EUR,SC-XBT,ETC-USD,BCH-USD,ICX-USD,QTUM-USD,ETH-XBT,ETH-USD,OMG-XBT,PAXG-EUR,REP-EUR,ADA-USD,USDT-USD,XMR-EUR,XRP-CAD,ATOM-EUR,ETC-XBT,XBT-EUR,XLM-USD,ATOM-ETH,LINK-EUR,PAXG-XBT,WAVES-USD,REP-ETH,XLM-XBT,QTUM-EUR,XTZ-XBT" - } - } - }, - "api": { - "authenticatedSupport": false, - "authenticatedWebsocketApiSupport": false, - "endpoints": { - "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" - }, - "credentials": { - "key": "Key", - "secret": "Secret" - }, - "credentialsValidator": { - "requiresKey": true, - "requiresSecret": true, - "requiresBase64DecodeSecret": true - } - }, - "features": { - "supports": { - "restAPI": true, - "restCapabilities": { - "tickerBatching": true, - "autoPairUpdates": true - }, - "websocketAPI": true, - "websocketCapabilities": {} - }, - "enabled": { - "autoPairUpdates": true, - "websocketAPI": true - } - }, - "bankAccounts": [ - { - "enabled": false, - "bankName": "", - "bankAddress": "", - "bankPostalCode": "", - "bankPostalCity": "", - "bankCountry": "", - "accountName": "", - "accountNumber": "", - "swiftCode": "", - "iban": "", - "supportedCurrencies": "" - } - ] + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "tickerBatching": true, + "autoPairUpdates": true + }, + "websocketAPI": true, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": true + } }, - { - "name": "Kucoin", - "enabled": true, - "verbose": false, - "httpTimeout": 15000000000, - "websocketResponseCheckTimeout": 30000000, - "websocketResponseMaxLimit": 7000000000, - "websocketTrafficTimeout": 30000000000, - "websocketOrderbookBufferLimit": 5, - "baseCurrencies": "USD", - "currencyPairs": { - "assetTypes": [ - "spot", - "margin", - "futures" - ], - "pairs": { - "spot": { - "assetEnabled": true, - "enabled": "BTC-USDT,ETH-BTC,ETH-USDT,LTC-USDT", - "available": "BTC-USDT,MHC-ETH,MHC-BTC,OXEN-BTC,OXEN-ETH,NRG-BTC,AVA-USDT,FET-BTC,FET-ETH,ANKR-BTC,MHC-USDT,XMR-BTC,XMR-ETH,RIF-BTC,MTV-BTC,MTV-ETH,CRO-BTC,MTV-USDT,KMD-BTC,KMD-USDT,RFOX-USDT,TEL-USDT,TT-USDT,AERGO-USDT,XMR-USDT,TRX-KCS,ATOM-BTC,ATOM-ETH,ATOM-USDT,ATOM-KCS,ETN-USDT,FTM-USDT,TOMO-USDT,VSYS-USDT,OCEAN-BTC,OCEAN-ETH,CHR-BTC,CHR-USDT,FX-BTC,FX-ETH,NIM-BTC,NIM-ETH,COTI-BTC,COTI-USDT,NRG-ETH,BNB-BTC,BNB-USDT,JAR-BTC,JAR-USDT,ALGO-BTC,ALGO-ETH,ALGO-USDT,XEM-BTC,XEM-USDT,CIX100-USDT,XTZ-BTC,XTZ-USDT,ZEC-BTC,ZEC-USDT,ADA-BTC,ADA-USDT,REV-USDT,WXT-BTC,WXT-USDT,FORESTPLUS-BTC,FORESTPLUS-USDT,BOLT-BTC,BOLT-USDT,ARPA-USDT,CHZ-BTC,CHZ-USDT,DAPPT-BTC,DAPPT-USDT,NOIA-BTC,NOIA-USDT,WIN-BTC,WIN-USDT,DERO-BTC,DERO-USDT,BTT-USDT,EOSC-USDT,ENQ-BTC,ENQ-USDT,ONE-BTC,ONE-USDT,TOKO-BTC,TOKO-USDT,VID-BTC,VID-USDT,LUNA-USDT,SXP-BTC,SXP-USDT,AKRO-BTC,AKRO-USDT,ROOBEE-BTC,WIN-TRX,MAP-BTC,MAP-USDT,AMPL-BTC,AMPL-USDT,DAG-USDT,POL-USDT,ARX-USDT,NWC-BTC,NWC-USDT,BEPRO-BTC,BEPRO-USDT,VRA-BTC,VRA-USDT,KSM-BTC,KSM-USDT,DASH-USDT,SUTER-USDT,ACOIN-USDT,SUTER-BTC,SENSO-USDT,PRE-BTC,XDB-USDT,SYLO-USDT,WOM-USDT,SENSO-BTC,DGB-USDT,LYXE-USDT,LYXE-ETH,XDB-BTC,STX-BTC,STX-USDT,XSR-USDT,COMP-USDT,CRO-USDT,KAI-USDT,KAI-BTC,WEST-BTC,WEST-USDT,EWT-BTC,WAVES-USDT,WAVES-BTC,ORN-USDT,AMPL-ETH,BNS-USDT,MKR-USDT,SUKU-BTC,MLK-BTC,MLK-USDT,JST-USDT,KAI-ETH,SUKU-USDT,DIA-USDT,DIA-BTC,LINK-BTC,LINK-USDT,DOT-USDT,DOT-BTC,SHA-BTC,SHA-USDT,EWT-USDT,USDJ-USDT,EFX-BTC,CKB-BTC,CKB-USDT,UMA-USDT,ALEPH-USDT,VELO-USDT,SUN-USDT,BUY-USDT,YFI-USDT,OXEN-USDT,UNI-USDT,UOS-USDT,UOS-BTC,NIM-USDT,DEGO-USDT,DEGO-ETH,UDOO-ETH,RFUEL-USDT,FIL-USDT,UBX-ETH,REAP-USDT,AAVE-USDT,AAVE-BTC,TONE-BTC,TONE-ETH,ELF-ETH,AERGO-BTC,IOST-ETH,KCS-USDT,SNX-ETH,TOMO-ETH,KCS-ETH,DRGN-BTC,WAN-ETH,NULS-ETH,AXPR-ETH,POWR-BTC,QTUM-BTC,MANA-BTC,TEL-BTC,XYO-ETH,AXPR-BTC,ETN-BTC,COV-ETH,VET-BTC,KCS-BTC,CAPP-ETH,ONT-BTC,DRGN-ETH,DAG-ETH,TOMO-BTC,WAN-BTC,KNC-ETH,CRPT-ETH,LTC-USDT,BAX-ETH,BSV-USDT,DENT-ETH,AION-ETH,LYM-ETH,TRAC-ETH,ENJ-BTC,WAXP-BTC,DGB-BTC,ELA-BTC,ZIL-BTC,BSV-BTC,XLM-USDT,IOTX-ETH,SOUL-BTC,DOCK-BTC,AMB-ETH,TRX-BTC,XRP-TUSD,NULS-BTC,ETH-DAI,LSK-BTC,GMB-ETH,GMB-BTC,NEO-ETH,OMG-ETH,BTC-TUSD,KAT-USDT,KNC-BTC,ELF-BTC,MANA-ETH,ETC-USDT,ONT-ETH,MKR-BTC,KAT-BTC,XRP-USDC,XYO-BTC,SNT-ETH,ZRX-BTC,LOOM-ETH,AION-BTC,POWR-ETH,OLT-ETH,OLT-BTC,SNT-BTC,TRAC-BTC,XLM-ETH,ETH-USDT,BSV-ETH,TRX-ETH,ETN-ETH,AOA-USDT,BCD-BTC,DENT-BTC,DOCK-ETH,KEY-BTC,EOS-KCS,XLM-BTC,ADB-ETH,TIME-ETH,CVC-BTC,LSK-ETH,QKC-BTC,AMB-BTC,USDT-TUSD,ETC-ETH,XRP-BTC,NEO-KCS,SNX-USDT,CRPT-BTC,IOTX-BTC,LTC-ETH,XRP-KCS,ADB-BTC,LTC-KCS,TEL-ETH,DCR-ETH,LYM-USDT,USDT-USDC,ETH-USDC,DAG-BTC,AVA-BTC,BTC-USDT,WAXP-ETH,XRP-USDT,KEY-ETH,VET-ETH,FTM-BTC,USDT-DAI,QKC-ETH,ETH-BTC,MAN-BTC,CPC-ETH,TRX-USDT,BTC-DAI,ONT-USDT,DASH-ETH,BAX-BTC,AVA-ETH,LOOM-BTC,MVP-BTC,MKR-ETH,COV-BTC,CPC-BTC,REQ-ETH,EOS-BTC,LTC-BTC,XRP-ETH,CAPP-BTC,FTM-ETH,BCD-ETH,ZRX-ETH,DGB-ETH,VET-USDT,REQ-BTC,UTK-BTC,PLAY-BTC,UTK-ETH,SNX-BTC,MVP-ETH,NEO-BTC,SOUL-ETH,NEO-USDT,ELA-ETH,OMG-BTC,TIME-BTC,AOA-BTC,ETC-BTC,DCR-BTC,BTC-USDC,ENJ-ETH,IOST-BTC,DASH-BTC,EOS-USDT,EOS-ETH,ZIL-ETH,ETH-TUSD,GAS-BTC,LYM-BTC,BCH-BTC,VSYS-BTC,BCH-USDT,MKR-DAI,SOLVE-BTC,GRIN-BTC,GRIN-USDT,UQC-BTC,UQC-ETH,OPCT-BTC,OPCT-ETH,PRE-USDT,SHR-BTC,SHR-USDT,UBXT-USDT,ROSE-USDT,USDC-USDT,CTI-USDT,CTI-ETH,ETH2-ETH,BUX-BTC,XHV-USDT,PLU-USDT,GRT-USDT,CAS-BTC,CAS-USDT,MSWAP-BTC,MSWAP-USDT,GOM2-BTC,GOM2-USDT,REVV-BTC,REVV-USDT,LON-USDT,1INCH-USDT,LOC-USDT,API3-USDT,UNFI-USDT,HTR-USDT,FRONT-USDT,FRONT-BTC,WBTC-BTC,WBTC-ETH,MIR-USDT,LTC-USDC,BCH-USDC,HYDRA-USDT,DFI-USDT,DFI-BTC,CRV-USDT,SUSHI-USDT,FRM-USDT,EOS-USDC,BSV-USDC,ZEN-USDT,CUDOS-USDT,ADA-USDC,REN-USDT,LRC-USDT,LINK-USDC,KLV-USDT,KLV-BTC,BOA-USDT,THETA-USDT,QNT-USDT,BAT-USDT,DOGE-USDT,DOGE-USDC,DAO-USDT,STRONG-USDT,TRIAS-USDT,TRIAS-BTC,DOGE-BTC,MITX-BTC,MITX-USDT,CAKE-USDT,ORAI-USDT,ZEE-USDT,LTX-USDT,LTX-BTC,MASK-USDT,KLV-TRX,IDEA-USDT,PHA-USDT,PHA-ETH,BCH-KCS,SRK-USDT,SRK-BTC,ADA-KCS,HTR-BTC,BSV-KCS,DOT-KCS,LINK-KCS,MIR-KCS,BNB-KCS,XLM-KCS,VET-KCS,SWINGBY-USDT,SWINGBY-BTC,XHV-BTC,DASH-KCS,UNI-KCS,AAVE-KCS,DOGE-KCS,ZEC-KCS,XTZ-KCS,GRT-KCS,ALGO-KCS,EWT-KCS,GAS-USDT,AVAX-USDT,AVAX-BTC,KRL-BTC,KRL-USDT,POLK-USDT,POLK-BTC,ENJ-USDT,MANA-USDT,RNDR-USDT,RNDR-BTC,RLY-USDT,ANC-USDT,SKEY-USDT,LAYER-USDT,TARA-USDT,TARA-ETH,IOST-USDT,DYP-USDT,DYP-ETH,XYM-USDT,XYM-BTC,PCX-USDT,PCX-BTC,ORBS-USDT,ORBS-BTC,BTC3L-USDT,BTC3S-USDT,ETH3L-USDT,ETH3S-USDT,ANKR-USDT,DSLA-USDT,DSLA-BTC,SAND-USDT,VAI-USDT,XCUR-USDT,XCUR-BTC,FLUX-USDT,OMG-USDT,ZIL-USDT,DODO-USDT,MAN-USDT,BAX-USDT,BOSON-USDT,BOSON-ETH,PUNDIX-USDT,PUNDIX-BTC,WAXP-USDT,HT-USDT,PDEX-USDT,LABS-USDT,LABS-ETH,GMB-USDT,PHNX-USDT,PHNX-BTC,HAI-USDT,EQZ-USDT,FORTH-USDT,HORD-USDT,CGG-USDT,UBX-USDT,GHX-USDT,TCP-USDT,STND-USDT,STND-ETH,TOWER-USDT,TOWER-BTC,ACE-USDT,LOCG-USDT,CARD-USDT,FLY-USDT,CWS-USDT,XDC-USDT,XDC-ETH,STRK-BTC,STRK-ETH,SHIB-USDT,POLX-USDT,KDA-USDT,KDA-BTC,ICP-USDT,ICP-BTC,STC-USDT,STC-BTC,GOVI-USDT,GOVI-BTC,FKX-USDT,CELO-USDT,CELO-BTC,CUSD-USDT,CUSD-BTC,FCL-USDT,MATIC-USDT,MATIC-BTC,ELA-USDT,CRPT-USDT,OPCT-USDT,OGN-USDT,OGN-BTC,OUSD-USDT,OUSD-BTC,TLOS-USDT,TLOS-BTC,YOP-USDT,YOP-ETH,GLQ-USDT,GLQ-BTC,MXC-USDT,ERSDL-USDT,HOTCROSS-USDT,ADA3L-USDT,ADA3S-USDT,HYVE-USDT,HYVE-BTC,DAPPX-USDT,KONO-USDT,PRQ-USDT,MAHA-USDT,MAHA-BTC,FEAR-USDT,PYR-USDT,PYR-BTC,PROM-USDT,PROM-BTC,GLCH-USDT,UNO-USDT,ALBT-USDT,ALBT-ETH,XCAD-USDT,EOS3L-USDT,EOS3S-USDT,BCH3L-USDT,BCH3S-USDT,ELON-USDT,APL-USDT,FCL-ETH,VEED-USDT,VEED-BTC,DIVI-USDT,PDEX-BTC,JUP-USDT,JUP-ETH,POLS-USDT,POLS-BTC,LPOOL-USDT,LPOOL-BTC,LSS-USDT,VET3L-USDT,VET3S-USDT,LTC3L-USDT,LTC3S-USDT,ABBC-USDT,ABBC-BTC,KOK-USDT,ROSN-USDT,DORA-USDT,DORA-BTC,ZCX-USDT,ZCX-BTC,NORD-USDT,GMEE-USDT,SFUND-USDT,XAVA-USDT,AI-USDT,ALPACA-USDT,IOI-USDT,NFT-USDT,NFT-TRX,MNST-USDT,MEM-USDT,AGIX-USDT,AGIX-BTC,AGIX-ETH,CQT-USDT,AIOZ-USDT,MARSH-USDT,HAPI-USDT,MODEFI-USDT,MODEFI-BTC,YFDAI-USDT,YFDAI-BTC,GENS-USDT,FORM-USDT,ARRR-USDT,ARRR-BTC,TOKO-KCS,EXRD-USDT,NGM-USDT,LPT-USDT,STMX-USDT,ASD-USDT,BOND-USDT,HAI-BTC,SOUL-USDT,2CRZ-USDT,NEAR-USDT,NEAR-BTC,DFYN-USDT,OOE-USDT,CFG-USDT,CFG-BTC,AXS-USDT,CLV-USDT,ROUTE-USDT,KAR-USDT,EFX-USDT,XDC-BTC,SHFT-USDT,PMON-USDT,DPET-USDT,ERG-USDT,ERG-BTC,SOL-USDT,SLP-USDT,LITH-USDT,LITH-ETH,XCH-USDT,HAKA-USDT,LAYER-BTC,MTL-USDT,MTL-BTC,IOTX-USDT,GALA-USDT,REQ-USDT,TXA-USDT,TXA-USDC,CIRUS-USDT,QI-USDT,QI-BTC,ODDZ-USDT,PNT-USDT,PNT-BTC,XPR-USDT,XPR-BTC,TRIBE-USDT,SHFT-BTC,MOVR-USDT,MOVR-ETH,WOO-USDT,WILD-USDT,QRDO-USDT,QRDO-ETH,SDN-USDT,SDN-ETH,MAKI-USDT,MAKI-BTC,REP-USDT,REP-BTC,REP-ETH,BNT-USDT,BNT-BTC,BNT-ETH,OXT-USDT,OXT-BTC,OXT-ETH,BAL-USDT,BAL-BTC,BAL-ETH,STORJ-USDT,STORJ-BTC,STORJ-ETH,YGG-USDT,NDAU-USDT,SDAO-USDT,SDAO-ETH,XRP3L-USDT,XRP3S-USDT,SKL-USDT,SKL-BTC,NMR-USDT,NMR-BTC,IXS-USDT,TRB-USDT,TRB-BTC,DYDX-USDT,XYO-USDT,GTC-USDT,GTC-BTC,EQX-USDT,EQX-BTC,RLC-USDT,RLC-BTC,XPRT-USDT,EGLD-USDT,EGLD-BTC,HBAR-USDT,HBAR-BTC,DOGE3L-USDT,DOGE3S-USDT,FLOW-USDT,FLOW-BTC,NKN-USDT,NKN-BTC,PBX-USDT,SOL3L-USDT,SOL3S-USDT,MLN-USDT,MLN-BTC,XNL-USDT,SOLVE-USDT,WNCG-USDT,WNCG-BTC,DMTR-USDT,LINK3L-USDT,LINK3S-USDT,DOT3L-USDT,DOT3S-USDT,CTSI-USDT,CTSI-BTC,ALICE-USDT,ALICE-BTC,ALICE-ETH,OPUL-USDT,ILV-USDT,BAND-USDT,BAND-BTC,FTT-USDT,FTT-BTC,DVPN-USDT,SKU-USDT,SKU-BTC,EDG-USDT,SLIM-USDT,TLM-USDT,TLM-BTC,TLM-ETH,DEXE-USDT,DEXE-BTC,DEXE-ETH,MATTER-USDT,CUDOS-BTC,RUNE-USDT,RUNE-BTC,RMRK-USDT,BMON-USDT,C98-USDT,BLOK-USDT,SOLR-USDT,ATOM3L-USDT,ATOM3S-USDT,UNI3L-USDT,UNI3S-USDT,WSIENNA-USDT,PUSH-USDT,PUSH-BTC,FORM-ETH,NTVRK-USDT,NTVRK-USDC,AXS3L-USDT,AXS3S-USDT,FTM3L-USDT,FTM3S-USDT,FLAME-USDT,AGLD-USDT,NAKA-USDT,YLD-USDT,TONE-USDT,REEF-USDT,REEF-BTC,TIDAL-USDT,TVK-USDT,TVK-BTC,INJ-USDT,INJ-BTC,BNB3L-USDT,BNB3S-USDT,MATIC3L-USDT,MATIC3S-USDT,NFTB-USDT,VEGA-USDT,VEGA-ETH,ALPHA-USDT,ALPHA-BTC,BADGER-USDT,BADGER-BTC,UNO-BTC,ZKT-USDT,AR-USDT,AR-BTC,XVS-USDT,XVS-BTC,JASMY-USDT,PERP-USDT,PERP-BTC,GHST-USDT,GHST-BTC,SCLP-USDT,SCLP-BTC,SUPER-USDT,SUPER-BTC,CPOOL-USDT,HERO-USDT,BASIC-USDT,XED-USDT,XED-BTC,AURY-USDT,SWASH-USDT,LTO-USDT,LTO-BTC,BUX-USDT,MTRG-USDT,DREAMS-USDT,SHIB-DOGE,QUICK-USDT,QUICK-BTC,TRU-USDT,TRU-BTC,WRX-USDT,WRX-BTC,TKO-USDT,TKO-BTC,SUSHI3L-USDT,SUSHI3S-USDT,NEAR3L-USDT,NEAR3S-USDT,DATA-USDT,DATA-BTC,NORD-BTC,ISP-USDT,CERE-USDT,SHILL-USDT,HEGIC-USDT,HEGIC-BTC,ERN-USDT,ERN-BTC,FTG-USDT,PAXG-USDT,PAXG-BTC,AUDIO-USDT,AUDIO-BTC,ENS-USDT,AAVE3L-USDT,AAVE3S-USDT,SAND3L-USDT,SAND3S-USDT,XTM-USDT,MNW-USDT,FXS-USDT,FXS-BTC,ATA-USDT,ATA-BTC,VXV-USDT,LRC-BTC,LRC-ETH,DPR-USDT,CWAR-USDT,CWAR-BTC,FLUX-BTC,EDG-BTC,PBR-USDT,WNXM-USDT,WNXM-BTC,ANT-USDT,ANT-BTC,COV-USDT,SWP-USDT,TWT-USDT,TWT-BTC,OM-USDT,OM-BTC,ADX-USDT,AVAX3L-USDT,AVAX3S-USDT,MANA3L-USDT,MANA3S-USDT,GLM-USDT,GLM-BTC,BAKE-USDT,BAKE-BTC,BAKE-ETH,NUM-USDT,VLX-USDT,VLX-BTC,TRADE-USDT,TRADE-BTC,1EARTH-USDT,MONI-USDT,LIKE-USDT,MFT-USDT,MFT-BTC,LIT-USDT,LIT-BTC,KAVA-USDT,SFP-USDT,SFP-BTC,BURGER-USDT,BURGER-BTC,ILA-USDT,CREAM-USDT,CREAM-BTC,RSR-USDT,RSR-BTC,BUY-BTC,IMX-USDT,GODS-USDT,KMA-USDT,SRM-USDT,SRM-BTC,POLC-USDT,XTAG-USDT,MNET-USDT,NGC-USDT,HARD-USDT,GALAX3L-USDT,GALAX3S-USDT,UNIC-USDT,POND-USDT,POND-BTC,VR-USDT,EPIK-USDT,NGL-USDT,NGL-BTC,KDON-USDT,PEL-USDT,CIRUS-ETH,LINA-USDT,LINA-BTC,KLAY-USDT,KLAY-BTC,CREDI-USDT,TRVL-USDT,LACE-USDT,LACE-ETH,ARKER-USDT,BONDLY-USDT,BONDLY-ETH,XEC-USDT,HEART-USDT,HEART-BTC,UNB-USDT,GAFI-USDT,KOL-USDT,KOL-ETH,H3RO3S-USDT,FALCONS-USDT,UFO-USDT,CHMB-USDT,GEEQ-USDT,ORC-USDT,RACEFI-USDT,PEOPLE-USDT,ADS-USDT,ADS-BTC,OCEAN-USDT,SOS-USDT,WHALE-USDT,TIME-USDT,CWEB-USDT,IOTA-USDT,IOTA-BTC,OOKI-USDT,OOKI-BTC,HNT-USDT,HNT-BTC,GGG-USDT,POWR-USDT,REVU-USDT,CLH-USDT,PLGR-USDT,GLMR-USDT,GLMR-BTC,LOVE-USDT,CTC-USDT,CTC-BTC,GARI-USDT,FRR-USDT,ASTR-USDT,ASTR-BTC,ERTHA-USDT,FCON-USDT,ACA-USDT,ACA-BTC,MTS-USDT,ROAR-USDT,HBB-USDT,SURV-USDT,CVX-USDT,AMP-USDT,ACT-USDT,MJT-USDT,MJT-KCS,SHX-USDT,SHX-BTC,STARLY-USDT,ONSTON-USDT,RANKER-USDT,WMT-USDT,XNO-USDT,XNO-BTC,MARS4-USDT,TFUEL-USDT,TFUEL-BTC,METIS-USDT,LAVAX-USDT,WAL-USDT,BULL-USDT,SON-USDT,MELOS-USDT,APE-USDT,GMT-USDT,BICO-USDT,STG-USDT,LMR-USDT,LMR-BTC,LOKA-USDT,URUS-USDT,JAM-USDT,JAM-ETH,BNC-USDT,LBP-USDT,CFX-USDT,LOOKS-USDT,XCN-USDT,XCN-BTC,KP3R-USDT,TITAN-USDT,INDI-USDT,UPO-USDT,SPELL-USDT,SLCL-USDT,CEEK-USDT,VEMP-USDT,BETA-USDT,NHCT-USDT,ARNM-USDT,FRA-USDT,VISION-USDT,COCOS-USDT,ALPINE-USDT,BNX-USDT,ZBC-USDT,WOOP-USDT,T-USDT,NYM-USDT,VOXEL-USDT,VOXEL-ETH,PSTAKE-USDT,SPA-USDT,SPA-ETH,SYNR-USDT,DAR-USDT,DAR-BTC,MV-USDT,XDEFI-USDT,RACA-USDT,XWG-USDT,HAWK-USDT,TRVL-BTC,SWFTC-USDT,IDEX-USDT,BRWL-USDT,PLATO-USDT,TAUM-USDT,CELR-USDT,AURORA-USDT,POSI-USDT,COOHA-USDT,KNC-USDT,EPK-USDT,PLD-USDT,PSL-USDT,PKF-USDT,OVR-USDT,SYS-USDT,SYS-BTC,BRISE-USDT,DG-USDT,EPX-USDT,GST-USDT,PLY-USDT,GAL-USDT,BSW-USDT,FITFI-USDT,FSN-USDT,H2O-USDT,GMM-USDT,AKT-USDT,SIN-USDT,AUSD-USDT,BOBA-USDT,KARA-USDT,BFC-USDT,BIFI-USDT,DFA-USDT,KYL-USDT,FCD-USDT,MBL-USDT,CELT-USDT,DUSK-USDT,USDD-USDT,USDD-USDC,FITFI-USDC,MBOX-USDT,MBOX-BTC,APE-USDC,AVAX-USDC,SHIB-USDC,XCN-USDC,TRX-USDC,NEAR-USDC,MATIC-USDC,FTM-USDC,ZIL-USDC,SOL-USDC,MLS-USDT,AFK-USDT,AFK-USDC,ACH-USDT,SCRT-USDT,SCRT-BTC,APE3L-USDT,APE3S-USDT,STORE-USDT,STORE-ETH,GMT3L-USDT,GMT3S-USDT,CCD-USDT,DOSE-USDC,LUNC-USDT,LUNC-USDC,USTC-USDT,USTC-USDC,GMT-USDC,VRA-USDC,DOT-USDC,RUNE-USDC,ATOM-USDC,BNB-USDC,JASMY-USDC,KCS-USDC,KDA-USDC,ALGO-USDC,LUNA-USDC,OP-USDT,OP-USDC,JASMY3L-USDT,JASMY3S-USDT,EVER-USDT,MOOV-USDT,IHC-USDT,ICX-USDT,ICX-ETH,BTC-BRL,ETH-BRL,USDT-BRL,WELL-USDT,FORT-USDT,USDP-USDT,USDD-TRX,CSPR-USDT,CSPR-ETH,WEMIX-USDT,REV3L-USDT,OLE-USDT,LDO-USDT,LDO-USDC,CULT-USDT,SWFTC-USDC,FIDA-USDT,BUSD-USDT,RBP-USDT,SRBP-USDT,HIBAYC-USDT,BUSD-USDC,OGV-USDT,WOMBAT-USDT,HIPUNKS-USDT,FT-USDT,ETC-USDC,HIENS4-USDT,EGAME-USDT,EGAME-BTC,STEPWATCH-USDT,HISAND33-USDT,DC-USDT,NEER-USDT,RVN-USDT,HIENS3-USDT,MC-USDT,PEEL-USDT,PEEL-BTC,SDL-USDT,SDL-BTC,SWEAT-USDT,HIODBS-USDT,CMP-USDT,PIX-USDT,MPLX-USDT,HIDOODLES-USDT,ETHW-USDT,QUARTZ-USDT,ACQ-USDT,ACQ-USDC,AOG-USDT,HIMAYC-USDT,PRMX-USDT,RED-USDT,PUMLX-USDT,XETA-USDT,GEM-USDT,DERC-USDT,P00LS-USDT,P00LS-USDC,KICKS-USDT,TRIBL-USDT,GMX-USDT,HIOD-USDT,POKT-USDT,EFI-USDT,APT-USDT,BBC-USDT,EUL-USDT,TON-USDT,PIAS-USDT,HIMEEBITS-USDT,HISQUIGGLE-USDT,XCV-USDT,HFT-USDT,HFT-USDC,ECOX-USDT,AMB-USDT,AZERO-USDT,HIFIDENZA-USDT,BEAT-USDT", - "requestFormat": { - "uppercase": true, - "delimiter": "-" - }, - "configFormat": { - "uppercase": true, - "delimiter": "-" - } - }, - "margin": { - "assetEnabled": true, - "enabled": "ETH-BTC,TRX-BTC,LTC-USDT,SOL-USDC", - "available": "BTC-USDT,MHC-ETH,MHC-BTC,OXEN-BTC,OXEN-ETH,NRG-BTC,AVA-USDT,FET-BTC,FET-ETH,ANKR-BTC,MHC-USDT,XMR-BTC,XMR-ETH,RIF-BTC,MTV-BTC,MTV-ETH,CRO-BTC,MTV-USDT,KMD-BTC,KMD-USDT,RFOX-USDT,TEL-USDT,TT-USDT,AERGO-USDT,XMR-USDT,TRX-KCS,ATOM-BTC,ATOM-ETH,ATOM-USDT,ATOM-KCS,ETN-USDT,FTM-USDT,TOMO-USDT,VSYS-USDT,OCEAN-BTC,OCEAN-ETH,CHR-BTC,CHR-USDT,FX-BTC,FX-ETH,NIM-BTC,NIM-ETH,COTI-BTC,COTI-USDT,NRG-ETH,BNB-BTC,BNB-USDT,JAR-BTC,JAR-USDT,ALGO-BTC,ALGO-ETH,ALGO-USDT,XEM-BTC,XEM-USDT,CIX100-USDT,XTZ-BTC,XTZ-USDT,ZEC-BTC,ZEC-USDT,ADA-BTC,ADA-USDT,REV-USDT,WXT-BTC,WXT-USDT,FORESTPLUS-BTC,FORESTPLUS-USDT,BOLT-BTC,BOLT-USDT,ARPA-USDT,CHZ-BTC,CHZ-USDT,DAPPT-BTC,DAPPT-USDT,NOIA-BTC,NOIA-USDT,WIN-BTC,WIN-USDT,DERO-BTC,DERO-USDT,BTT-USDT,EOSC-USDT,ENQ-BTC,ENQ-USDT,ONE-BTC,ONE-USDT,TOKO-BTC,TOKO-USDT,VID-BTC,VID-USDT,LUNA-USDT,SXP-BTC,SXP-USDT,AKRO-BTC,AKRO-USDT,ROOBEE-BTC,WIN-TRX,MAP-BTC,MAP-USDT,AMPL-BTC,AMPL-USDT,DAG-USDT,POL-USDT,ARX-USDT,NWC-BTC,NWC-USDT,BEPRO-BTC,BEPRO-USDT,VRA-BTC,VRA-USDT,KSM-BTC,KSM-USDT,DASH-USDT,SUTER-USDT,ACOIN-USDT,SUTER-BTC,SENSO-USDT,PRE-BTC,XDB-USDT,SYLO-USDT,WOM-USDT,SENSO-BTC,DGB-USDT,LYXE-USDT,LYXE-ETH,XDB-BTC,STX-BTC,STX-USDT,XSR-USDT,COMP-USDT,CRO-USDT,KAI-USDT,KAI-BTC,WEST-BTC,WEST-USDT,EWT-BTC,WAVES-USDT,WAVES-BTC,ORN-USDT,AMPL-ETH,BNS-USDT,MKR-USDT,SUKU-BTC,MLK-BTC,MLK-USDT,JST-USDT,KAI-ETH,SUKU-USDT,DIA-USDT,DIA-BTC,LINK-BTC,LINK-USDT,DOT-USDT,DOT-BTC,SHA-BTC,SHA-USDT,EWT-USDT,USDJ-USDT,EFX-BTC,CKB-BTC,CKB-USDT,UMA-USDT,ALEPH-USDT,VELO-USDT,SUN-USDT,BUY-USDT,YFI-USDT,OXEN-USDT,UNI-USDT,UOS-USDT,UOS-BTC,NIM-USDT,DEGO-USDT,DEGO-ETH,UDOO-ETH,RFUEL-USDT,FIL-USDT,UBX-ETH,REAP-USDT,AAVE-USDT,AAVE-BTC,TONE-BTC,TONE-ETH,ELF-ETH,AERGO-BTC,IOST-ETH,KCS-USDT,SNX-ETH,TOMO-ETH,KCS-ETH,DRGN-BTC,WAN-ETH,NULS-ETH,AXPR-ETH,POWR-BTC,QTUM-BTC,MANA-BTC,TEL-BTC,XYO-ETH,AXPR-BTC,ETN-BTC,COV-ETH,VET-BTC,KCS-BTC,CAPP-ETH,ONT-BTC,DRGN-ETH,DAG-ETH,TOMO-BTC,WAN-BTC,KNC-ETH,CRPT-ETH,LTC-USDT,BAX-ETH,BSV-USDT,DENT-ETH,AION-ETH,LYM-ETH,TRAC-ETH,ENJ-BTC,WAXP-BTC,DGB-BTC,ELA-BTC,ZIL-BTC,BSV-BTC,XLM-USDT,IOTX-ETH,SOUL-BTC,DOCK-BTC,AMB-ETH,TRX-BTC,XRP-TUSD,NULS-BTC,ETH-DAI,LSK-BTC,GMB-ETH,GMB-BTC,NEO-ETH,OMG-ETH,BTC-TUSD,KAT-USDT,KNC-BTC,ELF-BTC,MANA-ETH,ETC-USDT,ONT-ETH,MKR-BTC,KAT-BTC,XRP-USDC,XYO-BTC,SNT-ETH,ZRX-BTC,LOOM-ETH,AION-BTC,POWR-ETH,OLT-ETH,OLT-BTC,SNT-BTC,TRAC-BTC,XLM-ETH,ETH-USDT,BSV-ETH,TRX-ETH,ETN-ETH,AOA-USDT,BCD-BTC,DENT-BTC,DOCK-ETH,KEY-BTC,EOS-KCS,XLM-BTC,ADB-ETH,TIME-ETH,CVC-BTC,LSK-ETH,QKC-BTC,AMB-BTC,USDT-TUSD,ETC-ETH,XRP-BTC,NEO-KCS,SNX-USDT,CRPT-BTC,IOTX-BTC,LTC-ETH,XRP-KCS,ADB-BTC,LTC-KCS,TEL-ETH,DCR-ETH,LYM-USDT,USDT-USDC,ETH-USDC,DAG-BTC,AVA-BTC,BTC-USDT,WAXP-ETH,XRP-USDT,KEY-ETH,VET-ETH,FTM-BTC,USDT-DAI,QKC-ETH,ETH-BTC,MAN-BTC,CPC-ETH,TRX-USDT,BTC-DAI,ONT-USDT,DASH-ETH,BAX-BTC,AVA-ETH,LOOM-BTC,MVP-BTC,MKR-ETH,COV-BTC,CPC-BTC,REQ-ETH,EOS-BTC,LTC-BTC,XRP-ETH,CAPP-BTC,FTM-ETH,BCD-ETH,ZRX-ETH,DGB-ETH,VET-USDT,REQ-BTC,UTK-BTC,PLAY-BTC,UTK-ETH,SNX-BTC,MVP-ETH,NEO-BTC,SOUL-ETH,NEO-USDT,ELA-ETH,OMG-BTC,TIME-BTC,AOA-BTC,ETC-BTC,DCR-BTC,BTC-USDC,ENJ-ETH,IOST-BTC,DASH-BTC,EOS-USDT,EOS-ETH,ZIL-ETH,ETH-TUSD,GAS-BTC,LYM-BTC,BCH-BTC,VSYS-BTC,BCH-USDT,MKR-DAI,SOLVE-BTC,GRIN-BTC,GRIN-USDT,UQC-BTC,UQC-ETH,OPCT-BTC,OPCT-ETH,PRE-USDT,SHR-BTC,SHR-USDT,UBXT-USDT,ROSE-USDT,USDC-USDT,CTI-USDT,CTI-ETH,ETH2-ETH,BUX-BTC,XHV-USDT,PLU-USDT,GRT-USDT,CAS-BTC,CAS-USDT,MSWAP-BTC,MSWAP-USDT,GOM2-BTC,GOM2-USDT,REVV-BTC,REVV-USDT,LON-USDT,1INCH-USDT,LOC-USDT,API3-USDT,UNFI-USDT,HTR-USDT,FRONT-USDT,FRONT-BTC,WBTC-BTC,WBTC-ETH,MIR-USDT,LTC-USDC,BCH-USDC,HYDRA-USDT,DFI-USDT,DFI-BTC,CRV-USDT,SUSHI-USDT,FRM-USDT,EOS-USDC,BSV-USDC,ZEN-USDT,CUDOS-USDT,ADA-USDC,REN-USDT,LRC-USDT,LINK-USDC,KLV-USDT,KLV-BTC,BOA-USDT,THETA-USDT,QNT-USDT,BAT-USDT,DOGE-USDT,DOGE-USDC,DAO-USDT,STRONG-USDT,TRIAS-USDT,TRIAS-BTC,DOGE-BTC,MITX-BTC,MITX-USDT,CAKE-USDT,ORAI-USDT,ZEE-USDT,LTX-USDT,LTX-BTC,MASK-USDT,KLV-TRX,IDEA-USDT,PHA-USDT,PHA-ETH,BCH-KCS,SRK-USDT,SRK-BTC,ADA-KCS,HTR-BTC,BSV-KCS,DOT-KCS,LINK-KCS,MIR-KCS,BNB-KCS,XLM-KCS,VET-KCS,SWINGBY-USDT,SWINGBY-BTC,XHV-BTC,DASH-KCS,UNI-KCS,AAVE-KCS,DOGE-KCS,ZEC-KCS,XTZ-KCS,GRT-KCS,ALGO-KCS,EWT-KCS,GAS-USDT,AVAX-USDT,AVAX-BTC,KRL-BTC,KRL-USDT,POLK-USDT,POLK-BTC,ENJ-USDT,MANA-USDT,RNDR-USDT,RNDR-BTC,RLY-USDT,ANC-USDT,SKEY-USDT,LAYER-USDT,TARA-USDT,TARA-ETH,IOST-USDT,DYP-USDT,DYP-ETH,XYM-USDT,XYM-BTC,PCX-USDT,PCX-BTC,ORBS-USDT,ORBS-BTC,BTC3L-USDT,BTC3S-USDT,ETH3L-USDT,ETH3S-USDT,ANKR-USDT,DSLA-USDT,DSLA-BTC,SAND-USDT,VAI-USDT,XCUR-USDT,XCUR-BTC,FLUX-USDT,OMG-USDT,ZIL-USDT,DODO-USDT,MAN-USDT,BAX-USDT,BOSON-USDT,BOSON-ETH,PUNDIX-USDT,PUNDIX-BTC,WAXP-USDT,HT-USDT,PDEX-USDT,LABS-USDT,LABS-ETH,GMB-USDT,PHNX-USDT,PHNX-BTC,HAI-USDT,EQZ-USDT,FORTH-USDT,HORD-USDT,CGG-USDT,UBX-USDT,GHX-USDT,TCP-USDT,STND-USDT,STND-ETH,TOWER-USDT,TOWER-BTC,ACE-USDT,LOCG-USDT,CARD-USDT,FLY-USDT,CWS-USDT,XDC-USDT,XDC-ETH,STRK-BTC,STRK-ETH,SHIB-USDT,POLX-USDT,KDA-USDT,KDA-BTC,ICP-USDT,ICP-BTC,STC-USDT,STC-BTC,GOVI-USDT,GOVI-BTC,FKX-USDT,CELO-USDT,CELO-BTC,CUSD-USDT,CUSD-BTC,FCL-USDT,MATIC-USDT,MATIC-BTC,ELA-USDT,CRPT-USDT,OPCT-USDT,OGN-USDT,OGN-BTC,OUSD-USDT,OUSD-BTC,TLOS-USDT,TLOS-BTC,YOP-USDT,YOP-ETH,GLQ-USDT,GLQ-BTC,MXC-USDT,ERSDL-USDT,HOTCROSS-USDT,ADA3L-USDT,ADA3S-USDT,HYVE-USDT,HYVE-BTC,DAPPX-USDT,KONO-USDT,PRQ-USDT,MAHA-USDT,MAHA-BTC,FEAR-USDT,PYR-USDT,PYR-BTC,PROM-USDT,PROM-BTC,GLCH-USDT,UNO-USDT,ALBT-USDT,ALBT-ETH,XCAD-USDT,EOS3L-USDT,EOS3S-USDT,BCH3L-USDT,BCH3S-USDT,ELON-USDT,APL-USDT,FCL-ETH,VEED-USDT,VEED-BTC,DIVI-USDT,PDEX-BTC,JUP-USDT,JUP-ETH,POLS-USDT,POLS-BTC,LPOOL-USDT,LPOOL-BTC,LSS-USDT,VET3L-USDT,VET3S-USDT,LTC3L-USDT,LTC3S-USDT,ABBC-USDT,ABBC-BTC,KOK-USDT,ROSN-USDT,DORA-USDT,DORA-BTC,ZCX-USDT,ZCX-BTC,NORD-USDT,GMEE-USDT,SFUND-USDT,XAVA-USDT,AI-USDT,ALPACA-USDT,IOI-USDT,NFT-USDT,NFT-TRX,MNST-USDT,MEM-USDT,AGIX-USDT,AGIX-BTC,AGIX-ETH,CQT-USDT,AIOZ-USDT,MARSH-USDT,HAPI-USDT,MODEFI-USDT,MODEFI-BTC,YFDAI-USDT,YFDAI-BTC,GENS-USDT,FORM-USDT,ARRR-USDT,ARRR-BTC,TOKO-KCS,EXRD-USDT,NGM-USDT,LPT-USDT,STMX-USDT,ASD-USDT,BOND-USDT,HAI-BTC,SOUL-USDT,2CRZ-USDT,NEAR-USDT,NEAR-BTC,DFYN-USDT,OOE-USDT,CFG-USDT,CFG-BTC,AXS-USDT,CLV-USDT,ROUTE-USDT,KAR-USDT,EFX-USDT,XDC-BTC,SHFT-USDT,PMON-USDT,DPET-USDT,ERG-USDT,ERG-BTC,SOL-USDT,SLP-USDT,LITH-USDT,LITH-ETH,XCH-USDT,HAKA-USDT,LAYER-BTC,MTL-USDT,MTL-BTC,IOTX-USDT,GALA-USDT,REQ-USDT,TXA-USDT,TXA-USDC,CIRUS-USDT,QI-USDT,QI-BTC,ODDZ-USDT,PNT-USDT,PNT-BTC,XPR-USDT,XPR-BTC,TRIBE-USDT,SHFT-BTC,MOVR-USDT,MOVR-ETH,WOO-USDT,WILD-USDT,QRDO-USDT,QRDO-ETH,SDN-USDT,SDN-ETH,MAKI-USDT,MAKI-BTC,REP-USDT,REP-BTC,REP-ETH,BNT-USDT,BNT-BTC,BNT-ETH,OXT-USDT,OXT-BTC,OXT-ETH,BAL-USDT,BAL-BTC,BAL-ETH,STORJ-USDT,STORJ-BTC,STORJ-ETH,YGG-USDT,NDAU-USDT,SDAO-USDT,SDAO-ETH,XRP3L-USDT,XRP3S-USDT,SKL-USDT,SKL-BTC,NMR-USDT,NMR-BTC,IXS-USDT,TRB-USDT,TRB-BTC,DYDX-USDT,XYO-USDT,GTC-USDT,GTC-BTC,EQX-USDT,EQX-BTC,RLC-USDT,RLC-BTC,XPRT-USDT,EGLD-USDT,EGLD-BTC,HBAR-USDT,HBAR-BTC,DOGE3L-USDT,DOGE3S-USDT,FLOW-USDT,FLOW-BTC,NKN-USDT,NKN-BTC,PBX-USDT,SOL3L-USDT,SOL3S-USDT,MLN-USDT,MLN-BTC,XNL-USDT,SOLVE-USDT,WNCG-USDT,WNCG-BTC,DMTR-USDT,LINK3L-USDT,LINK3S-USDT,DOT3L-USDT,DOT3S-USDT,CTSI-USDT,CTSI-BTC,ALICE-USDT,ALICE-BTC,ALICE-ETH,OPUL-USDT,ILV-USDT,BAND-USDT,BAND-BTC,FTT-USDT,FTT-BTC,DVPN-USDT,SKU-USDT,SKU-BTC,EDG-USDT,SLIM-USDT,TLM-USDT,TLM-BTC,TLM-ETH,DEXE-USDT,DEXE-BTC,DEXE-ETH,MATTER-USDT,CUDOS-BTC,RUNE-USDT,RUNE-BTC,RMRK-USDT,BMON-USDT,C98-USDT,BLOK-USDT,SOLR-USDT,ATOM3L-USDT,ATOM3S-USDT,UNI3L-USDT,UNI3S-USDT,WSIENNA-USDT,PUSH-USDT,PUSH-BTC,FORM-ETH,NTVRK-USDT,NTVRK-USDC,AXS3L-USDT,AXS3S-USDT,FTM3L-USDT,FTM3S-USDT,FLAME-USDT,AGLD-USDT,NAKA-USDT,YLD-USDT,TONE-USDT,REEF-USDT,REEF-BTC,TIDAL-USDT,TVK-USDT,TVK-BTC,INJ-USDT,INJ-BTC,BNB3L-USDT,BNB3S-USDT,MATIC3L-USDT,MATIC3S-USDT,NFTB-USDT,VEGA-USDT,VEGA-ETH,ALPHA-USDT,ALPHA-BTC,BADGER-USDT,BADGER-BTC,UNO-BTC,ZKT-USDT,AR-USDT,AR-BTC,XVS-USDT,XVS-BTC,JASMY-USDT,PERP-USDT,PERP-BTC,GHST-USDT,GHST-BTC,SCLP-USDT,SCLP-BTC,SUPER-USDT,SUPER-BTC,CPOOL-USDT,HERO-USDT,BASIC-USDT,XED-USDT,XED-BTC,AURY-USDT,SWASH-USDT,LTO-USDT,LTO-BTC,BUX-USDT,MTRG-USDT,DREAMS-USDT,SHIB-DOGE,QUICK-USDT,QUICK-BTC,TRU-USDT,TRU-BTC,WRX-USDT,WRX-BTC,TKO-USDT,TKO-BTC,SUSHI3L-USDT,SUSHI3S-USDT,NEAR3L-USDT,NEAR3S-USDT,DATA-USDT,DATA-BTC,NORD-BTC,ISP-USDT,CERE-USDT,SHILL-USDT,HEGIC-USDT,HEGIC-BTC,ERN-USDT,ERN-BTC,FTG-USDT,PAXG-USDT,PAXG-BTC,AUDIO-USDT,AUDIO-BTC,ENS-USDT,AAVE3L-USDT,AAVE3S-USDT,SAND3L-USDT,SAND3S-USDT,XTM-USDT,MNW-USDT,FXS-USDT,FXS-BTC,ATA-USDT,ATA-BTC,VXV-USDT,LRC-BTC,LRC-ETH,DPR-USDT,CWAR-USDT,CWAR-BTC,FLUX-BTC,EDG-BTC,PBR-USDT,WNXM-USDT,WNXM-BTC,ANT-USDT,ANT-BTC,COV-USDT,SWP-USDT,TWT-USDT,TWT-BTC,OM-USDT,OM-BTC,ADX-USDT,AVAX3L-USDT,AVAX3S-USDT,MANA3L-USDT,MANA3S-USDT,GLM-USDT,GLM-BTC,BAKE-USDT,BAKE-BTC,BAKE-ETH,NUM-USDT,VLX-USDT,VLX-BTC,TRADE-USDT,TRADE-BTC,1EARTH-USDT,MONI-USDT,LIKE-USDT,MFT-USDT,MFT-BTC,LIT-USDT,LIT-BTC,KAVA-USDT,SFP-USDT,SFP-BTC,BURGER-USDT,BURGER-BTC,ILA-USDT,CREAM-USDT,CREAM-BTC,RSR-USDT,RSR-BTC,BUY-BTC,IMX-USDT,GODS-USDT,KMA-USDT,SRM-USDT,SRM-BTC,POLC-USDT,XTAG-USDT,MNET-USDT,NGC-USDT,HARD-USDT,GALAX3L-USDT,GALAX3S-USDT,UNIC-USDT,POND-USDT,POND-BTC,VR-USDT,EPIK-USDT,NGL-USDT,NGL-BTC,KDON-USDT,PEL-USDT,CIRUS-ETH,LINA-USDT,LINA-BTC,KLAY-USDT,KLAY-BTC,CREDI-USDT,TRVL-USDT,LACE-USDT,LACE-ETH,ARKER-USDT,BONDLY-USDT,BONDLY-ETH,XEC-USDT,HEART-USDT,HEART-BTC,UNB-USDT,GAFI-USDT,KOL-USDT,KOL-ETH,H3RO3S-USDT,FALCONS-USDT,UFO-USDT,CHMB-USDT,GEEQ-USDT,ORC-USDT,RACEFI-USDT,PEOPLE-USDT,ADS-USDT,ADS-BTC,OCEAN-USDT,SOS-USDT,WHALE-USDT,TIME-USDT,CWEB-USDT,IOTA-USDT,IOTA-BTC,OOKI-USDT,OOKI-BTC,HNT-USDT,HNT-BTC,GGG-USDT,POWR-USDT,REVU-USDT,CLH-USDT,PLGR-USDT,GLMR-USDT,GLMR-BTC,LOVE-USDT,CTC-USDT,CTC-BTC,GARI-USDT,FRR-USDT,ASTR-USDT,ASTR-BTC,ERTHA-USDT,FCON-USDT,ACA-USDT,ACA-BTC,MTS-USDT,ROAR-USDT,HBB-USDT,SURV-USDT,CVX-USDT,AMP-USDT,ACT-USDT,MJT-USDT,MJT-KCS,SHX-USDT,SHX-BTC,STARLY-USDT,ONSTON-USDT,RANKER-USDT,WMT-USDT,XNO-USDT,XNO-BTC,MARS4-USDT,TFUEL-USDT,TFUEL-BTC,METIS-USDT,LAVAX-USDT,WAL-USDT,BULL-USDT,SON-USDT,MELOS-USDT,APE-USDT,GMT-USDT,BICO-USDT,STG-USDT,LMR-USDT,LMR-BTC,LOKA-USDT,URUS-USDT,JAM-USDT,JAM-ETH,BNC-USDT,LBP-USDT,CFX-USDT,LOOKS-USDT,XCN-USDT,XCN-BTC,KP3R-USDT,TITAN-USDT,INDI-USDT,UPO-USDT,SPELL-USDT,SLCL-USDT,CEEK-USDT,VEMP-USDT,BETA-USDT,NHCT-USDT,ARNM-USDT,FRA-USDT,VISION-USDT,COCOS-USDT,ALPINE-USDT,BNX-USDT,ZBC-USDT,WOOP-USDT,T-USDT,NYM-USDT,VOXEL-USDT,VOXEL-ETH,PSTAKE-USDT,SPA-USDT,SPA-ETH,SYNR-USDT,DAR-USDT,DAR-BTC,MV-USDT,XDEFI-USDT,RACA-USDT,XWG-USDT,HAWK-USDT,TRVL-BTC,SWFTC-USDT,IDEX-USDT,BRWL-USDT,PLATO-USDT,TAUM-USDT,CELR-USDT,AURORA-USDT,POSI-USDT,COOHA-USDT,KNC-USDT,EPK-USDT,PLD-USDT,PSL-USDT,PKF-USDT,OVR-USDT,SYS-USDT,SYS-BTC,BRISE-USDT,DG-USDT,EPX-USDT,GST-USDT,PLY-USDT,GAL-USDT,BSW-USDT,FITFI-USDT,FSN-USDT,H2O-USDT,GMM-USDT,AKT-USDT,SIN-USDT,AUSD-USDT,BOBA-USDT,KARA-USDT,BFC-USDT,BIFI-USDT,DFA-USDT,KYL-USDT,FCD-USDT,MBL-USDT,CELT-USDT,DUSK-USDT,USDD-USDT,USDD-USDC,FITFI-USDC,MBOX-USDT,MBOX-BTC,APE-USDC,AVAX-USDC,SHIB-USDC,XCN-USDC,TRX-USDC,NEAR-USDC,MATIC-USDC,FTM-USDC,ZIL-USDC,SOL-USDC,MLS-USDT,AFK-USDT,AFK-USDC,ACH-USDT,SCRT-USDT,SCRT-BTC,APE3L-USDT,APE3S-USDT,STORE-USDT,STORE-ETH,GMT3L-USDT,GMT3S-USDT,CCD-USDT,DOSE-USDC,LUNC-USDT,LUNC-USDC,USTC-USDT,USTC-USDC,GMT-USDC,VRA-USDC,DOT-USDC,RUNE-USDC,ATOM-USDC,BNB-USDC,JASMY-USDC,KCS-USDC,KDA-USDC,ALGO-USDC,LUNA-USDC,OP-USDT,OP-USDC,JASMY3L-USDT,JASMY3S-USDT,EVER-USDT,MOOV-USDT,IHC-USDT,ICX-USDT,ICX-ETH,BTC-BRL,ETH-BRL,USDT-BRL,WELL-USDT,FORT-USDT,USDP-USDT,USDD-TRX,CSPR-USDT,CSPR-ETH,WEMIX-USDT,REV3L-USDT,OLE-USDT,LDO-USDT,LDO-USDC,CULT-USDT,SWFTC-USDC,FIDA-USDT,BUSD-USDT,RBP-USDT,SRBP-USDT,HIBAYC-USDT,BUSD-USDC,OGV-USDT,WOMBAT-USDT,HIPUNKS-USDT,FT-USDT,ETC-USDC,HIENS4-USDT,EGAME-USDT,EGAME-BTC,STEPWATCH-USDT,HISAND33-USDT,DC-USDT,NEER-USDT,RVN-USDT,HIENS3-USDT,MC-USDT,PEEL-USDT,PEEL-BTC,SDL-USDT,SDL-BTC,SWEAT-USDT,HIODBS-USDT,CMP-USDT,PIX-USDT,MPLX-USDT,HIDOODLES-USDT,ETHW-USDT,QUARTZ-USDT,ACQ-USDT,ACQ-USDC,AOG-USDT,HIMAYC-USDT,PRMX-USDT,RED-USDT,PUMLX-USDT,XETA-USDT,GEM-USDT,DERC-USDT,P00LS-USDT,P00LS-USDC,KICKS-USDT,TRIBL-USDT,GMX-USDT,HIOD-USDT,POKT-USDT,EFI-USDT,APT-USDT,BBC-USDT,EUL-USDT,TON-USDT,PIAS-USDT,HIMEEBITS-USDT,HISQUIGGLE-USDT,XCV-USDT,HFT-USDT,HFT-USDC,ECOX-USDT,AMB-USDT,AZERO-USDT,HIFIDENZA-USDT,BEAT-USDT", - "requestFormat": { - "uppercase": true, - "delimiter": "-" - }, - "configFormat": { - "uppercase": true, - "delimiter": "-" - } - }, - "futures": { - "assetEnabled": true, - "enabled": "ETH_USDCM,XBT_USDCM,SOL_USDTM", - "available": "XBT_USDTM,XBT_USDM,ETH_USDTM,BCH_USDTM,BSV_USDTM,LINK_USDTM,UNI_USDTM,YFI_USDTM,EOS_USDTM,DOT_USDTM,FIL_USDTM,ADA_USDTM,XRP_USDTM,LTC_USDTM,ETH_USDM,TRX_USDTM,GRT_USDTM,SUSHI_USDTM,XLM_USDTM,1INCH_USDTM,ZEC_USDTM,DASH_USDTM,DOT_USDM,XRP_USDM,AAVE_USDTM,KSM_USDTM,DOGE_USDTM,VET_USDTM,BNB_USDTM,SXP_USDTM,SOL_USDTM,CRV_USDTM,ALGO_USDTM,AVAX_USDTM,FTM_USDTM,MATIC_USDTM,THETA_USDTM,ATOM_USDTM,CHZ_USDTM,ENJ_USDTM,MANA_USDTM,DENT_USDTM,OCEAN_USDTM,BAT_USDTM,XEM_USDTM,QTUM_USDTM,XTZ_USDTM,SNX_USDTM,NEO_USDTM,ONT_USDTM,XMR_USDTM,COMP_USDTM,ETC_USDTM,WAVES_USDTM,BAND_USDTM,MKR_USDTM,RVN_USDTM,DGB_USDTM,SHIB_USDTM,ICP_USDTM,DYDX_USDTM,AXS_USDTM,HBAR_USDTM,EGLD_USDTM,ALICE_USDTM,YGG_USDTM,NEAR_USDTM,SAND_USDTM,C98_USDTM,ONE_USDTM,VRA_USDTM,GALA_USDTM,CHR_USDTM,LRC_USDTM,FLOW_USDTM,RNDR_USDTM,IOTX_USDTM,CRO_USDTM,WAXP_USDTM,PEOPLE_USDTM,OMG_USDTM,LINA_USDTM,IMX_USDTM,CELR_USDTM,ENS_USDTM,CELO_USDTM,CTSI_USDTM,ARPA_USDTM,KNC_USDTM,ROSE_USDTM,AGLD_USDTM,APE_USDTM,JASMY_USDTM,ZIL_USDTM,GMT_USDTM,RUNE_USDTM,LOOKS_USDTM,AUDIO_USDTM,KDA_USDTM,KAVA_USDTM,BAL_USDTM,GAL_USDTM,LUNA_USDTM,LUNC_USDTM,OP_USDTM,XCN_USDTM,UNFI_USDTM,LIT_USDTM,DUSK_USDTM,STORJ_USDTM,RSR_USDTM,OGN_USDTM,TRB_USDTM,PERP_USDTM,KLAY_USDTM,ANKR_USDTM,LDO_USDTM,WOO_USDTM,REN_USDTM,CVC_USDTM,INJ_USDTM,APT_USDTM,MASK_USDTM,REEF_USDTM,TON_USDTM,MAGIC_USDTM,CFX_USDTM,AGIX_USDTM,FXS_USDTM,FET_USDTM,AR_USDTM,GMX_USDTM,BLUR_USDTM,ASTR_USDTM,HIGH_USDTM,ACH_USDTM,STX_USDTM,SSV_USDTM,FLOKI_USDTM,CKB_USDTM,TRU_USDTM,QNT_USDTM,ETH_USDCM,MINA_USDTM,USDC_USDTM,T_USDTM,LQTY_USDTM,ARB_USDTM,DAR_USDTM,ID_USDTM,STG_USDTM,JOE_USDTM,RDNT_USDTM,DODO_USDTM,PAXG_USDTM,ZRX_USDTM,ICX_USDTM,HFT_USDTM,NKN_USDTM,HOOK_USDTM,ANT_USDTM,DC_USDTM,BEL_USDTM,SUI_USDTM,PEPE_USDTM,IDEX_USDTM,GNS_USDTM,CETUS_USDTM,KAS_USDTM,ORDI_USDTM,WOJAK_USDTM,POGAI_USDTM,UMA_USDTM,RAD_USDTM,XBT_USDCM,PHB_USDTM,FTT_USDTM,10000LADYS_USDTM,LEVER_USDTM,TURBO_USDTM,TOMO_USDTM,BOB_USDTM,KEY_USDTM,EDU_USDTM,MTL_USDTM,FLUX_USDTM,COMBO_USDTM,AMB_USDTM,ALPHA_USDTM,SFP_USDTM,MAV_USDTM,MDT_USDTM,XEC_USDTM,XVG_USDTM,1000PEPE2_USDTM,PENDLE_USDTM,STMX_USDTM,WLD_USDTM,LPT_USDTM,GTC_USDTM,BNT_USDTM,OXT_USDTM,BLZ_USDTM,SEI_USDTM,BAKE_USDTM,CYBER_USDTM,NMR_USDTM,FLM_USDTM,SPELL_USDTM,ARK_USDTM,XBT_MU23,XBT_MZ23", - "requestFormat": { - "uppercase": true, - "delimiter": "" - }, - "configFormat": { - "uppercase": true, - "delimiter": "_" - } - } - } - }, - "api": { - "authenticatedSupport": true, - "authenticatedWebsocketApiSupport": true, - "endpoints": { - "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" - }, - "credentials": { - "key": "Key", - "secret": "Secret", - "clientID": "ClientID" - }, - "credentialsValidator": { - "requiresKey": true, - "requiresSecret": true - } - }, - "features": { - "supports": { - "restAPI": true, - "restCapabilities": { - "tickerBatching": true, - "autoPairUpdates": true - }, - "websocketAPI": true, - "websocketCapabilities": {} - }, - "enabled": { - "autoPairUpdates": true, - "websocketAPI": true - } - }, - "bankAccounts": [ - { - "enabled": false, - "bankName": "", - "bankAddress": "", - "bankPostalCode": "", - "bankPostalCity": "", - "bankCountry": "", - "accountName": "", - "accountNumber": "", - "swiftCode": "", - "iban": "", - "supportedCurrencies": "" - } - ] + "bankAccounts": [ + { + "enabled": false, + "bankName": "Deutsche Bank Privat Und Geschaeftskunden AG", + "bankAddress": "Karlsruhe, 76125, GERMANY", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", + "accountName": "GLOBAL TRADE SOLUTIONS GmbH", + "accountNumber": "DE51660700240057016802", + "swiftCode": "DEUTDEDB660", + "iban": "DE51660700240057016802", + "supportedCurrencies": "EUR,USD" + }, + { + "enabled": false, + "bankName": "Deutsche Bank Privat Und Geschaeftskunden AG", + "bankAddress": "Karlsruhe, 76125, GERMANY", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", + "accountName": "GLOBAL TRADE SOLUTIONS GmbH", + "accountNumber": "DE78660700240057016801", + "swiftCode": "DEUTDEDB660", + "iban": "DE78660700240057016801", + "supportedCurrencies": "JPY,GBP" + } + ] + }, + { + "name": "Bitflyer", + "enabled": true, + "verbose": false, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, + "websocketOrderbookBufferLimit": 5, + "baseCurrencies": "JPY", + "currencyPairs": { + "requestFormat": { + "uppercase": true, + "delimiter": "_" + }, + "configFormat": { + "uppercase": true, + "delimiter": "_" + }, + "useGlobalFormat": true, + "lastUpdated": 1566798411, + "assetTypes": [ + "spot", + "futures" + ], + "pairs": { + "spot": { + "enabled": "BTC_JPY,ETH_BTC,BCH_BTC", + "available": "BTC_JPY,FXBTC_JPY,ETH_BTC,BCH_BTC" + } + } }, - { - "name": "LBank", - "enabled": true, - "verbose": false, - "httpTimeout": 15000000000, - "websocketResponseCheckTimeout": 30000000, - "websocketResponseMaxLimit": 7000000000, - "websocketTrafficTimeout": 30000000000, - "websocketOrderbookBufferLimit": 5, - "baseCurrencies": "USD", - "currencyPairs": { - "requestFormat": { - "uppercase": false, - "delimiter": "_" - }, - "configFormat": { - "uppercase": false, - "delimiter": "_" - }, - "useGlobalFormat": true, - "assetTypes": [ - "spot" - ], - "pairs": { - "spot": { - "enabled": "eth_btc", - "available": "FBC_USDT,GALT_USDT,IOEX_USDT,OATH_USDT,BLOC_USDT,BTC_USDT,ETH_USDT,ETH_BTC,ABBC_BTC,KISC_ETH,BXA_USDT,ATP_USDT,MAT_USDT,SKY_BTC,RNT_USDT,VENA_USDT,GRIN_USDT,IDA_USDT,PNT_USDT,OPX_USDT,VTHO_BTC,AMO_ETH,UBEX_BTC,EOS_BTC,UBEX_USDT,TNS_BTC,SAIT_ETH,DAX_BTC,DAX_ETH,DALI_USDT,VET_USDT,BCH_BTC,BCH_USDT,NEO_USDT,QTUM_USDT,ZEC_USDT,VET_BTC,PAI_BTC,PNT_BTC,NEO_BTC,DASH_BTC,LTC_BTC,ETC_BTC,QTUM_BTC,ZEC_BTC,SC_BTC,BTS_BTC,CPX_BTC,XWC_BTC,FIL6_BTC,FIL12_BTC,FIL36_BTC,EOS_USDT,UT_ETH,ELA_ETH,VET_ETH,VTHO_ETH,PAI_ETH,HER_ETH,PTT_ETH,TAC_ETH,IDHUB_ETH,SSC_ETH,SKM_ETH,PLY_ETH,EXT_ETH,EOS_ETH,YOYOW_ETH,TRX_ETH,QTUM_ETH,ZEC_ETH,BTS_ETH,BTM_ETH,MITH_ETH,NAS_ETH,MAN_ETH,DBC_ETH,BTO_ETH,DDD_ETH,CPX_ETH,CS_ETH,IHT_ETH,OCN_ETH,EKO_ETH,XWC_ETH,PUT_ETH,PNT_ETH,AAC_ETH,FIL6_ETH,FIL12_ETH,FIL36_ETH,SEER_ETH,BSB_ETH,CDC_ETH,GRAMS_ETH,DDMX_ETH,EAI_ETH,BNB_USDT,HT_USDT,KBC_BTC,KBC_USDT,MAI_USDT,PHV_USDT,GT_USDT,VOKEN_USDT,CYE_USDT,BRC_USDT,BTC_AUSD,DDMX_USDT,SEAL_USDT,SEOS_BTC,BTY_USDT,FO_USDT,DLX_USDT,BFC_USDT,LBK_USDT,SERO_USDT,MTV_USDT,CKB_USDT,ARPA_USDT,ZIP_USDT,AT_USDT,DOT_USDT,DILI_USDT,DUO_USDT,TEP_USDT,BIKI_USDT,MX_USDT,DNS_USDT,OKB_USDT,FLDT_USDT,CCTC_USDT,WIN_USDT,BTT_USDT,TRX_USDT,GRS_BTC,GST_USDT,GST_ETH,ABBC_USDT,UTK_USDT,GKI_USDT,BPX_USDT,SUTER_USDT,LT_USDT,LM_USDT,HTDF_USDT" - } - } - }, - "api": { - "authenticatedSupport": false, - "authenticatedWebsocketApiSupport": false, - "endpoints": { - "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" - }, - "credentials": { - "key": "Key", - "secret": "Secret" - }, - "credentialsValidator": { - "requiresKey": true, - "requiresSecret": true - } - }, - "features": { - "supports": { - "restAPI": true, - "restCapabilities": { - "tickerBatching": true, - "autoPairUpdates": true - }, - "websocketAPI": false, - "websocketCapabilities": {} - }, - "enabled": { - "autoPairUpdates": true, - "websocketAPI": false - } - }, - "bankAccounts": [ - { - "enabled": false, - "bankName": "", - "bankAddress": "", - "bankPostalCode": "", - "bankPostalCity": "", - "bankCountry": "", - "accountName": "", - "accountNumber": "", - "swiftCode": "", - "iban": "", - "supportedCurrencies": "" - } - ] + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true + } }, - { - "name": "Okcoin", - "enabled": true, - "verbose": false, - "httpTimeout": 15000000000, - "websocketResponseCheckTimeout": 30000000, - "websocketResponseMaxLimit": 7000000000, - "websocketTrafficTimeout": 30000000000, - "websocketOrderbookBufferLimit": 5, - "baseCurrencies": "USD", - "currencyPairs": { - "requestFormat": { - "uppercase": true, - "delimiter": "-" - }, - "configFormat": { - "uppercase": true, - "delimiter": "-" - }, - "useGlobalFormat": true, - "assetTypes": [ - "spot" - ], - "pairs": { - "spot": { - "enabled": "BTC-USD", - "available": "BTC-USD,LTC-USD,ETH-USD,ETC-USD,TUSD-USD,BCH-USD,EOS-USD,XRP-USD,TRX-USD,BSV-USD,USDT-USD,USDK-USD,XLM-USD,ADA-USD,BAT-USD,DCR-USD,EURS-USD,HBAR-USD,PAX-USD,USDC-USD,ZEC-USD,BTC-USDT,BTC-SGD,ETH-SGD,BTC-EUR,BTC-EURS,ETH-EUR,BCH-EUR,EURS-EUR" - }, - "margin": { - "enabled": "BTC-USD", - "available": "BTC-USD" - } - } - }, - "api": { - "authenticatedSupport": false, - "authenticatedWebsocketApiSupport": false, - "endpoints": { - "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" - }, - "credentials": { - "key": "Key", - "secret": "Secret" - }, - "credentialsValidator": { - "requiresKey": true, - "requiresSecret": true, - "requiresClientID": true - } - }, - "features": { - "supports": { - "restAPI": true, - "restCapabilities": { - "tickerBatching": true, - "autoPairUpdates": true - }, - "websocketAPI": true, - "websocketCapabilities": {} - }, - "enabled": { - "autoPairUpdates": true, - "websocketAPI": true - } - }, - "bankAccounts": [ - { - "enabled": false, - "bankName": "", - "bankAddress": "", - "bankPostalCode": "", - "bankPostalCity": "", - "bankCountry": "", - "accountName": "", - "accountNumber": "", - "swiftCode": "", - "iban": "", - "supportedCurrencies": "" - } - ] + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "autoPairUpdates": true + }, + "websocketAPI": false, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": false, + "websocketAPI": false + } }, - { - "name": "Okx", - "enabled": true, - "verbose": false, - "httpTimeout": 15000000000, - "websocketResponseCheckTimeout": 30000000, - "websocketResponseMaxLimit": 7000000000, - "websocketTrafficTimeout": 30000000000, - "baseCurrencies": "USD", - "currencyPairs": { - "bypassConfigFormatUpgrades": false, - "requestFormat": { - "uppercase": true, - "delimiter": "-" - }, - "configFormat": { - "uppercase": true, - "delimiter": "-" - }, - "useGlobalFormat": true, - "assetTypes": [ - "futures", - "margin", - "option", - "perpetualswap", - "spot" - ], - "pairs": { - "futures": { - "assetEnabled": true, - "enabled": "BTC-USD-221007,BTC-USD-221014", - "available": "BTC-USD-221230,BTC-USD-230331,ETH-USD-221007,ETH-USD-221014,ETH-USD-221230,ETH-USD-230331,LTC-USD-221007,LTC-USD-221014,LTC-USD-221230,LTC-USD-230331,DOT-USD-221007,DOT-USD-221014,DOT-USD-221230,DOT-USD-230331,FIL-USD-221007,FIL-USD-221014,FIL-USD-221230,FIL-USD-230331,ADA-USD-221007,ADA-USD-221014,ADA-USD-221230,ADA-USD-230331,AVAX-USD-221007,AVAX-USD-221014,AVAX-USD-221230,BTC-USD-221007,BTC-USD-221014,BTC-USD-221230,BTC-USD-230331,ETH-USD-221007,ETH-USD-221014,ETH-USD-221230,ETH-USD-230331,LTC-USD-221007,LTC-USD-221014,LTC-USD-221230,LTC-USD-230331,DOT-USD-221007,DOT-USD-221014,DOT-USD-221230,DOT-USD-230331,FIL-USD-221007,FIL-USD-221014,FIL-USD-221230,FIL-USD-230331,ADA-USD-221007,ADA-USD-221014,ADA-USD-221230,ADA-USD-230331,AVAX-USD-221007,AVAX-USD-221014,AVAX-USD-221230,AVAX-USD-230331,BCH-USD-221007,BCH-USD-221014,BCH-USD-221230,BCH-USD-230331,EOS-USD-221007,EOS-USD-221014,EOS-USD-221230,EOS-USD-230331,ETC-USD-221007,ETC-USD-221014,ETC-USD-221230,ETC-USD-230331,LINK-USD-221007,LINK-USD-221014,LINK-USD-221230,LINK-USD-230331,SOL-USD-221007,SOL-USD-221014,SOL-USD-221230,SOL-USD-230331,TRX-USD-221007,TRX-USD-221014,TRX-USD-221230,TRX-USD-230331,XRP-USD-221007,XRP-USD-221014,XRP-USD-221230,XRP-USD-230331,BTC-USDT-221007,BTC-USDT-221014,BTC-USDT-221230,BTC-USDT-230331,ETH-USDT-221007,ETH-USDT-221014,ETH-USDT-221230,ETH-USDT-230331,LTC-USDT-221007,LTC-USDT-221014,LTC-USDT-221230,LTC-USDT-230331,DOT-USDT-221007,DOT-USDT-221014,DOT-USDT-221230,DOT-USDT-230331,FIL-USDT-221007,FIL-USDT-221014,FIL-USDT-221230,FIL-USDT-230331,ADA-USDT-221007,ADA-USDT-221014,ADA-USDT-221230,ADA-USDT-230331,BCH-USDT-221007,BCH-USDT-221014,BCH-USDT-221230,BCH-USDT-230331,EOS-USDT-221007,EOS-USDT-221014,EOS-USDT-221230,EOS-USDT-230331,ETC-USDT-221007,ETC-USDT-221014,ETC-USDT-221230,ETC-USDT-230331,LINK-USDT-221007,LINK-USDT-221014,LINK-USDT-221230,LINK-USDT-230331,TRX-USDT-221007,TRX-USDT-221014,TRX-USDT-221230,TRX-USDT-230331,XRP-USDT-221007,XRP-USDT-221014,XRP-USDT-221230,XRP-USDT-230331" - }, - "margin": { - "assetEnabled": true, - "enabled": "BTC-USDT,ETH-USDT,OKB-USDT", - "available": "LTC-USDT,DOT-USDT,DOGE-USDT,LUNC-USDT,ETHW-USDT,LUNA-USDT,FIL-USDT,XRP-USDT,1INCH-USDT,AAVE-USDT,ADA-USDT,AGLD-USDT,AKITA-USDT,ALGO-USDT,ALPHA-USDT,BTC-USDT,ETH-USDT,OKB-USDT,LTC-USDT,DOT-USDT,DOGE-USDT,LUNC-USDT,ETHW-USDT,LUNA-USDT,FIL-USDT,XRP-USDT,1INCH-USDT,AAVE-USDT,ADA-USDT,AGLD-USDT,AKITA-USDT,ALGO-USDT,ALPHA-USDT,ANT-USDT,APE-USDT,API3-USDT,ASTR-USDT,ATOM-USDT,AVAX-USDT,AXS-USDT,BABYDOGE-USDT,BADGER-USDT,BAL-USDT,BAND-USDT,BAT-USDT,BCH-USDT,BICO-USDT,BNT-USDT,BSV-USDT,BTM-USDT,BTT-USDT,BZZ-USDT,CELO-USDT,CEL-USDT,CELR-USDT,CFX-USDT,CHZ-USDT,CLV-USDT,COMP-USDT,CONV-USDT,CQT-USDT,CRO-USDT,CRV-USDT,CSPR-USDT,CVC-USDT,DASH-USDT,DOME-USDT,DORA-USDT,DYDX-USDT,EFI-USDT,EGLD-USDT,ELF-USDT,ENJ-USDT,ENS-USDT,EOS-USDT,ETC-USDT,FITFI-USDT,FLM-USDT,FLOW-USDT,FTM-USDT,GALA-USDT,GLMR-USDT,GMT-USDT,GODS-USDT,GRT-USDT,HBAR-USDT,HC-USDT,ICP-USDT,IMX-USDT,IOST-USDT,IOTA-USDT,JST-USDT,KAR-USDT,KISHU-USDT,KNC-USDT,KSM-USDT,LAMB-USDT,LAT-USDT,LINK-USDT,LON-USDT,LOOKS-USDT,LPT-USDT,LRC-USDT,MANA-USDT,MASK-USDT,MATIC-USDT,MINA-USDT,MKR-USDT,NAS-USDT,NEAR-USDT,NEO-USDT,NFT-USDT,NYM-USDT,OMG-USDT,ONT-USDT,OP-USDT,PEOPLE-USDT,PERP-USDT,QTUM-USDT,REN-USDT,RSR-USDT,RSS3-USDT,RVN-USDT,SAND-USDT,SC-USDT,SHIB-USDT,SKL-USDT,SLP-USDT,SNT-USDT,SNX-USDT,SOL-USDT,SOS-USDT,SRM-USDT,STARL-USDT,STORJ-USDT,SUSHI-USDT,SWEAT-USDT,SWRV-USDT,THETA-USDT,TORN-USDT,TRB-USDT,TRX-USDT,UMA-USDT,UMEE-USDT,UNI-USDT,USDC-USDT,VSYS-USDT,WAVES-USDT,WNCG-USDT,WNXM-USDT,XCH-USDT,XEM-USDT,XLM-USDT,XMR-USDT,XTZ-USDT,YFI-USDT,YFII-USDT,YGG-USDT,ZEC-USDT,ZEN-USDT,ZIL-USDT,ZRX-USDT,BTC-USDC,ETH-BTC,LTC-USDC,DOT-USDC,DOGE-USDC,LUNC-USDC,LUNA-USDC,XRP-USDC,ADA-USDC,ATOM-USDC,AVAX-USDC,NEAR-USDC,OP-USDC,SOL-USDC,OKB-BTC,LTC-BTC,DOT-BTC,DOGE-BTC,FIL-BTC,XRP-BTC,AAVE-BTC,ADA-BTC,ALGO-BTC,ANT-BTC,ATOM-BTC,AVAX-BTC,BADGER-BTC,BAT-BTC,BCH-BTC,BNT-BTC,BSV-BTC,BTM-BTC,CHZ-BTC,COMP-BTC,CRO-BTC,CRV-BTC,CVC-BTC,DASH-BTC,EGLD-BTC,ELF-BTC,ENJ-BTC,EOS-BTC,ETC-BTC,GRT-BTC,HBAR-BTC,HC-BTC,ICP-BTC,IOST-BTC,IOTA-BTC,KNC-BTC,KSM-BTC,LINK-BTC,LRC-BTC,MANA-BTC,MKR-BTC,NAS-BTC,NEAR-BTC,NEO-BTC,OMG-BTC,ONT-BTC,QTUM-BTC,REN-BTC,RSR-BTC,RVN-BTC,SNT-BTC,SOL-BTC,SRM-BTC,THETA-BTC,TRX-BTC,UNI-BTC,VSYS-BTC,WAVES-BTC,XCH-BTC,XEM-BTC,XLM-BTC,XMR-BTC,XTZ-BTC,ZEC-BTC,ZIL-BTC,ZRX-BTC" - }, - "option": { - "assetEnabled": true, - "enabled": "BTC-USD-220930-28000-P,BTC-USD-220930-30000-C", - "available": "BTC-USD-220930-30000-P,BTC-USD-220930-32000-C,BTC-USD-220930-32000-P,BTC-USD-220930-34000-C,BTC-USD-220930-34000-P,BTC-USD-220930-35000-C,BTC-USD-220930-35000-P,BTC-USD-220930-36000-C,BTC-USD-220930-36000-P,BTC-USD-220930-40000-C,BTC-USD-220930-40000-P,BTC-USD-220930-45000-C,BTC-USD-220930-45000-P,BTC-USD-220930-50000-C,BTC-USD-220930-50000-P,BTC-USD-220930-55000-C,BTC-USD-220930-55000-P,BTC-USD-220930-60000-C,BTC-USD-220930-60000-P,BTC-USD-220930-65000-C,BTC-USD-220930-65000-P,BTC-USD-220930-70000-C,BTC-USD-220930-70000-P,BTC-USD-220930-80000-C,BTC-USD-220930-80000-P,BTC-USD-220930-90000-C,BTC-USD-220930-90000-P,BTC-USD-220930-100000-C,BTC-USD-220930-100000-P,BTC-USD-220930-120000-C,BTC-USD-220930-120000-P,BTC-USD-221007-13000-C,BTC-USD-221007-13000-P,BTC-USD-221007-14000-C,BTC-USD-221007-14000-P,BTC-USD-221007-15000-C,BTC-USD-221007-15000-P,BTC-USD-221007-16000-C,BTC-USD-221007-16000-P,BTC-USD-221007-17000-C,BTC-USD-221007-17000-P,BTC-USD-221007-18000-C,BTC-USD-221007-18000-P,BTC-USD-221007-18500-C,BTC-USD-221007-18500-P,BTC-USD-221007-19000-C,BTC-USD-221007-19000-P,BTC-USD-221007-20000-C,BTC-USD-221007-20000-P,BTC-USD-221007-21000-C,BTC-USD-221007-21000-P,BTC-USD-221007-22000-C,BTC-USD-221007-22000-P,BTC-USD-221007-23000-C,BTC-USD-221007-23000-P,BTC-USD-221007-24000-C,BTC-USD-221007-24000-P,BTC-USD-221007-26000-C,BTC-USD-221007-26000-P,BTC-USD-221007-28000-C,BTC-USD-221007-28000-P,BTC-USD-221007-30000-C,BTC-USD-221007-30000-P,BTC-USD-221014-14000-C,BTC-USD-221014-14000-P,BTC-USD-221014-15000-C,BTC-USD-221014-15000-P,BTC-USD-221014-16000-C,BTC-USD-221014-16000-P,BTC-USD-221014-17000-C,BTC-USD-221014-17000-P,BTC-USD-221014-18000-C,BTC-USD-221014-18000-P,BTC-USD-221014-19000-C,BTC-USD-221014-19000-P,BTC-USD-221014-20000-C,BTC-USD-221014-20000-P,BTC-USD-221014-21000-C,BTC-USD-221014-21000-P,BTC-USD-221014-22000-C,BTC-USD-221014-22000-P,BTC-USD-221014-23000-C,BTC-USD-221014-23000-P,BTC-USD-221014-24000-C,BTC-USD-221014-24000-P,BTC-USD-221014-26000-C,BTC-USD-221014-26000-P,BTC-USD-221014-28000-C,BTC-USD-221014-28000-P,BTC-USD-221014-30000-C,BTC-USD-221014-30000-P,BTC-USD-221028-10000-C,BTC-USD-221028-10000-P,BTC-USD-221028-12000-C,BTC-USD-221028-12000-P,BTC-USD-221028-14000-C,BTC-USD-221028-14000-P,BTC-USD-221028-16000-C,BTC-USD-221028-16000-P,BTC-USD-221028-17000-C,BTC-USD-221028-17000-P,BTC-USD-221028-18000-C,BTC-USD-221028-18000-P,BTC-USD-221028-19000-C,BTC-USD-221028-19000-P,BTC-USD-221028-20000-C,BTC-USD-221028-20000-P,BTC-USD-221028-21000-C,BTC-USD-221028-21000-P,BTC-USD-221028-22000-C,BTC-USD-221028-22000-P,BTC-USD-221028-24000-C,BTC-USD-221028-24000-P,BTC-USD-221028-26000-C,BTC-USD-221028-26000-P,BTC-USD-221028-28000-C,BTC-USD-221028-28000-P,BTC-USD-221028-30000-C,BTC-USD-221028-30000-P,BTC-USD-221028-32000-C,BTC-USD-221028-32000-P,BTC-USD-221028-35000-C,BTC-USD-220927-17000-C,BTC-USD-220927-17000-P,BTC-USD-220927-17500-C,BTC-USD-220927-17500-P,BTC-USD-220927-18000-C,BTC-USD-220927-18000-P,BTC-USD-220927-18200-C,BTC-USD-220927-18200-P,BTC-USD-220927-18400-C,BTC-USD-220927-18400-P,BTC-USD-220927-18600-C,BTC-USD-220927-18600-P,BTC-USD-220927-18800-C,BTC-USD-220927-18800-P,BTC-USD-220927-19000-C,BTC-USD-220927-19000-P,BTC-USD-220927-19200-C,BTC-USD-220927-19200-P,BTC-USD-220927-19400-C,BTC-USD-220927-19400-P,BTC-USD-220927-19600-C,BTC-USD-220927-19600-P,BTC-USD-220927-19800-C,BTC-USD-220927-19800-P,BTC-USD-220927-20000-C,BTC-USD-220927-20000-P,BTC-USD-220927-20500-C,BTC-USD-220927-20500-P,BTC-USD-220927-21000-C,BTC-USD-220927-21000-P,BTC-USD-220927-21500-C,BTC-USD-220927-21500-P,BTC-USD-220928-16500-C,BTC-USD-220928-16500-P,BTC-USD-220928-17000-C,BTC-USD-220928-17000-P,BTC-USD-220928-17500-C,BTC-USD-220928-17500-P,BTC-USD-220928-18000-C,BTC-USD-220928-18000-P,BTC-USD-220928-18200-C,BTC-USD-220928-18200-P,BTC-USD-220928-18400-C,BTC-USD-220928-18400-P,BTC-USD-220928-18600-C,BTC-USD-220928-18600-P,BTC-USD-220928-18800-C,BTC-USD-220928-18800-P,BTC-USD-220928-19000-C,BTC-USD-220928-19000-P,BTC-USD-220928-19200-C,BTC-USD-220928-19200-P,BTC-USD-220928-19400-C,BTC-USD-220928-19400-P,BTC-USD-220928-19600-C,BTC-USD-220928-19600-P,BTC-USD-220928-20000-C,BTC-USD-220928-20000-P,BTC-USD-220928-20500-C,BTC-USD-220928-20500-P,BTC-USD-220928-21000-C,BTC-USD-220928-21000-P,BTC-USD-220928-21500-C,BTC-USD-220928-21500-P,BTC-USD-220930-5000-C,BTC-USD-220930-5000-P,BTC-USD-220930-10000-C,BTC-USD-220930-10000-P,BTC-USD-220930-12000-C,BTC-USD-220930-12000-P,BTC-USD-220930-15000-C,BTC-USD-220930-15000-P,BTC-USD-220930-16000-C,BTC-USD-220930-16000-P,BTC-USD-220930-17000-C,BTC-USD-220930-17000-P,BTC-USD-220930-17500-C,BTC-USD-220930-17500-P,BTC-USD-220930-18000-C,BTC-USD-220930-18000-P,BTC-USD-220930-18500-C,BTC-USD-220930-18500-P,BTC-USD-220930-19000-C,BTC-USD-220930-19000-P,BTC-USD-220930-19500-C,BTC-USD-220930-19500-P,BTC-USD-220930-20000-C,BTC-USD-220930-20000-P,BTC-USD-220930-20500-C,BTC-USD-220930-20500-P,BTC-USD-220930-21000-C,BTC-USD-220930-21000-P,BTC-USD-220930-22000-C,BTC-USD-220930-22000-P,BTC-USD-220930-23000-C,BTC-USD-220930-23000-P,BTC-USD-220930-24000-C,BTC-USD-220930-24000-P,BTC-USD-220930-25000-C,BTC-USD-220930-25000-P,BTC-USD-220930-26000-C,BTC-USD-220930-26000-P,BTC-USD-220930-28000-C,BTC-USD-220930-28000-P,BTC-USD-220930-30000-C,BTC-USD-220930-30000-P,BTC-USD-220930-32000-C,BTC-USD-220930-32000-P,BTC-USD-220930-34000-C,BTC-USD-220930-34000-P,BTC-USD-220930-35000-C,BTC-USD-220930-35000-P,BTC-USD-220930-36000-C,BTC-USD-220930-36000-P,BTC-USD-220930-40000-C,BTC-USD-220930-40000-P,BTC-USD-220930-45000-C,BTC-USD-220930-45000-P,BTC-USD-220930-50000-C,BTC-USD-220930-50000-P,BTC-USD-220930-55000-C,BTC-USD-220930-55000-P,BTC-USD-220930-60000-C,BTC-USD-220930-60000-P,BTC-USD-220930-65000-C,BTC-USD-220930-65000-P,BTC-USD-220930-70000-C,BTC-USD-220930-70000-P,BTC-USD-220930-80000-C,BTC-USD-220930-80000-P,BTC-USD-220930-90000-C,BTC-USD-220930-90000-P,BTC-USD-220930-100000-C,BTC-USD-220930-100000-P,BTC-USD-220930-120000-C,BTC-USD-220930-120000-P,BTC-USD-221007-13000-C,BTC-USD-221007-13000-P,BTC-USD-221007-14000-C,BTC-USD-221007-14000-P,BTC-USD-221007-15000-C,BTC-USD-221007-15000-P,BTC-USD-221007-16000-C,BTC-USD-221007-16000-P,BTC-USD-221007-17000-C,BTC-USD-221007-17000-P,BTC-USD-221007-18000-C,BTC-USD-221007-18000-P,BTC-USD-221007-18500-C,BTC-USD-221007-18500-P,BTC-USD-221007-19000-C,BTC-USD-221007-19000-P,BTC-USD-221007-20000-C,BTC-USD-221007-20000-P,BTC-USD-221007-21000-C,BTC-USD-221007-21000-P,BTC-USD-221007-22000-C,BTC-USD-221007-22000-P,BTC-USD-221007-23000-C,BTC-USD-221007-23000-P,BTC-USD-221007-24000-C,BTC-USD-221007-24000-P,BTC-USD-221007-26000-C,BTC-USD-221007-26000-P,BTC-USD-221007-28000-C,BTC-USD-221007-28000-P,BTC-USD-221007-30000-C,BTC-USD-221007-30000-P,BTC-USD-221014-14000-C,BTC-USD-221014-14000-P,BTC-USD-221014-15000-C,BTC-USD-221014-15000-P,BTC-USD-221014-16000-C,BTC-USD-221014-16000-P,BTC-USD-221014-17000-C,BTC-USD-221014-17000-P,BTC-USD-221014-18000-C,BTC-USD-221014-18000-P,BTC-USD-221014-19000-C,BTC-USD-221014-19000-P,BTC-USD-221014-20000-C,BTC-USD-221014-20000-P,BTC-USD-221014-21000-C,BTC-USD-221014-21000-P,BTC-USD-221014-22000-C,BTC-USD-221014-22000-P,BTC-USD-221014-23000-C,BTC-USD-221014-23000-P,BTC-USD-221014-24000-C,BTC-USD-221014-24000-P,BTC-USD-221014-26000-C,BTC-USD-221014-26000-P,BTC-USD-221014-28000-C,BTC-USD-221014-28000-P,BTC-USD-221014-30000-C,BTC-USD-221014-30000-P,BTC-USD-221028-10000-C,BTC-USD-221028-10000-P,BTC-USD-221028-12000-C,BTC-USD-221028-12000-P,BTC-USD-221028-14000-C,BTC-USD-221028-14000-P,BTC-USD-221028-16000-C,BTC-USD-221028-16000-P,BTC-USD-221028-17000-C,BTC-USD-221028-17000-P,BTC-USD-221028-18000-C,BTC-USD-221028-18000-P,BTC-USD-221028-19000-C,BTC-USD-221028-19000-P,BTC-USD-221028-20000-C,BTC-USD-221028-20000-P,BTC-USD-221028-21000-C,BTC-USD-221028-21000-P,BTC-USD-221028-22000-C,BTC-USD-221028-22000-P,BTC-USD-221028-24000-C,BTC-USD-221028-24000-P,BTC-USD-221028-26000-C,BTC-USD-221028-26000-P,BTC-USD-221028-28000-C,BTC-USD-221028-28000-P,BTC-USD-221028-30000-C,BTC-USD-221028-30000-P,BTC-USD-221028-32000-C,BTC-USD-221028-32000-P,BTC-USD-221028-35000-C,BTC-USD-221028-35000-P,BTC-USD-221028-40000-C,BTC-USD-221028-40000-P,BTC-USD-221028-50000-C,BTC-USD-221028-50000-P,BTC-USD-221028-60000-C,BTC-USD-221028-60000-P,BTC-USD-221028-70000-C,BTC-USD-221028-70000-P,BTC-USD-221125-5000-C,BTC-USD-221125-5000-P,BTC-USD-221125-10000-C,BTC-USD-221125-10000-P,BTC-USD-221125-12000-C,BTC-USD-221125-12000-P,BTC-USD-221125-15000-C,BTC-USD-221125-15000-P,BTC-USD-221125-16000-C,BTC-USD-221125-16000-P,BTC-USD-221125-17000-C,BTC-USD-221125-17000-P,BTC-USD-221125-18000-C,BTC-USD-221125-18000-P,BTC-USD-221125-20000-C,BTC-USD-221125-20000-P,BTC-USD-221125-22000-C,BTC-USD-221125-22000-P,BTC-USD-221125-24000-C,BTC-USD-221125-24000-P,BTC-USD-221125-26000-C,BTC-USD-221125-26000-P,BTC-USD-221125-28000-C,BTC-USD-221125-28000-P,BTC-USD-221125-30000-C,BTC-USD-221125-30000-P,BTC-USD-221125-32000-C,BTC-USD-221125-32000-P,BTC-USD-221125-35000-C,BTC-USD-221125-35000-P,BTC-USD-221125-40000-C,BTC-USD-221125-40000-P,BTC-USD-221125-50000-C,BTC-USD-221125-50000-P,BTC-USD-221125-60000-C,BTC-USD-221125-60000-P,BTC-USD-221125-70000-C,BTC-USD-221125-70000-P,BTC-USD-221230-5000-C,BTC-USD-221230-5000-P,BTC-USD-221230-10000-C,BTC-USD-221230-10000-P,BTC-USD-221230-12000-C,BTC-USD-221230-12000-P,BTC-USD-221230-13000-C,BTC-USD-221230-13000-P,BTC-USD-221230-15000-C,BTC-USD-221230-15000-P,BTC-USD-221230-16000-C,BTC-USD-221230-16000-P,BTC-USD-221230-17000-C,BTC-USD-221230-17000-P,BTC-USD-221230-18000-C,BTC-USD-221230-18000-P,BTC-USD-221230-19000-C,BTC-USD-221230-19000-P,BTC-USD-221230-20000-C,BTC-USD-221230-20000-P,BTC-USD-221230-21000-C,BTC-USD-221230-21000-P,BTC-USD-221230-22000-C,BTC-USD-221230-22000-P,BTC-USD-221230-23000-C,BTC-USD-221230-23000-P,BTC-USD-221230-24000-C,BTC-USD-221230-24000-P,BTC-USD-221230-25000-C,BTC-USD-221230-25000-P,BTC-USD-221230-26000-C,BTC-USD-221230-26000-P,BTC-USD-221230-28000-C,BTC-USD-221230-28000-P,BTC-USD-221230-30000-C,BTC-USD-221230-30000-P,BTC-USD-221230-32000-C,BTC-USD-221230-32000-P,BTC-USD-221230-35000-C,BTC-USD-221230-35000-P" - }, - "perpetualswap": { - "assetEnabled": true, - "enabled": "BTC-USD-SWAP,ETH-USD-SWAP", - "available": "LTC-USD-SWAP,DOT-USD-SWAP,DOGE-USD-SWAP,MASK-USDT-SWAP,MATIC-USDT-SWAP,MINA-USDT-SWAP,MKR-USDT-SWAP,NEAR-USDT-SWAP,NEO-USDT-SWAP,NFT-USDT-SWAP,NYM-USDT-SWAP,OMG-USDT-SWAP,ONT-USDT-SWAP,OP-USDT-SWAP,PEOPLE-USDT-SWAP,BTC-USD-SWAP,ETH-USD-SWAP,LTC-USD-SWAP,DOT-USD-SWAP,DOGE-USD-SWAP,FIL-USD-SWAP,XRP-USD-SWAP,1INCH-USD-SWAP,ADA-USD-SWAP,ALGO-USD-SWAP,ATOM-USD-SWAP,AVAX-USD-SWAP,BCH-USD-SWAP,BSV-USD-SWAP,CRV-USD-SWAP,DASH-USD-SWAP,EOS-USD-SWAP,ETC-USD-SWAP,GRT-USD-SWAP,IOST-USD-SWAP,IOTA-USD-SWAP,KNC-USD-SWAP,KSM-USD-SWAP,LINK-USD-SWAP,MANA-USD-SWAP,NEO-USD-SWAP,ONT-USD-SWAP,QTUM-USD-SWAP,SAND-USD-SWAP,SOL-USD-SWAP,SUSHI-USD-SWAP,THETA-USD-SWAP,TRX-USD-SWAP,UNI-USD-SWAP,XLM-USD-SWAP,XMR-USD-SWAP,XTZ-USD-SWAP,YFI-USD-SWAP,YFII-USD-SWAP,ZEC-USD-SWAP,BTC-USDT-SWAP,ETH-USDT-SWAP,LTC-USDT-SWAP,DOT-USDT-SWAP,DOGE-USDT-SWAP,LUNC-USDT-SWAP,ETHW-USDT-SWAP,LUNA-USDT-SWAP,FIL-USDT-SWAP,XRP-USDT-SWAP,1INCH-USDT-SWAP,AAVE-USDT-SWAP,ADA-USDT-SWAP,AGLD-USDT-SWAP,ALGO-USDT-SWAP,ALPHA-USDT-SWAP,ANT-USDT-SWAP,APE-USDT-SWAP,API3-USDT-SWAP,ASTR-USDT-SWAP,ATOM-USDT-SWAP,AVAX-USDT-SWAP,AXS-USDT-SWAP,BABYDOGE-USDT-SWAP,BADGER-USDT-SWAP,BAL-USDT-SWAP,BAND-USDT-SWAP,BAT-USDT-SWAP,BCH-USDT-SWAP,BICO-USDT-SWAP,BNT-USDT-SWAP,BSV-USDT-SWAP,BTT-USDT-SWAP,CELO-USDT-SWAP,CEL-USDT-SWAP,CFX-USDT-SWAP,CHZ-USDT-SWAP,COMP-USDT-SWAP,CRO-USDT-SWAP,CRV-USDT-SWAP,CSPR-USDT-SWAP,CVC-USDT-SWAP,DASH-USDT-SWAP,DOME-USDT-SWAP,DORA-USDT-SWAP,DYDX-USDT-SWAP,EGLD-USDT-SWAP,ELON-USDT-SWAP,ENJ-USDT-SWAP,ENS-USDT-SWAP,EOS-USDT-SWAP,ETC-USDT-SWAP,FITFI-USDT-SWAP,FLM-USDT-SWAP,FTM-USDT-SWAP,GALA-USDT-SWAP,GMT-USDT-SWAP,GODS-USDT-SWAP,GRT-USDT-SWAP,ICP-USDT-SWAP,IMX-USDT-SWAP,IOST-USDT-SWAP,IOTA-USDT-SWAP,JST-USDT-SWAP,KISHU-USDT-SWAP,KNC-USDT-SWAP,KSM-USDT-SWAP,LINK-USDT-SWAP,LOOKS-USDT-SWAP,LPT-USDT-SWAP,LRC-USDT-SWAP,MANA-USDT-SWAP,MASK-USDT-SWAP,MATIC-USDT-SWAP,MINA-USDT-SWAP,MKR-USDT-SWAP,NEAR-USDT-SWAP,NEO-USDT-SWAP,NFT-USDT-SWAP,NYM-USDT-SWAP,OMG-USDT-SWAP,ONT-USDT-SWAP,OP-USDT-SWAP,PEOPLE-USDT-SWAP,PERP-USDT-SWAP,QTUM-USDT-SWAP,REN-USDT-SWAP,RSR-USDT-SWAP,RVN-USDT-SWAP,SAND-USDT-SWAP,SC-USDT-SWAP,SHIB-USDT-SWAP,SLP-USDT-SWAP,SNX-USDT-SWAP,SOL-USDT-SWAP,SOS-USDT-SWAP,SRM-USDT-SWAP,STARL-USDT-SWAP,STORJ-USDT-SWAP,SUSHI-USDT-SWAP,SWEAT-USDT-SWAP,THETA-USDT-SWAP,TRB-USDT-SWAP,TRX-USDT-SWAP,UMA-USDT-SWAP,UMEE-USDT-SWAP,UNI-USDT-SWAP,WAVES-USDT-SWAP,WNXM-USDT-SWAP,XCH-USDT-SWAP,XEM-USDT-SWAP,XLM-USDT-SWAP,XMR-USDT-SWAP,XTZ-USDT-SWAP,YFI-USDT-SWAP,YFII-USDT-SWAP,YGG-USDT-SWAP,ZEC-USDT-SWAP,ZEN-USDT-SWAP,ZIL-USDT-SWAP,ZRX-USDT-SWAP" - }, - "spot": { - "assetEnabled": true, - "enabled": "BTC-USDT,ETH-USDT,OKB-USDT", - "available": "OKT-USDT,LTC-USDT,DOT-USDT,DOGE-USDT,LUNC-USDT,ETHW-USDT,LUNA-USDT,FIL-USDT,XRP-USDT,CITY-USDT,MENGO-USDT,ARG-USDT,POR-USDT,1INCH-USDT,AAVE-USDT,ABT-USDT,ACA-USDT,ADA-USDT,AERGO-USDT,AGLD-USDT,XMR-ETH,YFI-ETH,ZEC-ETH,LTC-OKB,XRP-OKB,ETC-OKB,BTC-USDT,ETH-USDT,OKB-USDT,OKT-USDT,LTC-USDT,DOT-USDT,DOGE-USDT,LUNC-USDT,ETHW-USDT,LUNA-USDT,FIL-USDT,XRP-USDT,CITY-USDT,MENGO-USDT,ARG-USDT,POR-USDT,1INCH-USDT,AAVE-USDT,ABT-USDT,ACA-USDT,ADA-USDT,AERGO-USDT,AGLD-USDT,AKITA-USDT,ALCX-USDT,ALGO-USDT,ALPHA-USDT,ANC-USDT,ANT-USDT,ANW-USDT,APE-USDT,APIX-USDT,API3-USDT,APM-USDT,AR-USDT,ARK-USDT,AST-USDT,ASTR-USDT,ATOM-USDT,AUCTION-USDT,AVAX-USDT,AXS-USDT,AZY-USDT,BABYDOGE-USDT,BADGER-USDT,BAL-USDT,BAND-USDT,BAT-USDT,BCD-USDT,BCH-USDT,BETH-USDT,BHP-USDT,BICO-USDT,BLOK-USDT,BNT-USDT,BORING-USDT,BORA-USDT,BRWL-USDT,BSV-USDT,BTG-USDT,BTM-USDT,BTT-USDT,BZZ-USDT,CELO-USDT,CEL-USDT,CELR-USDT,CELT-USDT,CFG-USDT,CFX-USDT,CGS-USDT,CHAT-USDT,CHE-USDT,CHZ-USDT,CLV-USDT,CMT-USDT,CNTM-USDT,COMP-USDT,CONV-USDT,COVER-USDT,CQT-USDT,CRO-USDT,CRV-USDT,CSPR-USDT,CTC-USDT,CTXC-USDT,CVC-USDT,CVP-USDT,CVT-USDT,CVX-USDT,DAI-USDT,DAO-USDT,DASH-USDT,DCR-USDT,DEP-USDT,DEVT-USDT,DGB-USDT,DHT-USDT,DIA-USDT,DMD-USDT,DNA-USDT,DOME-USDT,DORA-USDT,DOSE-USDT,DYDX-USDT,EC-USDT,EDEN-USDT,EFI-USDT,EGLD-USDT,EGT-USDT,ELF-USDT,ELON-USDT,ELT-USDT,EM-USDT,ENJ-USDT,ENS-USDT,EOS-USDT,ERN-USDT,ETC-USDT,EURT-USDT,FAIR-USDT,FAME-USDT,FITFI-USDT,FLM-USDT,FLOW-USDT,FODL-USDT,FORTH-USDT,FRONT-USDT,FSN-USDT,FTM-USDT,GALA-USDT,GALFT-USDT,GARI-USDT,GAS-USDT,GF-USDT,GHST-USDT,GLM-USDT,GLMR-USDT,GM-USDT,GMT-USDT,GODS-USDT,GOG-USDT,GRT-USDT,GTO-USDT,GUSD-USDT,HBAR-USDT,HC-USDT,HDAO-USDT,HEGIC-USDT,HYC-USDT,ICP-USDT,ICX-USDT,ILV-USDT,IMX-USDT,INT-USDT,INX-USDT,IOST-USDT,IOTA-USDT,IQ-USDT,JFI-USDT,JOE-USDT,JST-USDT,KAN-USDT,KAR-USDT,KCASH-USDT,KDA-USDT,KINE-USDT,KISHU-USDT,KLAY-USDT,KNC-USDT,KOL-USDT,KONO-USDT,KP3R-USDT,KSM-USDT,LAMB-USDT,LAT-USDT,LBA-USDT,LDN-USDT,LDO-USDT,LEASH-USDT,LEO-USDT,LET-USDT,LINK-USDT,LING-USDT,LITH-USDT,LON-USDT,LOON-USDT,LOOKS-USDT,LPT-USDT,LRC-USDT,LSK-USDT,MAGIC-USDT,MANA-USDT,MASK-USDT,MATIC-USDT,MCO-USDT,MDA-USDT,MDT-USDT,MEME-USDT,METIS-USDT,MILO-USDT,MINA-USDT,MIR-USDT,MITH-USDT,MKR-USDT,MLN-USDT,MOF-USDT,MON-USDT,MOVR-USDT,MOVEZ-USDT,MXC-USDT,MXT-USDT,NAS-USDT,NEAR-USDT,NEO-USDT,NFT-USDT,NMR-USDT,NULS-USDT,NYM-USDT,OM-USDT,OMG-USDT,OMI-USDT,ONE-USDT,ONT-USDT,OP-USDT,ORBS-USDT,ORB-USDT,ORS-USDT,OXT-USDT,PAY-USDT,PCI-USDT,PEOPLE-USDT,PERP-USDT,PHA-USDT,PICKLE-USDT,PIT-USDT,PLG-USDT,PNK-USDT,POLS-USDT,POLYDOGE-USDT,PPT-USDT,PRQ-USDT,PST-USDT,PSTAKE-USDT,QOM-USDT,QTUM-USDT,RACA-USDT,RAY-USDT,REN-USDT,REP-USDT,REVV-USDT,RFUEL-USDT,RIO-USDT,RNT-USDT,ROAD-USDT,RON-USDT,RSR-USDT,RSS3-USDT,RVN-USDT,SAITAMA-USDT,SAMO-USDT,SAND-USDT,SC-USDT,SD-USDT,SFG-USDT,SHIB-USDT,SIS-USDT,SKEB-USDT,SKL-USDT,SLP-USDT,SNT-USDT,SNX-USDT,SOC-USDT,SOL-USDT,SOS-USDT,SPELL-USDT,SRM-USDT,STARL-USDT,STC-USDT,STORJ-USDT,STRK-USDT,STX-USDT,SUN-USDT,SUSHI-USDT,SWEAT-USDT,SWFTC-USDT,SWRV-USDT,T-USDT,TAI-USDT,TAKI-USDT,TCT-USDT,THETA-USDT,THG-USDT,TON-USDT,TOPC-USDT,TORN-USDT,TOWN-USDT,TRADE-USDT,TRA-USDT,TRB-USDT,TRUE-USDT,TRX-USDT,TUP-USDT,TUSD-USDT,UMA-USDT,UMEE-USDT,UNI-USDT,USDC-USDT,USDP-USDT,USTC-USDT,UTK-USDT,VALUE-USDT,VELO-USDT,VRA-USDT,VSYS-USDT,WAVES-USDT,WAXP-USDT,WBTC-USDT,WEMIX-USDT,WGRT-USDT,WING-USDT,WIN-USDT,WNCG-USDT,WNXM-USDT,WOO-USDT,WSB-USDT,WXT-USDT,XAUT-USDT,XCH-USDT,XEC-USDT,XEM-USDT,XETA-USDT,XLM-USDT,XMR-USDT,XNO-USDT,XPR-USDT,XTZ-USDT,YEE-USDT,YFI-USDT,YFII-USDT,YGG-USDT,YOU-USDT,YOYO-USDT,ZBC-USDT,ZEC-USDT,ZEN-USDT,ZIL-USDT,ZKS-USDT,ZRX-USDT,ZYRO-USDT,BTC-USDC,ETH-USDC,ETH-BTC,OKB-USDC,OKT-USDC,LTC-USDC,DOT-USDC,DOGE-USDC,LUNC-USDC,ETHW-USDC,LUNA-USDC,FIL-USDC,XRP-USDC,1INCH-USDC,AAVE-USDC,ADA-USDC,AGLD-USDC,ALGO-USDC,ANC-USDC,ANT-USDC,APE-USDC,API3-USDC,AR-USDC,ASTR-USDC,ATOM-USDC,AVAX-USDC,AXS-USDC,AZY-USDC,BABYDOGE-USDC,BAT-USDC,BCH-USDC,BICO-USDC,BSV-USDC,CEL-USDC,CELO-USDC,CELT-USDC,CHZ-USDC,COMP-USDC,CRO-USDC,CRV-USDC,CSPR-USDC,DASH-USDC,DEP-USDC,DOME-USDC,DYDX-USDC,EGLD-USDC,ELT-USDC,ENS-USDC,EOS-USDC,ETC-USDC,FITFI-USDC,FLM-USDC,FLOW-USDC,FTM-USDC,GALA-USDC,GALFT-USDC,GARI-USDC,GLMR-USDC,GMT-USDC,GODS-USDC,GRT-USDC,HBAR-USDC,ICP-USDC,IMX-USDC,IOST-USDC,JST-USDC,KISHU-USDC,KLAY-USDC,KNC-USDC,KSM-USDC,LINK-USDC,LOOKS-USDC,LRC-USDC,MANA-USDC,MASK-USDC,MATIC-USDC,MINA-USDC,MKR-USDC,MOF-USDC,MOVEZ-USDC,MXC-USDC,NEAR-USDC,NFT-USDC,NMR-USDC,NYM-USDC,OMG-USDC,OP-USDC,PEOPLE-USDC,PERP-USDC,RACA-USDC,RSR-USDC,SAITAMA-USDC,SAND-USDC,SHIB-USDC,SLP-USDC,SNX-USDC,SOC-USDC,SOL-USDC,SOS-USDC,SRM-USDC,STARL-USDC,STC-USDC,STORJ-USDC,STX-USDC,SUSHI-USDC,SWFTC-USDC,THETA-USDC,TON-USDC,TORN-USDC,TRB-USDC,TRX-USDC,UNI-USDC,USDP-USDC,USTC-USDC,VRA-USDC,WAVES-USDC,XCH-USDC,XEM-USDC,XLM-USDC,XMR-USDC,XNO-USDC,XTZ-USDC,YFI-USDC,YFII-USDC,YGG-USDC,ZEC-USDC,ZIL-USDC,BTC-DAI,ETH-DAI,BTC-USDK,ETH-USDK,USDT-USDK,OKB-BTC,OKT-BTC,LTC-BTC,DOT-BTC,DOGE-BTC,FIL-BTC,XRP-BTC,AAVE-BTC,ADA-BTC,ALGO-BTC,ALPHA-BTC,ANT-BTC,ATOM-BTC,AVAX-BTC,BADGER-BTC,BAT-BTC,BCD-BTC,BCH-BTC,BNT-BTC,BSV-BTC,BTG-BTC,BTM-BTC,CELO-BTC,CELT-BTC,CHZ-BTC,COMP-BTC,CQT-BTC,CRO-BTC,CRV-BTC,CTC-BTC,CVC-BTC,DASH-BTC,DCR-BTC,DGB-BTC,EGLD-BTC,ELF-BTC,ENJ-BTC,EOS-BTC,ETC-BTC,FLOW-BTC,GAS-BTC,GRT-BTC,GTO-BTC,HBAR-BTC,HC-BTC,ICP-BTC,ICX-BTC,INT-BTC,IOST-BTC,IOTA-BTC,KLAY-BTC,KNC-BTC,KSM-BTC,LINK-BTC,LRC-BTC,LSK-BTC,MANA-BTC,MITH-BTC,MKR-BTC,NAS-BTC,NEAR-BTC,NEO-BTC,NULS-BTC,OMG-BTC,ONT-BTC,PST-BTC,QTUM-BTC,REN-BTC,RSR-BTC,RVN-BTC,SC-BTC,SNT-BTC,SOL-BTC,SRM-BTC,STX-BTC,SWFTC-BTC,THETA-BTC,TRUE-BTC,TRX-BTC,UNI-BTC,VSYS-BTC,WAVES-BTC,WBTC-BTC,WXT-BTC,XCH-BTC,XEM-BTC,XLM-BTC,XMR-BTC,XTZ-BTC,YFI-BTC,YOU-BTC,ZEC-BTC,ZEN-BTC,ZIL-BTC,ZRX-BTC,OKB-ETH,OKT-ETH,LTC-ETH,DOT-ETH,DOGE-ETH,FIL-ETH,XRP-ETH,AAVE-ETH,ADA-ETH,API3-ETH,ATOM-ETH,AVAX-ETH,BETH-ETH,CRV-ETH,DASH-ETH,EOS-ETH,ETC-ETH,FLOW-ETH,GAS-ETH,GHST-ETH,HEGIC-ETH,INT-ETH,IOST-ETH,KSM-ETH,LINK-ETH,MANA-ETH,MKR-ETH,NEAR-ETH,NEO-ETH,NULS-ETH,OM-ETH,QTUM-ETH,SNX-ETH,SOL-ETH,SUSHI-ETH,SWFTC-ETH,TRX-ETH,UNI-ETH,WBTC-ETH,XLM-ETH,XMR-ETH,YFI-ETH,ZEC-ETH,LTC-OKB,XRP-OKB,ETC-OKB,OKDOT1-DOT,OKDOT2-DOT,BTC-EURT,ETH-EURT" - } - } - }, - "api": { - "authenticatedSupport": false, - "authenticatedWebsocketApiSupport": false, - "credentials": { - "key": "", - "secret": "", - "clientID": "" - }, - "credentialsValidator": { - "requiresKey": true, - "requiresSecret": true, - "requiresClientID": true - }, - "urlEndpoints": { - "RestSpotURL": "https://www.okx.com/api/v5/", - "WebsocketSpotURL": "wss://ws.okx.com:8443/ws/v5/public" - } - }, - "features": { - "supports": { - "restAPI": true, - "restCapabilities": { - "autoPairUpdates": true - }, - "websocketAPI": true, - "websocketCapabilities": {} - }, - "enabled": { - "autoPairUpdates": true, - "websocketAPI": true, - "saveTradeData": false, - "tradeFeed": false, - "fillsFeed": false - } - }, - "bankAccounts": [ - { - "enabled": false, - "bankName": "", - "bankAddress": "", - "bankPostalCode": "", - "bankPostalCity": "", - "bankCountry": "", - "accountName": "", - "accountNumber": "", - "swiftCode": "", - "iban": "", - "supportedCurrencies": "" - } - ], - "orderbook": { - "verificationBypass": false, - "websocketBufferLimit": 5, - "websocketBufferEnabled": false, - "publishPeriod": 10000000000 + "bankAccounts": [ + { + "enabled": false, + "bankName": "", + "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ] + }, + { + "name": "Bithumb", + "enabled": true, + "verbose": false, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, + "baseCurrencies": "KRW", + "currencyPairs": { + "requestFormat": { + "uppercase": true, + "delimiter": "_" + }, + "configFormat": { + "uppercase": true, + "delimiter": "-" + }, + "useGlobalFormat": true, + "pairs": { + "spot": { + "assetEnabled": true, + "enabled": "USDT-KRW,QTUM-KRW,BTC-KRW,ETH-KRW,ETC-KRW,XRP-KRW,BCH-KRW,BTG-KRW,EOS-KRW", + "available": "AVAX-KRW,STRAX-KRW,KSM-KRW,RPL-KRW,ADA-KRW,ONT-KRW,EOS-KRW,STAT-KRW,APM-KRW,XPLA-KRW,STMX-KRW,FET-KRW,XVS-KRW,ROA-KRW,JOE-KRW,BNT-KRW,T-KRW,AUDIO-KRW,MIX-KRW,PUNDIX-KRW,USDC-KRW,ALGO-KRW,CTXC-KRW,IQ-KRW,RLY-KRW,GRT-KRW,NMR-KRW,FTM-KRW,WNCG-KRW,NCT-KRW,CSPR-KRW,TFUEL-KRW,EGG-KRW,MOC-KRW,BAT-KRW,ETC-KRW,TIA-KRW,GRACY-KRW,FRONT-KRW,DAI-KRW,ANKR-KRW,META-KRW,HOOK-KRW,BEL-KRW,MAGIC-KRW,ENTC-KRW,HUNT-KRW,STX-KRW,FIT-KRW,STEEM-KRW,CTSI-KRW,JUP-KRW,CAKE-KRW,DOGE-KRW,SUN-KRW,OCEAN-KRW,SOL-KRW,REQ-KRW,BNB-KRW,GAL-KRW,MBL-KRW,LRC-KRW,ILV-KRW,PEPE-KRW,IOST-KRW,XLM-KRW,CRV-KRW,NFT-KRW,PYR-KRW,TRX-KRW,TAVA-KRW,PYTH-KRW,TT-KRW,AAVE-KRW,KLAY-KRW,BAL-KRW,EVZ-KRW,FX-KRW,UMA-KRW,FLOW-KRW,ALEX-KRW,ELF-KRW,CVC-KRW,FLOKI-KRW,MASK-KRW,GAS-KRW,VIX-KRW,CELR-KRW,BLY-KRW,ARK-KRW,FNSA-KRW,OXT-KRW,VALOR-KRW,XTZ-KRW,HBAR-KRW,ONG-KRW,MTL-KRW,WAVES-KRW,ORBS-KRW,MANTA-KRW,ICX-KRW,SNX-KRW,API3-KRW,PENDLE-KRW,FLZ-KRW,APE-KRW,POWR-KRW,OGN-KRW,EDU-KRW,ARB-KRW,AXS-KRW,MBX-KRW,XRP-KRW,MATIC-KRW,USDT-KRW,1INCH-KRW,STORJ-KRW,UOS-KRW,RVN-KRW,LPT-KRW,OSMO-KRW,ALICE-KRW,LDO-KRW,TEMCO-KRW,COMP-KRW,VET-KRW,SFP-KRW,WIKEN-KRW,LBL-KRW,SHIB-KRW,GMT-KRW,AZIT-KRW,ZBCN-KRW,FLUX-KRW,ALT-KRW,AGI-KRW,SPURS-KRW,GRS-KRW,C98-KRW,ZIL-KRW,BCH-KRW,QTCON-KRW,SEI-KRW,GRND-KRW,SWAP-KRW,ETH-KRW,RSS3-KRW,STPT-KRW,FXS-KRW,SAND-KRW,MAP-KRW,MAV-KRW,LINK-KRW,MVC-KRW,QTUM-KRW,DAR-KRW,FANC-KRW,HIGH-KRW,ARKM-KRW,MANA-KRW,SUSHI-KRW,DVI-KRW,XEC-KRW,BTC-KRW,EL-KRW,THETA-KRW,CELO-KRW,KNC-KRW,POLA-KRW,LOOM-KRW,JASMY-KRW,INJ-KRW,KAVA-KRW,NEO-KRW,BIGTIME-KRW,MINA-KRW,NPT-KRW,IMX-KRW,ASM-KRW,FCT2-KRW,RLC-KRW,HIFI-KRW,CTC-KRW,DYDX-KRW,ZTX-KRW,AGIX-KRW,WEMIX-KRW,GTC-KRW,LM-KRW,OP-KRW,ONIT-KRW,ACS-KRW,LSK-KRW,REI-KRW,ATOM-KRW,WLD-KRW,GLM-KRW,COS-KRW,BTT-KRW,BFC-KRW,ACE-KRW,SC-KRW,BORA-KRW,GHX-KRW,ADP-KRW,STRK-KRW,LEVER-KRW,BOBA-KRW,BOA-KRW,HFT-KRW,RNDR-KRW,ENJ-KRW,RSR-KRW,XPR-KRW,IOTX-KRW,CYBER-KRW,WAXP-KRW,OBSR-KRW,MEV-KRW,UNI-KRW,APT-KRW,DAO-KRW,WAXL-KRW,SIX-KRW,GMX-KRW,RDNT-KRW,BTG-KRW,MNT-KRW,BLUR-KRW,XCN-KRW,YGG-KRW,MXC-KRW,ACH-KRW,RAD-KRW,MLK-KRW,DOT-KRW,JST-KRW,ZRX-KRW,STG-KRW,SOFI-KRW,WOM-KRW,TDROP-KRW,SNT-KRW,COTI-KRW,WOO-KRW,OAS-KRW,CRO-KRW,AQT-KRW,EGLD-KRW,ARPA-KRW,BSV-KRW,ASTR-KRW,AMO-KRW,AERGO-KRW,ID-KRW,SUI-KRW,GALA-KRW,CKB-KRW,BIOT-KRW,CFX-KRW,CHR-KRW,FLR-KRW,FITFI-KRW,YFI-KRW,CTK-KRW,W-KRW,MED-KRW,MKR-KRW,SXP-KRW,HIVE-KRW,CRTS-KRW,CHZ-KRW" } + } }, - { - "name": "Poloniex", - "enabled": true, - "verbose": false, - "httpTimeout": 15000000000, - "websocketResponseCheckTimeout": 30000000, - "websocketResponseMaxLimit": 7000000000, - "websocketTrafficTimeout": 30000000000, - "websocketOrderbookBufferLimit": 5, - "baseCurrencies": "USD", - "currencyPairs": { - "requestFormat": { - "uppercase": true, - "delimiter": "_" - }, - "configFormat": { - "uppercase": true, - "delimiter": "_" - }, - "useGlobalFormat": true, - "assetTypes": [ - "spot" - ], - "pairs": { - "spot": { - "enabled": "BTC_LTC,BTC_ETH,BTC_DOGE,BTC_DASH,BTC_XRP", - "available": "USDC_GRIN,BTC_BCN,BTC_DGB,BTC_XMR,USDT_STR,BTC_SC,BTC_ZRX,USDC_XMR,BTC_TRX,BTC_STR,BTC_SNT,USDT_QTUM,USDC_BTC,BTC_NMR,BTC_DASH,BTC_NXT,USDT_LTC,BTC_DCR,USDT_ZRX,USDC_ZEC,USDT_REP,USDT_BAT,BTC_MANA,USDC_BCHABC,USDC_STR,BTC_XRP,USDT_ETH,BTC_REP,USDT_EOS,USDC_ATOM,USDT_XRP,BTC_ETH,USDT_LSK,USDT_SC,USDT_MANA,USDC_ETC,USDC_ETH,BTC_BTS,BTC_LTC,BTC_ETC,BTC_OMG,BTC_STORJ,USDC_XRP,USDT_GRIN,BTC_QTUM,BTC_MAID,BTC_XEM,USDT_BTC,USDT_DASH,ETH_REP,BTC_ZEC,BTC_STRAT,USDC_LTC,BTC_FOAM,USDC_TRX,BTC_DOGE,BTC_VIA,BTC_VTC,ETH_ETC,USDT_ETC,ETH_EOS,USDC_BCHSV,USDT_NXT,USDT_XMR,BTC_ARDR,BTC_CVC,ETH_BAT,USDC_DOGE,BTC_XPM,BTC_LOOM,BTC_LPT,USDC_EOS,USDT_DGB,USDT_BCHSV,BTC_OMNI,ETH_ZEC,BTC_EOS,BTC_KNC,BTC_BCHSV,BTC_POLY,USDC_DASH,USDT_GNT,BTC_BCHABC,BTC_GRIN,BTC_ATOM,USDT_ATOM,USDT_BCHABC,BTC_LSK,ETH_ZRX,BTC_GAS,BTC_BAT,BTC_BNT,USDT_TRX,BTC_FCT,USDT_ZEC,BTC_GNT,USDT_DOGE,USDC_USDT" - } - } - }, - "api": { - "authenticatedSupport": false, - "authenticatedWebsocketApiSupport": false, - "endpoints": { - "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" - }, - "credentials": { - "key": "Key", - "secret": "Secret" - }, - "credentialsValidator": { - "requiresKey": true, - "requiresSecret": true - } - }, - "features": { - "supports": { - "restAPI": true, - "restCapabilities": { - "tickerBatching": true, - "autoPairUpdates": true - }, - "websocketAPI": true, - "websocketCapabilities": {} - }, - "enabled": { - "autoPairUpdates": true, - "websocketAPI": false - } - }, - "bankAccounts": [ - { - "enabled": false, - "bankName": "", - "bankAddress": "", - "bankPostalCode": "", - "bankPostalCity": "", - "bankCountry": "", - "accountName": "", - "accountNumber": "", - "swiftCode": "", - "iban": "", - "supportedCurrencies": "" - } - ] + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret", + "clientID": "ClientID" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true + } }, - { - "name": "Yobit", - "enabled": true, - "verbose": false, - "httpTimeout": 15000000000, - "websocketResponseCheckTimeout": 30000000, - "websocketResponseMaxLimit": 7000000000, - "websocketTrafficTimeout": 30000000000, - "websocketOrderbookBufferLimit": 5, - "baseCurrencies": "USD", - "currencyPairs": { - "requestFormat": { - "uppercase": false, - "delimiter": "_", - "separator": "-" - }, - "configFormat": { - "uppercase": true, - "delimiter": "_" - }, - "useGlobalFormat": true, - "lastUpdated": 1566798411, - "assetTypes": [ - "spot" - ], - "pairs": { - "spot": { - "enabled": "LTC_BTC,ETH_BTC,BTC_USD,DASH_BTC", - "available": "DASH_BTC,WAVES_BTC,LSK_BTC,LIZA_BTC,BCC_BTC,ETH_BTC,LTC_BTC,TRX_BTC,DOGE_BTC,VNTX_BTC,SW_BTC,ZEC_BTC,DASH_ETH,WAVES_ETH,LSK_ETH,LIZA_ETH,BCC_ETH,LTC_ETH,TRX_ETH,DOGE_ETH,VNTX_ETH,SW_ETH,ZEC_ETH,DASH_DOGE,WAVES_DOGE,LSK_DOGE,LIZA_DOGE,BCC_DOGE,LTC_DOGE,TRX_DOGE,VNTX_DOGE,SW_DOGE,ZEC_DOGE,DASH_USD,WAVES_USD,LSK_USD,LIZA_USD,BCC_USD,LTC_USD,TRX_USD,VNTX_USD,SW_USD,ZEC_USD,ETH_USD,BTC_USD,DASH_RUR,WAVES_BTC,WAVES_RUR,LSK_RUR,LIZA_RUR,BCC_RUR,LTC_RUR,TRX_RUR,VNTX_RUR,SW_RUR,ETH_RUR,ZEC_RUR" - } - } - }, - "api": { - "authenticatedSupport": false, - "authenticatedWebsocketApiSupport": false, - "endpoints": { - "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" - }, - "credentials": { - "key": "Key", - "secret": "Secret" - }, - "credentialsValidator": { - "requiresKey": true, - "requiresSecret": true - } - }, - "features": { - "supports": { - "restAPI": true, - "restCapabilities": { - "tickerBatching": true, - "autoPairUpdates": true - }, - "websocketAPI": false, - "websocketCapabilities": {} - }, - "enabled": { - "autoPairUpdates": false, - "websocketAPI": false - } - }, - "bankAccounts": [ - { - "enabled": false, - "bankName": "", - "bankAddress": "", - "bankPostalCode": "", - "bankPostalCity": "", - "bankCountry": "", - "accountName": "", - "accountNumber": "", - "swiftCode": "", - "iban": "", - "supportedCurrencies": "" - } - ] + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "tickerBatching": true, + "autoPairUpdates": true + }, + "websocketAPI": false, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": false + } + }, + "bankAccounts": [ + { + "enabled": false, + "bankName": "", + "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ], + "orderbook": { + "verificationBypass": false, + "websocketBufferLimit": 5, + "websocketBufferEnabled": false, + "publishPeriod": 10000000000 } - ], - "bankAccounts": [ - { + }, + { + "name": "Bitmex", + "enabled": true, + "verbose": false, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, + "websocketOrderbookBufferLimit": 5, + "baseCurrencies": "USD", + "currencyPairs": { + "assetTypes": [ + "perpetualcontract", + "futures", + "downsideprofitcontract", + "upsideprofitcontract" + ], + "pairs": { + "downsideprofitcontract": { + "enabled": "XBT7D_D95", + "available": "XBT7D_D95", + "requestFormat": { + "uppercase": true, + "delimiter": "_" + }, + "configFormat": { + "uppercase": true, + "delimiter": "_" + } + }, + "futures": { + "enabled": "BCHZ19", + "available": "XRPZ19,BCHZ19,ADAZ19,EOSZ19,TRXZ19,XBTZ19,ETHZ19,LTCZ19", + "requestFormat": { + "uppercase": true + }, + "configFormat": { + "uppercase": true + } + }, + "perpetualcontract": { + "enabled": "ETHUSD", + "available": "XBTUSD,ETHUSD", + "requestFormat": { + "uppercase": true + }, + "configFormat": { + "uppercase": true + } + }, + "upsideprofitcontract": { + "enabled": "XBT7D_U105", + "available": "XBT7D_U105", + "requestFormat": { + "uppercase": true, + "delimiter": "_" + }, + "configFormat": { + "uppercase": true, + "delimiter": "_" + } + } + } + }, + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "tickerBatching": true, + "autoPairUpdates": true + }, + "websocketAPI": true, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": false + } + }, + "bankAccounts": [ + { + "enabled": false, + "bankName": "", + "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ] + }, + { + "name": "Bitstamp", + "enabled": true, + "verbose": false, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, + "websocketOrderbookBufferLimit": 5, + "baseCurrencies": "USD,EUR", + "currencyPairs": { + "requestFormat": { + "uppercase": true + }, + "configFormat": { + "uppercase": true + }, + "useGlobalFormat": true, + "assetTypes": [ + "spot" + ], + "pairs": { + "spot": { + "enabled": "BTCUSD,BTCEUR,EURUSD,XRPUSD,XRPEUR", + "available": "LTCUSD,ETHUSD,XRPEUR,BCHUSD,BCHEUR,BTCEUR,XRPBTC,EURUSD,BCHBTC,LTCEUR,BTCUSD,LTCBTC,XRPUSD,ETHBTC,ETHEUR" + } + } + }, + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret", + "clientID": "ClientID" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true, + "requiresClientID": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "autoPairUpdates": true + }, + "websocketAPI": true, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": false + } + }, + "bankAccounts": [ + { + "enabled": false, + "bankName": "", + "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ] + }, + { + "name": "Bybit", + "enabled": true, + "verbose": false, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, + "websocketOrderbookBufferLimit": 5, + "baseCurrencies": "USD", + "currencyPairs": { + "assetTypes": [ + "spot", + "margin", + "coinmarginedfutures", + "usdtmarginedfutures", + "usdcmarginedfutures", + "options" + ], + "pairs": { + "spot": { + "enabled": "BTC_USDT,ETH_USDT,XRP_USDT,EOS_USDT,ETH_BTC,XRP_BTC,DOT_USDT,XLM_USDT,LTC_USDT", + "available": "BTC_USDT,ETH_USDT,XRP_USDT,EOS_USDT,ETH_BTC,XRP_BTC,DOT_USDT,XLM_USDT,LTC_USDT,DOGE_USDT,CHZ_USDT,AXS_USDT,MANA_USDT,DYDX_USDT,MKR_USDT,COMP_USDT,AAVE_USDT,YFI_USDT,LINK_USDT,SUSHI_USDT,UNI_USDT,KSM_USDT,ICP_USDT,ADA_USDT,ETC_USDT,KLAY_USDT,XTZ_USDT,BCH_USDT,SRM_USDT,QNT_USDT,USDC_USDT,GRT_USDT,SOL_USDT,FIL_USDT,OMG_USDT,TRIBE_USDT,BAT_USDT,ZRX_USDT,CRV_USDT,AGLD_USDT,ANKR_USDT,PERP_USDT,MATIC_USDT,WAVES_USDT,LUNC_USDT,SPELL_USDT,SHIB_USDT,FTM_USDT,ATOM_USDT,ALGO_USDT,ENJ_USDT,CBX_USDT,SAND_USDT,AVAX_USDT,WOO_USDT,FTT_USDT,GODS_USDT,IMX_USDT,ENS_USDT,GM_USDT,CWAR_USDT,CAKE_USDT,STETH_USDT,GALFT_USDT,LFW_USDT,SLP_USDT,C98_USDT,PSP_USDT,GENE_USDT,AVA_USDT,ONE_USDT,PTU_USDT,SHILL_USDT,XYM_USDT,BOBA_USDT,JASMY_USDT,GALA_USDT,RNDR_USDT,TRVL_USDT,WEMIX_USDT,XEM_USDT,BICO_USDT,CEL_USDT,UMA_USDT,HOT_USDT,NEXO_USDT,BNT_USDT,SNX_USDT,REN_USDT,1INCH_USDT,TEL_USDT,SIS_USDT,LRC_USDT,LDO_USDT,REAL_USDT,KRL_USDT,DEVT_USDT,ETH_USDC,BTC_USDC,1SOL_USDT,PLT_USDT,IZI_USDT,QTUM_USDT,DCR_USDT,ZEN_USDT,THETA_USDT,MX_USDT,DGB_USDT,RVN_USDT,EGLD_USDT,RUNE_USDT,XLM_BTC,XLM_USDC,SOL_USDC,XRP_USDC,ALGO_BTC,SOL_BTC,RAIN_USDT,XEC_USDT,ICX_USDT,XDC_USDT,HNT_USDT,BTG_USDT,ZIL_USDT,HBAR_USDT,FLOW_USDT,SOS_USDT,KASTA_USDT,STX_USDT,SIDUS_USDT,VPAD_USDT,GGM_USDT,LOOKS_USDT,MBS_USDT,DAI_USDT,BUSD_USDT,ACA_USDT,MV_USDT,MIX_USDT,LTC_USDC,MANA_BTC,MATIC_BTC,LTC_BTC,DOT_BTC,SAND_BTC,MANA_USDC,MATIC_USDC,SAND_USDC,DOT_USDC,LUNC_USDC,RSS3_USDT,SYNR_USDT,TAP_USDT,ERTHA_USDT,GMX_USDT,T_USDT,ACH_USDT,JST_USDT,SUN_USDT,BTT_USDT,TRX_USDT,NFT_USDT,POKT_USDT,SCRT_USDT,PSTAKE_USDT,SON_USDT,HERO_USDT,DOME_USDT,USTC_USDT,BNB_USDT,NEAR_USDT,PAXG_USDT,SD_USDT,APE_USDT,BTC3S_USDT,BTC3L_USDT,FIDA_USDT,MINA_USDT,SC_USDT,RACA_USDT,CAPS_USDT,STG_USDT,GLMR_USDT,MOVR_USDT,ZAM_USDT,ETH_DAI,BTC_DAI,WBTC_USDT,XAVA_USDT,MELOS_USDT,GMT_USDT,GST_USDT,CELO_USDT,SFUND_USDT,ELT_USDT,LGX_USDT,APEX_USDT,CTC_USDT,COT_USDT,KMON_USDT,PLY_USDT,XWG_USDT,FITFI_USDT,STRM_USDT,GAL_USDT,ETH3S_USDT,ETH3L_USDT,KOK_USDT,FAME_USDT,XRP3S_USDT,XRP3L_USDT,USDD_USDT,OP_USDT,LUNA_USDT,DFI_USDT,MOVEZ_USDT,THN_USDT,DOT3S_USDT,DOT3L_USDT,VINU_USDT,BEL_USDT,FORT_USDT,AVAX2S_USDT,AVAX2L_USDT,ADA2S_USDT,ADA2L_USDT,WLKN_USDT,KON_USDT,LTC2S_USDT,LTC2L_USDT,SAND2S_USDT,SAND2L_USDT,OBX_USDT,SEOR_USDT,MNZ_USDT,CULT_USDT,DOGE_USDC,EOS_USDC,CUSD_USDT,SLG_USDT,CMP_USDT,KUNCI_USDT,GSTS_USDT,XETA_USDT,AZY_USDT,MMC_USDT,FLOKI_USDT,BABYDOGE_USDT,STAT_USDT,SAITAMA_USDT,MATIC2S_USDT,MATIC2L_USDT,ETC2S_USDT,ETC2L_USDT,DICE_USDT,WAXP_USDT,AR_USDT,KDA_USDT,ROSE_USDT,SLG_USDC,APE2S_USDT,APE2L_USDT,GMT2S_USDT,GMT2L_USDT,DEFY_USDT,PSG_USDT,BAR_USDT,JUV_USDT,ACM_USDT,INTER_USDT,AFC_USDT,CITY_USDT,LINK2L_USDT,LINK2S_USDT,FTM2L_USDT,FTM2S_USDT,SOLO_USDT,W_BTC,AVAX_USDC,ADA_USDC,OP_USDC,DOGE2S_USDT,DOGE2L_USDT,ATOM2S_USDT,ATOM2L_USDT,APEX_USDC,TRX_USDC,ICP_USDC,LINK_USDC,GMT_USDC,CHZ_USDC,SHIB_USDC,LDO_USDC,APE_USDC,FIL_USDC,CHRP_USDT,EOS2S_USDT,EOS2L_USDT,WWY_USDT,LING_USDT,SWEAT_USDT,DLC_USDT,OKG_USDT,ETHW_USDT,INJ_USDT,MPLX_USDT,MIBR_USDT,CO_USDT,AGLA_USDT,ROND_USDT,QMALL_USDT,PUMLX_USDT,GCAKE_USDT,APT_USDT,APT_USDC,USDT_EUR,MTK_USDT,MCRT_USDT,MASK_USDT,ECOX_USDT,HFT_USDC,HFT_USDT,KCAL_USDT,PEOPLE_USDT,TWT_USDT,ORT_USDT,HOOK_USDT,PRIMAL_USDT,MCT_USDT,OAS_USDT,MAGIC_USDT,MEE_USDT,TON_USDT,BONK_USDT,FLR_USDT,TIME_USDT,3P_USDT,RPL_USDT,SSV_USDT,FXS_USDT,CORE_USDT,RDNT_USDT,BLUR_USDT,LIS_USDT,AGIX_USDT,MDAO_USDT,ACS_USDT,HVH_USDT,GNS_USDT,DPX_USDT,PIP_USDT,PRIME_USDT,EVER_USDT,VRA_USDT,GPT_USDT,FB_USDT,DZOO_USDT,ID_USDT,ARB_USDC,ARB_USDT,XCAD_USDT,MBX_USDT,AXL_USDT,CGPT_USDT,PLAY_USDT,AGI_USDT,RLTM_USDT,SUI_USDT,SUI_USDC,TAMA_USDT,MVL_USDT,PEPE_USDT,LADYS_USDT,LMWR_USDT,BOB_USDT,TOMI_USDT,KARATE_USDT,SUIA_USDT,TURBOS_USDT,FMB_USDT,CAPO_USDT,TENET_USDT,VELO_USDT,ELDA_USDT,CANDY_USDT,FON_USDT,OMN_USDT,TOMS_USDT,MTC_USDT,VELA_USDT,USDT_BRZ,BTC_BRZ,PENDLE_USDT,EGO_USDT,PEPE2_USDT,NYM_USDT,MNT_USDT,MNT_USDC,MNT_BTC,GSWIFT_USDT,SALD_USDT,ARKM_USDT,NEON_USDT,WLD_USDC,WLD_USDT,PLANET_USDT,DSRUN_USDT,SPARTA_USDT,TAVA_USDT,SEILOR_USDT,SEI_USDT,CYBER_USDT,ORDI_USDT,KAVA_USDT,VV_USDT,SAIL_USDT,PYUSD_USDT,SOL_EUR,USDC_EUR,ADA_EUR,DOGE_EUR,LTC_EUR,XRP_EUR,ETH_EUR,BTC_EUR,VEXT_USDT,CTT_USDT,NEXT_USDT,KAS_USDT,NESS_USDT,CAT_USDT,FET_USDT,LEVER_USDT,VEGA_USDT,ZTX_USDT", + "requestFormat": { + "uppercase": true + }, + "configFormat": { + "uppercase": true, + "delimiter": "_" + } + }, + "coinmarginedfutures": { + "enabled": "ADA_USD,BTC_USD,BTC_USDH24,BTC_USDZ23,DOT_USD", + "available": "ADA_USD,BTC_USD,BTC_USDH24,BTC_USDZ23,DOT_USD,EOS_USD,ETH_USD,ETH_USDH24,ETH_USDZ23,LTC_USD,MAN_AUSD,XRP_USD", + "requestFormat": { + "uppercase": true + }, + "configFormat": { + "uppercase": true, + "delimiter": "_" + } + }, + "usdcmarginedfutures": { + "enabled": "ETH-PERP,BNB-PERP,SOL-PERP,BTC-PERP", + "available": "BNB-PERP,BTC-03NOV23,BTC-20OCT23,BTC-24NOV23,BTC-27OCT23,BTC-28JUN24,BTC-29DEC23,BTC-29MAR24,BTC-PERP,ETC-PERP,ETH-03NOV23,ETH-20OCT23,ETH-24NOV23,ETH-27OCT23,ETH-28JUN24,ETH-29DEC23,ETH-29MAR24,ETH-PERP,MAT-ICPERP,OPP-ERP,SOL-PERP,XRP-PERP", + "requestFormat": { + "uppercase": true + }, + "configFormat": { + "uppercase": true, + "delimiter": "-" + } + }, + "usdtmarginedfutures": { + "enabled": "BTC_USDT,10000LADYS_USDT,IOTA_USDT,AAVE_USDT", + "available": "10000LADYS_USDT,10000NFT_USDT,1000BONK_USDT,1000BTT_USDT,1000FLOKI_USDT,1000LUNC_USDT,1000PEPE_USDT,1000XEC_USDT,1INCH_USDT,AAVE_USDT,ACH_USDT,ADA_USDT,AGIX_USDT,AGLD_USDT,AKRO_USDT,ALGO_USDT,ALICE_USDT,ALPACA_USDT,ALPHA_USDT,AMB_USDT,ANKR_USDT,ANT_USDT,APE_USDT,API3_USDT,APT_USDT,ARB_USDT,ARKM_USDT,ARK_USDT,ARPA_USDT,AR_USDT,ASTR_USDT,ATA_USDT,ATOM_USDT,AUCTION_USDT,AUDIO_USDT,AVAX_USDT,AXS_USDT,BADGER_USDT,BAKE_USDT,BAL_USDT,BAND_USDT,BAT_USDT,BCH_USDT,BEL_USDT,BICO_USDT,BIGTIME_USDT,BLUR_USDT,BLZ_USDT,BNB_USDT,BNT_USDT,BNX_USDT,BOBA_USDT,BOND_USDT,BSV_USDT,BSW_USDT,BTC_USDT,BUSD_USDT,C98_USDT,CEEK_USDT,CELO_USDT,CELR_USDT,CFX_USDT,CHR_USDT,CHZ_USDT,CKB_USDT,COMBO_USDT,COMP_USDT,CORE_USDT,COTI_USDT,CRO_USDT,CRV_USDT,CTC_USDT,CTK_USDT,CTSI_USDT,CVC_USDT,CVX_USDT,CYBER_USDT,DAR_USDT,DASH_USDT,DENT_USDT,DGB_USDT,DODO_USDT,DOGE_USDT,DOT_USDT,DUSK_USDT,DYDX_USDT,EDU_USDT,EGLD_USDT,ENJ_USDT,ENS_USDT,EOS_USDT,ETC_USDT,ETH_USDT,ETHW_USDT,FET_USDT,FIL_USDT,FITFI_USDT,FLM_USDT,FLOW_USDT,FLR_USDT,FORTH_USDT,FRONT_USDT,FTM_USDT,FXS_USDT,GALA_USDT,GAL_USDT,GFT_USDT,GLMR_USDT,GLM_USDT,GMT_USDT,GMX_USDT,GPT_USDT,GRT_USDT,GTC_USDT,HBAR_USDT,HFT_USDT,HIFI_USDT,HIGH_USDT,HNT_USDT,HOOK_USDT,HOT_USDT,ICP_USDT,ICX_USDT,IDEX_USDT,ID_USDT,ILV_USDT,IMX_USDT,INJ_USDT,IOST_USDT,IOTA_USDT,IOTX_USDT,JASMY_USDT,JOE_USDT,JST_USDT,KAS_USDT,KAVA_USDT,KDA_USDT,KEY_USDT,KLAY_USDT,KNC_USDT,KSM_USDT,LDO_USDT,LEVER_USDT,LINA_USDT,LINK_USDT,LIT_USDT,LOOKS_USDT,LOOM_USDT,LPT_USDT,LQTY_USDT,LRC_USDT,LTC_USDT,LUNA2_USDT,MAGIC_USDT,MANA_USDT,MASK_USDT,MATIC_USDT,MAV_USDT,MC_USDT,MDT_USDT,MINA_USDT,MKR_USDT,MNT_USDT,MTL_USDT,MULTI_USDT,NEAR_USDT,NEO_USDT,NKN_USDT,NMR_USDT,NTRN_USDT,OCEAN_USDT,OGN_USDT,OG_USDT,OMG_USDT,ONE_USDT,ONT_USDT,OP_USDT,ORBS_USDT,ORDI_USDT,OXT_USDT,PAXG_USDT,PENDLE_USDT,PEOPLE_USDT,PERP_USDT,PHB_USDT,PROM_USDT,QNT_USDT,QTUM_USDT,RAD_USDT,RDNT_USDT,REEF_USDT,REN_USDT,REQ_USDT,RLC_USDT,RNDR_USDT,ROSE_USDT,RPL_USDT,RSR_USDT,RSS3_USDT,RUNE_USDT,RVN_USDT,SAND_USDT,SCRT_USDT,SC_USDT,SEI_USDT,SFP_USDT,SHIB1000_USDT,SKL_USDT,SLP_USDT,SNX_USDT,SOL_USDT,SPELL_USDT,SSV_USDT,STG_USDT,STMX_USDT,STORJ_USDT,STPT_USDT,STRAX_USDT,STX_USDT,SUI_USDT,SUN_USDT,SUSHI_USDT,SWEAT_USDT,SXP_USDT,THETA_USDT,TLM_USDT,TOMI_USDT,TOMO_USDT,TON_USDT,TRB_USDT,TRU_USDT,TRX_USDT,T_USDT,TWT_USDT,UMA_USDT,UNFI_USDT,UNI_USDT,USDC_USDT,VET_USDT,VGX_USDT,VRA_USDT,WAVES_USDT,WAXP_USDT,WLD_USDT,WOO_USDT,WSM_USDT,XCN_USDT,XEM_USDT,XLM_USDT,XMR_USDT,XNO_USDT,XRP_USDT,XTZ_USDT,XVG_USDT,XVS_USDT,YFII_USDT,YFI_USDT,YGG_USDT,ZEC_USDT,ZEN_USDT,ZIL_USDT,ZRX_USDT", + "requestFormat": { + "uppercase": true + }, + "configFormat": { + "uppercase": true, + "delimiter": "_" + } + }, + "options": { + "enabled": "BTC-28JUN24-70000-C,BTC-28JUN24-70000-P,BTC-28JUN24-60000-C,BTC-28JUN24-60000-P", + "available": "BTC-28JUN24-70000-C,BTC-28JUN24-70000-P,BTC-28JUN24-60000-C,BTC-28JUN24-60000-P,BTC-28JUN24-50000-C,BTC-28JUN24-50000-P,BTC-28JUN24-40000-C,BTC-28JUN24-40000-P,BTC-28JUN24-32000-C,BTC-28JUN24-32000-P,BTC-28JUN24-30000-C,BTC-28JUN24-30000-P,BTC-28JUN24-28000-C,BTC-28JUN24-28000-P,BTC-28JUN24-25000-C,BTC-28JUN24-25000-P,BTC-28JUN24-20000-C,BTC-28JUN24-20000-P,BTC-28JUN24-10000-C,BTC-28JUN24-10000-P,BTC-29MAR24-70000-C,BTC-29MAR24-70000-P,BTC-29MAR24-60000-C,BTC-29MAR24-60000-P,BTC-29MAR24-50000-C,BTC-29MAR24-50000-P,BTC-29MAR24-45000-C,BTC-29MAR24-45000-P,BTC-29MAR24-40000-C,BTC-29MAR24-40000-P,BTC-29MAR24-36000-C,BTC-29MAR24-36000-P,BTC-29MAR24-35000-C,BTC-29MAR24-35000-P,BTC-29MAR24-33000-C,BTC-29MAR24-33000-P,BTC-29MAR24-31000-C,BTC-29MAR24-31000-P,BTC-29MAR24-30000-C,BTC-29MAR24-30000-P,BTC-29MAR24-28000-C,BTC-29MAR24-28000-P,BTC-29MAR24-27000-C,BTC-29MAR24-27000-P,BTC-29MAR24-26000-C,BTC-29MAR24-26000-P,BTC-29MAR24-24000-C,BTC-29MAR24-24000-P,BTC-29MAR24-20000-C,BTC-29MAR24-20000-P,BTC-29MAR24-10000-C,BTC-29MAR24-10000-P,BTC-29DEC23-80000-C,BTC-29DEC23-80000-P,BTC-29DEC23-70000-C,BTC-29DEC23-70000-P,BTC-29DEC23-60000-C,BTC-29DEC23-60000-P,BTC-29DEC23-50000-C,BTC-29DEC23-50000-P,BTC-29DEC23-40000-C,BTC-29DEC23-40000-P,BTC-29DEC23-36000-C,BTC-29DEC23-36000-P,BTC-29DEC23-35000-C,BTC-29DEC23-35000-P,BTC-29DEC23-34000-C,BTC-29DEC23-34000-P,BTC-29DEC23-32000-C,BTC-29DEC23-32000-P,BTC-29DEC23-31500-C,BTC-29DEC23-31500-P,BTC-29DEC23-30500-C,BTC-29DEC23-30500-P,BTC-29DEC23-30000-C,BTC-29DEC23-30000-P,BTC-29DEC23-29500-C,BTC-29DEC23-29500-P,BTC-29DEC23-29000-C,BTC-29DEC23-29000-P,BTC-29DEC23-28000-C,BTC-29DEC23-28000-P,BTC-29DEC23-27500-C,BTC-29DEC23-27500-P,BTC-29DEC23-27000-C,BTC-29DEC23-27000-P,BTC-29DEC23-26000-C,BTC-29DEC23-26000-P,BTC-29DEC23-25000-C,BTC-29DEC23-25000-P,BTC-29DEC23-24000-C,BTC-29DEC23-24000-P,BTC-29DEC23-22000-C,BTC-29DEC23-22000-P,BTC-29DEC23-20000-C,BTC-29DEC23-20000-P,BTC-29DEC23-15000-C,BTC-29DEC23-15000-P,BTC-29DEC23-10000-C,BTC-29DEC23-10000-P,BTC-24NOV23-40000-C,BTC-24NOV23-40000-P,BTC-24NOV23-38000-C,BTC-24NOV23-38000-P,BTC-24NOV23-36000-C,BTC-24NOV23-36000-P,BTC-24NOV23-34000-C,BTC-24NOV23-34000-P,BTC-24NOV23-32000-C,BTC-24NOV23-32000-P,BTC-24NOV23-31500-C,BTC-24NOV23-31500-P,BTC-24NOV23-30500-C,BTC-24NOV23-30500-P,BTC-24NOV23-30000-C,BTC-24NOV23-30000-P,BTC-24NOV23-29500-C,BTC-24NOV23-29500-P,BTC-24NOV23-29000-C,BTC-24NOV23-29000-P,BTC-24NOV23-28500-C,BTC-24NOV23-28500-P,BTC-24NOV23-28000-C,BTC-24NOV23-28000-P,BTC-24NOV23-27500-C,BTC-24NOV23-27500-P,BTC-24NOV23-27000-C,BTC-24NOV23-27000-P,BTC-24NOV23-26500-C,BTC-24NOV23-26500-P,BTC-24NOV23-26000-C,BTC-24NOV23-26000-P,BTC-24NOV23-25500-C,BTC-24NOV23-25500-P,BTC-24NOV23-25000-C,BTC-24NOV23-25000-P,BTC-24NOV23-24000-C,BTC-24NOV23-24000-P,BTC-24NOV23-23000-C,BTC-24NOV23-23000-P,BTC-24NOV23-22000-C,BTC-24NOV23-22000-P,BTC-24NOV23-20000-C,BTC-24NOV23-20000-P,BTC-24NOV23-18000-C,BTC-24NOV23-18000-P,BTC-24NOV23-16000-C,BTC-24NOV23-16000-P,BTC-3NOV23-36000-C,BTC-3NOV23-36000-P,BTC-3NOV23-34000-C,BTC-3NOV23-34000-P,BTC-3NOV23-32000-C,BTC-3NOV23-32000-P,BTC-3NOV23-30000-C,BTC-3NOV23-30000-P,BTC-3NOV23-29000-C,BTC-3NOV23-29000-P,BTC-3NOV23-28500-C,BTC-3NOV23-28500-P,BTC-3NOV23-27500-C,BTC-3NOV23-27500-P,BTC-3NOV23-27000-C,BTC-3NOV23-27000-P,BTC-3NOV23-26500-C,BTC-3NOV23-26500-P,BTC-3NOV23-26000-C,BTC-3NOV23-26000-P,BTC-3NOV23-25000-C,BTC-3NOV23-25000-P,BTC-3NOV23-24000-C,BTC-3NOV23-24000-P,BTC-3NOV23-22000-C,BTC-3NOV23-22000-P,BTC-3NOV23-20000-C,BTC-3NOV23-20000-P,BTC-3NOV23-18000-C,BTC-3NOV23-18000-P,BTC-27OCT23-44000-C,BTC-27OCT23-44000-P,BTC-27OCT23-42000-C,BTC-27OCT23-42000-P,BTC-27OCT23-40000-C,BTC-27OCT23-40000-P,BTC-27OCT23-38000-C,BTC-27OCT23-38000-P,BTC-27OCT23-37000-C,BTC-27OCT23-37000-P,BTC-27OCT23-35000-C,BTC-27OCT23-35000-P,BTC-27OCT23-34500-C,BTC-27OCT23-34500-P,BTC-27OCT23-33500-C,BTC-27OCT23-33500-P,BTC-27OCT23-32500-C,BTC-27OCT23-32500-P,BTC-27OCT23-31500-C,BTC-27OCT23-31500-P,BTC-27OCT23-31000-C,BTC-27OCT23-31000-P,BTC-27OCT23-30500-C,BTC-27OCT23-30500-P,BTC-27OCT23-30000-C,BTC-27OCT23-30000-P,BTC-27OCT23-29500-C,BTC-27OCT23-29500-P,BTC-27OCT23-29000-C,BTC-27OCT23-29000-P,BTC-27OCT23-28750-C,BTC-27OCT23-28750-P,BTC-27OCT23-28500-C,BTC-27OCT23-28500-P,BTC-27OCT23-28250-C,BTC-27OCT23-28250-P,BTC-27OCT23-28000-C,BTC-27OCT23-28000-P,BTC-27OCT23-27750-C,BTC-27OCT23-27750-P,BTC-27OCT23-27500-C,BTC-27OCT23-27500-P,BTC-27OCT23-27250-C,BTC-27OCT23-27250-P,BTC-27OCT23-27000-C,BTC-27OCT23-27000-P,BTC-27OCT23-26500-C,BTC-27OCT23-26500-P,BTC-27OCT23-26000-C,BTC-27OCT23-26000-P,BTC-27OCT23-25500-C,BTC-27OCT23-25500-P,BTC-27OCT23-25000-C,BTC-27OCT23-25000-P,BTC-27OCT23-24000-C,BTC-27OCT23-24000-P,BTC-27OCT23-23000-C,BTC-27OCT23-23000-P,BTC-27OCT23-22000-C,BTC-27OCT23-22000-P,BTC-27OCT23-20000-C,BTC-27OCT23-20000-P,BTC-27OCT23-18000-C,BTC-27OCT23-18000-P,BTC-27OCT23-16000-C,BTC-27OCT23-16000-P,BTC-20OCT23-36000-C,BTC-20OCT23-36000-P,BTC-20OCT23-34000-C,BTC-20OCT23-34000-P,BTC-20OCT23-32000-C,BTC-20OCT23-32000-P,BTC-20OCT23-31000-C,BTC-20OCT23-31000-P,BTC-20OCT23-30500-C,BTC-20OCT23-30500-P,BTC-20OCT23-30000-C,BTC-20OCT23-30000-P,BTC-20OCT23-29500-C,BTC-20OCT23-29500-P,BTC-20OCT23-29000-C,BTC-20OCT23-29000-P,BTC-20OCT23-28750-C,BTC-20OCT23-28750-P,BTC-20OCT23-28500-C,BTC-20OCT23-28500-P,BTC-20OCT23-28250-C,BTC-20OCT23-28250-P,BTC-20OCT23-28000-C,BTC-20OCT23-28000-P,BTC-20OCT23-27750-C,BTC-20OCT23-27750-P,BTC-20OCT23-27500-C,BTC-20OCT23-27500-P,BTC-20OCT23-27250-C,BTC-20OCT23-27250-P,BTC-20OCT23-27000-C,BTC-20OCT23-27000-P,BTC-20OCT23-26750-C,BTC-20OCT23-26750-P,BTC-20OCT23-26500-C,BTC-20OCT23-26500-P,BTC-20OCT23-26250-C,BTC-20OCT23-26250-P,BTC-20OCT23-26000-C,BTC-20OCT23-26000-P,BTC-20OCT23-25750-C,BTC-20OCT23-25750-P,BTC-20OCT23-25500-C,BTC-20OCT23-25500-P,BTC-20OCT23-25000-C,BTC-20OCT23-25000-P,BTC-20OCT23-24000-C,BTC-20OCT23-24000-P,BTC-20OCT23-22000-C,BTC-20OCT23-22000-P,BTC-20OCT23-20000-C,BTC-20OCT23-20000-P,BTC-20OCT23-18000-C,BTC-20OCT23-18000-P,BTC-19OCT23-29750-C,BTC-19OCT23-29750-P,BTC-19OCT23-29500-C,BTC-19OCT23-29500-P,BTC-19OCT23-29250-C,BTC-19OCT23-29250-P,BTC-19OCT23-29000-C,BTC-19OCT23-29000-P,BTC-19OCT23-28750-C,BTC-19OCT23-28750-P,BTC-19OCT23-28500-C,BTC-19OCT23-28500-P,BTC-19OCT23-28250-C,BTC-19OCT23-28250-P,BTC-19OCT23-28000-C,BTC-19OCT23-28000-P,BTC-19OCT23-27750-C,BTC-19OCT23-27750-P,BTC-19OCT23-27500-C,BTC-19OCT23-27500-P,BTC-19OCT23-27250-C,BTC-19OCT23-27250-P,BTC-19OCT23-27000-C,BTC-19OCT23-27000-P,BTC-19OCT23-26750-C,BTC-19OCT23-26750-P,BTC-19OCT23-26500-C,BTC-19OCT23-26500-P,BTC-19OCT23-26250-C,BTC-19OCT23-26250-P,BTC-19OCT23-26000-C,BTC-19OCT23-26000-P,BTC-19OCT23-25750-C,BTC-19OCT23-25750-P,BTC-18OCT23-29500-C,BTC-18OCT23-29500-P,BTC-18OCT23-29250-C,BTC-18OCT23-29250-P,BTC-18OCT23-29000-C,BTC-18OCT23-29000-P,BTC-18OCT23-28750-C,BTC-18OCT23-28750-P,BTC-18OCT23-28500-C,BTC-18OCT23-28500-P,BTC-18OCT23-28250-C,BTC-18OCT23-28250-P,BTC-18OCT23-28000-C,BTC-18OCT23-28000-P,BTC-18OCT23-27750-C,BTC-18OCT23-27750-P,BTC-18OCT23-27500-C,BTC-18OCT23-27500-P,BTC-18OCT23-27250-C,BTC-18OCT23-27250-P,BTC-18OCT23-27000-C,BTC-18OCT23-27000-P,BTC-18OCT23-26750-C,BTC-18OCT23-26750-P,BTC-18OCT23-26500-C,BTC-18OCT23-26500-P,BTC-18OCT23-26250-C,BTC-18OCT23-26250-P,BTC-18OCT23-26000-C,BTC-18OCT23-26000-P,BTC-18OCT23-25750-C,BTC-18OCT23-25750-P,BTC-18OCT23-25500-C,BTC-18OCT23-25500-P,BTC-17OCT23-29250-C,BTC-17OCT23-29250-P,BTC-17OCT23-29000-C,BTC-17OCT23-29000-P,BTC-17OCT23-28750-C,BTC-17OCT23-28750-P,BTC-17OCT23-28500-C,BTC-17OCT23-28500-P,BTC-17OCT23-28250-C,BTC-17OCT23-28250-P,BTC-17OCT23-28000-C,BTC-17OCT23-28000-P,BTC-17OCT23-27750-C,BTC-17OCT23-27750-P,BTC-17OCT23-27500-C,BTC-17OCT23-27500-P,BTC-17OCT23-27250-C,BTC-17OCT23-27250-P,BTC-17OCT23-27000-C,BTC-17OCT23-27000-P,BTC-17OCT23-26750-C,BTC-17OCT23-26750-P,BTC-17OCT23-26500-C,BTC-17OCT23-26500-P,BTC-17OCT23-26250-C,BTC-17OCT23-26250-P,BTC-17OCT23-26000-C,BTC-17OCT23-26000-P,BTC-17OCT23-25750-C,BTC-17OCT23-25750-P,BTC-17OCT23-25500-C,BTC-17OCT23-25500-P", + "requestFormat": { + "uppercase": true, + "delimiter": "-" + }, + "configFormat": { + "uppercase": true, + "delimiter": "-" + } + } + } + }, + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "tickerBatching": true, + "autoPairUpdates": true + }, + "websocketAPI": true, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": true + } + }, + "bankAccounts": [ + { + "enabled": false, + "bankName": "", + "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ] + }, + { + "name": "COINUT", + "enabled": true, + "verbose": false, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, + "websocketOrderbookBufferLimit": 5, + "baseCurrencies": "USD", + "currencyPairs": { + "requestFormat": { + "uppercase": true + }, + "configFormat": { + "uppercase": true, + "delimiter": "-" + }, + "useGlobalFormat": true, + "assetTypes": [ + "spot" + ], + "pairs": { + "spot": { + "enabled": "LTC-USDT", + "available": "LTC-CAD,LTC-SGD,USDT-USD,ETC-LTC,LTC-BTC,USDT-SGD,XMR-USDT,ZEC-SGD,ETH-USD,BTC-USDT,ETC-BTC,ETH-LTC,LTC-USD,BTC-USD,ETH-USDT,XMR-LTC,ZEC-USD,ETC-SGD,DAI-SGD,ZEC-CAD,BTC-SGD,ETH-BTC,ETH-SGD,LTC-USDT,ZEC-BTC,ZEC-USDT,BTC-CAD,XMR-BTC,ZEC-LTC,ETC-USDT,ETH-CAD" + } + } + }, + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret", + "clientID": "ClientID" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresClientID": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "autoPairUpdates": true + }, + "websocketAPI": true, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": false + } + }, + "bankAccounts": [ + { + "enabled": false, + "bankName": "", + "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ] + }, + { + "name": "CoinbasePro", + "enabled": true, + "verbose": false, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, + "websocketOrderbookBufferLimit": 5, + "baseCurrencies": "USD,GBP,EUR", + "currencyPairs": { + "requestFormat": { + "uppercase": true, + "delimiter": "-" + }, + "configFormat": { + "uppercase": true, + "delimiter": "-" + }, + "useGlobalFormat": true, + "assetTypes": [ + "spot", + "futures" + ], + "pairs": { + "spot": { + "enabled": "BTC-USD", + "available": "LTC-GBP,XLM-BTC,DASH-BTC,DAI-USDC,ZEC-USDC,XLM-EUR,ZRX-BTC,LTC-BTC,ETC-BTC,ETH-USD,XRP-EUR,BTC-USDC,REP-USD,EOS-BTC,ZEC-BTC,ETC-GBP,LINK-ETH,XRP-BTC,ZRX-USD,ETH-USDC,MANA-USDC,BTC-EUR,BCH-GBP,DNT-USDC,EOS-EUR,BCH-EUR,LTC-EUR,CVC-USDC,ETH-GBP,DASH-USD,ETH-EUR,XTZ-BTC,ZRX-EUR,BAT-ETH,BTC-GBP,ETC-USD,BAT-USDC,BCH-USD,GNT-USDC,ALGO-USD,LINK-USD,XLM-USD,ETH-BTC,EOS-USD,REP-BTC,ETH-DAI,XRP-USD,LTC-USD,ETC-EUR,BTC-USD,XTZ-USD,BCH-BTC,LOOM-USDC" + }, + "futures": { + "enabled": "BTC-PERP-INTX", + "available": "BTC-PERP-INTX" + } + } + }, + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret", + "clientID": "ClientID" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true, + "requiresClientID": true, + "requiresBase64DecodeSecret": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "autoPairUpdates": true + }, + "websocketAPI": true, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": false + } + }, + "bankAccounts": [ + { + "enabled": false, + "bankName": "", + "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ] + }, + { + "name": "EXMO", + "enabled": true, + "verbose": false, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, + "websocketOrderbookBufferLimit": 5, + "baseCurrencies": "USD,EUR,RUB,PLN,UAH", + "currencyPairs": { + "requestFormat": { + "uppercase": true, + "delimiter": "_", + "separator": "," + }, + "configFormat": { + "uppercase": true, + "delimiter": "_" + }, + "useGlobalFormat": true, + "assetTypes": [ + "spot" + ], + "pairs": { + "spot": { + "enabled": "BTC_USD,LTC_USD", + "available": "BCH_RUB,DASH_RUB,EOS_USD,ETH_TRY,GNT_ETH,LTC_USD,PTI_USDT,XRP_BTC,EXM_BTC,BTG_BTC,ETC_RUB,BTG_USD,NEO_RUB,XMR_BTC,ZRX_ETH,MNX_BTC,USDC_BTC,XRP_EUR,SMART_USD,EOS_BTC,MNX_ETH,ZEC_BTC,BCH_USD,WAVES_USD,TRX_BTC,XRP_TRY,DASH_USD,DOGE_USD,ETZ_USDT,GUSD_USD,MNC_BTC,ZEC_USD,DCR_BTC,DXT_USD,PTI_RUB,XMR_ETH,ZRX_USD,DAI_RUB,MNC_USD,XLM_TRY,DAI_BTC,BTC_EUR,LTC_EUR,OMG_BTC,PTI_EOS,SMART_RUB,XTZ_USD,HP_EXM,ADA_USD,OMG_ETH,QTUM_USD,TRX_RUB,USDC_ETH,USDC_USDT,USD_RUB,BTC_UAH,BCH_USDT,ETH_PLN,KICK_RUB,LSK_RUB,SMART_BTC,XMR_UAH,XRP_USD,GUSD_BTC,QTUM_ETH,USDT_EUR,BTC_RUB,DCR_UAH,ETH_RUB,DOGE_BTC,ETZ_BTC,INK_USD,LTC_UAH,BTT_UAH,BTC_USDT,MNC_ETH,XTZ_ETH,BTC_TRY,DXT_BTC,KICK_USDT,OMG_USD,WAVES_BTC,XLM_BTC,BTCZ_BTC,GNT_BTC,LSK_BTC,LTC_RUB,NEO_BTC,XEM_UAH,XMR_USD,ZAG_BTC,GAS_USD,LTC_BTC,TRX_UAH,XEM_EUR,XMR_RUB,XTZ_RUB,ETZ_ETH,ETC_BTC,GUSD_RUB,INK_BTC,LSK_USD,MNX_USD,SMART_EUR,VLX_BTC,BCH_ETH,XMR_EUR,ADA_ETH,QTUM_BTC,XEM_USD,ATMCASH_BTC,ADA_BTC,ETH_EUR,TRX_USD,USDC_USD,BCH_BTC,ETH_UAH,KICK_BTC,WAVES_RUB,XEM_BTC,ETH_BTC,BCH_EUR,BTT_BTC,ROOBEE_BTC,XLM_USD,XRP_ETH,ETH_USD,MKR_DAI,XTZ_BTC,DAI_USD,BCH_UAH,INK_ETH,KICK_ETH,MKR_BTC,NEO_USD,XRP_USDT,ZEC_EUR,BTC_USD,XRP_RUB,EOS_EUR,ETH_USDT,USDT_UAH,XRP_UAH,ZEC_RUB,HP_BTC,BTT_RUB,DAI_ETH,DASH_UAH,DASH_USDT,ETH_LTC,GAS_BTC,USDT_USD,BTG_ETH,XLM_RUB,WAVES_ETH,USDT_RUB,ZRX_BTC,DASH_BTC,DCR_RUB,ETC_USD,HB_BTC,PTI_BTC,BTC_PLN" + } + } + }, + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "tickerBatching": true, + "autoPairUpdates": true + }, + "websocketAPI": false, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": false + } + }, + "bankAccounts": [ + { "enabled": false, - "bankName": "test", - "bankAddress": "test", + "bankName": "", + "bankAddress": "", "bankPostalCode": "", "bankPostalCity": "", "bankCountry": "", - "accountName": "TestAccount", - "accountNumber": "0234", - "swiftCode": "91272837", - "iban": "98218738671897", - "supportedCurrencies": "USD", - "supportedExchanges": "Kraken,Bitstamp" + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ] + }, + { + "name": "GateIO", + "enabled": true, + "verbose": false, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, + "websocketOrderbookBufferLimit": 5, + "baseCurrencies": "USD", + "currencyPairs": { + "requestFormat": { + "uppercase": true, + "delimiter": "_" + }, + "configFormat": { + "uppercase": true, + "delimiter": "_" + }, + "useGlobalFormat": true, + "assetTypes": [ + "spot", + "option", + "futures", + "cross_margin", + "margin", + "delivery" + ], + "pairs": { + "spot": { + "enabled": "BTC_USDT,IHT_ETH,AME_ETH,CEUR_ETH,ALEPH_USDT,OMG_TRY,BTC_TRY,OGN_USDT,ALA_USDT", + "available": "IHT_ETH,AME_ETH,CEUR_ETH,ALEPH_USDT,OMG_TRY,BTC_TRY,OGN_USDT,ALA_USDT,HC_USDT,BTC_USDT,QNT_USDT,QTUM_ETH,MAHA_ETH,XCN_ETH,POOL_USDT,KGC_USDT,MCO2_USDT,HARD_USDT,GHNY_USDT,FTT_ETH,K21_ETH,FINE_USDT,REP_USDT,SBR_USDT,SKM_ETH,QLC_ETH,GAS_BTC,ALICE3L_USDT,BAO_USDT,FALCONS_USDT,ANT_USDT,VIDYX_USDT,DXCT_ETH,SMTY_ETH,HERO_USDT,SHARE_USDT,FIN_USDT,MTV_USDT,MOO_USDT,SMTY_USDT,ORAO_USDT,AE_ETH,SUSD_USDT,MAN_USDT,UNDEAD_USDT,MC_USDT,VET_USDT,WAXP_ETH,MDA_ETH,LYXE_USDT,SPS_USDT,STX_ETH,WSIENNA_USDT,NAOS_BTC,NFTX_USDT,OPUL_USDT,ICP3L_USDT,SFI_ETH,CTT_USDT,BSV3L_USDT,DFI_USDT,DIS_ETH,FET_USDT,ARG_USDT,VELO_USDT,NSBT_BTC,GSE_ETH,HNS_BTC,DOGEDASH_ETH,BACON_USDT,DUSK_USDT,MAPE_USDT,EGLD_ETH,TDROP_USDT,C983L_USDT,FAN_ETH,CZZ_USDT,FIU_USDT,SWRV_USDT,ONT_ETH,KINE_ETH,IMX_ETH,SPAY_ETH,CFG_BTC,RACA3S_USDT,UNO_ETH,DMLG_USDT,SAKE_ETH,ASM_USDT,CUSD_ETH,SUSD_ETH,ONC_USDT,DAI_USDT,VEGA_ETH,PYM_USDT,LTC_TRY,LOKA_USDT,NIF_USDT,BNC_USDT,PERL_ETH,MATIC3S_USDT,STMX_USDT,SKL_USDT,WLKN_USDT,XYO_ETH,AMPL3S_USDT,WEX_USDT,ULU_ETH,LIKE_ETH,INSUR_ETH,CAKE_ETH,SXP_ETH,COTI_USDT,ORT_USDT,RACA3L_USDT,GASDAO_USDT,AVA_USDT,OPA_USDT,ATS_USDT,VEGA_USDT,KILT_USDT,HIT_ETH,BRISE_USDT,SAUBER_USDT,SPS_ETH,FSN_USDT,EOS_ETH,KYL_USDT,REVV_ETH,SVT_ETH,XRP_USDT,DYDX3S_USDT,MANA3S_USDT,ICP_ETH,ALICE3S_USDT,PCX_USDT,LEMO_ETH,MKR_ETH,WOO3S_USDT,CART_ETH,MATIC_USDT,UNI_USD,MOBI_BTC,ICP3S_USDT,BEAM_BTC,CRO3S_USDT,FTT_USDT,IQ_ETH,TAP_USDT,MLT_USDT,RBN_USDT,AMPL3L_USDT,KINT_ETH,HECH_USDT,GAFI_ETH,WOO3L_USDT,TAI_USDT,HERA_USDT,AST_USDT,DHV_ETH,XAVA_USDT,LSS_USDT,SNX3S_USDT,PBR_USDT,XEND_ETH,SHR_ETH,PRQ_USDT,MATIC3L_USDT,WIT_ETH,LPOOL_USDT,PSP_USDT,BXC_USDT,CBK_USDT,REVO_BTC,MANA3L_USDT,ALPINE_USDT,DEGO_USDT,SIN_USDT,OCT_USDT,KZEN_USDT,L3P_USDT,FX_ETH,ONC_ETH,AXS_USD,BORA_USDT,XTZ_ETH,NEO3L_USDT,FROG_USDT,CHAMP_USDT,XNFT_USDT,BCH3S_USDT,FORT_USDT,XLM_TRY,TRX_TRY,CRPT_USDT,ROUTE_USDT,GLM_USDT,SLRS_ETH,TIMECHRONO_USDT,VRA_USDT,ONS_USDT,ZEC3L_USDT,KFT_ETH,TFD_ETH,FRA_USDT,RDN_ETH,BLANK_USDT,IOST3L_USDT,DDD_USDT,DOGE_USD,UNQ_USDT,API33S_USDT,AKRO_ETH,GITCOIN_USDT,THG_USDT,BDX_USDT,LTO_ETH,FLY_USDT,CREDIT_USDT,RENA_USDT,ZRX_ETH,CRP_ETH,NBOT_USDT,HT3L_USDT,DORA_ETH,LLT_SNET,ASD_USDT,XMR_USDT,SSV_BTC,FTM_USDT,XELS_USDT,MTL_ETH,ADX_ETH,API33L_USDT,PIG_USDT,RUNE_ETH,QRDO_BTC,THN_USDT,BCUG_USDT,EGG_ETH,GGM_USDT,HOTCROSS_USDT,SKYRIM_USDT,BTG_USDT,POT_USDT,CS_USDT,XVS_USDT,A5T_USDT,GOD_BTC,WAVES_USDT,LSK_BTC,BTT_TRY,YIN_USDT,PEOPLE_USDT,SPELL_ETH,POLC_USDT,BZZ3L_USDT,UNO_USDT,HDV_USDT,CELL_USDT,DAR_ETH,MIR_ETH,FODL_USDT,SRM_ETH,PROS_USDT,ORN_ETH,WAG_USDT,RBC_ETH,VENT_USDT,WND_USDT,AAA_ETH,BSCS_ETH,ZEC3S_USDT,DOS_USDT,HT3S_USDT,LAND_USDT,BCD_BTC,RING_USDT,FIRO_USDT,AUDIO_USDT,KUMA_USDT,SOLO_BTC,CRBN_USDT,MM_ETH,SAKE_USDT,XMARK_USDT,SLP_USDT,F2C_USDT,LUNA_USDT,ONIT_USDT,FTM3L_USDT,POPK_USDT,RFUEL_USDT,NEO3S_USDT,MIR_USDT,ETC_BTC,STETH_ETH,MANA_TRY,ALPACA_ETH,WAXL_USDT,EGS_USDT,DAR_USDT,KSM_USDT,XMARK_ETH,QTUM_USDT,C983S_USDT,INDI_ETH,DOGE3S_USDT,RVN_USDT,NOS_USDT,ALU_ETH,ALD_ETH,LUNC_USDT,ARES_ETH,BZZ3S_USDT,TNC_ETH,ONE_USDT,SENC_ETH,FTM3S_USDT,FLUX_USDT,STORJ_ETH,MTN_ETH,MNW_USDT,BLES_ETH,STG_ETH,LIME_ETH,WAGYU_USDT,XRP_TRY,XOR_ETH,ANGLE_USDT,DOGA_USDT,JFI_USDT,USDG_USDT,GRND_USDT,BOND_ETH,DMTR_USDT,YIN_ETH,ENJ_USDT,GOLDMINER_USDT,WIT_USDT,DOGE3L_USDT,FORM_USDT,LYXE_ETH,MLK_USDT,VR_USDT,DMS_USDT,LRC_TRY,ONX_USDT,ASK_USDT,ISP_ETH,TXT_USDT,IOEN_ETH,NIIFI_USDT,VRX_USDT,DOME_USDT,CTSI_USDT,ORBS_USDT,ZLW_ETH,FIL_USDT,FTI_ETH,CTK_USDT,ASR_USDT,GBPT_BTC,CBK_BTC,MBOX_ETH,RAM_USDT,IRIS_USDT,AME_USDT,KUB_USDT,ENV_USDT,RING_ETH,COTI3S_USDT,JULD_ETH,POLK_ETH,ACH3S_USDT,HYVE_ETH,MIX_ETH,RFT_USDT,ORAO_ETH,IHT_USDT,POLYPAD_USDT,CTRC_USDT,SFUND_USDT,MXC_BTC,DDD_BTC,CHESS_ETH,SHIB_USDT,SN_USDT,NFT_USDT,ASTRO_ETH,SOLO_USDT,TSHP_USDT,AMP_USDT,BTCST_ETH,VLXPAD_USDT,GAN_USDT,O3_USDT,WBTC_TRY,TULIP_USDT,GS_ETH,DX_ETH,NYZO_ETH,TT_USDT,SHILL_USDT,RATING_ETH,DUST_USDT,PSB_USDT,BFT1_USDT,GALA_ETH,XDC_USDT,LON3L_USDT,HE_USDT,ICE_ETH,LINK_ETH,SKU_USDT,QLC_USDT,DOMI_USDT,IDEA_USDT,METO_USDT,LIFE_ETH,ACH3L_USDT,POWR_ETH,VET_ETH,ALGO_USDT,BLIN_USDT,BAO_ETH,RBLS_USDT,TORN_ETH,VRT_USDT,BLANKV2_ETH,AUCTION_ETH,OLE_USDT,NWC_BTC,DOT5S_USDT,M RCH_ETH,SUNNY_ETH,GST_USDT,ENJ_TRY,KIBA_USDT,KLAP_USDT,SNTR_ETH,CELR_ETH,CHESS_USDT,XLM3L_USDT,LIQ_USDT,TRU_ETH,CHZ_USD,EPK_USDT,MED_ETH,BSCPAD_ETH,ZCN_USDT,AIOZ_ETH,FOR_ETH,CVC3L_USDT,MNY_USDT,SALT_USDT,CSTR_USDT,MPL_USDT,PLY_ETH,FIS_USDT,CHO_USDT,BICO_ETH,STOX_ETH,HIGH_USDT,SDAO_BTC,STEP_USD,CRV_BTC,SCRT_ETH,ROSE_USDT,SKILL_ETH,FRAX_USDT,BAGS_USDT,WIKEN_BTC,WOO_USDT,BBANK_ETH,SNX3L_USDT,XRD_ETH,VTHO_USDT,OKB3L_USDT,SAFEMOON_USDT,RAD_ETH,IOI_USDT,LAMB_USDT,CHZ_USDT,FAR_ETH,OKB3S_USDT,ELU_USDT,JGN_ETH,EOS3S_USDT,DBC_USDT,ATOM_USDT,ACH_ETH,LBLOCK_USDT,WZRD_USDT,OST_ETH,MEAN_USDT,IDEX_USDT,HOT_TRY,EWT_ETH,EMON_USDT,FXS_USDT,PSY_ETH,SIDUS_USDT,ATA_USDT,CVC3S_USDT,LOOKS_ETH,ALPA_ETH,CGG_ETH,CIR_ETH,PRT_ETH,LON3S_USDT,INJ_USDT,FIRE_ETH,MAHA_USDT,IOST3S_USDT,NU_ETH,LEO_BTC,VOXEL_USDT,CRV_USDT,EQX_USDT,WHALE_USDT,INJ_ETH,GRAP_USDT,AVAX3S_USDT,TIFI_USDT,C98_USDT,ERN_ETH,SUSHI_ETH,VET3S_USDT,KPAD_USDT,CRPT_ETH,CRO_USDT,AZY_USDT,LEMD_USDT,ETH2_ETH,BASE_ETH,TT_ETH,PERL_USDT,BANK_ETH,LST_ETH,PYR_ETH,RATIO_USDT,UMB_USDT,M ETALDR_USDT,SWINGBY_ETH,WICC_ETH,NUM_USDT,SHOE_USDT,BORING_ETH,SDN_USDT,GXS_BTC,ALICE_ETH,BRKL_USDT,GF_ETH,ELEC_USDT,SFG_USDT,COFIX_USDT,TIPS_ETH,FIL_BTC,CWAR_USDT,WILD_USDT,RENBTC_USDT,BNX_USDT,TRU_USDT,SWEAT_USDT,IOST_BTC,NVIR_USDT,1EARTH_USDT,ADAPAD_USDT,PPS_USDT,CUBE_USDT,DLC_USDT,DAFI_ETH,UNISTAKE_ETH,NFTL_USDT,ATOM_TRY,SHIB3S_USDT,BNB_USD,CNAME_USDT,GTH_ETH,ZCX_USDT,DYDX3L_USDT,ASTRO_USDT,GLQ_USDT,PROPS_USDT,AART_USDT,BTRST_ETH,KFT_USDT,AERGO_USDT,RUFF_ETH,EOS3L_USDT,API3_USDT,MINA_BTC,ETHA_ETH,AXIS_ETH,LOON_USDT,AVAX3L_USDT,VET3L_USDT,AE_USDT,SHX_USDT,LYM_USDT,DCR_BTC,LBK_USDT,QTC_USDT,LAVA_USDT,XCN_USDT,BRT_USDT,RSV_USDT,KIF_USDT,PSL_USDT,AZERO_USDT,LUNA_ETH,MILO_USDT,OGN_ETH,TOTM_USDT,BYN_ETH,MINA_USDT,PUNDIX_ETH,SRT_USDT,DG_ETH,IHC_USDT,SYS_ETH,TITA_USDT,COTI3L_USDT,DAG_USDT,DOT5L_USDT,TRADE_USDT,SHPING_USDT,NU_USDT,BLANK_ETH,PCNT_ETH,SCCP_USDT,POLS_USDT,NPT_USDT,MTA_USDT,YIELD_USDT,ZCN_ETH,DVP_ETH,KART_USDT,SYLO_USDT,MCRT_USDT,SPFC_USDT,BASE_USDT,ICX_USDT,PET_USDT,GZONE_USDT,RED_ETH,SBTC_USDT,BATH_ ETH,SOL_USD,NAFT_USDT,GMX_USDT,VADER_USDT,GTC_USDT,CVP_ETH,XRPBEAR_USDT,TIME_USDT,SXP_USDT,CITY_USDT,QASH_USDT,FAST_USDT,BCD_USDT,KNIGHT_USDT,BOO_ETH,ZODI_USDT,REI_USDT,PBX_ETH,SRM_USDT,LDO_ETH,ZEC_USDT,UFT_USDT,DAG_BTC,RIDE_USDT,ERN_USDT,T_USDT,CEEK_USDT,STI_USDT,IMX3S_USDT,ELA_USDT,MNGO_ETH,EHASH_ETH,BADGER_ETH,SUPE_USDT,AR3L_USDT,AUDIO_ETH,DOCK_ETH,QSP_USDT,FLM_USDT,AAVE3S_USDT,BOND_USDT,HT_USD,TARA_USDT,TRX_USDT,SPO_USDT,DSLA_USDT,LTC_BTC,DOGE_USDT,SLIM_ETH,ALN_ETH,CFX3S_USDT,FXS_ETH,RARE_ETH,VLXPAD_ETH,ETH_USD,SDN_BTC,QUICK_USDT,UTK_USDT,XPNET_USDT,TRB_USDT,LAZIO_USDT,FTM_TRY,ALPHA_ETH,CVC_ETH,WSG_USDT,UNI_ETH,DASH3L_USDT,BTL_USDT,CPOOL_USDT,MCG_USDT,SFP_ETH,REALM_USDT,RUFF_BTC,MOB_ETH,IBFK_USDT,ALPHA3S_USDT,BLOK_USDT,WIKEN_USDT,OMG3S_USDT,UTK_ETH,BCH5S_USDT,MED_USDT,REN_USD,MAN_ETH,SLND_ETH,CGG_USDT,CRE_USDT,SOURCE_USDT,ABT_USDT,DPET_USDT,WOM_USDT,FOREX_ETH,SNFT1_USDT,RIF_USDT,BENQI_USDT,XCV_ETH,GTC_BTC,ADA_TRY,LAT_USDT,ITGR_USDT,DLTA_USDT,SMT_USDT,APYS_USDT,MFT_ETH,ABT_ETH,STOX_USDT,ZRX_BTC,GMAT_USDT,R OOM_ETH,STORJ_BTC,RAZOR_USDT,RAGE_USDT,DOCK_USDT,RDN_USDT,MTR_USDT,NKN_USDT,SWASH_USDT,FX_USDT,POR_USDT,DENT_ETH,DERI_USDT,DFND_USDT,BLES_USDT,SLND_USDT,WNXM_ETH,CRTS_USDT,BTC3S_USDT,BKC_USDT,STEPG_ETH,THETA3L_USDT,NBS_BTC,AVAX_ETH,NANO_BTC,DEFILAND_ETH,LOOKS_USDT,BCX_BTC,BCH_USD,ETH3L_USDT,QLC_BTC,BCUG_ETH,RDF_USDT,DOGEDASH_USDT,ARSW_USDT,NEAR_ETH,QTCON_USDT,BABI_USDT,MBX_USDT,PNL_USDT,ODDZ_ETH,ATOM_BTC,XRP_BTC,BTCBULL_USDT,HMT_USDT,PORTO_USDT,STND_USDT,ETHW_ETH,LPT_USDT,LTC3L_USDT,TOKAU_USDT,QI_ETH,TVK_USDT,CWS_USDT,SWOP_USDT,WBTC_USDT,INSTAR_ETH,ICX_ETH,GALA5L_USDT,XTZ_BTC,AGS_USDT,TARA_BTC,DYDX_ETH,CATGIRL_USDT,SASHIMI_ETH,EPX_ETH,GCOIN_USDT,GDAO_USDT,MARS_ETH,OMG_USD,PMON_USDT,MNGO_USDT,TVK_ETH,SLG_USDT,MSOL_USDT,POWR_USDT,UOS_USDT,USDD_USDT,SLICE_USDT,ARRR_ETH,NSBT_USDT,STR_ETH,BEAM3L_USDT,BEL_USDT,MM_USDT,AXS_ETH,WEST_ETH,FTT3L_USDT,OMI_USDT,TIPS_USDT,SLC_ETH,SQUID_USDT,FEI_USDT,GEM_USDT,UMEE_USDT,DOGE_TRY,FCD_USDT,PVU_USDT,XED_ETH,LRN_ETH,NRFB_USDT,LION_USDT,BLACK_USDT,DOGE5S_USDT,CUDOS_USDT,PCNT_USDT ,OVR_USDT,ETC3S_USDT,CHR_ETH,MER_USDT,BOBA_USDT,FUEL_USDT,BAC_USDT,ONE3S_USDT,CONV_ETH,CDT_BTC,CELL_ETH,ASM_ETH,OPIUM_USDT,JST3L_USDT,BONDLY_USDT,RAZE_USDT,LIME_BTC,NFTX_ETH,PNK_ETH,LDO_USDT,DKS_USDT,ORO_USDT,LITH_USDT,ALPHR_ETH,INK_BTC,RLY_USDT,NEAR3S_USDT,XLM3S_USDT,AR_USDT,AKT_USDT,HCT_USDT,REEF_ETH,BZZ_USDT,SRM3L_USDT,AQDC_USDT,OPIUM_ETH,BAT_TRY,EWT_USDT,ALCX_ETH,CORN_USDT,HYDRA_USDT,RUNE_USD,STEP_USDT,CKB_BTC,MATTER_USDT,STSOL_ETH,CEEK_ETH,FXF_ETH,LIKE_USDT,HIT_USDT,LEO_USDT,COMP_USDT,BAL_USDT,LMR_USDT,AQT_USDT,BUY_ETH,LINK3S_USDT,ROOK_ETH,IMX_USDT,EFI_USDT,TAUR_USDT,OKT_ETH,GALO_USDT,MOOV_USDT,RUNE_USDT,TCP_USDT,ITEM_USDT,SCLP_USDT,RBC_USDT,SPI_USDT,ETC_USDT,RENBTC_BTC,CHICKS_USDT,KNOT_USDT,XEC3L_USDT,XCV_USDT,ETC_ETH,AAVE_TRY,APT_USDT,GNX_ETH,KISHU_USDT,AE_BTC,LIEN_USDT,CREAM_USDT,ATOM3S_USDT,OP_ETH,FORTH_ETH,PYR_USDT,KTN_ETH,TKO_ETH,METAG_USDT,ACE_USDT,CIR_USDT,BEAM_ETH,TCP_ETH,SRM_USD,CEL_USD,TRIBE3S_USDT,MESA_ETH,EVA_USDT,BBANK_USDT,BLANKV2_USDT,FORM_ETH,BAL3S_USDT,VISR_ETH,REVO_ETH,ALTB_USDT,KNC_US DT,GAS_USDT,SAFEMARS_USDT,TIP_USDT,VADER_ETH,NWC_USDT,VALUE_USDT,ATA_ETH,SSX_USDT,JOE_USDT,BAS_ETH,FITFI3S_USDT,BIT_USDT,QNT_ETH,RFOX_ETH,MSU_USDT,MSOL_ETH,CRV3L_USDT,OXT_USDT,SHFT_USDT,VERA_ETH,LYM_ETH,BP_USDT,KBOX_USDT,DOGNFT_ETH,PERP_USDT,VELO_ETH,SAO_USDT,DUCK2_USDT,DEFILAND_USDT,DUCK2_ETH,GLMR3L_USDT,SERO_ETH,MTS_USDT,STX_USDT,KEX_ETH,ZIG_USDT,CARDS_USDT,ANML_USDT,GALA_USDT,RAY3S_USDT,KAVA3L_USDT,GARD_USDT,GRT3L_USDT,BFC_USDT,NIFT_USDT,ORION_USDT,CTX_USDT,ASW_USDT,CERE_USDT,COMBO_ETH,MKR_USDT,MASK_USDT,MGA_USDT,AVAX_USDT,SKL3L_USDT,FRR_USDT,MV_USDT,BMI_ETH,SFIL_USDT,TEER_USDT,KLV_USDT,DMS_ETH,LBL_USDT,MKR3L_USDT,LEDU_BTC,XLM_BTC,MIST_ETH,OIN_USDT,CAKE_USDT,RNDR_USDT,STEPG_USDT,YCT_USDT,OPS_ETH,SHR_USDT,OXY_ETH" + }, + "option": { + "enabled": "BTC_USDT-20230217-28000-P,BTC_USDT-20221028-34000-P,BTC_USDT-20221028-40000-C", + "available": "BTC_USDT-20221028-26000-C,BTC_USDT-20221028-34000-P,BTC_USDT-20221028-40000-C,BTC_USDT-20221028-28000-P,BTC_USDT-20221028-34000-C,BTC_USDT-20221028-28000-C,BTC_USDT-20221028-36000-P,BTC_USDT-20221028-50000-P,BTC_USDT-20221028-36000-C,BTC_USDT-20221028-50000-C,BTC_USDT-20221028-21000-P,BTC_USDT-20221028-38000-P,BTC_USDT-20221028-21000-C,BTC_USDT-20221028-38000-C,BTC_USDT-20221028-23000-P,BTC_USDT-20221028-17000-P,BTC_USDT-20221028-23000-C,BTC_USDT-20221028-17000-C,BTC_USDT-20221028-25000-P,BTC_USDT-20221028-19000-P,BTC_USDT-20221028-25000-C,BTC_USDT-20221028-10000-P,BTC_USDT-20221028-19000-C,BTC_USDT-20221028-27000-P,BTC_USDT-20221028-10000-C,BTC_USDT-20221028-27000-C,BTC_USDT-20221028-12000-P,BTC_USDT-20221028-12000-C,BTC_USDT-20221028-20000-P,BTC_USDT-20221028-5000-P,BTC_USDT-20221028-14000-P,BTC_USDT-20221028-20000-C,BTC_USDT-20221028-45000-P,BTC_USDT-20221028-5000-C,BTC_USDT-20221028-14000-C,BTC_USDT-20221028-22000-P,BTC_USDT-20221028-45000-C,BTC_USDT-20221028-16000-P,BTC_USDT-20221028-22000-C,BTC_USDT-20221028-30000-P,BTC_USDT-20221028-16000-C,BTC_USDT-20221028-24000-P,BTC_USDT-20221028-30000-C,BTC_USDT-20221028-18000-P,BTC_USDT-20221028-24000-C,BTC_USDT-20221028-32000-P,BTC_USDT-20221028-18000-C,BTC_USDT-20221028-26000-P,BTC_USDT-20221028-32000-C,BTC_USDT-20221028-40000-P" + }, + "futures": { + "enabled": "ETH_USD,BTC_USD,KNC_USDT,OOKI_USDT,BIT_USDT,ZEC_USDT,SC_USDT,RVN_USDT,ICX_USDT", + "available": "ETH_USD,BTC_USD,KNC_USDT,OOKI_USDT,BIT_USDT,ZEC_USDT,SC_USDT,RVN_USDT,ICX_USDT,DUSK_USDT,BEL_USDT,REEF_USDT,ALCX_USDT,ASTR_USDT,INJ_USDT,CAKE_USDT,LAZIO_USDT,ONE_USDT,CEL_USDT,ETH_USDT,KLAY_USDT,COTI_USDT,MKISHU_USDT,MANA_USDT,MOVR_USDT,OMG_USDT,UNI_USDT,LTC_USDT,AAVE_USDT,DENT_USDT,QRDO_USDT,BNB_USDT,ALPHA_USDT,RAY_USDT,APE_USDT,CERE_USDT,STMX_USDT,XCN_USDT,OGN_USDT,OKB_USDT,DOT_USDT,TLM_USDT,BTM_USDT,ADA_USDT,ANKR_USDT,ANT_USDT,TRX_USDT,MTL_USDT,YFII_USDT,SUN_USDT,SAND_USDT,MBABYDOGE_USDT,WIN_USDT,LUNC_USDT,SRM_USDT,STG_USDT,BAT_USDT,AXS_USDT,SOL_USDT,MAKITA_USDT,BNT_USDT,BLZ_USDT,PSG_USDT,IOTA_USDT,BONK_USDT,RSR_USDT,PYR_USDT,FITFI_USDT,MKR_USDT,PERP_USDT,COMP_USDT,LINK_USDT,CHR_USDT,CFX_USDT,GARI_USDT,DGB_USDT,MBOX_USDT,WEMIX_USDT,DYDX_USDT,LUNA_USDT,HT_USDT,TRB_USDT,CTK_USDT,ACA_USDT,TFUEL_USDT,OCEAN_USDT,XLM_USDT,HOT_USDT,FTM_USDT,LPT_USDT,SOS_USDT,ALGO_USDT,SHIB_USDT,BSV_USDT,PORTO_USDT,SFP_USDT,SANTOS_USDT,BADGER_USDT,DAR_USDT,DEFI_USDT,XEM_USDT,ALICE_USDT,ICP_USDT,RARE_USDT,LRC_USDT,BAKE_USDT,FLUX_USDT,CRO_USDT,CVC_USDT,MINA_USDT,LIT_USDT,AUDIO_USDT,ZIL_USDT,XMR_USDT,FRONT_USDT,CTSI_USDT,AGLD_USDT,YGG_USDT,OP_USDT,ZRX_USDT,GT_USDT,XCH_USDT,VET_USDT,MOB_USDT,BICO_USDT,SLP_USDT,ACH_USDT,AR_USDT,CLV_USDT,IMX_USDT,SPELL_USDT,UNFI_USDT,SUSHI_USDT,FTT_USDT,HIGH_USDT,HNT_USDT,ALT_USDT,YFI_USDT,NEAR_USDT,NKN_USDT,XVS_USDT,BAND_USDT,LOKA_USDT,BCH_USDT,TOMO_USDT,WAVES_USDT,FIDA_USDT,DIA_USDT,ANC_USDT,CELO_USDT,CRV_USDT,FLM_USDT,GLMR_USDT,FIL_USDT,PEOPLE_USDT,WAXP_USDT,IOTX_USDT,ATOM_USDT,RLC_USDT,HBAR_USDT,REN_USDT,GMT_USDT,KAVA_USDT,KDA_USDT,GALA_USDT,STORJ_USDT,PUNDIX_USDT,BAL_USDT,XAUG_USDT,GRIN_USDT,SXP_USDT,AKRO_USDT,NEXO_USDT,CKB_USDT,API3_USDT,NEST_USDT,ETHW_USDT,TONCOIN_USDT,THETA_USDT,CREAM_USDT,BTC_USDT,GST_USDT,BEAM_USDT,HFT_USDT,KSM_USDT,RAD_USDT,QTUM_USDT,WOO_USDT,ATA_USDT,AVAX_USDT,EOS_USDT,SNX_USDT,AUCTION_USDT,XRP_USDT,GITCOIN_USDT,MATIC_USDT,ONT_USDT,LINA_USDT,DASH_USDT,MASK_USDT,ETC_USDT,JST_USDT,BSW_USDT,CONV_USDT,SKL_USDT,GAL_USDT,DODO_USDT,GRT_USDT,TRU_USDT,STX_USDT,CVX_USDT,JASMY_USDT,HIVE_USDT,EXCH_USDT,ROSE_USDT,SUPER_USDT,SCRT_USDT,USTC_USDT,ENJ_USDT,BTS_USDT,LOOKS_USDT,QNT_USDT,HOOK_USDT,FLOW_USDT,RUNE_USDT,APT_USDT,CHZ_USDT,DOGE_USDT,1INCH_USDT,PRIV_USDT,CSPR_USDT,C98_USDT,RACA_USDT,CELR_USDT,XEC_USDT,ENS_USDT,POND_USDT,NYM_USDT,PROM_USDT,IOST_USDT,ZEN_USDT,LDO_USDT,RNDR_USDT,REQ_USDT,DEGO_USDT,VRA_USDT,QUICK_USDT,VGX_USDT,XTZ_USDT,EGLD_USDT,POLS_USDT,ARPA_USDT,NFT_USDT" + }, + "cross_margin": { + "enabled": "BTC_USDT,ERN_USDT,T_USDT,CEEK_USDT,OGN_USDT,QNT_USDT,WOZX_USDT,ZEE_USDT,FUN_USDT,FLM_USDT,BOND_USDT", + "available": "ERN_USDT,T_USDT,CEEK_USDT,OGN_USDT,QNT_USDT,WOZX_USDT,ZEE_USDT,FUN_USDT,FLM_USDT,BOND_USDT,TARA_USDT,TRX_USDT,OXY_USDT,LON_USDT,DOGE_USDT,ISP_USDT,TWT_USDT,BAO_USDT,QUACK_USDT,ANT_USDT,VGX_USDT,ARPA_USDT,QUICK_USDT,UTK_USDT,HERO_USDT,WSG_USDT,BICO_USDT,MTV_USDT,VET_USDT,GARI_USDT,BCH_USDT,KLAY_USDT,WING_USDT,BLOK_USDT,SPS_USDT,WIKEN_USDT,WSIENNA_USDT,PUNDIX_USDT,FIC_USDT,ASTR_USDT,FET_USDT,VELO_USDT,BENQI_USDT,CWEB_USDT,RIF_USDT,UNI_USDT,ONG_USDT,ERG_USDT,ALPHA_USDT,CELO_USDT,XVG_USDT,GMAT_USDT,BTS_USDT,DOCK_USDT,GMT_USDT,DIA_USDT,CSPR_USDT,NKN_USDT,STAKE_USDT,SWASH_USDT,XEC_USDT,SWRV_USDT,QRDO_USDT,BLES_USDT,EOS_USDT,GRT_USDT,ASM_USDT,FIL6_USDT,GNO_USDT,EGLD_USDT,XYM_USDT,LOOKS_USDT,LOKA_USDT,BNC_USDT,BAS_USDT,SKL_USDT,STMX_USDT,CVC_USDT,DDOS_USDT,COTI_USDT,AVA_USDT,HMT_USDT,DF_USDT,LPT_USDT,XRP_USDT,TVK_USDT,FEVR_USDT,MBL_USDT,KIN_USDT,SPELL_USDT,MATIC_USDT,FTT_USDT,NMR_USDT,PMON_USDT,BNB_USDT,USDD_USDT,LSS_USDT,MDX_USDT,PRQ_USDT,ALPINE_USDT,DEGO_USDT,OMI_USDT,TIPS_USDT,OCT_USDT,FEI_USDT,UMEE_USDT,CRP_USDT,LION_USDT,YFI_USDT,DASH_USDT,REQ_USDT,SDAO_USDT,PNT_USDT,INSUR_USDT,OOKI_USDT,SUN_USDT,CRPT_USDT,BAC_USDT,DATA_USDT,LRN_USDT,JGN_USDT,KIMCHI_USDT,SUKU_USDT,VRA_USDT,AAVE_USDT,FTI_USDT,LDO_USDT,FRA_USDT,BLANK_USDT,NEAR_USDT,ZKS_USDT,MTRG_USDT,RLY_USDT,TCT_USDT,FLY_USDT,JST_USDT,YFII_USDT,AR_USDT,POLY_USDT,JULD_USDT,SOL_USDT,BZZ_USDT,AXS_USDT,ASD_USDT,XMR_USDT,FTM_USDT,HIT_USDT,LEO_USDT,LIT_USDT,PIG_USDT,COMP_USDT,ELON_USDT,IMX_USDT,EFI_USDT,XVS_USDT,WAVES_USDT,PEOPLE_USDT,SOS_USDT,RUNE_USDT,POLC_USDT,SCLP_USDT,BABYDOGE_USDT,KONO_USDT,SPI_USDT,ETC_USDT,MDA_USDT,MTL_USDT,BCHA_USDT,KISHU_USDT,SUNNY_USDT,PYR_USDT,XTZ_USDT,TRIBE_USDT,AUDIO_USDT,FIRO_USDT,MANA_USDT,OKB_USDT,DOG_USDT,SLP_USDT,KNC_USDT,GAS_USDT,LUNA_USDT,SAFEMARS_USDT,MIR_USDT,DAR_USDT,EGS_USDT,KSM_USDT,ATP_USDT,BIT_USDT,STORJ_USDT,XEM_USDT,QTUM_USDT,AGLD_USDT,RVN_USDT,OXT_USDT,SHFT_USDT,IOTX_USDT,LUNC_USDT,NEXO_USDT,AKITA_USDT,PERP_USDT,ONE_USDT,ETH_USDT,FLUX_USDT,FLOKI_USDT,STX_USDT,ANML_USDT,XPRT_USDT,GALA_USDT,GXS_USDT,TORN_USDT,KAI_USDT,1INCH_USDT,CHR_USDT,GAL_USDT,GLMR_USDT,CTX_USDT,CERE_USDT,CART_USDT,STRAX_USDT,MASK_USDT,MKR_USDT,AVAX_USDT,ENJ_USDT,YAM_USDT,ALPACA_USDT,DODO_USDT,MFT_USDT,CAKE_USDT,RNDR_USDT,CTSI_USDT,GRIN_USDT,MXC_USDT,ONT_USDT,ANKR_USDT,SLIM_USDT,FIL_USDT,CTK_USDT,ASR_USDT,FEG_USDT,SERO_USDT,RSS3_USDT,IRIS_USDT,XCH_USDT,ZRX_USDT,BAND_USDT,BADGER_USDT,DAO_USDT,EPS_USDT,THETA_USDT,BAKE_USDT,SHIB_USDT,MBOX_USDT,NBS_USDT,SNT_USDT,DREP_USDT,NFT_USDT,AUCTION_USDT,BOSON_USDT,O3_USDT,NULS_USDT,OMG_USDT,PEARL_USDT,HAPI_USDT,STG_USDT,IDV_USDT,HORD_USDT,ZIL_USDT,SUPER_USDT,DENT_USDT,REN_USDT,RAI_USDT,ZEN_USDT,ALGO_USDT,BLZ_USDT,BOR_USDT,SC_USDT,HEGIC_USDT,MOB_USDT,DORA_USDT,FOR_USDT,FLOW_USDT,RARI_USDT,DYDX_USDT,ATLAS_USDT,GST_USDT,REEF_USDT,HT_USDT,XYO_USDT,CHESS_USDT,BAT_USDT,NYM_USDT,RAMP_USDT,USDC_USDT,ICP_USDT,EPK_USDT,EXRD_USDT,DOT_USDT,COOK_USDT,CKB_USDT,YGG_USDT,CRU_USDT,ANC_USDT,FIS_USDT,ALCX_USDT,HIGH_USDT,BEAM_USDT,BSW_USDT,STAR_USDT,ROSE_USDT,CNNS_USDT,BZRX_USDT,WOO_USDT,SAFEMOON_USDT,VTHO_USDT,OM_USDT,LAMB_USDT,CHZ_USDT,AIOZ_USDT,EDEN_USDT,POND_USDT,ATOM_USDT,UNFI_USDT,FORTH_USDT,MLN_USDT,NEO_USDT,MOVR_USDT,RLC_USDT,FXS_USDT,ENS_USDT,ATA_USDT,XPR_USDT,NEST_USDT,XLM_USDT,AUTO_USDT,SNX_USDT,OCN_USDT,RSR_USDT,MITH_USDT,KAR_USDT,INJ_USDT,PLA_USDT,CYS_USDT,WAXP_USDT,VOXEL_USDT,CRV_USDT,FITFI_USDT,WHALE_USDT,WRX_USDT,TIDAL_USDT,C98_USDT,HNT_USDT,TONCOIN_USDT,DOGGY_USDT,SYS_USDT,NPXS_USDT,CRO_USDT,LEMD_USDT,RAY_USDT,PERL_USDT,CQT_USDT,CFX_USDT,TOMO_USDT,ACA_USDT,SDN_USDT,OKT_USDT,WILD_USDT,BNX_USDT,TRU_USDT,RACA_USDT,SWEAT_USDT,ACH_USDT,AKRO_USDT,BTM_USDT,TKO_USDT,GT_USDT,OCEAN_USDT,WNCG_USDT,BSV_USDT,GHST_USDT,CELR_USDT,LINA_USDT,SAND_USDT,APE_USDT,WICC_USDT,FIDA_USDT,ADA_USDT,PROPS_USDT,METIS_USDT,KAVA_USDT,AERGO_USDT,CONV_USDT,TFUEL_USDT,FRONT_USDT,API3_USDT,FARM_USDT,AE_USDT,LRC_USDT,IOTA_USDT,RFOX_USDT,PHA_USDT,XCN_USDT,NAS_USDT,KEEP_USDT,VIDY_USDT,HOT_USDT,MINA_USDT,ETHW_USDT,ALICE_USDT,HAI_USDT,BTC_USDT,LTC_USDT,LTO_USDT,DC_USDT,NU_USDT,IOST_USDT,RAD_USDT,POLS_USDT,OP_USDT,WXT_USDT,STR_USDT,YIELD_USDT,GM_USDT,SPA_USDT,BTCST_USDT,WEMIX_USDT,CLV_USDT,ICX_USDT,PET_USDT,STARL_USDT,HBAR_USDT,REDTOKEN_USDT,BTT_USDT,LINK_USDT,TLM_USDT,ARES_USDT,GTC_USDT,SUSHI_USDT,KEY_USDT,ALN_USDT,KDA_USDT,DVI_USDT,SXP_USDT,MAPS_USDT,BCD_USDT,SRM_USDT,WIN_USDT,ZEC_USDT,JASMY_USDT" + }, + "margin": { + "enabled": "BTC_USDT,ERN_USDT,T_USDT,CEEK_USDT,OGN_USDT,QNT_USDT,WOZX_USDT,ZEE_USDT,FUN_USDT,FLM_USDT,BOND_USDT", + "available": "BTC_USDT,ERN_USDT,T_USDT,CEEK_USDT,OGN_USDT,QNT_USDT,WOZX_USDT,ZEE_USDT,FUN_USDT,FLM_USDT,BOND_USDT,TARA_USDT,TRX_USDT,OXY_USDT,LON_USDT,DOGE_USDT,ISP_USDT,TWT_USDT,BAO_USDT,QUACK_USDT,ANT_USDT,VGX_USDT,ARPA_USDT,QUICK_USDT,UTK_USDT,HERO_USDT,WSG_USDT,BICO_USDT,MTV_USDT,VET_USDT,GARI_USDT,BCH_USDT,KLAY_USDT,WING_USDT,BLOK_USDT,SPS_USDT,WIKEN_USDT,WSIENNA_USDT,PUNDIX_USDT,FIC_USDT,ASTR_USDT,FET_USDT,VELO_USDT,BENQI_USDT,CWEB_USDT,RIF_USDT,UNI_USDT,ONG_USDT,ERG_USDT,ALPHA_USDT,CELO_USDT,XVG_USDT,GMAT_USDT,BTS_USDT,DOCK_USDT,GMT_USDT,DIA_USDT,CSPR_USDT,NKN_USDT,STAKE_USDT,SWASH_USDT,XEC_USDT,SWRV_USDT,QRDO_USDT,BLES_USDT,EOS_USDT,GRT_USDT,ASM_USDT,FIL6_USDT,GNO_USDT,EGLD_USDT,XYM_USDT,LOOKS_USDT,LOKA_USDT,BNC_USDT,BAS_USDT,SKL_USDT,STMX_USDT,CVC_USDT,DDOS_USDT,COTI_USDT,AVA_USDT,HMT_USDT,DF_USDT,LPT_USDT,XRP_USDT,TVK_USDT,FEVR_USDT,MBL_USDT,KIN_USDT,SPELL_USDT,MATIC_USDT,FTT_USDT,NMR_USDT,PMON_USDT,BNB_USDT,USDD_USDT,LSS_USDT,MDX_USDT,PRQ_USDT,ALPINE_USDT,DEGO_USDT,OMI_USDT,TIPS_USDT,OCT_USDT,FEI_USDT,UMEE_USDT,CRP_USDT,LION_USDT,YFI_USDT,DASH_USDT,REQ_USDT,SDAO_USDT,PNT_USDT,INSUR_USDT,OOKI_USDT,SUN_USDT,CRPT_USDT,BAC_USDT,DATA_USDT,LRN_USDT,JGN_USDT,KIMCHI_USDT,SUKU_USDT,VRA_USDT,AAVE_USDT,FTI_USDT,LDO_USDT,FRA_USDT,BLANK_USDT,NEAR_USDT,ZKS_USDT,MTRG_USDT,RLY_USDT,TCT_USDT,FLY_USDT,JST_USDT,YFII_USDT,AR_USDT,POLY_USDT,JULD_USDT,SOL_USDT,BZZ_USDT,AXS_USDT,ASD_USDT,XMR_USDT,FTM_USDT,HIT_USDT,LEO_USDT,LIT_USDT,PIG_USDT,COMP_USDT,ELON_USDT,IMX_USDT,EFI_USDT,XVS_USDT,WAVES_USDT,PEOPLE_USDT,SOS_USDT,RUNE_USDT,POLC_USDT,SCLP_USDT,BABYDOGE_USDT,KONO_USDT,SPI_USDT,ETC_USDT,MDA_USDT,MTL_USDT,BCHA_USDT,KISHU_USDT,SUNNY_USDT,PYR_USDT,XTZ_USDT,TRIBE_USDT,AUDIO_USDT,FIRO_USDT,MANA_USDT,OKB_USDT,DOG_USDT,SLP_USDT,KNC_USDT,GAS_USDT,LUNA_USDT,SAFEMARS_USDT,MIR_USDT,DAR_USDT,EGS_USDT,KSM_USDT,ATP_USDT,BIT_USDT,STORJ_USDT,XEM_USDT,QTUM_USDT,AGLD_USDT,RVN_USDT,OXT_USDT,SHFT_USDT,IOTX_USDT,LUNC_USDT,NEXO_USDT,AKITA_USDT,PERP_USDT,ONE_USDT,ETH_USDT,FLUX_USDT,FLOKI_USDT,STX_USDT,ANML_USDT,XPRT_USDT,GALA_USDT,GXS_USDT,TORN_USDT,KAI_USDT,1INCH_USDT,CHR_USDT,GAL_USDT,GLMR_USDT,CTX_USDT,CERE_USDT,CART_USDT,STRAX_USDT,MASK_USDT,MKR_USDT,AVAX_USDT,ENJ_USDT,YAM_USDT,ALPACA_USDT,DODO_USDT,MFT_USDT,CAKE_USDT,RNDR_USDT,CTSI_USDT,GRIN_USDT,MXC_USDT,ONT_USDT,ANKR_USDT,SLIM_USDT,FIL_USDT,CTK_USDT,ASR_USDT,FEG_USDT,SERO_USDT,RSS3_USDT,IRIS_USDT,XCH_USDT,ZRX_USDT,BAND_USDT,BADGER_USDT,DAO_USDT,EPS_USDT,THETA_USDT,BAKE_USDT,SHIB_USDT,MBOX_USDT,NBS_USDT,SNT_USDT,DREP_USDT,NFT_USDT,AUCTION_USDT,BOSON_USDT,O3_USDT,NULS_USDT,OMG_USDT,PEARL_USDT,HAPI_USDT,STG_USDT,IDV_USDT,HORD_USDT,ZIL_USDT,SUPER_USDT,DENT_USDT,REN_USDT,RAI_USDT,ZEN_USDT,ALGO_USDT,BLZ_USDT,BOR_USDT,SC_USDT,HEGIC_USDT,MOB_USDT,DORA_USDT,FOR_USDT,FLOW_USDT,RARI_USDT,DYDX_USDT,ATLAS_USDT,GST_USDT,REEF_USDT,HT_USDT,XYO_USDT,CHESS_USDT,BAT_USDT,NYM_USDT,RAMP_USDT,USDC_USDT,ICP_USDT,EPK_USDT,EXRD_USDT,DOT_USDT,COOK_USDT,CKB_USDT,YGG_USDT,CRU_USDT,ANC_USDT,FIS_USDT,ALCX_USDT,HIGH_USDT,BEAM_USDT,BSW_USDT,STAR_USDT,ROSE_USDT,CNNS_USDT,BZRX_USDT,WOO_USDT,SAFEMOON_USDT,VTHO_USDT,OM_USDT,LAMB_USDT,CHZ_USDT,AIOZ_USDT,EDEN_USDT,POND_USDT,ATOM_USDT,UNFI_USDT,FORTH_USDT,MLN_USDT,NEO_USDT,MOVR_USDT,RLC_USDT,FXS_USDT,ENS_USDT,ATA_USDT,XPR_USDT,NEST_USDT,XLM_USDT,AUTO_USDT,SNX_USDT,OCN_USDT,RSR_USDT,MITH_USDT,KAR_USDT,INJ_USDT,PLA_USDT,CYS_USDT,WAXP_USDT,VOXEL_USDT,CRV_USDT,FITFI_USDT,WHALE_USDT,WRX_USDT,TIDAL_USDT,C98_USDT,HNT_USDT,TONCOIN_USDT,DOGGY_USDT,SYS_USDT,NPXS_USDT,CRO_USDT,LEMD_USDT,RAY_USDT,PERL_USDT,CQT_USDT,CFX_USDT,TOMO_USDT,ACA_USDT,SDN_USDT,OKT_USDT,WILD_USDT,BNX_USDT,TRU_USDT,RACA_USDT,SWEAT_USDT,ACH_USDT,AKRO_USDT,BTM_USDT,TKO_USDT,GT_USDT,OCEAN_USDT,WNCG_USDT,BSV_USDT,GHST_USDT,CELR_USDT,LINA_USDT,SAND_USDT,APE_USDT,WICC_USDT,FIDA_USDT,ADA_USDT,PROPS_USDT,METIS_USDT,KAVA_USDT,AERGO_USDT,CONV_USDT,TFUEL_USDT,FRONT_USDT,API3_USDT,FARM_USDT,AE_USDT,LRC_USDT,IOTA_USDT,RFOX_USDT,PHA_USDT,XCN_USDT,NAS_USDT,KEEP_USDT,VIDY_USDT,HOT_USDT,MINA_USDT,ETHW_USDT,ALICE_USDT,HAI_USDT,LTC_USDT,LTO_USDT,DC_USDT,NU_USDT,IOST_USDT,RAD_USDT,POLS_USDT,OP_USDT,WXT_USDT,STR_USDT,YIELD_USDT,GM_USDT,SPA_USDT,BTCST_USDT,WEMIX_USDT,CLV_USDT,ICX_USDT,PET_USDT,STARL_USDT,HBAR_USDT,REDTOKEN_USDT,BTT_USDT,LINK_USDT,TLM_USDT,ARES_USDT,GTC_USDT,SUSHI_USDT,KEY_USDT,ALN_USDT,KDA_USDT,DVI_USDT,SXP_USDT,MAPS_USDT,BCD_USDT,SRM_USDT,WIN_USDT,ZEC_USDT,JASMY_USDT" + }, + "delivery": { + "enabled": "BTC_USD_20230331,BTC_USD_20221230,BTC_USDT_20221021,BTC_USDT_20221014", + "available": "BTC_USD_20221021,BTC_USD_20221014,BTC_USD_20230331,BTC_USD_20221230,BTC_USDT_20221021,BTC_USDT_20221014,BTC_USDT_20230331,BTC_USDT_20221230" + } + } + }, + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "tickerBatching": true, + "autoPairUpdates": true + }, + "websocketAPI": true, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": true + } + }, + "bankAccounts": [ + { + "enabled": false, + "bankName": "", + "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ] + }, + { + "name": "Gemini", + "enabled": true, + "verbose": false, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, + "websocketOrderbookBufferLimit": 5, + "baseCurrencies": "USD", + "currencyPairs": { + "requestFormat": { + "uppercase": true + }, + "configFormat": { + "uppercase": true + }, + "useGlobalFormat": true, + "assetTypes": [ + "spot" + ], + "pairs": { + "spot": { + "enabled": "BTCUSD", + "available": "BTCUSD,ETHBTC,ETHUSD,BCHUSD,BCHBTC,BCHETH,LTCUSD,LTCBTC,LTCETH,LTCBCH,ZECUSD,ZECBTC,ZECETH,ZECBCH,ZECLTC" + } + } + }, + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "autoPairUpdates": true + }, + "websocketAPI": true, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": false + } + }, + "bankAccounts": [ + { + "enabled": false, + "bankName": "", + "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ] + }, + { + "name": "HitBTC", + "enabled": true, + "verbose": false, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, + "websocketOrderbookBufferLimit": 5, + "baseCurrencies": "USD", + "currencyPairs": { + "requestFormat": { + "uppercase": true + }, + "configFormat": { + "uppercase": true, + "delimiter": "-" + }, + "useGlobalFormat": true, + "assetTypes": [ + "spot" + ], + "pairs": { + "spot": { + "enabled": "BTC-USD", + "available": "BCN-BTC,BTC-USD,DASH-BTC,DOGE-BTC,DOGE-USD,EMC-BTC,ETH-BTC,LSK-BTC,LTC-BTC,LTC-USD,NXT-BTC,SBD-BTC,SC-BTC,STEEM-BTC,XDN-BTC,XEM-BTC,XMR-BTC,ARDR-BTC,ZEC-BTC,WAVES-BTC,MAID-BTC,DGD-BTC,SNGLS-BTC,1ST-BTC,TRST-BTC,TIME-BTC,GNO-BTC,REP-BTC,XMR-USD,DASH-USD,ETH-USD,NXT-USD,ZRC-BTC,BOS-BTC,DCT-BTC,ANT-BTC,AEON-BTC,GUP-BTC,PLU-BTC,LUN-BTC,EDG-BTC,RLC-BTC,SWT-BTC,TKN-BTC,WINGS-BTC,XAUR-BTC,AE-BTC,PTOY-BTC,ZEC-USD,XEM-USD,BCN-USD,XDN-USD,MAID-USD,ETC-BTC,ETC-USD,PLBT-BTC,BNT-BTC,SNT-ETH,CVC-USD,PAY-ETH,OAX-ETH,OMG-ETH,BQX-ETH,XTZ-BTC,DICE-BTC,PTOY-ETH,1ST-ETH,XAUR-ETH,TIME-ETH,DICE-ETH,SWT-ETH,XMR-ETH,ETC-ETH,DASH-ETH,ZEC-ETH,PLU-ETH,GNO-ETH,XRP-BTC,STRAT-USD,STRAT-BTC,SNC-ETH,ADX-ETH,BET-ETH,EOS-ETH,DENT-ETH,SAN-ETH,EOS-BTC,EOS-USD,XTZ-ETH,XTZ-USD,MYB-ETH,SUR-ETH,IXT-ETH,PLR-ETH,TIX-ETH,PRO-ETH,AVT-ETH,EVX-USD,DLT-BTC,BNT-ETH,BNT-USD,MANA-USD,DNT-BTC,FYP-BTC,OPT-BTC,TNT-ETH,STX-BTC,STX-ETH,STX-USD,TNT-USD,TNT-BTC,ENG-ETH,XUC-USD,SNC-BTC,SNC-USD,OAX-USD,OAX-BTC,ZRX-BTC,ZRX-ETH,ZRX-USD,RVT-BTC,PPC-BTC,PPC-USD,QTUM-ETH,IGNIS-ETH,BMC-BTC,BMC-ETH,BMC-USD,CND-BTC,CND-ETH,CND-USD,CDT-ETH,CDT-USD,FUN-BTC,FUN-ETH,FUN-USD,HVN-BTC,HVN-ETH,POE-BTC,POE-ETH,AMB-USD,AMB-ETH,AMB-BTC,HPC-BTC,PPT-ETH,MTH-BTC,MTH-ETH,LRC-BTC,LRC-ETH,ICX-BTC,ICX-ETH,NEO-BTC,NEO-ETH,NEO-USD,CSNO-BTC,ICX-USD,IND-ETH,KICK-BTC,YOYOW-BTC,CDT-BTC,XVG-BTC,XVG-ETH,XVG-USD,DGB-BTC,DGB-ETH,DGB-USD,DCN-ETH,DCN-USD,VIBE-BTC,ENJ-BTC,ENJ-ETH,ENJ-USD,ZSC-BTC,ZSC-ETH,ZSC-USD,TRX-BTC,TRX-ETH,TRX-USD,ART-BTC,EVX-BTC,EVX-ETH,SUB-BTC,SUB-ETH,SUB-USD,WTC-BTC,BTM-BTC,BTM-ETH,BTM-USD,LIFE-BTC,VIB-BTC,VIB-ETH,VIB-USD,DRT-ETH,STU-USD,OMG-BTC,PAY-BTC,PPT-BTC,SNT-BTC,BTG-BTC,BTG-ETH,BTG-USD,SMART-BTC,SMART-ETH,SMART-USD,XUC-ETH,XUC-BTC,LA-ETH,EDO-BTC,EDO-ETH,EDO-USD,HGT-ETH,IXT-BTC,SCL-BTC,ETP-BTC,ETP-ETH,ETP-USD,NEBL-BTC,NEBL-ETH,ARN-BTC,ARN-ETH,STU-BTC,STU-ETH,GVT-ETH,BTX-BTC,LTC-ETH,BCN-ETH,MAID-ETH,NXT-ETH,STRAT-ETH,XDN-ETH,XEM-ETH,PLR-BTC,SUR-BTC,BQX-BTC,DOGE-ETH,AMM-BTC,AMM-ETH,AMM-USD,DBIX-BTC,PRE-BTC,ZAP-BTC,DOV-BTC,DOV-ETH,XRP-ETH,XRP-USD,HSR-BTC,LEND-BTC,LEND-ETH,SPF-ETH,SBTC-BTC,SBTC-ETH,LOC-BTC,LOC-ETH,LOC-USD,SWFTC-BTC,SWFTC-ETH,SWFTC-USD,STAR-ETH,SBTC-USD,STORM-BTC,DIM-ETH,DIM-USD,DIM-BTC,NGC-BTC,NGC-ETH,NGC-USD,EMC-ETH,EMC-USD,MCO-BTC,MCO-ETH,MCO-USD,MANA-ETH,MANA-BTC,CPAY-ETH,DATA-BTC,DATA-ETH,DATA-USD,UTT-BTC,UTT-ETH,UTT-USD,KMD-BTC,KMD-ETH,KMD-USD,QTUM-USD,QTUM-BTC,SNT-USD,OMG-USD,EKO-BTC,EKO-ETH,ADX-BTC,ADX-USD,LSK-ETH,LSK-USD,PLR-USD,SUR-USD,BQX-USD,DRT-USD,REP-ETH,REP-USD,WAXP-BTC,WAXP-ETH,WAXP-USD,C20-BTC,C20-ETH,IDH-BTC,IDH-ETH,IPL-BTC,COV-BTC,COV-ETH,SENT-BTC,SENT-ETH,SENT-USD,SMT-BTC,SMT-ETH,SMT-USD,CHAT-BTC,CHAT-ETH,CHAT-USD,TRAC-ETH,JNT-ETH,UTK-BTC,UTK-ETH,UTK-USD,GNX-ETH,CHSB-BTC,CHSB-ETH,DAY-BTC,DAY-ETH,DAY-USD,NEU-BTC,NEU-ETH,NEU-USD,TAU-BTC,FLP-BTC,FLP-ETH,FLP-USD,R-BTC,R-ETH,EKO-USD,BCPT-ETH,BCPT-USD,PKT-BTC,PKT-ETH,BETR-BTC,BETR-ETH,HAND-ETH,HAND-USD,CHP-ETH,BCPT-BTC,ACT-BTC,ACT-ETH,ACT-USD,ADA-BTC,ADA-ETH,ADA-USD,SIG-BTC,MTX-BTC,MTX-ETH,MTX-USD,WIZ-BTC,WIZ-ETH,WIZ-USD,DADI-BTC,DADI-ETH,BDG-ETH,DATX-BTC,DATX-ETH,TRUE-BTC,DRG-BTC,DRG-ETH,BANCA-BTC,BANCA-ETH,ZAP-ETH,ZAP-USD,AUTO-BTC,SOC-BTC,OCN-BTC,OCN-ETH,STQ-BTC,STQ-ETH,XLM-BTC,XLM-ETH,XLM-USD,IOTA-BTC,IOTA-ETH,IOTA-USD,DRT-BTC,BETR-USD,ERT-BTC,CRPT-BTC,CRPT-USD,MESH-BTC,MESH-ETH,MESH-USD,IHT-BTC,IHT-ETH,IHT-USD,SCC-BTC,YCC-BTC,DAN-BTC,TEL-BTC,TEL-ETH,NCT-BTC,NCT-ETH,NCT-USD,BMH-BTC,BANCA-USD,BERRY-BTC,BERRY-ETH,BERRY-USD,GBX-BTC,GBX-ETH,GBX-USD,SHIP-BTC,SHIP-ETH,NANO-BTC,NANO-ETH,NANO-USD,LNC-BTC,KIN-ETH,ARDR-USD,FOTA-ETH,FOTA-BTC,CVT-BTC,CVT-ETH,CVT-USD,STQ-USD,GNT-BTC,GNT-ETH,GNT-USD,GET-BTC,MITH-BTC,MITH-ETH,MITH-USD,DADI-USD,TKY-BTC,ACAT-BTC,ACAT-ETH,ACAT-USD,BTX-USD,WIKI-BTC,WIKI-ETH,WIKI-USD,ONT-BTC,ONT-ETH,ONT-USD,FTX-BTC,FTX-ETH,NAVI-BTC,VME-ETH,NAVI-ETH,LND-ETH,CSM-BTC,NANJ-BTC,NTK-BTC,NTK-ETH,NTK-USD,AUC-BTC,AUC-ETH,CMCT-BTC,CMCT-ETH,CMCT-USD,MAN-BTC,MAN-ETH,MAN-USD,PNT-BTC,PNT-ETH,FXT-BTC,NEXO-BTC,PAT-BTC,PAT-ETH,XMC-BTC,FXT-ETH,XMC-ETH,XMC-USD,FDZ-BTC,FDZ-ETH,FDZ-USD,SPD-BTC,SPD-ETH,MITX-BTC,TIV-BTC,B2G-BTC,B2G-USD,HBZ-BTC,FACE-BTC,FACE-ETH,HBZ-ETH,HBZ-USD,CPT-BTC,PAT-USD,HTML-BTC,HTML-ETH,MITX-ETH,BTS-BTC,BNK-BTC,BNK-ETH,BNK-USD,TIV-ETH,TIV-USD,CSM-ETH,CSM-USD,INK-BTC,IOST-BTC,INK-ETH,INK-USD,CBC-BTC,IOST-USD,ZIL-BTC,ABYSS-BTC,ABYSS-ETH,ZIL-USD,BCI-BTC,CBC-ETH,CBC-USD,PITCH-BTC,PITCH-ETH,HTML-USD,TDS-BTC,TDS-ETH,TDS-USD,SBD-ETH,SBD-USD,DPN-BTC,UUU-BTC,UUU-ETH,XBP-BTC,ELEC-BTC,ELEC-ETH,ELEC-USD,QNTU-BTC,QNTU-ETH,QNTU-USD,IPL-ETH,IPL-USD,CENNZ-BTC,CENNZ-ETH,SWM-BTC,SPF-USD,SPF-BTC,LCC-BTC,HGT-BTC,ETH-TUSD,BTC-TUSD,LTC-TUSD,XMR-TUSD,ZRX-TUSD,NEO-TUSD,USD-TUSD,BTC-DAI,ETH-DAI,MKR-DAI,EOS-DAI,USD-DAI,MKR-BTC,MKR-ETH,MKR-USD,TUSD-DAI,NEO-DAI,LTC-DAI,XMR-DAI,XRP-DAI,NEXO-ETH,NEXO-USD,DWS-BTC,DWS-ETH,DWS-USD,APPC-BTC,APPC-ETH,APPC-USD,BIT-ETH,SPC-BTC,SPC-ETH,SPC-USD,REX-BTC,REX-ETH,REX-USD,ELF-BTC,ELF-USD,BCD-BTC,BCD-USD,CVCOIN-BTC,CVCOIN-ETH,CVCOIN-USD,EDG-ETH,EDG-USD,NLC2-BTC,DASH-EURS,ZEC-EURS,BTC-EURS,EOS-EURS,ETH-EURS,LTC-EURS,NEO-EURS,XMR-EURS,XRP-EURS,EURS-USD,EURS-TUSD,EURS-DAI,MNX-USD,ROX-ETH,ZPR-ETH,MNX-BTC,MNX-ETH,KIND-BTC,KIND-ETH,ENGT-BTC,ENGT-ETH,PMA-BTC,PMA-ETH,TV-BTC,TV-ETH,TV-USD,BAT-BTC,BAT-ETH,BAT-USD,SRN-BTC,SRN-ETH,SRN-USD,SVD-BTC,SVD-ETH,SVD-USD,GST-BTC,GST-ETH,GST-USD,BNB-BTC,BNB-ETH,BNB-USD,DIT-BTC,DIT-ETH,POA20-BTC,PROC-BTC,POA20-ETH,POA20-USD,POA20-DAI,NIM-BTC,USE-BTC,USE-ETH,DAV-BTC,DAV-ETH,ABTC-BTC,NIM-ETH,ABA-BTC,ABA-ETH,ABA-USD,BCN-EOS,LTC-EOS,XMR-EOS,DASH-EOS,TRX-EOS,NEO-EOS,ZEC-EOS,LSK-EOS,XEM-EOS,XRP-EOS,RCN-BTC,RCN-ETH,RCN-USD,HMQ-BTC,HMQ-ETH,MYST-BTC,MYST-ETH,USD-GUSD,BTC-GUSD,ETH-GUSD,EOS-GUSD,AXPR-BTC,AXPR-ETH,DAG-BTC,DAG-ETH,BITS-BTC,BITS-ETH,BITS-USD,CDCC-BTC,CDCC-ETH,CDCC-USD,VET-BTC,VET-ETH,VET-USD,SILK-ETH,BOX-BTC,BOX-ETH,BOX-EURS,BOX-EOS,VOCO-BTC,VOCO-ETH,VOCO-USD,PASS-BTC,PASS-ETH,SLX-BTC,SLX-USD,PBTT-BTC,PMA-USD,TRAD-BTC,DGTX-BTC,DGTX-ETH,DGTX-USD,MRK-BTC,MRK-ETH,DGB-TUSD,SNBL-BTC,BCH-BTC,BCH-USD,BSV-BTC,BSV-USD,BKX-BTC,NPLC-BTC,NPLC-ETH,ETN-BTC,ETN-ETH,ETN-USD,DTR-BTC,DTR-ETH,TDP-BTC,HBT-ETH,PXG-BTC,PXG-USD,BTC-PAX,ETH-PAX,USD-PAX,BTC-USDC,ETH-USDC,USD-USDC,TUSD-USDC,DAI-USDC,EOS-PAX,CLO-BTC,CLO-ETH,CLO-USD,PETH-BTC,PETH-ETH,PETH-USD,BRD-BTC,BRD-ETH,NMR-BTC,SALT-BTC,SALT-ETH,POLY-BTC,POLY-ETH,POWR-BTC,POWR-ETH,STORJ-BTC,STORJ-ETH,STORJ-USD,MLN-BTC,MLN-ETH,BDG-BTC,POA-ETH,POA-BTC,POA-USD,POA-DAI,KIN-BTC,VEO-BTC,PLA-BTC,PLA-ETH,PLA-USD,BTT-BTC,BTT-USD,BTT-ETH,ZEN-BTC,ZEN-ETH,ZEN-USD,GRIN-BTC,GRIN-ETH,GRIN-USD,FET-BTC,HT-BTC,HT-USD,XZC-BTC,XZC-ETH,XZC-USD,VRA-BTC,VRA-ETH,BTC-KRWB,USD-KRWB,WBTC-ETH,CRO-BTC,CRO-ETH,CRO-USD,GAS-BTC,GAS-ETH,GAS-USD,ORMEUS-BTC,ORMEUS-ETH,SWM-ETH,SWM-USD,PRE-ETH,PHX-BTC,PHX-ETH,PHX-USD,BET-BTC,USD-EOSDT,BTC-EOSDT,ETH-EOSDT,EOS-EOSDT,DAI-EOSDT,NUT-BTC,NUT-EOS,NUT-USD,CUTE-BTC,CUTE-ETH,CUTE-USD,CUTE-EOS,XCON-BTC,DCR-BTC,DCR-ETH,DCR-USD,MG-BTC,MG-ETH,MG-EOS,MG-USD,GNX-BTC,PRO-BTC,EURS-EOSDT,TUSD-EOSDT,ECOIN-BTC,ECOIN-ETH,ECOIN-USD,AGI-BTC,LOOM-BTC,LOOM-ETH,BLZ-BTC,QKC-BTC,QKC-ETH,KNC-BTC,KNC-ETH,KNC-USD,KEY-BTC,KEY-ETH,ATOM-BTC,ATOM-USD,ATOM-ETH,BRDG-BTC,BRDG-ETH,BRDG-USD,MTL-BTC,MTL-ETH,EXP-BTC,BTCB-BTC,PBT-BTC,PBT-ETH,LINK-BTC,LINK-ETH,LINK-USD,USD-USDT20,PHB-BTC,BCH-ETH,BCH-DAI,BCH-TUSD,BCH-EURS,DAPP-BTC,DAPP-EOS,BTC-USDT20,DENT-BTC,DENT-USD,NJBC-BTC,NJBC-ETH,XRC-BTC,EOS-BCH,LTC-BCH,XRP-BCH,TRX-BCH,XLM-BCH,ETC-BCH,DASH-BCH,ZEC-BCH,BKX-USD,LAMB-BTC,NPXS-BTC,HBAR-BTC,HBAR-USD,ONE-BTC,RFR-BTC,RFR-USD,BUSD-USD,PAXG-BTC,PAXG-USD,REN-BTC,IGNIS-BTC,CEL-BTC,CEL-ETH,WIN-USD,ADK-BTC,PART-BTC,SOZ-BTC,SOZ-ETH,SOZ-USD,WAVES-USD,ADA-BCH,ONT-BCH,XMR-BCH,ATOM-BCH,LINK-BCH,OMG-BCH,WAVES-BCH,IOTX-BTC,HOT-BTC,SLV-BTC,HEDG-BTC,CHZ-BTC,CHZ-USD,COCOS-BTC,COCOS-USD,SEELE-BTC,SEELE-USD,MDA-BTC,LEO-USD,REM-BTC,REM-ETH,REM-USD,SCD-DAI,BTC-BUSD,RVN-BTC,BST-BTC,ERD-BTC,KRL-BTC,FTT-BTC,FTT-USD,RAISE-BTC,RAISE-ETH" + } + } + }, + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "tickerBatching": true, + "autoPairUpdates": true + }, + "websocketAPI": true, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": false + } + }, + "bankAccounts": [ + { + "enabled": false, + "bankName": "", + "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ] + }, + { + "name": "Huobi", + "enabled": true, + "verbose": false, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, + "websocketOrderbookBufferLimit": 5, + "baseCurrencies": "USD", + "currencyPairs": { + "requestFormat": { + "uppercase": false + }, + "configFormat": { + "uppercase": true, + "delimiter": "-" + }, + "useGlobalFormat": true, + "assetTypes": [ + "spot", + "coinmarginedfutures", + "futures" + ], + "pairs": { + "coinmarginedfutures": { + "assetEnabled": true, + "enabled": "BTC-USD", + "available": "BTC-USD,ETH-USD,LINK-USD,DOT-USD,ADA-USD,LTC-USD,XRP-USD,TRX-USD,DOGE-USD", + "requestFormat": { + "uppercase": true, + "delimiter": "-" + }, + "configFormat": { + "uppercase": true, + "delimiter": "-" + } + }, + "futures": { + "assetEnabled": true, + "enabled": "BTC-230915", + "available": "BTC-230915,BTC-230922,BTC-230929,ETH-230915,ETH-230922,ETH-230929,TRX-230915,TRX-230922", + "requestFormat": { + "uppercase": true + }, + "configFormat": { + "uppercase": true, + "delimiter": "-" + } + }, + "spot": { + "enabled": "BTC-USDT", + "available": "PROPY-ETH,IOTA-BTC,UGAS-ETH,PAI-USDT,BSV-HUSD,MTX-ETH,BCH-BTC,LTC-HT,SOC-USDT,WXT-BTC,SALT-BTC,RCN-ETH,PNT-ETH,TT-USDT,AIDOC-ETH,BIX-BTC,OCN-USDT,QTUM-ETH,KCASH-ETH,SNT-USDT,LUN-BTC,QASH-BTC,ITC-BTC,NAS-BTC,XMR-BTC,TNT-ETH,UC-ETH,FAIR-BTC,PC-ETH,YEE-BTC,PAY-ETH,XMX-BTC,CRE-USDT,BAT-ETH,BHT-USDT,CKB-HT,LAMB-HT,AE-USDT,QUN-ETH,LYM-BTC,BCH-HT,BHT-BTC,RUFF-ETH,CNN-BTC,FOR-USDT,GTC-ETH,TRX-ETH,ELA-USDT,ACT-ETH,SMT-ETH,BUT-ETH,BCH-USDT,ICX-BTC,MEET-BTC,NCC-BTC,APPC-BTC,GVE-ETH,TNB-BTC,STEEM-ETH,18C-ETH,LBA-BTC,EKO-BTC,REQ-BTC,SOC-BTC,BOX-ETH,ELF-BTC,ZRX-ETH,LET-USDT,HT-BTC,TUSD-HUSD,EGCC-BTC,WTC-BTC,ATP-USDT,DOCK-USDT,PAI-BTC,ONT-ETH,IRIS-BTC,BTT-ETH,SC-BTC,XZC-BTC,LBA-USDT,HT-USDT,VET-ETH,KMD-ETH,SHE-ETH,PORTAL-BTC,ONE-BTC,BIX-USDT,RCCC-BTC,SKM-USDT,XTZ-ETH,SWFTC-BTC,RSR-BTC,LINK-ETH,DATX-BTC,HPT-HT,GET-ETH,BLZ-ETH,CTXC-USDT,CNNS-USDT,PVT-HT,ITC-USDT,LTC-BTC,NCASH-BTC,HOT-ETH,ADA-USDT,ADX-BTC,NODE-USDT,TRIO-BTC,GXC-ETH,SNT-BTC,FOR-BTC,DBC-BTC,UUU-USDT,CVCOIN-ETH,RSR-USDT,CRO-USDT,OCN-BTC,NEW-USDT,EGT-USDT,MANA-BTC,CMT-USDT,WXT-HT,XRP-BTC,MT-ETH,PAX-HUSD,LSK-ETH,IOTA-USDT,SRN-ETH,ZIL-ETH,ELF-USDT,LXT-ETH,LAMB-BTC,CRE-HT,CKB-BTC,XVG-BTC,BSV-BTC,BFT-BTC,WPR-ETH,HT-HUSD,POWR-BTC,MANA-ETH,ENG-ETH,ZJLT-ETH,SNC-ETH,ATOM-ETH,WICC-USDT,KAN-ETH,DGD-BTC,VSYS-HT,BCD-BTC,BTM-ETH,DOGE-USDT,MEX-BTC,BTG-BTC,DAC-ETH,DAT-BTC,GRS-ETH,ADX-ETH,EM-HT,GXC-USDT,CVC-BTC,OMG-ETH,SSP-ETH,OGO-HT,CMT-ETH,POLY-ETH,XZC-USDT,THETA-USDT,XEM-USDT,LOL-USDT,BCH-HUSD,GSC-BTC,DOGE-ETH,MDS-BTC,BTS-ETH,CTXC-BTC,MCO-BTC,BCX-BTC,ZLA-ETH,EKT-USDT,MAN-BTC,BLZ-BTC,ATOM-USDT,LOL-BTC,HPT-USDT,EM-BTC,EOS-USDT,WAN-BTC,GNT-BTC,CRO-BTC,MANA-USDT,SEELE-USDT,FSN-BTC,VIDY-HT,USDC-HUSD,LTC-HUSD,XRP-USDT,VSYS-BTC,STORJ-BTC,LOOM-ETH,SKM-BTC,LINK-USDT,TT-HT,QSP-ETH,ETN-BTC,FSN-HT,NODE-BTC,HC-USDT,PHX-BTC,XLM-BTC,RCCC-ETH,LTC-USDT,UUU-BTC,SEELE-ETH,PVT-BTC,HC-ETH,REN-ETH,KAN-USDT,EOS-ETH,BSV-USDT,BTS-USDT,KMD-BTC,OGO-USDT,THETA-ETH,MUSK-BTC,CNNS-HT,ETC-BTC,COVA-BTC,BTT-TRX,XMR-USDT,MTN-ETH,QUN-BTC,NAS-USDT,ELA-ETH,HIT-ETH,BTT-USDT,EKT-ETH,TOS-BTC,GAS-ETH,DCR-USDT,ONT-BTC,NEW-HT,NEXO-BTC,ETH-USDT,WXT-USDT,FOR-HT,ADA-BTC,EVX-ETH,VET-BTC,ZEC-USDT,NANO-ETH,IOST-HT,BCV-ETH,REN-USDT,NULS-ETH,ACT-USDT,LET-ETH,BTM-USDT,MEET-ETH,AKRO-HT,ARDR-BTC,DCR-ETH,NANO-USDT,BTC-HUSD,ALGO-BTC,IIC-ETH,BHD-BTC,KNC-ETH,ATP-BTC,ZRX-BTC,ABT-BTC,18C-BTC,XMR-ETH,WAXP-BTC,CVNT-BTC,MX-USDT,OST-ETH,NKN-BTC,TOPC-BTC,GNX-BTC,FTT-USDT,ONE-HT,DGB-ETH,NULS-USDT,DASH-BTC,UIP-BTC,KCASH-HT,WICC-ETH,EKO-ETH,EGT-HT,IRIS-USDT,STK-ETH,MXC-BTC,NAS-ETH,OMG-USDT,SMT-BTC,BUT-BTC,HIT-USDT,BAT-BTC,IRIS-ETH,NKN-HT,PC-BTC,TOP-USDT,GTC-BTC,LSK-BTC,ITC-ETH,DTA-BTC,HOT-BTC,BTT-BTC,FAIR-ETH,DOCK-ETH,QTUM-BTC,ZEN-BTC,ZIL-BTC,RCN-BTC,FTI-BTC,BHD-USDT,VIDY-USDT,LUN-ETH,DBC-ETH,TOPC-ETH,IIC-BTC,STEEM-USDT,IOTA-ETH,KCASH-BTC,RUFF-BTC,APPC-ETH,MT-BTC,SOC-ETH,GT-HT,PROPY-BTC,AIDOC-BTC,ACT-BTC,LYM-ETH,CHAT-BTC,SWFTC-ETH,ETH-BTC,UIP-USDT,UGAS-BTC,XRP-HUSD,ALGO-USDT,TNT-BTC,ONT-USDT,YEE-ETH,AKRO-BTC,TRX-USDT,OCN-ETH,SRN-BTC,DASH-USDT,XMX-ETH,NANO-BTC,QASH-ETH,EOS-HT,GT-BTC,XTZ-USDT,ARPA-USDT,SALT-ETH,BKBT-ETH,MTX-BTC,SMT-USDT,GXC-BTC,VIDY-BTC,FTT-HT,LAMB-ETH,TRX-BTC,TRIO-ETH,BFT-ETH,LINK-BTC,AE-ETH,NULS-BTC,BHD-HT,AST-ETH,NEO-USDT,EDU-BTC,CVCOIN-BTC,GVE-BTC,GET-BTC,ZRX-USDT,ELF-ETH,DATX-ETH,ADA-ETH,TOP-HT,NCASH-ETH,QTUM-USDT,ETC-HT,ZIL-USDT,TNB-ETH,BIX-ETH,SHE-BTC,PNT-BTC,BTC-USDT,PORTAL-ETH,WAVES-USDT,XZC-ETH,HT-ETH,POLY-BTC,MCO-ETH,MUSK-ETH,PAI-ETH,LXT-USDT,UTK-BTC,RTE-BTC,NCC-ETH,HB10-USDT,BOX-BTC,RDN-ETH,ARPA-BTC,LBA-ETH,CNN-ETH,AAC-ETH,XTZ-BTC,IDT-BTC,AKRO-USDT,IOST-BTC,GT-USDT,WAN-ETH,ETN-ETH,PVT-USDT,NEO-BTC,WAVES-ETH,ONE-USDT,ZEC-BTC,SKM-HT,IOST-ETH,NPXS-ETH,CVC-ETH,CMT-BTC,COVA-ETH,ARDR-ETH,RDN-BTC,DCR-BTC,REN-BTC,YCC-ETH,MX-HT,NEXO-ETH,XLM-ETH,YCC-BTC,ENG-BTC,CNNS-BTC,ZLA-BTC,QSP-BTC,MAN-ETH,UUU-ETH,ETH-HUSD,RTE-ETH,ATP-HT,BTM-BTC,DAC-BTC,TOS-ETH,LAMB-USDT,DASH-HT,NPXS-BTC,NEW-BTC,FTT-BTC,EOS-HUSD,GRS-BTC,POWR-ETH,VET-USDT,AAC-BTC,MX-BTC,MTN-BTC,XVG-ETH,GNX-ETH,SSP-BTC,WAVES-BTC,EGT-BTC,CTXC-ETH,IDT-ETH,STK-BTC,WICC-BTC,UTK-ETH,CRO-HT,LXT-BTC,GSC-ETH,OMG-BTC,XRP-HT,DGB-BTC,IOST-USDT,CVNT-ETH,GAS-BTC,HIT-BTC,CKB-USDT,ARPA-HT,RUFF-USDT,HC-BTC,WTC-ETH,MDS-USDT,ABT-ETH,ALGO-ETH,BIFI-BTC,KNC-BTC,TT-BTC,LET-BTC,NKN-USDT,PAY-BTC,DTA-USDT,AE-BTC,UC-BTC,VSYS-USDT,USDT-HUSD,EOS-BTC,STEEM-BTC,DOGE-BTC,NODE-HT,MDS-ETH,CRE-BTC,GNT-USDT,UIP-ETH,AST-BTC,XEM-BTC,ZEN-ETH,EDU-ETH,MEX-ETH,EKT-BTC,CVC-USDT,WAXP-ETH,REQ-ETH,OST-BTC,STORJ-USDT,SBTC-BTC,DGD-ETH,SC-ETH,WTC-USDT,THETA-BTC,DTA-ETH,BCV-BTC,SNC-BTC,RSR-HT,KAN-BTC,ELA-BTC,ATOM-BTC,BKBT-BTC,FSN-USDT,EM-USDT,WPR-BTC,TOP-BTC,BTS-BTC,EGCC-ETH,MTL-BTC,GNT-ETH,SEELE-BTC,EVX-BTC,FTI-ETH,BAT-USDT,MT-HT,LOL-HT,ICX-ETH,LOOM-BTC,ZJLT-BTC,XLM-USDT,OGO-BTC,DOCK-BTC,CHAT-ETH,DAT-ETH,ETC-USDT,HPT-BTC,BHT-HT" + } + } + }, + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "tickerBatching": true, + "autoPairUpdates": true + }, + "websocketAPI": true, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": false + } + }, + "bankAccounts": [ + { + "enabled": false, + "bankName": "", + "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ] + }, + { + "name": "Kraken", + "enabled": true, + "verbose": false, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, + "websocketOrderbookBufferLimit": 5, + "baseCurrencies": "EUR,USD,CAD,GBP,JPY", + "currencyPairs": { + "requestFormat": { + "uppercase": true, + "separator": "," + }, + "configFormat": { + "uppercase": true, + "delimiter": "-", + "separator": "," + }, + "useGlobalFormat": true, + "assetTypes": [ + "spot" + ], + "pairs": { + "futures": { + "assetEnabled": true, + "enabled": "PF_XBTUSD", + "available": "PI_XBTUSD,PI_ETHUSD,PI_LTCUSD,PI_BCHUSD,PI_XRPUSD,PF_XBTUSD,PF_ETHUSD,PF_ADAUSD,PF_XRPUSD,PF_SOLUSD,PF_UNIUSD,PF_ATOMUSD,PF_BCHUSD,PF_DOTUSD,PF_EOSUSD,PF_FILUSD,PF_LINKUSD,PF_LTCUSD,PF_MATICUSD,PF_DEFIUSD,PF_AVAXUSD,PF_XMRUSD,PF_GMTUSD,PF_LUNA2USD,PF_OPUSD,PF_NEARUSD,PF_APEUSD,PF_WAVESUSD,PF_DOGEUSD,PF_FTMUSD,PF_TRXUSD,PF_MANAUSD,PF_CRVUSD,PF_AAVEUSD,PF_SNXUSD,PF_XTZUSD,PF_XLMUSD,PF_ALGOUSD,PF_SANDUSD,PF_OMGUSD,PF_ENJUSD,PF_COMPUSD,PF_YFIUSD,PF_CHZUSD,PF_LPTUSD,PF_BATUSD,PF_MKRUSD,PF_AXSUSD,PF_GALAUSD,PF_ETCUSD,PF_KAVAUSD,PF_LRCUSD,PF_KSMUSD,PF_GRTUSD,PF_FLOWUSD,PF_ZECUSD,PF_QTUMUSD,PF_DASHUSD,PF_1INCHUSD,PF_KNCUSD,PF_OGNUSD,PF_SUSHIUSD,PF_STORJUSD,PF_RUNEUSD,PF_EGLDUSD,PF_DYDXUSD,PF_RENUSD,PF_ANKRUSD,PF_ICPUSD,PF_ETHWUSD,PF_OCEANUSD,PF_BANDUSD,PF_BALUSD,PF_ALICEUSD,PF_ICXUSD,PF_ENSUSD,PF_AUDIOUSD,PF_ANTUSD,PF_SCUSD,PF_MINAUSD,PF_GLMRUSD,PF_THETAUSD,PF_QNTUSD,PF_IMXUSD,PF_APTUSD,PF_FLRUSD,PF_BLURUSD,PF_GMXUSD,PF_MASKUSD,PF_LDOUSD,PF_USDCUSD,PF_USDTUSD,PF_ARBUSD,PF_FETUSD,PF_STXUSD,PF_RNDRUSD,PF_CVXUSD,PF_WOOUSD,PF_JASMYUSD,PF_INJUSD,PF_ZRXUSD,PF_RLCUSD,PF_GALUSD,PF_SUIUSD,PF_PEPEUSD,FI_XBTUSD_231229,FI_ETHUSD_231229,PF_SHIBUSD,PF_TUSDUSD,PF_SXPUSD,PF_ARPAUSD,PF_ALPHAUSD,PF_STGUSD,PF_HFTUSD,PF_ACHUSD,PF_WLDUSD,PF_MOONUSD,PF_LINAUSD,PF_CFXUSD,PF_PAXGUSD,PF_AGLDUSD,FF_ETHUSD_231229,FF_XBTUSD_231229,FI_ETHUSD_240329,FI_XBTUSD_240329,FI_XRPUSD_231229,FI_BCHUSD_231229,FI_LTCUSD_231229,PF_FXSUSD,PF_SEIUSD,FF_XBTUSD_231027,FF_ETHUSD_231027,FI_XBTUSD_231027,FI_ETHUSD_231027,FI_XRPUSD_231027,FI_BCHUSD_231027,FI_LTCUSD_231027", + "requestFormat": { + "uppercase": true, + "delimiter": "_" + }, + "configFormat": { + "uppercase": true, + "delimiter": "_" + } + }, + "spot": { + "enabled": "XBT-USD", + "available": "ETH-GBP,XRP-USD,DAI-EUR,LSK-USD,BAT-EUR,BCH-EUR,EOS-ETH,GNO-EUR,ETH-CAD,XRP-JPY,ADA-ETH,DAI-USD,DASH-EUR,GNO-USD,LSK-XBT,ETH-EUR,ZEC-EUR,DASH-XBT,EOS-EUR,ETH-CHF,SC-ETH,SC-USD,WAVES-EUR,XBT-USD,ADA-EUR,LINK-USD,NANO-EUR,PAXG-USD,SC-EUR,WAVES-ETH,REP-USD,EOS-XBT,ETC-ETH,XMR-USD,LTC-USD,MLN-XBT,XTZ-CAD,XBT-GBP,ADA-CAD,XTZ-EUR,ETH-JPY,XTZ-USD,XDG-XBT,XLM-EUR,ATOM-USD,ATOM-XBT,OMG-EUR,ZEC-JPY,ADA-XBT,GNO-ETH,LINK-XBT,ETC-EUR,BCH-XBT,QTUM-ETH,XBT-CHF,LTC-EUR,ETH-DAI,LSK-EUR,NANO-USD,QTUM-XBT,XRP-XBT,ZEC-USD,BAT-ETH,LINK-ETH,XBT-CAD,BAT-USD,GNO-XBT,ICX-XBT,PAXG-ETH,DAI-USDT,NANO-ETH,OMG-ETH,WAVES-XBT,ZEC-XBT,BAT-XBT,NANO-XBT,XBT-JPY,DASH-USD,ICX-ETH,LSK-ETH,QTUM-CAD,REP-XBT,XMR-XBT,XRP-EUR,ATOM-CAD,OMG-USD,LTC-XBT,MLN-ETH,XTZ-ETH,EOS-USD,ICX-EUR,SC-XBT,ETC-USD,BCH-USD,ICX-USD,QTUM-USD,ETH-XBT,ETH-USD,OMG-XBT,PAXG-EUR,REP-EUR,ADA-USD,USDT-USD,XMR-EUR,XRP-CAD,ATOM-EUR,ETC-XBT,XBT-EUR,XLM-USD,ATOM-ETH,LINK-EUR,PAXG-XBT,WAVES-USD,REP-ETH,XLM-XBT,QTUM-EUR,XTZ-XBT" + } + } + }, + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true, + "requiresBase64DecodeSecret": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "tickerBatching": true, + "autoPairUpdates": true + }, + "websocketAPI": true, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": true + } + }, + "bankAccounts": [ + { + "enabled": false, + "bankName": "", + "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ] + }, + { + "name": "Kucoin", + "enabled": true, + "verbose": false, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, + "websocketOrderbookBufferLimit": 5, + "baseCurrencies": "USD", + "currencyPairs": { + "assetTypes": [ + "spot", + "margin", + "futures" + ], + "pairs": { + "spot": { + "assetEnabled": true, + "enabled": "BTC-USDT,ETH-BTC,ETH-USDT,LTC-USDT", + "available": "BTC-USDT,MHC-ETH,MHC-BTC,OXEN-BTC,OXEN-ETH,NRG-BTC,AVA-USDT,FET-BTC,FET-ETH,ANKR-BTC,MHC-USDT,XMR-BTC,XMR-ETH,RIF-BTC,MTV-BTC,MTV-ETH,CRO-BTC,MTV-USDT,KMD-BTC,KMD-USDT,RFOX-USDT,TEL-USDT,TT-USDT,AERGO-USDT,XMR-USDT,TRX-KCS,ATOM-BTC,ATOM-ETH,ATOM-USDT,ATOM-KCS,ETN-USDT,FTM-USDT,TOMO-USDT,VSYS-USDT,OCEAN-BTC,OCEAN-ETH,CHR-BTC,CHR-USDT,FX-BTC,FX-ETH,NIM-BTC,NIM-ETH,COTI-BTC,COTI-USDT,NRG-ETH,BNB-BTC,BNB-USDT,JAR-BTC,JAR-USDT,ALGO-BTC,ALGO-ETH,ALGO-USDT,XEM-BTC,XEM-USDT,CIX100-USDT,XTZ-BTC,XTZ-USDT,ZEC-BTC,ZEC-USDT,ADA-BTC,ADA-USDT,REV-USDT,WXT-BTC,WXT-USDT,FORESTPLUS-BTC,FORESTPLUS-USDT,BOLT-BTC,BOLT-USDT,ARPA-USDT,CHZ-BTC,CHZ-USDT,DAPPT-BTC,DAPPT-USDT,NOIA-BTC,NOIA-USDT,WIN-BTC,WIN-USDT,DERO-BTC,DERO-USDT,BTT-USDT,EOSC-USDT,ENQ-BTC,ENQ-USDT,ONE-BTC,ONE-USDT,TOKO-BTC,TOKO-USDT,VID-BTC,VID-USDT,LUNA-USDT,SXP-BTC,SXP-USDT,AKRO-BTC,AKRO-USDT,ROOBEE-BTC,WIN-TRX,MAP-BTC,MAP-USDT,AMPL-BTC,AMPL-USDT,DAG-USDT,POL-USDT,ARX-USDT,NWC-BTC,NWC-USDT,BEPRO-BTC,BEPRO-USDT,VRA-BTC,VRA-USDT,KSM-BTC,KSM-USDT,DASH-USDT,SUTER-USDT,ACOIN-USDT,SUTER-BTC,SENSO-USDT,PRE-BTC,XDB-USDT,SYLO-USDT,WOM-USDT,SENSO-BTC,DGB-USDT,LYXE-USDT,LYXE-ETH,XDB-BTC,STX-BTC,STX-USDT,XSR-USDT,COMP-USDT,CRO-USDT,KAI-USDT,KAI-BTC,WEST-BTC,WEST-USDT,EWT-BTC,WAVES-USDT,WAVES-BTC,ORN-USDT,AMPL-ETH,BNS-USDT,MKR-USDT,SUKU-BTC,MLK-BTC,MLK-USDT,JST-USDT,KAI-ETH,SUKU-USDT,DIA-USDT,DIA-BTC,LINK-BTC,LINK-USDT,DOT-USDT,DOT-BTC,SHA-BTC,SHA-USDT,EWT-USDT,USDJ-USDT,EFX-BTC,CKB-BTC,CKB-USDT,UMA-USDT,ALEPH-USDT,VELO-USDT,SUN-USDT,BUY-USDT,YFI-USDT,OXEN-USDT,UNI-USDT,UOS-USDT,UOS-BTC,NIM-USDT,DEGO-USDT,DEGO-ETH,UDOO-ETH,RFUEL-USDT,FIL-USDT,UBX-ETH,REAP-USDT,AAVE-USDT,AAVE-BTC,TONE-BTC,TONE-ETH,ELF-ETH,AERGO-BTC,IOST-ETH,KCS-USDT,SNX-ETH,TOMO-ETH,KCS-ETH,DRGN-BTC,WAN-ETH,NULS-ETH,AXPR-ETH,POWR-BTC,QTUM-BTC,MANA-BTC,TEL-BTC,XYO-ETH,AXPR-BTC,ETN-BTC,COV-ETH,VET-BTC,KCS-BTC,CAPP-ETH,ONT-BTC,DRGN-ETH,DAG-ETH,TOMO-BTC,WAN-BTC,KNC-ETH,CRPT-ETH,LTC-USDT,BAX-ETH,BSV-USDT,DENT-ETH,AION-ETH,LYM-ETH,TRAC-ETH,ENJ-BTC,WAXP-BTC,DGB-BTC,ELA-BTC,ZIL-BTC,BSV-BTC,XLM-USDT,IOTX-ETH,SOUL-BTC,DOCK-BTC,AMB-ETH,TRX-BTC,XRP-TUSD,NULS-BTC,ETH-DAI,LSK-BTC,GMB-ETH,GMB-BTC,NEO-ETH,OMG-ETH,BTC-TUSD,KAT-USDT,KNC-BTC,ELF-BTC,MANA-ETH,ETC-USDT,ONT-ETH,MKR-BTC,KAT-BTC,XRP-USDC,XYO-BTC,SNT-ETH,ZRX-BTC,LOOM-ETH,AION-BTC,POWR-ETH,OLT-ETH,OLT-BTC,SNT-BTC,TRAC-BTC,XLM-ETH,ETH-USDT,BSV-ETH,TRX-ETH,ETN-ETH,AOA-USDT,BCD-BTC,DENT-BTC,DOCK-ETH,KEY-BTC,EOS-KCS,XLM-BTC,ADB-ETH,TIME-ETH,CVC-BTC,LSK-ETH,QKC-BTC,AMB-BTC,USDT-TUSD,ETC-ETH,XRP-BTC,NEO-KCS,SNX-USDT,CRPT-BTC,IOTX-BTC,LTC-ETH,XRP-KCS,ADB-BTC,LTC-KCS,TEL-ETH,DCR-ETH,LYM-USDT,USDT-USDC,ETH-USDC,DAG-BTC,AVA-BTC,BTC-USDT,WAXP-ETH,XRP-USDT,KEY-ETH,VET-ETH,FTM-BTC,USDT-DAI,QKC-ETH,ETH-BTC,MAN-BTC,CPC-ETH,TRX-USDT,BTC-DAI,ONT-USDT,DASH-ETH,BAX-BTC,AVA-ETH,LOOM-BTC,MVP-BTC,MKR-ETH,COV-BTC,CPC-BTC,REQ-ETH,EOS-BTC,LTC-BTC,XRP-ETH,CAPP-BTC,FTM-ETH,BCD-ETH,ZRX-ETH,DGB-ETH,VET-USDT,REQ-BTC,UTK-BTC,PLAY-BTC,UTK-ETH,SNX-BTC,MVP-ETH,NEO-BTC,SOUL-ETH,NEO-USDT,ELA-ETH,OMG-BTC,TIME-BTC,AOA-BTC,ETC-BTC,DCR-BTC,BTC-USDC,ENJ-ETH,IOST-BTC,DASH-BTC,EOS-USDT,EOS-ETH,ZIL-ETH,ETH-TUSD,GAS-BTC,LYM-BTC,BCH-BTC,VSYS-BTC,BCH-USDT,MKR-DAI,SOLVE-BTC,GRIN-BTC,GRIN-USDT,UQC-BTC,UQC-ETH,OPCT-BTC,OPCT-ETH,PRE-USDT,SHR-BTC,SHR-USDT,UBXT-USDT,ROSE-USDT,USDC-USDT,CTI-USDT,CTI-ETH,ETH2-ETH,BUX-BTC,XHV-USDT,PLU-USDT,GRT-USDT,CAS-BTC,CAS-USDT,MSWAP-BTC,MSWAP-USDT,GOM2-BTC,GOM2-USDT,REVV-BTC,REVV-USDT,LON-USDT,1INCH-USDT,LOC-USDT,API3-USDT,UNFI-USDT,HTR-USDT,FRONT-USDT,FRONT-BTC,WBTC-BTC,WBTC-ETH,MIR-USDT,LTC-USDC,BCH-USDC,HYDRA-USDT,DFI-USDT,DFI-BTC,CRV-USDT,SUSHI-USDT,FRM-USDT,EOS-USDC,BSV-USDC,ZEN-USDT,CUDOS-USDT,ADA-USDC,REN-USDT,LRC-USDT,LINK-USDC,KLV-USDT,KLV-BTC,BOA-USDT,THETA-USDT,QNT-USDT,BAT-USDT,DOGE-USDT,DOGE-USDC,DAO-USDT,STRONG-USDT,TRIAS-USDT,TRIAS-BTC,DOGE-BTC,MITX-BTC,MITX-USDT,CAKE-USDT,ORAI-USDT,ZEE-USDT,LTX-USDT,LTX-BTC,MASK-USDT,KLV-TRX,IDEA-USDT,PHA-USDT,PHA-ETH,BCH-KCS,SRK-USDT,SRK-BTC,ADA-KCS,HTR-BTC,BSV-KCS,DOT-KCS,LINK-KCS,MIR-KCS,BNB-KCS,XLM-KCS,VET-KCS,SWINGBY-USDT,SWINGBY-BTC,XHV-BTC,DASH-KCS,UNI-KCS,AAVE-KCS,DOGE-KCS,ZEC-KCS,XTZ-KCS,GRT-KCS,ALGO-KCS,EWT-KCS,GAS-USDT,AVAX-USDT,AVAX-BTC,KRL-BTC,KRL-USDT,POLK-USDT,POLK-BTC,ENJ-USDT,MANA-USDT,RNDR-USDT,RNDR-BTC,RLY-USDT,ANC-USDT,SKEY-USDT,LAYER-USDT,TARA-USDT,TARA-ETH,IOST-USDT,DYP-USDT,DYP-ETH,XYM-USDT,XYM-BTC,PCX-USDT,PCX-BTC,ORBS-USDT,ORBS-BTC,BTC3L-USDT,BTC3S-USDT,ETH3L-USDT,ETH3S-USDT,ANKR-USDT,DSLA-USDT,DSLA-BTC,SAND-USDT,VAI-USDT,XCUR-USDT,XCUR-BTC,FLUX-USDT,OMG-USDT,ZIL-USDT,DODO-USDT,MAN-USDT,BAX-USDT,BOSON-USDT,BOSON-ETH,PUNDIX-USDT,PUNDIX-BTC,WAXP-USDT,HT-USDT,PDEX-USDT,LABS-USDT,LABS-ETH,GMB-USDT,PHNX-USDT,PHNX-BTC,HAI-USDT,EQZ-USDT,FORTH-USDT,HORD-USDT,CGG-USDT,UBX-USDT,GHX-USDT,TCP-USDT,STND-USDT,STND-ETH,TOWER-USDT,TOWER-BTC,ACE-USDT,LOCG-USDT,CARD-USDT,FLY-USDT,CWS-USDT,XDC-USDT,XDC-ETH,STRK-BTC,STRK-ETH,SHIB-USDT,POLX-USDT,KDA-USDT,KDA-BTC,ICP-USDT,ICP-BTC,STC-USDT,STC-BTC,GOVI-USDT,GOVI-BTC,FKX-USDT,CELO-USDT,CELO-BTC,CUSD-USDT,CUSD-BTC,FCL-USDT,MATIC-USDT,MATIC-BTC,ELA-USDT,CRPT-USDT,OPCT-USDT,OGN-USDT,OGN-BTC,OUSD-USDT,OUSD-BTC,TLOS-USDT,TLOS-BTC,YOP-USDT,YOP-ETH,GLQ-USDT,GLQ-BTC,MXC-USDT,ERSDL-USDT,HOTCROSS-USDT,ADA3L-USDT,ADA3S-USDT,HYVE-USDT,HYVE-BTC,DAPPX-USDT,KONO-USDT,PRQ-USDT,MAHA-USDT,MAHA-BTC,FEAR-USDT,PYR-USDT,PYR-BTC,PROM-USDT,PROM-BTC,GLCH-USDT,UNO-USDT,ALBT-USDT,ALBT-ETH,XCAD-USDT,EOS3L-USDT,EOS3S-USDT,BCH3L-USDT,BCH3S-USDT,ELON-USDT,APL-USDT,FCL-ETH,VEED-USDT,VEED-BTC,DIVI-USDT,PDEX-BTC,JUP-USDT,JUP-ETH,POLS-USDT,POLS-BTC,LPOOL-USDT,LPOOL-BTC,LSS-USDT,VET3L-USDT,VET3S-USDT,LTC3L-USDT,LTC3S-USDT,ABBC-USDT,ABBC-BTC,KOK-USDT,ROSN-USDT,DORA-USDT,DORA-BTC,ZCX-USDT,ZCX-BTC,NORD-USDT,GMEE-USDT,SFUND-USDT,XAVA-USDT,AI-USDT,ALPACA-USDT,IOI-USDT,NFT-USDT,NFT-TRX,MNST-USDT,MEM-USDT,AGIX-USDT,AGIX-BTC,AGIX-ETH,CQT-USDT,AIOZ-USDT,MARSH-USDT,HAPI-USDT,MODEFI-USDT,MODEFI-BTC,YFDAI-USDT,YFDAI-BTC,GENS-USDT,FORM-USDT,ARRR-USDT,ARRR-BTC,TOKO-KCS,EXRD-USDT,NGM-USDT,LPT-USDT,STMX-USDT,ASD-USDT,BOND-USDT,HAI-BTC,SOUL-USDT,2CRZ-USDT,NEAR-USDT,NEAR-BTC,DFYN-USDT,OOE-USDT,CFG-USDT,CFG-BTC,AXS-USDT,CLV-USDT,ROUTE-USDT,KAR-USDT,EFX-USDT,XDC-BTC,SHFT-USDT,PMON-USDT,DPET-USDT,ERG-USDT,ERG-BTC,SOL-USDT,SLP-USDT,LITH-USDT,LITH-ETH,XCH-USDT,HAKA-USDT,LAYER-BTC,MTL-USDT,MTL-BTC,IOTX-USDT,GALA-USDT,REQ-USDT,TXA-USDT,TXA-USDC,CIRUS-USDT,QI-USDT,QI-BTC,ODDZ-USDT,PNT-USDT,PNT-BTC,XPR-USDT,XPR-BTC,TRIBE-USDT,SHFT-BTC,MOVR-USDT,MOVR-ETH,WOO-USDT,WILD-USDT,QRDO-USDT,QRDO-ETH,SDN-USDT,SDN-ETH,MAKI-USDT,MAKI-BTC,REP-USDT,REP-BTC,REP-ETH,BNT-USDT,BNT-BTC,BNT-ETH,OXT-USDT,OXT-BTC,OXT-ETH,BAL-USDT,BAL-BTC,BAL-ETH,STORJ-USDT,STORJ-BTC,STORJ-ETH,YGG-USDT,NDAU-USDT,SDAO-USDT,SDAO-ETH,XRP3L-USDT,XRP3S-USDT,SKL-USDT,SKL-BTC,NMR-USDT,NMR-BTC,IXS-USDT,TRB-USDT,TRB-BTC,DYDX-USDT,XYO-USDT,GTC-USDT,GTC-BTC,EQX-USDT,EQX-BTC,RLC-USDT,RLC-BTC,XPRT-USDT,EGLD-USDT,EGLD-BTC,HBAR-USDT,HBAR-BTC,DOGE3L-USDT,DOGE3S-USDT,FLOW-USDT,FLOW-BTC,NKN-USDT,NKN-BTC,PBX-USDT,SOL3L-USDT,SOL3S-USDT,MLN-USDT,MLN-BTC,XNL-USDT,SOLVE-USDT,WNCG-USDT,WNCG-BTC,DMTR-USDT,LINK3L-USDT,LINK3S-USDT,DOT3L-USDT,DOT3S-USDT,CTSI-USDT,CTSI-BTC,ALICE-USDT,ALICE-BTC,ALICE-ETH,OPUL-USDT,ILV-USDT,BAND-USDT,BAND-BTC,FTT-USDT,FTT-BTC,DVPN-USDT,SKU-USDT,SKU-BTC,EDG-USDT,SLIM-USDT,TLM-USDT,TLM-BTC,TLM-ETH,DEXE-USDT,DEXE-BTC,DEXE-ETH,MATTER-USDT,CUDOS-BTC,RUNE-USDT,RUNE-BTC,RMRK-USDT,BMON-USDT,C98-USDT,BLOK-USDT,SOLR-USDT,ATOM3L-USDT,ATOM3S-USDT,UNI3L-USDT,UNI3S-USDT,WSIENNA-USDT,PUSH-USDT,PUSH-BTC,FORM-ETH,NTVRK-USDT,NTVRK-USDC,AXS3L-USDT,AXS3S-USDT,FTM3L-USDT,FTM3S-USDT,FLAME-USDT,AGLD-USDT,NAKA-USDT,YLD-USDT,TONE-USDT,REEF-USDT,REEF-BTC,TIDAL-USDT,TVK-USDT,TVK-BTC,INJ-USDT,INJ-BTC,BNB3L-USDT,BNB3S-USDT,MATIC3L-USDT,MATIC3S-USDT,NFTB-USDT,VEGA-USDT,VEGA-ETH,ALPHA-USDT,ALPHA-BTC,BADGER-USDT,BADGER-BTC,UNO-BTC,ZKT-USDT,AR-USDT,AR-BTC,XVS-USDT,XVS-BTC,JASMY-USDT,PERP-USDT,PERP-BTC,GHST-USDT,GHST-BTC,SCLP-USDT,SCLP-BTC,SUPER-USDT,SUPER-BTC,CPOOL-USDT,HERO-USDT,BASIC-USDT,XED-USDT,XED-BTC,AURY-USDT,SWASH-USDT,LTO-USDT,LTO-BTC,BUX-USDT,MTRG-USDT,DREAMS-USDT,SHIB-DOGE,QUICK-USDT,QUICK-BTC,TRU-USDT,TRU-BTC,WRX-USDT,WRX-BTC,TKO-USDT,TKO-BTC,SUSHI3L-USDT,SUSHI3S-USDT,NEAR3L-USDT,NEAR3S-USDT,DATA-USDT,DATA-BTC,NORD-BTC,ISP-USDT,CERE-USDT,SHILL-USDT,HEGIC-USDT,HEGIC-BTC,ERN-USDT,ERN-BTC,FTG-USDT,PAXG-USDT,PAXG-BTC,AUDIO-USDT,AUDIO-BTC,ENS-USDT,AAVE3L-USDT,AAVE3S-USDT,SAND3L-USDT,SAND3S-USDT,XTM-USDT,MNW-USDT,FXS-USDT,FXS-BTC,ATA-USDT,ATA-BTC,VXV-USDT,LRC-BTC,LRC-ETH,DPR-USDT,CWAR-USDT,CWAR-BTC,FLUX-BTC,EDG-BTC,PBR-USDT,WNXM-USDT,WNXM-BTC,ANT-USDT,ANT-BTC,COV-USDT,SWP-USDT,TWT-USDT,TWT-BTC,OM-USDT,OM-BTC,ADX-USDT,AVAX3L-USDT,AVAX3S-USDT,MANA3L-USDT,MANA3S-USDT,GLM-USDT,GLM-BTC,BAKE-USDT,BAKE-BTC,BAKE-ETH,NUM-USDT,VLX-USDT,VLX-BTC,TRADE-USDT,TRADE-BTC,1EARTH-USDT,MONI-USDT,LIKE-USDT,MFT-USDT,MFT-BTC,LIT-USDT,LIT-BTC,KAVA-USDT,SFP-USDT,SFP-BTC,BURGER-USDT,BURGER-BTC,ILA-USDT,CREAM-USDT,CREAM-BTC,RSR-USDT,RSR-BTC,BUY-BTC,IMX-USDT,GODS-USDT,KMA-USDT,SRM-USDT,SRM-BTC,POLC-USDT,XTAG-USDT,MNET-USDT,NGC-USDT,HARD-USDT,GALAX3L-USDT,GALAX3S-USDT,UNIC-USDT,POND-USDT,POND-BTC,VR-USDT,EPIK-USDT,NGL-USDT,NGL-BTC,KDON-USDT,PEL-USDT,CIRUS-ETH,LINA-USDT,LINA-BTC,KLAY-USDT,KLAY-BTC,CREDI-USDT,TRVL-USDT,LACE-USDT,LACE-ETH,ARKER-USDT,BONDLY-USDT,BONDLY-ETH,XEC-USDT,HEART-USDT,HEART-BTC,UNB-USDT,GAFI-USDT,KOL-USDT,KOL-ETH,H3RO3S-USDT,FALCONS-USDT,UFO-USDT,CHMB-USDT,GEEQ-USDT,ORC-USDT,RACEFI-USDT,PEOPLE-USDT,ADS-USDT,ADS-BTC,OCEAN-USDT,SOS-USDT,WHALE-USDT,TIME-USDT,CWEB-USDT,IOTA-USDT,IOTA-BTC,OOKI-USDT,OOKI-BTC,HNT-USDT,HNT-BTC,GGG-USDT,POWR-USDT,REVU-USDT,CLH-USDT,PLGR-USDT,GLMR-USDT,GLMR-BTC,LOVE-USDT,CTC-USDT,CTC-BTC,GARI-USDT,FRR-USDT,ASTR-USDT,ASTR-BTC,ERTHA-USDT,FCON-USDT,ACA-USDT,ACA-BTC,MTS-USDT,ROAR-USDT,HBB-USDT,SURV-USDT,CVX-USDT,AMP-USDT,ACT-USDT,MJT-USDT,MJT-KCS,SHX-USDT,SHX-BTC,STARLY-USDT,ONSTON-USDT,RANKER-USDT,WMT-USDT,XNO-USDT,XNO-BTC,MARS4-USDT,TFUEL-USDT,TFUEL-BTC,METIS-USDT,LAVAX-USDT,WAL-USDT,BULL-USDT,SON-USDT,MELOS-USDT,APE-USDT,GMT-USDT,BICO-USDT,STG-USDT,LMR-USDT,LMR-BTC,LOKA-USDT,URUS-USDT,JAM-USDT,JAM-ETH,BNC-USDT,LBP-USDT,CFX-USDT,LOOKS-USDT,XCN-USDT,XCN-BTC,KP3R-USDT,TITAN-USDT,INDI-USDT,UPO-USDT,SPELL-USDT,SLCL-USDT,CEEK-USDT,VEMP-USDT,BETA-USDT,NHCT-USDT,ARNM-USDT,FRA-USDT,VISION-USDT,COCOS-USDT,ALPINE-USDT,BNX-USDT,ZBC-USDT,WOOP-USDT,T-USDT,NYM-USDT,VOXEL-USDT,VOXEL-ETH,PSTAKE-USDT,SPA-USDT,SPA-ETH,SYNR-USDT,DAR-USDT,DAR-BTC,MV-USDT,XDEFI-USDT,RACA-USDT,XWG-USDT,HAWK-USDT,TRVL-BTC,SWFTC-USDT,IDEX-USDT,BRWL-USDT,PLATO-USDT,TAUM-USDT,CELR-USDT,AURORA-USDT,POSI-USDT,COOHA-USDT,KNC-USDT,EPK-USDT,PLD-USDT,PSL-USDT,PKF-USDT,OVR-USDT,SYS-USDT,SYS-BTC,BRISE-USDT,DG-USDT,EPX-USDT,GST-USDT,PLY-USDT,GAL-USDT,BSW-USDT,FITFI-USDT,FSN-USDT,H2O-USDT,GMM-USDT,AKT-USDT,SIN-USDT,AUSD-USDT,BOBA-USDT,KARA-USDT,BFC-USDT,BIFI-USDT,DFA-USDT,KYL-USDT,FCD-USDT,MBL-USDT,CELT-USDT,DUSK-USDT,USDD-USDT,USDD-USDC,FITFI-USDC,MBOX-USDT,MBOX-BTC,APE-USDC,AVAX-USDC,SHIB-USDC,XCN-USDC,TRX-USDC,NEAR-USDC,MATIC-USDC,FTM-USDC,ZIL-USDC,SOL-USDC,MLS-USDT,AFK-USDT,AFK-USDC,ACH-USDT,SCRT-USDT,SCRT-BTC,APE3L-USDT,APE3S-USDT,STORE-USDT,STORE-ETH,GMT3L-USDT,GMT3S-USDT,CCD-USDT,DOSE-USDC,LUNC-USDT,LUNC-USDC,USTC-USDT,USTC-USDC,GMT-USDC,VRA-USDC,DOT-USDC,RUNE-USDC,ATOM-USDC,BNB-USDC,JASMY-USDC,KCS-USDC,KDA-USDC,ALGO-USDC,LUNA-USDC,OP-USDT,OP-USDC,JASMY3L-USDT,JASMY3S-USDT,EVER-USDT,MOOV-USDT,IHC-USDT,ICX-USDT,ICX-ETH,BTC-BRL,ETH-BRL,USDT-BRL,WELL-USDT,FORT-USDT,USDP-USDT,USDD-TRX,CSPR-USDT,CSPR-ETH,WEMIX-USDT,REV3L-USDT,OLE-USDT,LDO-USDT,LDO-USDC,CULT-USDT,SWFTC-USDC,FIDA-USDT,BUSD-USDT,RBP-USDT,SRBP-USDT,HIBAYC-USDT,BUSD-USDC,OGV-USDT,WOMBAT-USDT,HIPUNKS-USDT,FT-USDT,ETC-USDC,HIENS4-USDT,EGAME-USDT,EGAME-BTC,STEPWATCH-USDT,HISAND33-USDT,DC-USDT,NEER-USDT,RVN-USDT,HIENS3-USDT,MC-USDT,PEEL-USDT,PEEL-BTC,SDL-USDT,SDL-BTC,SWEAT-USDT,HIODBS-USDT,CMP-USDT,PIX-USDT,MPLX-USDT,HIDOODLES-USDT,ETHW-USDT,QUARTZ-USDT,ACQ-USDT,ACQ-USDC,AOG-USDT,HIMAYC-USDT,PRMX-USDT,RED-USDT,PUMLX-USDT,XETA-USDT,GEM-USDT,DERC-USDT,P00LS-USDT,P00LS-USDC,KICKS-USDT,TRIBL-USDT,GMX-USDT,HIOD-USDT,POKT-USDT,EFI-USDT,APT-USDT,BBC-USDT,EUL-USDT,TON-USDT,PIAS-USDT,HIMEEBITS-USDT,HISQUIGGLE-USDT,XCV-USDT,HFT-USDT,HFT-USDC,ECOX-USDT,AMB-USDT,AZERO-USDT,HIFIDENZA-USDT,BEAT-USDT", + "requestFormat": { + "uppercase": true, + "delimiter": "-" + }, + "configFormat": { + "uppercase": true, + "delimiter": "-" + } + }, + "margin": { + "assetEnabled": true, + "enabled": "ETH-BTC,TRX-BTC,LTC-USDT,SOL-USDC", + "available": "BTC-USDT,MHC-ETH,MHC-BTC,OXEN-BTC,OXEN-ETH,NRG-BTC,AVA-USDT,FET-BTC,FET-ETH,ANKR-BTC,MHC-USDT,XMR-BTC,XMR-ETH,RIF-BTC,MTV-BTC,MTV-ETH,CRO-BTC,MTV-USDT,KMD-BTC,KMD-USDT,RFOX-USDT,TEL-USDT,TT-USDT,AERGO-USDT,XMR-USDT,TRX-KCS,ATOM-BTC,ATOM-ETH,ATOM-USDT,ATOM-KCS,ETN-USDT,FTM-USDT,TOMO-USDT,VSYS-USDT,OCEAN-BTC,OCEAN-ETH,CHR-BTC,CHR-USDT,FX-BTC,FX-ETH,NIM-BTC,NIM-ETH,COTI-BTC,COTI-USDT,NRG-ETH,BNB-BTC,BNB-USDT,JAR-BTC,JAR-USDT,ALGO-BTC,ALGO-ETH,ALGO-USDT,XEM-BTC,XEM-USDT,CIX100-USDT,XTZ-BTC,XTZ-USDT,ZEC-BTC,ZEC-USDT,ADA-BTC,ADA-USDT,REV-USDT,WXT-BTC,WXT-USDT,FORESTPLUS-BTC,FORESTPLUS-USDT,BOLT-BTC,BOLT-USDT,ARPA-USDT,CHZ-BTC,CHZ-USDT,DAPPT-BTC,DAPPT-USDT,NOIA-BTC,NOIA-USDT,WIN-BTC,WIN-USDT,DERO-BTC,DERO-USDT,BTT-USDT,EOSC-USDT,ENQ-BTC,ENQ-USDT,ONE-BTC,ONE-USDT,TOKO-BTC,TOKO-USDT,VID-BTC,VID-USDT,LUNA-USDT,SXP-BTC,SXP-USDT,AKRO-BTC,AKRO-USDT,ROOBEE-BTC,WIN-TRX,MAP-BTC,MAP-USDT,AMPL-BTC,AMPL-USDT,DAG-USDT,POL-USDT,ARX-USDT,NWC-BTC,NWC-USDT,BEPRO-BTC,BEPRO-USDT,VRA-BTC,VRA-USDT,KSM-BTC,KSM-USDT,DASH-USDT,SUTER-USDT,ACOIN-USDT,SUTER-BTC,SENSO-USDT,PRE-BTC,XDB-USDT,SYLO-USDT,WOM-USDT,SENSO-BTC,DGB-USDT,LYXE-USDT,LYXE-ETH,XDB-BTC,STX-BTC,STX-USDT,XSR-USDT,COMP-USDT,CRO-USDT,KAI-USDT,KAI-BTC,WEST-BTC,WEST-USDT,EWT-BTC,WAVES-USDT,WAVES-BTC,ORN-USDT,AMPL-ETH,BNS-USDT,MKR-USDT,SUKU-BTC,MLK-BTC,MLK-USDT,JST-USDT,KAI-ETH,SUKU-USDT,DIA-USDT,DIA-BTC,LINK-BTC,LINK-USDT,DOT-USDT,DOT-BTC,SHA-BTC,SHA-USDT,EWT-USDT,USDJ-USDT,EFX-BTC,CKB-BTC,CKB-USDT,UMA-USDT,ALEPH-USDT,VELO-USDT,SUN-USDT,BUY-USDT,YFI-USDT,OXEN-USDT,UNI-USDT,UOS-USDT,UOS-BTC,NIM-USDT,DEGO-USDT,DEGO-ETH,UDOO-ETH,RFUEL-USDT,FIL-USDT,UBX-ETH,REAP-USDT,AAVE-USDT,AAVE-BTC,TONE-BTC,TONE-ETH,ELF-ETH,AERGO-BTC,IOST-ETH,KCS-USDT,SNX-ETH,TOMO-ETH,KCS-ETH,DRGN-BTC,WAN-ETH,NULS-ETH,AXPR-ETH,POWR-BTC,QTUM-BTC,MANA-BTC,TEL-BTC,XYO-ETH,AXPR-BTC,ETN-BTC,COV-ETH,VET-BTC,KCS-BTC,CAPP-ETH,ONT-BTC,DRGN-ETH,DAG-ETH,TOMO-BTC,WAN-BTC,KNC-ETH,CRPT-ETH,LTC-USDT,BAX-ETH,BSV-USDT,DENT-ETH,AION-ETH,LYM-ETH,TRAC-ETH,ENJ-BTC,WAXP-BTC,DGB-BTC,ELA-BTC,ZIL-BTC,BSV-BTC,XLM-USDT,IOTX-ETH,SOUL-BTC,DOCK-BTC,AMB-ETH,TRX-BTC,XRP-TUSD,NULS-BTC,ETH-DAI,LSK-BTC,GMB-ETH,GMB-BTC,NEO-ETH,OMG-ETH,BTC-TUSD,KAT-USDT,KNC-BTC,ELF-BTC,MANA-ETH,ETC-USDT,ONT-ETH,MKR-BTC,KAT-BTC,XRP-USDC,XYO-BTC,SNT-ETH,ZRX-BTC,LOOM-ETH,AION-BTC,POWR-ETH,OLT-ETH,OLT-BTC,SNT-BTC,TRAC-BTC,XLM-ETH,ETH-USDT,BSV-ETH,TRX-ETH,ETN-ETH,AOA-USDT,BCD-BTC,DENT-BTC,DOCK-ETH,KEY-BTC,EOS-KCS,XLM-BTC,ADB-ETH,TIME-ETH,CVC-BTC,LSK-ETH,QKC-BTC,AMB-BTC,USDT-TUSD,ETC-ETH,XRP-BTC,NEO-KCS,SNX-USDT,CRPT-BTC,IOTX-BTC,LTC-ETH,XRP-KCS,ADB-BTC,LTC-KCS,TEL-ETH,DCR-ETH,LYM-USDT,USDT-USDC,ETH-USDC,DAG-BTC,AVA-BTC,BTC-USDT,WAXP-ETH,XRP-USDT,KEY-ETH,VET-ETH,FTM-BTC,USDT-DAI,QKC-ETH,ETH-BTC,MAN-BTC,CPC-ETH,TRX-USDT,BTC-DAI,ONT-USDT,DASH-ETH,BAX-BTC,AVA-ETH,LOOM-BTC,MVP-BTC,MKR-ETH,COV-BTC,CPC-BTC,REQ-ETH,EOS-BTC,LTC-BTC,XRP-ETH,CAPP-BTC,FTM-ETH,BCD-ETH,ZRX-ETH,DGB-ETH,VET-USDT,REQ-BTC,UTK-BTC,PLAY-BTC,UTK-ETH,SNX-BTC,MVP-ETH,NEO-BTC,SOUL-ETH,NEO-USDT,ELA-ETH,OMG-BTC,TIME-BTC,AOA-BTC,ETC-BTC,DCR-BTC,BTC-USDC,ENJ-ETH,IOST-BTC,DASH-BTC,EOS-USDT,EOS-ETH,ZIL-ETH,ETH-TUSD,GAS-BTC,LYM-BTC,BCH-BTC,VSYS-BTC,BCH-USDT,MKR-DAI,SOLVE-BTC,GRIN-BTC,GRIN-USDT,UQC-BTC,UQC-ETH,OPCT-BTC,OPCT-ETH,PRE-USDT,SHR-BTC,SHR-USDT,UBXT-USDT,ROSE-USDT,USDC-USDT,CTI-USDT,CTI-ETH,ETH2-ETH,BUX-BTC,XHV-USDT,PLU-USDT,GRT-USDT,CAS-BTC,CAS-USDT,MSWAP-BTC,MSWAP-USDT,GOM2-BTC,GOM2-USDT,REVV-BTC,REVV-USDT,LON-USDT,1INCH-USDT,LOC-USDT,API3-USDT,UNFI-USDT,HTR-USDT,FRONT-USDT,FRONT-BTC,WBTC-BTC,WBTC-ETH,MIR-USDT,LTC-USDC,BCH-USDC,HYDRA-USDT,DFI-USDT,DFI-BTC,CRV-USDT,SUSHI-USDT,FRM-USDT,EOS-USDC,BSV-USDC,ZEN-USDT,CUDOS-USDT,ADA-USDC,REN-USDT,LRC-USDT,LINK-USDC,KLV-USDT,KLV-BTC,BOA-USDT,THETA-USDT,QNT-USDT,BAT-USDT,DOGE-USDT,DOGE-USDC,DAO-USDT,STRONG-USDT,TRIAS-USDT,TRIAS-BTC,DOGE-BTC,MITX-BTC,MITX-USDT,CAKE-USDT,ORAI-USDT,ZEE-USDT,LTX-USDT,LTX-BTC,MASK-USDT,KLV-TRX,IDEA-USDT,PHA-USDT,PHA-ETH,BCH-KCS,SRK-USDT,SRK-BTC,ADA-KCS,HTR-BTC,BSV-KCS,DOT-KCS,LINK-KCS,MIR-KCS,BNB-KCS,XLM-KCS,VET-KCS,SWINGBY-USDT,SWINGBY-BTC,XHV-BTC,DASH-KCS,UNI-KCS,AAVE-KCS,DOGE-KCS,ZEC-KCS,XTZ-KCS,GRT-KCS,ALGO-KCS,EWT-KCS,GAS-USDT,AVAX-USDT,AVAX-BTC,KRL-BTC,KRL-USDT,POLK-USDT,POLK-BTC,ENJ-USDT,MANA-USDT,RNDR-USDT,RNDR-BTC,RLY-USDT,ANC-USDT,SKEY-USDT,LAYER-USDT,TARA-USDT,TARA-ETH,IOST-USDT,DYP-USDT,DYP-ETH,XYM-USDT,XYM-BTC,PCX-USDT,PCX-BTC,ORBS-USDT,ORBS-BTC,BTC3L-USDT,BTC3S-USDT,ETH3L-USDT,ETH3S-USDT,ANKR-USDT,DSLA-USDT,DSLA-BTC,SAND-USDT,VAI-USDT,XCUR-USDT,XCUR-BTC,FLUX-USDT,OMG-USDT,ZIL-USDT,DODO-USDT,MAN-USDT,BAX-USDT,BOSON-USDT,BOSON-ETH,PUNDIX-USDT,PUNDIX-BTC,WAXP-USDT,HT-USDT,PDEX-USDT,LABS-USDT,LABS-ETH,GMB-USDT,PHNX-USDT,PHNX-BTC,HAI-USDT,EQZ-USDT,FORTH-USDT,HORD-USDT,CGG-USDT,UBX-USDT,GHX-USDT,TCP-USDT,STND-USDT,STND-ETH,TOWER-USDT,TOWER-BTC,ACE-USDT,LOCG-USDT,CARD-USDT,FLY-USDT,CWS-USDT,XDC-USDT,XDC-ETH,STRK-BTC,STRK-ETH,SHIB-USDT,POLX-USDT,KDA-USDT,KDA-BTC,ICP-USDT,ICP-BTC,STC-USDT,STC-BTC,GOVI-USDT,GOVI-BTC,FKX-USDT,CELO-USDT,CELO-BTC,CUSD-USDT,CUSD-BTC,FCL-USDT,MATIC-USDT,MATIC-BTC,ELA-USDT,CRPT-USDT,OPCT-USDT,OGN-USDT,OGN-BTC,OUSD-USDT,OUSD-BTC,TLOS-USDT,TLOS-BTC,YOP-USDT,YOP-ETH,GLQ-USDT,GLQ-BTC,MXC-USDT,ERSDL-USDT,HOTCROSS-USDT,ADA3L-USDT,ADA3S-USDT,HYVE-USDT,HYVE-BTC,DAPPX-USDT,KONO-USDT,PRQ-USDT,MAHA-USDT,MAHA-BTC,FEAR-USDT,PYR-USDT,PYR-BTC,PROM-USDT,PROM-BTC,GLCH-USDT,UNO-USDT,ALBT-USDT,ALBT-ETH,XCAD-USDT,EOS3L-USDT,EOS3S-USDT,BCH3L-USDT,BCH3S-USDT,ELON-USDT,APL-USDT,FCL-ETH,VEED-USDT,VEED-BTC,DIVI-USDT,PDEX-BTC,JUP-USDT,JUP-ETH,POLS-USDT,POLS-BTC,LPOOL-USDT,LPOOL-BTC,LSS-USDT,VET3L-USDT,VET3S-USDT,LTC3L-USDT,LTC3S-USDT,ABBC-USDT,ABBC-BTC,KOK-USDT,ROSN-USDT,DORA-USDT,DORA-BTC,ZCX-USDT,ZCX-BTC,NORD-USDT,GMEE-USDT,SFUND-USDT,XAVA-USDT,AI-USDT,ALPACA-USDT,IOI-USDT,NFT-USDT,NFT-TRX,MNST-USDT,MEM-USDT,AGIX-USDT,AGIX-BTC,AGIX-ETH,CQT-USDT,AIOZ-USDT,MARSH-USDT,HAPI-USDT,MODEFI-USDT,MODEFI-BTC,YFDAI-USDT,YFDAI-BTC,GENS-USDT,FORM-USDT,ARRR-USDT,ARRR-BTC,TOKO-KCS,EXRD-USDT,NGM-USDT,LPT-USDT,STMX-USDT,ASD-USDT,BOND-USDT,HAI-BTC,SOUL-USDT,2CRZ-USDT,NEAR-USDT,NEAR-BTC,DFYN-USDT,OOE-USDT,CFG-USDT,CFG-BTC,AXS-USDT,CLV-USDT,ROUTE-USDT,KAR-USDT,EFX-USDT,XDC-BTC,SHFT-USDT,PMON-USDT,DPET-USDT,ERG-USDT,ERG-BTC,SOL-USDT,SLP-USDT,LITH-USDT,LITH-ETH,XCH-USDT,HAKA-USDT,LAYER-BTC,MTL-USDT,MTL-BTC,IOTX-USDT,GALA-USDT,REQ-USDT,TXA-USDT,TXA-USDC,CIRUS-USDT,QI-USDT,QI-BTC,ODDZ-USDT,PNT-USDT,PNT-BTC,XPR-USDT,XPR-BTC,TRIBE-USDT,SHFT-BTC,MOVR-USDT,MOVR-ETH,WOO-USDT,WILD-USDT,QRDO-USDT,QRDO-ETH,SDN-USDT,SDN-ETH,MAKI-USDT,MAKI-BTC,REP-USDT,REP-BTC,REP-ETH,BNT-USDT,BNT-BTC,BNT-ETH,OXT-USDT,OXT-BTC,OXT-ETH,BAL-USDT,BAL-BTC,BAL-ETH,STORJ-USDT,STORJ-BTC,STORJ-ETH,YGG-USDT,NDAU-USDT,SDAO-USDT,SDAO-ETH,XRP3L-USDT,XRP3S-USDT,SKL-USDT,SKL-BTC,NMR-USDT,NMR-BTC,IXS-USDT,TRB-USDT,TRB-BTC,DYDX-USDT,XYO-USDT,GTC-USDT,GTC-BTC,EQX-USDT,EQX-BTC,RLC-USDT,RLC-BTC,XPRT-USDT,EGLD-USDT,EGLD-BTC,HBAR-USDT,HBAR-BTC,DOGE3L-USDT,DOGE3S-USDT,FLOW-USDT,FLOW-BTC,NKN-USDT,NKN-BTC,PBX-USDT,SOL3L-USDT,SOL3S-USDT,MLN-USDT,MLN-BTC,XNL-USDT,SOLVE-USDT,WNCG-USDT,WNCG-BTC,DMTR-USDT,LINK3L-USDT,LINK3S-USDT,DOT3L-USDT,DOT3S-USDT,CTSI-USDT,CTSI-BTC,ALICE-USDT,ALICE-BTC,ALICE-ETH,OPUL-USDT,ILV-USDT,BAND-USDT,BAND-BTC,FTT-USDT,FTT-BTC,DVPN-USDT,SKU-USDT,SKU-BTC,EDG-USDT,SLIM-USDT,TLM-USDT,TLM-BTC,TLM-ETH,DEXE-USDT,DEXE-BTC,DEXE-ETH,MATTER-USDT,CUDOS-BTC,RUNE-USDT,RUNE-BTC,RMRK-USDT,BMON-USDT,C98-USDT,BLOK-USDT,SOLR-USDT,ATOM3L-USDT,ATOM3S-USDT,UNI3L-USDT,UNI3S-USDT,WSIENNA-USDT,PUSH-USDT,PUSH-BTC,FORM-ETH,NTVRK-USDT,NTVRK-USDC,AXS3L-USDT,AXS3S-USDT,FTM3L-USDT,FTM3S-USDT,FLAME-USDT,AGLD-USDT,NAKA-USDT,YLD-USDT,TONE-USDT,REEF-USDT,REEF-BTC,TIDAL-USDT,TVK-USDT,TVK-BTC,INJ-USDT,INJ-BTC,BNB3L-USDT,BNB3S-USDT,MATIC3L-USDT,MATIC3S-USDT,NFTB-USDT,VEGA-USDT,VEGA-ETH,ALPHA-USDT,ALPHA-BTC,BADGER-USDT,BADGER-BTC,UNO-BTC,ZKT-USDT,AR-USDT,AR-BTC,XVS-USDT,XVS-BTC,JASMY-USDT,PERP-USDT,PERP-BTC,GHST-USDT,GHST-BTC,SCLP-USDT,SCLP-BTC,SUPER-USDT,SUPER-BTC,CPOOL-USDT,HERO-USDT,BASIC-USDT,XED-USDT,XED-BTC,AURY-USDT,SWASH-USDT,LTO-USDT,LTO-BTC,BUX-USDT,MTRG-USDT,DREAMS-USDT,SHIB-DOGE,QUICK-USDT,QUICK-BTC,TRU-USDT,TRU-BTC,WRX-USDT,WRX-BTC,TKO-USDT,TKO-BTC,SUSHI3L-USDT,SUSHI3S-USDT,NEAR3L-USDT,NEAR3S-USDT,DATA-USDT,DATA-BTC,NORD-BTC,ISP-USDT,CERE-USDT,SHILL-USDT,HEGIC-USDT,HEGIC-BTC,ERN-USDT,ERN-BTC,FTG-USDT,PAXG-USDT,PAXG-BTC,AUDIO-USDT,AUDIO-BTC,ENS-USDT,AAVE3L-USDT,AAVE3S-USDT,SAND3L-USDT,SAND3S-USDT,XTM-USDT,MNW-USDT,FXS-USDT,FXS-BTC,ATA-USDT,ATA-BTC,VXV-USDT,LRC-BTC,LRC-ETH,DPR-USDT,CWAR-USDT,CWAR-BTC,FLUX-BTC,EDG-BTC,PBR-USDT,WNXM-USDT,WNXM-BTC,ANT-USDT,ANT-BTC,COV-USDT,SWP-USDT,TWT-USDT,TWT-BTC,OM-USDT,OM-BTC,ADX-USDT,AVAX3L-USDT,AVAX3S-USDT,MANA3L-USDT,MANA3S-USDT,GLM-USDT,GLM-BTC,BAKE-USDT,BAKE-BTC,BAKE-ETH,NUM-USDT,VLX-USDT,VLX-BTC,TRADE-USDT,TRADE-BTC,1EARTH-USDT,MONI-USDT,LIKE-USDT,MFT-USDT,MFT-BTC,LIT-USDT,LIT-BTC,KAVA-USDT,SFP-USDT,SFP-BTC,BURGER-USDT,BURGER-BTC,ILA-USDT,CREAM-USDT,CREAM-BTC,RSR-USDT,RSR-BTC,BUY-BTC,IMX-USDT,GODS-USDT,KMA-USDT,SRM-USDT,SRM-BTC,POLC-USDT,XTAG-USDT,MNET-USDT,NGC-USDT,HARD-USDT,GALAX3L-USDT,GALAX3S-USDT,UNIC-USDT,POND-USDT,POND-BTC,VR-USDT,EPIK-USDT,NGL-USDT,NGL-BTC,KDON-USDT,PEL-USDT,CIRUS-ETH,LINA-USDT,LINA-BTC,KLAY-USDT,KLAY-BTC,CREDI-USDT,TRVL-USDT,LACE-USDT,LACE-ETH,ARKER-USDT,BONDLY-USDT,BONDLY-ETH,XEC-USDT,HEART-USDT,HEART-BTC,UNB-USDT,GAFI-USDT,KOL-USDT,KOL-ETH,H3RO3S-USDT,FALCONS-USDT,UFO-USDT,CHMB-USDT,GEEQ-USDT,ORC-USDT,RACEFI-USDT,PEOPLE-USDT,ADS-USDT,ADS-BTC,OCEAN-USDT,SOS-USDT,WHALE-USDT,TIME-USDT,CWEB-USDT,IOTA-USDT,IOTA-BTC,OOKI-USDT,OOKI-BTC,HNT-USDT,HNT-BTC,GGG-USDT,POWR-USDT,REVU-USDT,CLH-USDT,PLGR-USDT,GLMR-USDT,GLMR-BTC,LOVE-USDT,CTC-USDT,CTC-BTC,GARI-USDT,FRR-USDT,ASTR-USDT,ASTR-BTC,ERTHA-USDT,FCON-USDT,ACA-USDT,ACA-BTC,MTS-USDT,ROAR-USDT,HBB-USDT,SURV-USDT,CVX-USDT,AMP-USDT,ACT-USDT,MJT-USDT,MJT-KCS,SHX-USDT,SHX-BTC,STARLY-USDT,ONSTON-USDT,RANKER-USDT,WMT-USDT,XNO-USDT,XNO-BTC,MARS4-USDT,TFUEL-USDT,TFUEL-BTC,METIS-USDT,LAVAX-USDT,WAL-USDT,BULL-USDT,SON-USDT,MELOS-USDT,APE-USDT,GMT-USDT,BICO-USDT,STG-USDT,LMR-USDT,LMR-BTC,LOKA-USDT,URUS-USDT,JAM-USDT,JAM-ETH,BNC-USDT,LBP-USDT,CFX-USDT,LOOKS-USDT,XCN-USDT,XCN-BTC,KP3R-USDT,TITAN-USDT,INDI-USDT,UPO-USDT,SPELL-USDT,SLCL-USDT,CEEK-USDT,VEMP-USDT,BETA-USDT,NHCT-USDT,ARNM-USDT,FRA-USDT,VISION-USDT,COCOS-USDT,ALPINE-USDT,BNX-USDT,ZBC-USDT,WOOP-USDT,T-USDT,NYM-USDT,VOXEL-USDT,VOXEL-ETH,PSTAKE-USDT,SPA-USDT,SPA-ETH,SYNR-USDT,DAR-USDT,DAR-BTC,MV-USDT,XDEFI-USDT,RACA-USDT,XWG-USDT,HAWK-USDT,TRVL-BTC,SWFTC-USDT,IDEX-USDT,BRWL-USDT,PLATO-USDT,TAUM-USDT,CELR-USDT,AURORA-USDT,POSI-USDT,COOHA-USDT,KNC-USDT,EPK-USDT,PLD-USDT,PSL-USDT,PKF-USDT,OVR-USDT,SYS-USDT,SYS-BTC,BRISE-USDT,DG-USDT,EPX-USDT,GST-USDT,PLY-USDT,GAL-USDT,BSW-USDT,FITFI-USDT,FSN-USDT,H2O-USDT,GMM-USDT,AKT-USDT,SIN-USDT,AUSD-USDT,BOBA-USDT,KARA-USDT,BFC-USDT,BIFI-USDT,DFA-USDT,KYL-USDT,FCD-USDT,MBL-USDT,CELT-USDT,DUSK-USDT,USDD-USDT,USDD-USDC,FITFI-USDC,MBOX-USDT,MBOX-BTC,APE-USDC,AVAX-USDC,SHIB-USDC,XCN-USDC,TRX-USDC,NEAR-USDC,MATIC-USDC,FTM-USDC,ZIL-USDC,SOL-USDC,MLS-USDT,AFK-USDT,AFK-USDC,ACH-USDT,SCRT-USDT,SCRT-BTC,APE3L-USDT,APE3S-USDT,STORE-USDT,STORE-ETH,GMT3L-USDT,GMT3S-USDT,CCD-USDT,DOSE-USDC,LUNC-USDT,LUNC-USDC,USTC-USDT,USTC-USDC,GMT-USDC,VRA-USDC,DOT-USDC,RUNE-USDC,ATOM-USDC,BNB-USDC,JASMY-USDC,KCS-USDC,KDA-USDC,ALGO-USDC,LUNA-USDC,OP-USDT,OP-USDC,JASMY3L-USDT,JASMY3S-USDT,EVER-USDT,MOOV-USDT,IHC-USDT,ICX-USDT,ICX-ETH,BTC-BRL,ETH-BRL,USDT-BRL,WELL-USDT,FORT-USDT,USDP-USDT,USDD-TRX,CSPR-USDT,CSPR-ETH,WEMIX-USDT,REV3L-USDT,OLE-USDT,LDO-USDT,LDO-USDC,CULT-USDT,SWFTC-USDC,FIDA-USDT,BUSD-USDT,RBP-USDT,SRBP-USDT,HIBAYC-USDT,BUSD-USDC,OGV-USDT,WOMBAT-USDT,HIPUNKS-USDT,FT-USDT,ETC-USDC,HIENS4-USDT,EGAME-USDT,EGAME-BTC,STEPWATCH-USDT,HISAND33-USDT,DC-USDT,NEER-USDT,RVN-USDT,HIENS3-USDT,MC-USDT,PEEL-USDT,PEEL-BTC,SDL-USDT,SDL-BTC,SWEAT-USDT,HIODBS-USDT,CMP-USDT,PIX-USDT,MPLX-USDT,HIDOODLES-USDT,ETHW-USDT,QUARTZ-USDT,ACQ-USDT,ACQ-USDC,AOG-USDT,HIMAYC-USDT,PRMX-USDT,RED-USDT,PUMLX-USDT,XETA-USDT,GEM-USDT,DERC-USDT,P00LS-USDT,P00LS-USDC,KICKS-USDT,TRIBL-USDT,GMX-USDT,HIOD-USDT,POKT-USDT,EFI-USDT,APT-USDT,BBC-USDT,EUL-USDT,TON-USDT,PIAS-USDT,HIMEEBITS-USDT,HISQUIGGLE-USDT,XCV-USDT,HFT-USDT,HFT-USDC,ECOX-USDT,AMB-USDT,AZERO-USDT,HIFIDENZA-USDT,BEAT-USDT", + "requestFormat": { + "uppercase": true, + "delimiter": "-" + }, + "configFormat": { + "uppercase": true, + "delimiter": "-" + } + }, + "futures": { + "assetEnabled": true, + "enabled": "ETH_USDCM,XBT_USDCM,SOL_USDTM", + "available": "XBT_USDTM,XBT_USDM,ETH_USDTM,BCH_USDTM,BSV_USDTM,LINK_USDTM,UNI_USDTM,YFI_USDTM,EOS_USDTM,DOT_USDTM,FIL_USDTM,ADA_USDTM,XRP_USDTM,LTC_USDTM,ETH_USDM,TRX_USDTM,GRT_USDTM,SUSHI_USDTM,XLM_USDTM,1INCH_USDTM,ZEC_USDTM,DASH_USDTM,DOT_USDM,XRP_USDM,AAVE_USDTM,KSM_USDTM,DOGE_USDTM,VET_USDTM,BNB_USDTM,SXP_USDTM,SOL_USDTM,CRV_USDTM,ALGO_USDTM,AVAX_USDTM,FTM_USDTM,MATIC_USDTM,THETA_USDTM,ATOM_USDTM,CHZ_USDTM,ENJ_USDTM,MANA_USDTM,DENT_USDTM,OCEAN_USDTM,BAT_USDTM,XEM_USDTM,QTUM_USDTM,XTZ_USDTM,SNX_USDTM,NEO_USDTM,ONT_USDTM,XMR_USDTM,COMP_USDTM,ETC_USDTM,WAVES_USDTM,BAND_USDTM,MKR_USDTM,RVN_USDTM,DGB_USDTM,SHIB_USDTM,ICP_USDTM,DYDX_USDTM,AXS_USDTM,HBAR_USDTM,EGLD_USDTM,ALICE_USDTM,YGG_USDTM,NEAR_USDTM,SAND_USDTM,C98_USDTM,ONE_USDTM,VRA_USDTM,GALA_USDTM,CHR_USDTM,LRC_USDTM,FLOW_USDTM,RNDR_USDTM,IOTX_USDTM,CRO_USDTM,WAXP_USDTM,PEOPLE_USDTM,OMG_USDTM,LINA_USDTM,IMX_USDTM,CELR_USDTM,ENS_USDTM,CELO_USDTM,CTSI_USDTM,ARPA_USDTM,KNC_USDTM,ROSE_USDTM,AGLD_USDTM,APE_USDTM,JASMY_USDTM,ZIL_USDTM,GMT_USDTM,RUNE_USDTM,LOOKS_USDTM,AUDIO_USDTM,KDA_USDTM,KAVA_USDTM,BAL_USDTM,GAL_USDTM,LUNA_USDTM,LUNC_USDTM,OP_USDTM,XCN_USDTM,UNFI_USDTM,LIT_USDTM,DUSK_USDTM,STORJ_USDTM,RSR_USDTM,OGN_USDTM,TRB_USDTM,PERP_USDTM,KLAY_USDTM,ANKR_USDTM,LDO_USDTM,WOO_USDTM,REN_USDTM,CVC_USDTM,INJ_USDTM,APT_USDTM,MASK_USDTM,REEF_USDTM,TON_USDTM,MAGIC_USDTM,CFX_USDTM,AGIX_USDTM,FXS_USDTM,FET_USDTM,AR_USDTM,GMX_USDTM,BLUR_USDTM,ASTR_USDTM,HIGH_USDTM,ACH_USDTM,STX_USDTM,SSV_USDTM,FLOKI_USDTM,CKB_USDTM,TRU_USDTM,QNT_USDTM,ETH_USDCM,MINA_USDTM,USDC_USDTM,T_USDTM,LQTY_USDTM,ARB_USDTM,DAR_USDTM,ID_USDTM,STG_USDTM,JOE_USDTM,RDNT_USDTM,DODO_USDTM,PAXG_USDTM,ZRX_USDTM,ICX_USDTM,HFT_USDTM,NKN_USDTM,HOOK_USDTM,ANT_USDTM,DC_USDTM,BEL_USDTM,SUI_USDTM,PEPE_USDTM,IDEX_USDTM,GNS_USDTM,CETUS_USDTM,KAS_USDTM,ORDI_USDTM,WOJAK_USDTM,POGAI_USDTM,UMA_USDTM,RAD_USDTM,XBT_USDCM,PHB_USDTM,FTT_USDTM,10000LADYS_USDTM,LEVER_USDTM,TURBO_USDTM,TOMO_USDTM,BOB_USDTM,KEY_USDTM,EDU_USDTM,MTL_USDTM,FLUX_USDTM,COMBO_USDTM,AMB_USDTM,ALPHA_USDTM,SFP_USDTM,MAV_USDTM,MDT_USDTM,XEC_USDTM,XVG_USDTM,1000PEPE2_USDTM,PENDLE_USDTM,STMX_USDTM,WLD_USDTM,LPT_USDTM,GTC_USDTM,BNT_USDTM,OXT_USDTM,BLZ_USDTM,SEI_USDTM,BAKE_USDTM,CYBER_USDTM,NMR_USDTM,FLM_USDTM,SPELL_USDTM,ARK_USDTM,XBT_MU23,XBT_MZ23", + "requestFormat": { + "uppercase": true, + "delimiter": "" + }, + "configFormat": { + "uppercase": true, + "delimiter": "_" + } + } + } + }, + "api": { + "authenticatedSupport": true, + "authenticatedWebsocketApiSupport": true, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret", + "clientID": "ClientID" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "tickerBatching": true, + "autoPairUpdates": true + }, + "websocketAPI": true, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": true + } + }, + "bankAccounts": [ + { + "enabled": false, + "bankName": "", + "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ] + }, + { + "name": "LBank", + "enabled": true, + "verbose": false, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, + "websocketOrderbookBufferLimit": 5, + "baseCurrencies": "USD", + "currencyPairs": { + "requestFormat": { + "uppercase": false, + "delimiter": "_" + }, + "configFormat": { + "uppercase": false, + "delimiter": "_" + }, + "useGlobalFormat": true, + "assetTypes": [ + "spot" + ], + "pairs": { + "spot": { + "enabled": "eth_btc", + "available": "FBC_USDT,GALT_USDT,IOEX_USDT,OATH_USDT,BLOC_USDT,BTC_USDT,ETH_USDT,ETH_BTC,ABBC_BTC,KISC_ETH,BXA_USDT,ATP_USDT,MAT_USDT,SKY_BTC,RNT_USDT,VENA_USDT,GRIN_USDT,IDA_USDT,PNT_USDT,OPX_USDT,VTHO_BTC,AMO_ETH,UBEX_BTC,EOS_BTC,UBEX_USDT,TNS_BTC,SAIT_ETH,DAX_BTC,DAX_ETH,DALI_USDT,VET_USDT,BCH_BTC,BCH_USDT,NEO_USDT,QTUM_USDT,ZEC_USDT,VET_BTC,PAI_BTC,PNT_BTC,NEO_BTC,DASH_BTC,LTC_BTC,ETC_BTC,QTUM_BTC,ZEC_BTC,SC_BTC,BTS_BTC,CPX_BTC,XWC_BTC,FIL6_BTC,FIL12_BTC,FIL36_BTC,EOS_USDT,UT_ETH,ELA_ETH,VET_ETH,VTHO_ETH,PAI_ETH,HER_ETH,PTT_ETH,TAC_ETH,IDHUB_ETH,SSC_ETH,SKM_ETH,PLY_ETH,EXT_ETH,EOS_ETH,YOYOW_ETH,TRX_ETH,QTUM_ETH,ZEC_ETH,BTS_ETH,BTM_ETH,MITH_ETH,NAS_ETH,MAN_ETH,DBC_ETH,BTO_ETH,DDD_ETH,CPX_ETH,CS_ETH,IHT_ETH,OCN_ETH,EKO_ETH,XWC_ETH,PUT_ETH,PNT_ETH,AAC_ETH,FIL6_ETH,FIL12_ETH,FIL36_ETH,SEER_ETH,BSB_ETH,CDC_ETH,GRAMS_ETH,DDMX_ETH,EAI_ETH,BNB_USDT,HT_USDT,KBC_BTC,KBC_USDT,MAI_USDT,PHV_USDT,GT_USDT,VOKEN_USDT,CYE_USDT,BRC_USDT,BTC_AUSD,DDMX_USDT,SEAL_USDT,SEOS_BTC,BTY_USDT,FO_USDT,DLX_USDT,BFC_USDT,LBK_USDT,SERO_USDT,MTV_USDT,CKB_USDT,ARPA_USDT,ZIP_USDT,AT_USDT,DOT_USDT,DILI_USDT,DUO_USDT,TEP_USDT,BIKI_USDT,MX_USDT,DNS_USDT,OKB_USDT,FLDT_USDT,CCTC_USDT,WIN_USDT,BTT_USDT,TRX_USDT,GRS_BTC,GST_USDT,GST_ETH,ABBC_USDT,UTK_USDT,GKI_USDT,BPX_USDT,SUTER_USDT,LT_USDT,LM_USDT,HTDF_USDT" + } + } + }, + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "tickerBatching": true, + "autoPairUpdates": true + }, + "websocketAPI": false, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": false + } + }, + "bankAccounts": [ + { + "enabled": false, + "bankName": "", + "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ] + }, + { + "name": "Okcoin", + "enabled": true, + "verbose": false, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, + "websocketOrderbookBufferLimit": 5, + "baseCurrencies": "USD", + "currencyPairs": { + "requestFormat": { + "uppercase": true, + "delimiter": "-" + }, + "configFormat": { + "uppercase": true, + "delimiter": "-" + }, + "useGlobalFormat": true, + "assetTypes": [ + "spot" + ], + "pairs": { + "spot": { + "enabled": "BTC-USD", + "available": "BTC-USD,LTC-USD,ETH-USD,ETC-USD,TUSD-USD,BCH-USD,EOS-USD,XRP-USD,TRX-USD,BSV-USD,USDT-USD,USDK-USD,XLM-USD,ADA-USD,BAT-USD,DCR-USD,EURS-USD,HBAR-USD,PAX-USD,USDC-USD,ZEC-USD,BTC-USDT,BTC-SGD,ETH-SGD,BTC-EUR,BTC-EURS,ETH-EUR,BCH-EUR,EURS-EUR" + }, + "margin": { + "enabled": "BTC-USD", + "available": "BTC-USD" + } + } + }, + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true, + "requiresClientID": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "tickerBatching": true, + "autoPairUpdates": true + }, + "websocketAPI": true, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": true + } + }, + "bankAccounts": [ + { + "enabled": false, + "bankName": "", + "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ] + }, + { + "name": "Okx", + "enabled": true, + "verbose": false, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, + "baseCurrencies": "USD", + "currencyPairs": { + "bypassConfigFormatUpgrades": false, + "requestFormat": { + "uppercase": true, + "delimiter": "-" + }, + "configFormat": { + "uppercase": true, + "delimiter": "-" + }, + "useGlobalFormat": true, + "assetTypes": [ + "futures", + "margin", + "option", + "perpetualswap", + "spot" + ], + "pairs": { + "futures": { + "assetEnabled": true, + "enabled": "BTC-USD-221007,BTC-USD-221014", + "available": "BTC-USD-221230,BTC-USD-230331,ETH-USD-221007,ETH-USD-221014,ETH-USD-221230,ETH-USD-230331,LTC-USD-221007,LTC-USD-221014,LTC-USD-221230,LTC-USD-230331,DOT-USD-221007,DOT-USD-221014,DOT-USD-221230,DOT-USD-230331,FIL-USD-221007,FIL-USD-221014,FIL-USD-221230,FIL-USD-230331,ADA-USD-221007,ADA-USD-221014,ADA-USD-221230,ADA-USD-230331,AVAX-USD-221007,AVAX-USD-221014,AVAX-USD-221230,BTC-USD-221007,BTC-USD-221014,BTC-USD-221230,BTC-USD-230331,ETH-USD-221007,ETH-USD-221014,ETH-USD-221230,ETH-USD-230331,LTC-USD-221007,LTC-USD-221014,LTC-USD-221230,LTC-USD-230331,DOT-USD-221007,DOT-USD-221014,DOT-USD-221230,DOT-USD-230331,FIL-USD-221007,FIL-USD-221014,FIL-USD-221230,FIL-USD-230331,ADA-USD-221007,ADA-USD-221014,ADA-USD-221230,ADA-USD-230331,AVAX-USD-221007,AVAX-USD-221014,AVAX-USD-221230,AVAX-USD-230331,BCH-USD-221007,BCH-USD-221014,BCH-USD-221230,BCH-USD-230331,EOS-USD-221007,EOS-USD-221014,EOS-USD-221230,EOS-USD-230331,ETC-USD-221007,ETC-USD-221014,ETC-USD-221230,ETC-USD-230331,LINK-USD-221007,LINK-USD-221014,LINK-USD-221230,LINK-USD-230331,SOL-USD-221007,SOL-USD-221014,SOL-USD-221230,SOL-USD-230331,TRX-USD-221007,TRX-USD-221014,TRX-USD-221230,TRX-USD-230331,XRP-USD-221007,XRP-USD-221014,XRP-USD-221230,XRP-USD-230331,BTC-USDT-221007,BTC-USDT-221014,BTC-USDT-221230,BTC-USDT-230331,ETH-USDT-221007,ETH-USDT-221014,ETH-USDT-221230,ETH-USDT-230331,LTC-USDT-221007,LTC-USDT-221014,LTC-USDT-221230,LTC-USDT-230331,DOT-USDT-221007,DOT-USDT-221014,DOT-USDT-221230,DOT-USDT-230331,FIL-USDT-221007,FIL-USDT-221014,FIL-USDT-221230,FIL-USDT-230331,ADA-USDT-221007,ADA-USDT-221014,ADA-USDT-221230,ADA-USDT-230331,BCH-USDT-221007,BCH-USDT-221014,BCH-USDT-221230,BCH-USDT-230331,EOS-USDT-221007,EOS-USDT-221014,EOS-USDT-221230,EOS-USDT-230331,ETC-USDT-221007,ETC-USDT-221014,ETC-USDT-221230,ETC-USDT-230331,LINK-USDT-221007,LINK-USDT-221014,LINK-USDT-221230,LINK-USDT-230331,TRX-USDT-221007,TRX-USDT-221014,TRX-USDT-221230,TRX-USDT-230331,XRP-USDT-221007,XRP-USDT-221014,XRP-USDT-221230,XRP-USDT-230331" + }, + "margin": { + "assetEnabled": true, + "enabled": "BTC-USDT,ETH-USDT,OKB-USDT", + "available": "LTC-USDT,DOT-USDT,DOGE-USDT,LUNC-USDT,ETHW-USDT,LUNA-USDT,FIL-USDT,XRP-USDT,1INCH-USDT,AAVE-USDT,ADA-USDT,AGLD-USDT,AKITA-USDT,ALGO-USDT,ALPHA-USDT,BTC-USDT,ETH-USDT,OKB-USDT,LTC-USDT,DOT-USDT,DOGE-USDT,LUNC-USDT,ETHW-USDT,LUNA-USDT,FIL-USDT,XRP-USDT,1INCH-USDT,AAVE-USDT,ADA-USDT,AGLD-USDT,AKITA-USDT,ALGO-USDT,ALPHA-USDT,ANT-USDT,APE-USDT,API3-USDT,ASTR-USDT,ATOM-USDT,AVAX-USDT,AXS-USDT,BABYDOGE-USDT,BADGER-USDT,BAL-USDT,BAND-USDT,BAT-USDT,BCH-USDT,BICO-USDT,BNT-USDT,BSV-USDT,BTM-USDT,BTT-USDT,BZZ-USDT,CELO-USDT,CEL-USDT,CELR-USDT,CFX-USDT,CHZ-USDT,CLV-USDT,COMP-USDT,CONV-USDT,CQT-USDT,CRO-USDT,CRV-USDT,CSPR-USDT,CVC-USDT,DASH-USDT,DOME-USDT,DORA-USDT,DYDX-USDT,EFI-USDT,EGLD-USDT,ELF-USDT,ENJ-USDT,ENS-USDT,EOS-USDT,ETC-USDT,FITFI-USDT,FLM-USDT,FLOW-USDT,FTM-USDT,GALA-USDT,GLMR-USDT,GMT-USDT,GODS-USDT,GRT-USDT,HBAR-USDT,HC-USDT,ICP-USDT,IMX-USDT,IOST-USDT,IOTA-USDT,JST-USDT,KAR-USDT,KISHU-USDT,KNC-USDT,KSM-USDT,LAMB-USDT,LAT-USDT,LINK-USDT,LON-USDT,LOOKS-USDT,LPT-USDT,LRC-USDT,MANA-USDT,MASK-USDT,MATIC-USDT,MINA-USDT,MKR-USDT,NAS-USDT,NEAR-USDT,NEO-USDT,NFT-USDT,NYM-USDT,OMG-USDT,ONT-USDT,OP-USDT,PEOPLE-USDT,PERP-USDT,QTUM-USDT,REN-USDT,RSR-USDT,RSS3-USDT,RVN-USDT,SAND-USDT,SC-USDT,SHIB-USDT,SKL-USDT,SLP-USDT,SNT-USDT,SNX-USDT,SOL-USDT,SOS-USDT,SRM-USDT,STARL-USDT,STORJ-USDT,SUSHI-USDT,SWEAT-USDT,SWRV-USDT,THETA-USDT,TORN-USDT,TRB-USDT,TRX-USDT,UMA-USDT,UMEE-USDT,UNI-USDT,USDC-USDT,VSYS-USDT,WAVES-USDT,WNCG-USDT,WNXM-USDT,XCH-USDT,XEM-USDT,XLM-USDT,XMR-USDT,XTZ-USDT,YFI-USDT,YFII-USDT,YGG-USDT,ZEC-USDT,ZEN-USDT,ZIL-USDT,ZRX-USDT,BTC-USDC,ETH-BTC,LTC-USDC,DOT-USDC,DOGE-USDC,LUNC-USDC,LUNA-USDC,XRP-USDC,ADA-USDC,ATOM-USDC,AVAX-USDC,NEAR-USDC,OP-USDC,SOL-USDC,OKB-BTC,LTC-BTC,DOT-BTC,DOGE-BTC,FIL-BTC,XRP-BTC,AAVE-BTC,ADA-BTC,ALGO-BTC,ANT-BTC,ATOM-BTC,AVAX-BTC,BADGER-BTC,BAT-BTC,BCH-BTC,BNT-BTC,BSV-BTC,BTM-BTC,CHZ-BTC,COMP-BTC,CRO-BTC,CRV-BTC,CVC-BTC,DASH-BTC,EGLD-BTC,ELF-BTC,ENJ-BTC,EOS-BTC,ETC-BTC,GRT-BTC,HBAR-BTC,HC-BTC,ICP-BTC,IOST-BTC,IOTA-BTC,KNC-BTC,KSM-BTC,LINK-BTC,LRC-BTC,MANA-BTC,MKR-BTC,NAS-BTC,NEAR-BTC,NEO-BTC,OMG-BTC,ONT-BTC,QTUM-BTC,REN-BTC,RSR-BTC,RVN-BTC,SNT-BTC,SOL-BTC,SRM-BTC,THETA-BTC,TRX-BTC,UNI-BTC,VSYS-BTC,WAVES-BTC,XCH-BTC,XEM-BTC,XLM-BTC,XMR-BTC,XTZ-BTC,ZEC-BTC,ZIL-BTC,ZRX-BTC" + }, + "option": { + "assetEnabled": true, + "enabled": "BTC-USD-220930-28000-P,BTC-USD-220930-30000-C", + "available": "BTC-USD-220930-30000-P,BTC-USD-220930-32000-C,BTC-USD-220930-32000-P,BTC-USD-220930-34000-C,BTC-USD-220930-34000-P,BTC-USD-220930-35000-C,BTC-USD-220930-35000-P,BTC-USD-220930-36000-C,BTC-USD-220930-36000-P,BTC-USD-220930-40000-C,BTC-USD-220930-40000-P,BTC-USD-220930-45000-C,BTC-USD-220930-45000-P,BTC-USD-220930-50000-C,BTC-USD-220930-50000-P,BTC-USD-220930-55000-C,BTC-USD-220930-55000-P,BTC-USD-220930-60000-C,BTC-USD-220930-60000-P,BTC-USD-220930-65000-C,BTC-USD-220930-65000-P,BTC-USD-220930-70000-C,BTC-USD-220930-70000-P,BTC-USD-220930-80000-C,BTC-USD-220930-80000-P,BTC-USD-220930-90000-C,BTC-USD-220930-90000-P,BTC-USD-220930-100000-C,BTC-USD-220930-100000-P,BTC-USD-220930-120000-C,BTC-USD-220930-120000-P,BTC-USD-221007-13000-C,BTC-USD-221007-13000-P,BTC-USD-221007-14000-C,BTC-USD-221007-14000-P,BTC-USD-221007-15000-C,BTC-USD-221007-15000-P,BTC-USD-221007-16000-C,BTC-USD-221007-16000-P,BTC-USD-221007-17000-C,BTC-USD-221007-17000-P,BTC-USD-221007-18000-C,BTC-USD-221007-18000-P,BTC-USD-221007-18500-C,BTC-USD-221007-18500-P,BTC-USD-221007-19000-C,BTC-USD-221007-19000-P,BTC-USD-221007-20000-C,BTC-USD-221007-20000-P,BTC-USD-221007-21000-C,BTC-USD-221007-21000-P,BTC-USD-221007-22000-C,BTC-USD-221007-22000-P,BTC-USD-221007-23000-C,BTC-USD-221007-23000-P,BTC-USD-221007-24000-C,BTC-USD-221007-24000-P,BTC-USD-221007-26000-C,BTC-USD-221007-26000-P,BTC-USD-221007-28000-C,BTC-USD-221007-28000-P,BTC-USD-221007-30000-C,BTC-USD-221007-30000-P,BTC-USD-221014-14000-C,BTC-USD-221014-14000-P,BTC-USD-221014-15000-C,BTC-USD-221014-15000-P,BTC-USD-221014-16000-C,BTC-USD-221014-16000-P,BTC-USD-221014-17000-C,BTC-USD-221014-17000-P,BTC-USD-221014-18000-C,BTC-USD-221014-18000-P,BTC-USD-221014-19000-C,BTC-USD-221014-19000-P,BTC-USD-221014-20000-C,BTC-USD-221014-20000-P,BTC-USD-221014-21000-C,BTC-USD-221014-21000-P,BTC-USD-221014-22000-C,BTC-USD-221014-22000-P,BTC-USD-221014-23000-C,BTC-USD-221014-23000-P,BTC-USD-221014-24000-C,BTC-USD-221014-24000-P,BTC-USD-221014-26000-C,BTC-USD-221014-26000-P,BTC-USD-221014-28000-C,BTC-USD-221014-28000-P,BTC-USD-221014-30000-C,BTC-USD-221014-30000-P,BTC-USD-221028-10000-C,BTC-USD-221028-10000-P,BTC-USD-221028-12000-C,BTC-USD-221028-12000-P,BTC-USD-221028-14000-C,BTC-USD-221028-14000-P,BTC-USD-221028-16000-C,BTC-USD-221028-16000-P,BTC-USD-221028-17000-C,BTC-USD-221028-17000-P,BTC-USD-221028-18000-C,BTC-USD-221028-18000-P,BTC-USD-221028-19000-C,BTC-USD-221028-19000-P,BTC-USD-221028-20000-C,BTC-USD-221028-20000-P,BTC-USD-221028-21000-C,BTC-USD-221028-21000-P,BTC-USD-221028-22000-C,BTC-USD-221028-22000-P,BTC-USD-221028-24000-C,BTC-USD-221028-24000-P,BTC-USD-221028-26000-C,BTC-USD-221028-26000-P,BTC-USD-221028-28000-C,BTC-USD-221028-28000-P,BTC-USD-221028-30000-C,BTC-USD-221028-30000-P,BTC-USD-221028-32000-C,BTC-USD-221028-32000-P,BTC-USD-221028-35000-C,BTC-USD-220927-17000-C,BTC-USD-220927-17000-P,BTC-USD-220927-17500-C,BTC-USD-220927-17500-P,BTC-USD-220927-18000-C,BTC-USD-220927-18000-P,BTC-USD-220927-18200-C,BTC-USD-220927-18200-P,BTC-USD-220927-18400-C,BTC-USD-220927-18400-P,BTC-USD-220927-18600-C,BTC-USD-220927-18600-P,BTC-USD-220927-18800-C,BTC-USD-220927-18800-P,BTC-USD-220927-19000-C,BTC-USD-220927-19000-P,BTC-USD-220927-19200-C,BTC-USD-220927-19200-P,BTC-USD-220927-19400-C,BTC-USD-220927-19400-P,BTC-USD-220927-19600-C,BTC-USD-220927-19600-P,BTC-USD-220927-19800-C,BTC-USD-220927-19800-P,BTC-USD-220927-20000-C,BTC-USD-220927-20000-P,BTC-USD-220927-20500-C,BTC-USD-220927-20500-P,BTC-USD-220927-21000-C,BTC-USD-220927-21000-P,BTC-USD-220927-21500-C,BTC-USD-220927-21500-P,BTC-USD-220928-16500-C,BTC-USD-220928-16500-P,BTC-USD-220928-17000-C,BTC-USD-220928-17000-P,BTC-USD-220928-17500-C,BTC-USD-220928-17500-P,BTC-USD-220928-18000-C,BTC-USD-220928-18000-P,BTC-USD-220928-18200-C,BTC-USD-220928-18200-P,BTC-USD-220928-18400-C,BTC-USD-220928-18400-P,BTC-USD-220928-18600-C,BTC-USD-220928-18600-P,BTC-USD-220928-18800-C,BTC-USD-220928-18800-P,BTC-USD-220928-19000-C,BTC-USD-220928-19000-P,BTC-USD-220928-19200-C,BTC-USD-220928-19200-P,BTC-USD-220928-19400-C,BTC-USD-220928-19400-P,BTC-USD-220928-19600-C,BTC-USD-220928-19600-P,BTC-USD-220928-20000-C,BTC-USD-220928-20000-P,BTC-USD-220928-20500-C,BTC-USD-220928-20500-P,BTC-USD-220928-21000-C,BTC-USD-220928-21000-P,BTC-USD-220928-21500-C,BTC-USD-220928-21500-P,BTC-USD-220930-5000-C,BTC-USD-220930-5000-P,BTC-USD-220930-10000-C,BTC-USD-220930-10000-P,BTC-USD-220930-12000-C,BTC-USD-220930-12000-P,BTC-USD-220930-15000-C,BTC-USD-220930-15000-P,BTC-USD-220930-16000-C,BTC-USD-220930-16000-P,BTC-USD-220930-17000-C,BTC-USD-220930-17000-P,BTC-USD-220930-17500-C,BTC-USD-220930-17500-P,BTC-USD-220930-18000-C,BTC-USD-220930-18000-P,BTC-USD-220930-18500-C,BTC-USD-220930-18500-P,BTC-USD-220930-19000-C,BTC-USD-220930-19000-P,BTC-USD-220930-19500-C,BTC-USD-220930-19500-P,BTC-USD-220930-20000-C,BTC-USD-220930-20000-P,BTC-USD-220930-20500-C,BTC-USD-220930-20500-P,BTC-USD-220930-21000-C,BTC-USD-220930-21000-P,BTC-USD-220930-22000-C,BTC-USD-220930-22000-P,BTC-USD-220930-23000-C,BTC-USD-220930-23000-P,BTC-USD-220930-24000-C,BTC-USD-220930-24000-P,BTC-USD-220930-25000-C,BTC-USD-220930-25000-P,BTC-USD-220930-26000-C,BTC-USD-220930-26000-P,BTC-USD-220930-28000-C,BTC-USD-220930-28000-P,BTC-USD-220930-30000-C,BTC-USD-220930-30000-P,BTC-USD-220930-32000-C,BTC-USD-220930-32000-P,BTC-USD-220930-34000-C,BTC-USD-220930-34000-P,BTC-USD-220930-35000-C,BTC-USD-220930-35000-P,BTC-USD-220930-36000-C,BTC-USD-220930-36000-P,BTC-USD-220930-40000-C,BTC-USD-220930-40000-P,BTC-USD-220930-45000-C,BTC-USD-220930-45000-P,BTC-USD-220930-50000-C,BTC-USD-220930-50000-P,BTC-USD-220930-55000-C,BTC-USD-220930-55000-P,BTC-USD-220930-60000-C,BTC-USD-220930-60000-P,BTC-USD-220930-65000-C,BTC-USD-220930-65000-P,BTC-USD-220930-70000-C,BTC-USD-220930-70000-P,BTC-USD-220930-80000-C,BTC-USD-220930-80000-P,BTC-USD-220930-90000-C,BTC-USD-220930-90000-P,BTC-USD-220930-100000-C,BTC-USD-220930-100000-P,BTC-USD-220930-120000-C,BTC-USD-220930-120000-P,BTC-USD-221007-13000-C,BTC-USD-221007-13000-P,BTC-USD-221007-14000-C,BTC-USD-221007-14000-P,BTC-USD-221007-15000-C,BTC-USD-221007-15000-P,BTC-USD-221007-16000-C,BTC-USD-221007-16000-P,BTC-USD-221007-17000-C,BTC-USD-221007-17000-P,BTC-USD-221007-18000-C,BTC-USD-221007-18000-P,BTC-USD-221007-18500-C,BTC-USD-221007-18500-P,BTC-USD-221007-19000-C,BTC-USD-221007-19000-P,BTC-USD-221007-20000-C,BTC-USD-221007-20000-P,BTC-USD-221007-21000-C,BTC-USD-221007-21000-P,BTC-USD-221007-22000-C,BTC-USD-221007-22000-P,BTC-USD-221007-23000-C,BTC-USD-221007-23000-P,BTC-USD-221007-24000-C,BTC-USD-221007-24000-P,BTC-USD-221007-26000-C,BTC-USD-221007-26000-P,BTC-USD-221007-28000-C,BTC-USD-221007-28000-P,BTC-USD-221007-30000-C,BTC-USD-221007-30000-P,BTC-USD-221014-14000-C,BTC-USD-221014-14000-P,BTC-USD-221014-15000-C,BTC-USD-221014-15000-P,BTC-USD-221014-16000-C,BTC-USD-221014-16000-P,BTC-USD-221014-17000-C,BTC-USD-221014-17000-P,BTC-USD-221014-18000-C,BTC-USD-221014-18000-P,BTC-USD-221014-19000-C,BTC-USD-221014-19000-P,BTC-USD-221014-20000-C,BTC-USD-221014-20000-P,BTC-USD-221014-21000-C,BTC-USD-221014-21000-P,BTC-USD-221014-22000-C,BTC-USD-221014-22000-P,BTC-USD-221014-23000-C,BTC-USD-221014-23000-P,BTC-USD-221014-24000-C,BTC-USD-221014-24000-P,BTC-USD-221014-26000-C,BTC-USD-221014-26000-P,BTC-USD-221014-28000-C,BTC-USD-221014-28000-P,BTC-USD-221014-30000-C,BTC-USD-221014-30000-P,BTC-USD-221028-10000-C,BTC-USD-221028-10000-P,BTC-USD-221028-12000-C,BTC-USD-221028-12000-P,BTC-USD-221028-14000-C,BTC-USD-221028-14000-P,BTC-USD-221028-16000-C,BTC-USD-221028-16000-P,BTC-USD-221028-17000-C,BTC-USD-221028-17000-P,BTC-USD-221028-18000-C,BTC-USD-221028-18000-P,BTC-USD-221028-19000-C,BTC-USD-221028-19000-P,BTC-USD-221028-20000-C,BTC-USD-221028-20000-P,BTC-USD-221028-21000-C,BTC-USD-221028-21000-P,BTC-USD-221028-22000-C,BTC-USD-221028-22000-P,BTC-USD-221028-24000-C,BTC-USD-221028-24000-P,BTC-USD-221028-26000-C,BTC-USD-221028-26000-P,BTC-USD-221028-28000-C,BTC-USD-221028-28000-P,BTC-USD-221028-30000-C,BTC-USD-221028-30000-P,BTC-USD-221028-32000-C,BTC-USD-221028-32000-P,BTC-USD-221028-35000-C,BTC-USD-221028-35000-P,BTC-USD-221028-40000-C,BTC-USD-221028-40000-P,BTC-USD-221028-50000-C,BTC-USD-221028-50000-P,BTC-USD-221028-60000-C,BTC-USD-221028-60000-P,BTC-USD-221028-70000-C,BTC-USD-221028-70000-P,BTC-USD-221125-5000-C,BTC-USD-221125-5000-P,BTC-USD-221125-10000-C,BTC-USD-221125-10000-P,BTC-USD-221125-12000-C,BTC-USD-221125-12000-P,BTC-USD-221125-15000-C,BTC-USD-221125-15000-P,BTC-USD-221125-16000-C,BTC-USD-221125-16000-P,BTC-USD-221125-17000-C,BTC-USD-221125-17000-P,BTC-USD-221125-18000-C,BTC-USD-221125-18000-P,BTC-USD-221125-20000-C,BTC-USD-221125-20000-P,BTC-USD-221125-22000-C,BTC-USD-221125-22000-P,BTC-USD-221125-24000-C,BTC-USD-221125-24000-P,BTC-USD-221125-26000-C,BTC-USD-221125-26000-P,BTC-USD-221125-28000-C,BTC-USD-221125-28000-P,BTC-USD-221125-30000-C,BTC-USD-221125-30000-P,BTC-USD-221125-32000-C,BTC-USD-221125-32000-P,BTC-USD-221125-35000-C,BTC-USD-221125-35000-P,BTC-USD-221125-40000-C,BTC-USD-221125-40000-P,BTC-USD-221125-50000-C,BTC-USD-221125-50000-P,BTC-USD-221125-60000-C,BTC-USD-221125-60000-P,BTC-USD-221125-70000-C,BTC-USD-221125-70000-P,BTC-USD-221230-5000-C,BTC-USD-221230-5000-P,BTC-USD-221230-10000-C,BTC-USD-221230-10000-P,BTC-USD-221230-12000-C,BTC-USD-221230-12000-P,BTC-USD-221230-13000-C,BTC-USD-221230-13000-P,BTC-USD-221230-15000-C,BTC-USD-221230-15000-P,BTC-USD-221230-16000-C,BTC-USD-221230-16000-P,BTC-USD-221230-17000-C,BTC-USD-221230-17000-P,BTC-USD-221230-18000-C,BTC-USD-221230-18000-P,BTC-USD-221230-19000-C,BTC-USD-221230-19000-P,BTC-USD-221230-20000-C,BTC-USD-221230-20000-P,BTC-USD-221230-21000-C,BTC-USD-221230-21000-P,BTC-USD-221230-22000-C,BTC-USD-221230-22000-P,BTC-USD-221230-23000-C,BTC-USD-221230-23000-P,BTC-USD-221230-24000-C,BTC-USD-221230-24000-P,BTC-USD-221230-25000-C,BTC-USD-221230-25000-P,BTC-USD-221230-26000-C,BTC-USD-221230-26000-P,BTC-USD-221230-28000-C,BTC-USD-221230-28000-P,BTC-USD-221230-30000-C,BTC-USD-221230-30000-P,BTC-USD-221230-32000-C,BTC-USD-221230-32000-P,BTC-USD-221230-35000-C,BTC-USD-221230-35000-P" + }, + "perpetualswap": { + "assetEnabled": true, + "enabled": "BTC-USD-SWAP,ETH-USD-SWAP", + "available": "LTC-USD-SWAP,DOT-USD-SWAP,DOGE-USD-SWAP,MASK-USDT-SWAP,MATIC-USDT-SWAP,MINA-USDT-SWAP,MKR-USDT-SWAP,NEAR-USDT-SWAP,NEO-USDT-SWAP,NFT-USDT-SWAP,NYM-USDT-SWAP,OMG-USDT-SWAP,ONT-USDT-SWAP,OP-USDT-SWAP,PEOPLE-USDT-SWAP,BTC-USD-SWAP,ETH-USD-SWAP,LTC-USD-SWAP,DOT-USD-SWAP,DOGE-USD-SWAP,FIL-USD-SWAP,XRP-USD-SWAP,1INCH-USD-SWAP,ADA-USD-SWAP,ALGO-USD-SWAP,ATOM-USD-SWAP,AVAX-USD-SWAP,BCH-USD-SWAP,BSV-USD-SWAP,CRV-USD-SWAP,DASH-USD-SWAP,EOS-USD-SWAP,ETC-USD-SWAP,GRT-USD-SWAP,IOST-USD-SWAP,IOTA-USD-SWAP,KNC-USD-SWAP,KSM-USD-SWAP,LINK-USD-SWAP,MANA-USD-SWAP,NEO-USD-SWAP,ONT-USD-SWAP,QTUM-USD-SWAP,SAND-USD-SWAP,SOL-USD-SWAP,SUSHI-USD-SWAP,THETA-USD-SWAP,TRX-USD-SWAP,UNI-USD-SWAP,XLM-USD-SWAP,XMR-USD-SWAP,XTZ-USD-SWAP,YFI-USD-SWAP,YFII-USD-SWAP,ZEC-USD-SWAP,BTC-USDT-SWAP,ETH-USDT-SWAP,LTC-USDT-SWAP,DOT-USDT-SWAP,DOGE-USDT-SWAP,LUNC-USDT-SWAP,ETHW-USDT-SWAP,LUNA-USDT-SWAP,FIL-USDT-SWAP,XRP-USDT-SWAP,1INCH-USDT-SWAP,AAVE-USDT-SWAP,ADA-USDT-SWAP,AGLD-USDT-SWAP,ALGO-USDT-SWAP,ALPHA-USDT-SWAP,ANT-USDT-SWAP,APE-USDT-SWAP,API3-USDT-SWAP,ASTR-USDT-SWAP,ATOM-USDT-SWAP,AVAX-USDT-SWAP,AXS-USDT-SWAP,BABYDOGE-USDT-SWAP,BADGER-USDT-SWAP,BAL-USDT-SWAP,BAND-USDT-SWAP,BAT-USDT-SWAP,BCH-USDT-SWAP,BICO-USDT-SWAP,BNT-USDT-SWAP,BSV-USDT-SWAP,BTT-USDT-SWAP,CELO-USDT-SWAP,CEL-USDT-SWAP,CFX-USDT-SWAP,CHZ-USDT-SWAP,COMP-USDT-SWAP,CRO-USDT-SWAP,CRV-USDT-SWAP,CSPR-USDT-SWAP,CVC-USDT-SWAP,DASH-USDT-SWAP,DOME-USDT-SWAP,DORA-USDT-SWAP,DYDX-USDT-SWAP,EGLD-USDT-SWAP,ELON-USDT-SWAP,ENJ-USDT-SWAP,ENS-USDT-SWAP,EOS-USDT-SWAP,ETC-USDT-SWAP,FITFI-USDT-SWAP,FLM-USDT-SWAP,FTM-USDT-SWAP,GALA-USDT-SWAP,GMT-USDT-SWAP,GODS-USDT-SWAP,GRT-USDT-SWAP,ICP-USDT-SWAP,IMX-USDT-SWAP,IOST-USDT-SWAP,IOTA-USDT-SWAP,JST-USDT-SWAP,KISHU-USDT-SWAP,KNC-USDT-SWAP,KSM-USDT-SWAP,LINK-USDT-SWAP,LOOKS-USDT-SWAP,LPT-USDT-SWAP,LRC-USDT-SWAP,MANA-USDT-SWAP,MASK-USDT-SWAP,MATIC-USDT-SWAP,MINA-USDT-SWAP,MKR-USDT-SWAP,NEAR-USDT-SWAP,NEO-USDT-SWAP,NFT-USDT-SWAP,NYM-USDT-SWAP,OMG-USDT-SWAP,ONT-USDT-SWAP,OP-USDT-SWAP,PEOPLE-USDT-SWAP,PERP-USDT-SWAP,QTUM-USDT-SWAP,REN-USDT-SWAP,RSR-USDT-SWAP,RVN-USDT-SWAP,SAND-USDT-SWAP,SC-USDT-SWAP,SHIB-USDT-SWAP,SLP-USDT-SWAP,SNX-USDT-SWAP,SOL-USDT-SWAP,SOS-USDT-SWAP,SRM-USDT-SWAP,STARL-USDT-SWAP,STORJ-USDT-SWAP,SUSHI-USDT-SWAP,SWEAT-USDT-SWAP,THETA-USDT-SWAP,TRB-USDT-SWAP,TRX-USDT-SWAP,UMA-USDT-SWAP,UMEE-USDT-SWAP,UNI-USDT-SWAP,WAVES-USDT-SWAP,WNXM-USDT-SWAP,XCH-USDT-SWAP,XEM-USDT-SWAP,XLM-USDT-SWAP,XMR-USDT-SWAP,XTZ-USDT-SWAP,YFI-USDT-SWAP,YFII-USDT-SWAP,YGG-USDT-SWAP,ZEC-USDT-SWAP,ZEN-USDT-SWAP,ZIL-USDT-SWAP,ZRX-USDT-SWAP" + }, + "spot": { + "assetEnabled": true, + "enabled": "BTC-USDT,ETH-USDT,OKB-USDT", + "available": "OKT-USDT,LTC-USDT,DOT-USDT,DOGE-USDT,LUNC-USDT,ETHW-USDT,LUNA-USDT,FIL-USDT,XRP-USDT,CITY-USDT,MENGO-USDT,ARG-USDT,POR-USDT,1INCH-USDT,AAVE-USDT,ABT-USDT,ACA-USDT,ADA-USDT,AERGO-USDT,AGLD-USDT,XMR-ETH,YFI-ETH,ZEC-ETH,LTC-OKB,XRP-OKB,ETC-OKB,BTC-USDT,ETH-USDT,OKB-USDT,OKT-USDT,LTC-USDT,DOT-USDT,DOGE-USDT,LUNC-USDT,ETHW-USDT,LUNA-USDT,FIL-USDT,XRP-USDT,CITY-USDT,MENGO-USDT,ARG-USDT,POR-USDT,1INCH-USDT,AAVE-USDT,ABT-USDT,ACA-USDT,ADA-USDT,AERGO-USDT,AGLD-USDT,AKITA-USDT,ALCX-USDT,ALGO-USDT,ALPHA-USDT,ANC-USDT,ANT-USDT,ANW-USDT,APE-USDT,APIX-USDT,API3-USDT,APM-USDT,AR-USDT,ARK-USDT,AST-USDT,ASTR-USDT,ATOM-USDT,AUCTION-USDT,AVAX-USDT,AXS-USDT,AZY-USDT,BABYDOGE-USDT,BADGER-USDT,BAL-USDT,BAND-USDT,BAT-USDT,BCD-USDT,BCH-USDT,BETH-USDT,BHP-USDT,BICO-USDT,BLOK-USDT,BNT-USDT,BORING-USDT,BORA-USDT,BRWL-USDT,BSV-USDT,BTG-USDT,BTM-USDT,BTT-USDT,BZZ-USDT,CELO-USDT,CEL-USDT,CELR-USDT,CELT-USDT,CFG-USDT,CFX-USDT,CGS-USDT,CHAT-USDT,CHE-USDT,CHZ-USDT,CLV-USDT,CMT-USDT,CNTM-USDT,COMP-USDT,CONV-USDT,COVER-USDT,CQT-USDT,CRO-USDT,CRV-USDT,CSPR-USDT,CTC-USDT,CTXC-USDT,CVC-USDT,CVP-USDT,CVT-USDT,CVX-USDT,DAI-USDT,DAO-USDT,DASH-USDT,DCR-USDT,DEP-USDT,DEVT-USDT,DGB-USDT,DHT-USDT,DIA-USDT,DMD-USDT,DNA-USDT,DOME-USDT,DORA-USDT,DOSE-USDT,DYDX-USDT,EC-USDT,EDEN-USDT,EFI-USDT,EGLD-USDT,EGT-USDT,ELF-USDT,ELON-USDT,ELT-USDT,EM-USDT,ENJ-USDT,ENS-USDT,EOS-USDT,ERN-USDT,ETC-USDT,EURT-USDT,FAIR-USDT,FAME-USDT,FITFI-USDT,FLM-USDT,FLOW-USDT,FODL-USDT,FORTH-USDT,FRONT-USDT,FSN-USDT,FTM-USDT,GALA-USDT,GALFT-USDT,GARI-USDT,GAS-USDT,GF-USDT,GHST-USDT,GLM-USDT,GLMR-USDT,GM-USDT,GMT-USDT,GODS-USDT,GOG-USDT,GRT-USDT,GTO-USDT,GUSD-USDT,HBAR-USDT,HC-USDT,HDAO-USDT,HEGIC-USDT,HYC-USDT,ICP-USDT,ICX-USDT,ILV-USDT,IMX-USDT,INT-USDT,INX-USDT,IOST-USDT,IOTA-USDT,IQ-USDT,JFI-USDT,JOE-USDT,JST-USDT,KAN-USDT,KAR-USDT,KCASH-USDT,KDA-USDT,KINE-USDT,KISHU-USDT,KLAY-USDT,KNC-USDT,KOL-USDT,KONO-USDT,KP3R-USDT,KSM-USDT,LAMB-USDT,LAT-USDT,LBA-USDT,LDN-USDT,LDO-USDT,LEASH-USDT,LEO-USDT,LET-USDT,LINK-USDT,LING-USDT,LITH-USDT,LON-USDT,LOON-USDT,LOOKS-USDT,LPT-USDT,LRC-USDT,LSK-USDT,MAGIC-USDT,MANA-USDT,MASK-USDT,MATIC-USDT,MCO-USDT,MDA-USDT,MDT-USDT,MEME-USDT,METIS-USDT,MILO-USDT,MINA-USDT,MIR-USDT,MITH-USDT,MKR-USDT,MLN-USDT,MOF-USDT,MON-USDT,MOVR-USDT,MOVEZ-USDT,MXC-USDT,MXT-USDT,NAS-USDT,NEAR-USDT,NEO-USDT,NFT-USDT,NMR-USDT,NULS-USDT,NYM-USDT,OM-USDT,OMG-USDT,OMI-USDT,ONE-USDT,ONT-USDT,OP-USDT,ORBS-USDT,ORB-USDT,ORS-USDT,OXT-USDT,PAY-USDT,PCI-USDT,PEOPLE-USDT,PERP-USDT,PHA-USDT,PICKLE-USDT,PIT-USDT,PLG-USDT,PNK-USDT,POLS-USDT,POLYDOGE-USDT,PPT-USDT,PRQ-USDT,PST-USDT,PSTAKE-USDT,QOM-USDT,QTUM-USDT,RACA-USDT,RAY-USDT,REN-USDT,REP-USDT,REVV-USDT,RFUEL-USDT,RIO-USDT,RNT-USDT,ROAD-USDT,RON-USDT,RSR-USDT,RSS3-USDT,RVN-USDT,SAITAMA-USDT,SAMO-USDT,SAND-USDT,SC-USDT,SD-USDT,SFG-USDT,SHIB-USDT,SIS-USDT,SKEB-USDT,SKL-USDT,SLP-USDT,SNT-USDT,SNX-USDT,SOC-USDT,SOL-USDT,SOS-USDT,SPELL-USDT,SRM-USDT,STARL-USDT,STC-USDT,STORJ-USDT,STRK-USDT,STX-USDT,SUN-USDT,SUSHI-USDT,SWEAT-USDT,SWFTC-USDT,SWRV-USDT,T-USDT,TAI-USDT,TAKI-USDT,TCT-USDT,THETA-USDT,THG-USDT,TON-USDT,TOPC-USDT,TORN-USDT,TOWN-USDT,TRADE-USDT,TRA-USDT,TRB-USDT,TRUE-USDT,TRX-USDT,TUP-USDT,TUSD-USDT,UMA-USDT,UMEE-USDT,UNI-USDT,USDC-USDT,USDP-USDT,USTC-USDT,UTK-USDT,VALUE-USDT,VELO-USDT,VRA-USDT,VSYS-USDT,WAVES-USDT,WAXP-USDT,WBTC-USDT,WEMIX-USDT,WGRT-USDT,WING-USDT,WIN-USDT,WNCG-USDT,WNXM-USDT,WOO-USDT,WSB-USDT,WXT-USDT,XAUT-USDT,XCH-USDT,XEC-USDT,XEM-USDT,XETA-USDT,XLM-USDT,XMR-USDT,XNO-USDT,XPR-USDT,XTZ-USDT,YEE-USDT,YFI-USDT,YFII-USDT,YGG-USDT,YOU-USDT,YOYO-USDT,ZBC-USDT,ZEC-USDT,ZEN-USDT,ZIL-USDT,ZKS-USDT,ZRX-USDT,ZYRO-USDT,BTC-USDC,ETH-USDC,ETH-BTC,OKB-USDC,OKT-USDC,LTC-USDC,DOT-USDC,DOGE-USDC,LUNC-USDC,ETHW-USDC,LUNA-USDC,FIL-USDC,XRP-USDC,1INCH-USDC,AAVE-USDC,ADA-USDC,AGLD-USDC,ALGO-USDC,ANC-USDC,ANT-USDC,APE-USDC,API3-USDC,AR-USDC,ASTR-USDC,ATOM-USDC,AVAX-USDC,AXS-USDC,AZY-USDC,BABYDOGE-USDC,BAT-USDC,BCH-USDC,BICO-USDC,BSV-USDC,CEL-USDC,CELO-USDC,CELT-USDC,CHZ-USDC,COMP-USDC,CRO-USDC,CRV-USDC,CSPR-USDC,DASH-USDC,DEP-USDC,DOME-USDC,DYDX-USDC,EGLD-USDC,ELT-USDC,ENS-USDC,EOS-USDC,ETC-USDC,FITFI-USDC,FLM-USDC,FLOW-USDC,FTM-USDC,GALA-USDC,GALFT-USDC,GARI-USDC,GLMR-USDC,GMT-USDC,GODS-USDC,GRT-USDC,HBAR-USDC,ICP-USDC,IMX-USDC,IOST-USDC,JST-USDC,KISHU-USDC,KLAY-USDC,KNC-USDC,KSM-USDC,LINK-USDC,LOOKS-USDC,LRC-USDC,MANA-USDC,MASK-USDC,MATIC-USDC,MINA-USDC,MKR-USDC,MOF-USDC,MOVEZ-USDC,MXC-USDC,NEAR-USDC,NFT-USDC,NMR-USDC,NYM-USDC,OMG-USDC,OP-USDC,PEOPLE-USDC,PERP-USDC,RACA-USDC,RSR-USDC,SAITAMA-USDC,SAND-USDC,SHIB-USDC,SLP-USDC,SNX-USDC,SOC-USDC,SOL-USDC,SOS-USDC,SRM-USDC,STARL-USDC,STC-USDC,STORJ-USDC,STX-USDC,SUSHI-USDC,SWFTC-USDC,THETA-USDC,TON-USDC,TORN-USDC,TRB-USDC,TRX-USDC,UNI-USDC,USDP-USDC,USTC-USDC,VRA-USDC,WAVES-USDC,XCH-USDC,XEM-USDC,XLM-USDC,XMR-USDC,XNO-USDC,XTZ-USDC,YFI-USDC,YFII-USDC,YGG-USDC,ZEC-USDC,ZIL-USDC,BTC-DAI,ETH-DAI,BTC-USDK,ETH-USDK,USDT-USDK,OKB-BTC,OKT-BTC,LTC-BTC,DOT-BTC,DOGE-BTC,FIL-BTC,XRP-BTC,AAVE-BTC,ADA-BTC,ALGO-BTC,ALPHA-BTC,ANT-BTC,ATOM-BTC,AVAX-BTC,BADGER-BTC,BAT-BTC,BCD-BTC,BCH-BTC,BNT-BTC,BSV-BTC,BTG-BTC,BTM-BTC,CELO-BTC,CELT-BTC,CHZ-BTC,COMP-BTC,CQT-BTC,CRO-BTC,CRV-BTC,CTC-BTC,CVC-BTC,DASH-BTC,DCR-BTC,DGB-BTC,EGLD-BTC,ELF-BTC,ENJ-BTC,EOS-BTC,ETC-BTC,FLOW-BTC,GAS-BTC,GRT-BTC,GTO-BTC,HBAR-BTC,HC-BTC,ICP-BTC,ICX-BTC,INT-BTC,IOST-BTC,IOTA-BTC,KLAY-BTC,KNC-BTC,KSM-BTC,LINK-BTC,LRC-BTC,LSK-BTC,MANA-BTC,MITH-BTC,MKR-BTC,NAS-BTC,NEAR-BTC,NEO-BTC,NULS-BTC,OMG-BTC,ONT-BTC,PST-BTC,QTUM-BTC,REN-BTC,RSR-BTC,RVN-BTC,SC-BTC,SNT-BTC,SOL-BTC,SRM-BTC,STX-BTC,SWFTC-BTC,THETA-BTC,TRUE-BTC,TRX-BTC,UNI-BTC,VSYS-BTC,WAVES-BTC,WBTC-BTC,WXT-BTC,XCH-BTC,XEM-BTC,XLM-BTC,XMR-BTC,XTZ-BTC,YFI-BTC,YOU-BTC,ZEC-BTC,ZEN-BTC,ZIL-BTC,ZRX-BTC,OKB-ETH,OKT-ETH,LTC-ETH,DOT-ETH,DOGE-ETH,FIL-ETH,XRP-ETH,AAVE-ETH,ADA-ETH,API3-ETH,ATOM-ETH,AVAX-ETH,BETH-ETH,CRV-ETH,DASH-ETH,EOS-ETH,ETC-ETH,FLOW-ETH,GAS-ETH,GHST-ETH,HEGIC-ETH,INT-ETH,IOST-ETH,KSM-ETH,LINK-ETH,MANA-ETH,MKR-ETH,NEAR-ETH,NEO-ETH,NULS-ETH,OM-ETH,QTUM-ETH,SNX-ETH,SOL-ETH,SUSHI-ETH,SWFTC-ETH,TRX-ETH,UNI-ETH,WBTC-ETH,XLM-ETH,XMR-ETH,YFI-ETH,ZEC-ETH,LTC-OKB,XRP-OKB,ETC-OKB,OKDOT1-DOT,OKDOT2-DOT,BTC-EURT,ETH-EURT" + } + } + }, + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "credentials": { + "key": "", + "secret": "", + "clientID": "" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true, + "requiresClientID": true + }, + "urlEndpoints": { + "RestSpotURL": "https://www.okx.com/api/v5/", + "WebsocketSpotURL": "wss://ws.okx.com:8443/ws/v5/public" + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "autoPairUpdates": true + }, + "websocketAPI": true, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": true, + "saveTradeData": false, + "tradeFeed": false, + "fillsFeed": false + } + }, + "bankAccounts": [ + { + "enabled": false, + "bankName": "", + "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ], + "orderbook": { + "verificationBypass": false, + "websocketBufferLimit": 5, + "websocketBufferEnabled": false, + "publishPeriod": 10000000000 } + }, + { + "name": "Poloniex", + "enabled": true, + "verbose": false, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, + "websocketOrderbookBufferLimit": 5, + "baseCurrencies": "USD", + "currencyPairs": { + "requestFormat": { + "uppercase": true, + "delimiter": "_" + }, + "configFormat": { + "uppercase": true, + "delimiter": "_" + }, + "useGlobalFormat": true, + "assetTypes": [ + "spot" + ], + "pairs": { + "spot": { + "enabled": "BTC_LTC,BTC_ETH,BTC_DOGE,BTC_DASH,BTC_XRP", + "available": "USDC_GRIN,BTC_BCN,BTC_DGB,BTC_XMR,USDT_STR,BTC_SC,BTC_ZRX,USDC_XMR,BTC_TRX,BTC_STR,BTC_SNT,USDT_QTUM,USDC_BTC,BTC_NMR,BTC_DASH,BTC_NXT,USDT_LTC,BTC_DCR,USDT_ZRX,USDC_ZEC,USDT_REP,USDT_BAT,BTC_MANA,USDC_BCHABC,USDC_STR,BTC_XRP,USDT_ETH,BTC_REP,USDT_EOS,USDC_ATOM,USDT_XRP,BTC_ETH,USDT_LSK,USDT_SC,USDT_MANA,USDC_ETC,USDC_ETH,BTC_BTS,BTC_LTC,BTC_ETC,BTC_OMG,BTC_STORJ,USDC_XRP,USDT_GRIN,BTC_QTUM,BTC_MAID,BTC_XEM,USDT_BTC,USDT_DASH,ETH_REP,BTC_ZEC,BTC_STRAT,USDC_LTC,BTC_FOAM,USDC_TRX,BTC_DOGE,BTC_VIA,BTC_VTC,ETH_ETC,USDT_ETC,ETH_EOS,USDC_BCHSV,USDT_NXT,USDT_XMR,BTC_ARDR,BTC_CVC,ETH_BAT,USDC_DOGE,BTC_XPM,BTC_LOOM,BTC_LPT,USDC_EOS,USDT_DGB,USDT_BCHSV,BTC_OMNI,ETH_ZEC,BTC_EOS,BTC_KNC,BTC_BCHSV,BTC_POLY,USDC_DASH,USDT_GNT,BTC_BCHABC,BTC_GRIN,BTC_ATOM,USDT_ATOM,USDT_BCHABC,BTC_LSK,ETH_ZRX,BTC_GAS,BTC_BAT,BTC_BNT,USDT_TRX,BTC_FCT,USDT_ZEC,BTC_GNT,USDT_DOGE,USDC_USDT" + } + } + }, + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "tickerBatching": true, + "autoPairUpdates": true + }, + "websocketAPI": true, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": false + } + }, + "bankAccounts": [ + { + "enabled": false, + "bankName": "", + "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ] + }, + { + "name": "Yobit", + "enabled": true, + "verbose": false, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, + "websocketOrderbookBufferLimit": 5, + "baseCurrencies": "USD", + "currencyPairs": { + "requestFormat": { + "uppercase": false, + "delimiter": "_", + "separator": "-" + }, + "configFormat": { + "uppercase": true, + "delimiter": "_" + }, + "useGlobalFormat": true, + "lastUpdated": 1566798411, + "assetTypes": [ + "spot" + ], + "pairs": { + "spot": { + "enabled": "LTC_BTC,ETH_BTC,BTC_USD,DASH_BTC", + "available": "DASH_BTC,WAVES_BTC,LSK_BTC,LIZA_BTC,BCC_BTC,ETH_BTC,LTC_BTC,TRX_BTC,DOGE_BTC,VNTX_BTC,SW_BTC,ZEC_BTC,DASH_ETH,WAVES_ETH,LSK_ETH,LIZA_ETH,BCC_ETH,LTC_ETH,TRX_ETH,DOGE_ETH,VNTX_ETH,SW_ETH,ZEC_ETH,DASH_DOGE,WAVES_DOGE,LSK_DOGE,LIZA_DOGE,BCC_DOGE,LTC_DOGE,TRX_DOGE,VNTX_DOGE,SW_DOGE,ZEC_DOGE,DASH_USD,WAVES_USD,LSK_USD,LIZA_USD,BCC_USD,LTC_USD,TRX_USD,VNTX_USD,SW_USD,ZEC_USD,ETH_USD,BTC_USD,DASH_RUR,WAVES_BTC,WAVES_RUR,LSK_RUR,LIZA_RUR,BCC_RUR,LTC_RUR,TRX_RUR,VNTX_RUR,SW_RUR,ETH_RUR,ZEC_RUR" + } + } + }, + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "tickerBatching": true, + "autoPairUpdates": true + }, + "websocketAPI": false, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": false, + "websocketAPI": false + } + }, + "bankAccounts": [ + { + "enabled": false, + "bankName": "", + "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ] + } + ], + "bankAccounts": [ + { + "enabled": false, + "bankName": "test", + "bankAddress": "test", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", + "accountName": "TestAccount", + "accountNumber": "0234", + "swiftCode": "91272837", + "iban": "98218738671897", + "supportedCurrencies": "USD", + "supportedExchanges": "Kraken,Bitstamp" + } ] -} \ No newline at end of file + } + \ No newline at end of file From 01ccd0100fc79079e6c44cf7517fa94b0a8680de Mon Sep 17 00:00:00 2001 From: Samuel Reid <43227667+cranktakular@users.noreply.github.com> Date: Tue, 7 May 2024 15:39:28 +1000 Subject: [PATCH 47/79] Config fix (real) --- testdata/configtest.json | 4531 +++++++++++++++++++------------------- 1 file changed, 2265 insertions(+), 2266 deletions(-) diff --git a/testdata/configtest.json b/testdata/configtest.json index cf508c9bc3d..64754420c95 100644 --- a/testdata/configtest.json +++ b/testdata/configtest.json @@ -1,2374 +1,2373 @@ { - "name": "Skynet", - "encryptConfig": -1, - "globalHTTPTimeout": 15000000000, - "database": { - "enabled": true, - "verbose": false, - "driver": "sqlite3", - "connectionDetails": { - "host": "", - "port": 0, - "username": "", - "password": "", - "database": "gocryptotrader.db", - "sslmode": "" - } + "name": "Skynet", + "encryptConfig": -1, + "globalHTTPTimeout": 15000000000, + "database": { + "enabled": true, + "verbose": false, + "driver": "sqlite3", + "connectionDetails": { + "host": "", + "port": 0, + "username": "", + "password": "", + "database": "gocryptotrader.db", + "sslmode": "" + } + }, + "logging": { + "enabled": true, + "level": "INFO|DEBUG|WARN|ERROR", + "output": "console", + "fileSettings": { + "filename": "log.txt", + "rotate": false, + "maxsize": 100 }, - "logging": { - "enabled": true, - "level": "INFO|DEBUG|WARN|ERROR", - "output": "console", - "fileSettings": { - "filename": "log.txt", - "rotate": false, - "maxsize": 100 - }, - "advancedSettings": { - "showLogSystemName": false, - "spacer": " | ", - "timeStampFormat": " 02/01/2006 15:04:05 ", - "headers": { - "info": "[INFO]", - "warn": "[WARN]", - "debug": "[DEBUG]", - "error": "[ERROR]" - } + "advancedSettings": { + "showLogSystemName": false, + "spacer": " | ", + "timeStampFormat": " 02/01/2006 15:04:05 ", + "headers": { + "info": "[INFO]", + "warn": "[WARN]", + "debug": "[DEBUG]", + "error": "[ERROR]" + } + } + }, + "connectionMonitor": { + "preferredDNSList": [ + "8.8.8.8", + "8.8.4.4", + "1.1.1.1", + "1.0.0.1" + ], + "preferredDomainList": [ + "www.google.com", + "www.cloudflare.com", + "www.facebook.com" + ], + "checkInterval": 1000000000 + }, + "profiler": { + "enabled": false, + "mutex_profile_fraction": 0 + }, + "ntpclient": { + "enabled": 0, + "pool": [ + "0.pool.ntp.org:123", + "pool.ntp.org:123" + ], + "allowedDifference": 50000000, + "allowedNegativeDifference": 50000000 + }, + "gctscript": { + "enabled": false, + "timeout": 30000000000, + "max_virtual_machines": 10, + "allow_imports": false, + "auto_load": null, + "verbose": false + }, + "currencyConfig": { + "forexProviders": [ + { + "name": "CurrencyConverter", + "enabled": false, + "verbose": false, + "restPollingDelay": 600, + "apiKey": "Key", + "apiKeyLvl": -1, + "primaryProvider": false + }, + { + "name": "CurrencyLayer", + "enabled": false, + "verbose": false, + "restPollingDelay": 600, + "apiKey": "Key", + "apiKeyLvl": -1, + "primaryProvider": false + }, + { + "name": "Fixer", + "enabled": false, + "verbose": false, + "restPollingDelay": 600, + "apiKey": "Key", + "apiKeyLvl": -1, + "primaryProvider": false + }, + { + "name": "OpenExchangeRates", + "enabled": false, + "verbose": false, + "restPollingDelay": 600, + "apiKey": "Key", + "apiKeyLvl": -1, + "primaryProvider": false + }, + { + "name": "ExchangeRates", + "enabled": true, + "verbose": false, + "restPollingDelay": 600, + "apiKey": "Key", + "apiKeyLvl": -1, + "primaryProvider": true } + ], + "cryptocurrencyProvider": { + "name": "CoinMarketCap", + "enabled": false, + "verbose": false, + "apiKey": "Key", + "accountPlan": "accountPlan" }, - "connectionMonitor": { - "preferredDNSList": [ - "8.8.8.8", - "8.8.4.4", - "1.1.1.1", - "1.0.0.1" - ], - "preferredDomainList": [ - "www.google.com", - "www.cloudflare.com", - "www.facebook.com" - ], - "checkInterval": 1000000000 + "cryptocurrencies": "BTC,LTC,ETH,DOGE,DASH,XRP,XMR", + "currencyPairFormat": { + "uppercase": true, + "delimiter": "-" }, - "profiler": { + "fiatDisplayCurrency": "USD", + "currencyFileUpdateDuration": 0, + "foreignExchangeUpdateDuration": 0 + }, + "communications": { + "slack": { + "name": "Slack", "enabled": false, - "mutex_profile_fraction": 0 + "verbose": false, + "targetChannel": "general", + "verificationToken": "testtest" }, - "ntpclient": { - "enabled": 0, - "pool": [ - "0.pool.ntp.org:123", - "pool.ntp.org:123" - ], - "allowedDifference": 50000000, - "allowedNegativeDifference": 50000000 + "smsGlobal": { + "name": "SMSGlobal", + "from": "Skynet", + "enabled": true, + "verbose": false, + "username": "1234", + "password": "12334", + "contacts": [ + { + "name": "StyleGherkin", + "number": "1231424", + "enabled": true + } + ] + }, + "smtp": { + "name": "SMTP", + "enabled": false, + "verbose": false, + "host": "smtp.google.com", + "port": "537", + "accountName": "some", + "accountPassword": "password", + "from": "", + "recipientList": "lol123@gmail.com" }, - "gctscript": { + "telegram": { + "name": "Telegram", "enabled": false, - "timeout": 30000000000, - "max_virtual_machines": 10, - "allow_imports": false, - "auto_load": null, - "verbose": false + "verbose": false, + "verificationToken": "testest", + "authorisedClients": { + "user_example": 0 + } + } + }, + "remoteControl": { + "username": "admin", + "password": "Password", + "gRPC": { + "enabled": true, + "listenAddress": "localhost:9052", + "grpcProxyEnabled": true, + "grpcProxyListenAddress": "localhost:9053" + }, + "deprecatedRPC": { + "enabled": true, + "listenAddress": "localhost:9050" }, - "currencyConfig": { - "forexProviders": [ + "websocketRPC": { + "enabled": true, + "listenAddress": "localhost:9051", + "connectionLimit": 1, + "maxAuthFailures": 3, + "allowInsecureOrigin": true + } + }, + "portfolioAddresses": { + "addresses": [ + { + "Address": "1JCe8z4jJVNXSjohjM4i9Hh813dLCNx2Sy", + "CoinType": "BTC", + "Balance": 53000.01741264, + "Description": "", + "WhiteListed": false, + "ColdStorage": false, + "SupportedExchanges": "" + }, + { + "Address": "3Nxwenay9Z8Lc9JBiywExpnEFiLp6Afp8v", + "CoinType": "BTC", + "Balance": 107848.28963408, + "Description": "", + "WhiteListed": false, + "ColdStorage": false, + "SupportedExchanges": "" + }, + { + "Address": "LgY8ahfHRhvjVQC1zJnBhFMG5pCTMuKRqh", + "CoinType": "LTC", + "Balance": 0.03665026, + "Description": "", + "WhiteListed": false, + "ColdStorage": false, + "SupportedExchanges": "" + }, + { + "Address": "0xb794f5ea0ba39494ce839613fffba74279579268", + "CoinType": "ETH", + "Balance": 0.25555604051326, + "Description": "", + "WhiteListed": false, + "ColdStorage": false, + "SupportedExchanges": "" + } + ] + }, + "exchanges": [ + { + "name": "BTC Markets", + "enabled": true, + "verbose": false, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, + "websocketOrderbookBufferLimit": 5, + "baseCurrencies": "AUD", + "currencyPairs": { + "requestFormat": { + "uppercase": true, + "delimiter": "-" + }, + "configFormat": { + "uppercase": true, + "delimiter": "-" + }, + "useGlobalFormat": true, + "assetTypes": [ + "spot" + ], + "pairs": { + "spot": { + "enabled": "BTC-AUD", + "available": "BTC-AUD,LTC-AUD,LTC-BTC,ETH-BTC,ETH-AUD,ETC-AUD,ETC-BTC,XRP-AUD,XRP-BTC,POWR-AUD,POWR-BTC,OMG-AUD,OMG-BTC,BCHABC-AUD,BCHABC-BTC,BCHSV-AUD,BCHSV-BTC,GNT-AUD,GNT-BTC,BAT-AUD,BAT-BTC,XLM-AUD,XLM-BTC" + } + } + }, + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true, + "requiresBase64DecodeSecret": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "autoPairUpdates": true + }, + "websocketAPI": true, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": false + } + }, + "bankAccounts": [ { - "name": "CurrencyConverter", "enabled": false, - "verbose": false, - "restPollingDelay": 600, - "apiKey": "Key", - "apiKeyLvl": -1, - "primaryProvider": false - }, + "bankName": "", + "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ] + }, + { + "name": "BTSE", + "enabled": true, + "verbose": false, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, + "websocketOrderbookBufferLimit": 5, + "baseCurrencies": "USD", + "currencyPairs": { + "requestFormat": { + "uppercase": true, + "delimiter": "-" + }, + "configFormat": { + "uppercase": true, + "delimiter": "-" + }, + "useGlobalFormat": true, + "assetTypes": [ + "spot" + ], + "pairs": { + "spot": { + "enabled": "BTC-USD", + "available": "BTC-CNY,BTC-EUR,BTC-GBP,BTC-HKD,BTC-JPY,BTC-SGD,BTC-USD,ETH-CNY,ETH-EUR,ETH-GBP,ETH-HKD,ETH-JPY,ETH-SGD,ETH-USD,LTC-CNY,LTC-EUR,LTC-GBP,LTC-HKD,LTC-JPY,LTC-SGD,LTC-USD,USDT-CNY,USDT-EUR,USDT-GBP,USDT-HKD,USDT-JPY,USDT-SGD,USDT-USD,XMR-CNY,XMR-EUR,XMR-GBP,XMR-HKD,XMR-JPY,XMR-SGD,XMR-USD" + } + } + }, + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "autoPairUpdates": true + }, + "websocketAPI": true, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": true + } + }, + "bankAccounts": [ { - "name": "CurrencyLayer", "enabled": false, - "verbose": false, - "restPollingDelay": 600, - "apiKey": "Key", - "apiKeyLvl": -1, - "primaryProvider": false - }, + "bankName": "", + "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ] + }, + { + "name": "Binance", + "enabled": true, + "verbose": false, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, + "websocketOrderbookBufferLimit": 5, + "baseCurrencies": "USD", + "currencyPairs": { + "requestFormat": { + "uppercase": true + }, + "configFormat": { + "uppercase": true, + "delimiter": "-" + }, + "useGlobalFormat": true, + "assetTypes": [ + "spot" + ], + "pairs": { + "spot": { + "enabled": "BTC-USDT,DOGE-USDT", + "available": "ETH-BTC,LTC-BTC,BNB-BTC,NEO-BTC,QTUM-ETH,EOS-ETH,SNT-ETH,BNT-ETH,GAS-BTC,BNB-ETH,BTC-USDT,ETH-USDT,OAX-ETH,DNT-ETH,MCO-ETH,MCO-BTC,WTC-BTC,WTC-ETH,LRC-BTC,LRC-ETH,QTUM-BTC,YOYO-BTC,OMG-BTC,OMG-ETH,ZRX-BTC,ZRX-ETH,STRAT-BTC,STRAT-ETH,SNGLS-BTC,BQX-BTC,BQX-ETH,KNC-BTC,KNC-ETH,FUN-BTC,FUN-ETH,SNM-BTC,SNM-ETH,NEO-ETH,IOTA-BTC,IOTA-ETH,LINK-BTC,LINK-ETH,XVG-BTC,XVG-ETH,MDA-BTC,MDA-ETH,MTL-BTC,MTL-ETH,EOS-BTC,SNT-BTC,ETC-ETH,ETC-BTC,MTH-BTC,MTH-ETH,ENG-BTC,ENG-ETH,DNT-BTC,ZEC-BTC,ZEC-ETH,BNT-BTC,AST-BTC,AST-ETH,DASH-BTC,DASH-ETH,OAX-BTC,BTG-BTC,BTG-ETH,EVX-BTC,EVX-ETH,REQ-BTC,REQ-ETH,VIB-BTC,VIB-ETH,TRX-BTC,TRX-ETH,POWR-BTC,POWR-ETH,ARK-BTC,ARK-ETH,YOYO-ETH,XRP-BTC,XRP-ETH,ENJ-BTC,ENJ-ETH,STORJ-BTC,STORJ-ETH,BNB-USDT,YOYO-BNB,POWR-BNB,KMD-BTC,KMD-ETH,NULS-BNB,RCN-BTC,RCN-ETH,RCN-BNB,NULS-BTC,NULS-ETH,RDN-BTC,RDN-ETH,RDN-BNB,XMR-BTC,XMR-ETH,DLT-BNB,WTC-BNB,DLT-BTC,DLT-ETH,AMB-BTC,AMB-ETH,AMB-BNB,BAT-BTC,BAT-ETH,BAT-BNB,BCPT-BTC,BCPT-ETH,BCPT-BNB,ARN-BTC,ARN-ETH,GVT-BTC,GVT-ETH,CDT-BTC,CDT-ETH,GXS-BTC,GXS-ETH,NEO-USDT,NEO-BNB,POE-BTC,POE-ETH,QSP-BTC,QSP-ETH,QSP-BNB,BTS-BTC,BTS-ETH,XZC-BTC,XZC-ETH,XZC-BNB,LSK-BTC,LSK-ETH,LSK-BNB,TNT-BTC,TNT-ETH,FUEL-BTC,MANA-BTC,MANA-ETH,BCD-BTC,BCD-ETH,DGD-BTC,DGD-ETH,IOTA-BNB,ADX-BTC,ADX-ETH,ADA-BTC,ADA-ETH,PPT-BTC,PPT-ETH,CMT-BTC,CMT-ETH,CMT-BNB,XLM-BTC,XLM-ETH,XLM-BNB,CND-BTC,CND-ETH,CND-BNB,LEND-BTC,LEND-ETH,WABI-BTC,WABI-ETH,WABI-BNB,LTC-ETH,LTC-USDT,LTC-BNB,TNB-BTC,TNB-ETH,WAVES-BTC,WAVES-ETH,WAVES-BNB,GTO-BTC,GTO-ETH,GTO-BNB,ICX-BTC,ICX-ETH,ICX-BNB,OST-BTC,OST-ETH,OST-BNB,ELF-BTC,ELF-ETH,AION-BTC,AION-ETH,AION-BNB,NEBL-BTC,NEBL-ETH,NEBL-BNB,BRD-BTC,BRD-ETH,BRD-BNB,MCO-BNB,EDO-BTC,EDO-ETH,NAV-BTC,LUN-BTC,APPC-BTC,APPC-ETH,APPC-BNB,VIBE-BTC,VIBE-ETH,RLC-BTC,RLC-ETH,RLC-BNB,INS-BTC,INS-ETH,PIVX-BTC,PIVX-ETH,PIVX-BNB,IOST-BTC,IOST-ETH,STEEM-BTC,STEEM-ETH,STEEM-BNB,NANO-BTC,NANO-ETH,NANO-BNB,VIA-BTC,VIA-ETH,VIA-BNB,BLZ-BTC,BLZ-ETH,BLZ-BNB,AE-BTC,AE-ETH,AE-BNB,NCASH-BTC,NCASH-ETH,POA-BTC,POA-ETH,ZIL-BTC,ZIL-ETH,ZIL-BNB,ONT-BTC,ONT-ETH,ONT-BNB,STORM-BTC,STORM-ETH,STORM-BNB,QTUM-BNB,QTUM-USDT,XEM-BTC,XEM-ETH,XEM-BNB,WAN-BTC,WAN-ETH,WAN-BNB,WPR-BTC,WPR-ETH,QLC-BTC,QLC-ETH,SYS-BTC,SYS-ETH,SYS-BNB,QLC-BNB,GRS-BTC,GRS-ETH,ADA-USDT,ADA-BNB,GNT-BTC,GNT-ETH,LOOM-BTC,LOOM-ETH,LOOM-BNB,XRP-USDT,REP-BTC,REP-ETH,BTC-TUSD,ETH-TUSD,ZEN-BTC,ZEN-ETH,ZEN-BNB,SKY-BTC,SKY-ETH,SKY-BNB,EOS-USDT,EOS-BNB,CVC-BTC,CVC-ETH,THETA-BTC,THETA-ETH,THETA-BNB,XRP-BNB,TUSD-USDT,IOTA-USDT,XLM-USDT,IOTX-BTC,IOTX-ETH,QKC-BTC,QKC-ETH,AGI-BTC,AGI-ETH,AGI-BNB,NXS-BTC,NXS-ETH,NXS-BNB,ENJ-BNB,DATA-BTC,DATA-ETH,ONT-USDT,TRX-BNB,TRX-USDT,ETC-USDT,ETC-BNB,ICX-USDT,SC-BTC,SC-ETH,SC-BNB,NPXS-ETH,KEY-BTC,KEY-ETH,NAS-BTC,NAS-ETH,NAS-BNB,MFT-BTC,MFT-ETH,MFT-BNB,DENT-ETH,ARDR-BTC,ARDR-ETH,NULS-USDT,HOT-BTC,HOT-ETH,VET-BTC,VET-ETH,VET-USDT,VET-BNB,DOCK-BTC,DOCK-ETH,POLY-BTC,POLY-BNB,HC-BTC,HC-ETH,GO-BTC,GO-BNB,PAX-USDT,RVN-BTC,RVN-BNB,DCR-BTC,DCR-BNB,MITH-BTC,MITH-BNB,BNB-PAX,BTC-PAX,ETH-PAX,XRP-PAX,EOS-PAX,XLM-PAX,REN-BTC,REN-BNB,BNB-TUSD,XRP-TUSD,EOS-TUSD,XLM-TUSD,BNB-USDC,BTC-USDC,ETH-USDC,XRP-USDC,EOS-USDC,XLM-USDC,USDC-USDT,ADA-TUSD,TRX-TUSD,NEO-TUSD,TRX-XRP,XZC-XRP,PAX-TUSD,USDC-TUSD,USDC-PAX,LINK-USDT,LINK-TUSD,LINK-PAX,LINK-USDC,WAVES-USDT,WAVES-TUSD,WAVES-USDC,LTC-TUSD,LTC-PAX,LTC-USDC,TRX-PAX,TRX-USDC,BTT-BNB,BTT-USDT,BNB-USDS,BTC-USDS,USDS-USDT,USDS-PAX,USDS-TUSD,USDS-USDC,BTT-PAX,BTT-TUSD,BTT-USDC,ONG-BNB,ONG-BTC,ONG-USDT,HOT-BNB,HOT-USDT,ZIL-USDT,ZRX-BNB,ZRX-USDT,FET-BNB,FET-BTC,FET-USDT,BAT-USDT,XMR-BNB,XMR-USDT,ZEC-BNB,ZEC-USDT,ZEC-PAX,ZEC-TUSD,ZEC-USDC,IOST-BNB,IOST-USDT,CELR-BNB,CELR-BTC,CELR-USDT,ADA-PAX,ADA-USDC,NEO-PAX,NEO-USDC,DASH-BNB,DASH-USDT,NANO-USDT,OMG-BNB,OMG-USDT,THETA-USDT,ENJ-USDT,MITH-USDT,MATIC-BNB,MATIC-BTC,MATIC-USDT,ATOM-BNB,ATOM-BTC,ATOM-USDT,ATOM-USDC,ATOM-TUSD,ETC-TUSD,BAT-USDC,BAT-PAX,BAT-TUSD,PHB-BNB,PHB-BTC,PHB-TUSD,TFUEL-BNB,TFUEL-BTC,TFUEL-USDT,ONE-BNB,ONE-BTC,ONE-USDT,ONE-USDC,FTM-BNB,FTM-BTC,FTM-USDT,FTM-USDC,ALGO-BNB,ALGO-BTC,ALGO-USDT,ALGO-TUSD,ALGO-PAX,ALGO-USDC,GTO-USDT,ERD-BNB,ERD-BTC,ERD-USDT,DOGE-BNB,DOGE-BTC,DOGE-USDT,DUSK-BNB,DUSK-BTC,DUSK-USDT,DUSK-USDC,DUSK-PAX,BGBP-USDC,ANKR-BNB,ANKR-BTC,ANKR-USDT,ONT-PAX,ONT-USDC,WIN-BNB,WIN-USDT,WIN-USDC,COS-BNB,COS-BTC,COS-USDT,NPXS-USDT,COCOS-BNB,COCOS-BTC,COCOS-USDT,MTL-USDT,TOMO-BNB,TOMO-BTC,TOMO-USDT,TOMO-USDC,PERL-BNB,PERL-BTC,PERL-USDT,DENT-USDT,MFT-USDT,KEY-USDT,STORM-USDT,DOCK-USDT,WAN-USDT,FUN-USDT,CVC-USDT,BTT-TRX,WIN-TRX,CHZ-BNB,CHZ-BTC,CHZ-USDT,BAND-BNB,BAND-BTC,BAND-USDT,BNB-BUSD,BTC-BUSD,BUSD-USDT,BEAM-BNB,BEAM-BTC,BEAM-USDT,XTZ-BNB,XTZ-BTC,XTZ-USDT,REN-USDT,RVN-USDT,HC-USDT,HBAR-BNB,HBAR-BTC,HBAR-USDT,NKN-BNB,NKN-BTC,NKN-USDT,XRP-BUSD,ETH-BUSD,LTC-BUSD,LINK-BUSD,ETC-BUSD,STX-BNB,STX-BTC,STX-USDT,KAVA-BNB,KAVA-BTC,KAVA-USDT,BUSD-NGN,BNB-NGN,BTC-NGN,ARPA-BNB,ARPA-BTC,ARPA-USDT,TRX-BUSD,EOS-BUSD,IOTX-USDT,RLC-USDT,MCO-USDT,XLM-BUSD,ADA-BUSD,CTXC-BNB,CTXC-BTC,CTXC-USDT,BCH-BNB,BCH-BTC,BCH-USDT,BCH-USDC,BCH-TUSD,BCH-PAX,BCH-BUSD,BTC-RUB,ETH-RUB,XRP-RUB,BNB-RUB,TROY-BNB,TROY-BTC,TROY-USDT,BUSD-RUB,QTUM-BUSD,VET-BUSD" + } + } + }, + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "tickerBatching": true, + "autoPairUpdates": true + }, + "websocketAPI": true, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": true + } + }, + "bankAccounts": [ { - "name": "Fixer", "enabled": false, - "verbose": false, - "restPollingDelay": 600, - "apiKey": "Key", - "apiKeyLvl": -1, - "primaryProvider": false - }, + "bankName": "", + "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ] + }, + { + "name": "Binanceus", + "enabled": true, + "verbose": false, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, + "baseCurrencies": "USD", + "currencyPairs": { + "bypassConfigFormatUpgrades": false, + "pairs": { + "spot": { + "assetEnabled": true, + "enabled": "BTC-USDT,ETH-USDT,LTC-USDT,ADA-USDT", + "available": "BTC-USD,BCH-USD,LTC-USD,USDT-USD,BTC-USDT,ETH-USDT,BCH-USDT,LTC-USDT,BNB-USD,BNB-USDT,ETH-BTC,BNB-BTC,LTC-BTC,BCH-BTC,ADA-USD,BAT-USD,ETC-USD,XLM-USD,ZRX-USD,ADA-USDT,BAT-USDT,ETC-USDT,XLM-USDT,ZRX-USDT,LINK-USD,RVN-USD,DASH-USD,ZEC-USD,ALGO-USD,IOTA-USD,BUSD-USD,BTCB-USD,DOGE-USDT,WAVES-USD,ATOM-USDT,ATOM-USD,NEO-USDT,NEO-USD,VET-USDT,QTUM-USDT,QTUM-USD,ICX-USD,ENJ-USD,ONT-USD,ONT-USDT,ZIL-USD,ZILB-USD,VET-USD,BNBB-USD,ETHB-USD,ALGO-BUSD,XTZ-USD,XTZ-BUSD,HBAR-USD,HBAR-BUSD,OMG-USD,OMG-BUSD,MATIC-USD,MATIC-BUSD,XTZ-BTC,ADA-BTC,REP-BUSD,REP-USD,EOS-BUSD,EOS-USD,DOGE-USD,KNC-USD,KNC-USDT,VTHO-USDT,VTHO-USD,USDC-USD,COMP-USDT,COMP-USD,MANA-USD,HNT-USD,HNT-USDT,MKR-USD,MKR-USDT,DAI-USD,ONE-USDT,ONE-USD,BAND-USDT,BAND-USD,STORJ-USDT,STORJ-USD,UNI-USD,UNI-USDT,SOL-USD,SOL-USDT,LINK-BTC,VET-BTC,UNI-BTC,EGLD-USDT,EGLD-USD,PAXG-USDT,PAXG-USD,OXT-USDT,OXT-USD,ZEN-USDT,ZEN-USD,BTC-USDC,ONEB-USD,FIL-USDT,FIL-USD,AAVE-USDT,AAVE-USD,GRT-USDT,GRT-USD,SUSHI-USD,ANKR-USD,AMP-USD,SHIB-USDT,SHIB-BUSD,CRV-USDT,CRV-USD,AXS-USDT,AXS-USD,SOL-BTC,AVAX-USDT,AVAX-USD,CTSI-USDT,CTSI-USD,DOT-USDT,DOT-USD,YFI-USDT,YFI-USD,1INCH-USDT,1INCH-USD,FTM-USDT,FTM-USD,USDC-USDT,ETH-USDC,USDC-BUSD,MATIC-USDT,MANA-USDT,MANA-BUSD,ALGO-USDT,ADA-BUSD,SOL-BUSD,EOS-USDT,ENJ-USDT,NEAR-USDT,NEAR-BUSD,NEAR-USD,OMG-USDT,SUSHI-USDT,LRC-USDT,LRC-USD,LRC-BTC,KSHI-BUSD,LPT-USDT,LPT-BUSD,LPT-USD,POLY-USDT,POLY-BUSD,POLY-USD,POLY-BTC,MATIC-BTC,DOT-BTC,NMR-USDT,NMR-USD,SLP-USDT,ANT-USD,XNO-USD,CHZ-USDT,CHZ-USD,OGN-USDT,OGN-USD,GALA-USDT,GALA-USD,TLM-USDT,TLM-USD,SNX-USDT,SNX-USD,AUDIO-USDT,AUDIO-USD,ENS-USDT,MANA-BTC,ATOM-BTC,AVAX-BTC,WBTC-BTC,REQ-USDT,REQ-USD,APE-USDT,APE-USD,FLUX-USDT,FLUX-USD,TRX-BTC,TRX-BUSD,TRX-USDT,TRX-USD,COTI-USDT,COTI-USD,VOXEL-USDT,VOXEL-USD,RLC-USDT,RLC-USD,UST-USDT,UST-USD,BICO-USDT,BICO-USD,API3-USDT,API3-USD,ENS-USD,BTC-UST,BNT-USDT,BNT-USD,IMX-USDT,IMX-USD,SPELL-USDT,SPELL-USD,JASMY-USDT,JASMY-USD,FLOW-USDT,FLOW-USD,GTC-USDT,GTC-USD,BTC-BUSD,ZIL-BUSD,BNB-BUSD,ETH-BUSD,BUSD-USDT,ONE-BUSD,LINK-USDT,ZEC-USDT,SLP-USD,ANT-USDT", + "requestFormat": { + "uppercase": true + }, + "configFormat": { + "uppercase": true, + "delimiter": "-" + } + } + } + }, + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "credentials": { + "key": "", + "secret": "" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true + }, + "urlEndpoints": { + "RestSpotSupplementaryURL": "https://api.binance.us", + "RestSpotURL": "https://api.binance.us", + "WebsocketSpotSupplementaryURL": "wss://stream.binance.us:9443/stream", + "WebsocketSpotURL": "wss://stream.binance.us:9443/stream" + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "tickerBatching": true, + "autoPairUpdates": true + }, + "websocketAPI": true, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": true, + "saveTradeData": false, + "tradeFeed": false, + "fillsFeed": false + } + }, + "bankAccounts": [ { - "name": "OpenExchangeRates", "enabled": false, - "verbose": false, - "restPollingDelay": 600, - "apiKey": "Key", - "apiKeyLvl": -1, - "primaryProvider": false - }, - { - "name": "ExchangeRates", - "enabled": true, - "verbose": false, - "restPollingDelay": 600, - "apiKey": "Key", - "apiKeyLvl": -1, - "primaryProvider": true + "bankName": "", + "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" } ], - "cryptocurrencyProvider": { - "name": "CoinMarketCap", - "enabled": false, - "verbose": false, - "apiKey": "Key", - "accountPlan": "accountPlan" - }, - "cryptocurrencies": "BTC,LTC,ETH,DOGE,DASH,XRP,XMR", - "currencyPairFormat": { - "uppercase": true, - "delimiter": "-" - }, - "fiatDisplayCurrency": "USD", - "currencyFileUpdateDuration": 0, - "foreignExchangeUpdateDuration": 0 + "orderbook": { + "verificationBypass": false, + "websocketBufferLimit": 5, + "websocketBufferEnabled": false, + "publishPeriod": 10000000000 + } }, - "communications": { - "slack": { - "name": "Slack", - "enabled": false, - "verbose": false, - "targetChannel": "general", - "verificationToken": "testtest" - }, - "smsGlobal": { - "name": "SMSGlobal", - "from": "Skynet", - "enabled": true, - "verbose": false, - "username": "1234", - "password": "12334", - "contacts": [ - { - "name": "StyleGherkin", - "number": "1231424", - "enabled": true + { + "name": "Bitfinex", + "enabled": true, + "verbose": false, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, + "websocketOrderbookBufferLimit": 5, + "baseCurrencies": "USD", + "currencyPairs": { + "requestFormat": { + "uppercase": true + }, + "configFormat": { + "uppercase": true + }, + "useGlobalFormat": true, + "assetTypes": [ + "spot" + ], + "pairs": { + "spot": { + "enabled": "BTCUSD,LTCUSD,LTCBTC,ETHUSD,ETHBTC", + "available": "BTCUSD,LTCUSD,LTCBTC,ETHUSD,ETHBTC,ETCBTC,ETCUSD,RRTUSD,RRTBTC,ZECUSD,ZECBTC,XMRUSD,XMRBTC,DSHUSD,DSHBTC,BTCEUR,BTCJPY,XRPUSD,XRPBTC,IOTUSD,IOTBTC,IOTETH,EOSUSD,EOSBTC,EOSETH,SANUSD,SANBTC,SANETH,OMGUSD,OMGBTC,OMGETH,NEOUSD,NEOBTC,NEOETH,ETPUSD,ETPBTC,ETPETH,QTMUSD,QTMBTC,QTMETH,AVTUSD,AVTBTC,AVTETH,EDOUSD,EDOBTC,EDOETH,BTGUSD,BTGBTC,DATUSD,DATBTC,DATETH,QSHUSD,QSHBTC,QSHETH,YYWUSD,YYWBTC,YYWETH,GNTUSD,GNTBTC,GNTETH,SNTUSD,SNTBTC,SNTETH,IOTEUR,BATUSD,BATBTC,BATETH,MNAUSD,MNABTC,MNAETH,FUNUSD,FUNBTC,FUNETH,ZRXUSD,ZRXBTC,ZRXETH,TNBUSD,TNBBTC,TNBETH,SPKUSD,SPKBTC,SPKETH,TRXUSD,TRXBTC,TRXETH,RCNUSD,RCNBTC,RCNETH,RLCUSD,RLCBTC,RLCETH,AIDUSD,AIDBTC,AIDETH,SNGUSD,SNGBTC,SNGETH,REPUSD,REPBTC,REPETH,ELFUSD,ELFBTC,ELFETH,NECUSD,NECBTC,NECETH,BTCGBP,ETHEUR,ETHJPY,ETHGBP,NEOEUR,NEOJPY,NEOGBP,EOSEUR,EOSJPY,EOSGBP,IOTJPY,IOTGBP,IOSUSD,IOSBTC,IOSETH,AIOUSD,AIOBTC,AIOETH,REQUSD,REQBTC,REQETH,RDNUSD,RDNBTC,RDNETH,LRCUSD,LRCBTC,LRCETH,WAXUSD,WAXBTC,WAXETH,DAIUSD,DAIBTC,DAIETH,AGIUSD,AGIBTC,AGIETH,BFTUSD,BFTBTC,BFTETH,MTNUSD,MTNBTC,MTNETH,ODEUSD,ODEBTC,ODEETH,ANTUSD,ANTBTC,ANTETH,DTHUSD,DTHBTC,DTHETH,MITUSD,MITBTC,MITETH,STJUSD,STJBTC,STJETH,XLMUSD,XLMEUR,XLMJPY,XLMGBP,XLMBTC,XLMETH,XVGUSD,XVGEUR,XVGJPY,XVGGBP,XVGBTC,XVGETH,BCIUSD,BCIBTC,MKRUSD,MKRBTC,MKRETH,KNCUSD,KNCBTC,KNCETH,POAUSD,POABTC,POAETH,EVTUSD,LYMUSD,LYMBTC,LYMETH,UTKUSD,UTKBTC,UTKETH,VEEUSD,VEEBTC,VEEETH,DADUSD,DADBTC,DADETH,ORSUSD,ORSBTC,ORSETH,AUCUSD,AUCBTC,AUCETH,POYUSD,POYBTC,POYETH,FSNUSD,FSNBTC,FSNETH,CBTUSD,CBTBTC,CBTETH,ZCNUSD,ZCNBTC,ZCNETH,SENUSD,SENBTC,SENETH,NCAUSD,NCABTC,NCAETH,CNDUSD,CNDBTC,CNDETH,CTXUSD,CTXBTC,CTXETH,PAIUSD,PAIBTC,SEEUSD,SEEBTC,SEEETH,ESSUSD,ESSBTC,ESSETH,ATMUSD,ATMBTC,ATMETH,HOTUSD,HOTBTC,HOTETH,DTAUSD,DTABTC,DTAETH,IQXUSD,IQXBTC,IQXEOS,WPRUSD,WPRBTC,WPRETH,ZILUSD,ZILBTC,ZILETH,BNTUSD,BNTBTC,BNTETH,ABSUSD,ABSETH,XRAUSD,XRAETH,MANUSD,MANETH,BBNUSD,BBNETH,NIOUSD,NIOETH,DGXUSD,DGXETH,VETUSD,VETBTC,VETETH,UTNUSD,UTNETH,TKNUSD,TKNETH,GOTUSD,GOTEUR,GOTETH,XTZUSD,XTZBTC,CNNUSD,CNNETH,BOXUSD,BOXETH,TRXEUR,TRXGBP,TRXJPY,MGOUSD,MGOETH,RTEUSD,RTEETH,YGGUSD,YGGETH,MLNUSD,MLNETH,WTCUSD,WTCETH,CSXUSD,CSXETH,OMNUSD,OMNBTC,INTUSD,INTETH,DRNUSD,DRNETH,PNKUSD,PNKETH,DGBUSD,DGBBTC,BSVUSD,BSVBTC,BABUSD,BABBTC,WLOUSD,WLOXLM,VLDUSD,VLDETH,ENJUSD,ENJETH,ONLUSD,ONLETH,RBTUSD,RBTBTC,USTUSD,EUTEUR,EUTUSD,GSDUSD,UDCUSD,TSDUSD,PAXUSD,RIFUSD,RIFBTC,PASUSD,PASETH,VSYUSD,VSYBTC,ZRXDAI,MKRDAI,OMGDAI,BTTUSD,BTTBTC,BTCUST,ETHUST,CLOUSD,CLOBTC,IMPUSD,IMPETH,LTCUST,EOSUST,BABUST,SCRUSD,SCRETH,GNOUSD,GNOETH,GENUSD,GENETH,ATOUSD,ATOBTC,ATOETH,WBTUSD,XCHUSD,EUSUSD,WBTETH,XCHETH,EUSETH,LEOUSD,LEOBTC,LEOUST,LEOEOS,LEOETH,ASTUSD,ASTETH,FOAUSD,FOAETH,UFRUSD,UFRETH,ZBTUSD,ZBTUST,OKBUSD,USKUSD,GTXUSD,KANUSD,OKBUST,OKBETH,OKBBTC,USKUST,USKETH,USKBTC,USKEOS,GTXUST,KANUST,AMPUSD,ALGUSD,ALGBTC,ALGUST,BTCXCH,SWMUSD,SWMETH,TRIUSD,TRIETH,LOOUSD,LOOETH,AMPUST,DUSK:USD,DUSK:BTC,UOSUSD,UOSBTC,RRBUSD,RRBUST,DTXUSD,DTXUST,AMPBTC,FTTUSD,FTTUST,PAXUST,UDCUST,TSDUST,BTC:CNHT,UST:CNHT,CNH:CNHT,CHZUSD,CHZUST,BTCF0:USTF0,ETHF0:USTF0" } - ] + } }, - "smtp": { - "name": "SMTP", - "enabled": false, - "verbose": false, - "host": "smtp.google.com", - "port": "537", - "accountName": "some", - "accountPassword": "password", - "from": "", - "recipientList": "lol123@gmail.com" - }, - "telegram": { - "name": "Telegram", - "enabled": false, - "verbose": false, - "verificationToken": "testest", - "authorisedClients": { - "user_example": 0 + "api": { + "authenticatedSupport": true, + "authenticatedWebsocketApiSupport": true, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true } - } - }, - "remoteControl": { - "username": "admin", - "password": "Password", - "gRPC": { - "enabled": true, - "listenAddress": "localhost:9052", - "grpcProxyEnabled": true, - "grpcProxyListenAddress": "localhost:9053" }, - "deprecatedRPC": { - "enabled": true, - "listenAddress": "localhost:9050" + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "tickerBatching": true, + "autoPairUpdates": true + }, + "websocketAPI": true, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": true + } }, - "websocketRPC": { - "enabled": true, - "listenAddress": "localhost:9051", - "connectionLimit": 1, - "maxAuthFailures": 3, - "allowInsecureOrigin": true - } - }, - "portfolioAddresses": { - "addresses": [ - { - "Address": "1JCe8z4jJVNXSjohjM4i9Hh813dLCNx2Sy", - "CoinType": "BTC", - "Balance": 53000.01741264, - "Description": "", - "WhiteListed": false, - "ColdStorage": false, - "SupportedExchanges": "" - }, - { - "Address": "3Nxwenay9Z8Lc9JBiywExpnEFiLp6Afp8v", - "CoinType": "BTC", - "Balance": 107848.28963408, - "Description": "", - "WhiteListed": false, - "ColdStorage": false, - "SupportedExchanges": "" - }, + "bankAccounts": [ { - "Address": "LgY8ahfHRhvjVQC1zJnBhFMG5pCTMuKRqh", - "CoinType": "LTC", - "Balance": 0.03665026, - "Description": "", - "WhiteListed": false, - "ColdStorage": false, - "SupportedExchanges": "" + "enabled": false, + "bankName": "Deutsche Bank Privat Und Geschaeftskunden AG", + "bankAddress": "Karlsruhe, 76125, GERMANY", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", + "accountName": "GLOBAL TRADE SOLUTIONS GmbH", + "accountNumber": "DE51660700240057016802", + "swiftCode": "DEUTDEDB660", + "iban": "DE51660700240057016802", + "supportedCurrencies": "EUR,USD" }, { - "Address": "0xb794f5ea0ba39494ce839613fffba74279579268", - "CoinType": "ETH", - "Balance": 0.25555604051326, - "Description": "", - "WhiteListed": false, - "ColdStorage": false, - "SupportedExchanges": "" + "enabled": false, + "bankName": "Deutsche Bank Privat Und Geschaeftskunden AG", + "bankAddress": "Karlsruhe, 76125, GERMANY", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", + "accountName": "GLOBAL TRADE SOLUTIONS GmbH", + "accountNumber": "DE78660700240057016801", + "swiftCode": "DEUTDEDB660", + "iban": "DE78660700240057016801", + "supportedCurrencies": "JPY,GBP" } ] }, - "exchanges": [ - { - "name": "BTC Markets", - "enabled": true, - "verbose": false, - "httpTimeout": 15000000000, - "websocketResponseCheckTimeout": 30000000, - "websocketResponseMaxLimit": 7000000000, - "websocketTrafficTimeout": 30000000000, - "websocketOrderbookBufferLimit": 5, - "baseCurrencies": "AUD", - "currencyPairs": { - "requestFormat": { - "uppercase": true, - "delimiter": "-" - }, - "configFormat": { - "uppercase": true, - "delimiter": "-" - }, - "useGlobalFormat": true, - "assetTypes": [ - "spot" - ], - "pairs": { - "spot": { - "enabled": "BTC-AUD", - "available": "BTC-AUD,LTC-AUD,LTC-BTC,ETH-BTC,ETH-AUD,ETC-AUD,ETC-BTC,XRP-AUD,XRP-BTC,POWR-AUD,POWR-BTC,OMG-AUD,OMG-BTC,BCHABC-AUD,BCHABC-BTC,BCHSV-AUD,BCHSV-BTC,GNT-AUD,GNT-BTC,BAT-AUD,BAT-BTC,XLM-AUD,XLM-BTC" - } - } - }, - "api": { - "authenticatedSupport": false, - "authenticatedWebsocketApiSupport": false, - "endpoints": { - "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" - }, - "credentials": { - "key": "Key", - "secret": "Secret" - }, - "credentialsValidator": { - "requiresKey": true, - "requiresSecret": true, - "requiresBase64DecodeSecret": true - } - }, - "features": { - "supports": { - "restAPI": true, - "restCapabilities": { - "autoPairUpdates": true - }, - "websocketAPI": true, - "websocketCapabilities": {} - }, - "enabled": { - "autoPairUpdates": true, - "websocketAPI": false + { + "name": "Bitflyer", + "enabled": true, + "verbose": false, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, + "websocketOrderbookBufferLimit": 5, + "baseCurrencies": "JPY", + "currencyPairs": { + "requestFormat": { + "uppercase": true, + "delimiter": "_" + }, + "configFormat": { + "uppercase": true, + "delimiter": "_" + }, + "useGlobalFormat": true, + "lastUpdated": 1566798411, + "assetTypes": [ + "spot", + "futures" + ], + "pairs": { + "spot": { + "enabled": "BTC_JPY,ETH_BTC,BCH_BTC", + "available": "BTC_JPY,FXBTC_JPY,ETH_BTC,BCH_BTC" } - }, - "bankAccounts": [ - { - "enabled": false, - "bankName": "", - "bankAddress": "", - "bankPostalCode": "", - "bankPostalCity": "", - "bankCountry": "", - "accountName": "", - "accountNumber": "", - "swiftCode": "", - "iban": "", - "supportedCurrencies": "" + } + }, + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "autoPairUpdates": true + }, + "websocketAPI": false, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": false, + "websocketAPI": false + } + }, + "bankAccounts": [ + { + "enabled": false, + "bankName": "", + "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ] + }, + { + "name": "Bithumb", + "enabled": true, + "verbose": false, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, + "baseCurrencies": "KRW", + "currencyPairs": { + "requestFormat": { + "uppercase": true, + "delimiter": "_" + }, + "configFormat": { + "uppercase": true, + "delimiter": "-" + }, + "useGlobalFormat": true, + "pairs": { + "spot": { + "assetEnabled": true, + "enabled": "USDT-KRW,QTUM-KRW,BTC-KRW,ETH-KRW,ETC-KRW,XRP-KRW,BCH-KRW,BTG-KRW,EOS-KRW", + "available": "AVAX-KRW,STRAX-KRW,KSM-KRW,RPL-KRW,ADA-KRW,ONT-KRW,EOS-KRW,STAT-KRW,APM-KRW,XPLA-KRW,STMX-KRW,FET-KRW,XVS-KRW,ROA-KRW,JOE-KRW,BNT-KRW,T-KRW,AUDIO-KRW,MIX-KRW,PUNDIX-KRW,USDC-KRW,ALGO-KRW,CTXC-KRW,IQ-KRW,RLY-KRW,GRT-KRW,NMR-KRW,FTM-KRW,WNCG-KRW,NCT-KRW,CSPR-KRW,TFUEL-KRW,EGG-KRW,MOC-KRW,BAT-KRW,ETC-KRW,TIA-KRW,GRACY-KRW,FRONT-KRW,DAI-KRW,ANKR-KRW,META-KRW,HOOK-KRW,BEL-KRW,MAGIC-KRW,ENTC-KRW,HUNT-KRW,STX-KRW,FIT-KRW,STEEM-KRW,CTSI-KRW,JUP-KRW,CAKE-KRW,DOGE-KRW,SUN-KRW,OCEAN-KRW,SOL-KRW,REQ-KRW,BNB-KRW,GAL-KRW,MBL-KRW,LRC-KRW,ILV-KRW,PEPE-KRW,IOST-KRW,XLM-KRW,CRV-KRW,NFT-KRW,PYR-KRW,TRX-KRW,TAVA-KRW,PYTH-KRW,TT-KRW,AAVE-KRW,KLAY-KRW,BAL-KRW,EVZ-KRW,FX-KRW,UMA-KRW,FLOW-KRW,ALEX-KRW,ELF-KRW,CVC-KRW,FLOKI-KRW,MASK-KRW,GAS-KRW,VIX-KRW,CELR-KRW,BLY-KRW,ARK-KRW,FNSA-KRW,OXT-KRW,VALOR-KRW,XTZ-KRW,HBAR-KRW,ONG-KRW,MTL-KRW,WAVES-KRW,ORBS-KRW,MANTA-KRW,ICX-KRW,SNX-KRW,API3-KRW,PENDLE-KRW,FLZ-KRW,APE-KRW,POWR-KRW,OGN-KRW,EDU-KRW,ARB-KRW,AXS-KRW,MBX-KRW,XRP-KRW,MATIC-KRW,USDT-KRW,1INCH-KRW,STORJ-KRW,UOS-KRW,RVN-KRW,LPT-KRW,OSMO-KRW,ALICE-KRW,LDO-KRW,TEMCO-KRW,COMP-KRW,VET-KRW,SFP-KRW,WIKEN-KRW,LBL-KRW,SHIB-KRW,GMT-KRW,AZIT-KRW,ZBCN-KRW,FLUX-KRW,ALT-KRW,AGI-KRW,SPURS-KRW,GRS-KRW,C98-KRW,ZIL-KRW,BCH-KRW,QTCON-KRW,SEI-KRW,GRND-KRW,SWAP-KRW,ETH-KRW,RSS3-KRW,STPT-KRW,FXS-KRW,SAND-KRW,MAP-KRW,MAV-KRW,LINK-KRW,MVC-KRW,QTUM-KRW,DAR-KRW,FANC-KRW,HIGH-KRW,ARKM-KRW,MANA-KRW,SUSHI-KRW,DVI-KRW,XEC-KRW,BTC-KRW,EL-KRW,THETA-KRW,CELO-KRW,KNC-KRW,POLA-KRW,LOOM-KRW,JASMY-KRW,INJ-KRW,KAVA-KRW,NEO-KRW,BIGTIME-KRW,MINA-KRW,NPT-KRW,IMX-KRW,ASM-KRW,FCT2-KRW,RLC-KRW,HIFI-KRW,CTC-KRW,DYDX-KRW,ZTX-KRW,AGIX-KRW,WEMIX-KRW,GTC-KRW,LM-KRW,OP-KRW,ONIT-KRW,ACS-KRW,LSK-KRW,REI-KRW,ATOM-KRW,WLD-KRW,GLM-KRW,COS-KRW,BTT-KRW,BFC-KRW,ACE-KRW,SC-KRW,BORA-KRW,GHX-KRW,ADP-KRW,STRK-KRW,LEVER-KRW,BOBA-KRW,BOA-KRW,HFT-KRW,RNDR-KRW,ENJ-KRW,RSR-KRW,XPR-KRW,IOTX-KRW,CYBER-KRW,WAXP-KRW,OBSR-KRW,MEV-KRW,UNI-KRW,APT-KRW,DAO-KRW,WAXL-KRW,SIX-KRW,GMX-KRW,RDNT-KRW,BTG-KRW,MNT-KRW,BLUR-KRW,XCN-KRW,YGG-KRW,MXC-KRW,ACH-KRW,RAD-KRW,MLK-KRW,DOT-KRW,JST-KRW,ZRX-KRW,STG-KRW,SOFI-KRW,WOM-KRW,TDROP-KRW,SNT-KRW,COTI-KRW,WOO-KRW,OAS-KRW,CRO-KRW,AQT-KRW,EGLD-KRW,ARPA-KRW,BSV-KRW,ASTR-KRW,AMO-KRW,AERGO-KRW,ID-KRW,SUI-KRW,GALA-KRW,CKB-KRW,BIOT-KRW,CFX-KRW,CHR-KRW,FLR-KRW,FITFI-KRW,YFI-KRW,CTK-KRW,W-KRW,MED-KRW,MKR-KRW,SXP-KRW,HIVE-KRW,CRTS-KRW,CHZ-KRW" } - ] + } }, - { - "name": "BTSE", - "enabled": true, - "verbose": false, - "httpTimeout": 15000000000, - "websocketResponseCheckTimeout": 30000000, - "websocketResponseMaxLimit": 7000000000, - "websocketTrafficTimeout": 30000000000, - "websocketOrderbookBufferLimit": 5, - "baseCurrencies": "USD", - "currencyPairs": { - "requestFormat": { - "uppercase": true, - "delimiter": "-" - }, - "configFormat": { - "uppercase": true, - "delimiter": "-" - }, - "useGlobalFormat": true, - "assetTypes": [ - "spot" - ], - "pairs": { - "spot": { - "enabled": "BTC-USD", - "available": "BTC-CNY,BTC-EUR,BTC-GBP,BTC-HKD,BTC-JPY,BTC-SGD,BTC-USD,ETH-CNY,ETH-EUR,ETH-GBP,ETH-HKD,ETH-JPY,ETH-SGD,ETH-USD,LTC-CNY,LTC-EUR,LTC-GBP,LTC-HKD,LTC-JPY,LTC-SGD,LTC-USD,USDT-CNY,USDT-EUR,USDT-GBP,USDT-HKD,USDT-JPY,USDT-SGD,USDT-USD,XMR-CNY,XMR-EUR,XMR-GBP,XMR-HKD,XMR-JPY,XMR-SGD,XMR-USD" + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret", + "clientID": "ClientID" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "tickerBatching": true, + "autoPairUpdates": true + }, + "websocketAPI": false, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": false + } + }, + "bankAccounts": [ + { + "enabled": false, + "bankName": "", + "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ], + "orderbook": { + "verificationBypass": false, + "websocketBufferLimit": 5, + "websocketBufferEnabled": false, + "publishPeriod": 10000000000 + } + }, + { + "name": "Bitmex", + "enabled": true, + "verbose": false, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, + "websocketOrderbookBufferLimit": 5, + "baseCurrencies": "USD", + "currencyPairs": { + "assetTypes": [ + "perpetualcontract", + "futures", + "downsideprofitcontract", + "upsideprofitcontract" + ], + "pairs": { + "downsideprofitcontract": { + "enabled": "XBT7D_D95", + "available": "XBT7D_D95", + "requestFormat": { + "uppercase": true, + "delimiter": "_" + }, + "configFormat": { + "uppercase": true, + "delimiter": "_" } - } - }, - "api": { - "authenticatedSupport": false, - "authenticatedWebsocketApiSupport": false, - "endpoints": { - "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" }, - "credentials": { - "key": "Key", - "secret": "Secret" + "futures": { + "enabled": "BCHZ19", + "available": "XRPZ19,BCHZ19,ADAZ19,EOSZ19,TRXZ19,XBTZ19,ETHZ19,LTCZ19", + "requestFormat": { + "uppercase": true + }, + "configFormat": { + "uppercase": true + } }, - "credentialsValidator": { - "requiresKey": true, - "requiresSecret": true - } - }, - "features": { - "supports": { - "restAPI": true, - "restCapabilities": { - "autoPairUpdates": true + "perpetualcontract": { + "enabled": "ETHUSD", + "available": "XBTUSD,ETHUSD", + "requestFormat": { + "uppercase": true }, - "websocketAPI": true, - "websocketCapabilities": {} + "configFormat": { + "uppercase": true + } }, - "enabled": { - "autoPairUpdates": true, - "websocketAPI": true + "upsideprofitcontract": { + "enabled": "XBT7D_U105", + "available": "XBT7D_U105", + "requestFormat": { + "uppercase": true, + "delimiter": "_" + }, + "configFormat": { + "uppercase": true, + "delimiter": "_" + } } - }, - "bankAccounts": [ - { - "enabled": false, - "bankName": "", - "bankAddress": "", - "bankPostalCode": "", - "bankPostalCity": "", - "bankCountry": "", - "accountName": "", - "accountNumber": "", - "swiftCode": "", - "iban": "", - "supportedCurrencies": "" + } + }, + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "tickerBatching": true, + "autoPairUpdates": true + }, + "websocketAPI": true, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": false + } + }, + "bankAccounts": [ + { + "enabled": false, + "bankName": "", + "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ] + }, + { + "name": "Bitstamp", + "enabled": true, + "verbose": false, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, + "websocketOrderbookBufferLimit": 5, + "baseCurrencies": "USD,EUR", + "currencyPairs": { + "requestFormat": { + "uppercase": true + }, + "configFormat": { + "uppercase": true + }, + "useGlobalFormat": true, + "assetTypes": [ + "spot" + ], + "pairs": { + "spot": { + "enabled": "BTCUSD,BTCEUR,EURUSD,XRPUSD,XRPEUR", + "available": "LTCUSD,ETHUSD,XRPEUR,BCHUSD,BCHEUR,BTCEUR,XRPBTC,EURUSD,BCHBTC,LTCEUR,BTCUSD,LTCBTC,XRPUSD,ETHBTC,ETHEUR" } - ] + } }, - { - "name": "Binance", - "enabled": true, - "verbose": false, - "httpTimeout": 15000000000, - "websocketResponseCheckTimeout": 30000000, - "websocketResponseMaxLimit": 7000000000, - "websocketTrafficTimeout": 30000000000, - "websocketOrderbookBufferLimit": 5, - "baseCurrencies": "USD", - "currencyPairs": { - "requestFormat": { - "uppercase": true - }, - "configFormat": { - "uppercase": true, - "delimiter": "-" + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret", + "clientID": "ClientID" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true, + "requiresClientID": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "autoPairUpdates": true + }, + "websocketAPI": true, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": false + } + }, + "bankAccounts": [ + { + "enabled": false, + "bankName": "", + "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ] + }, + { + "name": "Bybit", + "enabled": true, + "verbose": false, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, + "websocketOrderbookBufferLimit": 5, + "baseCurrencies": "USD", + "currencyPairs": { + "assetTypes": [ + "spot", + "margin", + "coinmarginedfutures", + "usdtmarginedfutures", + "usdcmarginedfutures", + "options" + ], + "pairs": { + "spot": { + "enabled": "BTC_USDT,ETH_USDT,XRP_USDT,EOS_USDT,ETH_BTC,XRP_BTC,DOT_USDT,XLM_USDT,LTC_USDT", + "available": "BTC_USDT,ETH_USDT,XRP_USDT,EOS_USDT,ETH_BTC,XRP_BTC,DOT_USDT,XLM_USDT,LTC_USDT,DOGE_USDT,CHZ_USDT,AXS_USDT,MANA_USDT,DYDX_USDT,MKR_USDT,COMP_USDT,AAVE_USDT,YFI_USDT,LINK_USDT,SUSHI_USDT,UNI_USDT,KSM_USDT,ICP_USDT,ADA_USDT,ETC_USDT,KLAY_USDT,XTZ_USDT,BCH_USDT,SRM_USDT,QNT_USDT,USDC_USDT,GRT_USDT,SOL_USDT,FIL_USDT,OMG_USDT,TRIBE_USDT,BAT_USDT,ZRX_USDT,CRV_USDT,AGLD_USDT,ANKR_USDT,PERP_USDT,MATIC_USDT,WAVES_USDT,LUNC_USDT,SPELL_USDT,SHIB_USDT,FTM_USDT,ATOM_USDT,ALGO_USDT,ENJ_USDT,CBX_USDT,SAND_USDT,AVAX_USDT,WOO_USDT,FTT_USDT,GODS_USDT,IMX_USDT,ENS_USDT,GM_USDT,CWAR_USDT,CAKE_USDT,STETH_USDT,GALFT_USDT,LFW_USDT,SLP_USDT,C98_USDT,PSP_USDT,GENE_USDT,AVA_USDT,ONE_USDT,PTU_USDT,SHILL_USDT,XYM_USDT,BOBA_USDT,JASMY_USDT,GALA_USDT,RNDR_USDT,TRVL_USDT,WEMIX_USDT,XEM_USDT,BICO_USDT,CEL_USDT,UMA_USDT,HOT_USDT,NEXO_USDT,BNT_USDT,SNX_USDT,REN_USDT,1INCH_USDT,TEL_USDT,SIS_USDT,LRC_USDT,LDO_USDT,REAL_USDT,KRL_USDT,DEVT_USDT,ETH_USDC,BTC_USDC,1SOL_USDT,PLT_USDT,IZI_USDT,QTUM_USDT,DCR_USDT,ZEN_USDT,THETA_USDT,MX_USDT,DGB_USDT,RVN_USDT,EGLD_USDT,RUNE_USDT,XLM_BTC,XLM_USDC,SOL_USDC,XRP_USDC,ALGO_BTC,SOL_BTC,RAIN_USDT,XEC_USDT,ICX_USDT,XDC_USDT,HNT_USDT,BTG_USDT,ZIL_USDT,HBAR_USDT,FLOW_USDT,SOS_USDT,KASTA_USDT,STX_USDT,SIDUS_USDT,VPAD_USDT,GGM_USDT,LOOKS_USDT,MBS_USDT,DAI_USDT,BUSD_USDT,ACA_USDT,MV_USDT,MIX_USDT,LTC_USDC,MANA_BTC,MATIC_BTC,LTC_BTC,DOT_BTC,SAND_BTC,MANA_USDC,MATIC_USDC,SAND_USDC,DOT_USDC,LUNC_USDC,RSS3_USDT,SYNR_USDT,TAP_USDT,ERTHA_USDT,GMX_USDT,T_USDT,ACH_USDT,JST_USDT,SUN_USDT,BTT_USDT,TRX_USDT,NFT_USDT,POKT_USDT,SCRT_USDT,PSTAKE_USDT,SON_USDT,HERO_USDT,DOME_USDT,USTC_USDT,BNB_USDT,NEAR_USDT,PAXG_USDT,SD_USDT,APE_USDT,BTC3S_USDT,BTC3L_USDT,FIDA_USDT,MINA_USDT,SC_USDT,RACA_USDT,CAPS_USDT,STG_USDT,GLMR_USDT,MOVR_USDT,ZAM_USDT,ETH_DAI,BTC_DAI,WBTC_USDT,XAVA_USDT,MELOS_USDT,GMT_USDT,GST_USDT,CELO_USDT,SFUND_USDT,ELT_USDT,LGX_USDT,APEX_USDT,CTC_USDT,COT_USDT,KMON_USDT,PLY_USDT,XWG_USDT,FITFI_USDT,STRM_USDT,GAL_USDT,ETH3S_USDT,ETH3L_USDT,KOK_USDT,FAME_USDT,XRP3S_USDT,XRP3L_USDT,USDD_USDT,OP_USDT,LUNA_USDT,DFI_USDT,MOVEZ_USDT,THN_USDT,DOT3S_USDT,DOT3L_USDT,VINU_USDT,BEL_USDT,FORT_USDT,AVAX2S_USDT,AVAX2L_USDT,ADA2S_USDT,ADA2L_USDT,WLKN_USDT,KON_USDT,LTC2S_USDT,LTC2L_USDT,SAND2S_USDT,SAND2L_USDT,OBX_USDT,SEOR_USDT,MNZ_USDT,CULT_USDT,DOGE_USDC,EOS_USDC,CUSD_USDT,SLG_USDT,CMP_USDT,KUNCI_USDT,GSTS_USDT,XETA_USDT,AZY_USDT,MMC_USDT,FLOKI_USDT,BABYDOGE_USDT,STAT_USDT,SAITAMA_USDT,MATIC2S_USDT,MATIC2L_USDT,ETC2S_USDT,ETC2L_USDT,DICE_USDT,WAXP_USDT,AR_USDT,KDA_USDT,ROSE_USDT,SLG_USDC,APE2S_USDT,APE2L_USDT,GMT2S_USDT,GMT2L_USDT,DEFY_USDT,PSG_USDT,BAR_USDT,JUV_USDT,ACM_USDT,INTER_USDT,AFC_USDT,CITY_USDT,LINK2L_USDT,LINK2S_USDT,FTM2L_USDT,FTM2S_USDT,SOLO_USDT,W_BTC,AVAX_USDC,ADA_USDC,OP_USDC,DOGE2S_USDT,DOGE2L_USDT,ATOM2S_USDT,ATOM2L_USDT,APEX_USDC,TRX_USDC,ICP_USDC,LINK_USDC,GMT_USDC,CHZ_USDC,SHIB_USDC,LDO_USDC,APE_USDC,FIL_USDC,CHRP_USDT,EOS2S_USDT,EOS2L_USDT,WWY_USDT,LING_USDT,SWEAT_USDT,DLC_USDT,OKG_USDT,ETHW_USDT,INJ_USDT,MPLX_USDT,MIBR_USDT,CO_USDT,AGLA_USDT,ROND_USDT,QMALL_USDT,PUMLX_USDT,GCAKE_USDT,APT_USDT,APT_USDC,USDT_EUR,MTK_USDT,MCRT_USDT,MASK_USDT,ECOX_USDT,HFT_USDC,HFT_USDT,KCAL_USDT,PEOPLE_USDT,TWT_USDT,ORT_USDT,HOOK_USDT,PRIMAL_USDT,MCT_USDT,OAS_USDT,MAGIC_USDT,MEE_USDT,TON_USDT,BONK_USDT,FLR_USDT,TIME_USDT,3P_USDT,RPL_USDT,SSV_USDT,FXS_USDT,CORE_USDT,RDNT_USDT,BLUR_USDT,LIS_USDT,AGIX_USDT,MDAO_USDT,ACS_USDT,HVH_USDT,GNS_USDT,DPX_USDT,PIP_USDT,PRIME_USDT,EVER_USDT,VRA_USDT,GPT_USDT,FB_USDT,DZOO_USDT,ID_USDT,ARB_USDC,ARB_USDT,XCAD_USDT,MBX_USDT,AXL_USDT,CGPT_USDT,PLAY_USDT,AGI_USDT,RLTM_USDT,SUI_USDT,SUI_USDC,TAMA_USDT,MVL_USDT,PEPE_USDT,LADYS_USDT,LMWR_USDT,BOB_USDT,TOMI_USDT,KARATE_USDT,SUIA_USDT,TURBOS_USDT,FMB_USDT,CAPO_USDT,TENET_USDT,VELO_USDT,ELDA_USDT,CANDY_USDT,FON_USDT,OMN_USDT,TOMS_USDT,MTC_USDT,VELA_USDT,USDT_BRZ,BTC_BRZ,PENDLE_USDT,EGO_USDT,PEPE2_USDT,NYM_USDT,MNT_USDT,MNT_USDC,MNT_BTC,GSWIFT_USDT,SALD_USDT,ARKM_USDT,NEON_USDT,WLD_USDC,WLD_USDT,PLANET_USDT,DSRUN_USDT,SPARTA_USDT,TAVA_USDT,SEILOR_USDT,SEI_USDT,CYBER_USDT,ORDI_USDT,KAVA_USDT,VV_USDT,SAIL_USDT,PYUSD_USDT,SOL_EUR,USDC_EUR,ADA_EUR,DOGE_EUR,LTC_EUR,XRP_EUR,ETH_EUR,BTC_EUR,VEXT_USDT,CTT_USDT,NEXT_USDT,KAS_USDT,NESS_USDT,CAT_USDT,FET_USDT,LEVER_USDT,VEGA_USDT,ZTX_USDT", + "requestFormat": { + "uppercase": true + }, + "configFormat": { + "uppercase": true, + "delimiter": "_" + } }, - "useGlobalFormat": true, - "assetTypes": [ - "spot" - ], - "pairs": { - "spot": { - "enabled": "BTC-USDT,DOGE-USDT", - "available": "ETH-BTC,LTC-BTC,BNB-BTC,NEO-BTC,QTUM-ETH,EOS-ETH,SNT-ETH,BNT-ETH,GAS-BTC,BNB-ETH,BTC-USDT,ETH-USDT,OAX-ETH,DNT-ETH,MCO-ETH,MCO-BTC,WTC-BTC,WTC-ETH,LRC-BTC,LRC-ETH,QTUM-BTC,YOYO-BTC,OMG-BTC,OMG-ETH,ZRX-BTC,ZRX-ETH,STRAT-BTC,STRAT-ETH,SNGLS-BTC,BQX-BTC,BQX-ETH,KNC-BTC,KNC-ETH,FUN-BTC,FUN-ETH,SNM-BTC,SNM-ETH,NEO-ETH,IOTA-BTC,IOTA-ETH,LINK-BTC,LINK-ETH,XVG-BTC,XVG-ETH,MDA-BTC,MDA-ETH,MTL-BTC,MTL-ETH,EOS-BTC,SNT-BTC,ETC-ETH,ETC-BTC,MTH-BTC,MTH-ETH,ENG-BTC,ENG-ETH,DNT-BTC,ZEC-BTC,ZEC-ETH,BNT-BTC,AST-BTC,AST-ETH,DASH-BTC,DASH-ETH,OAX-BTC,BTG-BTC,BTG-ETH,EVX-BTC,EVX-ETH,REQ-BTC,REQ-ETH,VIB-BTC,VIB-ETH,TRX-BTC,TRX-ETH,POWR-BTC,POWR-ETH,ARK-BTC,ARK-ETH,YOYO-ETH,XRP-BTC,XRP-ETH,ENJ-BTC,ENJ-ETH,STORJ-BTC,STORJ-ETH,BNB-USDT,YOYO-BNB,POWR-BNB,KMD-BTC,KMD-ETH,NULS-BNB,RCN-BTC,RCN-ETH,RCN-BNB,NULS-BTC,NULS-ETH,RDN-BTC,RDN-ETH,RDN-BNB,XMR-BTC,XMR-ETH,DLT-BNB,WTC-BNB,DLT-BTC,DLT-ETH,AMB-BTC,AMB-ETH,AMB-BNB,BAT-BTC,BAT-ETH,BAT-BNB,BCPT-BTC,BCPT-ETH,BCPT-BNB,ARN-BTC,ARN-ETH,GVT-BTC,GVT-ETH,CDT-BTC,CDT-ETH,GXS-BTC,GXS-ETH,NEO-USDT,NEO-BNB,POE-BTC,POE-ETH,QSP-BTC,QSP-ETH,QSP-BNB,BTS-BTC,BTS-ETH,XZC-BTC,XZC-ETH,XZC-BNB,LSK-BTC,LSK-ETH,LSK-BNB,TNT-BTC,TNT-ETH,FUEL-BTC,MANA-BTC,MANA-ETH,BCD-BTC,BCD-ETH,DGD-BTC,DGD-ETH,IOTA-BNB,ADX-BTC,ADX-ETH,ADA-BTC,ADA-ETH,PPT-BTC,PPT-ETH,CMT-BTC,CMT-ETH,CMT-BNB,XLM-BTC,XLM-ETH,XLM-BNB,CND-BTC,CND-ETH,CND-BNB,LEND-BTC,LEND-ETH,WABI-BTC,WABI-ETH,WABI-BNB,LTC-ETH,LTC-USDT,LTC-BNB,TNB-BTC,TNB-ETH,WAVES-BTC,WAVES-ETH,WAVES-BNB,GTO-BTC,GTO-ETH,GTO-BNB,ICX-BTC,ICX-ETH,ICX-BNB,OST-BTC,OST-ETH,OST-BNB,ELF-BTC,ELF-ETH,AION-BTC,AION-ETH,AION-BNB,NEBL-BTC,NEBL-ETH,NEBL-BNB,BRD-BTC,BRD-ETH,BRD-BNB,MCO-BNB,EDO-BTC,EDO-ETH,NAV-BTC,LUN-BTC,APPC-BTC,APPC-ETH,APPC-BNB,VIBE-BTC,VIBE-ETH,RLC-BTC,RLC-ETH,RLC-BNB,INS-BTC,INS-ETH,PIVX-BTC,PIVX-ETH,PIVX-BNB,IOST-BTC,IOST-ETH,STEEM-BTC,STEEM-ETH,STEEM-BNB,NANO-BTC,NANO-ETH,NANO-BNB,VIA-BTC,VIA-ETH,VIA-BNB,BLZ-BTC,BLZ-ETH,BLZ-BNB,AE-BTC,AE-ETH,AE-BNB,NCASH-BTC,NCASH-ETH,POA-BTC,POA-ETH,ZIL-BTC,ZIL-ETH,ZIL-BNB,ONT-BTC,ONT-ETH,ONT-BNB,STORM-BTC,STORM-ETH,STORM-BNB,QTUM-BNB,QTUM-USDT,XEM-BTC,XEM-ETH,XEM-BNB,WAN-BTC,WAN-ETH,WAN-BNB,WPR-BTC,WPR-ETH,QLC-BTC,QLC-ETH,SYS-BTC,SYS-ETH,SYS-BNB,QLC-BNB,GRS-BTC,GRS-ETH,ADA-USDT,ADA-BNB,GNT-BTC,GNT-ETH,LOOM-BTC,LOOM-ETH,LOOM-BNB,XRP-USDT,REP-BTC,REP-ETH,BTC-TUSD,ETH-TUSD,ZEN-BTC,ZEN-ETH,ZEN-BNB,SKY-BTC,SKY-ETH,SKY-BNB,EOS-USDT,EOS-BNB,CVC-BTC,CVC-ETH,THETA-BTC,THETA-ETH,THETA-BNB,XRP-BNB,TUSD-USDT,IOTA-USDT,XLM-USDT,IOTX-BTC,IOTX-ETH,QKC-BTC,QKC-ETH,AGI-BTC,AGI-ETH,AGI-BNB,NXS-BTC,NXS-ETH,NXS-BNB,ENJ-BNB,DATA-BTC,DATA-ETH,ONT-USDT,TRX-BNB,TRX-USDT,ETC-USDT,ETC-BNB,ICX-USDT,SC-BTC,SC-ETH,SC-BNB,NPXS-ETH,KEY-BTC,KEY-ETH,NAS-BTC,NAS-ETH,NAS-BNB,MFT-BTC,MFT-ETH,MFT-BNB,DENT-ETH,ARDR-BTC,ARDR-ETH,NULS-USDT,HOT-BTC,HOT-ETH,VET-BTC,VET-ETH,VET-USDT,VET-BNB,DOCK-BTC,DOCK-ETH,POLY-BTC,POLY-BNB,HC-BTC,HC-ETH,GO-BTC,GO-BNB,PAX-USDT,RVN-BTC,RVN-BNB,DCR-BTC,DCR-BNB,MITH-BTC,MITH-BNB,BNB-PAX,BTC-PAX,ETH-PAX,XRP-PAX,EOS-PAX,XLM-PAX,REN-BTC,REN-BNB,BNB-TUSD,XRP-TUSD,EOS-TUSD,XLM-TUSD,BNB-USDC,BTC-USDC,ETH-USDC,XRP-USDC,EOS-USDC,XLM-USDC,USDC-USDT,ADA-TUSD,TRX-TUSD,NEO-TUSD,TRX-XRP,XZC-XRP,PAX-TUSD,USDC-TUSD,USDC-PAX,LINK-USDT,LINK-TUSD,LINK-PAX,LINK-USDC,WAVES-USDT,WAVES-TUSD,WAVES-USDC,LTC-TUSD,LTC-PAX,LTC-USDC,TRX-PAX,TRX-USDC,BTT-BNB,BTT-USDT,BNB-USDS,BTC-USDS,USDS-USDT,USDS-PAX,USDS-TUSD,USDS-USDC,BTT-PAX,BTT-TUSD,BTT-USDC,ONG-BNB,ONG-BTC,ONG-USDT,HOT-BNB,HOT-USDT,ZIL-USDT,ZRX-BNB,ZRX-USDT,FET-BNB,FET-BTC,FET-USDT,BAT-USDT,XMR-BNB,XMR-USDT,ZEC-BNB,ZEC-USDT,ZEC-PAX,ZEC-TUSD,ZEC-USDC,IOST-BNB,IOST-USDT,CELR-BNB,CELR-BTC,CELR-USDT,ADA-PAX,ADA-USDC,NEO-PAX,NEO-USDC,DASH-BNB,DASH-USDT,NANO-USDT,OMG-BNB,OMG-USDT,THETA-USDT,ENJ-USDT,MITH-USDT,MATIC-BNB,MATIC-BTC,MATIC-USDT,ATOM-BNB,ATOM-BTC,ATOM-USDT,ATOM-USDC,ATOM-TUSD,ETC-TUSD,BAT-USDC,BAT-PAX,BAT-TUSD,PHB-BNB,PHB-BTC,PHB-TUSD,TFUEL-BNB,TFUEL-BTC,TFUEL-USDT,ONE-BNB,ONE-BTC,ONE-USDT,ONE-USDC,FTM-BNB,FTM-BTC,FTM-USDT,FTM-USDC,ALGO-BNB,ALGO-BTC,ALGO-USDT,ALGO-TUSD,ALGO-PAX,ALGO-USDC,GTO-USDT,ERD-BNB,ERD-BTC,ERD-USDT,DOGE-BNB,DOGE-BTC,DOGE-USDT,DUSK-BNB,DUSK-BTC,DUSK-USDT,DUSK-USDC,DUSK-PAX,BGBP-USDC,ANKR-BNB,ANKR-BTC,ANKR-USDT,ONT-PAX,ONT-USDC,WIN-BNB,WIN-USDT,WIN-USDC,COS-BNB,COS-BTC,COS-USDT,NPXS-USDT,COCOS-BNB,COCOS-BTC,COCOS-USDT,MTL-USDT,TOMO-BNB,TOMO-BTC,TOMO-USDT,TOMO-USDC,PERL-BNB,PERL-BTC,PERL-USDT,DENT-USDT,MFT-USDT,KEY-USDT,STORM-USDT,DOCK-USDT,WAN-USDT,FUN-USDT,CVC-USDT,BTT-TRX,WIN-TRX,CHZ-BNB,CHZ-BTC,CHZ-USDT,BAND-BNB,BAND-BTC,BAND-USDT,BNB-BUSD,BTC-BUSD,BUSD-USDT,BEAM-BNB,BEAM-BTC,BEAM-USDT,XTZ-BNB,XTZ-BTC,XTZ-USDT,REN-USDT,RVN-USDT,HC-USDT,HBAR-BNB,HBAR-BTC,HBAR-USDT,NKN-BNB,NKN-BTC,NKN-USDT,XRP-BUSD,ETH-BUSD,LTC-BUSD,LINK-BUSD,ETC-BUSD,STX-BNB,STX-BTC,STX-USDT,KAVA-BNB,KAVA-BTC,KAVA-USDT,BUSD-NGN,BNB-NGN,BTC-NGN,ARPA-BNB,ARPA-BTC,ARPA-USDT,TRX-BUSD,EOS-BUSD,IOTX-USDT,RLC-USDT,MCO-USDT,XLM-BUSD,ADA-BUSD,CTXC-BNB,CTXC-BTC,CTXC-USDT,BCH-BNB,BCH-BTC,BCH-USDT,BCH-USDC,BCH-TUSD,BCH-PAX,BCH-BUSD,BTC-RUB,ETH-RUB,XRP-RUB,BNB-RUB,TROY-BNB,TROY-BTC,TROY-USDT,BUSD-RUB,QTUM-BUSD,VET-BUSD" + "coinmarginedfutures": { + "enabled": "ADA_USD,BTC_USD,BTC_USDH24,BTC_USDZ23,DOT_USD", + "available": "ADA_USD,BTC_USD,BTC_USDH24,BTC_USDZ23,DOT_USD,EOS_USD,ETH_USD,ETH_USDH24,ETH_USDZ23,LTC_USD,MAN_AUSD,XRP_USD", + "requestFormat": { + "uppercase": true + }, + "configFormat": { + "uppercase": true, + "delimiter": "_" } - } - }, - "api": { - "authenticatedSupport": false, - "authenticatedWebsocketApiSupport": false, - "endpoints": { - "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" }, - "credentials": { - "key": "Key", - "secret": "Secret" + "usdcmarginedfutures": { + "enabled": "ETH-PERP,BNB-PERP,SOL-PERP,BTC-PERP", + "available": "BNB-PERP,BTC-03NOV23,BTC-20OCT23,BTC-24NOV23,BTC-27OCT23,BTC-28JUN24,BTC-29DEC23,BTC-29MAR24,BTC-PERP,ETC-PERP,ETH-03NOV23,ETH-20OCT23,ETH-24NOV23,ETH-27OCT23,ETH-28JUN24,ETH-29DEC23,ETH-29MAR24,ETH-PERP,MAT-ICPERP,OPP-ERP,SOL-PERP,XRP-PERP", + "requestFormat": { + "uppercase": true + }, + "configFormat": { + "uppercase": true, + "delimiter": "-" + } }, - "credentialsValidator": { - "requiresKey": true, - "requiresSecret": true - } - }, - "features": { - "supports": { - "restAPI": true, - "restCapabilities": { - "tickerBatching": true, - "autoPairUpdates": true + "usdtmarginedfutures": { + "enabled": "BTC_USDT,10000LADYS_USDT,IOTA_USDT,AAVE_USDT", + "available": "10000LADYS_USDT,10000NFT_USDT,1000BONK_USDT,1000BTT_USDT,1000FLOKI_USDT,1000LUNC_USDT,1000PEPE_USDT,1000XEC_USDT,1INCH_USDT,AAVE_USDT,ACH_USDT,ADA_USDT,AGIX_USDT,AGLD_USDT,AKRO_USDT,ALGO_USDT,ALICE_USDT,ALPACA_USDT,ALPHA_USDT,AMB_USDT,ANKR_USDT,ANT_USDT,APE_USDT,API3_USDT,APT_USDT,ARB_USDT,ARKM_USDT,ARK_USDT,ARPA_USDT,AR_USDT,ASTR_USDT,ATA_USDT,ATOM_USDT,AUCTION_USDT,AUDIO_USDT,AVAX_USDT,AXS_USDT,BADGER_USDT,BAKE_USDT,BAL_USDT,BAND_USDT,BAT_USDT,BCH_USDT,BEL_USDT,BICO_USDT,BIGTIME_USDT,BLUR_USDT,BLZ_USDT,BNB_USDT,BNT_USDT,BNX_USDT,BOBA_USDT,BOND_USDT,BSV_USDT,BSW_USDT,BTC_USDT,BUSD_USDT,C98_USDT,CEEK_USDT,CELO_USDT,CELR_USDT,CFX_USDT,CHR_USDT,CHZ_USDT,CKB_USDT,COMBO_USDT,COMP_USDT,CORE_USDT,COTI_USDT,CRO_USDT,CRV_USDT,CTC_USDT,CTK_USDT,CTSI_USDT,CVC_USDT,CVX_USDT,CYBER_USDT,DAR_USDT,DASH_USDT,DENT_USDT,DGB_USDT,DODO_USDT,DOGE_USDT,DOT_USDT,DUSK_USDT,DYDX_USDT,EDU_USDT,EGLD_USDT,ENJ_USDT,ENS_USDT,EOS_USDT,ETC_USDT,ETH_USDT,ETHW_USDT,FET_USDT,FIL_USDT,FITFI_USDT,FLM_USDT,FLOW_USDT,FLR_USDT,FORTH_USDT,FRONT_USDT,FTM_USDT,FXS_USDT,GALA_USDT,GAL_USDT,GFT_USDT,GLMR_USDT,GLM_USDT,GMT_USDT,GMX_USDT,GPT_USDT,GRT_USDT,GTC_USDT,HBAR_USDT,HFT_USDT,HIFI_USDT,HIGH_USDT,HNT_USDT,HOOK_USDT,HOT_USDT,ICP_USDT,ICX_USDT,IDEX_USDT,ID_USDT,ILV_USDT,IMX_USDT,INJ_USDT,IOST_USDT,IOTA_USDT,IOTX_USDT,JASMY_USDT,JOE_USDT,JST_USDT,KAS_USDT,KAVA_USDT,KDA_USDT,KEY_USDT,KLAY_USDT,KNC_USDT,KSM_USDT,LDO_USDT,LEVER_USDT,LINA_USDT,LINK_USDT,LIT_USDT,LOOKS_USDT,LOOM_USDT,LPT_USDT,LQTY_USDT,LRC_USDT,LTC_USDT,LUNA2_USDT,MAGIC_USDT,MANA_USDT,MASK_USDT,MATIC_USDT,MAV_USDT,MC_USDT,MDT_USDT,MINA_USDT,MKR_USDT,MNT_USDT,MTL_USDT,MULTI_USDT,NEAR_USDT,NEO_USDT,NKN_USDT,NMR_USDT,NTRN_USDT,OCEAN_USDT,OGN_USDT,OG_USDT,OMG_USDT,ONE_USDT,ONT_USDT,OP_USDT,ORBS_USDT,ORDI_USDT,OXT_USDT,PAXG_USDT,PENDLE_USDT,PEOPLE_USDT,PERP_USDT,PHB_USDT,PROM_USDT,QNT_USDT,QTUM_USDT,RAD_USDT,RDNT_USDT,REEF_USDT,REN_USDT,REQ_USDT,RLC_USDT,RNDR_USDT,ROSE_USDT,RPL_USDT,RSR_USDT,RSS3_USDT,RUNE_USDT,RVN_USDT,SAND_USDT,SCRT_USDT,SC_USDT,SEI_USDT,SFP_USDT,SHIB1000_USDT,SKL_USDT,SLP_USDT,SNX_USDT,SOL_USDT,SPELL_USDT,SSV_USDT,STG_USDT,STMX_USDT,STORJ_USDT,STPT_USDT,STRAX_USDT,STX_USDT,SUI_USDT,SUN_USDT,SUSHI_USDT,SWEAT_USDT,SXP_USDT,THETA_USDT,TLM_USDT,TOMI_USDT,TOMO_USDT,TON_USDT,TRB_USDT,TRU_USDT,TRX_USDT,T_USDT,TWT_USDT,UMA_USDT,UNFI_USDT,UNI_USDT,USDC_USDT,VET_USDT,VGX_USDT,VRA_USDT,WAVES_USDT,WAXP_USDT,WLD_USDT,WOO_USDT,WSM_USDT,XCN_USDT,XEM_USDT,XLM_USDT,XMR_USDT,XNO_USDT,XRP_USDT,XTZ_USDT,XVG_USDT,XVS_USDT,YFII_USDT,YFI_USDT,YGG_USDT,ZEC_USDT,ZEN_USDT,ZIL_USDT,ZRX_USDT", + "requestFormat": { + "uppercase": true }, - "websocketAPI": true, - "websocketCapabilities": {} + "configFormat": { + "uppercase": true, + "delimiter": "_" + } }, - "enabled": { - "autoPairUpdates": true, - "websocketAPI": true + "options": { + "enabled": "BTC-28JUN24-70000-C,BTC-28JUN24-70000-P,BTC-28JUN24-60000-C,BTC-28JUN24-60000-P", + "available": "BTC-28JUN24-70000-C,BTC-28JUN24-70000-P,BTC-28JUN24-60000-C,BTC-28JUN24-60000-P,BTC-28JUN24-50000-C,BTC-28JUN24-50000-P,BTC-28JUN24-40000-C,BTC-28JUN24-40000-P,BTC-28JUN24-32000-C,BTC-28JUN24-32000-P,BTC-28JUN24-30000-C,BTC-28JUN24-30000-P,BTC-28JUN24-28000-C,BTC-28JUN24-28000-P,BTC-28JUN24-25000-C,BTC-28JUN24-25000-P,BTC-28JUN24-20000-C,BTC-28JUN24-20000-P,BTC-28JUN24-10000-C,BTC-28JUN24-10000-P,BTC-29MAR24-70000-C,BTC-29MAR24-70000-P,BTC-29MAR24-60000-C,BTC-29MAR24-60000-P,BTC-29MAR24-50000-C,BTC-29MAR24-50000-P,BTC-29MAR24-45000-C,BTC-29MAR24-45000-P,BTC-29MAR24-40000-C,BTC-29MAR24-40000-P,BTC-29MAR24-36000-C,BTC-29MAR24-36000-P,BTC-29MAR24-35000-C,BTC-29MAR24-35000-P,BTC-29MAR24-33000-C,BTC-29MAR24-33000-P,BTC-29MAR24-31000-C,BTC-29MAR24-31000-P,BTC-29MAR24-30000-C,BTC-29MAR24-30000-P,BTC-29MAR24-28000-C,BTC-29MAR24-28000-P,BTC-29MAR24-27000-C,BTC-29MAR24-27000-P,BTC-29MAR24-26000-C,BTC-29MAR24-26000-P,BTC-29MAR24-24000-C,BTC-29MAR24-24000-P,BTC-29MAR24-20000-C,BTC-29MAR24-20000-P,BTC-29MAR24-10000-C,BTC-29MAR24-10000-P,BTC-29DEC23-80000-C,BTC-29DEC23-80000-P,BTC-29DEC23-70000-C,BTC-29DEC23-70000-P,BTC-29DEC23-60000-C,BTC-29DEC23-60000-P,BTC-29DEC23-50000-C,BTC-29DEC23-50000-P,BTC-29DEC23-40000-C,BTC-29DEC23-40000-P,BTC-29DEC23-36000-C,BTC-29DEC23-36000-P,BTC-29DEC23-35000-C,BTC-29DEC23-35000-P,BTC-29DEC23-34000-C,BTC-29DEC23-34000-P,BTC-29DEC23-32000-C,BTC-29DEC23-32000-P,BTC-29DEC23-31500-C,BTC-29DEC23-31500-P,BTC-29DEC23-30500-C,BTC-29DEC23-30500-P,BTC-29DEC23-30000-C,BTC-29DEC23-30000-P,BTC-29DEC23-29500-C,BTC-29DEC23-29500-P,BTC-29DEC23-29000-C,BTC-29DEC23-29000-P,BTC-29DEC23-28000-C,BTC-29DEC23-28000-P,BTC-29DEC23-27500-C,BTC-29DEC23-27500-P,BTC-29DEC23-27000-C,BTC-29DEC23-27000-P,BTC-29DEC23-26000-C,BTC-29DEC23-26000-P,BTC-29DEC23-25000-C,BTC-29DEC23-25000-P,BTC-29DEC23-24000-C,BTC-29DEC23-24000-P,BTC-29DEC23-22000-C,BTC-29DEC23-22000-P,BTC-29DEC23-20000-C,BTC-29DEC23-20000-P,BTC-29DEC23-15000-C,BTC-29DEC23-15000-P,BTC-29DEC23-10000-C,BTC-29DEC23-10000-P,BTC-24NOV23-40000-C,BTC-24NOV23-40000-P,BTC-24NOV23-38000-C,BTC-24NOV23-38000-P,BTC-24NOV23-36000-C,BTC-24NOV23-36000-P,BTC-24NOV23-34000-C,BTC-24NOV23-34000-P,BTC-24NOV23-32000-C,BTC-24NOV23-32000-P,BTC-24NOV23-31500-C,BTC-24NOV23-31500-P,BTC-24NOV23-30500-C,BTC-24NOV23-30500-P,BTC-24NOV23-30000-C,BTC-24NOV23-30000-P,BTC-24NOV23-29500-C,BTC-24NOV23-29500-P,BTC-24NOV23-29000-C,BTC-24NOV23-29000-P,BTC-24NOV23-28500-C,BTC-24NOV23-28500-P,BTC-24NOV23-28000-C,BTC-24NOV23-28000-P,BTC-24NOV23-27500-C,BTC-24NOV23-27500-P,BTC-24NOV23-27000-C,BTC-24NOV23-27000-P,BTC-24NOV23-26500-C,BTC-24NOV23-26500-P,BTC-24NOV23-26000-C,BTC-24NOV23-26000-P,BTC-24NOV23-25500-C,BTC-24NOV23-25500-P,BTC-24NOV23-25000-C,BTC-24NOV23-25000-P,BTC-24NOV23-24000-C,BTC-24NOV23-24000-P,BTC-24NOV23-23000-C,BTC-24NOV23-23000-P,BTC-24NOV23-22000-C,BTC-24NOV23-22000-P,BTC-24NOV23-20000-C,BTC-24NOV23-20000-P,BTC-24NOV23-18000-C,BTC-24NOV23-18000-P,BTC-24NOV23-16000-C,BTC-24NOV23-16000-P,BTC-3NOV23-36000-C,BTC-3NOV23-36000-P,BTC-3NOV23-34000-C,BTC-3NOV23-34000-P,BTC-3NOV23-32000-C,BTC-3NOV23-32000-P,BTC-3NOV23-30000-C,BTC-3NOV23-30000-P,BTC-3NOV23-29000-C,BTC-3NOV23-29000-P,BTC-3NOV23-28500-C,BTC-3NOV23-28500-P,BTC-3NOV23-27500-C,BTC-3NOV23-27500-P,BTC-3NOV23-27000-C,BTC-3NOV23-27000-P,BTC-3NOV23-26500-C,BTC-3NOV23-26500-P,BTC-3NOV23-26000-C,BTC-3NOV23-26000-P,BTC-3NOV23-25000-C,BTC-3NOV23-25000-P,BTC-3NOV23-24000-C,BTC-3NOV23-24000-P,BTC-3NOV23-22000-C,BTC-3NOV23-22000-P,BTC-3NOV23-20000-C,BTC-3NOV23-20000-P,BTC-3NOV23-18000-C,BTC-3NOV23-18000-P,BTC-27OCT23-44000-C,BTC-27OCT23-44000-P,BTC-27OCT23-42000-C,BTC-27OCT23-42000-P,BTC-27OCT23-40000-C,BTC-27OCT23-40000-P,BTC-27OCT23-38000-C,BTC-27OCT23-38000-P,BTC-27OCT23-37000-C,BTC-27OCT23-37000-P,BTC-27OCT23-35000-C,BTC-27OCT23-35000-P,BTC-27OCT23-34500-C,BTC-27OCT23-34500-P,BTC-27OCT23-33500-C,BTC-27OCT23-33500-P,BTC-27OCT23-32500-C,BTC-27OCT23-32500-P,BTC-27OCT23-31500-C,BTC-27OCT23-31500-P,BTC-27OCT23-31000-C,BTC-27OCT23-31000-P,BTC-27OCT23-30500-C,BTC-27OCT23-30500-P,BTC-27OCT23-30000-C,BTC-27OCT23-30000-P,BTC-27OCT23-29500-C,BTC-27OCT23-29500-P,BTC-27OCT23-29000-C,BTC-27OCT23-29000-P,BTC-27OCT23-28750-C,BTC-27OCT23-28750-P,BTC-27OCT23-28500-C,BTC-27OCT23-28500-P,BTC-27OCT23-28250-C,BTC-27OCT23-28250-P,BTC-27OCT23-28000-C,BTC-27OCT23-28000-P,BTC-27OCT23-27750-C,BTC-27OCT23-27750-P,BTC-27OCT23-27500-C,BTC-27OCT23-27500-P,BTC-27OCT23-27250-C,BTC-27OCT23-27250-P,BTC-27OCT23-27000-C,BTC-27OCT23-27000-P,BTC-27OCT23-26500-C,BTC-27OCT23-26500-P,BTC-27OCT23-26000-C,BTC-27OCT23-26000-P,BTC-27OCT23-25500-C,BTC-27OCT23-25500-P,BTC-27OCT23-25000-C,BTC-27OCT23-25000-P,BTC-27OCT23-24000-C,BTC-27OCT23-24000-P,BTC-27OCT23-23000-C,BTC-27OCT23-23000-P,BTC-27OCT23-22000-C,BTC-27OCT23-22000-P,BTC-27OCT23-20000-C,BTC-27OCT23-20000-P,BTC-27OCT23-18000-C,BTC-27OCT23-18000-P,BTC-27OCT23-16000-C,BTC-27OCT23-16000-P,BTC-20OCT23-36000-C,BTC-20OCT23-36000-P,BTC-20OCT23-34000-C,BTC-20OCT23-34000-P,BTC-20OCT23-32000-C,BTC-20OCT23-32000-P,BTC-20OCT23-31000-C,BTC-20OCT23-31000-P,BTC-20OCT23-30500-C,BTC-20OCT23-30500-P,BTC-20OCT23-30000-C,BTC-20OCT23-30000-P,BTC-20OCT23-29500-C,BTC-20OCT23-29500-P,BTC-20OCT23-29000-C,BTC-20OCT23-29000-P,BTC-20OCT23-28750-C,BTC-20OCT23-28750-P,BTC-20OCT23-28500-C,BTC-20OCT23-28500-P,BTC-20OCT23-28250-C,BTC-20OCT23-28250-P,BTC-20OCT23-28000-C,BTC-20OCT23-28000-P,BTC-20OCT23-27750-C,BTC-20OCT23-27750-P,BTC-20OCT23-27500-C,BTC-20OCT23-27500-P,BTC-20OCT23-27250-C,BTC-20OCT23-27250-P,BTC-20OCT23-27000-C,BTC-20OCT23-27000-P,BTC-20OCT23-26750-C,BTC-20OCT23-26750-P,BTC-20OCT23-26500-C,BTC-20OCT23-26500-P,BTC-20OCT23-26250-C,BTC-20OCT23-26250-P,BTC-20OCT23-26000-C,BTC-20OCT23-26000-P,BTC-20OCT23-25750-C,BTC-20OCT23-25750-P,BTC-20OCT23-25500-C,BTC-20OCT23-25500-P,BTC-20OCT23-25000-C,BTC-20OCT23-25000-P,BTC-20OCT23-24000-C,BTC-20OCT23-24000-P,BTC-20OCT23-22000-C,BTC-20OCT23-22000-P,BTC-20OCT23-20000-C,BTC-20OCT23-20000-P,BTC-20OCT23-18000-C,BTC-20OCT23-18000-P,BTC-19OCT23-29750-C,BTC-19OCT23-29750-P,BTC-19OCT23-29500-C,BTC-19OCT23-29500-P,BTC-19OCT23-29250-C,BTC-19OCT23-29250-P,BTC-19OCT23-29000-C,BTC-19OCT23-29000-P,BTC-19OCT23-28750-C,BTC-19OCT23-28750-P,BTC-19OCT23-28500-C,BTC-19OCT23-28500-P,BTC-19OCT23-28250-C,BTC-19OCT23-28250-P,BTC-19OCT23-28000-C,BTC-19OCT23-28000-P,BTC-19OCT23-27750-C,BTC-19OCT23-27750-P,BTC-19OCT23-27500-C,BTC-19OCT23-27500-P,BTC-19OCT23-27250-C,BTC-19OCT23-27250-P,BTC-19OCT23-27000-C,BTC-19OCT23-27000-P,BTC-19OCT23-26750-C,BTC-19OCT23-26750-P,BTC-19OCT23-26500-C,BTC-19OCT23-26500-P,BTC-19OCT23-26250-C,BTC-19OCT23-26250-P,BTC-19OCT23-26000-C,BTC-19OCT23-26000-P,BTC-19OCT23-25750-C,BTC-19OCT23-25750-P,BTC-18OCT23-29500-C,BTC-18OCT23-29500-P,BTC-18OCT23-29250-C,BTC-18OCT23-29250-P,BTC-18OCT23-29000-C,BTC-18OCT23-29000-P,BTC-18OCT23-28750-C,BTC-18OCT23-28750-P,BTC-18OCT23-28500-C,BTC-18OCT23-28500-P,BTC-18OCT23-28250-C,BTC-18OCT23-28250-P,BTC-18OCT23-28000-C,BTC-18OCT23-28000-P,BTC-18OCT23-27750-C,BTC-18OCT23-27750-P,BTC-18OCT23-27500-C,BTC-18OCT23-27500-P,BTC-18OCT23-27250-C,BTC-18OCT23-27250-P,BTC-18OCT23-27000-C,BTC-18OCT23-27000-P,BTC-18OCT23-26750-C,BTC-18OCT23-26750-P,BTC-18OCT23-26500-C,BTC-18OCT23-26500-P,BTC-18OCT23-26250-C,BTC-18OCT23-26250-P,BTC-18OCT23-26000-C,BTC-18OCT23-26000-P,BTC-18OCT23-25750-C,BTC-18OCT23-25750-P,BTC-18OCT23-25500-C,BTC-18OCT23-25500-P,BTC-17OCT23-29250-C,BTC-17OCT23-29250-P,BTC-17OCT23-29000-C,BTC-17OCT23-29000-P,BTC-17OCT23-28750-C,BTC-17OCT23-28750-P,BTC-17OCT23-28500-C,BTC-17OCT23-28500-P,BTC-17OCT23-28250-C,BTC-17OCT23-28250-P,BTC-17OCT23-28000-C,BTC-17OCT23-28000-P,BTC-17OCT23-27750-C,BTC-17OCT23-27750-P,BTC-17OCT23-27500-C,BTC-17OCT23-27500-P,BTC-17OCT23-27250-C,BTC-17OCT23-27250-P,BTC-17OCT23-27000-C,BTC-17OCT23-27000-P,BTC-17OCT23-26750-C,BTC-17OCT23-26750-P,BTC-17OCT23-26500-C,BTC-17OCT23-26500-P,BTC-17OCT23-26250-C,BTC-17OCT23-26250-P,BTC-17OCT23-26000-C,BTC-17OCT23-26000-P,BTC-17OCT23-25750-C,BTC-17OCT23-25750-P,BTC-17OCT23-25500-C,BTC-17OCT23-25500-P", + "requestFormat": { + "uppercase": true, + "delimiter": "-" + }, + "configFormat": { + "uppercase": true, + "delimiter": "-" + } } - }, - "bankAccounts": [ - { - "enabled": false, - "bankName": "", - "bankAddress": "", - "bankPostalCode": "", - "bankPostalCity": "", - "bankCountry": "", - "accountName": "", - "accountNumber": "", - "swiftCode": "", - "iban": "", - "supportedCurrencies": "" - } - ] + } }, - { - "name": "Binanceus", - "enabled": true, - "verbose": false, - "httpTimeout": 15000000000, - "websocketResponseCheckTimeout": 30000000, - "websocketResponseMaxLimit": 7000000000, - "websocketTrafficTimeout": 30000000000, - "baseCurrencies": "USD", - "currencyPairs": { - "bypassConfigFormatUpgrades": false, - "pairs": { - "spot": { - "assetEnabled": true, - "enabled": "BTC-USDT,ETH-USDT,LTC-USDT,ADA-USDT", - "available": "BTC-USD,BCH-USD,LTC-USD,USDT-USD,BTC-USDT,ETH-USDT,BCH-USDT,LTC-USDT,BNB-USD,BNB-USDT,ETH-BTC,BNB-BTC,LTC-BTC,BCH-BTC,ADA-USD,BAT-USD,ETC-USD,XLM-USD,ZRX-USD,ADA-USDT,BAT-USDT,ETC-USDT,XLM-USDT,ZRX-USDT,LINK-USD,RVN-USD,DASH-USD,ZEC-USD,ALGO-USD,IOTA-USD,BUSD-USD,BTCB-USD,DOGE-USDT,WAVES-USD,ATOM-USDT,ATOM-USD,NEO-USDT,NEO-USD,VET-USDT,QTUM-USDT,QTUM-USD,ICX-USD,ENJ-USD,ONT-USD,ONT-USDT,ZIL-USD,ZILB-USD,VET-USD,BNBB-USD,ETHB-USD,ALGO-BUSD,XTZ-USD,XTZ-BUSD,HBAR-USD,HBAR-BUSD,OMG-USD,OMG-BUSD,MATIC-USD,MATIC-BUSD,XTZ-BTC,ADA-BTC,REP-BUSD,REP-USD,EOS-BUSD,EOS-USD,DOGE-USD,KNC-USD,KNC-USDT,VTHO-USDT,VTHO-USD,USDC-USD,COMP-USDT,COMP-USD,MANA-USD,HNT-USD,HNT-USDT,MKR-USD,MKR-USDT,DAI-USD,ONE-USDT,ONE-USD,BAND-USDT,BAND-USD,STORJ-USDT,STORJ-USD,UNI-USD,UNI-USDT,SOL-USD,SOL-USDT,LINK-BTC,VET-BTC,UNI-BTC,EGLD-USDT,EGLD-USD,PAXG-USDT,PAXG-USD,OXT-USDT,OXT-USD,ZEN-USDT,ZEN-USD,BTC-USDC,ONEB-USD,FIL-USDT,FIL-USD,AAVE-USDT,AAVE-USD,GRT-USDT,GRT-USD,SUSHI-USD,ANKR-USD,AMP-USD,SHIB-USDT,SHIB-BUSD,CRV-USDT,CRV-USD,AXS-USDT,AXS-USD,SOL-BTC,AVAX-USDT,AVAX-USD,CTSI-USDT,CTSI-USD,DOT-USDT,DOT-USD,YFI-USDT,YFI-USD,1INCH-USDT,1INCH-USD,FTM-USDT,FTM-USD,USDC-USDT,ETH-USDC,USDC-BUSD,MATIC-USDT,MANA-USDT,MANA-BUSD,ALGO-USDT,ADA-BUSD,SOL-BUSD,EOS-USDT,ENJ-USDT,NEAR-USDT,NEAR-BUSD,NEAR-USD,OMG-USDT,SUSHI-USDT,LRC-USDT,LRC-USD,LRC-BTC,KSHI-BUSD,LPT-USDT,LPT-BUSD,LPT-USD,POLY-USDT,POLY-BUSD,POLY-USD,POLY-BTC,MATIC-BTC,DOT-BTC,NMR-USDT,NMR-USD,SLP-USDT,ANT-USD,XNO-USD,CHZ-USDT,CHZ-USD,OGN-USDT,OGN-USD,GALA-USDT,GALA-USD,TLM-USDT,TLM-USD,SNX-USDT,SNX-USD,AUDIO-USDT,AUDIO-USD,ENS-USDT,MANA-BTC,ATOM-BTC,AVAX-BTC,WBTC-BTC,REQ-USDT,REQ-USD,APE-USDT,APE-USD,FLUX-USDT,FLUX-USD,TRX-BTC,TRX-BUSD,TRX-USDT,TRX-USD,COTI-USDT,COTI-USD,VOXEL-USDT,VOXEL-USD,RLC-USDT,RLC-USD,UST-USDT,UST-USD,BICO-USDT,BICO-USD,API3-USDT,API3-USD,ENS-USD,BTC-UST,BNT-USDT,BNT-USD,IMX-USDT,IMX-USD,SPELL-USDT,SPELL-USD,JASMY-USDT,JASMY-USD,FLOW-USDT,FLOW-USD,GTC-USDT,GTC-USD,BTC-BUSD,ZIL-BUSD,BNB-BUSD,ETH-BUSD,BUSD-USDT,ONE-BUSD,LINK-USDT,ZEC-USDT,SLP-USD,ANT-USDT", - "requestFormat": { - "uppercase": true - }, - "configFormat": { - "uppercase": true, - "delimiter": "-" - } - } - } - }, - "api": { - "authenticatedSupport": false, - "authenticatedWebsocketApiSupport": false, - "credentials": { - "key": "", - "secret": "" - }, - "credentialsValidator": { - "requiresKey": true, - "requiresSecret": true - }, - "urlEndpoints": { - "RestSpotSupplementaryURL": "https://api.binance.us", - "RestSpotURL": "https://api.binance.us", - "WebsocketSpotSupplementaryURL": "wss://stream.binance.us:9443/stream", - "WebsocketSpotURL": "wss://stream.binance.us:9443/stream" - } - }, - "features": { - "supports": { - "restAPI": true, - "restCapabilities": { - "tickerBatching": true, - "autoPairUpdates": true - }, - "websocketAPI": true, - "websocketCapabilities": {} - }, - "enabled": { - "autoPairUpdates": true, - "websocketAPI": true, - "saveTradeData": false, - "tradeFeed": false, - "fillsFeed": false - } - }, - "bankAccounts": [ - { - "enabled": false, - "bankName": "", - "bankAddress": "", - "bankPostalCode": "", - "bankPostalCity": "", - "bankCountry": "", - "accountName": "", - "accountNumber": "", - "swiftCode": "", - "iban": "", - "supportedCurrencies": "" - } - ], - "orderbook": { - "verificationBypass": false, - "websocketBufferLimit": 5, - "websocketBufferEnabled": false, - "publishPeriod": 10000000000 + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true } }, - { - "name": "Bitfinex", - "enabled": true, - "verbose": false, - "httpTimeout": 15000000000, - "websocketResponseCheckTimeout": 30000000, - "websocketResponseMaxLimit": 7000000000, - "websocketTrafficTimeout": 30000000000, - "websocketOrderbookBufferLimit": 5, - "baseCurrencies": "USD", - "currencyPairs": { - "requestFormat": { - "uppercase": true - }, - "configFormat": { - "uppercase": true - }, - "useGlobalFormat": true, - "assetTypes": [ - "spot" - ], - "pairs": { - "spot": { - "enabled": "BTCUSD,LTCUSD,LTCBTC,ETHUSD,ETHBTC", - "available": "BTCUSD,LTCUSD,LTCBTC,ETHUSD,ETHBTC,ETCBTC,ETCUSD,RRTUSD,RRTBTC,ZECUSD,ZECBTC,XMRUSD,XMRBTC,DSHUSD,DSHBTC,BTCEUR,BTCJPY,XRPUSD,XRPBTC,IOTUSD,IOTBTC,IOTETH,EOSUSD,EOSBTC,EOSETH,SANUSD,SANBTC,SANETH,OMGUSD,OMGBTC,OMGETH,NEOUSD,NEOBTC,NEOETH,ETPUSD,ETPBTC,ETPETH,QTMUSD,QTMBTC,QTMETH,AVTUSD,AVTBTC,AVTETH,EDOUSD,EDOBTC,EDOETH,BTGUSD,BTGBTC,DATUSD,DATBTC,DATETH,QSHUSD,QSHBTC,QSHETH,YYWUSD,YYWBTC,YYWETH,GNTUSD,GNTBTC,GNTETH,SNTUSD,SNTBTC,SNTETH,IOTEUR,BATUSD,BATBTC,BATETH,MNAUSD,MNABTC,MNAETH,FUNUSD,FUNBTC,FUNETH,ZRXUSD,ZRXBTC,ZRXETH,TNBUSD,TNBBTC,TNBETH,SPKUSD,SPKBTC,SPKETH,TRXUSD,TRXBTC,TRXETH,RCNUSD,RCNBTC,RCNETH,RLCUSD,RLCBTC,RLCETH,AIDUSD,AIDBTC,AIDETH,SNGUSD,SNGBTC,SNGETH,REPUSD,REPBTC,REPETH,ELFUSD,ELFBTC,ELFETH,NECUSD,NECBTC,NECETH,BTCGBP,ETHEUR,ETHJPY,ETHGBP,NEOEUR,NEOJPY,NEOGBP,EOSEUR,EOSJPY,EOSGBP,IOTJPY,IOTGBP,IOSUSD,IOSBTC,IOSETH,AIOUSD,AIOBTC,AIOETH,REQUSD,REQBTC,REQETH,RDNUSD,RDNBTC,RDNETH,LRCUSD,LRCBTC,LRCETH,WAXUSD,WAXBTC,WAXETH,DAIUSD,DAIBTC,DAIETH,AGIUSD,AGIBTC,AGIETH,BFTUSD,BFTBTC,BFTETH,MTNUSD,MTNBTC,MTNETH,ODEUSD,ODEBTC,ODEETH,ANTUSD,ANTBTC,ANTETH,DTHUSD,DTHBTC,DTHETH,MITUSD,MITBTC,MITETH,STJUSD,STJBTC,STJETH,XLMUSD,XLMEUR,XLMJPY,XLMGBP,XLMBTC,XLMETH,XVGUSD,XVGEUR,XVGJPY,XVGGBP,XVGBTC,XVGETH,BCIUSD,BCIBTC,MKRUSD,MKRBTC,MKRETH,KNCUSD,KNCBTC,KNCETH,POAUSD,POABTC,POAETH,EVTUSD,LYMUSD,LYMBTC,LYMETH,UTKUSD,UTKBTC,UTKETH,VEEUSD,VEEBTC,VEEETH,DADUSD,DADBTC,DADETH,ORSUSD,ORSBTC,ORSETH,AUCUSD,AUCBTC,AUCETH,POYUSD,POYBTC,POYETH,FSNUSD,FSNBTC,FSNETH,CBTUSD,CBTBTC,CBTETH,ZCNUSD,ZCNBTC,ZCNETH,SENUSD,SENBTC,SENETH,NCAUSD,NCABTC,NCAETH,CNDUSD,CNDBTC,CNDETH,CTXUSD,CTXBTC,CTXETH,PAIUSD,PAIBTC,SEEUSD,SEEBTC,SEEETH,ESSUSD,ESSBTC,ESSETH,ATMUSD,ATMBTC,ATMETH,HOTUSD,HOTBTC,HOTETH,DTAUSD,DTABTC,DTAETH,IQXUSD,IQXBTC,IQXEOS,WPRUSD,WPRBTC,WPRETH,ZILUSD,ZILBTC,ZILETH,BNTUSD,BNTBTC,BNTETH,ABSUSD,ABSETH,XRAUSD,XRAETH,MANUSD,MANETH,BBNUSD,BBNETH,NIOUSD,NIOETH,DGXUSD,DGXETH,VETUSD,VETBTC,VETETH,UTNUSD,UTNETH,TKNUSD,TKNETH,GOTUSD,GOTEUR,GOTETH,XTZUSD,XTZBTC,CNNUSD,CNNETH,BOXUSD,BOXETH,TRXEUR,TRXGBP,TRXJPY,MGOUSD,MGOETH,RTEUSD,RTEETH,YGGUSD,YGGETH,MLNUSD,MLNETH,WTCUSD,WTCETH,CSXUSD,CSXETH,OMNUSD,OMNBTC,INTUSD,INTETH,DRNUSD,DRNETH,PNKUSD,PNKETH,DGBUSD,DGBBTC,BSVUSD,BSVBTC,BABUSD,BABBTC,WLOUSD,WLOXLM,VLDUSD,VLDETH,ENJUSD,ENJETH,ONLUSD,ONLETH,RBTUSD,RBTBTC,USTUSD,EUTEUR,EUTUSD,GSDUSD,UDCUSD,TSDUSD,PAXUSD,RIFUSD,RIFBTC,PASUSD,PASETH,VSYUSD,VSYBTC,ZRXDAI,MKRDAI,OMGDAI,BTTUSD,BTTBTC,BTCUST,ETHUST,CLOUSD,CLOBTC,IMPUSD,IMPETH,LTCUST,EOSUST,BABUST,SCRUSD,SCRETH,GNOUSD,GNOETH,GENUSD,GENETH,ATOUSD,ATOBTC,ATOETH,WBTUSD,XCHUSD,EUSUSD,WBTETH,XCHETH,EUSETH,LEOUSD,LEOBTC,LEOUST,LEOEOS,LEOETH,ASTUSD,ASTETH,FOAUSD,FOAETH,UFRUSD,UFRETH,ZBTUSD,ZBTUST,OKBUSD,USKUSD,GTXUSD,KANUSD,OKBUST,OKBETH,OKBBTC,USKUST,USKETH,USKBTC,USKEOS,GTXUST,KANUST,AMPUSD,ALGUSD,ALGBTC,ALGUST,BTCXCH,SWMUSD,SWMETH,TRIUSD,TRIETH,LOOUSD,LOOETH,AMPUST,DUSK:USD,DUSK:BTC,UOSUSD,UOSBTC,RRBUSD,RRBUST,DTXUSD,DTXUST,AMPBTC,FTTUSD,FTTUST,PAXUST,UDCUST,TSDUST,BTC:CNHT,UST:CNHT,CNH:CNHT,CHZUSD,CHZUST,BTCF0:USTF0,ETHF0:USTF0" - } - } - }, - "api": { - "authenticatedSupport": true, - "authenticatedWebsocketApiSupport": true, - "endpoints": { - "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" - }, - "credentials": { - "key": "Key", - "secret": "Secret" - }, - "credentialsValidator": { - "requiresKey": true, - "requiresSecret": true - } - }, - "features": { - "supports": { - "restAPI": true, - "restCapabilities": { - "tickerBatching": true, - "autoPairUpdates": true - }, - "websocketAPI": true, - "websocketCapabilities": {} - }, - "enabled": { - "autoPairUpdates": true, - "websocketAPI": true - } - }, - "bankAccounts": [ - { - "enabled": false, - "bankName": "Deutsche Bank Privat Und Geschaeftskunden AG", - "bankAddress": "Karlsruhe, 76125, GERMANY", - "bankPostalCode": "", - "bankPostalCity": "", - "bankCountry": "", - "accountName": "GLOBAL TRADE SOLUTIONS GmbH", - "accountNumber": "DE51660700240057016802", - "swiftCode": "DEUTDEDB660", - "iban": "DE51660700240057016802", - "supportedCurrencies": "EUR,USD" - }, - { - "enabled": false, - "bankName": "Deutsche Bank Privat Und Geschaeftskunden AG", - "bankAddress": "Karlsruhe, 76125, GERMANY", - "bankPostalCode": "", - "bankPostalCity": "", - "bankCountry": "", - "accountName": "GLOBAL TRADE SOLUTIONS GmbH", - "accountNumber": "DE78660700240057016801", - "swiftCode": "DEUTDEDB660", - "iban": "DE78660700240057016801", - "supportedCurrencies": "JPY,GBP" - } - ] + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "tickerBatching": true, + "autoPairUpdates": true + }, + "websocketAPI": true, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": true + } }, - { - "name": "Bitflyer", - "enabled": true, - "verbose": false, - "httpTimeout": 15000000000, - "websocketResponseCheckTimeout": 30000000, - "websocketResponseMaxLimit": 7000000000, - "websocketTrafficTimeout": 30000000000, - "websocketOrderbookBufferLimit": 5, - "baseCurrencies": "JPY", - "currencyPairs": { - "requestFormat": { - "uppercase": true, - "delimiter": "_" - }, - "configFormat": { - "uppercase": true, - "delimiter": "_" - }, - "useGlobalFormat": true, - "lastUpdated": 1566798411, - "assetTypes": [ - "spot", - "futures" - ], - "pairs": { - "spot": { - "enabled": "BTC_JPY,ETH_BTC,BCH_BTC", - "available": "BTC_JPY,FXBTC_JPY,ETH_BTC,BCH_BTC" - } - } - }, - "api": { - "authenticatedSupport": false, - "authenticatedWebsocketApiSupport": false, - "endpoints": { - "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" - }, - "credentials": { - "key": "Key", - "secret": "Secret" - }, - "credentialsValidator": { - "requiresKey": true, - "requiresSecret": true + "bankAccounts": [ + { + "enabled": false, + "bankName": "", + "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ] + }, + { + "name": "COINUT", + "enabled": true, + "verbose": false, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, + "websocketOrderbookBufferLimit": 5, + "baseCurrencies": "USD", + "currencyPairs": { + "requestFormat": { + "uppercase": true + }, + "configFormat": { + "uppercase": true, + "delimiter": "-" + }, + "useGlobalFormat": true, + "assetTypes": [ + "spot" + ], + "pairs": { + "spot": { + "enabled": "LTC-USDT", + "available": "LTC-CAD,LTC-SGD,USDT-USD,ETC-LTC,LTC-BTC,USDT-SGD,XMR-USDT,ZEC-SGD,ETH-USD,BTC-USDT,ETC-BTC,ETH-LTC,LTC-USD,BTC-USD,ETH-USDT,XMR-LTC,ZEC-USD,ETC-SGD,DAI-SGD,ZEC-CAD,BTC-SGD,ETH-BTC,ETH-SGD,LTC-USDT,ZEC-BTC,ZEC-USDT,BTC-CAD,XMR-BTC,ZEC-LTC,ETC-USDT,ETH-CAD" } - }, - "features": { - "supports": { - "restAPI": true, - "restCapabilities": { - "autoPairUpdates": true - }, - "websocketAPI": false, - "websocketCapabilities": {} + } + }, + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret", + "clientID": "ClientID" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresClientID": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "autoPairUpdates": true + }, + "websocketAPI": true, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": false + } + }, + "bankAccounts": [ + { + "enabled": false, + "bankName": "", + "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ] + }, + { + "name": "CoinbasePro", + "enabled": true, + "verbose": false, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, + "websocketOrderbookBufferLimit": 5, + "baseCurrencies": "USD,GBP,EUR", + "currencyPairs": { + "requestFormat": { + "uppercase": true, + "delimiter": "-" + }, + "configFormat": { + "uppercase": true, + "delimiter": "-" + }, + "useGlobalFormat": true, + "assetTypes": [ + "spot", + "futures" + ], + "pairs": { + "spot": { + "enabled": "BTC-USD", + "available": "LTC-GBP,XLM-BTC,DASH-BTC,DAI-USDC,ZEC-USDC,XLM-EUR,ZRX-BTC,LTC-BTC,ETC-BTC,ETH-USD,XRP-EUR,BTC-USDC,REP-USD,EOS-BTC,ZEC-BTC,ETC-GBP,LINK-ETH,XRP-BTC,ZRX-USD,ETH-USDC,MANA-USDC,BTC-EUR,BCH-GBP,DNT-USDC,EOS-EUR,BCH-EUR,LTC-EUR,CVC-USDC,ETH-GBP,DASH-USD,ETH-EUR,XTZ-BTC,ZRX-EUR,BAT-ETH,BTC-GBP,ETC-USD,BAT-USDC,BCH-USD,GNT-USDC,ALGO-USD,LINK-USD,XLM-USD,ETH-BTC,EOS-USD,REP-BTC,ETH-DAI,XRP-USD,LTC-USD,ETC-EUR,BTC-USD,XTZ-USD,BCH-BTC,LOOM-USDC" }, - "enabled": { - "autoPairUpdates": false, - "websocketAPI": false + "futures": { + "enabled": "BTC-PERP-INTX", + "available": "BTC-PERP-INTX" } - }, - "bankAccounts": [ - { - "enabled": false, - "bankName": "", - "bankAddress": "", - "bankPostalCode": "", - "bankPostalCity": "", - "bankCountry": "", - "accountName": "", - "accountNumber": "", - "swiftCode": "", - "iban": "", - "supportedCurrencies": "" + } + }, + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret", + "clientID": "ClientID" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true, + "requiresClientID": true, + "requiresBase64DecodeSecret": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "autoPairUpdates": true + }, + "websocketAPI": true, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": false + } + }, + "bankAccounts": [ + { + "enabled": false, + "bankName": "", + "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ] + }, + { + "name": "EXMO", + "enabled": true, + "verbose": false, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, + "websocketOrderbookBufferLimit": 5, + "baseCurrencies": "USD,EUR,RUB,PLN,UAH", + "currencyPairs": { + "requestFormat": { + "uppercase": true, + "delimiter": "_", + "separator": "," + }, + "configFormat": { + "uppercase": true, + "delimiter": "_" + }, + "useGlobalFormat": true, + "assetTypes": [ + "spot" + ], + "pairs": { + "spot": { + "enabled": "BTC_USD,LTC_USD", + "available": "BCH_RUB,DASH_RUB,EOS_USD,ETH_TRY,GNT_ETH,LTC_USD,PTI_USDT,XRP_BTC,EXM_BTC,BTG_BTC,ETC_RUB,BTG_USD,NEO_RUB,XMR_BTC,ZRX_ETH,MNX_BTC,USDC_BTC,XRP_EUR,SMART_USD,EOS_BTC,MNX_ETH,ZEC_BTC,BCH_USD,WAVES_USD,TRX_BTC,XRP_TRY,DASH_USD,DOGE_USD,ETZ_USDT,GUSD_USD,MNC_BTC,ZEC_USD,DCR_BTC,DXT_USD,PTI_RUB,XMR_ETH,ZRX_USD,DAI_RUB,MNC_USD,XLM_TRY,DAI_BTC,BTC_EUR,LTC_EUR,OMG_BTC,PTI_EOS,SMART_RUB,XTZ_USD,HP_EXM,ADA_USD,OMG_ETH,QTUM_USD,TRX_RUB,USDC_ETH,USDC_USDT,USD_RUB,BTC_UAH,BCH_USDT,ETH_PLN,KICK_RUB,LSK_RUB,SMART_BTC,XMR_UAH,XRP_USD,GUSD_BTC,QTUM_ETH,USDT_EUR,BTC_RUB,DCR_UAH,ETH_RUB,DOGE_BTC,ETZ_BTC,INK_USD,LTC_UAH,BTT_UAH,BTC_USDT,MNC_ETH,XTZ_ETH,BTC_TRY,DXT_BTC,KICK_USDT,OMG_USD,WAVES_BTC,XLM_BTC,BTCZ_BTC,GNT_BTC,LSK_BTC,LTC_RUB,NEO_BTC,XEM_UAH,XMR_USD,ZAG_BTC,GAS_USD,LTC_BTC,TRX_UAH,XEM_EUR,XMR_RUB,XTZ_RUB,ETZ_ETH,ETC_BTC,GUSD_RUB,INK_BTC,LSK_USD,MNX_USD,SMART_EUR,VLX_BTC,BCH_ETH,XMR_EUR,ADA_ETH,QTUM_BTC,XEM_USD,ATMCASH_BTC,ADA_BTC,ETH_EUR,TRX_USD,USDC_USD,BCH_BTC,ETH_UAH,KICK_BTC,WAVES_RUB,XEM_BTC,ETH_BTC,BCH_EUR,BTT_BTC,ROOBEE_BTC,XLM_USD,XRP_ETH,ETH_USD,MKR_DAI,XTZ_BTC,DAI_USD,BCH_UAH,INK_ETH,KICK_ETH,MKR_BTC,NEO_USD,XRP_USDT,ZEC_EUR,BTC_USD,XRP_RUB,EOS_EUR,ETH_USDT,USDT_UAH,XRP_UAH,ZEC_RUB,HP_BTC,BTT_RUB,DAI_ETH,DASH_UAH,DASH_USDT,ETH_LTC,GAS_BTC,USDT_USD,BTG_ETH,XLM_RUB,WAVES_ETH,USDT_RUB,ZRX_BTC,DASH_BTC,DCR_RUB,ETC_USD,HB_BTC,PTI_BTC,BTC_PLN" } - ] + } }, - { - "name": "Bithumb", - "enabled": true, - "verbose": false, - "httpTimeout": 15000000000, - "websocketResponseCheckTimeout": 30000000, - "websocketResponseMaxLimit": 7000000000, - "websocketTrafficTimeout": 30000000000, - "baseCurrencies": "KRW", - "currencyPairs": { - "requestFormat": { - "uppercase": true, - "delimiter": "_" + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "tickerBatching": true, + "autoPairUpdates": true + }, + "websocketAPI": false, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": false + } + }, + "bankAccounts": [ + { + "enabled": false, + "bankName": "", + "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ] + }, + { + "name": "GateIO", + "enabled": true, + "verbose": false, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, + "websocketOrderbookBufferLimit": 5, + "baseCurrencies": "USD", + "currencyPairs": { + "requestFormat": { + "uppercase": true, + "delimiter": "_" + }, + "configFormat": { + "uppercase": true, + "delimiter": "_" + }, + "useGlobalFormat": true, + "assetTypes": [ + "spot", + "option", + "futures", + "cross_margin", + "margin", + "delivery" + ], + "pairs": { + "spot": { + "enabled": "BTC_USDT,IHT_ETH,AME_ETH,CEUR_ETH,ALEPH_USDT,OMG_TRY,BTC_TRY,OGN_USDT,ALA_USDT", + "available": "IHT_ETH,AME_ETH,CEUR_ETH,ALEPH_USDT,OMG_TRY,BTC_TRY,OGN_USDT,ALA_USDT,HC_USDT,BTC_USDT,QNT_USDT,QTUM_ETH,MAHA_ETH,XCN_ETH,POOL_USDT,KGC_USDT,MCO2_USDT,HARD_USDT,GHNY_USDT,FTT_ETH,K21_ETH,FINE_USDT,REP_USDT,SBR_USDT,SKM_ETH,QLC_ETH,GAS_BTC,ALICE3L_USDT,BAO_USDT,FALCONS_USDT,ANT_USDT,VIDYX_USDT,DXCT_ETH,SMTY_ETH,HERO_USDT,SHARE_USDT,FIN_USDT,MTV_USDT,MOO_USDT,SMTY_USDT,ORAO_USDT,AE_ETH,SUSD_USDT,MAN_USDT,UNDEAD_USDT,MC_USDT,VET_USDT,WAXP_ETH,MDA_ETH,LYXE_USDT,SPS_USDT,STX_ETH,WSIENNA_USDT,NAOS_BTC,NFTX_USDT,OPUL_USDT,ICP3L_USDT,SFI_ETH,CTT_USDT,BSV3L_USDT,DFI_USDT,DIS_ETH,FET_USDT,ARG_USDT,VELO_USDT,NSBT_BTC,GSE_ETH,HNS_BTC,DOGEDASH_ETH,BACON_USDT,DUSK_USDT,MAPE_USDT,EGLD_ETH,TDROP_USDT,C983L_USDT,FAN_ETH,CZZ_USDT,FIU_USDT,SWRV_USDT,ONT_ETH,KINE_ETH,IMX_ETH,SPAY_ETH,CFG_BTC,RACA3S_USDT,UNO_ETH,DMLG_USDT,SAKE_ETH,ASM_USDT,CUSD_ETH,SUSD_ETH,ONC_USDT,DAI_USDT,VEGA_ETH,PYM_USDT,LTC_TRY,LOKA_USDT,NIF_USDT,BNC_USDT,PERL_ETH,MATIC3S_USDT,STMX_USDT,SKL_USDT,WLKN_USDT,XYO_ETH,AMPL3S_USDT,WEX_USDT,ULU_ETH,LIKE_ETH,INSUR_ETH,CAKE_ETH,SXP_ETH,COTI_USDT,ORT_USDT,RACA3L_USDT,GASDAO_USDT,AVA_USDT,OPA_USDT,ATS_USDT,VEGA_USDT,KILT_USDT,HIT_ETH,BRISE_USDT,SAUBER_USDT,SPS_ETH,FSN_USDT,EOS_ETH,KYL_USDT,REVV_ETH,SVT_ETH,XRP_USDT,DYDX3S_USDT,MANA3S_USDT,ICP_ETH,ALICE3S_USDT,PCX_USDT,LEMO_ETH,MKR_ETH,WOO3S_USDT,CART_ETH,MATIC_USDT,UNI_USD,MOBI_BTC,ICP3S_USDT,BEAM_BTC,CRO3S_USDT,FTT_USDT,IQ_ETH,TAP_USDT,MLT_USDT,RBN_USDT,AMPL3L_USDT,KINT_ETH,HECH_USDT,GAFI_ETH,WOO3L_USDT,TAI_USDT,HERA_USDT,AST_USDT,DHV_ETH,XAVA_USDT,LSS_USDT,SNX3S_USDT,PBR_USDT,XEND_ETH,SHR_ETH,PRQ_USDT,MATIC3L_USDT,WIT_ETH,LPOOL_USDT,PSP_USDT,BXC_USDT,CBK_USDT,REVO_BTC,MANA3L_USDT,ALPINE_USDT,DEGO_USDT,SIN_USDT,OCT_USDT,KZEN_USDT,L3P_USDT,FX_ETH,ONC_ETH,AXS_USD,BORA_USDT,XTZ_ETH,NEO3L_USDT,FROG_USDT,CHAMP_USDT,XNFT_USDT,BCH3S_USDT,FORT_USDT,XLM_TRY,TRX_TRY,CRPT_USDT,ROUTE_USDT,GLM_USDT,SLRS_ETH,TIMECHRONO_USDT,VRA_USDT,ONS_USDT,ZEC3L_USDT,KFT_ETH,TFD_ETH,FRA_USDT,RDN_ETH,BLANK_USDT,IOST3L_USDT,DDD_USDT,DOGE_USD,UNQ_USDT,API33S_USDT,AKRO_ETH,GITCOIN_USDT,THG_USDT,BDX_USDT,LTO_ETH,FLY_USDT,CREDIT_USDT,RENA_USDT,ZRX_ETH,CRP_ETH,NBOT_USDT,HT3L_USDT,DORA_ETH,LLT_SNET,ASD_USDT,XMR_USDT,SSV_BTC,FTM_USDT,XELS_USDT,MTL_ETH,ADX_ETH,API33L_USDT,PIG_USDT,RUNE_ETH,QRDO_BTC,THN_USDT,BCUG_USDT,EGG_ETH,GGM_USDT,HOTCROSS_USDT,SKYRIM_USDT,BTG_USDT,POT_USDT,CS_USDT,XVS_USDT,A5T_USDT,GOD_BTC,WAVES_USDT,LSK_BTC,BTT_TRY,YIN_USDT,PEOPLE_USDT,SPELL_ETH,POLC_USDT,BZZ3L_USDT,UNO_USDT,HDV_USDT,CELL_USDT,DAR_ETH,MIR_ETH,FODL_USDT,SRM_ETH,PROS_USDT,ORN_ETH,WAG_USDT,RBC_ETH,VENT_USDT,WND_USDT,AAA_ETH,BSCS_ETH,ZEC3S_USDT,DOS_USDT,HT3S_USDT,LAND_USDT,BCD_BTC,RING_USDT,FIRO_USDT,AUDIO_USDT,KUMA_USDT,SOLO_BTC,CRBN_USDT,MM_ETH,SAKE_USDT,XMARK_USDT,SLP_USDT,F2C_USDT,LUNA_USDT,ONIT_USDT,FTM3L_USDT,POPK_USDT,RFUEL_USDT,NEO3S_USDT,MIR_USDT,ETC_BTC,STETH_ETH,MANA_TRY,ALPACA_ETH,WAXL_USDT,EGS_USDT,DAR_USDT,KSM_USDT,XMARK_ETH,QTUM_USDT,C983S_USDT,INDI_ETH,DOGE3S_USDT,RVN_USDT,NOS_USDT,ALU_ETH,ALD_ETH,LUNC_USDT,ARES_ETH,BZZ3S_USDT,TNC_ETH,ONE_USDT,SENC_ETH,FTM3S_USDT,FLUX_USDT,STORJ_ETH,MTN_ETH,MNW_USDT,BLES_ETH,STG_ETH,LIME_ETH,WAGYU_USDT,XRP_TRY,XOR_ETH,ANGLE_USDT,DOGA_USDT,JFI_USDT,USDG_USDT,GRND_USDT,BOND_ETH,DMTR_USDT,YIN_ETH,ENJ_USDT,GOLDMINER_USDT,WIT_USDT,DOGE3L_USDT,FORM_USDT,LYXE_ETH,MLK_USDT,VR_USDT,DMS_USDT,LRC_TRY,ONX_USDT,ASK_USDT,ISP_ETH,TXT_USDT,IOEN_ETH,NIIFI_USDT,VRX_USDT,DOME_USDT,CTSI_USDT,ORBS_USDT,ZLW_ETH,FIL_USDT,FTI_ETH,CTK_USDT,ASR_USDT,GBPT_BTC,CBK_BTC,MBOX_ETH,RAM_USDT,IRIS_USDT,AME_USDT,KUB_USDT,ENV_USDT,RING_ETH,COTI3S_USDT,JULD_ETH,POLK_ETH,ACH3S_USDT,HYVE_ETH,MIX_ETH,RFT_USDT,ORAO_ETH,IHT_USDT,POLYPAD_USDT,CTRC_USDT,SFUND_USDT,MXC_BTC,DDD_BTC,CHESS_ETH,SHIB_USDT,SN_USDT,NFT_USDT,ASTRO_ETH,SOLO_USDT,TSHP_USDT,AMP_USDT,BTCST_ETH,VLXPAD_USDT,GAN_USDT,O3_USDT,WBTC_TRY,TULIP_USDT,GS_ETH,DX_ETH,NYZO_ETH,TT_USDT,SHILL_USDT,RATING_ETH,DUST_USDT,PSB_USDT,BFT1_USDT,GALA_ETH,XDC_USDT,LON3L_USDT,HE_USDT,ICE_ETH,LINK_ETH,SKU_USDT,QLC_USDT,DOMI_USDT,IDEA_USDT,METO_USDT,LIFE_ETH,ACH3L_USDT,POWR_ETH,VET_ETH,ALGO_USDT,BLIN_USDT,BAO_ETH,RBLS_USDT,TORN_ETH,VRT_USDT,BLANKV2_ETH,AUCTION_ETH,OLE_USDT,NWC_BTC,DOT5S_USDT,M RCH_ETH,SUNNY_ETH,GST_USDT,ENJ_TRY,KIBA_USDT,KLAP_USDT,SNTR_ETH,CELR_ETH,CHESS_USDT,XLM3L_USDT,LIQ_USDT,TRU_ETH,CHZ_USD,EPK_USDT,MED_ETH,BSCPAD_ETH,ZCN_USDT,AIOZ_ETH,FOR_ETH,CVC3L_USDT,MNY_USDT,SALT_USDT,CSTR_USDT,MPL_USDT,PLY_ETH,FIS_USDT,CHO_USDT,BICO_ETH,STOX_ETH,HIGH_USDT,SDAO_BTC,STEP_USD,CRV_BTC,SCRT_ETH,ROSE_USDT,SKILL_ETH,FRAX_USDT,BAGS_USDT,WIKEN_BTC,WOO_USDT,BBANK_ETH,SNX3L_USDT,XRD_ETH,VTHO_USDT,OKB3L_USDT,SAFEMOON_USDT,RAD_ETH,IOI_USDT,LAMB_USDT,CHZ_USDT,FAR_ETH,OKB3S_USDT,ELU_USDT,JGN_ETH,EOS3S_USDT,DBC_USDT,ATOM_USDT,ACH_ETH,LBLOCK_USDT,WZRD_USDT,OST_ETH,MEAN_USDT,IDEX_USDT,HOT_TRY,EWT_ETH,EMON_USDT,FXS_USDT,PSY_ETH,SIDUS_USDT,ATA_USDT,CVC3S_USDT,LOOKS_ETH,ALPA_ETH,CGG_ETH,CIR_ETH,PRT_ETH,LON3S_USDT,INJ_USDT,FIRE_ETH,MAHA_USDT,IOST3S_USDT,NU_ETH,LEO_BTC,VOXEL_USDT,CRV_USDT,EQX_USDT,WHALE_USDT,INJ_ETH,GRAP_USDT,AVAX3S_USDT,TIFI_USDT,C98_USDT,ERN_ETH,SUSHI_ETH,VET3S_USDT,KPAD_USDT,CRPT_ETH,CRO_USDT,AZY_USDT,LEMD_USDT,ETH2_ETH,BASE_ETH,TT_ETH,PERL_USDT,BANK_ETH,LST_ETH,PYR_ETH,RATIO_USDT,UMB_USDT,M ETALDR_USDT,SWINGBY_ETH,WICC_ETH,NUM_USDT,SHOE_USDT,BORING_ETH,SDN_USDT,GXS_BTC,ALICE_ETH,BRKL_USDT,GF_ETH,ELEC_USDT,SFG_USDT,COFIX_USDT,TIPS_ETH,FIL_BTC,CWAR_USDT,WILD_USDT,RENBTC_USDT,BNX_USDT,TRU_USDT,SWEAT_USDT,IOST_BTC,NVIR_USDT,1EARTH_USDT,ADAPAD_USDT,PPS_USDT,CUBE_USDT,DLC_USDT,DAFI_ETH,UNISTAKE_ETH,NFTL_USDT,ATOM_TRY,SHIB3S_USDT,BNB_USD,CNAME_USDT,GTH_ETH,ZCX_USDT,DYDX3L_USDT,ASTRO_USDT,GLQ_USDT,PROPS_USDT,AART_USDT,BTRST_ETH,KFT_USDT,AERGO_USDT,RUFF_ETH,EOS3L_USDT,API3_USDT,MINA_BTC,ETHA_ETH,AXIS_ETH,LOON_USDT,AVAX3L_USDT,VET3L_USDT,AE_USDT,SHX_USDT,LYM_USDT,DCR_BTC,LBK_USDT,QTC_USDT,LAVA_USDT,XCN_USDT,BRT_USDT,RSV_USDT,KIF_USDT,PSL_USDT,AZERO_USDT,LUNA_ETH,MILO_USDT,OGN_ETH,TOTM_USDT,BYN_ETH,MINA_USDT,PUNDIX_ETH,SRT_USDT,DG_ETH,IHC_USDT,SYS_ETH,TITA_USDT,COTI3L_USDT,DAG_USDT,DOT5L_USDT,TRADE_USDT,SHPING_USDT,NU_USDT,BLANK_ETH,PCNT_ETH,SCCP_USDT,POLS_USDT,NPT_USDT,MTA_USDT,YIELD_USDT,ZCN_ETH,DVP_ETH,KART_USDT,SYLO_USDT,MCRT_USDT,SPFC_USDT,BASE_USDT,ICX_USDT,PET_USDT,GZONE_USDT,RED_ETH,SBTC_USDT,BATH_ ETH,SOL_USD,NAFT_USDT,GMX_USDT,VADER_USDT,GTC_USDT,CVP_ETH,XRPBEAR_USDT,TIME_USDT,SXP_USDT,CITY_USDT,QASH_USDT,FAST_USDT,BCD_USDT,KNIGHT_USDT,BOO_ETH,ZODI_USDT,REI_USDT,PBX_ETH,SRM_USDT,LDO_ETH,ZEC_USDT,UFT_USDT,DAG_BTC,RIDE_USDT,ERN_USDT,T_USDT,CEEK_USDT,STI_USDT,IMX3S_USDT,ELA_USDT,MNGO_ETH,EHASH_ETH,BADGER_ETH,SUPE_USDT,AR3L_USDT,AUDIO_ETH,DOCK_ETH,QSP_USDT,FLM_USDT,AAVE3S_USDT,BOND_USDT,HT_USD,TARA_USDT,TRX_USDT,SPO_USDT,DSLA_USDT,LTC_BTC,DOGE_USDT,SLIM_ETH,ALN_ETH,CFX3S_USDT,FXS_ETH,RARE_ETH,VLXPAD_ETH,ETH_USD,SDN_BTC,QUICK_USDT,UTK_USDT,XPNET_USDT,TRB_USDT,LAZIO_USDT,FTM_TRY,ALPHA_ETH,CVC_ETH,WSG_USDT,UNI_ETH,DASH3L_USDT,BTL_USDT,CPOOL_USDT,MCG_USDT,SFP_ETH,REALM_USDT,RUFF_BTC,MOB_ETH,IBFK_USDT,ALPHA3S_USDT,BLOK_USDT,WIKEN_USDT,OMG3S_USDT,UTK_ETH,BCH5S_USDT,MED_USDT,REN_USD,MAN_ETH,SLND_ETH,CGG_USDT,CRE_USDT,SOURCE_USDT,ABT_USDT,DPET_USDT,WOM_USDT,FOREX_ETH,SNFT1_USDT,RIF_USDT,BENQI_USDT,XCV_ETH,GTC_BTC,ADA_TRY,LAT_USDT,ITGR_USDT,DLTA_USDT,SMT_USDT,APYS_USDT,MFT_ETH,ABT_ETH,STOX_USDT,ZRX_BTC,GMAT_USDT,R OOM_ETH,STORJ_BTC,RAZOR_USDT,RAGE_USDT,DOCK_USDT,RDN_USDT,MTR_USDT,NKN_USDT,SWASH_USDT,FX_USDT,POR_USDT,DENT_ETH,DERI_USDT,DFND_USDT,BLES_USDT,SLND_USDT,WNXM_ETH,CRTS_USDT,BTC3S_USDT,BKC_USDT,STEPG_ETH,THETA3L_USDT,NBS_BTC,AVAX_ETH,NANO_BTC,DEFILAND_ETH,LOOKS_USDT,BCX_BTC,BCH_USD,ETH3L_USDT,QLC_BTC,BCUG_ETH,RDF_USDT,DOGEDASH_USDT,ARSW_USDT,NEAR_ETH,QTCON_USDT,BABI_USDT,MBX_USDT,PNL_USDT,ODDZ_ETH,ATOM_BTC,XRP_BTC,BTCBULL_USDT,HMT_USDT,PORTO_USDT,STND_USDT,ETHW_ETH,LPT_USDT,LTC3L_USDT,TOKAU_USDT,QI_ETH,TVK_USDT,CWS_USDT,SWOP_USDT,WBTC_USDT,INSTAR_ETH,ICX_ETH,GALA5L_USDT,XTZ_BTC,AGS_USDT,TARA_BTC,DYDX_ETH,CATGIRL_USDT,SASHIMI_ETH,EPX_ETH,GCOIN_USDT,GDAO_USDT,MARS_ETH,OMG_USD,PMON_USDT,MNGO_USDT,TVK_ETH,SLG_USDT,MSOL_USDT,POWR_USDT,UOS_USDT,USDD_USDT,SLICE_USDT,ARRR_ETH,NSBT_USDT,STR_ETH,BEAM3L_USDT,BEL_USDT,MM_USDT,AXS_ETH,WEST_ETH,FTT3L_USDT,OMI_USDT,TIPS_USDT,SLC_ETH,SQUID_USDT,FEI_USDT,GEM_USDT,UMEE_USDT,DOGE_TRY,FCD_USDT,PVU_USDT,XED_ETH,LRN_ETH,NRFB_USDT,LION_USDT,BLACK_USDT,DOGE5S_USDT,CUDOS_USDT,PCNT_USDT ,OVR_USDT,ETC3S_USDT,CHR_ETH,MER_USDT,BOBA_USDT,FUEL_USDT,BAC_USDT,ONE3S_USDT,CONV_ETH,CDT_BTC,CELL_ETH,ASM_ETH,OPIUM_USDT,JST3L_USDT,BONDLY_USDT,RAZE_USDT,LIME_BTC,NFTX_ETH,PNK_ETH,LDO_USDT,DKS_USDT,ORO_USDT,LITH_USDT,ALPHR_ETH,INK_BTC,RLY_USDT,NEAR3S_USDT,XLM3S_USDT,AR_USDT,AKT_USDT,HCT_USDT,REEF_ETH,BZZ_USDT,SRM3L_USDT,AQDC_USDT,OPIUM_ETH,BAT_TRY,EWT_USDT,ALCX_ETH,CORN_USDT,HYDRA_USDT,RUNE_USD,STEP_USDT,CKB_BTC,MATTER_USDT,STSOL_ETH,CEEK_ETH,FXF_ETH,LIKE_USDT,HIT_USDT,LEO_USDT,COMP_USDT,BAL_USDT,LMR_USDT,AQT_USDT,BUY_ETH,LINK3S_USDT,ROOK_ETH,IMX_USDT,EFI_USDT,TAUR_USDT,OKT_ETH,GALO_USDT,MOOV_USDT,RUNE_USDT,TCP_USDT,ITEM_USDT,SCLP_USDT,RBC_USDT,SPI_USDT,ETC_USDT,RENBTC_BTC,CHICKS_USDT,KNOT_USDT,XEC3L_USDT,XCV_USDT,ETC_ETH,AAVE_TRY,APT_USDT,GNX_ETH,KISHU_USDT,AE_BTC,LIEN_USDT,CREAM_USDT,ATOM3S_USDT,OP_ETH,FORTH_ETH,PYR_USDT,KTN_ETH,TKO_ETH,METAG_USDT,ACE_USDT,CIR_USDT,BEAM_ETH,TCP_ETH,SRM_USD,CEL_USD,TRIBE3S_USDT,MESA_ETH,EVA_USDT,BBANK_USDT,BLANKV2_USDT,FORM_ETH,BAL3S_USDT,VISR_ETH,REVO_ETH,ALTB_USDT,KNC_US DT,GAS_USDT,SAFEMARS_USDT,TIP_USDT,VADER_ETH,NWC_USDT,VALUE_USDT,ATA_ETH,SSX_USDT,JOE_USDT,BAS_ETH,FITFI3S_USDT,BIT_USDT,QNT_ETH,RFOX_ETH,MSU_USDT,MSOL_ETH,CRV3L_USDT,OXT_USDT,SHFT_USDT,VERA_ETH,LYM_ETH,BP_USDT,KBOX_USDT,DOGNFT_ETH,PERP_USDT,VELO_ETH,SAO_USDT,DUCK2_USDT,DEFILAND_USDT,DUCK2_ETH,GLMR3L_USDT,SERO_ETH,MTS_USDT,STX_USDT,KEX_ETH,ZIG_USDT,CARDS_USDT,ANML_USDT,GALA_USDT,RAY3S_USDT,KAVA3L_USDT,GARD_USDT,GRT3L_USDT,BFC_USDT,NIFT_USDT,ORION_USDT,CTX_USDT,ASW_USDT,CERE_USDT,COMBO_ETH,MKR_USDT,MASK_USDT,MGA_USDT,AVAX_USDT,SKL3L_USDT,FRR_USDT,MV_USDT,BMI_ETH,SFIL_USDT,TEER_USDT,KLV_USDT,DMS_ETH,LBL_USDT,MKR3L_USDT,LEDU_BTC,XLM_BTC,MIST_ETH,OIN_USDT,CAKE_USDT,RNDR_USDT,STEPG_USDT,YCT_USDT,OPS_ETH,SHR_USDT,OXY_ETH" }, - "configFormat": { - "uppercase": true, - "delimiter": "-" + "option": { + "enabled": "BTC_USDT-20230217-28000-P,BTC_USDT-20221028-34000-P,BTC_USDT-20221028-40000-C", + "available": "BTC_USDT-20221028-26000-C,BTC_USDT-20221028-34000-P,BTC_USDT-20221028-40000-C,BTC_USDT-20221028-28000-P,BTC_USDT-20221028-34000-C,BTC_USDT-20221028-28000-C,BTC_USDT-20221028-36000-P,BTC_USDT-20221028-50000-P,BTC_USDT-20221028-36000-C,BTC_USDT-20221028-50000-C,BTC_USDT-20221028-21000-P,BTC_USDT-20221028-38000-P,BTC_USDT-20221028-21000-C,BTC_USDT-20221028-38000-C,BTC_USDT-20221028-23000-P,BTC_USDT-20221028-17000-P,BTC_USDT-20221028-23000-C,BTC_USDT-20221028-17000-C,BTC_USDT-20221028-25000-P,BTC_USDT-20221028-19000-P,BTC_USDT-20221028-25000-C,BTC_USDT-20221028-10000-P,BTC_USDT-20221028-19000-C,BTC_USDT-20221028-27000-P,BTC_USDT-20221028-10000-C,BTC_USDT-20221028-27000-C,BTC_USDT-20221028-12000-P,BTC_USDT-20221028-12000-C,BTC_USDT-20221028-20000-P,BTC_USDT-20221028-5000-P,BTC_USDT-20221028-14000-P,BTC_USDT-20221028-20000-C,BTC_USDT-20221028-45000-P,BTC_USDT-20221028-5000-C,BTC_USDT-20221028-14000-C,BTC_USDT-20221028-22000-P,BTC_USDT-20221028-45000-C,BTC_USDT-20221028-16000-P,BTC_USDT-20221028-22000-C,BTC_USDT-20221028-30000-P,BTC_USDT-20221028-16000-C,BTC_USDT-20221028-24000-P,BTC_USDT-20221028-30000-C,BTC_USDT-20221028-18000-P,BTC_USDT-20221028-24000-C,BTC_USDT-20221028-32000-P,BTC_USDT-20221028-18000-C,BTC_USDT-20221028-26000-P,BTC_USDT-20221028-32000-C,BTC_USDT-20221028-40000-P" }, - "useGlobalFormat": true, - "pairs": { - "spot": { - "assetEnabled": true, - "enabled": "USDT-KRW,QTUM-KRW,BTC-KRW,ETH-KRW,ETC-KRW,XRP-KRW,BCH-KRW,BTG-KRW,EOS-KRW", - "available": "AVAX-KRW,STRAX-KRW,KSM-KRW,RPL-KRW,ADA-KRW,ONT-KRW,EOS-KRW,STAT-KRW,APM-KRW,XPLA-KRW,STMX-KRW,FET-KRW,XVS-KRW,ROA-KRW,JOE-KRW,BNT-KRW,T-KRW,AUDIO-KRW,MIX-KRW,PUNDIX-KRW,USDC-KRW,ALGO-KRW,CTXC-KRW,IQ-KRW,RLY-KRW,GRT-KRW,NMR-KRW,FTM-KRW,WNCG-KRW,NCT-KRW,CSPR-KRW,TFUEL-KRW,EGG-KRW,MOC-KRW,BAT-KRW,ETC-KRW,TIA-KRW,GRACY-KRW,FRONT-KRW,DAI-KRW,ANKR-KRW,META-KRW,HOOK-KRW,BEL-KRW,MAGIC-KRW,ENTC-KRW,HUNT-KRW,STX-KRW,FIT-KRW,STEEM-KRW,CTSI-KRW,JUP-KRW,CAKE-KRW,DOGE-KRW,SUN-KRW,OCEAN-KRW,SOL-KRW,REQ-KRW,BNB-KRW,GAL-KRW,MBL-KRW,LRC-KRW,ILV-KRW,PEPE-KRW,IOST-KRW,XLM-KRW,CRV-KRW,NFT-KRW,PYR-KRW,TRX-KRW,TAVA-KRW,PYTH-KRW,TT-KRW,AAVE-KRW,KLAY-KRW,BAL-KRW,EVZ-KRW,FX-KRW,UMA-KRW,FLOW-KRW,ALEX-KRW,ELF-KRW,CVC-KRW,FLOKI-KRW,MASK-KRW,GAS-KRW,VIX-KRW,CELR-KRW,BLY-KRW,ARK-KRW,FNSA-KRW,OXT-KRW,VALOR-KRW,XTZ-KRW,HBAR-KRW,ONG-KRW,MTL-KRW,WAVES-KRW,ORBS-KRW,MANTA-KRW,ICX-KRW,SNX-KRW,API3-KRW,PENDLE-KRW,FLZ-KRW,APE-KRW,POWR-KRW,OGN-KRW,EDU-KRW,ARB-KRW,AXS-KRW,MBX-KRW,XRP-KRW,MATIC-KRW,USDT-KRW,1INCH-KRW,STORJ-KRW,UOS-KRW,RVN-KRW,LPT-KRW,OSMO-KRW,ALICE-KRW,LDO-KRW,TEMCO-KRW,COMP-KRW,VET-KRW,SFP-KRW,WIKEN-KRW,LBL-KRW,SHIB-KRW,GMT-KRW,AZIT-KRW,ZBCN-KRW,FLUX-KRW,ALT-KRW,AGI-KRW,SPURS-KRW,GRS-KRW,C98-KRW,ZIL-KRW,BCH-KRW,QTCON-KRW,SEI-KRW,GRND-KRW,SWAP-KRW,ETH-KRW,RSS3-KRW,STPT-KRW,FXS-KRW,SAND-KRW,MAP-KRW,MAV-KRW,LINK-KRW,MVC-KRW,QTUM-KRW,DAR-KRW,FANC-KRW,HIGH-KRW,ARKM-KRW,MANA-KRW,SUSHI-KRW,DVI-KRW,XEC-KRW,BTC-KRW,EL-KRW,THETA-KRW,CELO-KRW,KNC-KRW,POLA-KRW,LOOM-KRW,JASMY-KRW,INJ-KRW,KAVA-KRW,NEO-KRW,BIGTIME-KRW,MINA-KRW,NPT-KRW,IMX-KRW,ASM-KRW,FCT2-KRW,RLC-KRW,HIFI-KRW,CTC-KRW,DYDX-KRW,ZTX-KRW,AGIX-KRW,WEMIX-KRW,GTC-KRW,LM-KRW,OP-KRW,ONIT-KRW,ACS-KRW,LSK-KRW,REI-KRW,ATOM-KRW,WLD-KRW,GLM-KRW,COS-KRW,BTT-KRW,BFC-KRW,ACE-KRW,SC-KRW,BORA-KRW,GHX-KRW,ADP-KRW,STRK-KRW,LEVER-KRW,BOBA-KRW,BOA-KRW,HFT-KRW,RNDR-KRW,ENJ-KRW,RSR-KRW,XPR-KRW,IOTX-KRW,CYBER-KRW,WAXP-KRW,OBSR-KRW,MEV-KRW,UNI-KRW,APT-KRW,DAO-KRW,WAXL-KRW,SIX-KRW,GMX-KRW,RDNT-KRW,BTG-KRW,MNT-KRW,BLUR-KRW,XCN-KRW,YGG-KRW,MXC-KRW,ACH-KRW,RAD-KRW,MLK-KRW,DOT-KRW,JST-KRW,ZRX-KRW,STG-KRW,SOFI-KRW,WOM-KRW,TDROP-KRW,SNT-KRW,COTI-KRW,WOO-KRW,OAS-KRW,CRO-KRW,AQT-KRW,EGLD-KRW,ARPA-KRW,BSV-KRW,ASTR-KRW,AMO-KRW,AERGO-KRW,ID-KRW,SUI-KRW,GALA-KRW,CKB-KRW,BIOT-KRW,CFX-KRW,CHR-KRW,FLR-KRW,FITFI-KRW,YFI-KRW,CTK-KRW,W-KRW,MED-KRW,MKR-KRW,SXP-KRW,HIVE-KRW,CRTS-KRW,CHZ-KRW" - } - } - }, - "api": { - "authenticatedSupport": false, - "authenticatedWebsocketApiSupport": false, - "endpoints": { - "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + "futures": { + "enabled": "ETH_USD,BTC_USD,KNC_USDT,OOKI_USDT,BIT_USDT,ZEC_USDT,SC_USDT,RVN_USDT,ICX_USDT", + "available": "ETH_USD,BTC_USD,KNC_USDT,OOKI_USDT,BIT_USDT,ZEC_USDT,SC_USDT,RVN_USDT,ICX_USDT,DUSK_USDT,BEL_USDT,REEF_USDT,ALCX_USDT,ASTR_USDT,INJ_USDT,CAKE_USDT,LAZIO_USDT,ONE_USDT,CEL_USDT,ETH_USDT,KLAY_USDT,COTI_USDT,MKISHU_USDT,MANA_USDT,MOVR_USDT,OMG_USDT,UNI_USDT,LTC_USDT,AAVE_USDT,DENT_USDT,QRDO_USDT,BNB_USDT,ALPHA_USDT,RAY_USDT,APE_USDT,CERE_USDT,STMX_USDT,XCN_USDT,OGN_USDT,OKB_USDT,DOT_USDT,TLM_USDT,BTM_USDT,ADA_USDT,ANKR_USDT,ANT_USDT,TRX_USDT,MTL_USDT,YFII_USDT,SUN_USDT,SAND_USDT,MBABYDOGE_USDT,WIN_USDT,LUNC_USDT,SRM_USDT,STG_USDT,BAT_USDT,AXS_USDT,SOL_USDT,MAKITA_USDT,BNT_USDT,BLZ_USDT,PSG_USDT,IOTA_USDT,BONK_USDT,RSR_USDT,PYR_USDT,FITFI_USDT,MKR_USDT,PERP_USDT,COMP_USDT,LINK_USDT,CHR_USDT,CFX_USDT,GARI_USDT,DGB_USDT,MBOX_USDT,WEMIX_USDT,DYDX_USDT,LUNA_USDT,HT_USDT,TRB_USDT,CTK_USDT,ACA_USDT,TFUEL_USDT,OCEAN_USDT,XLM_USDT,HOT_USDT,FTM_USDT,LPT_USDT,SOS_USDT,ALGO_USDT,SHIB_USDT,BSV_USDT,PORTO_USDT,SFP_USDT,SANTOS_USDT,BADGER_USDT,DAR_USDT,DEFI_USDT,XEM_USDT,ALICE_USDT,ICP_USDT,RARE_USDT,LRC_USDT,BAKE_USDT,FLUX_USDT,CRO_USDT,CVC_USDT,MINA_USDT,LIT_USDT,AUDIO_USDT,ZIL_USDT,XMR_USDT,FRONT_USDT,CTSI_USDT,AGLD_USDT,YGG_USDT,OP_USDT,ZRX_USDT,GT_USDT,XCH_USDT,VET_USDT,MOB_USDT,BICO_USDT,SLP_USDT,ACH_USDT,AR_USDT,CLV_USDT,IMX_USDT,SPELL_USDT,UNFI_USDT,SUSHI_USDT,FTT_USDT,HIGH_USDT,HNT_USDT,ALT_USDT,YFI_USDT,NEAR_USDT,NKN_USDT,XVS_USDT,BAND_USDT,LOKA_USDT,BCH_USDT,TOMO_USDT,WAVES_USDT,FIDA_USDT,DIA_USDT,ANC_USDT,CELO_USDT,CRV_USDT,FLM_USDT,GLMR_USDT,FIL_USDT,PEOPLE_USDT,WAXP_USDT,IOTX_USDT,ATOM_USDT,RLC_USDT,HBAR_USDT,REN_USDT,GMT_USDT,KAVA_USDT,KDA_USDT,GALA_USDT,STORJ_USDT,PUNDIX_USDT,BAL_USDT,XAUG_USDT,GRIN_USDT,SXP_USDT,AKRO_USDT,NEXO_USDT,CKB_USDT,API3_USDT,NEST_USDT,ETHW_USDT,TONCOIN_USDT,THETA_USDT,CREAM_USDT,BTC_USDT,GST_USDT,BEAM_USDT,HFT_USDT,KSM_USDT,RAD_USDT,QTUM_USDT,WOO_USDT,ATA_USDT,AVAX_USDT,EOS_USDT,SNX_USDT,AUCTION_USDT,XRP_USDT,GITCOIN_USDT,MATIC_USDT,ONT_USDT,LINA_USDT,DASH_USDT,MASK_USDT,ETC_USDT,JST_USDT,BSW_USDT,CONV_USDT,SKL_USDT,GAL_USDT,DODO_USDT,GRT_USDT,TRU_USDT,STX_USDT,CVX_USDT,JASMY_USDT,HIVE_USDT,EXCH_USDT,ROSE_USDT,SUPER_USDT,SCRT_USDT,USTC_USDT,ENJ_USDT,BTS_USDT,LOOKS_USDT,QNT_USDT,HOOK_USDT,FLOW_USDT,RUNE_USDT,APT_USDT,CHZ_USDT,DOGE_USDT,1INCH_USDT,PRIV_USDT,CSPR_USDT,C98_USDT,RACA_USDT,CELR_USDT,XEC_USDT,ENS_USDT,POND_USDT,NYM_USDT,PROM_USDT,IOST_USDT,ZEN_USDT,LDO_USDT,RNDR_USDT,REQ_USDT,DEGO_USDT,VRA_USDT,QUICK_USDT,VGX_USDT,XTZ_USDT,EGLD_USDT,POLS_USDT,ARPA_USDT,NFT_USDT" }, - "credentials": { - "key": "Key", - "secret": "Secret", - "clientID": "ClientID" + "cross_margin": { + "enabled": "BTC_USDT,ERN_USDT,T_USDT,CEEK_USDT,OGN_USDT,QNT_USDT,WOZX_USDT,ZEE_USDT,FUN_USDT,FLM_USDT,BOND_USDT", + "available": "ERN_USDT,T_USDT,CEEK_USDT,OGN_USDT,QNT_USDT,WOZX_USDT,ZEE_USDT,FUN_USDT,FLM_USDT,BOND_USDT,TARA_USDT,TRX_USDT,OXY_USDT,LON_USDT,DOGE_USDT,ISP_USDT,TWT_USDT,BAO_USDT,QUACK_USDT,ANT_USDT,VGX_USDT,ARPA_USDT,QUICK_USDT,UTK_USDT,HERO_USDT,WSG_USDT,BICO_USDT,MTV_USDT,VET_USDT,GARI_USDT,BCH_USDT,KLAY_USDT,WING_USDT,BLOK_USDT,SPS_USDT,WIKEN_USDT,WSIENNA_USDT,PUNDIX_USDT,FIC_USDT,ASTR_USDT,FET_USDT,VELO_USDT,BENQI_USDT,CWEB_USDT,RIF_USDT,UNI_USDT,ONG_USDT,ERG_USDT,ALPHA_USDT,CELO_USDT,XVG_USDT,GMAT_USDT,BTS_USDT,DOCK_USDT,GMT_USDT,DIA_USDT,CSPR_USDT,NKN_USDT,STAKE_USDT,SWASH_USDT,XEC_USDT,SWRV_USDT,QRDO_USDT,BLES_USDT,EOS_USDT,GRT_USDT,ASM_USDT,FIL6_USDT,GNO_USDT,EGLD_USDT,XYM_USDT,LOOKS_USDT,LOKA_USDT,BNC_USDT,BAS_USDT,SKL_USDT,STMX_USDT,CVC_USDT,DDOS_USDT,COTI_USDT,AVA_USDT,HMT_USDT,DF_USDT,LPT_USDT,XRP_USDT,TVK_USDT,FEVR_USDT,MBL_USDT,KIN_USDT,SPELL_USDT,MATIC_USDT,FTT_USDT,NMR_USDT,PMON_USDT,BNB_USDT,USDD_USDT,LSS_USDT,MDX_USDT,PRQ_USDT,ALPINE_USDT,DEGO_USDT,OMI_USDT,TIPS_USDT,OCT_USDT,FEI_USDT,UMEE_USDT,CRP_USDT,LION_USDT,YFI_USDT,DASH_USDT,REQ_USDT,SDAO_USDT,PNT_USDT,INSUR_USDT,OOKI_USDT,SUN_USDT,CRPT_USDT,BAC_USDT,DATA_USDT,LRN_USDT,JGN_USDT,KIMCHI_USDT,SUKU_USDT,VRA_USDT,AAVE_USDT,FTI_USDT,LDO_USDT,FRA_USDT,BLANK_USDT,NEAR_USDT,ZKS_USDT,MTRG_USDT,RLY_USDT,TCT_USDT,FLY_USDT,JST_USDT,YFII_USDT,AR_USDT,POLY_USDT,JULD_USDT,SOL_USDT,BZZ_USDT,AXS_USDT,ASD_USDT,XMR_USDT,FTM_USDT,HIT_USDT,LEO_USDT,LIT_USDT,PIG_USDT,COMP_USDT,ELON_USDT,IMX_USDT,EFI_USDT,XVS_USDT,WAVES_USDT,PEOPLE_USDT,SOS_USDT,RUNE_USDT,POLC_USDT,SCLP_USDT,BABYDOGE_USDT,KONO_USDT,SPI_USDT,ETC_USDT,MDA_USDT,MTL_USDT,BCHA_USDT,KISHU_USDT,SUNNY_USDT,PYR_USDT,XTZ_USDT,TRIBE_USDT,AUDIO_USDT,FIRO_USDT,MANA_USDT,OKB_USDT,DOG_USDT,SLP_USDT,KNC_USDT,GAS_USDT,LUNA_USDT,SAFEMARS_USDT,MIR_USDT,DAR_USDT,EGS_USDT,KSM_USDT,ATP_USDT,BIT_USDT,STORJ_USDT,XEM_USDT,QTUM_USDT,AGLD_USDT,RVN_USDT,OXT_USDT,SHFT_USDT,IOTX_USDT,LUNC_USDT,NEXO_USDT,AKITA_USDT,PERP_USDT,ONE_USDT,ETH_USDT,FLUX_USDT,FLOKI_USDT,STX_USDT,ANML_USDT,XPRT_USDT,GALA_USDT,GXS_USDT,TORN_USDT,KAI_USDT,1INCH_USDT,CHR_USDT,GAL_USDT,GLMR_USDT,CTX_USDT,CERE_USDT,CART_USDT,STRAX_USDT,MASK_USDT,MKR_USDT,AVAX_USDT,ENJ_USDT,YAM_USDT,ALPACA_USDT,DODO_USDT,MFT_USDT,CAKE_USDT,RNDR_USDT,CTSI_USDT,GRIN_USDT,MXC_USDT,ONT_USDT,ANKR_USDT,SLIM_USDT,FIL_USDT,CTK_USDT,ASR_USDT,FEG_USDT,SERO_USDT,RSS3_USDT,IRIS_USDT,XCH_USDT,ZRX_USDT,BAND_USDT,BADGER_USDT,DAO_USDT,EPS_USDT,THETA_USDT,BAKE_USDT,SHIB_USDT,MBOX_USDT,NBS_USDT,SNT_USDT,DREP_USDT,NFT_USDT,AUCTION_USDT,BOSON_USDT,O3_USDT,NULS_USDT,OMG_USDT,PEARL_USDT,HAPI_USDT,STG_USDT,IDV_USDT,HORD_USDT,ZIL_USDT,SUPER_USDT,DENT_USDT,REN_USDT,RAI_USDT,ZEN_USDT,ALGO_USDT,BLZ_USDT,BOR_USDT,SC_USDT,HEGIC_USDT,MOB_USDT,DORA_USDT,FOR_USDT,FLOW_USDT,RARI_USDT,DYDX_USDT,ATLAS_USDT,GST_USDT,REEF_USDT,HT_USDT,XYO_USDT,CHESS_USDT,BAT_USDT,NYM_USDT,RAMP_USDT,USDC_USDT,ICP_USDT,EPK_USDT,EXRD_USDT,DOT_USDT,COOK_USDT,CKB_USDT,YGG_USDT,CRU_USDT,ANC_USDT,FIS_USDT,ALCX_USDT,HIGH_USDT,BEAM_USDT,BSW_USDT,STAR_USDT,ROSE_USDT,CNNS_USDT,BZRX_USDT,WOO_USDT,SAFEMOON_USDT,VTHO_USDT,OM_USDT,LAMB_USDT,CHZ_USDT,AIOZ_USDT,EDEN_USDT,POND_USDT,ATOM_USDT,UNFI_USDT,FORTH_USDT,MLN_USDT,NEO_USDT,MOVR_USDT,RLC_USDT,FXS_USDT,ENS_USDT,ATA_USDT,XPR_USDT,NEST_USDT,XLM_USDT,AUTO_USDT,SNX_USDT,OCN_USDT,RSR_USDT,MITH_USDT,KAR_USDT,INJ_USDT,PLA_USDT,CYS_USDT,WAXP_USDT,VOXEL_USDT,CRV_USDT,FITFI_USDT,WHALE_USDT,WRX_USDT,TIDAL_USDT,C98_USDT,HNT_USDT,TONCOIN_USDT,DOGGY_USDT,SYS_USDT,NPXS_USDT,CRO_USDT,LEMD_USDT,RAY_USDT,PERL_USDT,CQT_USDT,CFX_USDT,TOMO_USDT,ACA_USDT,SDN_USDT,OKT_USDT,WILD_USDT,BNX_USDT,TRU_USDT,RACA_USDT,SWEAT_USDT,ACH_USDT,AKRO_USDT,BTM_USDT,TKO_USDT,GT_USDT,OCEAN_USDT,WNCG_USDT,BSV_USDT,GHST_USDT,CELR_USDT,LINA_USDT,SAND_USDT,APE_USDT,WICC_USDT,FIDA_USDT,ADA_USDT,PROPS_USDT,METIS_USDT,KAVA_USDT,AERGO_USDT,CONV_USDT,TFUEL_USDT,FRONT_USDT,API3_USDT,FARM_USDT,AE_USDT,LRC_USDT,IOTA_USDT,RFOX_USDT,PHA_USDT,XCN_USDT,NAS_USDT,KEEP_USDT,VIDY_USDT,HOT_USDT,MINA_USDT,ETHW_USDT,ALICE_USDT,HAI_USDT,BTC_USDT,LTC_USDT,LTO_USDT,DC_USDT,NU_USDT,IOST_USDT,RAD_USDT,POLS_USDT,OP_USDT,WXT_USDT,STR_USDT,YIELD_USDT,GM_USDT,SPA_USDT,BTCST_USDT,WEMIX_USDT,CLV_USDT,ICX_USDT,PET_USDT,STARL_USDT,HBAR_USDT,REDTOKEN_USDT,BTT_USDT,LINK_USDT,TLM_USDT,ARES_USDT,GTC_USDT,SUSHI_USDT,KEY_USDT,ALN_USDT,KDA_USDT,DVI_USDT,SXP_USDT,MAPS_USDT,BCD_USDT,SRM_USDT,WIN_USDT,ZEC_USDT,JASMY_USDT" }, - "credentialsValidator": { - "requiresKey": true, - "requiresSecret": true - } - }, - "features": { - "supports": { - "restAPI": true, - "restCapabilities": { - "tickerBatching": true, - "autoPairUpdates": true - }, - "websocketAPI": false, - "websocketCapabilities": {} + "margin": { + "enabled": "BTC_USDT,ERN_USDT,T_USDT,CEEK_USDT,OGN_USDT,QNT_USDT,WOZX_USDT,ZEE_USDT,FUN_USDT,FLM_USDT,BOND_USDT", + "available": "BTC_USDT,ERN_USDT,T_USDT,CEEK_USDT,OGN_USDT,QNT_USDT,WOZX_USDT,ZEE_USDT,FUN_USDT,FLM_USDT,BOND_USDT,TARA_USDT,TRX_USDT,OXY_USDT,LON_USDT,DOGE_USDT,ISP_USDT,TWT_USDT,BAO_USDT,QUACK_USDT,ANT_USDT,VGX_USDT,ARPA_USDT,QUICK_USDT,UTK_USDT,HERO_USDT,WSG_USDT,BICO_USDT,MTV_USDT,VET_USDT,GARI_USDT,BCH_USDT,KLAY_USDT,WING_USDT,BLOK_USDT,SPS_USDT,WIKEN_USDT,WSIENNA_USDT,PUNDIX_USDT,FIC_USDT,ASTR_USDT,FET_USDT,VELO_USDT,BENQI_USDT,CWEB_USDT,RIF_USDT,UNI_USDT,ONG_USDT,ERG_USDT,ALPHA_USDT,CELO_USDT,XVG_USDT,GMAT_USDT,BTS_USDT,DOCK_USDT,GMT_USDT,DIA_USDT,CSPR_USDT,NKN_USDT,STAKE_USDT,SWASH_USDT,XEC_USDT,SWRV_USDT,QRDO_USDT,BLES_USDT,EOS_USDT,GRT_USDT,ASM_USDT,FIL6_USDT,GNO_USDT,EGLD_USDT,XYM_USDT,LOOKS_USDT,LOKA_USDT,BNC_USDT,BAS_USDT,SKL_USDT,STMX_USDT,CVC_USDT,DDOS_USDT,COTI_USDT,AVA_USDT,HMT_USDT,DF_USDT,LPT_USDT,XRP_USDT,TVK_USDT,FEVR_USDT,MBL_USDT,KIN_USDT,SPELL_USDT,MATIC_USDT,FTT_USDT,NMR_USDT,PMON_USDT,BNB_USDT,USDD_USDT,LSS_USDT,MDX_USDT,PRQ_USDT,ALPINE_USDT,DEGO_USDT,OMI_USDT,TIPS_USDT,OCT_USDT,FEI_USDT,UMEE_USDT,CRP_USDT,LION_USDT,YFI_USDT,DASH_USDT,REQ_USDT,SDAO_USDT,PNT_USDT,INSUR_USDT,OOKI_USDT,SUN_USDT,CRPT_USDT,BAC_USDT,DATA_USDT,LRN_USDT,JGN_USDT,KIMCHI_USDT,SUKU_USDT,VRA_USDT,AAVE_USDT,FTI_USDT,LDO_USDT,FRA_USDT,BLANK_USDT,NEAR_USDT,ZKS_USDT,MTRG_USDT,RLY_USDT,TCT_USDT,FLY_USDT,JST_USDT,YFII_USDT,AR_USDT,POLY_USDT,JULD_USDT,SOL_USDT,BZZ_USDT,AXS_USDT,ASD_USDT,XMR_USDT,FTM_USDT,HIT_USDT,LEO_USDT,LIT_USDT,PIG_USDT,COMP_USDT,ELON_USDT,IMX_USDT,EFI_USDT,XVS_USDT,WAVES_USDT,PEOPLE_USDT,SOS_USDT,RUNE_USDT,POLC_USDT,SCLP_USDT,BABYDOGE_USDT,KONO_USDT,SPI_USDT,ETC_USDT,MDA_USDT,MTL_USDT,BCHA_USDT,KISHU_USDT,SUNNY_USDT,PYR_USDT,XTZ_USDT,TRIBE_USDT,AUDIO_USDT,FIRO_USDT,MANA_USDT,OKB_USDT,DOG_USDT,SLP_USDT,KNC_USDT,GAS_USDT,LUNA_USDT,SAFEMARS_USDT,MIR_USDT,DAR_USDT,EGS_USDT,KSM_USDT,ATP_USDT,BIT_USDT,STORJ_USDT,XEM_USDT,QTUM_USDT,AGLD_USDT,RVN_USDT,OXT_USDT,SHFT_USDT,IOTX_USDT,LUNC_USDT,NEXO_USDT,AKITA_USDT,PERP_USDT,ONE_USDT,ETH_USDT,FLUX_USDT,FLOKI_USDT,STX_USDT,ANML_USDT,XPRT_USDT,GALA_USDT,GXS_USDT,TORN_USDT,KAI_USDT,1INCH_USDT,CHR_USDT,GAL_USDT,GLMR_USDT,CTX_USDT,CERE_USDT,CART_USDT,STRAX_USDT,MASK_USDT,MKR_USDT,AVAX_USDT,ENJ_USDT,YAM_USDT,ALPACA_USDT,DODO_USDT,MFT_USDT,CAKE_USDT,RNDR_USDT,CTSI_USDT,GRIN_USDT,MXC_USDT,ONT_USDT,ANKR_USDT,SLIM_USDT,FIL_USDT,CTK_USDT,ASR_USDT,FEG_USDT,SERO_USDT,RSS3_USDT,IRIS_USDT,XCH_USDT,ZRX_USDT,BAND_USDT,BADGER_USDT,DAO_USDT,EPS_USDT,THETA_USDT,BAKE_USDT,SHIB_USDT,MBOX_USDT,NBS_USDT,SNT_USDT,DREP_USDT,NFT_USDT,AUCTION_USDT,BOSON_USDT,O3_USDT,NULS_USDT,OMG_USDT,PEARL_USDT,HAPI_USDT,STG_USDT,IDV_USDT,HORD_USDT,ZIL_USDT,SUPER_USDT,DENT_USDT,REN_USDT,RAI_USDT,ZEN_USDT,ALGO_USDT,BLZ_USDT,BOR_USDT,SC_USDT,HEGIC_USDT,MOB_USDT,DORA_USDT,FOR_USDT,FLOW_USDT,RARI_USDT,DYDX_USDT,ATLAS_USDT,GST_USDT,REEF_USDT,HT_USDT,XYO_USDT,CHESS_USDT,BAT_USDT,NYM_USDT,RAMP_USDT,USDC_USDT,ICP_USDT,EPK_USDT,EXRD_USDT,DOT_USDT,COOK_USDT,CKB_USDT,YGG_USDT,CRU_USDT,ANC_USDT,FIS_USDT,ALCX_USDT,HIGH_USDT,BEAM_USDT,BSW_USDT,STAR_USDT,ROSE_USDT,CNNS_USDT,BZRX_USDT,WOO_USDT,SAFEMOON_USDT,VTHO_USDT,OM_USDT,LAMB_USDT,CHZ_USDT,AIOZ_USDT,EDEN_USDT,POND_USDT,ATOM_USDT,UNFI_USDT,FORTH_USDT,MLN_USDT,NEO_USDT,MOVR_USDT,RLC_USDT,FXS_USDT,ENS_USDT,ATA_USDT,XPR_USDT,NEST_USDT,XLM_USDT,AUTO_USDT,SNX_USDT,OCN_USDT,RSR_USDT,MITH_USDT,KAR_USDT,INJ_USDT,PLA_USDT,CYS_USDT,WAXP_USDT,VOXEL_USDT,CRV_USDT,FITFI_USDT,WHALE_USDT,WRX_USDT,TIDAL_USDT,C98_USDT,HNT_USDT,TONCOIN_USDT,DOGGY_USDT,SYS_USDT,NPXS_USDT,CRO_USDT,LEMD_USDT,RAY_USDT,PERL_USDT,CQT_USDT,CFX_USDT,TOMO_USDT,ACA_USDT,SDN_USDT,OKT_USDT,WILD_USDT,BNX_USDT,TRU_USDT,RACA_USDT,SWEAT_USDT,ACH_USDT,AKRO_USDT,BTM_USDT,TKO_USDT,GT_USDT,OCEAN_USDT,WNCG_USDT,BSV_USDT,GHST_USDT,CELR_USDT,LINA_USDT,SAND_USDT,APE_USDT,WICC_USDT,FIDA_USDT,ADA_USDT,PROPS_USDT,METIS_USDT,KAVA_USDT,AERGO_USDT,CONV_USDT,TFUEL_USDT,FRONT_USDT,API3_USDT,FARM_USDT,AE_USDT,LRC_USDT,IOTA_USDT,RFOX_USDT,PHA_USDT,XCN_USDT,NAS_USDT,KEEP_USDT,VIDY_USDT,HOT_USDT,MINA_USDT,ETHW_USDT,ALICE_USDT,HAI_USDT,LTC_USDT,LTO_USDT,DC_USDT,NU_USDT,IOST_USDT,RAD_USDT,POLS_USDT,OP_USDT,WXT_USDT,STR_USDT,YIELD_USDT,GM_USDT,SPA_USDT,BTCST_USDT,WEMIX_USDT,CLV_USDT,ICX_USDT,PET_USDT,STARL_USDT,HBAR_USDT,REDTOKEN_USDT,BTT_USDT,LINK_USDT,TLM_USDT,ARES_USDT,GTC_USDT,SUSHI_USDT,KEY_USDT,ALN_USDT,KDA_USDT,DVI_USDT,SXP_USDT,MAPS_USDT,BCD_USDT,SRM_USDT,WIN_USDT,ZEC_USDT,JASMY_USDT" }, - "enabled": { - "autoPairUpdates": true, - "websocketAPI": false + "delivery": { + "enabled": "BTC_USD_20230331,BTC_USD_20221230,BTC_USDT_20221021,BTC_USDT_20221014", + "available": "BTC_USD_20221021,BTC_USD_20221014,BTC_USD_20230331,BTC_USD_20221230,BTC_USDT_20221021,BTC_USDT_20221014,BTC_USDT_20230331,BTC_USDT_20221230" } - }, - "bankAccounts": [ - { - "enabled": false, - "bankName": "", - "bankAddress": "", - "bankPostalCode": "", - "bankPostalCity": "", - "bankCountry": "", - "accountName": "", - "accountNumber": "", - "swiftCode": "", - "iban": "", - "supportedCurrencies": "" + } + }, + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "tickerBatching": true, + "autoPairUpdates": true + }, + "websocketAPI": true, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": true + } + }, + "bankAccounts": [ + { + "enabled": false, + "bankName": "", + "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ] + }, + { + "name": "Gemini", + "enabled": true, + "verbose": false, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, + "websocketOrderbookBufferLimit": 5, + "baseCurrencies": "USD", + "currencyPairs": { + "requestFormat": { + "uppercase": true + }, + "configFormat": { + "uppercase": true + }, + "useGlobalFormat": true, + "assetTypes": [ + "spot" + ], + "pairs": { + "spot": { + "enabled": "BTCUSD", + "available": "BTCUSD,ETHBTC,ETHUSD,BCHUSD,BCHBTC,BCHETH,LTCUSD,LTCBTC,LTCETH,LTCBCH,ZECUSD,ZECBTC,ZECETH,ZECBCH,ZECLTC" } + } + }, + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "autoPairUpdates": true + }, + "websocketAPI": true, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": false + } + }, + "bankAccounts": [ + { + "enabled": false, + "bankName": "", + "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ] + }, + { + "name": "HitBTC", + "enabled": true, + "verbose": false, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, + "websocketOrderbookBufferLimit": 5, + "baseCurrencies": "USD", + "currencyPairs": { + "requestFormat": { + "uppercase": true + }, + "configFormat": { + "uppercase": true, + "delimiter": "-" + }, + "useGlobalFormat": true, + "assetTypes": [ + "spot" ], - "orderbook": { - "verificationBypass": false, - "websocketBufferLimit": 5, - "websocketBufferEnabled": false, - "publishPeriod": 10000000000 + "pairs": { + "spot": { + "enabled": "BTC-USD", + "available": "BCN-BTC,BTC-USD,DASH-BTC,DOGE-BTC,DOGE-USD,EMC-BTC,ETH-BTC,LSK-BTC,LTC-BTC,LTC-USD,NXT-BTC,SBD-BTC,SC-BTC,STEEM-BTC,XDN-BTC,XEM-BTC,XMR-BTC,ARDR-BTC,ZEC-BTC,WAVES-BTC,MAID-BTC,DGD-BTC,SNGLS-BTC,1ST-BTC,TRST-BTC,TIME-BTC,GNO-BTC,REP-BTC,XMR-USD,DASH-USD,ETH-USD,NXT-USD,ZRC-BTC,BOS-BTC,DCT-BTC,ANT-BTC,AEON-BTC,GUP-BTC,PLU-BTC,LUN-BTC,EDG-BTC,RLC-BTC,SWT-BTC,TKN-BTC,WINGS-BTC,XAUR-BTC,AE-BTC,PTOY-BTC,ZEC-USD,XEM-USD,BCN-USD,XDN-USD,MAID-USD,ETC-BTC,ETC-USD,PLBT-BTC,BNT-BTC,SNT-ETH,CVC-USD,PAY-ETH,OAX-ETH,OMG-ETH,BQX-ETH,XTZ-BTC,DICE-BTC,PTOY-ETH,1ST-ETH,XAUR-ETH,TIME-ETH,DICE-ETH,SWT-ETH,XMR-ETH,ETC-ETH,DASH-ETH,ZEC-ETH,PLU-ETH,GNO-ETH,XRP-BTC,STRAT-USD,STRAT-BTC,SNC-ETH,ADX-ETH,BET-ETH,EOS-ETH,DENT-ETH,SAN-ETH,EOS-BTC,EOS-USD,XTZ-ETH,XTZ-USD,MYB-ETH,SUR-ETH,IXT-ETH,PLR-ETH,TIX-ETH,PRO-ETH,AVT-ETH,EVX-USD,DLT-BTC,BNT-ETH,BNT-USD,MANA-USD,DNT-BTC,FYP-BTC,OPT-BTC,TNT-ETH,STX-BTC,STX-ETH,STX-USD,TNT-USD,TNT-BTC,ENG-ETH,XUC-USD,SNC-BTC,SNC-USD,OAX-USD,OAX-BTC,ZRX-BTC,ZRX-ETH,ZRX-USD,RVT-BTC,PPC-BTC,PPC-USD,QTUM-ETH,IGNIS-ETH,BMC-BTC,BMC-ETH,BMC-USD,CND-BTC,CND-ETH,CND-USD,CDT-ETH,CDT-USD,FUN-BTC,FUN-ETH,FUN-USD,HVN-BTC,HVN-ETH,POE-BTC,POE-ETH,AMB-USD,AMB-ETH,AMB-BTC,HPC-BTC,PPT-ETH,MTH-BTC,MTH-ETH,LRC-BTC,LRC-ETH,ICX-BTC,ICX-ETH,NEO-BTC,NEO-ETH,NEO-USD,CSNO-BTC,ICX-USD,IND-ETH,KICK-BTC,YOYOW-BTC,CDT-BTC,XVG-BTC,XVG-ETH,XVG-USD,DGB-BTC,DGB-ETH,DGB-USD,DCN-ETH,DCN-USD,VIBE-BTC,ENJ-BTC,ENJ-ETH,ENJ-USD,ZSC-BTC,ZSC-ETH,ZSC-USD,TRX-BTC,TRX-ETH,TRX-USD,ART-BTC,EVX-BTC,EVX-ETH,SUB-BTC,SUB-ETH,SUB-USD,WTC-BTC,BTM-BTC,BTM-ETH,BTM-USD,LIFE-BTC,VIB-BTC,VIB-ETH,VIB-USD,DRT-ETH,STU-USD,OMG-BTC,PAY-BTC,PPT-BTC,SNT-BTC,BTG-BTC,BTG-ETH,BTG-USD,SMART-BTC,SMART-ETH,SMART-USD,XUC-ETH,XUC-BTC,LA-ETH,EDO-BTC,EDO-ETH,EDO-USD,HGT-ETH,IXT-BTC,SCL-BTC,ETP-BTC,ETP-ETH,ETP-USD,NEBL-BTC,NEBL-ETH,ARN-BTC,ARN-ETH,STU-BTC,STU-ETH,GVT-ETH,BTX-BTC,LTC-ETH,BCN-ETH,MAID-ETH,NXT-ETH,STRAT-ETH,XDN-ETH,XEM-ETH,PLR-BTC,SUR-BTC,BQX-BTC,DOGE-ETH,AMM-BTC,AMM-ETH,AMM-USD,DBIX-BTC,PRE-BTC,ZAP-BTC,DOV-BTC,DOV-ETH,XRP-ETH,XRP-USD,HSR-BTC,LEND-BTC,LEND-ETH,SPF-ETH,SBTC-BTC,SBTC-ETH,LOC-BTC,LOC-ETH,LOC-USD,SWFTC-BTC,SWFTC-ETH,SWFTC-USD,STAR-ETH,SBTC-USD,STORM-BTC,DIM-ETH,DIM-USD,DIM-BTC,NGC-BTC,NGC-ETH,NGC-USD,EMC-ETH,EMC-USD,MCO-BTC,MCO-ETH,MCO-USD,MANA-ETH,MANA-BTC,CPAY-ETH,DATA-BTC,DATA-ETH,DATA-USD,UTT-BTC,UTT-ETH,UTT-USD,KMD-BTC,KMD-ETH,KMD-USD,QTUM-USD,QTUM-BTC,SNT-USD,OMG-USD,EKO-BTC,EKO-ETH,ADX-BTC,ADX-USD,LSK-ETH,LSK-USD,PLR-USD,SUR-USD,BQX-USD,DRT-USD,REP-ETH,REP-USD,WAXP-BTC,WAXP-ETH,WAXP-USD,C20-BTC,C20-ETH,IDH-BTC,IDH-ETH,IPL-BTC,COV-BTC,COV-ETH,SENT-BTC,SENT-ETH,SENT-USD,SMT-BTC,SMT-ETH,SMT-USD,CHAT-BTC,CHAT-ETH,CHAT-USD,TRAC-ETH,JNT-ETH,UTK-BTC,UTK-ETH,UTK-USD,GNX-ETH,CHSB-BTC,CHSB-ETH,DAY-BTC,DAY-ETH,DAY-USD,NEU-BTC,NEU-ETH,NEU-USD,TAU-BTC,FLP-BTC,FLP-ETH,FLP-USD,R-BTC,R-ETH,EKO-USD,BCPT-ETH,BCPT-USD,PKT-BTC,PKT-ETH,BETR-BTC,BETR-ETH,HAND-ETH,HAND-USD,CHP-ETH,BCPT-BTC,ACT-BTC,ACT-ETH,ACT-USD,ADA-BTC,ADA-ETH,ADA-USD,SIG-BTC,MTX-BTC,MTX-ETH,MTX-USD,WIZ-BTC,WIZ-ETH,WIZ-USD,DADI-BTC,DADI-ETH,BDG-ETH,DATX-BTC,DATX-ETH,TRUE-BTC,DRG-BTC,DRG-ETH,BANCA-BTC,BANCA-ETH,ZAP-ETH,ZAP-USD,AUTO-BTC,SOC-BTC,OCN-BTC,OCN-ETH,STQ-BTC,STQ-ETH,XLM-BTC,XLM-ETH,XLM-USD,IOTA-BTC,IOTA-ETH,IOTA-USD,DRT-BTC,BETR-USD,ERT-BTC,CRPT-BTC,CRPT-USD,MESH-BTC,MESH-ETH,MESH-USD,IHT-BTC,IHT-ETH,IHT-USD,SCC-BTC,YCC-BTC,DAN-BTC,TEL-BTC,TEL-ETH,NCT-BTC,NCT-ETH,NCT-USD,BMH-BTC,BANCA-USD,BERRY-BTC,BERRY-ETH,BERRY-USD,GBX-BTC,GBX-ETH,GBX-USD,SHIP-BTC,SHIP-ETH,NANO-BTC,NANO-ETH,NANO-USD,LNC-BTC,KIN-ETH,ARDR-USD,FOTA-ETH,FOTA-BTC,CVT-BTC,CVT-ETH,CVT-USD,STQ-USD,GNT-BTC,GNT-ETH,GNT-USD,GET-BTC,MITH-BTC,MITH-ETH,MITH-USD,DADI-USD,TKY-BTC,ACAT-BTC,ACAT-ETH,ACAT-USD,BTX-USD,WIKI-BTC,WIKI-ETH,WIKI-USD,ONT-BTC,ONT-ETH,ONT-USD,FTX-BTC,FTX-ETH,NAVI-BTC,VME-ETH,NAVI-ETH,LND-ETH,CSM-BTC,NANJ-BTC,NTK-BTC,NTK-ETH,NTK-USD,AUC-BTC,AUC-ETH,CMCT-BTC,CMCT-ETH,CMCT-USD,MAN-BTC,MAN-ETH,MAN-USD,PNT-BTC,PNT-ETH,FXT-BTC,NEXO-BTC,PAT-BTC,PAT-ETH,XMC-BTC,FXT-ETH,XMC-ETH,XMC-USD,FDZ-BTC,FDZ-ETH,FDZ-USD,SPD-BTC,SPD-ETH,MITX-BTC,TIV-BTC,B2G-BTC,B2G-USD,HBZ-BTC,FACE-BTC,FACE-ETH,HBZ-ETH,HBZ-USD,CPT-BTC,PAT-USD,HTML-BTC,HTML-ETH,MITX-ETH,BTS-BTC,BNK-BTC,BNK-ETH,BNK-USD,TIV-ETH,TIV-USD,CSM-ETH,CSM-USD,INK-BTC,IOST-BTC,INK-ETH,INK-USD,CBC-BTC,IOST-USD,ZIL-BTC,ABYSS-BTC,ABYSS-ETH,ZIL-USD,BCI-BTC,CBC-ETH,CBC-USD,PITCH-BTC,PITCH-ETH,HTML-USD,TDS-BTC,TDS-ETH,TDS-USD,SBD-ETH,SBD-USD,DPN-BTC,UUU-BTC,UUU-ETH,XBP-BTC,ELEC-BTC,ELEC-ETH,ELEC-USD,QNTU-BTC,QNTU-ETH,QNTU-USD,IPL-ETH,IPL-USD,CENNZ-BTC,CENNZ-ETH,SWM-BTC,SPF-USD,SPF-BTC,LCC-BTC,HGT-BTC,ETH-TUSD,BTC-TUSD,LTC-TUSD,XMR-TUSD,ZRX-TUSD,NEO-TUSD,USD-TUSD,BTC-DAI,ETH-DAI,MKR-DAI,EOS-DAI,USD-DAI,MKR-BTC,MKR-ETH,MKR-USD,TUSD-DAI,NEO-DAI,LTC-DAI,XMR-DAI,XRP-DAI,NEXO-ETH,NEXO-USD,DWS-BTC,DWS-ETH,DWS-USD,APPC-BTC,APPC-ETH,APPC-USD,BIT-ETH,SPC-BTC,SPC-ETH,SPC-USD,REX-BTC,REX-ETH,REX-USD,ELF-BTC,ELF-USD,BCD-BTC,BCD-USD,CVCOIN-BTC,CVCOIN-ETH,CVCOIN-USD,EDG-ETH,EDG-USD,NLC2-BTC,DASH-EURS,ZEC-EURS,BTC-EURS,EOS-EURS,ETH-EURS,LTC-EURS,NEO-EURS,XMR-EURS,XRP-EURS,EURS-USD,EURS-TUSD,EURS-DAI,MNX-USD,ROX-ETH,ZPR-ETH,MNX-BTC,MNX-ETH,KIND-BTC,KIND-ETH,ENGT-BTC,ENGT-ETH,PMA-BTC,PMA-ETH,TV-BTC,TV-ETH,TV-USD,BAT-BTC,BAT-ETH,BAT-USD,SRN-BTC,SRN-ETH,SRN-USD,SVD-BTC,SVD-ETH,SVD-USD,GST-BTC,GST-ETH,GST-USD,BNB-BTC,BNB-ETH,BNB-USD,DIT-BTC,DIT-ETH,POA20-BTC,PROC-BTC,POA20-ETH,POA20-USD,POA20-DAI,NIM-BTC,USE-BTC,USE-ETH,DAV-BTC,DAV-ETH,ABTC-BTC,NIM-ETH,ABA-BTC,ABA-ETH,ABA-USD,BCN-EOS,LTC-EOS,XMR-EOS,DASH-EOS,TRX-EOS,NEO-EOS,ZEC-EOS,LSK-EOS,XEM-EOS,XRP-EOS,RCN-BTC,RCN-ETH,RCN-USD,HMQ-BTC,HMQ-ETH,MYST-BTC,MYST-ETH,USD-GUSD,BTC-GUSD,ETH-GUSD,EOS-GUSD,AXPR-BTC,AXPR-ETH,DAG-BTC,DAG-ETH,BITS-BTC,BITS-ETH,BITS-USD,CDCC-BTC,CDCC-ETH,CDCC-USD,VET-BTC,VET-ETH,VET-USD,SILK-ETH,BOX-BTC,BOX-ETH,BOX-EURS,BOX-EOS,VOCO-BTC,VOCO-ETH,VOCO-USD,PASS-BTC,PASS-ETH,SLX-BTC,SLX-USD,PBTT-BTC,PMA-USD,TRAD-BTC,DGTX-BTC,DGTX-ETH,DGTX-USD,MRK-BTC,MRK-ETH,DGB-TUSD,SNBL-BTC,BCH-BTC,BCH-USD,BSV-BTC,BSV-USD,BKX-BTC,NPLC-BTC,NPLC-ETH,ETN-BTC,ETN-ETH,ETN-USD,DTR-BTC,DTR-ETH,TDP-BTC,HBT-ETH,PXG-BTC,PXG-USD,BTC-PAX,ETH-PAX,USD-PAX,BTC-USDC,ETH-USDC,USD-USDC,TUSD-USDC,DAI-USDC,EOS-PAX,CLO-BTC,CLO-ETH,CLO-USD,PETH-BTC,PETH-ETH,PETH-USD,BRD-BTC,BRD-ETH,NMR-BTC,SALT-BTC,SALT-ETH,POLY-BTC,POLY-ETH,POWR-BTC,POWR-ETH,STORJ-BTC,STORJ-ETH,STORJ-USD,MLN-BTC,MLN-ETH,BDG-BTC,POA-ETH,POA-BTC,POA-USD,POA-DAI,KIN-BTC,VEO-BTC,PLA-BTC,PLA-ETH,PLA-USD,BTT-BTC,BTT-USD,BTT-ETH,ZEN-BTC,ZEN-ETH,ZEN-USD,GRIN-BTC,GRIN-ETH,GRIN-USD,FET-BTC,HT-BTC,HT-USD,XZC-BTC,XZC-ETH,XZC-USD,VRA-BTC,VRA-ETH,BTC-KRWB,USD-KRWB,WBTC-ETH,CRO-BTC,CRO-ETH,CRO-USD,GAS-BTC,GAS-ETH,GAS-USD,ORMEUS-BTC,ORMEUS-ETH,SWM-ETH,SWM-USD,PRE-ETH,PHX-BTC,PHX-ETH,PHX-USD,BET-BTC,USD-EOSDT,BTC-EOSDT,ETH-EOSDT,EOS-EOSDT,DAI-EOSDT,NUT-BTC,NUT-EOS,NUT-USD,CUTE-BTC,CUTE-ETH,CUTE-USD,CUTE-EOS,XCON-BTC,DCR-BTC,DCR-ETH,DCR-USD,MG-BTC,MG-ETH,MG-EOS,MG-USD,GNX-BTC,PRO-BTC,EURS-EOSDT,TUSD-EOSDT,ECOIN-BTC,ECOIN-ETH,ECOIN-USD,AGI-BTC,LOOM-BTC,LOOM-ETH,BLZ-BTC,QKC-BTC,QKC-ETH,KNC-BTC,KNC-ETH,KNC-USD,KEY-BTC,KEY-ETH,ATOM-BTC,ATOM-USD,ATOM-ETH,BRDG-BTC,BRDG-ETH,BRDG-USD,MTL-BTC,MTL-ETH,EXP-BTC,BTCB-BTC,PBT-BTC,PBT-ETH,LINK-BTC,LINK-ETH,LINK-USD,USD-USDT20,PHB-BTC,BCH-ETH,BCH-DAI,BCH-TUSD,BCH-EURS,DAPP-BTC,DAPP-EOS,BTC-USDT20,DENT-BTC,DENT-USD,NJBC-BTC,NJBC-ETH,XRC-BTC,EOS-BCH,LTC-BCH,XRP-BCH,TRX-BCH,XLM-BCH,ETC-BCH,DASH-BCH,ZEC-BCH,BKX-USD,LAMB-BTC,NPXS-BTC,HBAR-BTC,HBAR-USD,ONE-BTC,RFR-BTC,RFR-USD,BUSD-USD,PAXG-BTC,PAXG-USD,REN-BTC,IGNIS-BTC,CEL-BTC,CEL-ETH,WIN-USD,ADK-BTC,PART-BTC,SOZ-BTC,SOZ-ETH,SOZ-USD,WAVES-USD,ADA-BCH,ONT-BCH,XMR-BCH,ATOM-BCH,LINK-BCH,OMG-BCH,WAVES-BCH,IOTX-BTC,HOT-BTC,SLV-BTC,HEDG-BTC,CHZ-BTC,CHZ-USD,COCOS-BTC,COCOS-USD,SEELE-BTC,SEELE-USD,MDA-BTC,LEO-USD,REM-BTC,REM-ETH,REM-USD,SCD-DAI,BTC-BUSD,RVN-BTC,BST-BTC,ERD-BTC,KRL-BTC,FTT-BTC,FTT-USD,RAISE-BTC,RAISE-ETH" + } } }, - { - "name": "Bitmex", - "enabled": true, - "verbose": false, - "httpTimeout": 15000000000, - "websocketResponseCheckTimeout": 30000000, - "websocketResponseMaxLimit": 7000000000, - "websocketTrafficTimeout": 30000000000, - "websocketOrderbookBufferLimit": 5, - "baseCurrencies": "USD", - "currencyPairs": { - "assetTypes": [ - "perpetualcontract", - "futures", - "downsideprofitcontract", - "upsideprofitcontract" - ], - "pairs": { - "downsideprofitcontract": { - "enabled": "XBT7D_D95", - "available": "XBT7D_D95", - "requestFormat": { - "uppercase": true, - "delimiter": "_" - }, - "configFormat": { - "uppercase": true, - "delimiter": "_" - } - }, - "futures": { - "enabled": "BCHZ19", - "available": "XRPZ19,BCHZ19,ADAZ19,EOSZ19,TRXZ19,XBTZ19,ETHZ19,LTCZ19", - "requestFormat": { - "uppercase": true - }, - "configFormat": { - "uppercase": true - } - }, - "perpetualcontract": { - "enabled": "ETHUSD", - "available": "XBTUSD,ETHUSD", - "requestFormat": { - "uppercase": true - }, - "configFormat": { - "uppercase": true - } + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "tickerBatching": true, + "autoPairUpdates": true + }, + "websocketAPI": true, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": false + } + }, + "bankAccounts": [ + { + "enabled": false, + "bankName": "", + "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ] + }, + { + "name": "Huobi", + "enabled": true, + "verbose": false, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, + "websocketOrderbookBufferLimit": 5, + "baseCurrencies": "USD", + "currencyPairs": { + "requestFormat": { + "uppercase": false + }, + "configFormat": { + "uppercase": true, + "delimiter": "-" + }, + "useGlobalFormat": true, + "assetTypes": [ + "spot", + "coinmarginedfutures", + "futures" + ], + "pairs": { + "coinmarginedfutures": { + "assetEnabled": true, + "enabled": "BTC-USD", + "available": "BTC-USD,ETH-USD,LINK-USD,DOT-USD,ADA-USD,LTC-USD,XRP-USD,TRX-USD,DOGE-USD", + "requestFormat": { + "uppercase": true, + "delimiter": "-" }, - "upsideprofitcontract": { - "enabled": "XBT7D_U105", - "available": "XBT7D_U105", - "requestFormat": { - "uppercase": true, - "delimiter": "_" - }, - "configFormat": { - "uppercase": true, - "delimiter": "_" - } + "configFormat": { + "uppercase": true, + "delimiter": "-" } - } - }, - "api": { - "authenticatedSupport": false, - "authenticatedWebsocketApiSupport": false, - "endpoints": { - "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" }, - "credentials": { - "key": "Key", - "secret": "Secret" - }, - "credentialsValidator": { - "requiresKey": true, - "requiresSecret": true - } - }, - "features": { - "supports": { - "restAPI": true, - "restCapabilities": { - "tickerBatching": true, - "autoPairUpdates": true + "futures": { + "assetEnabled": true, + "enabled": "BTC-230915", + "available": "BTC-230915,BTC-230922,BTC-230929,ETH-230915,ETH-230922,ETH-230929,TRX-230915,TRX-230922", + "requestFormat": { + "uppercase": true }, - "websocketAPI": true, - "websocketCapabilities": {} - }, - "enabled": { - "autoPairUpdates": true, - "websocketAPI": false - } - }, - "bankAccounts": [ - { - "enabled": false, - "bankName": "", - "bankAddress": "", - "bankPostalCode": "", - "bankPostalCity": "", - "bankCountry": "", - "accountName": "", - "accountNumber": "", - "swiftCode": "", - "iban": "", - "supportedCurrencies": "" - } - ] - }, - { - "name": "Bitstamp", - "enabled": true, - "verbose": false, - "httpTimeout": 15000000000, - "websocketResponseCheckTimeout": 30000000, - "websocketResponseMaxLimit": 7000000000, - "websocketTrafficTimeout": 30000000000, - "websocketOrderbookBufferLimit": 5, - "baseCurrencies": "USD,EUR", - "currencyPairs": { - "requestFormat": { - "uppercase": true - }, - "configFormat": { - "uppercase": true - }, - "useGlobalFormat": true, - "assetTypes": [ - "spot" - ], - "pairs": { - "spot": { - "enabled": "BTCUSD,BTCEUR,EURUSD,XRPUSD,XRPEUR", - "available": "LTCUSD,ETHUSD,XRPEUR,BCHUSD,BCHEUR,BTCEUR,XRPBTC,EURUSD,BCHBTC,LTCEUR,BTCUSD,LTCBTC,XRPUSD,ETHBTC,ETHEUR" + "configFormat": { + "uppercase": true, + "delimiter": "-" } - } - }, - "api": { - "authenticatedSupport": false, - "authenticatedWebsocketApiSupport": false, - "endpoints": { - "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" - }, - "credentials": { - "key": "Key", - "secret": "Secret", - "clientID": "ClientID" - }, - "credentialsValidator": { - "requiresKey": true, - "requiresSecret": true, - "requiresClientID": true - } - }, - "features": { - "supports": { - "restAPI": true, - "restCapabilities": { - "autoPairUpdates": true - }, - "websocketAPI": true, - "websocketCapabilities": {} }, - "enabled": { - "autoPairUpdates": true, - "websocketAPI": false + "spot": { + "enabled": "BTC-USDT", + "available": "PROPY-ETH,IOTA-BTC,UGAS-ETH,PAI-USDT,BSV-HUSD,MTX-ETH,BCH-BTC,LTC-HT,SOC-USDT,WXT-BTC,SALT-BTC,RCN-ETH,PNT-ETH,TT-USDT,AIDOC-ETH,BIX-BTC,OCN-USDT,QTUM-ETH,KCASH-ETH,SNT-USDT,LUN-BTC,QASH-BTC,ITC-BTC,NAS-BTC,XMR-BTC,TNT-ETH,UC-ETH,FAIR-BTC,PC-ETH,YEE-BTC,PAY-ETH,XMX-BTC,CRE-USDT,BAT-ETH,BHT-USDT,CKB-HT,LAMB-HT,AE-USDT,QUN-ETH,LYM-BTC,BCH-HT,BHT-BTC,RUFF-ETH,CNN-BTC,FOR-USDT,GTC-ETH,TRX-ETH,ELA-USDT,ACT-ETH,SMT-ETH,BUT-ETH,BCH-USDT,ICX-BTC,MEET-BTC,NCC-BTC,APPC-BTC,GVE-ETH,TNB-BTC,STEEM-ETH,18C-ETH,LBA-BTC,EKO-BTC,REQ-BTC,SOC-BTC,BOX-ETH,ELF-BTC,ZRX-ETH,LET-USDT,HT-BTC,TUSD-HUSD,EGCC-BTC,WTC-BTC,ATP-USDT,DOCK-USDT,PAI-BTC,ONT-ETH,IRIS-BTC,BTT-ETH,SC-BTC,XZC-BTC,LBA-USDT,HT-USDT,VET-ETH,KMD-ETH,SHE-ETH,PORTAL-BTC,ONE-BTC,BIX-USDT,RCCC-BTC,SKM-USDT,XTZ-ETH,SWFTC-BTC,RSR-BTC,LINK-ETH,DATX-BTC,HPT-HT,GET-ETH,BLZ-ETH,CTXC-USDT,CNNS-USDT,PVT-HT,ITC-USDT,LTC-BTC,NCASH-BTC,HOT-ETH,ADA-USDT,ADX-BTC,NODE-USDT,TRIO-BTC,GXC-ETH,SNT-BTC,FOR-BTC,DBC-BTC,UUU-USDT,CVCOIN-ETH,RSR-USDT,CRO-USDT,OCN-BTC,NEW-USDT,EGT-USDT,MANA-BTC,CMT-USDT,WXT-HT,XRP-BTC,MT-ETH,PAX-HUSD,LSK-ETH,IOTA-USDT,SRN-ETH,ZIL-ETH,ELF-USDT,LXT-ETH,LAMB-BTC,CRE-HT,CKB-BTC,XVG-BTC,BSV-BTC,BFT-BTC,WPR-ETH,HT-HUSD,POWR-BTC,MANA-ETH,ENG-ETH,ZJLT-ETH,SNC-ETH,ATOM-ETH,WICC-USDT,KAN-ETH,DGD-BTC,VSYS-HT,BCD-BTC,BTM-ETH,DOGE-USDT,MEX-BTC,BTG-BTC,DAC-ETH,DAT-BTC,GRS-ETH,ADX-ETH,EM-HT,GXC-USDT,CVC-BTC,OMG-ETH,SSP-ETH,OGO-HT,CMT-ETH,POLY-ETH,XZC-USDT,THETA-USDT,XEM-USDT,LOL-USDT,BCH-HUSD,GSC-BTC,DOGE-ETH,MDS-BTC,BTS-ETH,CTXC-BTC,MCO-BTC,BCX-BTC,ZLA-ETH,EKT-USDT,MAN-BTC,BLZ-BTC,ATOM-USDT,LOL-BTC,HPT-USDT,EM-BTC,EOS-USDT,WAN-BTC,GNT-BTC,CRO-BTC,MANA-USDT,SEELE-USDT,FSN-BTC,VIDY-HT,USDC-HUSD,LTC-HUSD,XRP-USDT,VSYS-BTC,STORJ-BTC,LOOM-ETH,SKM-BTC,LINK-USDT,TT-HT,QSP-ETH,ETN-BTC,FSN-HT,NODE-BTC,HC-USDT,PHX-BTC,XLM-BTC,RCCC-ETH,LTC-USDT,UUU-BTC,SEELE-ETH,PVT-BTC,HC-ETH,REN-ETH,KAN-USDT,EOS-ETH,BSV-USDT,BTS-USDT,KMD-BTC,OGO-USDT,THETA-ETH,MUSK-BTC,CNNS-HT,ETC-BTC,COVA-BTC,BTT-TRX,XMR-USDT,MTN-ETH,QUN-BTC,NAS-USDT,ELA-ETH,HIT-ETH,BTT-USDT,EKT-ETH,TOS-BTC,GAS-ETH,DCR-USDT,ONT-BTC,NEW-HT,NEXO-BTC,ETH-USDT,WXT-USDT,FOR-HT,ADA-BTC,EVX-ETH,VET-BTC,ZEC-USDT,NANO-ETH,IOST-HT,BCV-ETH,REN-USDT,NULS-ETH,ACT-USDT,LET-ETH,BTM-USDT,MEET-ETH,AKRO-HT,ARDR-BTC,DCR-ETH,NANO-USDT,BTC-HUSD,ALGO-BTC,IIC-ETH,BHD-BTC,KNC-ETH,ATP-BTC,ZRX-BTC,ABT-BTC,18C-BTC,XMR-ETH,WAXP-BTC,CVNT-BTC,MX-USDT,OST-ETH,NKN-BTC,TOPC-BTC,GNX-BTC,FTT-USDT,ONE-HT,DGB-ETH,NULS-USDT,DASH-BTC,UIP-BTC,KCASH-HT,WICC-ETH,EKO-ETH,EGT-HT,IRIS-USDT,STK-ETH,MXC-BTC,NAS-ETH,OMG-USDT,SMT-BTC,BUT-BTC,HIT-USDT,BAT-BTC,IRIS-ETH,NKN-HT,PC-BTC,TOP-USDT,GTC-BTC,LSK-BTC,ITC-ETH,DTA-BTC,HOT-BTC,BTT-BTC,FAIR-ETH,DOCK-ETH,QTUM-BTC,ZEN-BTC,ZIL-BTC,RCN-BTC,FTI-BTC,BHD-USDT,VIDY-USDT,LUN-ETH,DBC-ETH,TOPC-ETH,IIC-BTC,STEEM-USDT,IOTA-ETH,KCASH-BTC,RUFF-BTC,APPC-ETH,MT-BTC,SOC-ETH,GT-HT,PROPY-BTC,AIDOC-BTC,ACT-BTC,LYM-ETH,CHAT-BTC,SWFTC-ETH,ETH-BTC,UIP-USDT,UGAS-BTC,XRP-HUSD,ALGO-USDT,TNT-BTC,ONT-USDT,YEE-ETH,AKRO-BTC,TRX-USDT,OCN-ETH,SRN-BTC,DASH-USDT,XMX-ETH,NANO-BTC,QASH-ETH,EOS-HT,GT-BTC,XTZ-USDT,ARPA-USDT,SALT-ETH,BKBT-ETH,MTX-BTC,SMT-USDT,GXC-BTC,VIDY-BTC,FTT-HT,LAMB-ETH,TRX-BTC,TRIO-ETH,BFT-ETH,LINK-BTC,AE-ETH,NULS-BTC,BHD-HT,AST-ETH,NEO-USDT,EDU-BTC,CVCOIN-BTC,GVE-BTC,GET-BTC,ZRX-USDT,ELF-ETH,DATX-ETH,ADA-ETH,TOP-HT,NCASH-ETH,QTUM-USDT,ETC-HT,ZIL-USDT,TNB-ETH,BIX-ETH,SHE-BTC,PNT-BTC,BTC-USDT,PORTAL-ETH,WAVES-USDT,XZC-ETH,HT-ETH,POLY-BTC,MCO-ETH,MUSK-ETH,PAI-ETH,LXT-USDT,UTK-BTC,RTE-BTC,NCC-ETH,HB10-USDT,BOX-BTC,RDN-ETH,ARPA-BTC,LBA-ETH,CNN-ETH,AAC-ETH,XTZ-BTC,IDT-BTC,AKRO-USDT,IOST-BTC,GT-USDT,WAN-ETH,ETN-ETH,PVT-USDT,NEO-BTC,WAVES-ETH,ONE-USDT,ZEC-BTC,SKM-HT,IOST-ETH,NPXS-ETH,CVC-ETH,CMT-BTC,COVA-ETH,ARDR-ETH,RDN-BTC,DCR-BTC,REN-BTC,YCC-ETH,MX-HT,NEXO-ETH,XLM-ETH,YCC-BTC,ENG-BTC,CNNS-BTC,ZLA-BTC,QSP-BTC,MAN-ETH,UUU-ETH,ETH-HUSD,RTE-ETH,ATP-HT,BTM-BTC,DAC-BTC,TOS-ETH,LAMB-USDT,DASH-HT,NPXS-BTC,NEW-BTC,FTT-BTC,EOS-HUSD,GRS-BTC,POWR-ETH,VET-USDT,AAC-BTC,MX-BTC,MTN-BTC,XVG-ETH,GNX-ETH,SSP-BTC,WAVES-BTC,EGT-BTC,CTXC-ETH,IDT-ETH,STK-BTC,WICC-BTC,UTK-ETH,CRO-HT,LXT-BTC,GSC-ETH,OMG-BTC,XRP-HT,DGB-BTC,IOST-USDT,CVNT-ETH,GAS-BTC,HIT-BTC,CKB-USDT,ARPA-HT,RUFF-USDT,HC-BTC,WTC-ETH,MDS-USDT,ABT-ETH,ALGO-ETH,BIFI-BTC,KNC-BTC,TT-BTC,LET-BTC,NKN-USDT,PAY-BTC,DTA-USDT,AE-BTC,UC-BTC,VSYS-USDT,USDT-HUSD,EOS-BTC,STEEM-BTC,DOGE-BTC,NODE-HT,MDS-ETH,CRE-BTC,GNT-USDT,UIP-ETH,AST-BTC,XEM-BTC,ZEN-ETH,EDU-ETH,MEX-ETH,EKT-BTC,CVC-USDT,WAXP-ETH,REQ-ETH,OST-BTC,STORJ-USDT,SBTC-BTC,DGD-ETH,SC-ETH,WTC-USDT,THETA-BTC,DTA-ETH,BCV-BTC,SNC-BTC,RSR-HT,KAN-BTC,ELA-BTC,ATOM-BTC,BKBT-BTC,FSN-USDT,EM-USDT,WPR-BTC,TOP-BTC,BTS-BTC,EGCC-ETH,MTL-BTC,GNT-ETH,SEELE-BTC,EVX-BTC,FTI-ETH,BAT-USDT,MT-HT,LOL-HT,ICX-ETH,LOOM-BTC,ZJLT-BTC,XLM-USDT,OGO-BTC,DOCK-BTC,CHAT-ETH,DAT-ETH,ETC-USDT,HPT-BTC,BHT-HT" } - }, - "bankAccounts": [ - { - "enabled": false, - "bankName": "", - "bankAddress": "", - "bankPostalCode": "", - "bankPostalCity": "", - "bankCountry": "", - "accountName": "", - "accountNumber": "", - "swiftCode": "", - "iban": "", - "supportedCurrencies": "" - } - ] + } }, - { - "name": "Bybit", - "enabled": true, - "verbose": false, - "httpTimeout": 15000000000, - "websocketResponseCheckTimeout": 30000000, - "websocketResponseMaxLimit": 7000000000, - "websocketTrafficTimeout": 30000000000, - "websocketOrderbookBufferLimit": 5, - "baseCurrencies": "USD", - "currencyPairs": { - "assetTypes": [ - "spot", - "margin", - "coinmarginedfutures", - "usdtmarginedfutures", - "usdcmarginedfutures", - "options" - ], - "pairs": { - "spot": { - "enabled": "BTC_USDT,ETH_USDT,XRP_USDT,EOS_USDT,ETH_BTC,XRP_BTC,DOT_USDT,XLM_USDT,LTC_USDT", - "available": "BTC_USDT,ETH_USDT,XRP_USDT,EOS_USDT,ETH_BTC,XRP_BTC,DOT_USDT,XLM_USDT,LTC_USDT,DOGE_USDT,CHZ_USDT,AXS_USDT,MANA_USDT,DYDX_USDT,MKR_USDT,COMP_USDT,AAVE_USDT,YFI_USDT,LINK_USDT,SUSHI_USDT,UNI_USDT,KSM_USDT,ICP_USDT,ADA_USDT,ETC_USDT,KLAY_USDT,XTZ_USDT,BCH_USDT,SRM_USDT,QNT_USDT,USDC_USDT,GRT_USDT,SOL_USDT,FIL_USDT,OMG_USDT,TRIBE_USDT,BAT_USDT,ZRX_USDT,CRV_USDT,AGLD_USDT,ANKR_USDT,PERP_USDT,MATIC_USDT,WAVES_USDT,LUNC_USDT,SPELL_USDT,SHIB_USDT,FTM_USDT,ATOM_USDT,ALGO_USDT,ENJ_USDT,CBX_USDT,SAND_USDT,AVAX_USDT,WOO_USDT,FTT_USDT,GODS_USDT,IMX_USDT,ENS_USDT,GM_USDT,CWAR_USDT,CAKE_USDT,STETH_USDT,GALFT_USDT,LFW_USDT,SLP_USDT,C98_USDT,PSP_USDT,GENE_USDT,AVA_USDT,ONE_USDT,PTU_USDT,SHILL_USDT,XYM_USDT,BOBA_USDT,JASMY_USDT,GALA_USDT,RNDR_USDT,TRVL_USDT,WEMIX_USDT,XEM_USDT,BICO_USDT,CEL_USDT,UMA_USDT,HOT_USDT,NEXO_USDT,BNT_USDT,SNX_USDT,REN_USDT,1INCH_USDT,TEL_USDT,SIS_USDT,LRC_USDT,LDO_USDT,REAL_USDT,KRL_USDT,DEVT_USDT,ETH_USDC,BTC_USDC,1SOL_USDT,PLT_USDT,IZI_USDT,QTUM_USDT,DCR_USDT,ZEN_USDT,THETA_USDT,MX_USDT,DGB_USDT,RVN_USDT,EGLD_USDT,RUNE_USDT,XLM_BTC,XLM_USDC,SOL_USDC,XRP_USDC,ALGO_BTC,SOL_BTC,RAIN_USDT,XEC_USDT,ICX_USDT,XDC_USDT,HNT_USDT,BTG_USDT,ZIL_USDT,HBAR_USDT,FLOW_USDT,SOS_USDT,KASTA_USDT,STX_USDT,SIDUS_USDT,VPAD_USDT,GGM_USDT,LOOKS_USDT,MBS_USDT,DAI_USDT,BUSD_USDT,ACA_USDT,MV_USDT,MIX_USDT,LTC_USDC,MANA_BTC,MATIC_BTC,LTC_BTC,DOT_BTC,SAND_BTC,MANA_USDC,MATIC_USDC,SAND_USDC,DOT_USDC,LUNC_USDC,RSS3_USDT,SYNR_USDT,TAP_USDT,ERTHA_USDT,GMX_USDT,T_USDT,ACH_USDT,JST_USDT,SUN_USDT,BTT_USDT,TRX_USDT,NFT_USDT,POKT_USDT,SCRT_USDT,PSTAKE_USDT,SON_USDT,HERO_USDT,DOME_USDT,USTC_USDT,BNB_USDT,NEAR_USDT,PAXG_USDT,SD_USDT,APE_USDT,BTC3S_USDT,BTC3L_USDT,FIDA_USDT,MINA_USDT,SC_USDT,RACA_USDT,CAPS_USDT,STG_USDT,GLMR_USDT,MOVR_USDT,ZAM_USDT,ETH_DAI,BTC_DAI,WBTC_USDT,XAVA_USDT,MELOS_USDT,GMT_USDT,GST_USDT,CELO_USDT,SFUND_USDT,ELT_USDT,LGX_USDT,APEX_USDT,CTC_USDT,COT_USDT,KMON_USDT,PLY_USDT,XWG_USDT,FITFI_USDT,STRM_USDT,GAL_USDT,ETH3S_USDT,ETH3L_USDT,KOK_USDT,FAME_USDT,XRP3S_USDT,XRP3L_USDT,USDD_USDT,OP_USDT,LUNA_USDT,DFI_USDT,MOVEZ_USDT,THN_USDT,DOT3S_USDT,DOT3L_USDT,VINU_USDT,BEL_USDT,FORT_USDT,AVAX2S_USDT,AVAX2L_USDT,ADA2S_USDT,ADA2L_USDT,WLKN_USDT,KON_USDT,LTC2S_USDT,LTC2L_USDT,SAND2S_USDT,SAND2L_USDT,OBX_USDT,SEOR_USDT,MNZ_USDT,CULT_USDT,DOGE_USDC,EOS_USDC,CUSD_USDT,SLG_USDT,CMP_USDT,KUNCI_USDT,GSTS_USDT,XETA_USDT,AZY_USDT,MMC_USDT,FLOKI_USDT,BABYDOGE_USDT,STAT_USDT,SAITAMA_USDT,MATIC2S_USDT,MATIC2L_USDT,ETC2S_USDT,ETC2L_USDT,DICE_USDT,WAXP_USDT,AR_USDT,KDA_USDT,ROSE_USDT,SLG_USDC,APE2S_USDT,APE2L_USDT,GMT2S_USDT,GMT2L_USDT,DEFY_USDT,PSG_USDT,BAR_USDT,JUV_USDT,ACM_USDT,INTER_USDT,AFC_USDT,CITY_USDT,LINK2L_USDT,LINK2S_USDT,FTM2L_USDT,FTM2S_USDT,SOLO_USDT,W_BTC,AVAX_USDC,ADA_USDC,OP_USDC,DOGE2S_USDT,DOGE2L_USDT,ATOM2S_USDT,ATOM2L_USDT,APEX_USDC,TRX_USDC,ICP_USDC,LINK_USDC,GMT_USDC,CHZ_USDC,SHIB_USDC,LDO_USDC,APE_USDC,FIL_USDC,CHRP_USDT,EOS2S_USDT,EOS2L_USDT,WWY_USDT,LING_USDT,SWEAT_USDT,DLC_USDT,OKG_USDT,ETHW_USDT,INJ_USDT,MPLX_USDT,MIBR_USDT,CO_USDT,AGLA_USDT,ROND_USDT,QMALL_USDT,PUMLX_USDT,GCAKE_USDT,APT_USDT,APT_USDC,USDT_EUR,MTK_USDT,MCRT_USDT,MASK_USDT,ECOX_USDT,HFT_USDC,HFT_USDT,KCAL_USDT,PEOPLE_USDT,TWT_USDT,ORT_USDT,HOOK_USDT,PRIMAL_USDT,MCT_USDT,OAS_USDT,MAGIC_USDT,MEE_USDT,TON_USDT,BONK_USDT,FLR_USDT,TIME_USDT,3P_USDT,RPL_USDT,SSV_USDT,FXS_USDT,CORE_USDT,RDNT_USDT,BLUR_USDT,LIS_USDT,AGIX_USDT,MDAO_USDT,ACS_USDT,HVH_USDT,GNS_USDT,DPX_USDT,PIP_USDT,PRIME_USDT,EVER_USDT,VRA_USDT,GPT_USDT,FB_USDT,DZOO_USDT,ID_USDT,ARB_USDC,ARB_USDT,XCAD_USDT,MBX_USDT,AXL_USDT,CGPT_USDT,PLAY_USDT,AGI_USDT,RLTM_USDT,SUI_USDT,SUI_USDC,TAMA_USDT,MVL_USDT,PEPE_USDT,LADYS_USDT,LMWR_USDT,BOB_USDT,TOMI_USDT,KARATE_USDT,SUIA_USDT,TURBOS_USDT,FMB_USDT,CAPO_USDT,TENET_USDT,VELO_USDT,ELDA_USDT,CANDY_USDT,FON_USDT,OMN_USDT,TOMS_USDT,MTC_USDT,VELA_USDT,USDT_BRZ,BTC_BRZ,PENDLE_USDT,EGO_USDT,PEPE2_USDT,NYM_USDT,MNT_USDT,MNT_USDC,MNT_BTC,GSWIFT_USDT,SALD_USDT,ARKM_USDT,NEON_USDT,WLD_USDC,WLD_USDT,PLANET_USDT,DSRUN_USDT,SPARTA_USDT,TAVA_USDT,SEILOR_USDT,SEI_USDT,CYBER_USDT,ORDI_USDT,KAVA_USDT,VV_USDT,SAIL_USDT,PYUSD_USDT,SOL_EUR,USDC_EUR,ADA_EUR,DOGE_EUR,LTC_EUR,XRP_EUR,ETH_EUR,BTC_EUR,VEXT_USDT,CTT_USDT,NEXT_USDT,KAS_USDT,NESS_USDT,CAT_USDT,FET_USDT,LEVER_USDT,VEGA_USDT,ZTX_USDT", - "requestFormat": { - "uppercase": true - }, - "configFormat": { - "uppercase": true, - "delimiter": "_" - } - }, - "coinmarginedfutures": { - "enabled": "ADA_USD,BTC_USD,BTC_USDH24,BTC_USDZ23,DOT_USD", - "available": "ADA_USD,BTC_USD,BTC_USDH24,BTC_USDZ23,DOT_USD,EOS_USD,ETH_USD,ETH_USDH24,ETH_USDZ23,LTC_USD,MAN_AUSD,XRP_USD", - "requestFormat": { - "uppercase": true - }, - "configFormat": { - "uppercase": true, - "delimiter": "_" - } - }, - "usdcmarginedfutures": { - "enabled": "ETH-PERP,BNB-PERP,SOL-PERP,BTC-PERP", - "available": "BNB-PERP,BTC-03NOV23,BTC-20OCT23,BTC-24NOV23,BTC-27OCT23,BTC-28JUN24,BTC-29DEC23,BTC-29MAR24,BTC-PERP,ETC-PERP,ETH-03NOV23,ETH-20OCT23,ETH-24NOV23,ETH-27OCT23,ETH-28JUN24,ETH-29DEC23,ETH-29MAR24,ETH-PERP,MAT-ICPERP,OPP-ERP,SOL-PERP,XRP-PERP", - "requestFormat": { - "uppercase": true - }, - "configFormat": { - "uppercase": true, - "delimiter": "-" - } - }, - "usdtmarginedfutures": { - "enabled": "BTC_USDT,10000LADYS_USDT,IOTA_USDT,AAVE_USDT", - "available": "10000LADYS_USDT,10000NFT_USDT,1000BONK_USDT,1000BTT_USDT,1000FLOKI_USDT,1000LUNC_USDT,1000PEPE_USDT,1000XEC_USDT,1INCH_USDT,AAVE_USDT,ACH_USDT,ADA_USDT,AGIX_USDT,AGLD_USDT,AKRO_USDT,ALGO_USDT,ALICE_USDT,ALPACA_USDT,ALPHA_USDT,AMB_USDT,ANKR_USDT,ANT_USDT,APE_USDT,API3_USDT,APT_USDT,ARB_USDT,ARKM_USDT,ARK_USDT,ARPA_USDT,AR_USDT,ASTR_USDT,ATA_USDT,ATOM_USDT,AUCTION_USDT,AUDIO_USDT,AVAX_USDT,AXS_USDT,BADGER_USDT,BAKE_USDT,BAL_USDT,BAND_USDT,BAT_USDT,BCH_USDT,BEL_USDT,BICO_USDT,BIGTIME_USDT,BLUR_USDT,BLZ_USDT,BNB_USDT,BNT_USDT,BNX_USDT,BOBA_USDT,BOND_USDT,BSV_USDT,BSW_USDT,BTC_USDT,BUSD_USDT,C98_USDT,CEEK_USDT,CELO_USDT,CELR_USDT,CFX_USDT,CHR_USDT,CHZ_USDT,CKB_USDT,COMBO_USDT,COMP_USDT,CORE_USDT,COTI_USDT,CRO_USDT,CRV_USDT,CTC_USDT,CTK_USDT,CTSI_USDT,CVC_USDT,CVX_USDT,CYBER_USDT,DAR_USDT,DASH_USDT,DENT_USDT,DGB_USDT,DODO_USDT,DOGE_USDT,DOT_USDT,DUSK_USDT,DYDX_USDT,EDU_USDT,EGLD_USDT,ENJ_USDT,ENS_USDT,EOS_USDT,ETC_USDT,ETH_USDT,ETHW_USDT,FET_USDT,FIL_USDT,FITFI_USDT,FLM_USDT,FLOW_USDT,FLR_USDT,FORTH_USDT,FRONT_USDT,FTM_USDT,FXS_USDT,GALA_USDT,GAL_USDT,GFT_USDT,GLMR_USDT,GLM_USDT,GMT_USDT,GMX_USDT,GPT_USDT,GRT_USDT,GTC_USDT,HBAR_USDT,HFT_USDT,HIFI_USDT,HIGH_USDT,HNT_USDT,HOOK_USDT,HOT_USDT,ICP_USDT,ICX_USDT,IDEX_USDT,ID_USDT,ILV_USDT,IMX_USDT,INJ_USDT,IOST_USDT,IOTA_USDT,IOTX_USDT,JASMY_USDT,JOE_USDT,JST_USDT,KAS_USDT,KAVA_USDT,KDA_USDT,KEY_USDT,KLAY_USDT,KNC_USDT,KSM_USDT,LDO_USDT,LEVER_USDT,LINA_USDT,LINK_USDT,LIT_USDT,LOOKS_USDT,LOOM_USDT,LPT_USDT,LQTY_USDT,LRC_USDT,LTC_USDT,LUNA2_USDT,MAGIC_USDT,MANA_USDT,MASK_USDT,MATIC_USDT,MAV_USDT,MC_USDT,MDT_USDT,MINA_USDT,MKR_USDT,MNT_USDT,MTL_USDT,MULTI_USDT,NEAR_USDT,NEO_USDT,NKN_USDT,NMR_USDT,NTRN_USDT,OCEAN_USDT,OGN_USDT,OG_USDT,OMG_USDT,ONE_USDT,ONT_USDT,OP_USDT,ORBS_USDT,ORDI_USDT,OXT_USDT,PAXG_USDT,PENDLE_USDT,PEOPLE_USDT,PERP_USDT,PHB_USDT,PROM_USDT,QNT_USDT,QTUM_USDT,RAD_USDT,RDNT_USDT,REEF_USDT,REN_USDT,REQ_USDT,RLC_USDT,RNDR_USDT,ROSE_USDT,RPL_USDT,RSR_USDT,RSS3_USDT,RUNE_USDT,RVN_USDT,SAND_USDT,SCRT_USDT,SC_USDT,SEI_USDT,SFP_USDT,SHIB1000_USDT,SKL_USDT,SLP_USDT,SNX_USDT,SOL_USDT,SPELL_USDT,SSV_USDT,STG_USDT,STMX_USDT,STORJ_USDT,STPT_USDT,STRAX_USDT,STX_USDT,SUI_USDT,SUN_USDT,SUSHI_USDT,SWEAT_USDT,SXP_USDT,THETA_USDT,TLM_USDT,TOMI_USDT,TOMO_USDT,TON_USDT,TRB_USDT,TRU_USDT,TRX_USDT,T_USDT,TWT_USDT,UMA_USDT,UNFI_USDT,UNI_USDT,USDC_USDT,VET_USDT,VGX_USDT,VRA_USDT,WAVES_USDT,WAXP_USDT,WLD_USDT,WOO_USDT,WSM_USDT,XCN_USDT,XEM_USDT,XLM_USDT,XMR_USDT,XNO_USDT,XRP_USDT,XTZ_USDT,XVG_USDT,XVS_USDT,YFII_USDT,YFI_USDT,YGG_USDT,ZEC_USDT,ZEN_USDT,ZIL_USDT,ZRX_USDT", - "requestFormat": { - "uppercase": true - }, - "configFormat": { - "uppercase": true, - "delimiter": "_" - } + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "tickerBatching": true, + "autoPairUpdates": true + }, + "websocketAPI": true, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": false + } + }, + "bankAccounts": [ + { + "enabled": false, + "bankName": "", + "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ] + }, + { + "name": "Kraken", + "enabled": true, + "verbose": false, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, + "websocketOrderbookBufferLimit": 5, + "baseCurrencies": "EUR,USD,CAD,GBP,JPY", + "currencyPairs": { + "requestFormat": { + "uppercase": true, + "separator": "," + }, + "configFormat": { + "uppercase": true, + "delimiter": "-", + "separator": "," + }, + "useGlobalFormat": true, + "assetTypes": [ + "spot" + ], + "pairs": { + "futures": { + "assetEnabled": true, + "enabled": "PF_XBTUSD", + "available": "PI_XBTUSD,PI_ETHUSD,PI_LTCUSD,PI_BCHUSD,PI_XRPUSD,PF_XBTUSD,PF_ETHUSD,PF_ADAUSD,PF_XRPUSD,PF_SOLUSD,PF_UNIUSD,PF_ATOMUSD,PF_BCHUSD,PF_DOTUSD,PF_EOSUSD,PF_FILUSD,PF_LINKUSD,PF_LTCUSD,PF_MATICUSD,PF_DEFIUSD,PF_AVAXUSD,PF_XMRUSD,PF_GMTUSD,PF_LUNA2USD,PF_OPUSD,PF_NEARUSD,PF_APEUSD,PF_WAVESUSD,PF_DOGEUSD,PF_FTMUSD,PF_TRXUSD,PF_MANAUSD,PF_CRVUSD,PF_AAVEUSD,PF_SNXUSD,PF_XTZUSD,PF_XLMUSD,PF_ALGOUSD,PF_SANDUSD,PF_OMGUSD,PF_ENJUSD,PF_COMPUSD,PF_YFIUSD,PF_CHZUSD,PF_LPTUSD,PF_BATUSD,PF_MKRUSD,PF_AXSUSD,PF_GALAUSD,PF_ETCUSD,PF_KAVAUSD,PF_LRCUSD,PF_KSMUSD,PF_GRTUSD,PF_FLOWUSD,PF_ZECUSD,PF_QTUMUSD,PF_DASHUSD,PF_1INCHUSD,PF_KNCUSD,PF_OGNUSD,PF_SUSHIUSD,PF_STORJUSD,PF_RUNEUSD,PF_EGLDUSD,PF_DYDXUSD,PF_RENUSD,PF_ANKRUSD,PF_ICPUSD,PF_ETHWUSD,PF_OCEANUSD,PF_BANDUSD,PF_BALUSD,PF_ALICEUSD,PF_ICXUSD,PF_ENSUSD,PF_AUDIOUSD,PF_ANTUSD,PF_SCUSD,PF_MINAUSD,PF_GLMRUSD,PF_THETAUSD,PF_QNTUSD,PF_IMXUSD,PF_APTUSD,PF_FLRUSD,PF_BLURUSD,PF_GMXUSD,PF_MASKUSD,PF_LDOUSD,PF_USDCUSD,PF_USDTUSD,PF_ARBUSD,PF_FETUSD,PF_STXUSD,PF_RNDRUSD,PF_CVXUSD,PF_WOOUSD,PF_JASMYUSD,PF_INJUSD,PF_ZRXUSD,PF_RLCUSD,PF_GALUSD,PF_SUIUSD,PF_PEPEUSD,FI_XBTUSD_231229,FI_ETHUSD_231229,PF_SHIBUSD,PF_TUSDUSD,PF_SXPUSD,PF_ARPAUSD,PF_ALPHAUSD,PF_STGUSD,PF_HFTUSD,PF_ACHUSD,PF_WLDUSD,PF_MOONUSD,PF_LINAUSD,PF_CFXUSD,PF_PAXGUSD,PF_AGLDUSD,FF_ETHUSD_231229,FF_XBTUSD_231229,FI_ETHUSD_240329,FI_XBTUSD_240329,FI_XRPUSD_231229,FI_BCHUSD_231229,FI_LTCUSD_231229,PF_FXSUSD,PF_SEIUSD,FF_XBTUSD_231027,FF_ETHUSD_231027,FI_XBTUSD_231027,FI_ETHUSD_231027,FI_XRPUSD_231027,FI_BCHUSD_231027,FI_LTCUSD_231027", + "requestFormat": { + "uppercase": true, + "delimiter": "_" }, - "options": { - "enabled": "BTC-28JUN24-70000-C,BTC-28JUN24-70000-P,BTC-28JUN24-60000-C,BTC-28JUN24-60000-P", - "available": "BTC-28JUN24-70000-C,BTC-28JUN24-70000-P,BTC-28JUN24-60000-C,BTC-28JUN24-60000-P,BTC-28JUN24-50000-C,BTC-28JUN24-50000-P,BTC-28JUN24-40000-C,BTC-28JUN24-40000-P,BTC-28JUN24-32000-C,BTC-28JUN24-32000-P,BTC-28JUN24-30000-C,BTC-28JUN24-30000-P,BTC-28JUN24-28000-C,BTC-28JUN24-28000-P,BTC-28JUN24-25000-C,BTC-28JUN24-25000-P,BTC-28JUN24-20000-C,BTC-28JUN24-20000-P,BTC-28JUN24-10000-C,BTC-28JUN24-10000-P,BTC-29MAR24-70000-C,BTC-29MAR24-70000-P,BTC-29MAR24-60000-C,BTC-29MAR24-60000-P,BTC-29MAR24-50000-C,BTC-29MAR24-50000-P,BTC-29MAR24-45000-C,BTC-29MAR24-45000-P,BTC-29MAR24-40000-C,BTC-29MAR24-40000-P,BTC-29MAR24-36000-C,BTC-29MAR24-36000-P,BTC-29MAR24-35000-C,BTC-29MAR24-35000-P,BTC-29MAR24-33000-C,BTC-29MAR24-33000-P,BTC-29MAR24-31000-C,BTC-29MAR24-31000-P,BTC-29MAR24-30000-C,BTC-29MAR24-30000-P,BTC-29MAR24-28000-C,BTC-29MAR24-28000-P,BTC-29MAR24-27000-C,BTC-29MAR24-27000-P,BTC-29MAR24-26000-C,BTC-29MAR24-26000-P,BTC-29MAR24-24000-C,BTC-29MAR24-24000-P,BTC-29MAR24-20000-C,BTC-29MAR24-20000-P,BTC-29MAR24-10000-C,BTC-29MAR24-10000-P,BTC-29DEC23-80000-C,BTC-29DEC23-80000-P,BTC-29DEC23-70000-C,BTC-29DEC23-70000-P,BTC-29DEC23-60000-C,BTC-29DEC23-60000-P,BTC-29DEC23-50000-C,BTC-29DEC23-50000-P,BTC-29DEC23-40000-C,BTC-29DEC23-40000-P,BTC-29DEC23-36000-C,BTC-29DEC23-36000-P,BTC-29DEC23-35000-C,BTC-29DEC23-35000-P,BTC-29DEC23-34000-C,BTC-29DEC23-34000-P,BTC-29DEC23-32000-C,BTC-29DEC23-32000-P,BTC-29DEC23-31500-C,BTC-29DEC23-31500-P,BTC-29DEC23-30500-C,BTC-29DEC23-30500-P,BTC-29DEC23-30000-C,BTC-29DEC23-30000-P,BTC-29DEC23-29500-C,BTC-29DEC23-29500-P,BTC-29DEC23-29000-C,BTC-29DEC23-29000-P,BTC-29DEC23-28000-C,BTC-29DEC23-28000-P,BTC-29DEC23-27500-C,BTC-29DEC23-27500-P,BTC-29DEC23-27000-C,BTC-29DEC23-27000-P,BTC-29DEC23-26000-C,BTC-29DEC23-26000-P,BTC-29DEC23-25000-C,BTC-29DEC23-25000-P,BTC-29DEC23-24000-C,BTC-29DEC23-24000-P,BTC-29DEC23-22000-C,BTC-29DEC23-22000-P,BTC-29DEC23-20000-C,BTC-29DEC23-20000-P,BTC-29DEC23-15000-C,BTC-29DEC23-15000-P,BTC-29DEC23-10000-C,BTC-29DEC23-10000-P,BTC-24NOV23-40000-C,BTC-24NOV23-40000-P,BTC-24NOV23-38000-C,BTC-24NOV23-38000-P,BTC-24NOV23-36000-C,BTC-24NOV23-36000-P,BTC-24NOV23-34000-C,BTC-24NOV23-34000-P,BTC-24NOV23-32000-C,BTC-24NOV23-32000-P,BTC-24NOV23-31500-C,BTC-24NOV23-31500-P,BTC-24NOV23-30500-C,BTC-24NOV23-30500-P,BTC-24NOV23-30000-C,BTC-24NOV23-30000-P,BTC-24NOV23-29500-C,BTC-24NOV23-29500-P,BTC-24NOV23-29000-C,BTC-24NOV23-29000-P,BTC-24NOV23-28500-C,BTC-24NOV23-28500-P,BTC-24NOV23-28000-C,BTC-24NOV23-28000-P,BTC-24NOV23-27500-C,BTC-24NOV23-27500-P,BTC-24NOV23-27000-C,BTC-24NOV23-27000-P,BTC-24NOV23-26500-C,BTC-24NOV23-26500-P,BTC-24NOV23-26000-C,BTC-24NOV23-26000-P,BTC-24NOV23-25500-C,BTC-24NOV23-25500-P,BTC-24NOV23-25000-C,BTC-24NOV23-25000-P,BTC-24NOV23-24000-C,BTC-24NOV23-24000-P,BTC-24NOV23-23000-C,BTC-24NOV23-23000-P,BTC-24NOV23-22000-C,BTC-24NOV23-22000-P,BTC-24NOV23-20000-C,BTC-24NOV23-20000-P,BTC-24NOV23-18000-C,BTC-24NOV23-18000-P,BTC-24NOV23-16000-C,BTC-24NOV23-16000-P,BTC-3NOV23-36000-C,BTC-3NOV23-36000-P,BTC-3NOV23-34000-C,BTC-3NOV23-34000-P,BTC-3NOV23-32000-C,BTC-3NOV23-32000-P,BTC-3NOV23-30000-C,BTC-3NOV23-30000-P,BTC-3NOV23-29000-C,BTC-3NOV23-29000-P,BTC-3NOV23-28500-C,BTC-3NOV23-28500-P,BTC-3NOV23-27500-C,BTC-3NOV23-27500-P,BTC-3NOV23-27000-C,BTC-3NOV23-27000-P,BTC-3NOV23-26500-C,BTC-3NOV23-26500-P,BTC-3NOV23-26000-C,BTC-3NOV23-26000-P,BTC-3NOV23-25000-C,BTC-3NOV23-25000-P,BTC-3NOV23-24000-C,BTC-3NOV23-24000-P,BTC-3NOV23-22000-C,BTC-3NOV23-22000-P,BTC-3NOV23-20000-C,BTC-3NOV23-20000-P,BTC-3NOV23-18000-C,BTC-3NOV23-18000-P,BTC-27OCT23-44000-C,BTC-27OCT23-44000-P,BTC-27OCT23-42000-C,BTC-27OCT23-42000-P,BTC-27OCT23-40000-C,BTC-27OCT23-40000-P,BTC-27OCT23-38000-C,BTC-27OCT23-38000-P,BTC-27OCT23-37000-C,BTC-27OCT23-37000-P,BTC-27OCT23-35000-C,BTC-27OCT23-35000-P,BTC-27OCT23-34500-C,BTC-27OCT23-34500-P,BTC-27OCT23-33500-C,BTC-27OCT23-33500-P,BTC-27OCT23-32500-C,BTC-27OCT23-32500-P,BTC-27OCT23-31500-C,BTC-27OCT23-31500-P,BTC-27OCT23-31000-C,BTC-27OCT23-31000-P,BTC-27OCT23-30500-C,BTC-27OCT23-30500-P,BTC-27OCT23-30000-C,BTC-27OCT23-30000-P,BTC-27OCT23-29500-C,BTC-27OCT23-29500-P,BTC-27OCT23-29000-C,BTC-27OCT23-29000-P,BTC-27OCT23-28750-C,BTC-27OCT23-28750-P,BTC-27OCT23-28500-C,BTC-27OCT23-28500-P,BTC-27OCT23-28250-C,BTC-27OCT23-28250-P,BTC-27OCT23-28000-C,BTC-27OCT23-28000-P,BTC-27OCT23-27750-C,BTC-27OCT23-27750-P,BTC-27OCT23-27500-C,BTC-27OCT23-27500-P,BTC-27OCT23-27250-C,BTC-27OCT23-27250-P,BTC-27OCT23-27000-C,BTC-27OCT23-27000-P,BTC-27OCT23-26500-C,BTC-27OCT23-26500-P,BTC-27OCT23-26000-C,BTC-27OCT23-26000-P,BTC-27OCT23-25500-C,BTC-27OCT23-25500-P,BTC-27OCT23-25000-C,BTC-27OCT23-25000-P,BTC-27OCT23-24000-C,BTC-27OCT23-24000-P,BTC-27OCT23-23000-C,BTC-27OCT23-23000-P,BTC-27OCT23-22000-C,BTC-27OCT23-22000-P,BTC-27OCT23-20000-C,BTC-27OCT23-20000-P,BTC-27OCT23-18000-C,BTC-27OCT23-18000-P,BTC-27OCT23-16000-C,BTC-27OCT23-16000-P,BTC-20OCT23-36000-C,BTC-20OCT23-36000-P,BTC-20OCT23-34000-C,BTC-20OCT23-34000-P,BTC-20OCT23-32000-C,BTC-20OCT23-32000-P,BTC-20OCT23-31000-C,BTC-20OCT23-31000-P,BTC-20OCT23-30500-C,BTC-20OCT23-30500-P,BTC-20OCT23-30000-C,BTC-20OCT23-30000-P,BTC-20OCT23-29500-C,BTC-20OCT23-29500-P,BTC-20OCT23-29000-C,BTC-20OCT23-29000-P,BTC-20OCT23-28750-C,BTC-20OCT23-28750-P,BTC-20OCT23-28500-C,BTC-20OCT23-28500-P,BTC-20OCT23-28250-C,BTC-20OCT23-28250-P,BTC-20OCT23-28000-C,BTC-20OCT23-28000-P,BTC-20OCT23-27750-C,BTC-20OCT23-27750-P,BTC-20OCT23-27500-C,BTC-20OCT23-27500-P,BTC-20OCT23-27250-C,BTC-20OCT23-27250-P,BTC-20OCT23-27000-C,BTC-20OCT23-27000-P,BTC-20OCT23-26750-C,BTC-20OCT23-26750-P,BTC-20OCT23-26500-C,BTC-20OCT23-26500-P,BTC-20OCT23-26250-C,BTC-20OCT23-26250-P,BTC-20OCT23-26000-C,BTC-20OCT23-26000-P,BTC-20OCT23-25750-C,BTC-20OCT23-25750-P,BTC-20OCT23-25500-C,BTC-20OCT23-25500-P,BTC-20OCT23-25000-C,BTC-20OCT23-25000-P,BTC-20OCT23-24000-C,BTC-20OCT23-24000-P,BTC-20OCT23-22000-C,BTC-20OCT23-22000-P,BTC-20OCT23-20000-C,BTC-20OCT23-20000-P,BTC-20OCT23-18000-C,BTC-20OCT23-18000-P,BTC-19OCT23-29750-C,BTC-19OCT23-29750-P,BTC-19OCT23-29500-C,BTC-19OCT23-29500-P,BTC-19OCT23-29250-C,BTC-19OCT23-29250-P,BTC-19OCT23-29000-C,BTC-19OCT23-29000-P,BTC-19OCT23-28750-C,BTC-19OCT23-28750-P,BTC-19OCT23-28500-C,BTC-19OCT23-28500-P,BTC-19OCT23-28250-C,BTC-19OCT23-28250-P,BTC-19OCT23-28000-C,BTC-19OCT23-28000-P,BTC-19OCT23-27750-C,BTC-19OCT23-27750-P,BTC-19OCT23-27500-C,BTC-19OCT23-27500-P,BTC-19OCT23-27250-C,BTC-19OCT23-27250-P,BTC-19OCT23-27000-C,BTC-19OCT23-27000-P,BTC-19OCT23-26750-C,BTC-19OCT23-26750-P,BTC-19OCT23-26500-C,BTC-19OCT23-26500-P,BTC-19OCT23-26250-C,BTC-19OCT23-26250-P,BTC-19OCT23-26000-C,BTC-19OCT23-26000-P,BTC-19OCT23-25750-C,BTC-19OCT23-25750-P,BTC-18OCT23-29500-C,BTC-18OCT23-29500-P,BTC-18OCT23-29250-C,BTC-18OCT23-29250-P,BTC-18OCT23-29000-C,BTC-18OCT23-29000-P,BTC-18OCT23-28750-C,BTC-18OCT23-28750-P,BTC-18OCT23-28500-C,BTC-18OCT23-28500-P,BTC-18OCT23-28250-C,BTC-18OCT23-28250-P,BTC-18OCT23-28000-C,BTC-18OCT23-28000-P,BTC-18OCT23-27750-C,BTC-18OCT23-27750-P,BTC-18OCT23-27500-C,BTC-18OCT23-27500-P,BTC-18OCT23-27250-C,BTC-18OCT23-27250-P,BTC-18OCT23-27000-C,BTC-18OCT23-27000-P,BTC-18OCT23-26750-C,BTC-18OCT23-26750-P,BTC-18OCT23-26500-C,BTC-18OCT23-26500-P,BTC-18OCT23-26250-C,BTC-18OCT23-26250-P,BTC-18OCT23-26000-C,BTC-18OCT23-26000-P,BTC-18OCT23-25750-C,BTC-18OCT23-25750-P,BTC-18OCT23-25500-C,BTC-18OCT23-25500-P,BTC-17OCT23-29250-C,BTC-17OCT23-29250-P,BTC-17OCT23-29000-C,BTC-17OCT23-29000-P,BTC-17OCT23-28750-C,BTC-17OCT23-28750-P,BTC-17OCT23-28500-C,BTC-17OCT23-28500-P,BTC-17OCT23-28250-C,BTC-17OCT23-28250-P,BTC-17OCT23-28000-C,BTC-17OCT23-28000-P,BTC-17OCT23-27750-C,BTC-17OCT23-27750-P,BTC-17OCT23-27500-C,BTC-17OCT23-27500-P,BTC-17OCT23-27250-C,BTC-17OCT23-27250-P,BTC-17OCT23-27000-C,BTC-17OCT23-27000-P,BTC-17OCT23-26750-C,BTC-17OCT23-26750-P,BTC-17OCT23-26500-C,BTC-17OCT23-26500-P,BTC-17OCT23-26250-C,BTC-17OCT23-26250-P,BTC-17OCT23-26000-C,BTC-17OCT23-26000-P,BTC-17OCT23-25750-C,BTC-17OCT23-25750-P,BTC-17OCT23-25500-C,BTC-17OCT23-25500-P", - "requestFormat": { - "uppercase": true, - "delimiter": "-" - }, - "configFormat": { - "uppercase": true, - "delimiter": "-" - } + "configFormat": { + "uppercase": true, + "delimiter": "_" } - } - }, - "api": { - "authenticatedSupport": false, - "authenticatedWebsocketApiSupport": false, - "endpoints": { - "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" - }, - "credentials": { - "key": "Key", - "secret": "Secret" }, - "credentialsValidator": { - "requiresKey": true, - "requiresSecret": true + "spot": { + "enabled": "XBT-USD", + "available": "ETH-GBP,XRP-USD,DAI-EUR,LSK-USD,BAT-EUR,BCH-EUR,EOS-ETH,GNO-EUR,ETH-CAD,XRP-JPY,ADA-ETH,DAI-USD,DASH-EUR,GNO-USD,LSK-XBT,ETH-EUR,ZEC-EUR,DASH-XBT,EOS-EUR,ETH-CHF,SC-ETH,SC-USD,WAVES-EUR,XBT-USD,ADA-EUR,LINK-USD,NANO-EUR,PAXG-USD,SC-EUR,WAVES-ETH,REP-USD,EOS-XBT,ETC-ETH,XMR-USD,LTC-USD,MLN-XBT,XTZ-CAD,XBT-GBP,ADA-CAD,XTZ-EUR,ETH-JPY,XTZ-USD,XDG-XBT,XLM-EUR,ATOM-USD,ATOM-XBT,OMG-EUR,ZEC-JPY,ADA-XBT,GNO-ETH,LINK-XBT,ETC-EUR,BCH-XBT,QTUM-ETH,XBT-CHF,LTC-EUR,ETH-DAI,LSK-EUR,NANO-USD,QTUM-XBT,XRP-XBT,ZEC-USD,BAT-ETH,LINK-ETH,XBT-CAD,BAT-USD,GNO-XBT,ICX-XBT,PAXG-ETH,DAI-USDT,NANO-ETH,OMG-ETH,WAVES-XBT,ZEC-XBT,BAT-XBT,NANO-XBT,XBT-JPY,DASH-USD,ICX-ETH,LSK-ETH,QTUM-CAD,REP-XBT,XMR-XBT,XRP-EUR,ATOM-CAD,OMG-USD,LTC-XBT,MLN-ETH,XTZ-ETH,EOS-USD,ICX-EUR,SC-XBT,ETC-USD,BCH-USD,ICX-USD,QTUM-USD,ETH-XBT,ETH-USD,OMG-XBT,PAXG-EUR,REP-EUR,ADA-USD,USDT-USD,XMR-EUR,XRP-CAD,ATOM-EUR,ETC-XBT,XBT-EUR,XLM-USD,ATOM-ETH,LINK-EUR,PAXG-XBT,WAVES-USD,REP-ETH,XLM-XBT,QTUM-EUR,XTZ-XBT" } - }, - "features": { - "supports": { - "restAPI": true, - "restCapabilities": { - "tickerBatching": true, - "autoPairUpdates": true - }, - "websocketAPI": true, - "websocketCapabilities": {} - }, - "enabled": { - "autoPairUpdates": true, - "websocketAPI": true - } - }, - "bankAccounts": [ - { - "enabled": false, - "bankName": "", - "bankAddress": "", - "bankPostalCode": "", - "bankPostalCity": "", - "bankCountry": "", - "accountName": "", - "accountNumber": "", - "swiftCode": "", - "iban": "", - "supportedCurrencies": "" - } - ] + } }, - { - "name": "COINUT", - "enabled": true, - "verbose": false, - "httpTimeout": 15000000000, - "websocketResponseCheckTimeout": 30000000, - "websocketResponseMaxLimit": 7000000000, - "websocketTrafficTimeout": 30000000000, - "websocketOrderbookBufferLimit": 5, - "baseCurrencies": "USD", - "currencyPairs": { - "requestFormat": { - "uppercase": true - }, - "configFormat": { - "uppercase": true, - "delimiter": "-" - }, - "useGlobalFormat": true, - "assetTypes": [ - "spot" - ], - "pairs": { - "spot": { - "enabled": "LTC-USDT", - "available": "LTC-CAD,LTC-SGD,USDT-USD,ETC-LTC,LTC-BTC,USDT-SGD,XMR-USDT,ZEC-SGD,ETH-USD,BTC-USDT,ETC-BTC,ETH-LTC,LTC-USD,BTC-USD,ETH-USDT,XMR-LTC,ZEC-USD,ETC-SGD,DAI-SGD,ZEC-CAD,BTC-SGD,ETH-BTC,ETH-SGD,LTC-USDT,ZEC-BTC,ZEC-USDT,BTC-CAD,XMR-BTC,ZEC-LTC,ETC-USDT,ETH-CAD" - } - } - }, - "api": { - "authenticatedSupport": false, - "authenticatedWebsocketApiSupport": false, - "endpoints": { - "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" - }, - "credentials": { - "key": "Key", - "secret": "Secret", - "clientID": "ClientID" - }, - "credentialsValidator": { - "requiresKey": true, - "requiresClientID": true - } - }, - "features": { - "supports": { - "restAPI": true, - "restCapabilities": { - "autoPairUpdates": true - }, - "websocketAPI": true, - "websocketCapabilities": {} - }, - "enabled": { - "autoPairUpdates": true, - "websocketAPI": false - } - }, - "bankAccounts": [ - { - "enabled": false, - "bankName": "", - "bankAddress": "", - "bankPostalCode": "", - "bankPostalCity": "", - "bankCountry": "", - "accountName": "", - "accountNumber": "", - "swiftCode": "", - "iban": "", - "supportedCurrencies": "" - } - ] + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true, + "requiresBase64DecodeSecret": true + } }, - { - "name": "CoinbasePro", - "enabled": true, - "verbose": false, - "httpTimeout": 15000000000, - "websocketResponseCheckTimeout": 30000000, - "websocketResponseMaxLimit": 7000000000, - "websocketTrafficTimeout": 30000000000, - "websocketOrderbookBufferLimit": 5, - "baseCurrencies": "USD,GBP,EUR", - "currencyPairs": { - "requestFormat": { - "uppercase": true, - "delimiter": "-" - }, - "configFormat": { - "uppercase": true, - "delimiter": "-" - }, - "useGlobalFormat": true, - "assetTypes": [ - "spot", - "futures" - ], - "pairs": { - "spot": { - "enabled": "BTC-USD", - "available": "LTC-GBP,XLM-BTC,DASH-BTC,DAI-USDC,ZEC-USDC,XLM-EUR,ZRX-BTC,LTC-BTC,ETC-BTC,ETH-USD,XRP-EUR,BTC-USDC,REP-USD,EOS-BTC,ZEC-BTC,ETC-GBP,LINK-ETH,XRP-BTC,ZRX-USD,ETH-USDC,MANA-USDC,BTC-EUR,BCH-GBP,DNT-USDC,EOS-EUR,BCH-EUR,LTC-EUR,CVC-USDC,ETH-GBP,DASH-USD,ETH-EUR,XTZ-BTC,ZRX-EUR,BAT-ETH,BTC-GBP,ETC-USD,BAT-USDC,BCH-USD,GNT-USDC,ALGO-USD,LINK-USD,XLM-USD,ETH-BTC,EOS-USD,REP-BTC,ETH-DAI,XRP-USD,LTC-USD,ETC-EUR,BTC-USD,XTZ-USD,BCH-BTC,LOOM-USDC" + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "tickerBatching": true, + "autoPairUpdates": true + }, + "websocketAPI": true, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": true + } + }, + "bankAccounts": [ + { + "enabled": false, + "bankName": "", + "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ] + }, + { + "name": "Kucoin", + "enabled": true, + "verbose": false, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, + "websocketOrderbookBufferLimit": 5, + "baseCurrencies": "USD", + "currencyPairs": { + "assetTypes": [ + "spot", + "margin", + "futures" + ], + "pairs": { + "spot": { + "assetEnabled": true, + "enabled": "BTC-USDT,ETH-BTC,ETH-USDT,LTC-USDT", + "available": "BTC-USDT,MHC-ETH,MHC-BTC,OXEN-BTC,OXEN-ETH,NRG-BTC,AVA-USDT,FET-BTC,FET-ETH,ANKR-BTC,MHC-USDT,XMR-BTC,XMR-ETH,RIF-BTC,MTV-BTC,MTV-ETH,CRO-BTC,MTV-USDT,KMD-BTC,KMD-USDT,RFOX-USDT,TEL-USDT,TT-USDT,AERGO-USDT,XMR-USDT,TRX-KCS,ATOM-BTC,ATOM-ETH,ATOM-USDT,ATOM-KCS,ETN-USDT,FTM-USDT,TOMO-USDT,VSYS-USDT,OCEAN-BTC,OCEAN-ETH,CHR-BTC,CHR-USDT,FX-BTC,FX-ETH,NIM-BTC,NIM-ETH,COTI-BTC,COTI-USDT,NRG-ETH,BNB-BTC,BNB-USDT,JAR-BTC,JAR-USDT,ALGO-BTC,ALGO-ETH,ALGO-USDT,XEM-BTC,XEM-USDT,CIX100-USDT,XTZ-BTC,XTZ-USDT,ZEC-BTC,ZEC-USDT,ADA-BTC,ADA-USDT,REV-USDT,WXT-BTC,WXT-USDT,FORESTPLUS-BTC,FORESTPLUS-USDT,BOLT-BTC,BOLT-USDT,ARPA-USDT,CHZ-BTC,CHZ-USDT,DAPPT-BTC,DAPPT-USDT,NOIA-BTC,NOIA-USDT,WIN-BTC,WIN-USDT,DERO-BTC,DERO-USDT,BTT-USDT,EOSC-USDT,ENQ-BTC,ENQ-USDT,ONE-BTC,ONE-USDT,TOKO-BTC,TOKO-USDT,VID-BTC,VID-USDT,LUNA-USDT,SXP-BTC,SXP-USDT,AKRO-BTC,AKRO-USDT,ROOBEE-BTC,WIN-TRX,MAP-BTC,MAP-USDT,AMPL-BTC,AMPL-USDT,DAG-USDT,POL-USDT,ARX-USDT,NWC-BTC,NWC-USDT,BEPRO-BTC,BEPRO-USDT,VRA-BTC,VRA-USDT,KSM-BTC,KSM-USDT,DASH-USDT,SUTER-USDT,ACOIN-USDT,SUTER-BTC,SENSO-USDT,PRE-BTC,XDB-USDT,SYLO-USDT,WOM-USDT,SENSO-BTC,DGB-USDT,LYXE-USDT,LYXE-ETH,XDB-BTC,STX-BTC,STX-USDT,XSR-USDT,COMP-USDT,CRO-USDT,KAI-USDT,KAI-BTC,WEST-BTC,WEST-USDT,EWT-BTC,WAVES-USDT,WAVES-BTC,ORN-USDT,AMPL-ETH,BNS-USDT,MKR-USDT,SUKU-BTC,MLK-BTC,MLK-USDT,JST-USDT,KAI-ETH,SUKU-USDT,DIA-USDT,DIA-BTC,LINK-BTC,LINK-USDT,DOT-USDT,DOT-BTC,SHA-BTC,SHA-USDT,EWT-USDT,USDJ-USDT,EFX-BTC,CKB-BTC,CKB-USDT,UMA-USDT,ALEPH-USDT,VELO-USDT,SUN-USDT,BUY-USDT,YFI-USDT,OXEN-USDT,UNI-USDT,UOS-USDT,UOS-BTC,NIM-USDT,DEGO-USDT,DEGO-ETH,UDOO-ETH,RFUEL-USDT,FIL-USDT,UBX-ETH,REAP-USDT,AAVE-USDT,AAVE-BTC,TONE-BTC,TONE-ETH,ELF-ETH,AERGO-BTC,IOST-ETH,KCS-USDT,SNX-ETH,TOMO-ETH,KCS-ETH,DRGN-BTC,WAN-ETH,NULS-ETH,AXPR-ETH,POWR-BTC,QTUM-BTC,MANA-BTC,TEL-BTC,XYO-ETH,AXPR-BTC,ETN-BTC,COV-ETH,VET-BTC,KCS-BTC,CAPP-ETH,ONT-BTC,DRGN-ETH,DAG-ETH,TOMO-BTC,WAN-BTC,KNC-ETH,CRPT-ETH,LTC-USDT,BAX-ETH,BSV-USDT,DENT-ETH,AION-ETH,LYM-ETH,TRAC-ETH,ENJ-BTC,WAXP-BTC,DGB-BTC,ELA-BTC,ZIL-BTC,BSV-BTC,XLM-USDT,IOTX-ETH,SOUL-BTC,DOCK-BTC,AMB-ETH,TRX-BTC,XRP-TUSD,NULS-BTC,ETH-DAI,LSK-BTC,GMB-ETH,GMB-BTC,NEO-ETH,OMG-ETH,BTC-TUSD,KAT-USDT,KNC-BTC,ELF-BTC,MANA-ETH,ETC-USDT,ONT-ETH,MKR-BTC,KAT-BTC,XRP-USDC,XYO-BTC,SNT-ETH,ZRX-BTC,LOOM-ETH,AION-BTC,POWR-ETH,OLT-ETH,OLT-BTC,SNT-BTC,TRAC-BTC,XLM-ETH,ETH-USDT,BSV-ETH,TRX-ETH,ETN-ETH,AOA-USDT,BCD-BTC,DENT-BTC,DOCK-ETH,KEY-BTC,EOS-KCS,XLM-BTC,ADB-ETH,TIME-ETH,CVC-BTC,LSK-ETH,QKC-BTC,AMB-BTC,USDT-TUSD,ETC-ETH,XRP-BTC,NEO-KCS,SNX-USDT,CRPT-BTC,IOTX-BTC,LTC-ETH,XRP-KCS,ADB-BTC,LTC-KCS,TEL-ETH,DCR-ETH,LYM-USDT,USDT-USDC,ETH-USDC,DAG-BTC,AVA-BTC,BTC-USDT,WAXP-ETH,XRP-USDT,KEY-ETH,VET-ETH,FTM-BTC,USDT-DAI,QKC-ETH,ETH-BTC,MAN-BTC,CPC-ETH,TRX-USDT,BTC-DAI,ONT-USDT,DASH-ETH,BAX-BTC,AVA-ETH,LOOM-BTC,MVP-BTC,MKR-ETH,COV-BTC,CPC-BTC,REQ-ETH,EOS-BTC,LTC-BTC,XRP-ETH,CAPP-BTC,FTM-ETH,BCD-ETH,ZRX-ETH,DGB-ETH,VET-USDT,REQ-BTC,UTK-BTC,PLAY-BTC,UTK-ETH,SNX-BTC,MVP-ETH,NEO-BTC,SOUL-ETH,NEO-USDT,ELA-ETH,OMG-BTC,TIME-BTC,AOA-BTC,ETC-BTC,DCR-BTC,BTC-USDC,ENJ-ETH,IOST-BTC,DASH-BTC,EOS-USDT,EOS-ETH,ZIL-ETH,ETH-TUSD,GAS-BTC,LYM-BTC,BCH-BTC,VSYS-BTC,BCH-USDT,MKR-DAI,SOLVE-BTC,GRIN-BTC,GRIN-USDT,UQC-BTC,UQC-ETH,OPCT-BTC,OPCT-ETH,PRE-USDT,SHR-BTC,SHR-USDT,UBXT-USDT,ROSE-USDT,USDC-USDT,CTI-USDT,CTI-ETH,ETH2-ETH,BUX-BTC,XHV-USDT,PLU-USDT,GRT-USDT,CAS-BTC,CAS-USDT,MSWAP-BTC,MSWAP-USDT,GOM2-BTC,GOM2-USDT,REVV-BTC,REVV-USDT,LON-USDT,1INCH-USDT,LOC-USDT,API3-USDT,UNFI-USDT,HTR-USDT,FRONT-USDT,FRONT-BTC,WBTC-BTC,WBTC-ETH,MIR-USDT,LTC-USDC,BCH-USDC,HYDRA-USDT,DFI-USDT,DFI-BTC,CRV-USDT,SUSHI-USDT,FRM-USDT,EOS-USDC,BSV-USDC,ZEN-USDT,CUDOS-USDT,ADA-USDC,REN-USDT,LRC-USDT,LINK-USDC,KLV-USDT,KLV-BTC,BOA-USDT,THETA-USDT,QNT-USDT,BAT-USDT,DOGE-USDT,DOGE-USDC,DAO-USDT,STRONG-USDT,TRIAS-USDT,TRIAS-BTC,DOGE-BTC,MITX-BTC,MITX-USDT,CAKE-USDT,ORAI-USDT,ZEE-USDT,LTX-USDT,LTX-BTC,MASK-USDT,KLV-TRX,IDEA-USDT,PHA-USDT,PHA-ETH,BCH-KCS,SRK-USDT,SRK-BTC,ADA-KCS,HTR-BTC,BSV-KCS,DOT-KCS,LINK-KCS,MIR-KCS,BNB-KCS,XLM-KCS,VET-KCS,SWINGBY-USDT,SWINGBY-BTC,XHV-BTC,DASH-KCS,UNI-KCS,AAVE-KCS,DOGE-KCS,ZEC-KCS,XTZ-KCS,GRT-KCS,ALGO-KCS,EWT-KCS,GAS-USDT,AVAX-USDT,AVAX-BTC,KRL-BTC,KRL-USDT,POLK-USDT,POLK-BTC,ENJ-USDT,MANA-USDT,RNDR-USDT,RNDR-BTC,RLY-USDT,ANC-USDT,SKEY-USDT,LAYER-USDT,TARA-USDT,TARA-ETH,IOST-USDT,DYP-USDT,DYP-ETH,XYM-USDT,XYM-BTC,PCX-USDT,PCX-BTC,ORBS-USDT,ORBS-BTC,BTC3L-USDT,BTC3S-USDT,ETH3L-USDT,ETH3S-USDT,ANKR-USDT,DSLA-USDT,DSLA-BTC,SAND-USDT,VAI-USDT,XCUR-USDT,XCUR-BTC,FLUX-USDT,OMG-USDT,ZIL-USDT,DODO-USDT,MAN-USDT,BAX-USDT,BOSON-USDT,BOSON-ETH,PUNDIX-USDT,PUNDIX-BTC,WAXP-USDT,HT-USDT,PDEX-USDT,LABS-USDT,LABS-ETH,GMB-USDT,PHNX-USDT,PHNX-BTC,HAI-USDT,EQZ-USDT,FORTH-USDT,HORD-USDT,CGG-USDT,UBX-USDT,GHX-USDT,TCP-USDT,STND-USDT,STND-ETH,TOWER-USDT,TOWER-BTC,ACE-USDT,LOCG-USDT,CARD-USDT,FLY-USDT,CWS-USDT,XDC-USDT,XDC-ETH,STRK-BTC,STRK-ETH,SHIB-USDT,POLX-USDT,KDA-USDT,KDA-BTC,ICP-USDT,ICP-BTC,STC-USDT,STC-BTC,GOVI-USDT,GOVI-BTC,FKX-USDT,CELO-USDT,CELO-BTC,CUSD-USDT,CUSD-BTC,FCL-USDT,MATIC-USDT,MATIC-BTC,ELA-USDT,CRPT-USDT,OPCT-USDT,OGN-USDT,OGN-BTC,OUSD-USDT,OUSD-BTC,TLOS-USDT,TLOS-BTC,YOP-USDT,YOP-ETH,GLQ-USDT,GLQ-BTC,MXC-USDT,ERSDL-USDT,HOTCROSS-USDT,ADA3L-USDT,ADA3S-USDT,HYVE-USDT,HYVE-BTC,DAPPX-USDT,KONO-USDT,PRQ-USDT,MAHA-USDT,MAHA-BTC,FEAR-USDT,PYR-USDT,PYR-BTC,PROM-USDT,PROM-BTC,GLCH-USDT,UNO-USDT,ALBT-USDT,ALBT-ETH,XCAD-USDT,EOS3L-USDT,EOS3S-USDT,BCH3L-USDT,BCH3S-USDT,ELON-USDT,APL-USDT,FCL-ETH,VEED-USDT,VEED-BTC,DIVI-USDT,PDEX-BTC,JUP-USDT,JUP-ETH,POLS-USDT,POLS-BTC,LPOOL-USDT,LPOOL-BTC,LSS-USDT,VET3L-USDT,VET3S-USDT,LTC3L-USDT,LTC3S-USDT,ABBC-USDT,ABBC-BTC,KOK-USDT,ROSN-USDT,DORA-USDT,DORA-BTC,ZCX-USDT,ZCX-BTC,NORD-USDT,GMEE-USDT,SFUND-USDT,XAVA-USDT,AI-USDT,ALPACA-USDT,IOI-USDT,NFT-USDT,NFT-TRX,MNST-USDT,MEM-USDT,AGIX-USDT,AGIX-BTC,AGIX-ETH,CQT-USDT,AIOZ-USDT,MARSH-USDT,HAPI-USDT,MODEFI-USDT,MODEFI-BTC,YFDAI-USDT,YFDAI-BTC,GENS-USDT,FORM-USDT,ARRR-USDT,ARRR-BTC,TOKO-KCS,EXRD-USDT,NGM-USDT,LPT-USDT,STMX-USDT,ASD-USDT,BOND-USDT,HAI-BTC,SOUL-USDT,2CRZ-USDT,NEAR-USDT,NEAR-BTC,DFYN-USDT,OOE-USDT,CFG-USDT,CFG-BTC,AXS-USDT,CLV-USDT,ROUTE-USDT,KAR-USDT,EFX-USDT,XDC-BTC,SHFT-USDT,PMON-USDT,DPET-USDT,ERG-USDT,ERG-BTC,SOL-USDT,SLP-USDT,LITH-USDT,LITH-ETH,XCH-USDT,HAKA-USDT,LAYER-BTC,MTL-USDT,MTL-BTC,IOTX-USDT,GALA-USDT,REQ-USDT,TXA-USDT,TXA-USDC,CIRUS-USDT,QI-USDT,QI-BTC,ODDZ-USDT,PNT-USDT,PNT-BTC,XPR-USDT,XPR-BTC,TRIBE-USDT,SHFT-BTC,MOVR-USDT,MOVR-ETH,WOO-USDT,WILD-USDT,QRDO-USDT,QRDO-ETH,SDN-USDT,SDN-ETH,MAKI-USDT,MAKI-BTC,REP-USDT,REP-BTC,REP-ETH,BNT-USDT,BNT-BTC,BNT-ETH,OXT-USDT,OXT-BTC,OXT-ETH,BAL-USDT,BAL-BTC,BAL-ETH,STORJ-USDT,STORJ-BTC,STORJ-ETH,YGG-USDT,NDAU-USDT,SDAO-USDT,SDAO-ETH,XRP3L-USDT,XRP3S-USDT,SKL-USDT,SKL-BTC,NMR-USDT,NMR-BTC,IXS-USDT,TRB-USDT,TRB-BTC,DYDX-USDT,XYO-USDT,GTC-USDT,GTC-BTC,EQX-USDT,EQX-BTC,RLC-USDT,RLC-BTC,XPRT-USDT,EGLD-USDT,EGLD-BTC,HBAR-USDT,HBAR-BTC,DOGE3L-USDT,DOGE3S-USDT,FLOW-USDT,FLOW-BTC,NKN-USDT,NKN-BTC,PBX-USDT,SOL3L-USDT,SOL3S-USDT,MLN-USDT,MLN-BTC,XNL-USDT,SOLVE-USDT,WNCG-USDT,WNCG-BTC,DMTR-USDT,LINK3L-USDT,LINK3S-USDT,DOT3L-USDT,DOT3S-USDT,CTSI-USDT,CTSI-BTC,ALICE-USDT,ALICE-BTC,ALICE-ETH,OPUL-USDT,ILV-USDT,BAND-USDT,BAND-BTC,FTT-USDT,FTT-BTC,DVPN-USDT,SKU-USDT,SKU-BTC,EDG-USDT,SLIM-USDT,TLM-USDT,TLM-BTC,TLM-ETH,DEXE-USDT,DEXE-BTC,DEXE-ETH,MATTER-USDT,CUDOS-BTC,RUNE-USDT,RUNE-BTC,RMRK-USDT,BMON-USDT,C98-USDT,BLOK-USDT,SOLR-USDT,ATOM3L-USDT,ATOM3S-USDT,UNI3L-USDT,UNI3S-USDT,WSIENNA-USDT,PUSH-USDT,PUSH-BTC,FORM-ETH,NTVRK-USDT,NTVRK-USDC,AXS3L-USDT,AXS3S-USDT,FTM3L-USDT,FTM3S-USDT,FLAME-USDT,AGLD-USDT,NAKA-USDT,YLD-USDT,TONE-USDT,REEF-USDT,REEF-BTC,TIDAL-USDT,TVK-USDT,TVK-BTC,INJ-USDT,INJ-BTC,BNB3L-USDT,BNB3S-USDT,MATIC3L-USDT,MATIC3S-USDT,NFTB-USDT,VEGA-USDT,VEGA-ETH,ALPHA-USDT,ALPHA-BTC,BADGER-USDT,BADGER-BTC,UNO-BTC,ZKT-USDT,AR-USDT,AR-BTC,XVS-USDT,XVS-BTC,JASMY-USDT,PERP-USDT,PERP-BTC,GHST-USDT,GHST-BTC,SCLP-USDT,SCLP-BTC,SUPER-USDT,SUPER-BTC,CPOOL-USDT,HERO-USDT,BASIC-USDT,XED-USDT,XED-BTC,AURY-USDT,SWASH-USDT,LTO-USDT,LTO-BTC,BUX-USDT,MTRG-USDT,DREAMS-USDT,SHIB-DOGE,QUICK-USDT,QUICK-BTC,TRU-USDT,TRU-BTC,WRX-USDT,WRX-BTC,TKO-USDT,TKO-BTC,SUSHI3L-USDT,SUSHI3S-USDT,NEAR3L-USDT,NEAR3S-USDT,DATA-USDT,DATA-BTC,NORD-BTC,ISP-USDT,CERE-USDT,SHILL-USDT,HEGIC-USDT,HEGIC-BTC,ERN-USDT,ERN-BTC,FTG-USDT,PAXG-USDT,PAXG-BTC,AUDIO-USDT,AUDIO-BTC,ENS-USDT,AAVE3L-USDT,AAVE3S-USDT,SAND3L-USDT,SAND3S-USDT,XTM-USDT,MNW-USDT,FXS-USDT,FXS-BTC,ATA-USDT,ATA-BTC,VXV-USDT,LRC-BTC,LRC-ETH,DPR-USDT,CWAR-USDT,CWAR-BTC,FLUX-BTC,EDG-BTC,PBR-USDT,WNXM-USDT,WNXM-BTC,ANT-USDT,ANT-BTC,COV-USDT,SWP-USDT,TWT-USDT,TWT-BTC,OM-USDT,OM-BTC,ADX-USDT,AVAX3L-USDT,AVAX3S-USDT,MANA3L-USDT,MANA3S-USDT,GLM-USDT,GLM-BTC,BAKE-USDT,BAKE-BTC,BAKE-ETH,NUM-USDT,VLX-USDT,VLX-BTC,TRADE-USDT,TRADE-BTC,1EARTH-USDT,MONI-USDT,LIKE-USDT,MFT-USDT,MFT-BTC,LIT-USDT,LIT-BTC,KAVA-USDT,SFP-USDT,SFP-BTC,BURGER-USDT,BURGER-BTC,ILA-USDT,CREAM-USDT,CREAM-BTC,RSR-USDT,RSR-BTC,BUY-BTC,IMX-USDT,GODS-USDT,KMA-USDT,SRM-USDT,SRM-BTC,POLC-USDT,XTAG-USDT,MNET-USDT,NGC-USDT,HARD-USDT,GALAX3L-USDT,GALAX3S-USDT,UNIC-USDT,POND-USDT,POND-BTC,VR-USDT,EPIK-USDT,NGL-USDT,NGL-BTC,KDON-USDT,PEL-USDT,CIRUS-ETH,LINA-USDT,LINA-BTC,KLAY-USDT,KLAY-BTC,CREDI-USDT,TRVL-USDT,LACE-USDT,LACE-ETH,ARKER-USDT,BONDLY-USDT,BONDLY-ETH,XEC-USDT,HEART-USDT,HEART-BTC,UNB-USDT,GAFI-USDT,KOL-USDT,KOL-ETH,H3RO3S-USDT,FALCONS-USDT,UFO-USDT,CHMB-USDT,GEEQ-USDT,ORC-USDT,RACEFI-USDT,PEOPLE-USDT,ADS-USDT,ADS-BTC,OCEAN-USDT,SOS-USDT,WHALE-USDT,TIME-USDT,CWEB-USDT,IOTA-USDT,IOTA-BTC,OOKI-USDT,OOKI-BTC,HNT-USDT,HNT-BTC,GGG-USDT,POWR-USDT,REVU-USDT,CLH-USDT,PLGR-USDT,GLMR-USDT,GLMR-BTC,LOVE-USDT,CTC-USDT,CTC-BTC,GARI-USDT,FRR-USDT,ASTR-USDT,ASTR-BTC,ERTHA-USDT,FCON-USDT,ACA-USDT,ACA-BTC,MTS-USDT,ROAR-USDT,HBB-USDT,SURV-USDT,CVX-USDT,AMP-USDT,ACT-USDT,MJT-USDT,MJT-KCS,SHX-USDT,SHX-BTC,STARLY-USDT,ONSTON-USDT,RANKER-USDT,WMT-USDT,XNO-USDT,XNO-BTC,MARS4-USDT,TFUEL-USDT,TFUEL-BTC,METIS-USDT,LAVAX-USDT,WAL-USDT,BULL-USDT,SON-USDT,MELOS-USDT,APE-USDT,GMT-USDT,BICO-USDT,STG-USDT,LMR-USDT,LMR-BTC,LOKA-USDT,URUS-USDT,JAM-USDT,JAM-ETH,BNC-USDT,LBP-USDT,CFX-USDT,LOOKS-USDT,XCN-USDT,XCN-BTC,KP3R-USDT,TITAN-USDT,INDI-USDT,UPO-USDT,SPELL-USDT,SLCL-USDT,CEEK-USDT,VEMP-USDT,BETA-USDT,NHCT-USDT,ARNM-USDT,FRA-USDT,VISION-USDT,COCOS-USDT,ALPINE-USDT,BNX-USDT,ZBC-USDT,WOOP-USDT,T-USDT,NYM-USDT,VOXEL-USDT,VOXEL-ETH,PSTAKE-USDT,SPA-USDT,SPA-ETH,SYNR-USDT,DAR-USDT,DAR-BTC,MV-USDT,XDEFI-USDT,RACA-USDT,XWG-USDT,HAWK-USDT,TRVL-BTC,SWFTC-USDT,IDEX-USDT,BRWL-USDT,PLATO-USDT,TAUM-USDT,CELR-USDT,AURORA-USDT,POSI-USDT,COOHA-USDT,KNC-USDT,EPK-USDT,PLD-USDT,PSL-USDT,PKF-USDT,OVR-USDT,SYS-USDT,SYS-BTC,BRISE-USDT,DG-USDT,EPX-USDT,GST-USDT,PLY-USDT,GAL-USDT,BSW-USDT,FITFI-USDT,FSN-USDT,H2O-USDT,GMM-USDT,AKT-USDT,SIN-USDT,AUSD-USDT,BOBA-USDT,KARA-USDT,BFC-USDT,BIFI-USDT,DFA-USDT,KYL-USDT,FCD-USDT,MBL-USDT,CELT-USDT,DUSK-USDT,USDD-USDT,USDD-USDC,FITFI-USDC,MBOX-USDT,MBOX-BTC,APE-USDC,AVAX-USDC,SHIB-USDC,XCN-USDC,TRX-USDC,NEAR-USDC,MATIC-USDC,FTM-USDC,ZIL-USDC,SOL-USDC,MLS-USDT,AFK-USDT,AFK-USDC,ACH-USDT,SCRT-USDT,SCRT-BTC,APE3L-USDT,APE3S-USDT,STORE-USDT,STORE-ETH,GMT3L-USDT,GMT3S-USDT,CCD-USDT,DOSE-USDC,LUNC-USDT,LUNC-USDC,USTC-USDT,USTC-USDC,GMT-USDC,VRA-USDC,DOT-USDC,RUNE-USDC,ATOM-USDC,BNB-USDC,JASMY-USDC,KCS-USDC,KDA-USDC,ALGO-USDC,LUNA-USDC,OP-USDT,OP-USDC,JASMY3L-USDT,JASMY3S-USDT,EVER-USDT,MOOV-USDT,IHC-USDT,ICX-USDT,ICX-ETH,BTC-BRL,ETH-BRL,USDT-BRL,WELL-USDT,FORT-USDT,USDP-USDT,USDD-TRX,CSPR-USDT,CSPR-ETH,WEMIX-USDT,REV3L-USDT,OLE-USDT,LDO-USDT,LDO-USDC,CULT-USDT,SWFTC-USDC,FIDA-USDT,BUSD-USDT,RBP-USDT,SRBP-USDT,HIBAYC-USDT,BUSD-USDC,OGV-USDT,WOMBAT-USDT,HIPUNKS-USDT,FT-USDT,ETC-USDC,HIENS4-USDT,EGAME-USDT,EGAME-BTC,STEPWATCH-USDT,HISAND33-USDT,DC-USDT,NEER-USDT,RVN-USDT,HIENS3-USDT,MC-USDT,PEEL-USDT,PEEL-BTC,SDL-USDT,SDL-BTC,SWEAT-USDT,HIODBS-USDT,CMP-USDT,PIX-USDT,MPLX-USDT,HIDOODLES-USDT,ETHW-USDT,QUARTZ-USDT,ACQ-USDT,ACQ-USDC,AOG-USDT,HIMAYC-USDT,PRMX-USDT,RED-USDT,PUMLX-USDT,XETA-USDT,GEM-USDT,DERC-USDT,P00LS-USDT,P00LS-USDC,KICKS-USDT,TRIBL-USDT,GMX-USDT,HIOD-USDT,POKT-USDT,EFI-USDT,APT-USDT,BBC-USDT,EUL-USDT,TON-USDT,PIAS-USDT,HIMEEBITS-USDT,HISQUIGGLE-USDT,XCV-USDT,HFT-USDT,HFT-USDC,ECOX-USDT,AMB-USDT,AZERO-USDT,HIFIDENZA-USDT,BEAT-USDT", + "requestFormat": { + "uppercase": true, + "delimiter": "-" }, - "futures": { - "enabled": "BTC-PERP-INTX", - "available": "BTC-PERP-INTX" + "configFormat": { + "uppercase": true, + "delimiter": "-" } - } - }, - "api": { - "authenticatedSupport": false, - "authenticatedWebsocketApiSupport": false, - "endpoints": { - "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" }, - "credentials": { - "key": "Key", - "secret": "Secret", - "clientID": "ClientID" - }, - "credentialsValidator": { - "requiresKey": true, - "requiresSecret": true, - "requiresClientID": true, - "requiresBase64DecodeSecret": true - } - }, - "features": { - "supports": { - "restAPI": true, - "restCapabilities": { - "autoPairUpdates": true + "margin": { + "assetEnabled": true, + "enabled": "ETH-BTC,TRX-BTC,LTC-USDT,SOL-USDC", + "available": "BTC-USDT,MHC-ETH,MHC-BTC,OXEN-BTC,OXEN-ETH,NRG-BTC,AVA-USDT,FET-BTC,FET-ETH,ANKR-BTC,MHC-USDT,XMR-BTC,XMR-ETH,RIF-BTC,MTV-BTC,MTV-ETH,CRO-BTC,MTV-USDT,KMD-BTC,KMD-USDT,RFOX-USDT,TEL-USDT,TT-USDT,AERGO-USDT,XMR-USDT,TRX-KCS,ATOM-BTC,ATOM-ETH,ATOM-USDT,ATOM-KCS,ETN-USDT,FTM-USDT,TOMO-USDT,VSYS-USDT,OCEAN-BTC,OCEAN-ETH,CHR-BTC,CHR-USDT,FX-BTC,FX-ETH,NIM-BTC,NIM-ETH,COTI-BTC,COTI-USDT,NRG-ETH,BNB-BTC,BNB-USDT,JAR-BTC,JAR-USDT,ALGO-BTC,ALGO-ETH,ALGO-USDT,XEM-BTC,XEM-USDT,CIX100-USDT,XTZ-BTC,XTZ-USDT,ZEC-BTC,ZEC-USDT,ADA-BTC,ADA-USDT,REV-USDT,WXT-BTC,WXT-USDT,FORESTPLUS-BTC,FORESTPLUS-USDT,BOLT-BTC,BOLT-USDT,ARPA-USDT,CHZ-BTC,CHZ-USDT,DAPPT-BTC,DAPPT-USDT,NOIA-BTC,NOIA-USDT,WIN-BTC,WIN-USDT,DERO-BTC,DERO-USDT,BTT-USDT,EOSC-USDT,ENQ-BTC,ENQ-USDT,ONE-BTC,ONE-USDT,TOKO-BTC,TOKO-USDT,VID-BTC,VID-USDT,LUNA-USDT,SXP-BTC,SXP-USDT,AKRO-BTC,AKRO-USDT,ROOBEE-BTC,WIN-TRX,MAP-BTC,MAP-USDT,AMPL-BTC,AMPL-USDT,DAG-USDT,POL-USDT,ARX-USDT,NWC-BTC,NWC-USDT,BEPRO-BTC,BEPRO-USDT,VRA-BTC,VRA-USDT,KSM-BTC,KSM-USDT,DASH-USDT,SUTER-USDT,ACOIN-USDT,SUTER-BTC,SENSO-USDT,PRE-BTC,XDB-USDT,SYLO-USDT,WOM-USDT,SENSO-BTC,DGB-USDT,LYXE-USDT,LYXE-ETH,XDB-BTC,STX-BTC,STX-USDT,XSR-USDT,COMP-USDT,CRO-USDT,KAI-USDT,KAI-BTC,WEST-BTC,WEST-USDT,EWT-BTC,WAVES-USDT,WAVES-BTC,ORN-USDT,AMPL-ETH,BNS-USDT,MKR-USDT,SUKU-BTC,MLK-BTC,MLK-USDT,JST-USDT,KAI-ETH,SUKU-USDT,DIA-USDT,DIA-BTC,LINK-BTC,LINK-USDT,DOT-USDT,DOT-BTC,SHA-BTC,SHA-USDT,EWT-USDT,USDJ-USDT,EFX-BTC,CKB-BTC,CKB-USDT,UMA-USDT,ALEPH-USDT,VELO-USDT,SUN-USDT,BUY-USDT,YFI-USDT,OXEN-USDT,UNI-USDT,UOS-USDT,UOS-BTC,NIM-USDT,DEGO-USDT,DEGO-ETH,UDOO-ETH,RFUEL-USDT,FIL-USDT,UBX-ETH,REAP-USDT,AAVE-USDT,AAVE-BTC,TONE-BTC,TONE-ETH,ELF-ETH,AERGO-BTC,IOST-ETH,KCS-USDT,SNX-ETH,TOMO-ETH,KCS-ETH,DRGN-BTC,WAN-ETH,NULS-ETH,AXPR-ETH,POWR-BTC,QTUM-BTC,MANA-BTC,TEL-BTC,XYO-ETH,AXPR-BTC,ETN-BTC,COV-ETH,VET-BTC,KCS-BTC,CAPP-ETH,ONT-BTC,DRGN-ETH,DAG-ETH,TOMO-BTC,WAN-BTC,KNC-ETH,CRPT-ETH,LTC-USDT,BAX-ETH,BSV-USDT,DENT-ETH,AION-ETH,LYM-ETH,TRAC-ETH,ENJ-BTC,WAXP-BTC,DGB-BTC,ELA-BTC,ZIL-BTC,BSV-BTC,XLM-USDT,IOTX-ETH,SOUL-BTC,DOCK-BTC,AMB-ETH,TRX-BTC,XRP-TUSD,NULS-BTC,ETH-DAI,LSK-BTC,GMB-ETH,GMB-BTC,NEO-ETH,OMG-ETH,BTC-TUSD,KAT-USDT,KNC-BTC,ELF-BTC,MANA-ETH,ETC-USDT,ONT-ETH,MKR-BTC,KAT-BTC,XRP-USDC,XYO-BTC,SNT-ETH,ZRX-BTC,LOOM-ETH,AION-BTC,POWR-ETH,OLT-ETH,OLT-BTC,SNT-BTC,TRAC-BTC,XLM-ETH,ETH-USDT,BSV-ETH,TRX-ETH,ETN-ETH,AOA-USDT,BCD-BTC,DENT-BTC,DOCK-ETH,KEY-BTC,EOS-KCS,XLM-BTC,ADB-ETH,TIME-ETH,CVC-BTC,LSK-ETH,QKC-BTC,AMB-BTC,USDT-TUSD,ETC-ETH,XRP-BTC,NEO-KCS,SNX-USDT,CRPT-BTC,IOTX-BTC,LTC-ETH,XRP-KCS,ADB-BTC,LTC-KCS,TEL-ETH,DCR-ETH,LYM-USDT,USDT-USDC,ETH-USDC,DAG-BTC,AVA-BTC,BTC-USDT,WAXP-ETH,XRP-USDT,KEY-ETH,VET-ETH,FTM-BTC,USDT-DAI,QKC-ETH,ETH-BTC,MAN-BTC,CPC-ETH,TRX-USDT,BTC-DAI,ONT-USDT,DASH-ETH,BAX-BTC,AVA-ETH,LOOM-BTC,MVP-BTC,MKR-ETH,COV-BTC,CPC-BTC,REQ-ETH,EOS-BTC,LTC-BTC,XRP-ETH,CAPP-BTC,FTM-ETH,BCD-ETH,ZRX-ETH,DGB-ETH,VET-USDT,REQ-BTC,UTK-BTC,PLAY-BTC,UTK-ETH,SNX-BTC,MVP-ETH,NEO-BTC,SOUL-ETH,NEO-USDT,ELA-ETH,OMG-BTC,TIME-BTC,AOA-BTC,ETC-BTC,DCR-BTC,BTC-USDC,ENJ-ETH,IOST-BTC,DASH-BTC,EOS-USDT,EOS-ETH,ZIL-ETH,ETH-TUSD,GAS-BTC,LYM-BTC,BCH-BTC,VSYS-BTC,BCH-USDT,MKR-DAI,SOLVE-BTC,GRIN-BTC,GRIN-USDT,UQC-BTC,UQC-ETH,OPCT-BTC,OPCT-ETH,PRE-USDT,SHR-BTC,SHR-USDT,UBXT-USDT,ROSE-USDT,USDC-USDT,CTI-USDT,CTI-ETH,ETH2-ETH,BUX-BTC,XHV-USDT,PLU-USDT,GRT-USDT,CAS-BTC,CAS-USDT,MSWAP-BTC,MSWAP-USDT,GOM2-BTC,GOM2-USDT,REVV-BTC,REVV-USDT,LON-USDT,1INCH-USDT,LOC-USDT,API3-USDT,UNFI-USDT,HTR-USDT,FRONT-USDT,FRONT-BTC,WBTC-BTC,WBTC-ETH,MIR-USDT,LTC-USDC,BCH-USDC,HYDRA-USDT,DFI-USDT,DFI-BTC,CRV-USDT,SUSHI-USDT,FRM-USDT,EOS-USDC,BSV-USDC,ZEN-USDT,CUDOS-USDT,ADA-USDC,REN-USDT,LRC-USDT,LINK-USDC,KLV-USDT,KLV-BTC,BOA-USDT,THETA-USDT,QNT-USDT,BAT-USDT,DOGE-USDT,DOGE-USDC,DAO-USDT,STRONG-USDT,TRIAS-USDT,TRIAS-BTC,DOGE-BTC,MITX-BTC,MITX-USDT,CAKE-USDT,ORAI-USDT,ZEE-USDT,LTX-USDT,LTX-BTC,MASK-USDT,KLV-TRX,IDEA-USDT,PHA-USDT,PHA-ETH,BCH-KCS,SRK-USDT,SRK-BTC,ADA-KCS,HTR-BTC,BSV-KCS,DOT-KCS,LINK-KCS,MIR-KCS,BNB-KCS,XLM-KCS,VET-KCS,SWINGBY-USDT,SWINGBY-BTC,XHV-BTC,DASH-KCS,UNI-KCS,AAVE-KCS,DOGE-KCS,ZEC-KCS,XTZ-KCS,GRT-KCS,ALGO-KCS,EWT-KCS,GAS-USDT,AVAX-USDT,AVAX-BTC,KRL-BTC,KRL-USDT,POLK-USDT,POLK-BTC,ENJ-USDT,MANA-USDT,RNDR-USDT,RNDR-BTC,RLY-USDT,ANC-USDT,SKEY-USDT,LAYER-USDT,TARA-USDT,TARA-ETH,IOST-USDT,DYP-USDT,DYP-ETH,XYM-USDT,XYM-BTC,PCX-USDT,PCX-BTC,ORBS-USDT,ORBS-BTC,BTC3L-USDT,BTC3S-USDT,ETH3L-USDT,ETH3S-USDT,ANKR-USDT,DSLA-USDT,DSLA-BTC,SAND-USDT,VAI-USDT,XCUR-USDT,XCUR-BTC,FLUX-USDT,OMG-USDT,ZIL-USDT,DODO-USDT,MAN-USDT,BAX-USDT,BOSON-USDT,BOSON-ETH,PUNDIX-USDT,PUNDIX-BTC,WAXP-USDT,HT-USDT,PDEX-USDT,LABS-USDT,LABS-ETH,GMB-USDT,PHNX-USDT,PHNX-BTC,HAI-USDT,EQZ-USDT,FORTH-USDT,HORD-USDT,CGG-USDT,UBX-USDT,GHX-USDT,TCP-USDT,STND-USDT,STND-ETH,TOWER-USDT,TOWER-BTC,ACE-USDT,LOCG-USDT,CARD-USDT,FLY-USDT,CWS-USDT,XDC-USDT,XDC-ETH,STRK-BTC,STRK-ETH,SHIB-USDT,POLX-USDT,KDA-USDT,KDA-BTC,ICP-USDT,ICP-BTC,STC-USDT,STC-BTC,GOVI-USDT,GOVI-BTC,FKX-USDT,CELO-USDT,CELO-BTC,CUSD-USDT,CUSD-BTC,FCL-USDT,MATIC-USDT,MATIC-BTC,ELA-USDT,CRPT-USDT,OPCT-USDT,OGN-USDT,OGN-BTC,OUSD-USDT,OUSD-BTC,TLOS-USDT,TLOS-BTC,YOP-USDT,YOP-ETH,GLQ-USDT,GLQ-BTC,MXC-USDT,ERSDL-USDT,HOTCROSS-USDT,ADA3L-USDT,ADA3S-USDT,HYVE-USDT,HYVE-BTC,DAPPX-USDT,KONO-USDT,PRQ-USDT,MAHA-USDT,MAHA-BTC,FEAR-USDT,PYR-USDT,PYR-BTC,PROM-USDT,PROM-BTC,GLCH-USDT,UNO-USDT,ALBT-USDT,ALBT-ETH,XCAD-USDT,EOS3L-USDT,EOS3S-USDT,BCH3L-USDT,BCH3S-USDT,ELON-USDT,APL-USDT,FCL-ETH,VEED-USDT,VEED-BTC,DIVI-USDT,PDEX-BTC,JUP-USDT,JUP-ETH,POLS-USDT,POLS-BTC,LPOOL-USDT,LPOOL-BTC,LSS-USDT,VET3L-USDT,VET3S-USDT,LTC3L-USDT,LTC3S-USDT,ABBC-USDT,ABBC-BTC,KOK-USDT,ROSN-USDT,DORA-USDT,DORA-BTC,ZCX-USDT,ZCX-BTC,NORD-USDT,GMEE-USDT,SFUND-USDT,XAVA-USDT,AI-USDT,ALPACA-USDT,IOI-USDT,NFT-USDT,NFT-TRX,MNST-USDT,MEM-USDT,AGIX-USDT,AGIX-BTC,AGIX-ETH,CQT-USDT,AIOZ-USDT,MARSH-USDT,HAPI-USDT,MODEFI-USDT,MODEFI-BTC,YFDAI-USDT,YFDAI-BTC,GENS-USDT,FORM-USDT,ARRR-USDT,ARRR-BTC,TOKO-KCS,EXRD-USDT,NGM-USDT,LPT-USDT,STMX-USDT,ASD-USDT,BOND-USDT,HAI-BTC,SOUL-USDT,2CRZ-USDT,NEAR-USDT,NEAR-BTC,DFYN-USDT,OOE-USDT,CFG-USDT,CFG-BTC,AXS-USDT,CLV-USDT,ROUTE-USDT,KAR-USDT,EFX-USDT,XDC-BTC,SHFT-USDT,PMON-USDT,DPET-USDT,ERG-USDT,ERG-BTC,SOL-USDT,SLP-USDT,LITH-USDT,LITH-ETH,XCH-USDT,HAKA-USDT,LAYER-BTC,MTL-USDT,MTL-BTC,IOTX-USDT,GALA-USDT,REQ-USDT,TXA-USDT,TXA-USDC,CIRUS-USDT,QI-USDT,QI-BTC,ODDZ-USDT,PNT-USDT,PNT-BTC,XPR-USDT,XPR-BTC,TRIBE-USDT,SHFT-BTC,MOVR-USDT,MOVR-ETH,WOO-USDT,WILD-USDT,QRDO-USDT,QRDO-ETH,SDN-USDT,SDN-ETH,MAKI-USDT,MAKI-BTC,REP-USDT,REP-BTC,REP-ETH,BNT-USDT,BNT-BTC,BNT-ETH,OXT-USDT,OXT-BTC,OXT-ETH,BAL-USDT,BAL-BTC,BAL-ETH,STORJ-USDT,STORJ-BTC,STORJ-ETH,YGG-USDT,NDAU-USDT,SDAO-USDT,SDAO-ETH,XRP3L-USDT,XRP3S-USDT,SKL-USDT,SKL-BTC,NMR-USDT,NMR-BTC,IXS-USDT,TRB-USDT,TRB-BTC,DYDX-USDT,XYO-USDT,GTC-USDT,GTC-BTC,EQX-USDT,EQX-BTC,RLC-USDT,RLC-BTC,XPRT-USDT,EGLD-USDT,EGLD-BTC,HBAR-USDT,HBAR-BTC,DOGE3L-USDT,DOGE3S-USDT,FLOW-USDT,FLOW-BTC,NKN-USDT,NKN-BTC,PBX-USDT,SOL3L-USDT,SOL3S-USDT,MLN-USDT,MLN-BTC,XNL-USDT,SOLVE-USDT,WNCG-USDT,WNCG-BTC,DMTR-USDT,LINK3L-USDT,LINK3S-USDT,DOT3L-USDT,DOT3S-USDT,CTSI-USDT,CTSI-BTC,ALICE-USDT,ALICE-BTC,ALICE-ETH,OPUL-USDT,ILV-USDT,BAND-USDT,BAND-BTC,FTT-USDT,FTT-BTC,DVPN-USDT,SKU-USDT,SKU-BTC,EDG-USDT,SLIM-USDT,TLM-USDT,TLM-BTC,TLM-ETH,DEXE-USDT,DEXE-BTC,DEXE-ETH,MATTER-USDT,CUDOS-BTC,RUNE-USDT,RUNE-BTC,RMRK-USDT,BMON-USDT,C98-USDT,BLOK-USDT,SOLR-USDT,ATOM3L-USDT,ATOM3S-USDT,UNI3L-USDT,UNI3S-USDT,WSIENNA-USDT,PUSH-USDT,PUSH-BTC,FORM-ETH,NTVRK-USDT,NTVRK-USDC,AXS3L-USDT,AXS3S-USDT,FTM3L-USDT,FTM3S-USDT,FLAME-USDT,AGLD-USDT,NAKA-USDT,YLD-USDT,TONE-USDT,REEF-USDT,REEF-BTC,TIDAL-USDT,TVK-USDT,TVK-BTC,INJ-USDT,INJ-BTC,BNB3L-USDT,BNB3S-USDT,MATIC3L-USDT,MATIC3S-USDT,NFTB-USDT,VEGA-USDT,VEGA-ETH,ALPHA-USDT,ALPHA-BTC,BADGER-USDT,BADGER-BTC,UNO-BTC,ZKT-USDT,AR-USDT,AR-BTC,XVS-USDT,XVS-BTC,JASMY-USDT,PERP-USDT,PERP-BTC,GHST-USDT,GHST-BTC,SCLP-USDT,SCLP-BTC,SUPER-USDT,SUPER-BTC,CPOOL-USDT,HERO-USDT,BASIC-USDT,XED-USDT,XED-BTC,AURY-USDT,SWASH-USDT,LTO-USDT,LTO-BTC,BUX-USDT,MTRG-USDT,DREAMS-USDT,SHIB-DOGE,QUICK-USDT,QUICK-BTC,TRU-USDT,TRU-BTC,WRX-USDT,WRX-BTC,TKO-USDT,TKO-BTC,SUSHI3L-USDT,SUSHI3S-USDT,NEAR3L-USDT,NEAR3S-USDT,DATA-USDT,DATA-BTC,NORD-BTC,ISP-USDT,CERE-USDT,SHILL-USDT,HEGIC-USDT,HEGIC-BTC,ERN-USDT,ERN-BTC,FTG-USDT,PAXG-USDT,PAXG-BTC,AUDIO-USDT,AUDIO-BTC,ENS-USDT,AAVE3L-USDT,AAVE3S-USDT,SAND3L-USDT,SAND3S-USDT,XTM-USDT,MNW-USDT,FXS-USDT,FXS-BTC,ATA-USDT,ATA-BTC,VXV-USDT,LRC-BTC,LRC-ETH,DPR-USDT,CWAR-USDT,CWAR-BTC,FLUX-BTC,EDG-BTC,PBR-USDT,WNXM-USDT,WNXM-BTC,ANT-USDT,ANT-BTC,COV-USDT,SWP-USDT,TWT-USDT,TWT-BTC,OM-USDT,OM-BTC,ADX-USDT,AVAX3L-USDT,AVAX3S-USDT,MANA3L-USDT,MANA3S-USDT,GLM-USDT,GLM-BTC,BAKE-USDT,BAKE-BTC,BAKE-ETH,NUM-USDT,VLX-USDT,VLX-BTC,TRADE-USDT,TRADE-BTC,1EARTH-USDT,MONI-USDT,LIKE-USDT,MFT-USDT,MFT-BTC,LIT-USDT,LIT-BTC,KAVA-USDT,SFP-USDT,SFP-BTC,BURGER-USDT,BURGER-BTC,ILA-USDT,CREAM-USDT,CREAM-BTC,RSR-USDT,RSR-BTC,BUY-BTC,IMX-USDT,GODS-USDT,KMA-USDT,SRM-USDT,SRM-BTC,POLC-USDT,XTAG-USDT,MNET-USDT,NGC-USDT,HARD-USDT,GALAX3L-USDT,GALAX3S-USDT,UNIC-USDT,POND-USDT,POND-BTC,VR-USDT,EPIK-USDT,NGL-USDT,NGL-BTC,KDON-USDT,PEL-USDT,CIRUS-ETH,LINA-USDT,LINA-BTC,KLAY-USDT,KLAY-BTC,CREDI-USDT,TRVL-USDT,LACE-USDT,LACE-ETH,ARKER-USDT,BONDLY-USDT,BONDLY-ETH,XEC-USDT,HEART-USDT,HEART-BTC,UNB-USDT,GAFI-USDT,KOL-USDT,KOL-ETH,H3RO3S-USDT,FALCONS-USDT,UFO-USDT,CHMB-USDT,GEEQ-USDT,ORC-USDT,RACEFI-USDT,PEOPLE-USDT,ADS-USDT,ADS-BTC,OCEAN-USDT,SOS-USDT,WHALE-USDT,TIME-USDT,CWEB-USDT,IOTA-USDT,IOTA-BTC,OOKI-USDT,OOKI-BTC,HNT-USDT,HNT-BTC,GGG-USDT,POWR-USDT,REVU-USDT,CLH-USDT,PLGR-USDT,GLMR-USDT,GLMR-BTC,LOVE-USDT,CTC-USDT,CTC-BTC,GARI-USDT,FRR-USDT,ASTR-USDT,ASTR-BTC,ERTHA-USDT,FCON-USDT,ACA-USDT,ACA-BTC,MTS-USDT,ROAR-USDT,HBB-USDT,SURV-USDT,CVX-USDT,AMP-USDT,ACT-USDT,MJT-USDT,MJT-KCS,SHX-USDT,SHX-BTC,STARLY-USDT,ONSTON-USDT,RANKER-USDT,WMT-USDT,XNO-USDT,XNO-BTC,MARS4-USDT,TFUEL-USDT,TFUEL-BTC,METIS-USDT,LAVAX-USDT,WAL-USDT,BULL-USDT,SON-USDT,MELOS-USDT,APE-USDT,GMT-USDT,BICO-USDT,STG-USDT,LMR-USDT,LMR-BTC,LOKA-USDT,URUS-USDT,JAM-USDT,JAM-ETH,BNC-USDT,LBP-USDT,CFX-USDT,LOOKS-USDT,XCN-USDT,XCN-BTC,KP3R-USDT,TITAN-USDT,INDI-USDT,UPO-USDT,SPELL-USDT,SLCL-USDT,CEEK-USDT,VEMP-USDT,BETA-USDT,NHCT-USDT,ARNM-USDT,FRA-USDT,VISION-USDT,COCOS-USDT,ALPINE-USDT,BNX-USDT,ZBC-USDT,WOOP-USDT,T-USDT,NYM-USDT,VOXEL-USDT,VOXEL-ETH,PSTAKE-USDT,SPA-USDT,SPA-ETH,SYNR-USDT,DAR-USDT,DAR-BTC,MV-USDT,XDEFI-USDT,RACA-USDT,XWG-USDT,HAWK-USDT,TRVL-BTC,SWFTC-USDT,IDEX-USDT,BRWL-USDT,PLATO-USDT,TAUM-USDT,CELR-USDT,AURORA-USDT,POSI-USDT,COOHA-USDT,KNC-USDT,EPK-USDT,PLD-USDT,PSL-USDT,PKF-USDT,OVR-USDT,SYS-USDT,SYS-BTC,BRISE-USDT,DG-USDT,EPX-USDT,GST-USDT,PLY-USDT,GAL-USDT,BSW-USDT,FITFI-USDT,FSN-USDT,H2O-USDT,GMM-USDT,AKT-USDT,SIN-USDT,AUSD-USDT,BOBA-USDT,KARA-USDT,BFC-USDT,BIFI-USDT,DFA-USDT,KYL-USDT,FCD-USDT,MBL-USDT,CELT-USDT,DUSK-USDT,USDD-USDT,USDD-USDC,FITFI-USDC,MBOX-USDT,MBOX-BTC,APE-USDC,AVAX-USDC,SHIB-USDC,XCN-USDC,TRX-USDC,NEAR-USDC,MATIC-USDC,FTM-USDC,ZIL-USDC,SOL-USDC,MLS-USDT,AFK-USDT,AFK-USDC,ACH-USDT,SCRT-USDT,SCRT-BTC,APE3L-USDT,APE3S-USDT,STORE-USDT,STORE-ETH,GMT3L-USDT,GMT3S-USDT,CCD-USDT,DOSE-USDC,LUNC-USDT,LUNC-USDC,USTC-USDT,USTC-USDC,GMT-USDC,VRA-USDC,DOT-USDC,RUNE-USDC,ATOM-USDC,BNB-USDC,JASMY-USDC,KCS-USDC,KDA-USDC,ALGO-USDC,LUNA-USDC,OP-USDT,OP-USDC,JASMY3L-USDT,JASMY3S-USDT,EVER-USDT,MOOV-USDT,IHC-USDT,ICX-USDT,ICX-ETH,BTC-BRL,ETH-BRL,USDT-BRL,WELL-USDT,FORT-USDT,USDP-USDT,USDD-TRX,CSPR-USDT,CSPR-ETH,WEMIX-USDT,REV3L-USDT,OLE-USDT,LDO-USDT,LDO-USDC,CULT-USDT,SWFTC-USDC,FIDA-USDT,BUSD-USDT,RBP-USDT,SRBP-USDT,HIBAYC-USDT,BUSD-USDC,OGV-USDT,WOMBAT-USDT,HIPUNKS-USDT,FT-USDT,ETC-USDC,HIENS4-USDT,EGAME-USDT,EGAME-BTC,STEPWATCH-USDT,HISAND33-USDT,DC-USDT,NEER-USDT,RVN-USDT,HIENS3-USDT,MC-USDT,PEEL-USDT,PEEL-BTC,SDL-USDT,SDL-BTC,SWEAT-USDT,HIODBS-USDT,CMP-USDT,PIX-USDT,MPLX-USDT,HIDOODLES-USDT,ETHW-USDT,QUARTZ-USDT,ACQ-USDT,ACQ-USDC,AOG-USDT,HIMAYC-USDT,PRMX-USDT,RED-USDT,PUMLX-USDT,XETA-USDT,GEM-USDT,DERC-USDT,P00LS-USDT,P00LS-USDC,KICKS-USDT,TRIBL-USDT,GMX-USDT,HIOD-USDT,POKT-USDT,EFI-USDT,APT-USDT,BBC-USDT,EUL-USDT,TON-USDT,PIAS-USDT,HIMEEBITS-USDT,HISQUIGGLE-USDT,XCV-USDT,HFT-USDT,HFT-USDC,ECOX-USDT,AMB-USDT,AZERO-USDT,HIFIDENZA-USDT,BEAT-USDT", + "requestFormat": { + "uppercase": true, + "delimiter": "-" }, - "websocketAPI": true, - "websocketCapabilities": {} - }, - "enabled": { - "autoPairUpdates": true, - "websocketAPI": false - } - }, - "bankAccounts": [ - { - "enabled": false, - "bankName": "", - "bankAddress": "", - "bankPostalCode": "", - "bankPostalCity": "", - "bankCountry": "", - "accountName": "", - "accountNumber": "", - "swiftCode": "", - "iban": "", - "supportedCurrencies": "" - } - ] - }, - { - "name": "EXMO", - "enabled": true, - "verbose": false, - "httpTimeout": 15000000000, - "websocketResponseCheckTimeout": 30000000, - "websocketResponseMaxLimit": 7000000000, - "websocketTrafficTimeout": 30000000000, - "websocketOrderbookBufferLimit": 5, - "baseCurrencies": "USD,EUR,RUB,PLN,UAH", - "currencyPairs": { - "requestFormat": { - "uppercase": true, - "delimiter": "_", - "separator": "," - }, - "configFormat": { - "uppercase": true, - "delimiter": "_" + "configFormat": { + "uppercase": true, + "delimiter": "-" + } }, - "useGlobalFormat": true, - "assetTypes": [ - "spot" - ], - "pairs": { - "spot": { - "enabled": "BTC_USD,LTC_USD", - "available": "BCH_RUB,DASH_RUB,EOS_USD,ETH_TRY,GNT_ETH,LTC_USD,PTI_USDT,XRP_BTC,EXM_BTC,BTG_BTC,ETC_RUB,BTG_USD,NEO_RUB,XMR_BTC,ZRX_ETH,MNX_BTC,USDC_BTC,XRP_EUR,SMART_USD,EOS_BTC,MNX_ETH,ZEC_BTC,BCH_USD,WAVES_USD,TRX_BTC,XRP_TRY,DASH_USD,DOGE_USD,ETZ_USDT,GUSD_USD,MNC_BTC,ZEC_USD,DCR_BTC,DXT_USD,PTI_RUB,XMR_ETH,ZRX_USD,DAI_RUB,MNC_USD,XLM_TRY,DAI_BTC,BTC_EUR,LTC_EUR,OMG_BTC,PTI_EOS,SMART_RUB,XTZ_USD,HP_EXM,ADA_USD,OMG_ETH,QTUM_USD,TRX_RUB,USDC_ETH,USDC_USDT,USD_RUB,BTC_UAH,BCH_USDT,ETH_PLN,KICK_RUB,LSK_RUB,SMART_BTC,XMR_UAH,XRP_USD,GUSD_BTC,QTUM_ETH,USDT_EUR,BTC_RUB,DCR_UAH,ETH_RUB,DOGE_BTC,ETZ_BTC,INK_USD,LTC_UAH,BTT_UAH,BTC_USDT,MNC_ETH,XTZ_ETH,BTC_TRY,DXT_BTC,KICK_USDT,OMG_USD,WAVES_BTC,XLM_BTC,BTCZ_BTC,GNT_BTC,LSK_BTC,LTC_RUB,NEO_BTC,XEM_UAH,XMR_USD,ZAG_BTC,GAS_USD,LTC_BTC,TRX_UAH,XEM_EUR,XMR_RUB,XTZ_RUB,ETZ_ETH,ETC_BTC,GUSD_RUB,INK_BTC,LSK_USD,MNX_USD,SMART_EUR,VLX_BTC,BCH_ETH,XMR_EUR,ADA_ETH,QTUM_BTC,XEM_USD,ATMCASH_BTC,ADA_BTC,ETH_EUR,TRX_USD,USDC_USD,BCH_BTC,ETH_UAH,KICK_BTC,WAVES_RUB,XEM_BTC,ETH_BTC,BCH_EUR,BTT_BTC,ROOBEE_BTC,XLM_USD,XRP_ETH,ETH_USD,MKR_DAI,XTZ_BTC,DAI_USD,BCH_UAH,INK_ETH,KICK_ETH,MKR_BTC,NEO_USD,XRP_USDT,ZEC_EUR,BTC_USD,XRP_RUB,EOS_EUR,ETH_USDT,USDT_UAH,XRP_UAH,ZEC_RUB,HP_BTC,BTT_RUB,DAI_ETH,DASH_UAH,DASH_USDT,ETH_LTC,GAS_BTC,USDT_USD,BTG_ETH,XLM_RUB,WAVES_ETH,USDT_RUB,ZRX_BTC,DASH_BTC,DCR_RUB,ETC_USD,HB_BTC,PTI_BTC,BTC_PLN" + "futures": { + "assetEnabled": true, + "enabled": "ETH_USDCM,XBT_USDCM,SOL_USDTM", + "available": "XBT_USDTM,XBT_USDM,ETH_USDTM,BCH_USDTM,BSV_USDTM,LINK_USDTM,UNI_USDTM,YFI_USDTM,EOS_USDTM,DOT_USDTM,FIL_USDTM,ADA_USDTM,XRP_USDTM,LTC_USDTM,ETH_USDM,TRX_USDTM,GRT_USDTM,SUSHI_USDTM,XLM_USDTM,1INCH_USDTM,ZEC_USDTM,DASH_USDTM,DOT_USDM,XRP_USDM,AAVE_USDTM,KSM_USDTM,DOGE_USDTM,VET_USDTM,BNB_USDTM,SXP_USDTM,SOL_USDTM,CRV_USDTM,ALGO_USDTM,AVAX_USDTM,FTM_USDTM,MATIC_USDTM,THETA_USDTM,ATOM_USDTM,CHZ_USDTM,ENJ_USDTM,MANA_USDTM,DENT_USDTM,OCEAN_USDTM,BAT_USDTM,XEM_USDTM,QTUM_USDTM,XTZ_USDTM,SNX_USDTM,NEO_USDTM,ONT_USDTM,XMR_USDTM,COMP_USDTM,ETC_USDTM,WAVES_USDTM,BAND_USDTM,MKR_USDTM,RVN_USDTM,DGB_USDTM,SHIB_USDTM,ICP_USDTM,DYDX_USDTM,AXS_USDTM,HBAR_USDTM,EGLD_USDTM,ALICE_USDTM,YGG_USDTM,NEAR_USDTM,SAND_USDTM,C98_USDTM,ONE_USDTM,VRA_USDTM,GALA_USDTM,CHR_USDTM,LRC_USDTM,FLOW_USDTM,RNDR_USDTM,IOTX_USDTM,CRO_USDTM,WAXP_USDTM,PEOPLE_USDTM,OMG_USDTM,LINA_USDTM,IMX_USDTM,CELR_USDTM,ENS_USDTM,CELO_USDTM,CTSI_USDTM,ARPA_USDTM,KNC_USDTM,ROSE_USDTM,AGLD_USDTM,APE_USDTM,JASMY_USDTM,ZIL_USDTM,GMT_USDTM,RUNE_USDTM,LOOKS_USDTM,AUDIO_USDTM,KDA_USDTM,KAVA_USDTM,BAL_USDTM,GAL_USDTM,LUNA_USDTM,LUNC_USDTM,OP_USDTM,XCN_USDTM,UNFI_USDTM,LIT_USDTM,DUSK_USDTM,STORJ_USDTM,RSR_USDTM,OGN_USDTM,TRB_USDTM,PERP_USDTM,KLAY_USDTM,ANKR_USDTM,LDO_USDTM,WOO_USDTM,REN_USDTM,CVC_USDTM,INJ_USDTM,APT_USDTM,MASK_USDTM,REEF_USDTM,TON_USDTM,MAGIC_USDTM,CFX_USDTM,AGIX_USDTM,FXS_USDTM,FET_USDTM,AR_USDTM,GMX_USDTM,BLUR_USDTM,ASTR_USDTM,HIGH_USDTM,ACH_USDTM,STX_USDTM,SSV_USDTM,FLOKI_USDTM,CKB_USDTM,TRU_USDTM,QNT_USDTM,ETH_USDCM,MINA_USDTM,USDC_USDTM,T_USDTM,LQTY_USDTM,ARB_USDTM,DAR_USDTM,ID_USDTM,STG_USDTM,JOE_USDTM,RDNT_USDTM,DODO_USDTM,PAXG_USDTM,ZRX_USDTM,ICX_USDTM,HFT_USDTM,NKN_USDTM,HOOK_USDTM,ANT_USDTM,DC_USDTM,BEL_USDTM,SUI_USDTM,PEPE_USDTM,IDEX_USDTM,GNS_USDTM,CETUS_USDTM,KAS_USDTM,ORDI_USDTM,WOJAK_USDTM,POGAI_USDTM,UMA_USDTM,RAD_USDTM,XBT_USDCM,PHB_USDTM,FTT_USDTM,10000LADYS_USDTM,LEVER_USDTM,TURBO_USDTM,TOMO_USDTM,BOB_USDTM,KEY_USDTM,EDU_USDTM,MTL_USDTM,FLUX_USDTM,COMBO_USDTM,AMB_USDTM,ALPHA_USDTM,SFP_USDTM,MAV_USDTM,MDT_USDTM,XEC_USDTM,XVG_USDTM,1000PEPE2_USDTM,PENDLE_USDTM,STMX_USDTM,WLD_USDTM,LPT_USDTM,GTC_USDTM,BNT_USDTM,OXT_USDTM,BLZ_USDTM,SEI_USDTM,BAKE_USDTM,CYBER_USDTM,NMR_USDTM,FLM_USDTM,SPELL_USDTM,ARK_USDTM,XBT_MU23,XBT_MZ23", + "requestFormat": { + "uppercase": true, + "delimiter": "" + }, + "configFormat": { + "uppercase": true, + "delimiter": "_" } } - }, - "api": { - "authenticatedSupport": false, - "authenticatedWebsocketApiSupport": false, - "endpoints": { - "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" - }, - "credentials": { - "key": "Key", - "secret": "Secret" - }, - "credentialsValidator": { - "requiresKey": true, - "requiresSecret": true - } - }, - "features": { - "supports": { - "restAPI": true, - "restCapabilities": { - "tickerBatching": true, - "autoPairUpdates": true - }, - "websocketAPI": false, - "websocketCapabilities": {} - }, - "enabled": { - "autoPairUpdates": true, - "websocketAPI": false - } - }, - "bankAccounts": [ - { - "enabled": false, - "bankName": "", - "bankAddress": "", - "bankPostalCode": "", - "bankPostalCity": "", - "bankCountry": "", - "accountName": "", - "accountNumber": "", - "swiftCode": "", - "iban": "", - "supportedCurrencies": "" - } - ] + } }, - { - "name": "GateIO", - "enabled": true, - "verbose": false, - "httpTimeout": 15000000000, - "websocketResponseCheckTimeout": 30000000, - "websocketResponseMaxLimit": 7000000000, - "websocketTrafficTimeout": 30000000000, - "websocketOrderbookBufferLimit": 5, - "baseCurrencies": "USD", - "currencyPairs": { - "requestFormat": { - "uppercase": true, - "delimiter": "_" - }, - "configFormat": { - "uppercase": true, - "delimiter": "_" - }, - "useGlobalFormat": true, - "assetTypes": [ - "spot", - "option", - "futures", - "cross_margin", - "margin", - "delivery" - ], - "pairs": { - "spot": { - "enabled": "BTC_USDT,IHT_ETH,AME_ETH,CEUR_ETH,ALEPH_USDT,OMG_TRY,BTC_TRY,OGN_USDT,ALA_USDT", - "available": "IHT_ETH,AME_ETH,CEUR_ETH,ALEPH_USDT,OMG_TRY,BTC_TRY,OGN_USDT,ALA_USDT,HC_USDT,BTC_USDT,QNT_USDT,QTUM_ETH,MAHA_ETH,XCN_ETH,POOL_USDT,KGC_USDT,MCO2_USDT,HARD_USDT,GHNY_USDT,FTT_ETH,K21_ETH,FINE_USDT,REP_USDT,SBR_USDT,SKM_ETH,QLC_ETH,GAS_BTC,ALICE3L_USDT,BAO_USDT,FALCONS_USDT,ANT_USDT,VIDYX_USDT,DXCT_ETH,SMTY_ETH,HERO_USDT,SHARE_USDT,FIN_USDT,MTV_USDT,MOO_USDT,SMTY_USDT,ORAO_USDT,AE_ETH,SUSD_USDT,MAN_USDT,UNDEAD_USDT,MC_USDT,VET_USDT,WAXP_ETH,MDA_ETH,LYXE_USDT,SPS_USDT,STX_ETH,WSIENNA_USDT,NAOS_BTC,NFTX_USDT,OPUL_USDT,ICP3L_USDT,SFI_ETH,CTT_USDT,BSV3L_USDT,DFI_USDT,DIS_ETH,FET_USDT,ARG_USDT,VELO_USDT,NSBT_BTC,GSE_ETH,HNS_BTC,DOGEDASH_ETH,BACON_USDT,DUSK_USDT,MAPE_USDT,EGLD_ETH,TDROP_USDT,C983L_USDT,FAN_ETH,CZZ_USDT,FIU_USDT,SWRV_USDT,ONT_ETH,KINE_ETH,IMX_ETH,SPAY_ETH,CFG_BTC,RACA3S_USDT,UNO_ETH,DMLG_USDT,SAKE_ETH,ASM_USDT,CUSD_ETH,SUSD_ETH,ONC_USDT,DAI_USDT,VEGA_ETH,PYM_USDT,LTC_TRY,LOKA_USDT,NIF_USDT,BNC_USDT,PERL_ETH,MATIC3S_USDT,STMX_USDT,SKL_USDT,WLKN_USDT,XYO_ETH,AMPL3S_USDT,WEX_USDT,ULU_ETH,LIKE_ETH,INSUR_ETH,CAKE_ETH,SXP_ETH,COTI_USDT,ORT_USDT,RACA3L_USDT,GASDAO_USDT,AVA_USDT,OPA_USDT,ATS_USDT,VEGA_USDT,KILT_USDT,HIT_ETH,BRISE_USDT,SAUBER_USDT,SPS_ETH,FSN_USDT,EOS_ETH,KYL_USDT,REVV_ETH,SVT_ETH,XRP_USDT,DYDX3S_USDT,MANA3S_USDT,ICP_ETH,ALICE3S_USDT,PCX_USDT,LEMO_ETH,MKR_ETH,WOO3S_USDT,CART_ETH,MATIC_USDT,UNI_USD,MOBI_BTC,ICP3S_USDT,BEAM_BTC,CRO3S_USDT,FTT_USDT,IQ_ETH,TAP_USDT,MLT_USDT,RBN_USDT,AMPL3L_USDT,KINT_ETH,HECH_USDT,GAFI_ETH,WOO3L_USDT,TAI_USDT,HERA_USDT,AST_USDT,DHV_ETH,XAVA_USDT,LSS_USDT,SNX3S_USDT,PBR_USDT,XEND_ETH,SHR_ETH,PRQ_USDT,MATIC3L_USDT,WIT_ETH,LPOOL_USDT,PSP_USDT,BXC_USDT,CBK_USDT,REVO_BTC,MANA3L_USDT,ALPINE_USDT,DEGO_USDT,SIN_USDT,OCT_USDT,KZEN_USDT,L3P_USDT,FX_ETH,ONC_ETH,AXS_USD,BORA_USDT,XTZ_ETH,NEO3L_USDT,FROG_USDT,CHAMP_USDT,XNFT_USDT,BCH3S_USDT,FORT_USDT,XLM_TRY,TRX_TRY,CRPT_USDT,ROUTE_USDT,GLM_USDT,SLRS_ETH,TIMECHRONO_USDT,VRA_USDT,ONS_USDT,ZEC3L_USDT,KFT_ETH,TFD_ETH,FRA_USDT,RDN_ETH,BLANK_USDT,IOST3L_USDT,DDD_USDT,DOGE_USD,UNQ_USDT,API33S_USDT,AKRO_ETH,GITCOIN_USDT,THG_USDT,BDX_USDT,LTO_ETH,FLY_USDT,CREDIT_USDT,RENA_USDT,ZRX_ETH,CRP_ETH,NBOT_USDT,HT3L_USDT,DORA_ETH,LLT_SNET,ASD_USDT,XMR_USDT,SSV_BTC,FTM_USDT,XELS_USDT,MTL_ETH,ADX_ETH,API33L_USDT,PIG_USDT,RUNE_ETH,QRDO_BTC,THN_USDT,BCUG_USDT,EGG_ETH,GGM_USDT,HOTCROSS_USDT,SKYRIM_USDT,BTG_USDT,POT_USDT,CS_USDT,XVS_USDT,A5T_USDT,GOD_BTC,WAVES_USDT,LSK_BTC,BTT_TRY,YIN_USDT,PEOPLE_USDT,SPELL_ETH,POLC_USDT,BZZ3L_USDT,UNO_USDT,HDV_USDT,CELL_USDT,DAR_ETH,MIR_ETH,FODL_USDT,SRM_ETH,PROS_USDT,ORN_ETH,WAG_USDT,RBC_ETH,VENT_USDT,WND_USDT,AAA_ETH,BSCS_ETH,ZEC3S_USDT,DOS_USDT,HT3S_USDT,LAND_USDT,BCD_BTC,RING_USDT,FIRO_USDT,AUDIO_USDT,KUMA_USDT,SOLO_BTC,CRBN_USDT,MM_ETH,SAKE_USDT,XMARK_USDT,SLP_USDT,F2C_USDT,LUNA_USDT,ONIT_USDT,FTM3L_USDT,POPK_USDT,RFUEL_USDT,NEO3S_USDT,MIR_USDT,ETC_BTC,STETH_ETH,MANA_TRY,ALPACA_ETH,WAXL_USDT,EGS_USDT,DAR_USDT,KSM_USDT,XMARK_ETH,QTUM_USDT,C983S_USDT,INDI_ETH,DOGE3S_USDT,RVN_USDT,NOS_USDT,ALU_ETH,ALD_ETH,LUNC_USDT,ARES_ETH,BZZ3S_USDT,TNC_ETH,ONE_USDT,SENC_ETH,FTM3S_USDT,FLUX_USDT,STORJ_ETH,MTN_ETH,MNW_USDT,BLES_ETH,STG_ETH,LIME_ETH,WAGYU_USDT,XRP_TRY,XOR_ETH,ANGLE_USDT,DOGA_USDT,JFI_USDT,USDG_USDT,GRND_USDT,BOND_ETH,DMTR_USDT,YIN_ETH,ENJ_USDT,GOLDMINER_USDT,WIT_USDT,DOGE3L_USDT,FORM_USDT,LYXE_ETH,MLK_USDT,VR_USDT,DMS_USDT,LRC_TRY,ONX_USDT,ASK_USDT,ISP_ETH,TXT_USDT,IOEN_ETH,NIIFI_USDT,VRX_USDT,DOME_USDT,CTSI_USDT,ORBS_USDT,ZLW_ETH,FIL_USDT,FTI_ETH,CTK_USDT,ASR_USDT,GBPT_BTC,CBK_BTC,MBOX_ETH,RAM_USDT,IRIS_USDT,AME_USDT,KUB_USDT,ENV_USDT,RING_ETH,COTI3S_USDT,JULD_ETH,POLK_ETH,ACH3S_USDT,HYVE_ETH,MIX_ETH,RFT_USDT,ORAO_ETH,IHT_USDT,POLYPAD_USDT,CTRC_USDT,SFUND_USDT,MXC_BTC,DDD_BTC,CHESS_ETH,SHIB_USDT,SN_USDT,NFT_USDT,ASTRO_ETH,SOLO_USDT,TSHP_USDT,AMP_USDT,BTCST_ETH,VLXPAD_USDT,GAN_USDT,O3_USDT,WBTC_TRY,TULIP_USDT,GS_ETH,DX_ETH,NYZO_ETH,TT_USDT,SHILL_USDT,RATING_ETH,DUST_USDT,PSB_USDT,BFT1_USDT,GALA_ETH,XDC_USDT,LON3L_USDT,HE_USDT,ICE_ETH,LINK_ETH,SKU_USDT,QLC_USDT,DOMI_USDT,IDEA_USDT,METO_USDT,LIFE_ETH,ACH3L_USDT,POWR_ETH,VET_ETH,ALGO_USDT,BLIN_USDT,BAO_ETH,RBLS_USDT,TORN_ETH,VRT_USDT,BLANKV2_ETH,AUCTION_ETH,OLE_USDT,NWC_BTC,DOT5S_USDT,M RCH_ETH,SUNNY_ETH,GST_USDT,ENJ_TRY,KIBA_USDT,KLAP_USDT,SNTR_ETH,CELR_ETH,CHESS_USDT,XLM3L_USDT,LIQ_USDT,TRU_ETH,CHZ_USD,EPK_USDT,MED_ETH,BSCPAD_ETH,ZCN_USDT,AIOZ_ETH,FOR_ETH,CVC3L_USDT,MNY_USDT,SALT_USDT,CSTR_USDT,MPL_USDT,PLY_ETH,FIS_USDT,CHO_USDT,BICO_ETH,STOX_ETH,HIGH_USDT,SDAO_BTC,STEP_USD,CRV_BTC,SCRT_ETH,ROSE_USDT,SKILL_ETH,FRAX_USDT,BAGS_USDT,WIKEN_BTC,WOO_USDT,BBANK_ETH,SNX3L_USDT,XRD_ETH,VTHO_USDT,OKB3L_USDT,SAFEMOON_USDT,RAD_ETH,IOI_USDT,LAMB_USDT,CHZ_USDT,FAR_ETH,OKB3S_USDT,ELU_USDT,JGN_ETH,EOS3S_USDT,DBC_USDT,ATOM_USDT,ACH_ETH,LBLOCK_USDT,WZRD_USDT,OST_ETH,MEAN_USDT,IDEX_USDT,HOT_TRY,EWT_ETH,EMON_USDT,FXS_USDT,PSY_ETH,SIDUS_USDT,ATA_USDT,CVC3S_USDT,LOOKS_ETH,ALPA_ETH,CGG_ETH,CIR_ETH,PRT_ETH,LON3S_USDT,INJ_USDT,FIRE_ETH,MAHA_USDT,IOST3S_USDT,NU_ETH,LEO_BTC,VOXEL_USDT,CRV_USDT,EQX_USDT,WHALE_USDT,INJ_ETH,GRAP_USDT,AVAX3S_USDT,TIFI_USDT,C98_USDT,ERN_ETH,SUSHI_ETH,VET3S_USDT,KPAD_USDT,CRPT_ETH,CRO_USDT,AZY_USDT,LEMD_USDT,ETH2_ETH,BASE_ETH,TT_ETH,PERL_USDT,BANK_ETH,LST_ETH,PYR_ETH,RATIO_USDT,UMB_USDT,M ETALDR_USDT,SWINGBY_ETH,WICC_ETH,NUM_USDT,SHOE_USDT,BORING_ETH,SDN_USDT,GXS_BTC,ALICE_ETH,BRKL_USDT,GF_ETH,ELEC_USDT,SFG_USDT,COFIX_USDT,TIPS_ETH,FIL_BTC,CWAR_USDT,WILD_USDT,RENBTC_USDT,BNX_USDT,TRU_USDT,SWEAT_USDT,IOST_BTC,NVIR_USDT,1EARTH_USDT,ADAPAD_USDT,PPS_USDT,CUBE_USDT,DLC_USDT,DAFI_ETH,UNISTAKE_ETH,NFTL_USDT,ATOM_TRY,SHIB3S_USDT,BNB_USD,CNAME_USDT,GTH_ETH,ZCX_USDT,DYDX3L_USDT,ASTRO_USDT,GLQ_USDT,PROPS_USDT,AART_USDT,BTRST_ETH,KFT_USDT,AERGO_USDT,RUFF_ETH,EOS3L_USDT,API3_USDT,MINA_BTC,ETHA_ETH,AXIS_ETH,LOON_USDT,AVAX3L_USDT,VET3L_USDT,AE_USDT,SHX_USDT,LYM_USDT,DCR_BTC,LBK_USDT,QTC_USDT,LAVA_USDT,XCN_USDT,BRT_USDT,RSV_USDT,KIF_USDT,PSL_USDT,AZERO_USDT,LUNA_ETH,MILO_USDT,OGN_ETH,TOTM_USDT,BYN_ETH,MINA_USDT,PUNDIX_ETH,SRT_USDT,DG_ETH,IHC_USDT,SYS_ETH,TITA_USDT,COTI3L_USDT,DAG_USDT,DOT5L_USDT,TRADE_USDT,SHPING_USDT,NU_USDT,BLANK_ETH,PCNT_ETH,SCCP_USDT,POLS_USDT,NPT_USDT,MTA_USDT,YIELD_USDT,ZCN_ETH,DVP_ETH,KART_USDT,SYLO_USDT,MCRT_USDT,SPFC_USDT,BASE_USDT,ICX_USDT,PET_USDT,GZONE_USDT,RED_ETH,SBTC_USDT,BATH_ ETH,SOL_USD,NAFT_USDT,GMX_USDT,VADER_USDT,GTC_USDT,CVP_ETH,XRPBEAR_USDT,TIME_USDT,SXP_USDT,CITY_USDT,QASH_USDT,FAST_USDT,BCD_USDT,KNIGHT_USDT,BOO_ETH,ZODI_USDT,REI_USDT,PBX_ETH,SRM_USDT,LDO_ETH,ZEC_USDT,UFT_USDT,DAG_BTC,RIDE_USDT,ERN_USDT,T_USDT,CEEK_USDT,STI_USDT,IMX3S_USDT,ELA_USDT,MNGO_ETH,EHASH_ETH,BADGER_ETH,SUPE_USDT,AR3L_USDT,AUDIO_ETH,DOCK_ETH,QSP_USDT,FLM_USDT,AAVE3S_USDT,BOND_USDT,HT_USD,TARA_USDT,TRX_USDT,SPO_USDT,DSLA_USDT,LTC_BTC,DOGE_USDT,SLIM_ETH,ALN_ETH,CFX3S_USDT,FXS_ETH,RARE_ETH,VLXPAD_ETH,ETH_USD,SDN_BTC,QUICK_USDT,UTK_USDT,XPNET_USDT,TRB_USDT,LAZIO_USDT,FTM_TRY,ALPHA_ETH,CVC_ETH,WSG_USDT,UNI_ETH,DASH3L_USDT,BTL_USDT,CPOOL_USDT,MCG_USDT,SFP_ETH,REALM_USDT,RUFF_BTC,MOB_ETH,IBFK_USDT,ALPHA3S_USDT,BLOK_USDT,WIKEN_USDT,OMG3S_USDT,UTK_ETH,BCH5S_USDT,MED_USDT,REN_USD,MAN_ETH,SLND_ETH,CGG_USDT,CRE_USDT,SOURCE_USDT,ABT_USDT,DPET_USDT,WOM_USDT,FOREX_ETH,SNFT1_USDT,RIF_USDT,BENQI_USDT,XCV_ETH,GTC_BTC,ADA_TRY,LAT_USDT,ITGR_USDT,DLTA_USDT,SMT_USDT,APYS_USDT,MFT_ETH,ABT_ETH,STOX_USDT,ZRX_BTC,GMAT_USDT,R OOM_ETH,STORJ_BTC,RAZOR_USDT,RAGE_USDT,DOCK_USDT,RDN_USDT,MTR_USDT,NKN_USDT,SWASH_USDT,FX_USDT,POR_USDT,DENT_ETH,DERI_USDT,DFND_USDT,BLES_USDT,SLND_USDT,WNXM_ETH,CRTS_USDT,BTC3S_USDT,BKC_USDT,STEPG_ETH,THETA3L_USDT,NBS_BTC,AVAX_ETH,NANO_BTC,DEFILAND_ETH,LOOKS_USDT,BCX_BTC,BCH_USD,ETH3L_USDT,QLC_BTC,BCUG_ETH,RDF_USDT,DOGEDASH_USDT,ARSW_USDT,NEAR_ETH,QTCON_USDT,BABI_USDT,MBX_USDT,PNL_USDT,ODDZ_ETH,ATOM_BTC,XRP_BTC,BTCBULL_USDT,HMT_USDT,PORTO_USDT,STND_USDT,ETHW_ETH,LPT_USDT,LTC3L_USDT,TOKAU_USDT,QI_ETH,TVK_USDT,CWS_USDT,SWOP_USDT,WBTC_USDT,INSTAR_ETH,ICX_ETH,GALA5L_USDT,XTZ_BTC,AGS_USDT,TARA_BTC,DYDX_ETH,CATGIRL_USDT,SASHIMI_ETH,EPX_ETH,GCOIN_USDT,GDAO_USDT,MARS_ETH,OMG_USD,PMON_USDT,MNGO_USDT,TVK_ETH,SLG_USDT,MSOL_USDT,POWR_USDT,UOS_USDT,USDD_USDT,SLICE_USDT,ARRR_ETH,NSBT_USDT,STR_ETH,BEAM3L_USDT,BEL_USDT,MM_USDT,AXS_ETH,WEST_ETH,FTT3L_USDT,OMI_USDT,TIPS_USDT,SLC_ETH,SQUID_USDT,FEI_USDT,GEM_USDT,UMEE_USDT,DOGE_TRY,FCD_USDT,PVU_USDT,XED_ETH,LRN_ETH,NRFB_USDT,LION_USDT,BLACK_USDT,DOGE5S_USDT,CUDOS_USDT,PCNT_USDT ,OVR_USDT,ETC3S_USDT,CHR_ETH,MER_USDT,BOBA_USDT,FUEL_USDT,BAC_USDT,ONE3S_USDT,CONV_ETH,CDT_BTC,CELL_ETH,ASM_ETH,OPIUM_USDT,JST3L_USDT,BONDLY_USDT,RAZE_USDT,LIME_BTC,NFTX_ETH,PNK_ETH,LDO_USDT,DKS_USDT,ORO_USDT,LITH_USDT,ALPHR_ETH,INK_BTC,RLY_USDT,NEAR3S_USDT,XLM3S_USDT,AR_USDT,AKT_USDT,HCT_USDT,REEF_ETH,BZZ_USDT,SRM3L_USDT,AQDC_USDT,OPIUM_ETH,BAT_TRY,EWT_USDT,ALCX_ETH,CORN_USDT,HYDRA_USDT,RUNE_USD,STEP_USDT,CKB_BTC,MATTER_USDT,STSOL_ETH,CEEK_ETH,FXF_ETH,LIKE_USDT,HIT_USDT,LEO_USDT,COMP_USDT,BAL_USDT,LMR_USDT,AQT_USDT,BUY_ETH,LINK3S_USDT,ROOK_ETH,IMX_USDT,EFI_USDT,TAUR_USDT,OKT_ETH,GALO_USDT,MOOV_USDT,RUNE_USDT,TCP_USDT,ITEM_USDT,SCLP_USDT,RBC_USDT,SPI_USDT,ETC_USDT,RENBTC_BTC,CHICKS_USDT,KNOT_USDT,XEC3L_USDT,XCV_USDT,ETC_ETH,AAVE_TRY,APT_USDT,GNX_ETH,KISHU_USDT,AE_BTC,LIEN_USDT,CREAM_USDT,ATOM3S_USDT,OP_ETH,FORTH_ETH,PYR_USDT,KTN_ETH,TKO_ETH,METAG_USDT,ACE_USDT,CIR_USDT,BEAM_ETH,TCP_ETH,SRM_USD,CEL_USD,TRIBE3S_USDT,MESA_ETH,EVA_USDT,BBANK_USDT,BLANKV2_USDT,FORM_ETH,BAL3S_USDT,VISR_ETH,REVO_ETH,ALTB_USDT,KNC_US DT,GAS_USDT,SAFEMARS_USDT,TIP_USDT,VADER_ETH,NWC_USDT,VALUE_USDT,ATA_ETH,SSX_USDT,JOE_USDT,BAS_ETH,FITFI3S_USDT,BIT_USDT,QNT_ETH,RFOX_ETH,MSU_USDT,MSOL_ETH,CRV3L_USDT,OXT_USDT,SHFT_USDT,VERA_ETH,LYM_ETH,BP_USDT,KBOX_USDT,DOGNFT_ETH,PERP_USDT,VELO_ETH,SAO_USDT,DUCK2_USDT,DEFILAND_USDT,DUCK2_ETH,GLMR3L_USDT,SERO_ETH,MTS_USDT,STX_USDT,KEX_ETH,ZIG_USDT,CARDS_USDT,ANML_USDT,GALA_USDT,RAY3S_USDT,KAVA3L_USDT,GARD_USDT,GRT3L_USDT,BFC_USDT,NIFT_USDT,ORION_USDT,CTX_USDT,ASW_USDT,CERE_USDT,COMBO_ETH,MKR_USDT,MASK_USDT,MGA_USDT,AVAX_USDT,SKL3L_USDT,FRR_USDT,MV_USDT,BMI_ETH,SFIL_USDT,TEER_USDT,KLV_USDT,DMS_ETH,LBL_USDT,MKR3L_USDT,LEDU_BTC,XLM_BTC,MIST_ETH,OIN_USDT,CAKE_USDT,RNDR_USDT,STEPG_USDT,YCT_USDT,OPS_ETH,SHR_USDT,OXY_ETH" - }, - "option": { - "enabled": "BTC_USDT-20230217-28000-P,BTC_USDT-20221028-34000-P,BTC_USDT-20221028-40000-C", - "available": "BTC_USDT-20221028-26000-C,BTC_USDT-20221028-34000-P,BTC_USDT-20221028-40000-C,BTC_USDT-20221028-28000-P,BTC_USDT-20221028-34000-C,BTC_USDT-20221028-28000-C,BTC_USDT-20221028-36000-P,BTC_USDT-20221028-50000-P,BTC_USDT-20221028-36000-C,BTC_USDT-20221028-50000-C,BTC_USDT-20221028-21000-P,BTC_USDT-20221028-38000-P,BTC_USDT-20221028-21000-C,BTC_USDT-20221028-38000-C,BTC_USDT-20221028-23000-P,BTC_USDT-20221028-17000-P,BTC_USDT-20221028-23000-C,BTC_USDT-20221028-17000-C,BTC_USDT-20221028-25000-P,BTC_USDT-20221028-19000-P,BTC_USDT-20221028-25000-C,BTC_USDT-20221028-10000-P,BTC_USDT-20221028-19000-C,BTC_USDT-20221028-27000-P,BTC_USDT-20221028-10000-C,BTC_USDT-20221028-27000-C,BTC_USDT-20221028-12000-P,BTC_USDT-20221028-12000-C,BTC_USDT-20221028-20000-P,BTC_USDT-20221028-5000-P,BTC_USDT-20221028-14000-P,BTC_USDT-20221028-20000-C,BTC_USDT-20221028-45000-P,BTC_USDT-20221028-5000-C,BTC_USDT-20221028-14000-C,BTC_USDT-20221028-22000-P,BTC_USDT-20221028-45000-C,BTC_USDT-20221028-16000-P,BTC_USDT-20221028-22000-C,BTC_USDT-20221028-30000-P,BTC_USDT-20221028-16000-C,BTC_USDT-20221028-24000-P,BTC_USDT-20221028-30000-C,BTC_USDT-20221028-18000-P,BTC_USDT-20221028-24000-C,BTC_USDT-20221028-32000-P,BTC_USDT-20221028-18000-C,BTC_USDT-20221028-26000-P,BTC_USDT-20221028-32000-C,BTC_USDT-20221028-40000-P" - }, - "futures": { - "enabled": "ETH_USD,BTC_USD,KNC_USDT,OOKI_USDT,BIT_USDT,ZEC_USDT,SC_USDT,RVN_USDT,ICX_USDT", - "available": "ETH_USD,BTC_USD,KNC_USDT,OOKI_USDT,BIT_USDT,ZEC_USDT,SC_USDT,RVN_USDT,ICX_USDT,DUSK_USDT,BEL_USDT,REEF_USDT,ALCX_USDT,ASTR_USDT,INJ_USDT,CAKE_USDT,LAZIO_USDT,ONE_USDT,CEL_USDT,ETH_USDT,KLAY_USDT,COTI_USDT,MKISHU_USDT,MANA_USDT,MOVR_USDT,OMG_USDT,UNI_USDT,LTC_USDT,AAVE_USDT,DENT_USDT,QRDO_USDT,BNB_USDT,ALPHA_USDT,RAY_USDT,APE_USDT,CERE_USDT,STMX_USDT,XCN_USDT,OGN_USDT,OKB_USDT,DOT_USDT,TLM_USDT,BTM_USDT,ADA_USDT,ANKR_USDT,ANT_USDT,TRX_USDT,MTL_USDT,YFII_USDT,SUN_USDT,SAND_USDT,MBABYDOGE_USDT,WIN_USDT,LUNC_USDT,SRM_USDT,STG_USDT,BAT_USDT,AXS_USDT,SOL_USDT,MAKITA_USDT,BNT_USDT,BLZ_USDT,PSG_USDT,IOTA_USDT,BONK_USDT,RSR_USDT,PYR_USDT,FITFI_USDT,MKR_USDT,PERP_USDT,COMP_USDT,LINK_USDT,CHR_USDT,CFX_USDT,GARI_USDT,DGB_USDT,MBOX_USDT,WEMIX_USDT,DYDX_USDT,LUNA_USDT,HT_USDT,TRB_USDT,CTK_USDT,ACA_USDT,TFUEL_USDT,OCEAN_USDT,XLM_USDT,HOT_USDT,FTM_USDT,LPT_USDT,SOS_USDT,ALGO_USDT,SHIB_USDT,BSV_USDT,PORTO_USDT,SFP_USDT,SANTOS_USDT,BADGER_USDT,DAR_USDT,DEFI_USDT,XEM_USDT,ALICE_USDT,ICP_USDT,RARE_USDT,LRC_USDT,BAKE_USDT,FLUX_USDT,CRO_USDT,CVC_USDT,MINA_USDT,LIT_USDT,AUDIO_USDT,ZIL_USDT,XMR_USDT,FRONT_USDT,CTSI_USDT,AGLD_USDT,YGG_USDT,OP_USDT,ZRX_USDT,GT_USDT,XCH_USDT,VET_USDT,MOB_USDT,BICO_USDT,SLP_USDT,ACH_USDT,AR_USDT,CLV_USDT,IMX_USDT,SPELL_USDT,UNFI_USDT,SUSHI_USDT,FTT_USDT,HIGH_USDT,HNT_USDT,ALT_USDT,YFI_USDT,NEAR_USDT,NKN_USDT,XVS_USDT,BAND_USDT,LOKA_USDT,BCH_USDT,TOMO_USDT,WAVES_USDT,FIDA_USDT,DIA_USDT,ANC_USDT,CELO_USDT,CRV_USDT,FLM_USDT,GLMR_USDT,FIL_USDT,PEOPLE_USDT,WAXP_USDT,IOTX_USDT,ATOM_USDT,RLC_USDT,HBAR_USDT,REN_USDT,GMT_USDT,KAVA_USDT,KDA_USDT,GALA_USDT,STORJ_USDT,PUNDIX_USDT,BAL_USDT,XAUG_USDT,GRIN_USDT,SXP_USDT,AKRO_USDT,NEXO_USDT,CKB_USDT,API3_USDT,NEST_USDT,ETHW_USDT,TONCOIN_USDT,THETA_USDT,CREAM_USDT,BTC_USDT,GST_USDT,BEAM_USDT,HFT_USDT,KSM_USDT,RAD_USDT,QTUM_USDT,WOO_USDT,ATA_USDT,AVAX_USDT,EOS_USDT,SNX_USDT,AUCTION_USDT,XRP_USDT,GITCOIN_USDT,MATIC_USDT,ONT_USDT,LINA_USDT,DASH_USDT,MASK_USDT,ETC_USDT,JST_USDT,BSW_USDT,CONV_USDT,SKL_USDT,GAL_USDT,DODO_USDT,GRT_USDT,TRU_USDT,STX_USDT,CVX_USDT,JASMY_USDT,HIVE_USDT,EXCH_USDT,ROSE_USDT,SUPER_USDT,SCRT_USDT,USTC_USDT,ENJ_USDT,BTS_USDT,LOOKS_USDT,QNT_USDT,HOOK_USDT,FLOW_USDT,RUNE_USDT,APT_USDT,CHZ_USDT,DOGE_USDT,1INCH_USDT,PRIV_USDT,CSPR_USDT,C98_USDT,RACA_USDT,CELR_USDT,XEC_USDT,ENS_USDT,POND_USDT,NYM_USDT,PROM_USDT,IOST_USDT,ZEN_USDT,LDO_USDT,RNDR_USDT,REQ_USDT,DEGO_USDT,VRA_USDT,QUICK_USDT,VGX_USDT,XTZ_USDT,EGLD_USDT,POLS_USDT,ARPA_USDT,NFT_USDT" - }, - "cross_margin": { - "enabled": "BTC_USDT,ERN_USDT,T_USDT,CEEK_USDT,OGN_USDT,QNT_USDT,WOZX_USDT,ZEE_USDT,FUN_USDT,FLM_USDT,BOND_USDT", - "available": "ERN_USDT,T_USDT,CEEK_USDT,OGN_USDT,QNT_USDT,WOZX_USDT,ZEE_USDT,FUN_USDT,FLM_USDT,BOND_USDT,TARA_USDT,TRX_USDT,OXY_USDT,LON_USDT,DOGE_USDT,ISP_USDT,TWT_USDT,BAO_USDT,QUACK_USDT,ANT_USDT,VGX_USDT,ARPA_USDT,QUICK_USDT,UTK_USDT,HERO_USDT,WSG_USDT,BICO_USDT,MTV_USDT,VET_USDT,GARI_USDT,BCH_USDT,KLAY_USDT,WING_USDT,BLOK_USDT,SPS_USDT,WIKEN_USDT,WSIENNA_USDT,PUNDIX_USDT,FIC_USDT,ASTR_USDT,FET_USDT,VELO_USDT,BENQI_USDT,CWEB_USDT,RIF_USDT,UNI_USDT,ONG_USDT,ERG_USDT,ALPHA_USDT,CELO_USDT,XVG_USDT,GMAT_USDT,BTS_USDT,DOCK_USDT,GMT_USDT,DIA_USDT,CSPR_USDT,NKN_USDT,STAKE_USDT,SWASH_USDT,XEC_USDT,SWRV_USDT,QRDO_USDT,BLES_USDT,EOS_USDT,GRT_USDT,ASM_USDT,FIL6_USDT,GNO_USDT,EGLD_USDT,XYM_USDT,LOOKS_USDT,LOKA_USDT,BNC_USDT,BAS_USDT,SKL_USDT,STMX_USDT,CVC_USDT,DDOS_USDT,COTI_USDT,AVA_USDT,HMT_USDT,DF_USDT,LPT_USDT,XRP_USDT,TVK_USDT,FEVR_USDT,MBL_USDT,KIN_USDT,SPELL_USDT,MATIC_USDT,FTT_USDT,NMR_USDT,PMON_USDT,BNB_USDT,USDD_USDT,LSS_USDT,MDX_USDT,PRQ_USDT,ALPINE_USDT,DEGO_USDT,OMI_USDT,TIPS_USDT,OCT_USDT,FEI_USDT,UMEE_USDT,CRP_USDT,LION_USDT,YFI_USDT,DASH_USDT,REQ_USDT,SDAO_USDT,PNT_USDT,INSUR_USDT,OOKI_USDT,SUN_USDT,CRPT_USDT,BAC_USDT,DATA_USDT,LRN_USDT,JGN_USDT,KIMCHI_USDT,SUKU_USDT,VRA_USDT,AAVE_USDT,FTI_USDT,LDO_USDT,FRA_USDT,BLANK_USDT,NEAR_USDT,ZKS_USDT,MTRG_USDT,RLY_USDT,TCT_USDT,FLY_USDT,JST_USDT,YFII_USDT,AR_USDT,POLY_USDT,JULD_USDT,SOL_USDT,BZZ_USDT,AXS_USDT,ASD_USDT,XMR_USDT,FTM_USDT,HIT_USDT,LEO_USDT,LIT_USDT,PIG_USDT,COMP_USDT,ELON_USDT,IMX_USDT,EFI_USDT,XVS_USDT,WAVES_USDT,PEOPLE_USDT,SOS_USDT,RUNE_USDT,POLC_USDT,SCLP_USDT,BABYDOGE_USDT,KONO_USDT,SPI_USDT,ETC_USDT,MDA_USDT,MTL_USDT,BCHA_USDT,KISHU_USDT,SUNNY_USDT,PYR_USDT,XTZ_USDT,TRIBE_USDT,AUDIO_USDT,FIRO_USDT,MANA_USDT,OKB_USDT,DOG_USDT,SLP_USDT,KNC_USDT,GAS_USDT,LUNA_USDT,SAFEMARS_USDT,MIR_USDT,DAR_USDT,EGS_USDT,KSM_USDT,ATP_USDT,BIT_USDT,STORJ_USDT,XEM_USDT,QTUM_USDT,AGLD_USDT,RVN_USDT,OXT_USDT,SHFT_USDT,IOTX_USDT,LUNC_USDT,NEXO_USDT,AKITA_USDT,PERP_USDT,ONE_USDT,ETH_USDT,FLUX_USDT,FLOKI_USDT,STX_USDT,ANML_USDT,XPRT_USDT,GALA_USDT,GXS_USDT,TORN_USDT,KAI_USDT,1INCH_USDT,CHR_USDT,GAL_USDT,GLMR_USDT,CTX_USDT,CERE_USDT,CART_USDT,STRAX_USDT,MASK_USDT,MKR_USDT,AVAX_USDT,ENJ_USDT,YAM_USDT,ALPACA_USDT,DODO_USDT,MFT_USDT,CAKE_USDT,RNDR_USDT,CTSI_USDT,GRIN_USDT,MXC_USDT,ONT_USDT,ANKR_USDT,SLIM_USDT,FIL_USDT,CTK_USDT,ASR_USDT,FEG_USDT,SERO_USDT,RSS3_USDT,IRIS_USDT,XCH_USDT,ZRX_USDT,BAND_USDT,BADGER_USDT,DAO_USDT,EPS_USDT,THETA_USDT,BAKE_USDT,SHIB_USDT,MBOX_USDT,NBS_USDT,SNT_USDT,DREP_USDT,NFT_USDT,AUCTION_USDT,BOSON_USDT,O3_USDT,NULS_USDT,OMG_USDT,PEARL_USDT,HAPI_USDT,STG_USDT,IDV_USDT,HORD_USDT,ZIL_USDT,SUPER_USDT,DENT_USDT,REN_USDT,RAI_USDT,ZEN_USDT,ALGO_USDT,BLZ_USDT,BOR_USDT,SC_USDT,HEGIC_USDT,MOB_USDT,DORA_USDT,FOR_USDT,FLOW_USDT,RARI_USDT,DYDX_USDT,ATLAS_USDT,GST_USDT,REEF_USDT,HT_USDT,XYO_USDT,CHESS_USDT,BAT_USDT,NYM_USDT,RAMP_USDT,USDC_USDT,ICP_USDT,EPK_USDT,EXRD_USDT,DOT_USDT,COOK_USDT,CKB_USDT,YGG_USDT,CRU_USDT,ANC_USDT,FIS_USDT,ALCX_USDT,HIGH_USDT,BEAM_USDT,BSW_USDT,STAR_USDT,ROSE_USDT,CNNS_USDT,BZRX_USDT,WOO_USDT,SAFEMOON_USDT,VTHO_USDT,OM_USDT,LAMB_USDT,CHZ_USDT,AIOZ_USDT,EDEN_USDT,POND_USDT,ATOM_USDT,UNFI_USDT,FORTH_USDT,MLN_USDT,NEO_USDT,MOVR_USDT,RLC_USDT,FXS_USDT,ENS_USDT,ATA_USDT,XPR_USDT,NEST_USDT,XLM_USDT,AUTO_USDT,SNX_USDT,OCN_USDT,RSR_USDT,MITH_USDT,KAR_USDT,INJ_USDT,PLA_USDT,CYS_USDT,WAXP_USDT,VOXEL_USDT,CRV_USDT,FITFI_USDT,WHALE_USDT,WRX_USDT,TIDAL_USDT,C98_USDT,HNT_USDT,TONCOIN_USDT,DOGGY_USDT,SYS_USDT,NPXS_USDT,CRO_USDT,LEMD_USDT,RAY_USDT,PERL_USDT,CQT_USDT,CFX_USDT,TOMO_USDT,ACA_USDT,SDN_USDT,OKT_USDT,WILD_USDT,BNX_USDT,TRU_USDT,RACA_USDT,SWEAT_USDT,ACH_USDT,AKRO_USDT,BTM_USDT,TKO_USDT,GT_USDT,OCEAN_USDT,WNCG_USDT,BSV_USDT,GHST_USDT,CELR_USDT,LINA_USDT,SAND_USDT,APE_USDT,WICC_USDT,FIDA_USDT,ADA_USDT,PROPS_USDT,METIS_USDT,KAVA_USDT,AERGO_USDT,CONV_USDT,TFUEL_USDT,FRONT_USDT,API3_USDT,FARM_USDT,AE_USDT,LRC_USDT,IOTA_USDT,RFOX_USDT,PHA_USDT,XCN_USDT,NAS_USDT,KEEP_USDT,VIDY_USDT,HOT_USDT,MINA_USDT,ETHW_USDT,ALICE_USDT,HAI_USDT,BTC_USDT,LTC_USDT,LTO_USDT,DC_USDT,NU_USDT,IOST_USDT,RAD_USDT,POLS_USDT,OP_USDT,WXT_USDT,STR_USDT,YIELD_USDT,GM_USDT,SPA_USDT,BTCST_USDT,WEMIX_USDT,CLV_USDT,ICX_USDT,PET_USDT,STARL_USDT,HBAR_USDT,REDTOKEN_USDT,BTT_USDT,LINK_USDT,TLM_USDT,ARES_USDT,GTC_USDT,SUSHI_USDT,KEY_USDT,ALN_USDT,KDA_USDT,DVI_USDT,SXP_USDT,MAPS_USDT,BCD_USDT,SRM_USDT,WIN_USDT,ZEC_USDT,JASMY_USDT" - }, - "margin": { - "enabled": "BTC_USDT,ERN_USDT,T_USDT,CEEK_USDT,OGN_USDT,QNT_USDT,WOZX_USDT,ZEE_USDT,FUN_USDT,FLM_USDT,BOND_USDT", - "available": "BTC_USDT,ERN_USDT,T_USDT,CEEK_USDT,OGN_USDT,QNT_USDT,WOZX_USDT,ZEE_USDT,FUN_USDT,FLM_USDT,BOND_USDT,TARA_USDT,TRX_USDT,OXY_USDT,LON_USDT,DOGE_USDT,ISP_USDT,TWT_USDT,BAO_USDT,QUACK_USDT,ANT_USDT,VGX_USDT,ARPA_USDT,QUICK_USDT,UTK_USDT,HERO_USDT,WSG_USDT,BICO_USDT,MTV_USDT,VET_USDT,GARI_USDT,BCH_USDT,KLAY_USDT,WING_USDT,BLOK_USDT,SPS_USDT,WIKEN_USDT,WSIENNA_USDT,PUNDIX_USDT,FIC_USDT,ASTR_USDT,FET_USDT,VELO_USDT,BENQI_USDT,CWEB_USDT,RIF_USDT,UNI_USDT,ONG_USDT,ERG_USDT,ALPHA_USDT,CELO_USDT,XVG_USDT,GMAT_USDT,BTS_USDT,DOCK_USDT,GMT_USDT,DIA_USDT,CSPR_USDT,NKN_USDT,STAKE_USDT,SWASH_USDT,XEC_USDT,SWRV_USDT,QRDO_USDT,BLES_USDT,EOS_USDT,GRT_USDT,ASM_USDT,FIL6_USDT,GNO_USDT,EGLD_USDT,XYM_USDT,LOOKS_USDT,LOKA_USDT,BNC_USDT,BAS_USDT,SKL_USDT,STMX_USDT,CVC_USDT,DDOS_USDT,COTI_USDT,AVA_USDT,HMT_USDT,DF_USDT,LPT_USDT,XRP_USDT,TVK_USDT,FEVR_USDT,MBL_USDT,KIN_USDT,SPELL_USDT,MATIC_USDT,FTT_USDT,NMR_USDT,PMON_USDT,BNB_USDT,USDD_USDT,LSS_USDT,MDX_USDT,PRQ_USDT,ALPINE_USDT,DEGO_USDT,OMI_USDT,TIPS_USDT,OCT_USDT,FEI_USDT,UMEE_USDT,CRP_USDT,LION_USDT,YFI_USDT,DASH_USDT,REQ_USDT,SDAO_USDT,PNT_USDT,INSUR_USDT,OOKI_USDT,SUN_USDT,CRPT_USDT,BAC_USDT,DATA_USDT,LRN_USDT,JGN_USDT,KIMCHI_USDT,SUKU_USDT,VRA_USDT,AAVE_USDT,FTI_USDT,LDO_USDT,FRA_USDT,BLANK_USDT,NEAR_USDT,ZKS_USDT,MTRG_USDT,RLY_USDT,TCT_USDT,FLY_USDT,JST_USDT,YFII_USDT,AR_USDT,POLY_USDT,JULD_USDT,SOL_USDT,BZZ_USDT,AXS_USDT,ASD_USDT,XMR_USDT,FTM_USDT,HIT_USDT,LEO_USDT,LIT_USDT,PIG_USDT,COMP_USDT,ELON_USDT,IMX_USDT,EFI_USDT,XVS_USDT,WAVES_USDT,PEOPLE_USDT,SOS_USDT,RUNE_USDT,POLC_USDT,SCLP_USDT,BABYDOGE_USDT,KONO_USDT,SPI_USDT,ETC_USDT,MDA_USDT,MTL_USDT,BCHA_USDT,KISHU_USDT,SUNNY_USDT,PYR_USDT,XTZ_USDT,TRIBE_USDT,AUDIO_USDT,FIRO_USDT,MANA_USDT,OKB_USDT,DOG_USDT,SLP_USDT,KNC_USDT,GAS_USDT,LUNA_USDT,SAFEMARS_USDT,MIR_USDT,DAR_USDT,EGS_USDT,KSM_USDT,ATP_USDT,BIT_USDT,STORJ_USDT,XEM_USDT,QTUM_USDT,AGLD_USDT,RVN_USDT,OXT_USDT,SHFT_USDT,IOTX_USDT,LUNC_USDT,NEXO_USDT,AKITA_USDT,PERP_USDT,ONE_USDT,ETH_USDT,FLUX_USDT,FLOKI_USDT,STX_USDT,ANML_USDT,XPRT_USDT,GALA_USDT,GXS_USDT,TORN_USDT,KAI_USDT,1INCH_USDT,CHR_USDT,GAL_USDT,GLMR_USDT,CTX_USDT,CERE_USDT,CART_USDT,STRAX_USDT,MASK_USDT,MKR_USDT,AVAX_USDT,ENJ_USDT,YAM_USDT,ALPACA_USDT,DODO_USDT,MFT_USDT,CAKE_USDT,RNDR_USDT,CTSI_USDT,GRIN_USDT,MXC_USDT,ONT_USDT,ANKR_USDT,SLIM_USDT,FIL_USDT,CTK_USDT,ASR_USDT,FEG_USDT,SERO_USDT,RSS3_USDT,IRIS_USDT,XCH_USDT,ZRX_USDT,BAND_USDT,BADGER_USDT,DAO_USDT,EPS_USDT,THETA_USDT,BAKE_USDT,SHIB_USDT,MBOX_USDT,NBS_USDT,SNT_USDT,DREP_USDT,NFT_USDT,AUCTION_USDT,BOSON_USDT,O3_USDT,NULS_USDT,OMG_USDT,PEARL_USDT,HAPI_USDT,STG_USDT,IDV_USDT,HORD_USDT,ZIL_USDT,SUPER_USDT,DENT_USDT,REN_USDT,RAI_USDT,ZEN_USDT,ALGO_USDT,BLZ_USDT,BOR_USDT,SC_USDT,HEGIC_USDT,MOB_USDT,DORA_USDT,FOR_USDT,FLOW_USDT,RARI_USDT,DYDX_USDT,ATLAS_USDT,GST_USDT,REEF_USDT,HT_USDT,XYO_USDT,CHESS_USDT,BAT_USDT,NYM_USDT,RAMP_USDT,USDC_USDT,ICP_USDT,EPK_USDT,EXRD_USDT,DOT_USDT,COOK_USDT,CKB_USDT,YGG_USDT,CRU_USDT,ANC_USDT,FIS_USDT,ALCX_USDT,HIGH_USDT,BEAM_USDT,BSW_USDT,STAR_USDT,ROSE_USDT,CNNS_USDT,BZRX_USDT,WOO_USDT,SAFEMOON_USDT,VTHO_USDT,OM_USDT,LAMB_USDT,CHZ_USDT,AIOZ_USDT,EDEN_USDT,POND_USDT,ATOM_USDT,UNFI_USDT,FORTH_USDT,MLN_USDT,NEO_USDT,MOVR_USDT,RLC_USDT,FXS_USDT,ENS_USDT,ATA_USDT,XPR_USDT,NEST_USDT,XLM_USDT,AUTO_USDT,SNX_USDT,OCN_USDT,RSR_USDT,MITH_USDT,KAR_USDT,INJ_USDT,PLA_USDT,CYS_USDT,WAXP_USDT,VOXEL_USDT,CRV_USDT,FITFI_USDT,WHALE_USDT,WRX_USDT,TIDAL_USDT,C98_USDT,HNT_USDT,TONCOIN_USDT,DOGGY_USDT,SYS_USDT,NPXS_USDT,CRO_USDT,LEMD_USDT,RAY_USDT,PERL_USDT,CQT_USDT,CFX_USDT,TOMO_USDT,ACA_USDT,SDN_USDT,OKT_USDT,WILD_USDT,BNX_USDT,TRU_USDT,RACA_USDT,SWEAT_USDT,ACH_USDT,AKRO_USDT,BTM_USDT,TKO_USDT,GT_USDT,OCEAN_USDT,WNCG_USDT,BSV_USDT,GHST_USDT,CELR_USDT,LINA_USDT,SAND_USDT,APE_USDT,WICC_USDT,FIDA_USDT,ADA_USDT,PROPS_USDT,METIS_USDT,KAVA_USDT,AERGO_USDT,CONV_USDT,TFUEL_USDT,FRONT_USDT,API3_USDT,FARM_USDT,AE_USDT,LRC_USDT,IOTA_USDT,RFOX_USDT,PHA_USDT,XCN_USDT,NAS_USDT,KEEP_USDT,VIDY_USDT,HOT_USDT,MINA_USDT,ETHW_USDT,ALICE_USDT,HAI_USDT,LTC_USDT,LTO_USDT,DC_USDT,NU_USDT,IOST_USDT,RAD_USDT,POLS_USDT,OP_USDT,WXT_USDT,STR_USDT,YIELD_USDT,GM_USDT,SPA_USDT,BTCST_USDT,WEMIX_USDT,CLV_USDT,ICX_USDT,PET_USDT,STARL_USDT,HBAR_USDT,REDTOKEN_USDT,BTT_USDT,LINK_USDT,TLM_USDT,ARES_USDT,GTC_USDT,SUSHI_USDT,KEY_USDT,ALN_USDT,KDA_USDT,DVI_USDT,SXP_USDT,MAPS_USDT,BCD_USDT,SRM_USDT,WIN_USDT,ZEC_USDT,JASMY_USDT" - }, - "delivery": { - "enabled": "BTC_USD_20230331,BTC_USD_20221230,BTC_USDT_20221021,BTC_USDT_20221014", - "available": "BTC_USD_20221021,BTC_USD_20221014,BTC_USD_20230331,BTC_USD_20221230,BTC_USDT_20221021,BTC_USDT_20221014,BTC_USDT_20230331,BTC_USDT_20221230" - } - } - }, - "api": { - "authenticatedSupport": false, - "authenticatedWebsocketApiSupport": false, - "endpoints": { - "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" - }, - "credentials": { - "key": "Key", - "secret": "Secret" - }, - "credentialsValidator": { - "requiresKey": true, - "requiresSecret": true - } - }, - "features": { - "supports": { - "restAPI": true, - "restCapabilities": { - "tickerBatching": true, - "autoPairUpdates": true - }, - "websocketAPI": true, - "websocketCapabilities": {} - }, - "enabled": { - "autoPairUpdates": true, - "websocketAPI": true - } - }, - "bankAccounts": [ - { - "enabled": false, - "bankName": "", - "bankAddress": "", - "bankPostalCode": "", - "bankPostalCity": "", - "bankCountry": "", - "accountName": "", - "accountNumber": "", - "swiftCode": "", - "iban": "", - "supportedCurrencies": "" - } - ] + "api": { + "authenticatedSupport": true, + "authenticatedWebsocketApiSupport": true, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret", + "clientID": "ClientID" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true + } }, - { - "name": "Gemini", - "enabled": true, - "verbose": false, - "httpTimeout": 15000000000, - "websocketResponseCheckTimeout": 30000000, - "websocketResponseMaxLimit": 7000000000, - "websocketTrafficTimeout": 30000000000, - "websocketOrderbookBufferLimit": 5, - "baseCurrencies": "USD", - "currencyPairs": { - "requestFormat": { - "uppercase": true - }, - "configFormat": { - "uppercase": true - }, - "useGlobalFormat": true, - "assetTypes": [ - "spot" - ], - "pairs": { - "spot": { - "enabled": "BTCUSD", - "available": "BTCUSD,ETHBTC,ETHUSD,BCHUSD,BCHBTC,BCHETH,LTCUSD,LTCBTC,LTCETH,LTCBCH,ZECUSD,ZECBTC,ZECETH,ZECBCH,ZECLTC" - } - } - }, - "api": { - "authenticatedSupport": false, - "authenticatedWebsocketApiSupport": false, - "endpoints": { - "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" - }, - "credentials": { - "key": "Key", - "secret": "Secret" - }, - "credentialsValidator": { - "requiresKey": true, - "requiresSecret": true - } - }, - "features": { - "supports": { - "restAPI": true, - "restCapabilities": { - "autoPairUpdates": true - }, - "websocketAPI": true, - "websocketCapabilities": {} - }, - "enabled": { - "autoPairUpdates": true, - "websocketAPI": false - } - }, - "bankAccounts": [ - { - "enabled": false, - "bankName": "", - "bankAddress": "", - "bankPostalCode": "", - "bankPostalCity": "", - "bankCountry": "", - "accountName": "", - "accountNumber": "", - "swiftCode": "", - "iban": "", - "supportedCurrencies": "" - } - ] + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "tickerBatching": true, + "autoPairUpdates": true + }, + "websocketAPI": true, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": true + } }, - { - "name": "HitBTC", - "enabled": true, - "verbose": false, - "httpTimeout": 15000000000, - "websocketResponseCheckTimeout": 30000000, - "websocketResponseMaxLimit": 7000000000, - "websocketTrafficTimeout": 30000000000, - "websocketOrderbookBufferLimit": 5, - "baseCurrencies": "USD", - "currencyPairs": { - "requestFormat": { - "uppercase": true - }, - "configFormat": { - "uppercase": true, - "delimiter": "-" - }, - "useGlobalFormat": true, - "assetTypes": [ - "spot" - ], - "pairs": { - "spot": { - "enabled": "BTC-USD", - "available": "BCN-BTC,BTC-USD,DASH-BTC,DOGE-BTC,DOGE-USD,EMC-BTC,ETH-BTC,LSK-BTC,LTC-BTC,LTC-USD,NXT-BTC,SBD-BTC,SC-BTC,STEEM-BTC,XDN-BTC,XEM-BTC,XMR-BTC,ARDR-BTC,ZEC-BTC,WAVES-BTC,MAID-BTC,DGD-BTC,SNGLS-BTC,1ST-BTC,TRST-BTC,TIME-BTC,GNO-BTC,REP-BTC,XMR-USD,DASH-USD,ETH-USD,NXT-USD,ZRC-BTC,BOS-BTC,DCT-BTC,ANT-BTC,AEON-BTC,GUP-BTC,PLU-BTC,LUN-BTC,EDG-BTC,RLC-BTC,SWT-BTC,TKN-BTC,WINGS-BTC,XAUR-BTC,AE-BTC,PTOY-BTC,ZEC-USD,XEM-USD,BCN-USD,XDN-USD,MAID-USD,ETC-BTC,ETC-USD,PLBT-BTC,BNT-BTC,SNT-ETH,CVC-USD,PAY-ETH,OAX-ETH,OMG-ETH,BQX-ETH,XTZ-BTC,DICE-BTC,PTOY-ETH,1ST-ETH,XAUR-ETH,TIME-ETH,DICE-ETH,SWT-ETH,XMR-ETH,ETC-ETH,DASH-ETH,ZEC-ETH,PLU-ETH,GNO-ETH,XRP-BTC,STRAT-USD,STRAT-BTC,SNC-ETH,ADX-ETH,BET-ETH,EOS-ETH,DENT-ETH,SAN-ETH,EOS-BTC,EOS-USD,XTZ-ETH,XTZ-USD,MYB-ETH,SUR-ETH,IXT-ETH,PLR-ETH,TIX-ETH,PRO-ETH,AVT-ETH,EVX-USD,DLT-BTC,BNT-ETH,BNT-USD,MANA-USD,DNT-BTC,FYP-BTC,OPT-BTC,TNT-ETH,STX-BTC,STX-ETH,STX-USD,TNT-USD,TNT-BTC,ENG-ETH,XUC-USD,SNC-BTC,SNC-USD,OAX-USD,OAX-BTC,ZRX-BTC,ZRX-ETH,ZRX-USD,RVT-BTC,PPC-BTC,PPC-USD,QTUM-ETH,IGNIS-ETH,BMC-BTC,BMC-ETH,BMC-USD,CND-BTC,CND-ETH,CND-USD,CDT-ETH,CDT-USD,FUN-BTC,FUN-ETH,FUN-USD,HVN-BTC,HVN-ETH,POE-BTC,POE-ETH,AMB-USD,AMB-ETH,AMB-BTC,HPC-BTC,PPT-ETH,MTH-BTC,MTH-ETH,LRC-BTC,LRC-ETH,ICX-BTC,ICX-ETH,NEO-BTC,NEO-ETH,NEO-USD,CSNO-BTC,ICX-USD,IND-ETH,KICK-BTC,YOYOW-BTC,CDT-BTC,XVG-BTC,XVG-ETH,XVG-USD,DGB-BTC,DGB-ETH,DGB-USD,DCN-ETH,DCN-USD,VIBE-BTC,ENJ-BTC,ENJ-ETH,ENJ-USD,ZSC-BTC,ZSC-ETH,ZSC-USD,TRX-BTC,TRX-ETH,TRX-USD,ART-BTC,EVX-BTC,EVX-ETH,SUB-BTC,SUB-ETH,SUB-USD,WTC-BTC,BTM-BTC,BTM-ETH,BTM-USD,LIFE-BTC,VIB-BTC,VIB-ETH,VIB-USD,DRT-ETH,STU-USD,OMG-BTC,PAY-BTC,PPT-BTC,SNT-BTC,BTG-BTC,BTG-ETH,BTG-USD,SMART-BTC,SMART-ETH,SMART-USD,XUC-ETH,XUC-BTC,LA-ETH,EDO-BTC,EDO-ETH,EDO-USD,HGT-ETH,IXT-BTC,SCL-BTC,ETP-BTC,ETP-ETH,ETP-USD,NEBL-BTC,NEBL-ETH,ARN-BTC,ARN-ETH,STU-BTC,STU-ETH,GVT-ETH,BTX-BTC,LTC-ETH,BCN-ETH,MAID-ETH,NXT-ETH,STRAT-ETH,XDN-ETH,XEM-ETH,PLR-BTC,SUR-BTC,BQX-BTC,DOGE-ETH,AMM-BTC,AMM-ETH,AMM-USD,DBIX-BTC,PRE-BTC,ZAP-BTC,DOV-BTC,DOV-ETH,XRP-ETH,XRP-USD,HSR-BTC,LEND-BTC,LEND-ETH,SPF-ETH,SBTC-BTC,SBTC-ETH,LOC-BTC,LOC-ETH,LOC-USD,SWFTC-BTC,SWFTC-ETH,SWFTC-USD,STAR-ETH,SBTC-USD,STORM-BTC,DIM-ETH,DIM-USD,DIM-BTC,NGC-BTC,NGC-ETH,NGC-USD,EMC-ETH,EMC-USD,MCO-BTC,MCO-ETH,MCO-USD,MANA-ETH,MANA-BTC,CPAY-ETH,DATA-BTC,DATA-ETH,DATA-USD,UTT-BTC,UTT-ETH,UTT-USD,KMD-BTC,KMD-ETH,KMD-USD,QTUM-USD,QTUM-BTC,SNT-USD,OMG-USD,EKO-BTC,EKO-ETH,ADX-BTC,ADX-USD,LSK-ETH,LSK-USD,PLR-USD,SUR-USD,BQX-USD,DRT-USD,REP-ETH,REP-USD,WAXP-BTC,WAXP-ETH,WAXP-USD,C20-BTC,C20-ETH,IDH-BTC,IDH-ETH,IPL-BTC,COV-BTC,COV-ETH,SENT-BTC,SENT-ETH,SENT-USD,SMT-BTC,SMT-ETH,SMT-USD,CHAT-BTC,CHAT-ETH,CHAT-USD,TRAC-ETH,JNT-ETH,UTK-BTC,UTK-ETH,UTK-USD,GNX-ETH,CHSB-BTC,CHSB-ETH,DAY-BTC,DAY-ETH,DAY-USD,NEU-BTC,NEU-ETH,NEU-USD,TAU-BTC,FLP-BTC,FLP-ETH,FLP-USD,R-BTC,R-ETH,EKO-USD,BCPT-ETH,BCPT-USD,PKT-BTC,PKT-ETH,BETR-BTC,BETR-ETH,HAND-ETH,HAND-USD,CHP-ETH,BCPT-BTC,ACT-BTC,ACT-ETH,ACT-USD,ADA-BTC,ADA-ETH,ADA-USD,SIG-BTC,MTX-BTC,MTX-ETH,MTX-USD,WIZ-BTC,WIZ-ETH,WIZ-USD,DADI-BTC,DADI-ETH,BDG-ETH,DATX-BTC,DATX-ETH,TRUE-BTC,DRG-BTC,DRG-ETH,BANCA-BTC,BANCA-ETH,ZAP-ETH,ZAP-USD,AUTO-BTC,SOC-BTC,OCN-BTC,OCN-ETH,STQ-BTC,STQ-ETH,XLM-BTC,XLM-ETH,XLM-USD,IOTA-BTC,IOTA-ETH,IOTA-USD,DRT-BTC,BETR-USD,ERT-BTC,CRPT-BTC,CRPT-USD,MESH-BTC,MESH-ETH,MESH-USD,IHT-BTC,IHT-ETH,IHT-USD,SCC-BTC,YCC-BTC,DAN-BTC,TEL-BTC,TEL-ETH,NCT-BTC,NCT-ETH,NCT-USD,BMH-BTC,BANCA-USD,BERRY-BTC,BERRY-ETH,BERRY-USD,GBX-BTC,GBX-ETH,GBX-USD,SHIP-BTC,SHIP-ETH,NANO-BTC,NANO-ETH,NANO-USD,LNC-BTC,KIN-ETH,ARDR-USD,FOTA-ETH,FOTA-BTC,CVT-BTC,CVT-ETH,CVT-USD,STQ-USD,GNT-BTC,GNT-ETH,GNT-USD,GET-BTC,MITH-BTC,MITH-ETH,MITH-USD,DADI-USD,TKY-BTC,ACAT-BTC,ACAT-ETH,ACAT-USD,BTX-USD,WIKI-BTC,WIKI-ETH,WIKI-USD,ONT-BTC,ONT-ETH,ONT-USD,FTX-BTC,FTX-ETH,NAVI-BTC,VME-ETH,NAVI-ETH,LND-ETH,CSM-BTC,NANJ-BTC,NTK-BTC,NTK-ETH,NTK-USD,AUC-BTC,AUC-ETH,CMCT-BTC,CMCT-ETH,CMCT-USD,MAN-BTC,MAN-ETH,MAN-USD,PNT-BTC,PNT-ETH,FXT-BTC,NEXO-BTC,PAT-BTC,PAT-ETH,XMC-BTC,FXT-ETH,XMC-ETH,XMC-USD,FDZ-BTC,FDZ-ETH,FDZ-USD,SPD-BTC,SPD-ETH,MITX-BTC,TIV-BTC,B2G-BTC,B2G-USD,HBZ-BTC,FACE-BTC,FACE-ETH,HBZ-ETH,HBZ-USD,CPT-BTC,PAT-USD,HTML-BTC,HTML-ETH,MITX-ETH,BTS-BTC,BNK-BTC,BNK-ETH,BNK-USD,TIV-ETH,TIV-USD,CSM-ETH,CSM-USD,INK-BTC,IOST-BTC,INK-ETH,INK-USD,CBC-BTC,IOST-USD,ZIL-BTC,ABYSS-BTC,ABYSS-ETH,ZIL-USD,BCI-BTC,CBC-ETH,CBC-USD,PITCH-BTC,PITCH-ETH,HTML-USD,TDS-BTC,TDS-ETH,TDS-USD,SBD-ETH,SBD-USD,DPN-BTC,UUU-BTC,UUU-ETH,XBP-BTC,ELEC-BTC,ELEC-ETH,ELEC-USD,QNTU-BTC,QNTU-ETH,QNTU-USD,IPL-ETH,IPL-USD,CENNZ-BTC,CENNZ-ETH,SWM-BTC,SPF-USD,SPF-BTC,LCC-BTC,HGT-BTC,ETH-TUSD,BTC-TUSD,LTC-TUSD,XMR-TUSD,ZRX-TUSD,NEO-TUSD,USD-TUSD,BTC-DAI,ETH-DAI,MKR-DAI,EOS-DAI,USD-DAI,MKR-BTC,MKR-ETH,MKR-USD,TUSD-DAI,NEO-DAI,LTC-DAI,XMR-DAI,XRP-DAI,NEXO-ETH,NEXO-USD,DWS-BTC,DWS-ETH,DWS-USD,APPC-BTC,APPC-ETH,APPC-USD,BIT-ETH,SPC-BTC,SPC-ETH,SPC-USD,REX-BTC,REX-ETH,REX-USD,ELF-BTC,ELF-USD,BCD-BTC,BCD-USD,CVCOIN-BTC,CVCOIN-ETH,CVCOIN-USD,EDG-ETH,EDG-USD,NLC2-BTC,DASH-EURS,ZEC-EURS,BTC-EURS,EOS-EURS,ETH-EURS,LTC-EURS,NEO-EURS,XMR-EURS,XRP-EURS,EURS-USD,EURS-TUSD,EURS-DAI,MNX-USD,ROX-ETH,ZPR-ETH,MNX-BTC,MNX-ETH,KIND-BTC,KIND-ETH,ENGT-BTC,ENGT-ETH,PMA-BTC,PMA-ETH,TV-BTC,TV-ETH,TV-USD,BAT-BTC,BAT-ETH,BAT-USD,SRN-BTC,SRN-ETH,SRN-USD,SVD-BTC,SVD-ETH,SVD-USD,GST-BTC,GST-ETH,GST-USD,BNB-BTC,BNB-ETH,BNB-USD,DIT-BTC,DIT-ETH,POA20-BTC,PROC-BTC,POA20-ETH,POA20-USD,POA20-DAI,NIM-BTC,USE-BTC,USE-ETH,DAV-BTC,DAV-ETH,ABTC-BTC,NIM-ETH,ABA-BTC,ABA-ETH,ABA-USD,BCN-EOS,LTC-EOS,XMR-EOS,DASH-EOS,TRX-EOS,NEO-EOS,ZEC-EOS,LSK-EOS,XEM-EOS,XRP-EOS,RCN-BTC,RCN-ETH,RCN-USD,HMQ-BTC,HMQ-ETH,MYST-BTC,MYST-ETH,USD-GUSD,BTC-GUSD,ETH-GUSD,EOS-GUSD,AXPR-BTC,AXPR-ETH,DAG-BTC,DAG-ETH,BITS-BTC,BITS-ETH,BITS-USD,CDCC-BTC,CDCC-ETH,CDCC-USD,VET-BTC,VET-ETH,VET-USD,SILK-ETH,BOX-BTC,BOX-ETH,BOX-EURS,BOX-EOS,VOCO-BTC,VOCO-ETH,VOCO-USD,PASS-BTC,PASS-ETH,SLX-BTC,SLX-USD,PBTT-BTC,PMA-USD,TRAD-BTC,DGTX-BTC,DGTX-ETH,DGTX-USD,MRK-BTC,MRK-ETH,DGB-TUSD,SNBL-BTC,BCH-BTC,BCH-USD,BSV-BTC,BSV-USD,BKX-BTC,NPLC-BTC,NPLC-ETH,ETN-BTC,ETN-ETH,ETN-USD,DTR-BTC,DTR-ETH,TDP-BTC,HBT-ETH,PXG-BTC,PXG-USD,BTC-PAX,ETH-PAX,USD-PAX,BTC-USDC,ETH-USDC,USD-USDC,TUSD-USDC,DAI-USDC,EOS-PAX,CLO-BTC,CLO-ETH,CLO-USD,PETH-BTC,PETH-ETH,PETH-USD,BRD-BTC,BRD-ETH,NMR-BTC,SALT-BTC,SALT-ETH,POLY-BTC,POLY-ETH,POWR-BTC,POWR-ETH,STORJ-BTC,STORJ-ETH,STORJ-USD,MLN-BTC,MLN-ETH,BDG-BTC,POA-ETH,POA-BTC,POA-USD,POA-DAI,KIN-BTC,VEO-BTC,PLA-BTC,PLA-ETH,PLA-USD,BTT-BTC,BTT-USD,BTT-ETH,ZEN-BTC,ZEN-ETH,ZEN-USD,GRIN-BTC,GRIN-ETH,GRIN-USD,FET-BTC,HT-BTC,HT-USD,XZC-BTC,XZC-ETH,XZC-USD,VRA-BTC,VRA-ETH,BTC-KRWB,USD-KRWB,WBTC-ETH,CRO-BTC,CRO-ETH,CRO-USD,GAS-BTC,GAS-ETH,GAS-USD,ORMEUS-BTC,ORMEUS-ETH,SWM-ETH,SWM-USD,PRE-ETH,PHX-BTC,PHX-ETH,PHX-USD,BET-BTC,USD-EOSDT,BTC-EOSDT,ETH-EOSDT,EOS-EOSDT,DAI-EOSDT,NUT-BTC,NUT-EOS,NUT-USD,CUTE-BTC,CUTE-ETH,CUTE-USD,CUTE-EOS,XCON-BTC,DCR-BTC,DCR-ETH,DCR-USD,MG-BTC,MG-ETH,MG-EOS,MG-USD,GNX-BTC,PRO-BTC,EURS-EOSDT,TUSD-EOSDT,ECOIN-BTC,ECOIN-ETH,ECOIN-USD,AGI-BTC,LOOM-BTC,LOOM-ETH,BLZ-BTC,QKC-BTC,QKC-ETH,KNC-BTC,KNC-ETH,KNC-USD,KEY-BTC,KEY-ETH,ATOM-BTC,ATOM-USD,ATOM-ETH,BRDG-BTC,BRDG-ETH,BRDG-USD,MTL-BTC,MTL-ETH,EXP-BTC,BTCB-BTC,PBT-BTC,PBT-ETH,LINK-BTC,LINK-ETH,LINK-USD,USD-USDT20,PHB-BTC,BCH-ETH,BCH-DAI,BCH-TUSD,BCH-EURS,DAPP-BTC,DAPP-EOS,BTC-USDT20,DENT-BTC,DENT-USD,NJBC-BTC,NJBC-ETH,XRC-BTC,EOS-BCH,LTC-BCH,XRP-BCH,TRX-BCH,XLM-BCH,ETC-BCH,DASH-BCH,ZEC-BCH,BKX-USD,LAMB-BTC,NPXS-BTC,HBAR-BTC,HBAR-USD,ONE-BTC,RFR-BTC,RFR-USD,BUSD-USD,PAXG-BTC,PAXG-USD,REN-BTC,IGNIS-BTC,CEL-BTC,CEL-ETH,WIN-USD,ADK-BTC,PART-BTC,SOZ-BTC,SOZ-ETH,SOZ-USD,WAVES-USD,ADA-BCH,ONT-BCH,XMR-BCH,ATOM-BCH,LINK-BCH,OMG-BCH,WAVES-BCH,IOTX-BTC,HOT-BTC,SLV-BTC,HEDG-BTC,CHZ-BTC,CHZ-USD,COCOS-BTC,COCOS-USD,SEELE-BTC,SEELE-USD,MDA-BTC,LEO-USD,REM-BTC,REM-ETH,REM-USD,SCD-DAI,BTC-BUSD,RVN-BTC,BST-BTC,ERD-BTC,KRL-BTC,FTT-BTC,FTT-USD,RAISE-BTC,RAISE-ETH" - } - } - }, - "api": { - "authenticatedSupport": false, - "authenticatedWebsocketApiSupport": false, - "endpoints": { - "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" - }, - "credentials": { - "key": "Key", - "secret": "Secret" - }, - "credentialsValidator": { - "requiresKey": true, - "requiresSecret": true - } - }, - "features": { - "supports": { - "restAPI": true, - "restCapabilities": { - "tickerBatching": true, - "autoPairUpdates": true - }, - "websocketAPI": true, - "websocketCapabilities": {} - }, - "enabled": { - "autoPairUpdates": true, - "websocketAPI": false - } - }, - "bankAccounts": [ - { - "enabled": false, - "bankName": "", - "bankAddress": "", - "bankPostalCode": "", - "bankPostalCity": "", - "bankCountry": "", - "accountName": "", - "accountNumber": "", - "swiftCode": "", - "iban": "", - "supportedCurrencies": "" + "bankAccounts": [ + { + "enabled": false, + "bankName": "", + "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ] + }, + { + "name": "LBank", + "enabled": true, + "verbose": false, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, + "websocketOrderbookBufferLimit": 5, + "baseCurrencies": "USD", + "currencyPairs": { + "requestFormat": { + "uppercase": false, + "delimiter": "_" + }, + "configFormat": { + "uppercase": false, + "delimiter": "_" + }, + "useGlobalFormat": true, + "assetTypes": [ + "spot" + ], + "pairs": { + "spot": { + "enabled": "eth_btc", + "available": "FBC_USDT,GALT_USDT,IOEX_USDT,OATH_USDT,BLOC_USDT,BTC_USDT,ETH_USDT,ETH_BTC,ABBC_BTC,KISC_ETH,BXA_USDT,ATP_USDT,MAT_USDT,SKY_BTC,RNT_USDT,VENA_USDT,GRIN_USDT,IDA_USDT,PNT_USDT,OPX_USDT,VTHO_BTC,AMO_ETH,UBEX_BTC,EOS_BTC,UBEX_USDT,TNS_BTC,SAIT_ETH,DAX_BTC,DAX_ETH,DALI_USDT,VET_USDT,BCH_BTC,BCH_USDT,NEO_USDT,QTUM_USDT,ZEC_USDT,VET_BTC,PAI_BTC,PNT_BTC,NEO_BTC,DASH_BTC,LTC_BTC,ETC_BTC,QTUM_BTC,ZEC_BTC,SC_BTC,BTS_BTC,CPX_BTC,XWC_BTC,FIL6_BTC,FIL12_BTC,FIL36_BTC,EOS_USDT,UT_ETH,ELA_ETH,VET_ETH,VTHO_ETH,PAI_ETH,HER_ETH,PTT_ETH,TAC_ETH,IDHUB_ETH,SSC_ETH,SKM_ETH,PLY_ETH,EXT_ETH,EOS_ETH,YOYOW_ETH,TRX_ETH,QTUM_ETH,ZEC_ETH,BTS_ETH,BTM_ETH,MITH_ETH,NAS_ETH,MAN_ETH,DBC_ETH,BTO_ETH,DDD_ETH,CPX_ETH,CS_ETH,IHT_ETH,OCN_ETH,EKO_ETH,XWC_ETH,PUT_ETH,PNT_ETH,AAC_ETH,FIL6_ETH,FIL12_ETH,FIL36_ETH,SEER_ETH,BSB_ETH,CDC_ETH,GRAMS_ETH,DDMX_ETH,EAI_ETH,BNB_USDT,HT_USDT,KBC_BTC,KBC_USDT,MAI_USDT,PHV_USDT,GT_USDT,VOKEN_USDT,CYE_USDT,BRC_USDT,BTC_AUSD,DDMX_USDT,SEAL_USDT,SEOS_BTC,BTY_USDT,FO_USDT,DLX_USDT,BFC_USDT,LBK_USDT,SERO_USDT,MTV_USDT,CKB_USDT,ARPA_USDT,ZIP_USDT,AT_USDT,DOT_USDT,DILI_USDT,DUO_USDT,TEP_USDT,BIKI_USDT,MX_USDT,DNS_USDT,OKB_USDT,FLDT_USDT,CCTC_USDT,WIN_USDT,BTT_USDT,TRX_USDT,GRS_BTC,GST_USDT,GST_ETH,ABBC_USDT,UTK_USDT,GKI_USDT,BPX_USDT,SUTER_USDT,LT_USDT,LM_USDT,HTDF_USDT" } - ] + } }, - { - "name": "Huobi", - "enabled": true, - "verbose": false, - "httpTimeout": 15000000000, - "websocketResponseCheckTimeout": 30000000, - "websocketResponseMaxLimit": 7000000000, - "websocketTrafficTimeout": 30000000000, - "websocketOrderbookBufferLimit": 5, - "baseCurrencies": "USD", - "currencyPairs": { - "requestFormat": { - "uppercase": false - }, - "configFormat": { - "uppercase": true, - "delimiter": "-" - }, - "useGlobalFormat": true, - "assetTypes": [ - "spot", - "coinmarginedfutures", - "futures" - ], - "pairs": { - "coinmarginedfutures": { - "assetEnabled": true, - "enabled": "BTC-USD", - "available": "BTC-USD,ETH-USD,LINK-USD,DOT-USD,ADA-USD,LTC-USD,XRP-USD,TRX-USD,DOGE-USD", - "requestFormat": { - "uppercase": true, - "delimiter": "-" - }, - "configFormat": { - "uppercase": true, - "delimiter": "-" - } - }, - "futures": { - "assetEnabled": true, - "enabled": "BTC-230915", - "available": "BTC-230915,BTC-230922,BTC-230929,ETH-230915,ETH-230922,ETH-230929,TRX-230915,TRX-230922", - "requestFormat": { - "uppercase": true - }, - "configFormat": { - "uppercase": true, - "delimiter": "-" - } - }, - "spot": { - "enabled": "BTC-USDT", - "available": "PROPY-ETH,IOTA-BTC,UGAS-ETH,PAI-USDT,BSV-HUSD,MTX-ETH,BCH-BTC,LTC-HT,SOC-USDT,WXT-BTC,SALT-BTC,RCN-ETH,PNT-ETH,TT-USDT,AIDOC-ETH,BIX-BTC,OCN-USDT,QTUM-ETH,KCASH-ETH,SNT-USDT,LUN-BTC,QASH-BTC,ITC-BTC,NAS-BTC,XMR-BTC,TNT-ETH,UC-ETH,FAIR-BTC,PC-ETH,YEE-BTC,PAY-ETH,XMX-BTC,CRE-USDT,BAT-ETH,BHT-USDT,CKB-HT,LAMB-HT,AE-USDT,QUN-ETH,LYM-BTC,BCH-HT,BHT-BTC,RUFF-ETH,CNN-BTC,FOR-USDT,GTC-ETH,TRX-ETH,ELA-USDT,ACT-ETH,SMT-ETH,BUT-ETH,BCH-USDT,ICX-BTC,MEET-BTC,NCC-BTC,APPC-BTC,GVE-ETH,TNB-BTC,STEEM-ETH,18C-ETH,LBA-BTC,EKO-BTC,REQ-BTC,SOC-BTC,BOX-ETH,ELF-BTC,ZRX-ETH,LET-USDT,HT-BTC,TUSD-HUSD,EGCC-BTC,WTC-BTC,ATP-USDT,DOCK-USDT,PAI-BTC,ONT-ETH,IRIS-BTC,BTT-ETH,SC-BTC,XZC-BTC,LBA-USDT,HT-USDT,VET-ETH,KMD-ETH,SHE-ETH,PORTAL-BTC,ONE-BTC,BIX-USDT,RCCC-BTC,SKM-USDT,XTZ-ETH,SWFTC-BTC,RSR-BTC,LINK-ETH,DATX-BTC,HPT-HT,GET-ETH,BLZ-ETH,CTXC-USDT,CNNS-USDT,PVT-HT,ITC-USDT,LTC-BTC,NCASH-BTC,HOT-ETH,ADA-USDT,ADX-BTC,NODE-USDT,TRIO-BTC,GXC-ETH,SNT-BTC,FOR-BTC,DBC-BTC,UUU-USDT,CVCOIN-ETH,RSR-USDT,CRO-USDT,OCN-BTC,NEW-USDT,EGT-USDT,MANA-BTC,CMT-USDT,WXT-HT,XRP-BTC,MT-ETH,PAX-HUSD,LSK-ETH,IOTA-USDT,SRN-ETH,ZIL-ETH,ELF-USDT,LXT-ETH,LAMB-BTC,CRE-HT,CKB-BTC,XVG-BTC,BSV-BTC,BFT-BTC,WPR-ETH,HT-HUSD,POWR-BTC,MANA-ETH,ENG-ETH,ZJLT-ETH,SNC-ETH,ATOM-ETH,WICC-USDT,KAN-ETH,DGD-BTC,VSYS-HT,BCD-BTC,BTM-ETH,DOGE-USDT,MEX-BTC,BTG-BTC,DAC-ETH,DAT-BTC,GRS-ETH,ADX-ETH,EM-HT,GXC-USDT,CVC-BTC,OMG-ETH,SSP-ETH,OGO-HT,CMT-ETH,POLY-ETH,XZC-USDT,THETA-USDT,XEM-USDT,LOL-USDT,BCH-HUSD,GSC-BTC,DOGE-ETH,MDS-BTC,BTS-ETH,CTXC-BTC,MCO-BTC,BCX-BTC,ZLA-ETH,EKT-USDT,MAN-BTC,BLZ-BTC,ATOM-USDT,LOL-BTC,HPT-USDT,EM-BTC,EOS-USDT,WAN-BTC,GNT-BTC,CRO-BTC,MANA-USDT,SEELE-USDT,FSN-BTC,VIDY-HT,USDC-HUSD,LTC-HUSD,XRP-USDT,VSYS-BTC,STORJ-BTC,LOOM-ETH,SKM-BTC,LINK-USDT,TT-HT,QSP-ETH,ETN-BTC,FSN-HT,NODE-BTC,HC-USDT,PHX-BTC,XLM-BTC,RCCC-ETH,LTC-USDT,UUU-BTC,SEELE-ETH,PVT-BTC,HC-ETH,REN-ETH,KAN-USDT,EOS-ETH,BSV-USDT,BTS-USDT,KMD-BTC,OGO-USDT,THETA-ETH,MUSK-BTC,CNNS-HT,ETC-BTC,COVA-BTC,BTT-TRX,XMR-USDT,MTN-ETH,QUN-BTC,NAS-USDT,ELA-ETH,HIT-ETH,BTT-USDT,EKT-ETH,TOS-BTC,GAS-ETH,DCR-USDT,ONT-BTC,NEW-HT,NEXO-BTC,ETH-USDT,WXT-USDT,FOR-HT,ADA-BTC,EVX-ETH,VET-BTC,ZEC-USDT,NANO-ETH,IOST-HT,BCV-ETH,REN-USDT,NULS-ETH,ACT-USDT,LET-ETH,BTM-USDT,MEET-ETH,AKRO-HT,ARDR-BTC,DCR-ETH,NANO-USDT,BTC-HUSD,ALGO-BTC,IIC-ETH,BHD-BTC,KNC-ETH,ATP-BTC,ZRX-BTC,ABT-BTC,18C-BTC,XMR-ETH,WAXP-BTC,CVNT-BTC,MX-USDT,OST-ETH,NKN-BTC,TOPC-BTC,GNX-BTC,FTT-USDT,ONE-HT,DGB-ETH,NULS-USDT,DASH-BTC,UIP-BTC,KCASH-HT,WICC-ETH,EKO-ETH,EGT-HT,IRIS-USDT,STK-ETH,MXC-BTC,NAS-ETH,OMG-USDT,SMT-BTC,BUT-BTC,HIT-USDT,BAT-BTC,IRIS-ETH,NKN-HT,PC-BTC,TOP-USDT,GTC-BTC,LSK-BTC,ITC-ETH,DTA-BTC,HOT-BTC,BTT-BTC,FAIR-ETH,DOCK-ETH,QTUM-BTC,ZEN-BTC,ZIL-BTC,RCN-BTC,FTI-BTC,BHD-USDT,VIDY-USDT,LUN-ETH,DBC-ETH,TOPC-ETH,IIC-BTC,STEEM-USDT,IOTA-ETH,KCASH-BTC,RUFF-BTC,APPC-ETH,MT-BTC,SOC-ETH,GT-HT,PROPY-BTC,AIDOC-BTC,ACT-BTC,LYM-ETH,CHAT-BTC,SWFTC-ETH,ETH-BTC,UIP-USDT,UGAS-BTC,XRP-HUSD,ALGO-USDT,TNT-BTC,ONT-USDT,YEE-ETH,AKRO-BTC,TRX-USDT,OCN-ETH,SRN-BTC,DASH-USDT,XMX-ETH,NANO-BTC,QASH-ETH,EOS-HT,GT-BTC,XTZ-USDT,ARPA-USDT,SALT-ETH,BKBT-ETH,MTX-BTC,SMT-USDT,GXC-BTC,VIDY-BTC,FTT-HT,LAMB-ETH,TRX-BTC,TRIO-ETH,BFT-ETH,LINK-BTC,AE-ETH,NULS-BTC,BHD-HT,AST-ETH,NEO-USDT,EDU-BTC,CVCOIN-BTC,GVE-BTC,GET-BTC,ZRX-USDT,ELF-ETH,DATX-ETH,ADA-ETH,TOP-HT,NCASH-ETH,QTUM-USDT,ETC-HT,ZIL-USDT,TNB-ETH,BIX-ETH,SHE-BTC,PNT-BTC,BTC-USDT,PORTAL-ETH,WAVES-USDT,XZC-ETH,HT-ETH,POLY-BTC,MCO-ETH,MUSK-ETH,PAI-ETH,LXT-USDT,UTK-BTC,RTE-BTC,NCC-ETH,HB10-USDT,BOX-BTC,RDN-ETH,ARPA-BTC,LBA-ETH,CNN-ETH,AAC-ETH,XTZ-BTC,IDT-BTC,AKRO-USDT,IOST-BTC,GT-USDT,WAN-ETH,ETN-ETH,PVT-USDT,NEO-BTC,WAVES-ETH,ONE-USDT,ZEC-BTC,SKM-HT,IOST-ETH,NPXS-ETH,CVC-ETH,CMT-BTC,COVA-ETH,ARDR-ETH,RDN-BTC,DCR-BTC,REN-BTC,YCC-ETH,MX-HT,NEXO-ETH,XLM-ETH,YCC-BTC,ENG-BTC,CNNS-BTC,ZLA-BTC,QSP-BTC,MAN-ETH,UUU-ETH,ETH-HUSD,RTE-ETH,ATP-HT,BTM-BTC,DAC-BTC,TOS-ETH,LAMB-USDT,DASH-HT,NPXS-BTC,NEW-BTC,FTT-BTC,EOS-HUSD,GRS-BTC,POWR-ETH,VET-USDT,AAC-BTC,MX-BTC,MTN-BTC,XVG-ETH,GNX-ETH,SSP-BTC,WAVES-BTC,EGT-BTC,CTXC-ETH,IDT-ETH,STK-BTC,WICC-BTC,UTK-ETH,CRO-HT,LXT-BTC,GSC-ETH,OMG-BTC,XRP-HT,DGB-BTC,IOST-USDT,CVNT-ETH,GAS-BTC,HIT-BTC,CKB-USDT,ARPA-HT,RUFF-USDT,HC-BTC,WTC-ETH,MDS-USDT,ABT-ETH,ALGO-ETH,BIFI-BTC,KNC-BTC,TT-BTC,LET-BTC,NKN-USDT,PAY-BTC,DTA-USDT,AE-BTC,UC-BTC,VSYS-USDT,USDT-HUSD,EOS-BTC,STEEM-BTC,DOGE-BTC,NODE-HT,MDS-ETH,CRE-BTC,GNT-USDT,UIP-ETH,AST-BTC,XEM-BTC,ZEN-ETH,EDU-ETH,MEX-ETH,EKT-BTC,CVC-USDT,WAXP-ETH,REQ-ETH,OST-BTC,STORJ-USDT,SBTC-BTC,DGD-ETH,SC-ETH,WTC-USDT,THETA-BTC,DTA-ETH,BCV-BTC,SNC-BTC,RSR-HT,KAN-BTC,ELA-BTC,ATOM-BTC,BKBT-BTC,FSN-USDT,EM-USDT,WPR-BTC,TOP-BTC,BTS-BTC,EGCC-ETH,MTL-BTC,GNT-ETH,SEELE-BTC,EVX-BTC,FTI-ETH,BAT-USDT,MT-HT,LOL-HT,ICX-ETH,LOOM-BTC,ZJLT-BTC,XLM-USDT,OGO-BTC,DOCK-BTC,CHAT-ETH,DAT-ETH,ETC-USDT,HPT-BTC,BHT-HT" - } - } - }, - "api": { - "authenticatedSupport": false, - "authenticatedWebsocketApiSupport": false, - "endpoints": { - "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" - }, - "credentials": { - "key": "Key", - "secret": "Secret" - }, - "credentialsValidator": { - "requiresKey": true, - "requiresSecret": true - } - }, - "features": { - "supports": { - "restAPI": true, - "restCapabilities": { - "tickerBatching": true, - "autoPairUpdates": true - }, - "websocketAPI": true, - "websocketCapabilities": {} - }, - "enabled": { - "autoPairUpdates": true, - "websocketAPI": false - } - }, - "bankAccounts": [ - { - "enabled": false, - "bankName": "", - "bankAddress": "", - "bankPostalCode": "", - "bankPostalCity": "", - "bankCountry": "", - "accountName": "", - "accountNumber": "", - "swiftCode": "", - "iban": "", - "supportedCurrencies": "" - } - ] + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true + } }, - { - "name": "Kraken", - "enabled": true, - "verbose": false, - "httpTimeout": 15000000000, - "websocketResponseCheckTimeout": 30000000, - "websocketResponseMaxLimit": 7000000000, - "websocketTrafficTimeout": 30000000000, - "websocketOrderbookBufferLimit": 5, - "baseCurrencies": "EUR,USD,CAD,GBP,JPY", - "currencyPairs": { - "requestFormat": { - "uppercase": true, - "separator": "," - }, - "configFormat": { - "uppercase": true, - "delimiter": "-", - "separator": "," - }, - "useGlobalFormat": true, - "assetTypes": [ - "spot" - ], - "pairs": { - "futures": { - "assetEnabled": true, - "enabled": "PF_XBTUSD", - "available": "PI_XBTUSD,PI_ETHUSD,PI_LTCUSD,PI_BCHUSD,PI_XRPUSD,PF_XBTUSD,PF_ETHUSD,PF_ADAUSD,PF_XRPUSD,PF_SOLUSD,PF_UNIUSD,PF_ATOMUSD,PF_BCHUSD,PF_DOTUSD,PF_EOSUSD,PF_FILUSD,PF_LINKUSD,PF_LTCUSD,PF_MATICUSD,PF_DEFIUSD,PF_AVAXUSD,PF_XMRUSD,PF_GMTUSD,PF_LUNA2USD,PF_OPUSD,PF_NEARUSD,PF_APEUSD,PF_WAVESUSD,PF_DOGEUSD,PF_FTMUSD,PF_TRXUSD,PF_MANAUSD,PF_CRVUSD,PF_AAVEUSD,PF_SNXUSD,PF_XTZUSD,PF_XLMUSD,PF_ALGOUSD,PF_SANDUSD,PF_OMGUSD,PF_ENJUSD,PF_COMPUSD,PF_YFIUSD,PF_CHZUSD,PF_LPTUSD,PF_BATUSD,PF_MKRUSD,PF_AXSUSD,PF_GALAUSD,PF_ETCUSD,PF_KAVAUSD,PF_LRCUSD,PF_KSMUSD,PF_GRTUSD,PF_FLOWUSD,PF_ZECUSD,PF_QTUMUSD,PF_DASHUSD,PF_1INCHUSD,PF_KNCUSD,PF_OGNUSD,PF_SUSHIUSD,PF_STORJUSD,PF_RUNEUSD,PF_EGLDUSD,PF_DYDXUSD,PF_RENUSD,PF_ANKRUSD,PF_ICPUSD,PF_ETHWUSD,PF_OCEANUSD,PF_BANDUSD,PF_BALUSD,PF_ALICEUSD,PF_ICXUSD,PF_ENSUSD,PF_AUDIOUSD,PF_ANTUSD,PF_SCUSD,PF_MINAUSD,PF_GLMRUSD,PF_THETAUSD,PF_QNTUSD,PF_IMXUSD,PF_APTUSD,PF_FLRUSD,PF_BLURUSD,PF_GMXUSD,PF_MASKUSD,PF_LDOUSD,PF_USDCUSD,PF_USDTUSD,PF_ARBUSD,PF_FETUSD,PF_STXUSD,PF_RNDRUSD,PF_CVXUSD,PF_WOOUSD,PF_JASMYUSD,PF_INJUSD,PF_ZRXUSD,PF_RLCUSD,PF_GALUSD,PF_SUIUSD,PF_PEPEUSD,FI_XBTUSD_231229,FI_ETHUSD_231229,PF_SHIBUSD,PF_TUSDUSD,PF_SXPUSD,PF_ARPAUSD,PF_ALPHAUSD,PF_STGUSD,PF_HFTUSD,PF_ACHUSD,PF_WLDUSD,PF_MOONUSD,PF_LINAUSD,PF_CFXUSD,PF_PAXGUSD,PF_AGLDUSD,FF_ETHUSD_231229,FF_XBTUSD_231229,FI_ETHUSD_240329,FI_XBTUSD_240329,FI_XRPUSD_231229,FI_BCHUSD_231229,FI_LTCUSD_231229,PF_FXSUSD,PF_SEIUSD,FF_XBTUSD_231027,FF_ETHUSD_231027,FI_XBTUSD_231027,FI_ETHUSD_231027,FI_XRPUSD_231027,FI_BCHUSD_231027,FI_LTCUSD_231027", - "requestFormat": { - "uppercase": true, - "delimiter": "_" - }, - "configFormat": { - "uppercase": true, - "delimiter": "_" - } - }, - "spot": { - "enabled": "XBT-USD", - "available": "ETH-GBP,XRP-USD,DAI-EUR,LSK-USD,BAT-EUR,BCH-EUR,EOS-ETH,GNO-EUR,ETH-CAD,XRP-JPY,ADA-ETH,DAI-USD,DASH-EUR,GNO-USD,LSK-XBT,ETH-EUR,ZEC-EUR,DASH-XBT,EOS-EUR,ETH-CHF,SC-ETH,SC-USD,WAVES-EUR,XBT-USD,ADA-EUR,LINK-USD,NANO-EUR,PAXG-USD,SC-EUR,WAVES-ETH,REP-USD,EOS-XBT,ETC-ETH,XMR-USD,LTC-USD,MLN-XBT,XTZ-CAD,XBT-GBP,ADA-CAD,XTZ-EUR,ETH-JPY,XTZ-USD,XDG-XBT,XLM-EUR,ATOM-USD,ATOM-XBT,OMG-EUR,ZEC-JPY,ADA-XBT,GNO-ETH,LINK-XBT,ETC-EUR,BCH-XBT,QTUM-ETH,XBT-CHF,LTC-EUR,ETH-DAI,LSK-EUR,NANO-USD,QTUM-XBT,XRP-XBT,ZEC-USD,BAT-ETH,LINK-ETH,XBT-CAD,BAT-USD,GNO-XBT,ICX-XBT,PAXG-ETH,DAI-USDT,NANO-ETH,OMG-ETH,WAVES-XBT,ZEC-XBT,BAT-XBT,NANO-XBT,XBT-JPY,DASH-USD,ICX-ETH,LSK-ETH,QTUM-CAD,REP-XBT,XMR-XBT,XRP-EUR,ATOM-CAD,OMG-USD,LTC-XBT,MLN-ETH,XTZ-ETH,EOS-USD,ICX-EUR,SC-XBT,ETC-USD,BCH-USD,ICX-USD,QTUM-USD,ETH-XBT,ETH-USD,OMG-XBT,PAXG-EUR,REP-EUR,ADA-USD,USDT-USD,XMR-EUR,XRP-CAD,ATOM-EUR,ETC-XBT,XBT-EUR,XLM-USD,ATOM-ETH,LINK-EUR,PAXG-XBT,WAVES-USD,REP-ETH,XLM-XBT,QTUM-EUR,XTZ-XBT" - } - } - }, - "api": { - "authenticatedSupport": false, - "authenticatedWebsocketApiSupport": false, - "endpoints": { - "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" - }, - "credentials": { - "key": "Key", - "secret": "Secret" - }, - "credentialsValidator": { - "requiresKey": true, - "requiresSecret": true, - "requiresBase64DecodeSecret": true - } - }, - "features": { - "supports": { - "restAPI": true, - "restCapabilities": { - "tickerBatching": true, - "autoPairUpdates": true - }, - "websocketAPI": true, - "websocketCapabilities": {} - }, - "enabled": { - "autoPairUpdates": true, - "websocketAPI": true - } - }, - "bankAccounts": [ - { - "enabled": false, - "bankName": "", - "bankAddress": "", - "bankPostalCode": "", - "bankPostalCity": "", - "bankCountry": "", - "accountName": "", - "accountNumber": "", - "swiftCode": "", - "iban": "", - "supportedCurrencies": "" - } - ] + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "tickerBatching": true, + "autoPairUpdates": true + }, + "websocketAPI": false, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": false + } }, - { - "name": "Kucoin", - "enabled": true, - "verbose": false, - "httpTimeout": 15000000000, - "websocketResponseCheckTimeout": 30000000, - "websocketResponseMaxLimit": 7000000000, - "websocketTrafficTimeout": 30000000000, - "websocketOrderbookBufferLimit": 5, - "baseCurrencies": "USD", - "currencyPairs": { - "assetTypes": [ - "spot", - "margin", - "futures" - ], - "pairs": { - "spot": { - "assetEnabled": true, - "enabled": "BTC-USDT,ETH-BTC,ETH-USDT,LTC-USDT", - "available": "BTC-USDT,MHC-ETH,MHC-BTC,OXEN-BTC,OXEN-ETH,NRG-BTC,AVA-USDT,FET-BTC,FET-ETH,ANKR-BTC,MHC-USDT,XMR-BTC,XMR-ETH,RIF-BTC,MTV-BTC,MTV-ETH,CRO-BTC,MTV-USDT,KMD-BTC,KMD-USDT,RFOX-USDT,TEL-USDT,TT-USDT,AERGO-USDT,XMR-USDT,TRX-KCS,ATOM-BTC,ATOM-ETH,ATOM-USDT,ATOM-KCS,ETN-USDT,FTM-USDT,TOMO-USDT,VSYS-USDT,OCEAN-BTC,OCEAN-ETH,CHR-BTC,CHR-USDT,FX-BTC,FX-ETH,NIM-BTC,NIM-ETH,COTI-BTC,COTI-USDT,NRG-ETH,BNB-BTC,BNB-USDT,JAR-BTC,JAR-USDT,ALGO-BTC,ALGO-ETH,ALGO-USDT,XEM-BTC,XEM-USDT,CIX100-USDT,XTZ-BTC,XTZ-USDT,ZEC-BTC,ZEC-USDT,ADA-BTC,ADA-USDT,REV-USDT,WXT-BTC,WXT-USDT,FORESTPLUS-BTC,FORESTPLUS-USDT,BOLT-BTC,BOLT-USDT,ARPA-USDT,CHZ-BTC,CHZ-USDT,DAPPT-BTC,DAPPT-USDT,NOIA-BTC,NOIA-USDT,WIN-BTC,WIN-USDT,DERO-BTC,DERO-USDT,BTT-USDT,EOSC-USDT,ENQ-BTC,ENQ-USDT,ONE-BTC,ONE-USDT,TOKO-BTC,TOKO-USDT,VID-BTC,VID-USDT,LUNA-USDT,SXP-BTC,SXP-USDT,AKRO-BTC,AKRO-USDT,ROOBEE-BTC,WIN-TRX,MAP-BTC,MAP-USDT,AMPL-BTC,AMPL-USDT,DAG-USDT,POL-USDT,ARX-USDT,NWC-BTC,NWC-USDT,BEPRO-BTC,BEPRO-USDT,VRA-BTC,VRA-USDT,KSM-BTC,KSM-USDT,DASH-USDT,SUTER-USDT,ACOIN-USDT,SUTER-BTC,SENSO-USDT,PRE-BTC,XDB-USDT,SYLO-USDT,WOM-USDT,SENSO-BTC,DGB-USDT,LYXE-USDT,LYXE-ETH,XDB-BTC,STX-BTC,STX-USDT,XSR-USDT,COMP-USDT,CRO-USDT,KAI-USDT,KAI-BTC,WEST-BTC,WEST-USDT,EWT-BTC,WAVES-USDT,WAVES-BTC,ORN-USDT,AMPL-ETH,BNS-USDT,MKR-USDT,SUKU-BTC,MLK-BTC,MLK-USDT,JST-USDT,KAI-ETH,SUKU-USDT,DIA-USDT,DIA-BTC,LINK-BTC,LINK-USDT,DOT-USDT,DOT-BTC,SHA-BTC,SHA-USDT,EWT-USDT,USDJ-USDT,EFX-BTC,CKB-BTC,CKB-USDT,UMA-USDT,ALEPH-USDT,VELO-USDT,SUN-USDT,BUY-USDT,YFI-USDT,OXEN-USDT,UNI-USDT,UOS-USDT,UOS-BTC,NIM-USDT,DEGO-USDT,DEGO-ETH,UDOO-ETH,RFUEL-USDT,FIL-USDT,UBX-ETH,REAP-USDT,AAVE-USDT,AAVE-BTC,TONE-BTC,TONE-ETH,ELF-ETH,AERGO-BTC,IOST-ETH,KCS-USDT,SNX-ETH,TOMO-ETH,KCS-ETH,DRGN-BTC,WAN-ETH,NULS-ETH,AXPR-ETH,POWR-BTC,QTUM-BTC,MANA-BTC,TEL-BTC,XYO-ETH,AXPR-BTC,ETN-BTC,COV-ETH,VET-BTC,KCS-BTC,CAPP-ETH,ONT-BTC,DRGN-ETH,DAG-ETH,TOMO-BTC,WAN-BTC,KNC-ETH,CRPT-ETH,LTC-USDT,BAX-ETH,BSV-USDT,DENT-ETH,AION-ETH,LYM-ETH,TRAC-ETH,ENJ-BTC,WAXP-BTC,DGB-BTC,ELA-BTC,ZIL-BTC,BSV-BTC,XLM-USDT,IOTX-ETH,SOUL-BTC,DOCK-BTC,AMB-ETH,TRX-BTC,XRP-TUSD,NULS-BTC,ETH-DAI,LSK-BTC,GMB-ETH,GMB-BTC,NEO-ETH,OMG-ETH,BTC-TUSD,KAT-USDT,KNC-BTC,ELF-BTC,MANA-ETH,ETC-USDT,ONT-ETH,MKR-BTC,KAT-BTC,XRP-USDC,XYO-BTC,SNT-ETH,ZRX-BTC,LOOM-ETH,AION-BTC,POWR-ETH,OLT-ETH,OLT-BTC,SNT-BTC,TRAC-BTC,XLM-ETH,ETH-USDT,BSV-ETH,TRX-ETH,ETN-ETH,AOA-USDT,BCD-BTC,DENT-BTC,DOCK-ETH,KEY-BTC,EOS-KCS,XLM-BTC,ADB-ETH,TIME-ETH,CVC-BTC,LSK-ETH,QKC-BTC,AMB-BTC,USDT-TUSD,ETC-ETH,XRP-BTC,NEO-KCS,SNX-USDT,CRPT-BTC,IOTX-BTC,LTC-ETH,XRP-KCS,ADB-BTC,LTC-KCS,TEL-ETH,DCR-ETH,LYM-USDT,USDT-USDC,ETH-USDC,DAG-BTC,AVA-BTC,BTC-USDT,WAXP-ETH,XRP-USDT,KEY-ETH,VET-ETH,FTM-BTC,USDT-DAI,QKC-ETH,ETH-BTC,MAN-BTC,CPC-ETH,TRX-USDT,BTC-DAI,ONT-USDT,DASH-ETH,BAX-BTC,AVA-ETH,LOOM-BTC,MVP-BTC,MKR-ETH,COV-BTC,CPC-BTC,REQ-ETH,EOS-BTC,LTC-BTC,XRP-ETH,CAPP-BTC,FTM-ETH,BCD-ETH,ZRX-ETH,DGB-ETH,VET-USDT,REQ-BTC,UTK-BTC,PLAY-BTC,UTK-ETH,SNX-BTC,MVP-ETH,NEO-BTC,SOUL-ETH,NEO-USDT,ELA-ETH,OMG-BTC,TIME-BTC,AOA-BTC,ETC-BTC,DCR-BTC,BTC-USDC,ENJ-ETH,IOST-BTC,DASH-BTC,EOS-USDT,EOS-ETH,ZIL-ETH,ETH-TUSD,GAS-BTC,LYM-BTC,BCH-BTC,VSYS-BTC,BCH-USDT,MKR-DAI,SOLVE-BTC,GRIN-BTC,GRIN-USDT,UQC-BTC,UQC-ETH,OPCT-BTC,OPCT-ETH,PRE-USDT,SHR-BTC,SHR-USDT,UBXT-USDT,ROSE-USDT,USDC-USDT,CTI-USDT,CTI-ETH,ETH2-ETH,BUX-BTC,XHV-USDT,PLU-USDT,GRT-USDT,CAS-BTC,CAS-USDT,MSWAP-BTC,MSWAP-USDT,GOM2-BTC,GOM2-USDT,REVV-BTC,REVV-USDT,LON-USDT,1INCH-USDT,LOC-USDT,API3-USDT,UNFI-USDT,HTR-USDT,FRONT-USDT,FRONT-BTC,WBTC-BTC,WBTC-ETH,MIR-USDT,LTC-USDC,BCH-USDC,HYDRA-USDT,DFI-USDT,DFI-BTC,CRV-USDT,SUSHI-USDT,FRM-USDT,EOS-USDC,BSV-USDC,ZEN-USDT,CUDOS-USDT,ADA-USDC,REN-USDT,LRC-USDT,LINK-USDC,KLV-USDT,KLV-BTC,BOA-USDT,THETA-USDT,QNT-USDT,BAT-USDT,DOGE-USDT,DOGE-USDC,DAO-USDT,STRONG-USDT,TRIAS-USDT,TRIAS-BTC,DOGE-BTC,MITX-BTC,MITX-USDT,CAKE-USDT,ORAI-USDT,ZEE-USDT,LTX-USDT,LTX-BTC,MASK-USDT,KLV-TRX,IDEA-USDT,PHA-USDT,PHA-ETH,BCH-KCS,SRK-USDT,SRK-BTC,ADA-KCS,HTR-BTC,BSV-KCS,DOT-KCS,LINK-KCS,MIR-KCS,BNB-KCS,XLM-KCS,VET-KCS,SWINGBY-USDT,SWINGBY-BTC,XHV-BTC,DASH-KCS,UNI-KCS,AAVE-KCS,DOGE-KCS,ZEC-KCS,XTZ-KCS,GRT-KCS,ALGO-KCS,EWT-KCS,GAS-USDT,AVAX-USDT,AVAX-BTC,KRL-BTC,KRL-USDT,POLK-USDT,POLK-BTC,ENJ-USDT,MANA-USDT,RNDR-USDT,RNDR-BTC,RLY-USDT,ANC-USDT,SKEY-USDT,LAYER-USDT,TARA-USDT,TARA-ETH,IOST-USDT,DYP-USDT,DYP-ETH,XYM-USDT,XYM-BTC,PCX-USDT,PCX-BTC,ORBS-USDT,ORBS-BTC,BTC3L-USDT,BTC3S-USDT,ETH3L-USDT,ETH3S-USDT,ANKR-USDT,DSLA-USDT,DSLA-BTC,SAND-USDT,VAI-USDT,XCUR-USDT,XCUR-BTC,FLUX-USDT,OMG-USDT,ZIL-USDT,DODO-USDT,MAN-USDT,BAX-USDT,BOSON-USDT,BOSON-ETH,PUNDIX-USDT,PUNDIX-BTC,WAXP-USDT,HT-USDT,PDEX-USDT,LABS-USDT,LABS-ETH,GMB-USDT,PHNX-USDT,PHNX-BTC,HAI-USDT,EQZ-USDT,FORTH-USDT,HORD-USDT,CGG-USDT,UBX-USDT,GHX-USDT,TCP-USDT,STND-USDT,STND-ETH,TOWER-USDT,TOWER-BTC,ACE-USDT,LOCG-USDT,CARD-USDT,FLY-USDT,CWS-USDT,XDC-USDT,XDC-ETH,STRK-BTC,STRK-ETH,SHIB-USDT,POLX-USDT,KDA-USDT,KDA-BTC,ICP-USDT,ICP-BTC,STC-USDT,STC-BTC,GOVI-USDT,GOVI-BTC,FKX-USDT,CELO-USDT,CELO-BTC,CUSD-USDT,CUSD-BTC,FCL-USDT,MATIC-USDT,MATIC-BTC,ELA-USDT,CRPT-USDT,OPCT-USDT,OGN-USDT,OGN-BTC,OUSD-USDT,OUSD-BTC,TLOS-USDT,TLOS-BTC,YOP-USDT,YOP-ETH,GLQ-USDT,GLQ-BTC,MXC-USDT,ERSDL-USDT,HOTCROSS-USDT,ADA3L-USDT,ADA3S-USDT,HYVE-USDT,HYVE-BTC,DAPPX-USDT,KONO-USDT,PRQ-USDT,MAHA-USDT,MAHA-BTC,FEAR-USDT,PYR-USDT,PYR-BTC,PROM-USDT,PROM-BTC,GLCH-USDT,UNO-USDT,ALBT-USDT,ALBT-ETH,XCAD-USDT,EOS3L-USDT,EOS3S-USDT,BCH3L-USDT,BCH3S-USDT,ELON-USDT,APL-USDT,FCL-ETH,VEED-USDT,VEED-BTC,DIVI-USDT,PDEX-BTC,JUP-USDT,JUP-ETH,POLS-USDT,POLS-BTC,LPOOL-USDT,LPOOL-BTC,LSS-USDT,VET3L-USDT,VET3S-USDT,LTC3L-USDT,LTC3S-USDT,ABBC-USDT,ABBC-BTC,KOK-USDT,ROSN-USDT,DORA-USDT,DORA-BTC,ZCX-USDT,ZCX-BTC,NORD-USDT,GMEE-USDT,SFUND-USDT,XAVA-USDT,AI-USDT,ALPACA-USDT,IOI-USDT,NFT-USDT,NFT-TRX,MNST-USDT,MEM-USDT,AGIX-USDT,AGIX-BTC,AGIX-ETH,CQT-USDT,AIOZ-USDT,MARSH-USDT,HAPI-USDT,MODEFI-USDT,MODEFI-BTC,YFDAI-USDT,YFDAI-BTC,GENS-USDT,FORM-USDT,ARRR-USDT,ARRR-BTC,TOKO-KCS,EXRD-USDT,NGM-USDT,LPT-USDT,STMX-USDT,ASD-USDT,BOND-USDT,HAI-BTC,SOUL-USDT,2CRZ-USDT,NEAR-USDT,NEAR-BTC,DFYN-USDT,OOE-USDT,CFG-USDT,CFG-BTC,AXS-USDT,CLV-USDT,ROUTE-USDT,KAR-USDT,EFX-USDT,XDC-BTC,SHFT-USDT,PMON-USDT,DPET-USDT,ERG-USDT,ERG-BTC,SOL-USDT,SLP-USDT,LITH-USDT,LITH-ETH,XCH-USDT,HAKA-USDT,LAYER-BTC,MTL-USDT,MTL-BTC,IOTX-USDT,GALA-USDT,REQ-USDT,TXA-USDT,TXA-USDC,CIRUS-USDT,QI-USDT,QI-BTC,ODDZ-USDT,PNT-USDT,PNT-BTC,XPR-USDT,XPR-BTC,TRIBE-USDT,SHFT-BTC,MOVR-USDT,MOVR-ETH,WOO-USDT,WILD-USDT,QRDO-USDT,QRDO-ETH,SDN-USDT,SDN-ETH,MAKI-USDT,MAKI-BTC,REP-USDT,REP-BTC,REP-ETH,BNT-USDT,BNT-BTC,BNT-ETH,OXT-USDT,OXT-BTC,OXT-ETH,BAL-USDT,BAL-BTC,BAL-ETH,STORJ-USDT,STORJ-BTC,STORJ-ETH,YGG-USDT,NDAU-USDT,SDAO-USDT,SDAO-ETH,XRP3L-USDT,XRP3S-USDT,SKL-USDT,SKL-BTC,NMR-USDT,NMR-BTC,IXS-USDT,TRB-USDT,TRB-BTC,DYDX-USDT,XYO-USDT,GTC-USDT,GTC-BTC,EQX-USDT,EQX-BTC,RLC-USDT,RLC-BTC,XPRT-USDT,EGLD-USDT,EGLD-BTC,HBAR-USDT,HBAR-BTC,DOGE3L-USDT,DOGE3S-USDT,FLOW-USDT,FLOW-BTC,NKN-USDT,NKN-BTC,PBX-USDT,SOL3L-USDT,SOL3S-USDT,MLN-USDT,MLN-BTC,XNL-USDT,SOLVE-USDT,WNCG-USDT,WNCG-BTC,DMTR-USDT,LINK3L-USDT,LINK3S-USDT,DOT3L-USDT,DOT3S-USDT,CTSI-USDT,CTSI-BTC,ALICE-USDT,ALICE-BTC,ALICE-ETH,OPUL-USDT,ILV-USDT,BAND-USDT,BAND-BTC,FTT-USDT,FTT-BTC,DVPN-USDT,SKU-USDT,SKU-BTC,EDG-USDT,SLIM-USDT,TLM-USDT,TLM-BTC,TLM-ETH,DEXE-USDT,DEXE-BTC,DEXE-ETH,MATTER-USDT,CUDOS-BTC,RUNE-USDT,RUNE-BTC,RMRK-USDT,BMON-USDT,C98-USDT,BLOK-USDT,SOLR-USDT,ATOM3L-USDT,ATOM3S-USDT,UNI3L-USDT,UNI3S-USDT,WSIENNA-USDT,PUSH-USDT,PUSH-BTC,FORM-ETH,NTVRK-USDT,NTVRK-USDC,AXS3L-USDT,AXS3S-USDT,FTM3L-USDT,FTM3S-USDT,FLAME-USDT,AGLD-USDT,NAKA-USDT,YLD-USDT,TONE-USDT,REEF-USDT,REEF-BTC,TIDAL-USDT,TVK-USDT,TVK-BTC,INJ-USDT,INJ-BTC,BNB3L-USDT,BNB3S-USDT,MATIC3L-USDT,MATIC3S-USDT,NFTB-USDT,VEGA-USDT,VEGA-ETH,ALPHA-USDT,ALPHA-BTC,BADGER-USDT,BADGER-BTC,UNO-BTC,ZKT-USDT,AR-USDT,AR-BTC,XVS-USDT,XVS-BTC,JASMY-USDT,PERP-USDT,PERP-BTC,GHST-USDT,GHST-BTC,SCLP-USDT,SCLP-BTC,SUPER-USDT,SUPER-BTC,CPOOL-USDT,HERO-USDT,BASIC-USDT,XED-USDT,XED-BTC,AURY-USDT,SWASH-USDT,LTO-USDT,LTO-BTC,BUX-USDT,MTRG-USDT,DREAMS-USDT,SHIB-DOGE,QUICK-USDT,QUICK-BTC,TRU-USDT,TRU-BTC,WRX-USDT,WRX-BTC,TKO-USDT,TKO-BTC,SUSHI3L-USDT,SUSHI3S-USDT,NEAR3L-USDT,NEAR3S-USDT,DATA-USDT,DATA-BTC,NORD-BTC,ISP-USDT,CERE-USDT,SHILL-USDT,HEGIC-USDT,HEGIC-BTC,ERN-USDT,ERN-BTC,FTG-USDT,PAXG-USDT,PAXG-BTC,AUDIO-USDT,AUDIO-BTC,ENS-USDT,AAVE3L-USDT,AAVE3S-USDT,SAND3L-USDT,SAND3S-USDT,XTM-USDT,MNW-USDT,FXS-USDT,FXS-BTC,ATA-USDT,ATA-BTC,VXV-USDT,LRC-BTC,LRC-ETH,DPR-USDT,CWAR-USDT,CWAR-BTC,FLUX-BTC,EDG-BTC,PBR-USDT,WNXM-USDT,WNXM-BTC,ANT-USDT,ANT-BTC,COV-USDT,SWP-USDT,TWT-USDT,TWT-BTC,OM-USDT,OM-BTC,ADX-USDT,AVAX3L-USDT,AVAX3S-USDT,MANA3L-USDT,MANA3S-USDT,GLM-USDT,GLM-BTC,BAKE-USDT,BAKE-BTC,BAKE-ETH,NUM-USDT,VLX-USDT,VLX-BTC,TRADE-USDT,TRADE-BTC,1EARTH-USDT,MONI-USDT,LIKE-USDT,MFT-USDT,MFT-BTC,LIT-USDT,LIT-BTC,KAVA-USDT,SFP-USDT,SFP-BTC,BURGER-USDT,BURGER-BTC,ILA-USDT,CREAM-USDT,CREAM-BTC,RSR-USDT,RSR-BTC,BUY-BTC,IMX-USDT,GODS-USDT,KMA-USDT,SRM-USDT,SRM-BTC,POLC-USDT,XTAG-USDT,MNET-USDT,NGC-USDT,HARD-USDT,GALAX3L-USDT,GALAX3S-USDT,UNIC-USDT,POND-USDT,POND-BTC,VR-USDT,EPIK-USDT,NGL-USDT,NGL-BTC,KDON-USDT,PEL-USDT,CIRUS-ETH,LINA-USDT,LINA-BTC,KLAY-USDT,KLAY-BTC,CREDI-USDT,TRVL-USDT,LACE-USDT,LACE-ETH,ARKER-USDT,BONDLY-USDT,BONDLY-ETH,XEC-USDT,HEART-USDT,HEART-BTC,UNB-USDT,GAFI-USDT,KOL-USDT,KOL-ETH,H3RO3S-USDT,FALCONS-USDT,UFO-USDT,CHMB-USDT,GEEQ-USDT,ORC-USDT,RACEFI-USDT,PEOPLE-USDT,ADS-USDT,ADS-BTC,OCEAN-USDT,SOS-USDT,WHALE-USDT,TIME-USDT,CWEB-USDT,IOTA-USDT,IOTA-BTC,OOKI-USDT,OOKI-BTC,HNT-USDT,HNT-BTC,GGG-USDT,POWR-USDT,REVU-USDT,CLH-USDT,PLGR-USDT,GLMR-USDT,GLMR-BTC,LOVE-USDT,CTC-USDT,CTC-BTC,GARI-USDT,FRR-USDT,ASTR-USDT,ASTR-BTC,ERTHA-USDT,FCON-USDT,ACA-USDT,ACA-BTC,MTS-USDT,ROAR-USDT,HBB-USDT,SURV-USDT,CVX-USDT,AMP-USDT,ACT-USDT,MJT-USDT,MJT-KCS,SHX-USDT,SHX-BTC,STARLY-USDT,ONSTON-USDT,RANKER-USDT,WMT-USDT,XNO-USDT,XNO-BTC,MARS4-USDT,TFUEL-USDT,TFUEL-BTC,METIS-USDT,LAVAX-USDT,WAL-USDT,BULL-USDT,SON-USDT,MELOS-USDT,APE-USDT,GMT-USDT,BICO-USDT,STG-USDT,LMR-USDT,LMR-BTC,LOKA-USDT,URUS-USDT,JAM-USDT,JAM-ETH,BNC-USDT,LBP-USDT,CFX-USDT,LOOKS-USDT,XCN-USDT,XCN-BTC,KP3R-USDT,TITAN-USDT,INDI-USDT,UPO-USDT,SPELL-USDT,SLCL-USDT,CEEK-USDT,VEMP-USDT,BETA-USDT,NHCT-USDT,ARNM-USDT,FRA-USDT,VISION-USDT,COCOS-USDT,ALPINE-USDT,BNX-USDT,ZBC-USDT,WOOP-USDT,T-USDT,NYM-USDT,VOXEL-USDT,VOXEL-ETH,PSTAKE-USDT,SPA-USDT,SPA-ETH,SYNR-USDT,DAR-USDT,DAR-BTC,MV-USDT,XDEFI-USDT,RACA-USDT,XWG-USDT,HAWK-USDT,TRVL-BTC,SWFTC-USDT,IDEX-USDT,BRWL-USDT,PLATO-USDT,TAUM-USDT,CELR-USDT,AURORA-USDT,POSI-USDT,COOHA-USDT,KNC-USDT,EPK-USDT,PLD-USDT,PSL-USDT,PKF-USDT,OVR-USDT,SYS-USDT,SYS-BTC,BRISE-USDT,DG-USDT,EPX-USDT,GST-USDT,PLY-USDT,GAL-USDT,BSW-USDT,FITFI-USDT,FSN-USDT,H2O-USDT,GMM-USDT,AKT-USDT,SIN-USDT,AUSD-USDT,BOBA-USDT,KARA-USDT,BFC-USDT,BIFI-USDT,DFA-USDT,KYL-USDT,FCD-USDT,MBL-USDT,CELT-USDT,DUSK-USDT,USDD-USDT,USDD-USDC,FITFI-USDC,MBOX-USDT,MBOX-BTC,APE-USDC,AVAX-USDC,SHIB-USDC,XCN-USDC,TRX-USDC,NEAR-USDC,MATIC-USDC,FTM-USDC,ZIL-USDC,SOL-USDC,MLS-USDT,AFK-USDT,AFK-USDC,ACH-USDT,SCRT-USDT,SCRT-BTC,APE3L-USDT,APE3S-USDT,STORE-USDT,STORE-ETH,GMT3L-USDT,GMT3S-USDT,CCD-USDT,DOSE-USDC,LUNC-USDT,LUNC-USDC,USTC-USDT,USTC-USDC,GMT-USDC,VRA-USDC,DOT-USDC,RUNE-USDC,ATOM-USDC,BNB-USDC,JASMY-USDC,KCS-USDC,KDA-USDC,ALGO-USDC,LUNA-USDC,OP-USDT,OP-USDC,JASMY3L-USDT,JASMY3S-USDT,EVER-USDT,MOOV-USDT,IHC-USDT,ICX-USDT,ICX-ETH,BTC-BRL,ETH-BRL,USDT-BRL,WELL-USDT,FORT-USDT,USDP-USDT,USDD-TRX,CSPR-USDT,CSPR-ETH,WEMIX-USDT,REV3L-USDT,OLE-USDT,LDO-USDT,LDO-USDC,CULT-USDT,SWFTC-USDC,FIDA-USDT,BUSD-USDT,RBP-USDT,SRBP-USDT,HIBAYC-USDT,BUSD-USDC,OGV-USDT,WOMBAT-USDT,HIPUNKS-USDT,FT-USDT,ETC-USDC,HIENS4-USDT,EGAME-USDT,EGAME-BTC,STEPWATCH-USDT,HISAND33-USDT,DC-USDT,NEER-USDT,RVN-USDT,HIENS3-USDT,MC-USDT,PEEL-USDT,PEEL-BTC,SDL-USDT,SDL-BTC,SWEAT-USDT,HIODBS-USDT,CMP-USDT,PIX-USDT,MPLX-USDT,HIDOODLES-USDT,ETHW-USDT,QUARTZ-USDT,ACQ-USDT,ACQ-USDC,AOG-USDT,HIMAYC-USDT,PRMX-USDT,RED-USDT,PUMLX-USDT,XETA-USDT,GEM-USDT,DERC-USDT,P00LS-USDT,P00LS-USDC,KICKS-USDT,TRIBL-USDT,GMX-USDT,HIOD-USDT,POKT-USDT,EFI-USDT,APT-USDT,BBC-USDT,EUL-USDT,TON-USDT,PIAS-USDT,HIMEEBITS-USDT,HISQUIGGLE-USDT,XCV-USDT,HFT-USDT,HFT-USDC,ECOX-USDT,AMB-USDT,AZERO-USDT,HIFIDENZA-USDT,BEAT-USDT", - "requestFormat": { - "uppercase": true, - "delimiter": "-" - }, - "configFormat": { - "uppercase": true, - "delimiter": "-" - } - }, - "margin": { - "assetEnabled": true, - "enabled": "ETH-BTC,TRX-BTC,LTC-USDT,SOL-USDC", - "available": "BTC-USDT,MHC-ETH,MHC-BTC,OXEN-BTC,OXEN-ETH,NRG-BTC,AVA-USDT,FET-BTC,FET-ETH,ANKR-BTC,MHC-USDT,XMR-BTC,XMR-ETH,RIF-BTC,MTV-BTC,MTV-ETH,CRO-BTC,MTV-USDT,KMD-BTC,KMD-USDT,RFOX-USDT,TEL-USDT,TT-USDT,AERGO-USDT,XMR-USDT,TRX-KCS,ATOM-BTC,ATOM-ETH,ATOM-USDT,ATOM-KCS,ETN-USDT,FTM-USDT,TOMO-USDT,VSYS-USDT,OCEAN-BTC,OCEAN-ETH,CHR-BTC,CHR-USDT,FX-BTC,FX-ETH,NIM-BTC,NIM-ETH,COTI-BTC,COTI-USDT,NRG-ETH,BNB-BTC,BNB-USDT,JAR-BTC,JAR-USDT,ALGO-BTC,ALGO-ETH,ALGO-USDT,XEM-BTC,XEM-USDT,CIX100-USDT,XTZ-BTC,XTZ-USDT,ZEC-BTC,ZEC-USDT,ADA-BTC,ADA-USDT,REV-USDT,WXT-BTC,WXT-USDT,FORESTPLUS-BTC,FORESTPLUS-USDT,BOLT-BTC,BOLT-USDT,ARPA-USDT,CHZ-BTC,CHZ-USDT,DAPPT-BTC,DAPPT-USDT,NOIA-BTC,NOIA-USDT,WIN-BTC,WIN-USDT,DERO-BTC,DERO-USDT,BTT-USDT,EOSC-USDT,ENQ-BTC,ENQ-USDT,ONE-BTC,ONE-USDT,TOKO-BTC,TOKO-USDT,VID-BTC,VID-USDT,LUNA-USDT,SXP-BTC,SXP-USDT,AKRO-BTC,AKRO-USDT,ROOBEE-BTC,WIN-TRX,MAP-BTC,MAP-USDT,AMPL-BTC,AMPL-USDT,DAG-USDT,POL-USDT,ARX-USDT,NWC-BTC,NWC-USDT,BEPRO-BTC,BEPRO-USDT,VRA-BTC,VRA-USDT,KSM-BTC,KSM-USDT,DASH-USDT,SUTER-USDT,ACOIN-USDT,SUTER-BTC,SENSO-USDT,PRE-BTC,XDB-USDT,SYLO-USDT,WOM-USDT,SENSO-BTC,DGB-USDT,LYXE-USDT,LYXE-ETH,XDB-BTC,STX-BTC,STX-USDT,XSR-USDT,COMP-USDT,CRO-USDT,KAI-USDT,KAI-BTC,WEST-BTC,WEST-USDT,EWT-BTC,WAVES-USDT,WAVES-BTC,ORN-USDT,AMPL-ETH,BNS-USDT,MKR-USDT,SUKU-BTC,MLK-BTC,MLK-USDT,JST-USDT,KAI-ETH,SUKU-USDT,DIA-USDT,DIA-BTC,LINK-BTC,LINK-USDT,DOT-USDT,DOT-BTC,SHA-BTC,SHA-USDT,EWT-USDT,USDJ-USDT,EFX-BTC,CKB-BTC,CKB-USDT,UMA-USDT,ALEPH-USDT,VELO-USDT,SUN-USDT,BUY-USDT,YFI-USDT,OXEN-USDT,UNI-USDT,UOS-USDT,UOS-BTC,NIM-USDT,DEGO-USDT,DEGO-ETH,UDOO-ETH,RFUEL-USDT,FIL-USDT,UBX-ETH,REAP-USDT,AAVE-USDT,AAVE-BTC,TONE-BTC,TONE-ETH,ELF-ETH,AERGO-BTC,IOST-ETH,KCS-USDT,SNX-ETH,TOMO-ETH,KCS-ETH,DRGN-BTC,WAN-ETH,NULS-ETH,AXPR-ETH,POWR-BTC,QTUM-BTC,MANA-BTC,TEL-BTC,XYO-ETH,AXPR-BTC,ETN-BTC,COV-ETH,VET-BTC,KCS-BTC,CAPP-ETH,ONT-BTC,DRGN-ETH,DAG-ETH,TOMO-BTC,WAN-BTC,KNC-ETH,CRPT-ETH,LTC-USDT,BAX-ETH,BSV-USDT,DENT-ETH,AION-ETH,LYM-ETH,TRAC-ETH,ENJ-BTC,WAXP-BTC,DGB-BTC,ELA-BTC,ZIL-BTC,BSV-BTC,XLM-USDT,IOTX-ETH,SOUL-BTC,DOCK-BTC,AMB-ETH,TRX-BTC,XRP-TUSD,NULS-BTC,ETH-DAI,LSK-BTC,GMB-ETH,GMB-BTC,NEO-ETH,OMG-ETH,BTC-TUSD,KAT-USDT,KNC-BTC,ELF-BTC,MANA-ETH,ETC-USDT,ONT-ETH,MKR-BTC,KAT-BTC,XRP-USDC,XYO-BTC,SNT-ETH,ZRX-BTC,LOOM-ETH,AION-BTC,POWR-ETH,OLT-ETH,OLT-BTC,SNT-BTC,TRAC-BTC,XLM-ETH,ETH-USDT,BSV-ETH,TRX-ETH,ETN-ETH,AOA-USDT,BCD-BTC,DENT-BTC,DOCK-ETH,KEY-BTC,EOS-KCS,XLM-BTC,ADB-ETH,TIME-ETH,CVC-BTC,LSK-ETH,QKC-BTC,AMB-BTC,USDT-TUSD,ETC-ETH,XRP-BTC,NEO-KCS,SNX-USDT,CRPT-BTC,IOTX-BTC,LTC-ETH,XRP-KCS,ADB-BTC,LTC-KCS,TEL-ETH,DCR-ETH,LYM-USDT,USDT-USDC,ETH-USDC,DAG-BTC,AVA-BTC,BTC-USDT,WAXP-ETH,XRP-USDT,KEY-ETH,VET-ETH,FTM-BTC,USDT-DAI,QKC-ETH,ETH-BTC,MAN-BTC,CPC-ETH,TRX-USDT,BTC-DAI,ONT-USDT,DASH-ETH,BAX-BTC,AVA-ETH,LOOM-BTC,MVP-BTC,MKR-ETH,COV-BTC,CPC-BTC,REQ-ETH,EOS-BTC,LTC-BTC,XRP-ETH,CAPP-BTC,FTM-ETH,BCD-ETH,ZRX-ETH,DGB-ETH,VET-USDT,REQ-BTC,UTK-BTC,PLAY-BTC,UTK-ETH,SNX-BTC,MVP-ETH,NEO-BTC,SOUL-ETH,NEO-USDT,ELA-ETH,OMG-BTC,TIME-BTC,AOA-BTC,ETC-BTC,DCR-BTC,BTC-USDC,ENJ-ETH,IOST-BTC,DASH-BTC,EOS-USDT,EOS-ETH,ZIL-ETH,ETH-TUSD,GAS-BTC,LYM-BTC,BCH-BTC,VSYS-BTC,BCH-USDT,MKR-DAI,SOLVE-BTC,GRIN-BTC,GRIN-USDT,UQC-BTC,UQC-ETH,OPCT-BTC,OPCT-ETH,PRE-USDT,SHR-BTC,SHR-USDT,UBXT-USDT,ROSE-USDT,USDC-USDT,CTI-USDT,CTI-ETH,ETH2-ETH,BUX-BTC,XHV-USDT,PLU-USDT,GRT-USDT,CAS-BTC,CAS-USDT,MSWAP-BTC,MSWAP-USDT,GOM2-BTC,GOM2-USDT,REVV-BTC,REVV-USDT,LON-USDT,1INCH-USDT,LOC-USDT,API3-USDT,UNFI-USDT,HTR-USDT,FRONT-USDT,FRONT-BTC,WBTC-BTC,WBTC-ETH,MIR-USDT,LTC-USDC,BCH-USDC,HYDRA-USDT,DFI-USDT,DFI-BTC,CRV-USDT,SUSHI-USDT,FRM-USDT,EOS-USDC,BSV-USDC,ZEN-USDT,CUDOS-USDT,ADA-USDC,REN-USDT,LRC-USDT,LINK-USDC,KLV-USDT,KLV-BTC,BOA-USDT,THETA-USDT,QNT-USDT,BAT-USDT,DOGE-USDT,DOGE-USDC,DAO-USDT,STRONG-USDT,TRIAS-USDT,TRIAS-BTC,DOGE-BTC,MITX-BTC,MITX-USDT,CAKE-USDT,ORAI-USDT,ZEE-USDT,LTX-USDT,LTX-BTC,MASK-USDT,KLV-TRX,IDEA-USDT,PHA-USDT,PHA-ETH,BCH-KCS,SRK-USDT,SRK-BTC,ADA-KCS,HTR-BTC,BSV-KCS,DOT-KCS,LINK-KCS,MIR-KCS,BNB-KCS,XLM-KCS,VET-KCS,SWINGBY-USDT,SWINGBY-BTC,XHV-BTC,DASH-KCS,UNI-KCS,AAVE-KCS,DOGE-KCS,ZEC-KCS,XTZ-KCS,GRT-KCS,ALGO-KCS,EWT-KCS,GAS-USDT,AVAX-USDT,AVAX-BTC,KRL-BTC,KRL-USDT,POLK-USDT,POLK-BTC,ENJ-USDT,MANA-USDT,RNDR-USDT,RNDR-BTC,RLY-USDT,ANC-USDT,SKEY-USDT,LAYER-USDT,TARA-USDT,TARA-ETH,IOST-USDT,DYP-USDT,DYP-ETH,XYM-USDT,XYM-BTC,PCX-USDT,PCX-BTC,ORBS-USDT,ORBS-BTC,BTC3L-USDT,BTC3S-USDT,ETH3L-USDT,ETH3S-USDT,ANKR-USDT,DSLA-USDT,DSLA-BTC,SAND-USDT,VAI-USDT,XCUR-USDT,XCUR-BTC,FLUX-USDT,OMG-USDT,ZIL-USDT,DODO-USDT,MAN-USDT,BAX-USDT,BOSON-USDT,BOSON-ETH,PUNDIX-USDT,PUNDIX-BTC,WAXP-USDT,HT-USDT,PDEX-USDT,LABS-USDT,LABS-ETH,GMB-USDT,PHNX-USDT,PHNX-BTC,HAI-USDT,EQZ-USDT,FORTH-USDT,HORD-USDT,CGG-USDT,UBX-USDT,GHX-USDT,TCP-USDT,STND-USDT,STND-ETH,TOWER-USDT,TOWER-BTC,ACE-USDT,LOCG-USDT,CARD-USDT,FLY-USDT,CWS-USDT,XDC-USDT,XDC-ETH,STRK-BTC,STRK-ETH,SHIB-USDT,POLX-USDT,KDA-USDT,KDA-BTC,ICP-USDT,ICP-BTC,STC-USDT,STC-BTC,GOVI-USDT,GOVI-BTC,FKX-USDT,CELO-USDT,CELO-BTC,CUSD-USDT,CUSD-BTC,FCL-USDT,MATIC-USDT,MATIC-BTC,ELA-USDT,CRPT-USDT,OPCT-USDT,OGN-USDT,OGN-BTC,OUSD-USDT,OUSD-BTC,TLOS-USDT,TLOS-BTC,YOP-USDT,YOP-ETH,GLQ-USDT,GLQ-BTC,MXC-USDT,ERSDL-USDT,HOTCROSS-USDT,ADA3L-USDT,ADA3S-USDT,HYVE-USDT,HYVE-BTC,DAPPX-USDT,KONO-USDT,PRQ-USDT,MAHA-USDT,MAHA-BTC,FEAR-USDT,PYR-USDT,PYR-BTC,PROM-USDT,PROM-BTC,GLCH-USDT,UNO-USDT,ALBT-USDT,ALBT-ETH,XCAD-USDT,EOS3L-USDT,EOS3S-USDT,BCH3L-USDT,BCH3S-USDT,ELON-USDT,APL-USDT,FCL-ETH,VEED-USDT,VEED-BTC,DIVI-USDT,PDEX-BTC,JUP-USDT,JUP-ETH,POLS-USDT,POLS-BTC,LPOOL-USDT,LPOOL-BTC,LSS-USDT,VET3L-USDT,VET3S-USDT,LTC3L-USDT,LTC3S-USDT,ABBC-USDT,ABBC-BTC,KOK-USDT,ROSN-USDT,DORA-USDT,DORA-BTC,ZCX-USDT,ZCX-BTC,NORD-USDT,GMEE-USDT,SFUND-USDT,XAVA-USDT,AI-USDT,ALPACA-USDT,IOI-USDT,NFT-USDT,NFT-TRX,MNST-USDT,MEM-USDT,AGIX-USDT,AGIX-BTC,AGIX-ETH,CQT-USDT,AIOZ-USDT,MARSH-USDT,HAPI-USDT,MODEFI-USDT,MODEFI-BTC,YFDAI-USDT,YFDAI-BTC,GENS-USDT,FORM-USDT,ARRR-USDT,ARRR-BTC,TOKO-KCS,EXRD-USDT,NGM-USDT,LPT-USDT,STMX-USDT,ASD-USDT,BOND-USDT,HAI-BTC,SOUL-USDT,2CRZ-USDT,NEAR-USDT,NEAR-BTC,DFYN-USDT,OOE-USDT,CFG-USDT,CFG-BTC,AXS-USDT,CLV-USDT,ROUTE-USDT,KAR-USDT,EFX-USDT,XDC-BTC,SHFT-USDT,PMON-USDT,DPET-USDT,ERG-USDT,ERG-BTC,SOL-USDT,SLP-USDT,LITH-USDT,LITH-ETH,XCH-USDT,HAKA-USDT,LAYER-BTC,MTL-USDT,MTL-BTC,IOTX-USDT,GALA-USDT,REQ-USDT,TXA-USDT,TXA-USDC,CIRUS-USDT,QI-USDT,QI-BTC,ODDZ-USDT,PNT-USDT,PNT-BTC,XPR-USDT,XPR-BTC,TRIBE-USDT,SHFT-BTC,MOVR-USDT,MOVR-ETH,WOO-USDT,WILD-USDT,QRDO-USDT,QRDO-ETH,SDN-USDT,SDN-ETH,MAKI-USDT,MAKI-BTC,REP-USDT,REP-BTC,REP-ETH,BNT-USDT,BNT-BTC,BNT-ETH,OXT-USDT,OXT-BTC,OXT-ETH,BAL-USDT,BAL-BTC,BAL-ETH,STORJ-USDT,STORJ-BTC,STORJ-ETH,YGG-USDT,NDAU-USDT,SDAO-USDT,SDAO-ETH,XRP3L-USDT,XRP3S-USDT,SKL-USDT,SKL-BTC,NMR-USDT,NMR-BTC,IXS-USDT,TRB-USDT,TRB-BTC,DYDX-USDT,XYO-USDT,GTC-USDT,GTC-BTC,EQX-USDT,EQX-BTC,RLC-USDT,RLC-BTC,XPRT-USDT,EGLD-USDT,EGLD-BTC,HBAR-USDT,HBAR-BTC,DOGE3L-USDT,DOGE3S-USDT,FLOW-USDT,FLOW-BTC,NKN-USDT,NKN-BTC,PBX-USDT,SOL3L-USDT,SOL3S-USDT,MLN-USDT,MLN-BTC,XNL-USDT,SOLVE-USDT,WNCG-USDT,WNCG-BTC,DMTR-USDT,LINK3L-USDT,LINK3S-USDT,DOT3L-USDT,DOT3S-USDT,CTSI-USDT,CTSI-BTC,ALICE-USDT,ALICE-BTC,ALICE-ETH,OPUL-USDT,ILV-USDT,BAND-USDT,BAND-BTC,FTT-USDT,FTT-BTC,DVPN-USDT,SKU-USDT,SKU-BTC,EDG-USDT,SLIM-USDT,TLM-USDT,TLM-BTC,TLM-ETH,DEXE-USDT,DEXE-BTC,DEXE-ETH,MATTER-USDT,CUDOS-BTC,RUNE-USDT,RUNE-BTC,RMRK-USDT,BMON-USDT,C98-USDT,BLOK-USDT,SOLR-USDT,ATOM3L-USDT,ATOM3S-USDT,UNI3L-USDT,UNI3S-USDT,WSIENNA-USDT,PUSH-USDT,PUSH-BTC,FORM-ETH,NTVRK-USDT,NTVRK-USDC,AXS3L-USDT,AXS3S-USDT,FTM3L-USDT,FTM3S-USDT,FLAME-USDT,AGLD-USDT,NAKA-USDT,YLD-USDT,TONE-USDT,REEF-USDT,REEF-BTC,TIDAL-USDT,TVK-USDT,TVK-BTC,INJ-USDT,INJ-BTC,BNB3L-USDT,BNB3S-USDT,MATIC3L-USDT,MATIC3S-USDT,NFTB-USDT,VEGA-USDT,VEGA-ETH,ALPHA-USDT,ALPHA-BTC,BADGER-USDT,BADGER-BTC,UNO-BTC,ZKT-USDT,AR-USDT,AR-BTC,XVS-USDT,XVS-BTC,JASMY-USDT,PERP-USDT,PERP-BTC,GHST-USDT,GHST-BTC,SCLP-USDT,SCLP-BTC,SUPER-USDT,SUPER-BTC,CPOOL-USDT,HERO-USDT,BASIC-USDT,XED-USDT,XED-BTC,AURY-USDT,SWASH-USDT,LTO-USDT,LTO-BTC,BUX-USDT,MTRG-USDT,DREAMS-USDT,SHIB-DOGE,QUICK-USDT,QUICK-BTC,TRU-USDT,TRU-BTC,WRX-USDT,WRX-BTC,TKO-USDT,TKO-BTC,SUSHI3L-USDT,SUSHI3S-USDT,NEAR3L-USDT,NEAR3S-USDT,DATA-USDT,DATA-BTC,NORD-BTC,ISP-USDT,CERE-USDT,SHILL-USDT,HEGIC-USDT,HEGIC-BTC,ERN-USDT,ERN-BTC,FTG-USDT,PAXG-USDT,PAXG-BTC,AUDIO-USDT,AUDIO-BTC,ENS-USDT,AAVE3L-USDT,AAVE3S-USDT,SAND3L-USDT,SAND3S-USDT,XTM-USDT,MNW-USDT,FXS-USDT,FXS-BTC,ATA-USDT,ATA-BTC,VXV-USDT,LRC-BTC,LRC-ETH,DPR-USDT,CWAR-USDT,CWAR-BTC,FLUX-BTC,EDG-BTC,PBR-USDT,WNXM-USDT,WNXM-BTC,ANT-USDT,ANT-BTC,COV-USDT,SWP-USDT,TWT-USDT,TWT-BTC,OM-USDT,OM-BTC,ADX-USDT,AVAX3L-USDT,AVAX3S-USDT,MANA3L-USDT,MANA3S-USDT,GLM-USDT,GLM-BTC,BAKE-USDT,BAKE-BTC,BAKE-ETH,NUM-USDT,VLX-USDT,VLX-BTC,TRADE-USDT,TRADE-BTC,1EARTH-USDT,MONI-USDT,LIKE-USDT,MFT-USDT,MFT-BTC,LIT-USDT,LIT-BTC,KAVA-USDT,SFP-USDT,SFP-BTC,BURGER-USDT,BURGER-BTC,ILA-USDT,CREAM-USDT,CREAM-BTC,RSR-USDT,RSR-BTC,BUY-BTC,IMX-USDT,GODS-USDT,KMA-USDT,SRM-USDT,SRM-BTC,POLC-USDT,XTAG-USDT,MNET-USDT,NGC-USDT,HARD-USDT,GALAX3L-USDT,GALAX3S-USDT,UNIC-USDT,POND-USDT,POND-BTC,VR-USDT,EPIK-USDT,NGL-USDT,NGL-BTC,KDON-USDT,PEL-USDT,CIRUS-ETH,LINA-USDT,LINA-BTC,KLAY-USDT,KLAY-BTC,CREDI-USDT,TRVL-USDT,LACE-USDT,LACE-ETH,ARKER-USDT,BONDLY-USDT,BONDLY-ETH,XEC-USDT,HEART-USDT,HEART-BTC,UNB-USDT,GAFI-USDT,KOL-USDT,KOL-ETH,H3RO3S-USDT,FALCONS-USDT,UFO-USDT,CHMB-USDT,GEEQ-USDT,ORC-USDT,RACEFI-USDT,PEOPLE-USDT,ADS-USDT,ADS-BTC,OCEAN-USDT,SOS-USDT,WHALE-USDT,TIME-USDT,CWEB-USDT,IOTA-USDT,IOTA-BTC,OOKI-USDT,OOKI-BTC,HNT-USDT,HNT-BTC,GGG-USDT,POWR-USDT,REVU-USDT,CLH-USDT,PLGR-USDT,GLMR-USDT,GLMR-BTC,LOVE-USDT,CTC-USDT,CTC-BTC,GARI-USDT,FRR-USDT,ASTR-USDT,ASTR-BTC,ERTHA-USDT,FCON-USDT,ACA-USDT,ACA-BTC,MTS-USDT,ROAR-USDT,HBB-USDT,SURV-USDT,CVX-USDT,AMP-USDT,ACT-USDT,MJT-USDT,MJT-KCS,SHX-USDT,SHX-BTC,STARLY-USDT,ONSTON-USDT,RANKER-USDT,WMT-USDT,XNO-USDT,XNO-BTC,MARS4-USDT,TFUEL-USDT,TFUEL-BTC,METIS-USDT,LAVAX-USDT,WAL-USDT,BULL-USDT,SON-USDT,MELOS-USDT,APE-USDT,GMT-USDT,BICO-USDT,STG-USDT,LMR-USDT,LMR-BTC,LOKA-USDT,URUS-USDT,JAM-USDT,JAM-ETH,BNC-USDT,LBP-USDT,CFX-USDT,LOOKS-USDT,XCN-USDT,XCN-BTC,KP3R-USDT,TITAN-USDT,INDI-USDT,UPO-USDT,SPELL-USDT,SLCL-USDT,CEEK-USDT,VEMP-USDT,BETA-USDT,NHCT-USDT,ARNM-USDT,FRA-USDT,VISION-USDT,COCOS-USDT,ALPINE-USDT,BNX-USDT,ZBC-USDT,WOOP-USDT,T-USDT,NYM-USDT,VOXEL-USDT,VOXEL-ETH,PSTAKE-USDT,SPA-USDT,SPA-ETH,SYNR-USDT,DAR-USDT,DAR-BTC,MV-USDT,XDEFI-USDT,RACA-USDT,XWG-USDT,HAWK-USDT,TRVL-BTC,SWFTC-USDT,IDEX-USDT,BRWL-USDT,PLATO-USDT,TAUM-USDT,CELR-USDT,AURORA-USDT,POSI-USDT,COOHA-USDT,KNC-USDT,EPK-USDT,PLD-USDT,PSL-USDT,PKF-USDT,OVR-USDT,SYS-USDT,SYS-BTC,BRISE-USDT,DG-USDT,EPX-USDT,GST-USDT,PLY-USDT,GAL-USDT,BSW-USDT,FITFI-USDT,FSN-USDT,H2O-USDT,GMM-USDT,AKT-USDT,SIN-USDT,AUSD-USDT,BOBA-USDT,KARA-USDT,BFC-USDT,BIFI-USDT,DFA-USDT,KYL-USDT,FCD-USDT,MBL-USDT,CELT-USDT,DUSK-USDT,USDD-USDT,USDD-USDC,FITFI-USDC,MBOX-USDT,MBOX-BTC,APE-USDC,AVAX-USDC,SHIB-USDC,XCN-USDC,TRX-USDC,NEAR-USDC,MATIC-USDC,FTM-USDC,ZIL-USDC,SOL-USDC,MLS-USDT,AFK-USDT,AFK-USDC,ACH-USDT,SCRT-USDT,SCRT-BTC,APE3L-USDT,APE3S-USDT,STORE-USDT,STORE-ETH,GMT3L-USDT,GMT3S-USDT,CCD-USDT,DOSE-USDC,LUNC-USDT,LUNC-USDC,USTC-USDT,USTC-USDC,GMT-USDC,VRA-USDC,DOT-USDC,RUNE-USDC,ATOM-USDC,BNB-USDC,JASMY-USDC,KCS-USDC,KDA-USDC,ALGO-USDC,LUNA-USDC,OP-USDT,OP-USDC,JASMY3L-USDT,JASMY3S-USDT,EVER-USDT,MOOV-USDT,IHC-USDT,ICX-USDT,ICX-ETH,BTC-BRL,ETH-BRL,USDT-BRL,WELL-USDT,FORT-USDT,USDP-USDT,USDD-TRX,CSPR-USDT,CSPR-ETH,WEMIX-USDT,REV3L-USDT,OLE-USDT,LDO-USDT,LDO-USDC,CULT-USDT,SWFTC-USDC,FIDA-USDT,BUSD-USDT,RBP-USDT,SRBP-USDT,HIBAYC-USDT,BUSD-USDC,OGV-USDT,WOMBAT-USDT,HIPUNKS-USDT,FT-USDT,ETC-USDC,HIENS4-USDT,EGAME-USDT,EGAME-BTC,STEPWATCH-USDT,HISAND33-USDT,DC-USDT,NEER-USDT,RVN-USDT,HIENS3-USDT,MC-USDT,PEEL-USDT,PEEL-BTC,SDL-USDT,SDL-BTC,SWEAT-USDT,HIODBS-USDT,CMP-USDT,PIX-USDT,MPLX-USDT,HIDOODLES-USDT,ETHW-USDT,QUARTZ-USDT,ACQ-USDT,ACQ-USDC,AOG-USDT,HIMAYC-USDT,PRMX-USDT,RED-USDT,PUMLX-USDT,XETA-USDT,GEM-USDT,DERC-USDT,P00LS-USDT,P00LS-USDC,KICKS-USDT,TRIBL-USDT,GMX-USDT,HIOD-USDT,POKT-USDT,EFI-USDT,APT-USDT,BBC-USDT,EUL-USDT,TON-USDT,PIAS-USDT,HIMEEBITS-USDT,HISQUIGGLE-USDT,XCV-USDT,HFT-USDT,HFT-USDC,ECOX-USDT,AMB-USDT,AZERO-USDT,HIFIDENZA-USDT,BEAT-USDT", - "requestFormat": { - "uppercase": true, - "delimiter": "-" - }, - "configFormat": { - "uppercase": true, - "delimiter": "-" - } - }, - "futures": { - "assetEnabled": true, - "enabled": "ETH_USDCM,XBT_USDCM,SOL_USDTM", - "available": "XBT_USDTM,XBT_USDM,ETH_USDTM,BCH_USDTM,BSV_USDTM,LINK_USDTM,UNI_USDTM,YFI_USDTM,EOS_USDTM,DOT_USDTM,FIL_USDTM,ADA_USDTM,XRP_USDTM,LTC_USDTM,ETH_USDM,TRX_USDTM,GRT_USDTM,SUSHI_USDTM,XLM_USDTM,1INCH_USDTM,ZEC_USDTM,DASH_USDTM,DOT_USDM,XRP_USDM,AAVE_USDTM,KSM_USDTM,DOGE_USDTM,VET_USDTM,BNB_USDTM,SXP_USDTM,SOL_USDTM,CRV_USDTM,ALGO_USDTM,AVAX_USDTM,FTM_USDTM,MATIC_USDTM,THETA_USDTM,ATOM_USDTM,CHZ_USDTM,ENJ_USDTM,MANA_USDTM,DENT_USDTM,OCEAN_USDTM,BAT_USDTM,XEM_USDTM,QTUM_USDTM,XTZ_USDTM,SNX_USDTM,NEO_USDTM,ONT_USDTM,XMR_USDTM,COMP_USDTM,ETC_USDTM,WAVES_USDTM,BAND_USDTM,MKR_USDTM,RVN_USDTM,DGB_USDTM,SHIB_USDTM,ICP_USDTM,DYDX_USDTM,AXS_USDTM,HBAR_USDTM,EGLD_USDTM,ALICE_USDTM,YGG_USDTM,NEAR_USDTM,SAND_USDTM,C98_USDTM,ONE_USDTM,VRA_USDTM,GALA_USDTM,CHR_USDTM,LRC_USDTM,FLOW_USDTM,RNDR_USDTM,IOTX_USDTM,CRO_USDTM,WAXP_USDTM,PEOPLE_USDTM,OMG_USDTM,LINA_USDTM,IMX_USDTM,CELR_USDTM,ENS_USDTM,CELO_USDTM,CTSI_USDTM,ARPA_USDTM,KNC_USDTM,ROSE_USDTM,AGLD_USDTM,APE_USDTM,JASMY_USDTM,ZIL_USDTM,GMT_USDTM,RUNE_USDTM,LOOKS_USDTM,AUDIO_USDTM,KDA_USDTM,KAVA_USDTM,BAL_USDTM,GAL_USDTM,LUNA_USDTM,LUNC_USDTM,OP_USDTM,XCN_USDTM,UNFI_USDTM,LIT_USDTM,DUSK_USDTM,STORJ_USDTM,RSR_USDTM,OGN_USDTM,TRB_USDTM,PERP_USDTM,KLAY_USDTM,ANKR_USDTM,LDO_USDTM,WOO_USDTM,REN_USDTM,CVC_USDTM,INJ_USDTM,APT_USDTM,MASK_USDTM,REEF_USDTM,TON_USDTM,MAGIC_USDTM,CFX_USDTM,AGIX_USDTM,FXS_USDTM,FET_USDTM,AR_USDTM,GMX_USDTM,BLUR_USDTM,ASTR_USDTM,HIGH_USDTM,ACH_USDTM,STX_USDTM,SSV_USDTM,FLOKI_USDTM,CKB_USDTM,TRU_USDTM,QNT_USDTM,ETH_USDCM,MINA_USDTM,USDC_USDTM,T_USDTM,LQTY_USDTM,ARB_USDTM,DAR_USDTM,ID_USDTM,STG_USDTM,JOE_USDTM,RDNT_USDTM,DODO_USDTM,PAXG_USDTM,ZRX_USDTM,ICX_USDTM,HFT_USDTM,NKN_USDTM,HOOK_USDTM,ANT_USDTM,DC_USDTM,BEL_USDTM,SUI_USDTM,PEPE_USDTM,IDEX_USDTM,GNS_USDTM,CETUS_USDTM,KAS_USDTM,ORDI_USDTM,WOJAK_USDTM,POGAI_USDTM,UMA_USDTM,RAD_USDTM,XBT_USDCM,PHB_USDTM,FTT_USDTM,10000LADYS_USDTM,LEVER_USDTM,TURBO_USDTM,TOMO_USDTM,BOB_USDTM,KEY_USDTM,EDU_USDTM,MTL_USDTM,FLUX_USDTM,COMBO_USDTM,AMB_USDTM,ALPHA_USDTM,SFP_USDTM,MAV_USDTM,MDT_USDTM,XEC_USDTM,XVG_USDTM,1000PEPE2_USDTM,PENDLE_USDTM,STMX_USDTM,WLD_USDTM,LPT_USDTM,GTC_USDTM,BNT_USDTM,OXT_USDTM,BLZ_USDTM,SEI_USDTM,BAKE_USDTM,CYBER_USDTM,NMR_USDTM,FLM_USDTM,SPELL_USDTM,ARK_USDTM,XBT_MU23,XBT_MZ23", - "requestFormat": { - "uppercase": true, - "delimiter": "" - }, - "configFormat": { - "uppercase": true, - "delimiter": "_" - } - } - } - }, - "api": { - "authenticatedSupport": true, - "authenticatedWebsocketApiSupport": true, - "endpoints": { - "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" - }, - "credentials": { - "key": "Key", - "secret": "Secret", - "clientID": "ClientID" - }, - "credentialsValidator": { - "requiresKey": true, - "requiresSecret": true - } - }, - "features": { - "supports": { - "restAPI": true, - "restCapabilities": { - "tickerBatching": true, - "autoPairUpdates": true - }, - "websocketAPI": true, - "websocketCapabilities": {} + "bankAccounts": [ + { + "enabled": false, + "bankName": "", + "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ] + }, + { + "name": "Okcoin", + "enabled": true, + "verbose": false, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, + "websocketOrderbookBufferLimit": 5, + "baseCurrencies": "USD", + "currencyPairs": { + "requestFormat": { + "uppercase": true, + "delimiter": "-" + }, + "configFormat": { + "uppercase": true, + "delimiter": "-" + }, + "useGlobalFormat": true, + "assetTypes": [ + "spot" + ], + "pairs": { + "spot": { + "enabled": "BTC-USD", + "available": "BTC-USD,LTC-USD,ETH-USD,ETC-USD,TUSD-USD,BCH-USD,EOS-USD,XRP-USD,TRX-USD,BSV-USD,USDT-USD,USDK-USD,XLM-USD,ADA-USD,BAT-USD,DCR-USD,EURS-USD,HBAR-USD,PAX-USD,USDC-USD,ZEC-USD,BTC-USDT,BTC-SGD,ETH-SGD,BTC-EUR,BTC-EURS,ETH-EUR,BCH-EUR,EURS-EUR" }, - "enabled": { - "autoPairUpdates": true, - "websocketAPI": true + "margin": { + "enabled": "BTC-USD", + "available": "BTC-USD" } - }, - "bankAccounts": [ - { - "enabled": false, - "bankName": "", - "bankAddress": "", - "bankPostalCode": "", - "bankPostalCity": "", - "bankCountry": "", - "accountName": "", - "accountNumber": "", - "swiftCode": "", - "iban": "", - "supportedCurrencies": "" - } - ] + } }, - { - "name": "LBank", - "enabled": true, - "verbose": false, - "httpTimeout": 15000000000, - "websocketResponseCheckTimeout": 30000000, - "websocketResponseMaxLimit": 7000000000, - "websocketTrafficTimeout": 30000000000, - "websocketOrderbookBufferLimit": 5, - "baseCurrencies": "USD", - "currencyPairs": { - "requestFormat": { - "uppercase": false, - "delimiter": "_" - }, - "configFormat": { - "uppercase": false, - "delimiter": "_" - }, - "useGlobalFormat": true, - "assetTypes": [ - "spot" - ], - "pairs": { - "spot": { - "enabled": "eth_btc", - "available": "FBC_USDT,GALT_USDT,IOEX_USDT,OATH_USDT,BLOC_USDT,BTC_USDT,ETH_USDT,ETH_BTC,ABBC_BTC,KISC_ETH,BXA_USDT,ATP_USDT,MAT_USDT,SKY_BTC,RNT_USDT,VENA_USDT,GRIN_USDT,IDA_USDT,PNT_USDT,OPX_USDT,VTHO_BTC,AMO_ETH,UBEX_BTC,EOS_BTC,UBEX_USDT,TNS_BTC,SAIT_ETH,DAX_BTC,DAX_ETH,DALI_USDT,VET_USDT,BCH_BTC,BCH_USDT,NEO_USDT,QTUM_USDT,ZEC_USDT,VET_BTC,PAI_BTC,PNT_BTC,NEO_BTC,DASH_BTC,LTC_BTC,ETC_BTC,QTUM_BTC,ZEC_BTC,SC_BTC,BTS_BTC,CPX_BTC,XWC_BTC,FIL6_BTC,FIL12_BTC,FIL36_BTC,EOS_USDT,UT_ETH,ELA_ETH,VET_ETH,VTHO_ETH,PAI_ETH,HER_ETH,PTT_ETH,TAC_ETH,IDHUB_ETH,SSC_ETH,SKM_ETH,PLY_ETH,EXT_ETH,EOS_ETH,YOYOW_ETH,TRX_ETH,QTUM_ETH,ZEC_ETH,BTS_ETH,BTM_ETH,MITH_ETH,NAS_ETH,MAN_ETH,DBC_ETH,BTO_ETH,DDD_ETH,CPX_ETH,CS_ETH,IHT_ETH,OCN_ETH,EKO_ETH,XWC_ETH,PUT_ETH,PNT_ETH,AAC_ETH,FIL6_ETH,FIL12_ETH,FIL36_ETH,SEER_ETH,BSB_ETH,CDC_ETH,GRAMS_ETH,DDMX_ETH,EAI_ETH,BNB_USDT,HT_USDT,KBC_BTC,KBC_USDT,MAI_USDT,PHV_USDT,GT_USDT,VOKEN_USDT,CYE_USDT,BRC_USDT,BTC_AUSD,DDMX_USDT,SEAL_USDT,SEOS_BTC,BTY_USDT,FO_USDT,DLX_USDT,BFC_USDT,LBK_USDT,SERO_USDT,MTV_USDT,CKB_USDT,ARPA_USDT,ZIP_USDT,AT_USDT,DOT_USDT,DILI_USDT,DUO_USDT,TEP_USDT,BIKI_USDT,MX_USDT,DNS_USDT,OKB_USDT,FLDT_USDT,CCTC_USDT,WIN_USDT,BTT_USDT,TRX_USDT,GRS_BTC,GST_USDT,GST_ETH,ABBC_USDT,UTK_USDT,GKI_USDT,BPX_USDT,SUTER_USDT,LT_USDT,LM_USDT,HTDF_USDT" - } - } - }, - "api": { - "authenticatedSupport": false, - "authenticatedWebsocketApiSupport": false, - "endpoints": { - "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" - }, - "credentials": { - "key": "Key", - "secret": "Secret" - }, - "credentialsValidator": { - "requiresKey": true, - "requiresSecret": true - } - }, - "features": { - "supports": { - "restAPI": true, - "restCapabilities": { - "tickerBatching": true, - "autoPairUpdates": true - }, - "websocketAPI": false, - "websocketCapabilities": {} - }, - "enabled": { - "autoPairUpdates": true, - "websocketAPI": false - } - }, - "bankAccounts": [ - { - "enabled": false, - "bankName": "", - "bankAddress": "", - "bankPostalCode": "", - "bankPostalCity": "", - "bankCountry": "", - "accountName": "", - "accountNumber": "", - "swiftCode": "", - "iban": "", - "supportedCurrencies": "" - } - ] + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true, + "requiresClientID": true + } }, - { - "name": "Okcoin", - "enabled": true, - "verbose": false, - "httpTimeout": 15000000000, - "websocketResponseCheckTimeout": 30000000, - "websocketResponseMaxLimit": 7000000000, - "websocketTrafficTimeout": 30000000000, - "websocketOrderbookBufferLimit": 5, - "baseCurrencies": "USD", - "currencyPairs": { - "requestFormat": { - "uppercase": true, - "delimiter": "-" - }, - "configFormat": { - "uppercase": true, - "delimiter": "-" - }, - "useGlobalFormat": true, - "assetTypes": [ - "spot" - ], - "pairs": { - "spot": { - "enabled": "BTC-USD", - "available": "BTC-USD,LTC-USD,ETH-USD,ETC-USD,TUSD-USD,BCH-USD,EOS-USD,XRP-USD,TRX-USD,BSV-USD,USDT-USD,USDK-USD,XLM-USD,ADA-USD,BAT-USD,DCR-USD,EURS-USD,HBAR-USD,PAX-USD,USDC-USD,ZEC-USD,BTC-USDT,BTC-SGD,ETH-SGD,BTC-EUR,BTC-EURS,ETH-EUR,BCH-EUR,EURS-EUR" - }, - "margin": { - "enabled": "BTC-USD", - "available": "BTC-USD" - } - } - }, - "api": { - "authenticatedSupport": false, - "authenticatedWebsocketApiSupport": false, - "endpoints": { - "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" - }, - "credentials": { - "key": "Key", - "secret": "Secret" - }, - "credentialsValidator": { - "requiresKey": true, - "requiresSecret": true, - "requiresClientID": true - } - }, - "features": { - "supports": { - "restAPI": true, - "restCapabilities": { - "tickerBatching": true, - "autoPairUpdates": true - }, - "websocketAPI": true, - "websocketCapabilities": {} - }, - "enabled": { - "autoPairUpdates": true, - "websocketAPI": true - } - }, - "bankAccounts": [ - { - "enabled": false, - "bankName": "", - "bankAddress": "", - "bankPostalCode": "", - "bankPostalCity": "", - "bankCountry": "", - "accountName": "", - "accountNumber": "", - "swiftCode": "", - "iban": "", - "supportedCurrencies": "" - } - ] + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "tickerBatching": true, + "autoPairUpdates": true + }, + "websocketAPI": true, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": true + } }, - { - "name": "Okx", - "enabled": true, - "verbose": false, - "httpTimeout": 15000000000, - "websocketResponseCheckTimeout": 30000000, - "websocketResponseMaxLimit": 7000000000, - "websocketTrafficTimeout": 30000000000, - "baseCurrencies": "USD", - "currencyPairs": { - "bypassConfigFormatUpgrades": false, - "requestFormat": { - "uppercase": true, - "delimiter": "-" - }, - "configFormat": { - "uppercase": true, - "delimiter": "-" - }, - "useGlobalFormat": true, - "assetTypes": [ - "futures", - "margin", - "option", - "perpetualswap", - "spot" - ], - "pairs": { - "futures": { - "assetEnabled": true, - "enabled": "BTC-USD-221007,BTC-USD-221014", - "available": "BTC-USD-221230,BTC-USD-230331,ETH-USD-221007,ETH-USD-221014,ETH-USD-221230,ETH-USD-230331,LTC-USD-221007,LTC-USD-221014,LTC-USD-221230,LTC-USD-230331,DOT-USD-221007,DOT-USD-221014,DOT-USD-221230,DOT-USD-230331,FIL-USD-221007,FIL-USD-221014,FIL-USD-221230,FIL-USD-230331,ADA-USD-221007,ADA-USD-221014,ADA-USD-221230,ADA-USD-230331,AVAX-USD-221007,AVAX-USD-221014,AVAX-USD-221230,BTC-USD-221007,BTC-USD-221014,BTC-USD-221230,BTC-USD-230331,ETH-USD-221007,ETH-USD-221014,ETH-USD-221230,ETH-USD-230331,LTC-USD-221007,LTC-USD-221014,LTC-USD-221230,LTC-USD-230331,DOT-USD-221007,DOT-USD-221014,DOT-USD-221230,DOT-USD-230331,FIL-USD-221007,FIL-USD-221014,FIL-USD-221230,FIL-USD-230331,ADA-USD-221007,ADA-USD-221014,ADA-USD-221230,ADA-USD-230331,AVAX-USD-221007,AVAX-USD-221014,AVAX-USD-221230,AVAX-USD-230331,BCH-USD-221007,BCH-USD-221014,BCH-USD-221230,BCH-USD-230331,EOS-USD-221007,EOS-USD-221014,EOS-USD-221230,EOS-USD-230331,ETC-USD-221007,ETC-USD-221014,ETC-USD-221230,ETC-USD-230331,LINK-USD-221007,LINK-USD-221014,LINK-USD-221230,LINK-USD-230331,SOL-USD-221007,SOL-USD-221014,SOL-USD-221230,SOL-USD-230331,TRX-USD-221007,TRX-USD-221014,TRX-USD-221230,TRX-USD-230331,XRP-USD-221007,XRP-USD-221014,XRP-USD-221230,XRP-USD-230331,BTC-USDT-221007,BTC-USDT-221014,BTC-USDT-221230,BTC-USDT-230331,ETH-USDT-221007,ETH-USDT-221014,ETH-USDT-221230,ETH-USDT-230331,LTC-USDT-221007,LTC-USDT-221014,LTC-USDT-221230,LTC-USDT-230331,DOT-USDT-221007,DOT-USDT-221014,DOT-USDT-221230,DOT-USDT-230331,FIL-USDT-221007,FIL-USDT-221014,FIL-USDT-221230,FIL-USDT-230331,ADA-USDT-221007,ADA-USDT-221014,ADA-USDT-221230,ADA-USDT-230331,BCH-USDT-221007,BCH-USDT-221014,BCH-USDT-221230,BCH-USDT-230331,EOS-USDT-221007,EOS-USDT-221014,EOS-USDT-221230,EOS-USDT-230331,ETC-USDT-221007,ETC-USDT-221014,ETC-USDT-221230,ETC-USDT-230331,LINK-USDT-221007,LINK-USDT-221014,LINK-USDT-221230,LINK-USDT-230331,TRX-USDT-221007,TRX-USDT-221014,TRX-USDT-221230,TRX-USDT-230331,XRP-USDT-221007,XRP-USDT-221014,XRP-USDT-221230,XRP-USDT-230331" - }, - "margin": { - "assetEnabled": true, - "enabled": "BTC-USDT,ETH-USDT,OKB-USDT", - "available": "LTC-USDT,DOT-USDT,DOGE-USDT,LUNC-USDT,ETHW-USDT,LUNA-USDT,FIL-USDT,XRP-USDT,1INCH-USDT,AAVE-USDT,ADA-USDT,AGLD-USDT,AKITA-USDT,ALGO-USDT,ALPHA-USDT,BTC-USDT,ETH-USDT,OKB-USDT,LTC-USDT,DOT-USDT,DOGE-USDT,LUNC-USDT,ETHW-USDT,LUNA-USDT,FIL-USDT,XRP-USDT,1INCH-USDT,AAVE-USDT,ADA-USDT,AGLD-USDT,AKITA-USDT,ALGO-USDT,ALPHA-USDT,ANT-USDT,APE-USDT,API3-USDT,ASTR-USDT,ATOM-USDT,AVAX-USDT,AXS-USDT,BABYDOGE-USDT,BADGER-USDT,BAL-USDT,BAND-USDT,BAT-USDT,BCH-USDT,BICO-USDT,BNT-USDT,BSV-USDT,BTM-USDT,BTT-USDT,BZZ-USDT,CELO-USDT,CEL-USDT,CELR-USDT,CFX-USDT,CHZ-USDT,CLV-USDT,COMP-USDT,CONV-USDT,CQT-USDT,CRO-USDT,CRV-USDT,CSPR-USDT,CVC-USDT,DASH-USDT,DOME-USDT,DORA-USDT,DYDX-USDT,EFI-USDT,EGLD-USDT,ELF-USDT,ENJ-USDT,ENS-USDT,EOS-USDT,ETC-USDT,FITFI-USDT,FLM-USDT,FLOW-USDT,FTM-USDT,GALA-USDT,GLMR-USDT,GMT-USDT,GODS-USDT,GRT-USDT,HBAR-USDT,HC-USDT,ICP-USDT,IMX-USDT,IOST-USDT,IOTA-USDT,JST-USDT,KAR-USDT,KISHU-USDT,KNC-USDT,KSM-USDT,LAMB-USDT,LAT-USDT,LINK-USDT,LON-USDT,LOOKS-USDT,LPT-USDT,LRC-USDT,MANA-USDT,MASK-USDT,MATIC-USDT,MINA-USDT,MKR-USDT,NAS-USDT,NEAR-USDT,NEO-USDT,NFT-USDT,NYM-USDT,OMG-USDT,ONT-USDT,OP-USDT,PEOPLE-USDT,PERP-USDT,QTUM-USDT,REN-USDT,RSR-USDT,RSS3-USDT,RVN-USDT,SAND-USDT,SC-USDT,SHIB-USDT,SKL-USDT,SLP-USDT,SNT-USDT,SNX-USDT,SOL-USDT,SOS-USDT,SRM-USDT,STARL-USDT,STORJ-USDT,SUSHI-USDT,SWEAT-USDT,SWRV-USDT,THETA-USDT,TORN-USDT,TRB-USDT,TRX-USDT,UMA-USDT,UMEE-USDT,UNI-USDT,USDC-USDT,VSYS-USDT,WAVES-USDT,WNCG-USDT,WNXM-USDT,XCH-USDT,XEM-USDT,XLM-USDT,XMR-USDT,XTZ-USDT,YFI-USDT,YFII-USDT,YGG-USDT,ZEC-USDT,ZEN-USDT,ZIL-USDT,ZRX-USDT,BTC-USDC,ETH-BTC,LTC-USDC,DOT-USDC,DOGE-USDC,LUNC-USDC,LUNA-USDC,XRP-USDC,ADA-USDC,ATOM-USDC,AVAX-USDC,NEAR-USDC,OP-USDC,SOL-USDC,OKB-BTC,LTC-BTC,DOT-BTC,DOGE-BTC,FIL-BTC,XRP-BTC,AAVE-BTC,ADA-BTC,ALGO-BTC,ANT-BTC,ATOM-BTC,AVAX-BTC,BADGER-BTC,BAT-BTC,BCH-BTC,BNT-BTC,BSV-BTC,BTM-BTC,CHZ-BTC,COMP-BTC,CRO-BTC,CRV-BTC,CVC-BTC,DASH-BTC,EGLD-BTC,ELF-BTC,ENJ-BTC,EOS-BTC,ETC-BTC,GRT-BTC,HBAR-BTC,HC-BTC,ICP-BTC,IOST-BTC,IOTA-BTC,KNC-BTC,KSM-BTC,LINK-BTC,LRC-BTC,MANA-BTC,MKR-BTC,NAS-BTC,NEAR-BTC,NEO-BTC,OMG-BTC,ONT-BTC,QTUM-BTC,REN-BTC,RSR-BTC,RVN-BTC,SNT-BTC,SOL-BTC,SRM-BTC,THETA-BTC,TRX-BTC,UNI-BTC,VSYS-BTC,WAVES-BTC,XCH-BTC,XEM-BTC,XLM-BTC,XMR-BTC,XTZ-BTC,ZEC-BTC,ZIL-BTC,ZRX-BTC" - }, - "option": { - "assetEnabled": true, - "enabled": "BTC-USD-220930-28000-P,BTC-USD-220930-30000-C", - "available": "BTC-USD-220930-30000-P,BTC-USD-220930-32000-C,BTC-USD-220930-32000-P,BTC-USD-220930-34000-C,BTC-USD-220930-34000-P,BTC-USD-220930-35000-C,BTC-USD-220930-35000-P,BTC-USD-220930-36000-C,BTC-USD-220930-36000-P,BTC-USD-220930-40000-C,BTC-USD-220930-40000-P,BTC-USD-220930-45000-C,BTC-USD-220930-45000-P,BTC-USD-220930-50000-C,BTC-USD-220930-50000-P,BTC-USD-220930-55000-C,BTC-USD-220930-55000-P,BTC-USD-220930-60000-C,BTC-USD-220930-60000-P,BTC-USD-220930-65000-C,BTC-USD-220930-65000-P,BTC-USD-220930-70000-C,BTC-USD-220930-70000-P,BTC-USD-220930-80000-C,BTC-USD-220930-80000-P,BTC-USD-220930-90000-C,BTC-USD-220930-90000-P,BTC-USD-220930-100000-C,BTC-USD-220930-100000-P,BTC-USD-220930-120000-C,BTC-USD-220930-120000-P,BTC-USD-221007-13000-C,BTC-USD-221007-13000-P,BTC-USD-221007-14000-C,BTC-USD-221007-14000-P,BTC-USD-221007-15000-C,BTC-USD-221007-15000-P,BTC-USD-221007-16000-C,BTC-USD-221007-16000-P,BTC-USD-221007-17000-C,BTC-USD-221007-17000-P,BTC-USD-221007-18000-C,BTC-USD-221007-18000-P,BTC-USD-221007-18500-C,BTC-USD-221007-18500-P,BTC-USD-221007-19000-C,BTC-USD-221007-19000-P,BTC-USD-221007-20000-C,BTC-USD-221007-20000-P,BTC-USD-221007-21000-C,BTC-USD-221007-21000-P,BTC-USD-221007-22000-C,BTC-USD-221007-22000-P,BTC-USD-221007-23000-C,BTC-USD-221007-23000-P,BTC-USD-221007-24000-C,BTC-USD-221007-24000-P,BTC-USD-221007-26000-C,BTC-USD-221007-26000-P,BTC-USD-221007-28000-C,BTC-USD-221007-28000-P,BTC-USD-221007-30000-C,BTC-USD-221007-30000-P,BTC-USD-221014-14000-C,BTC-USD-221014-14000-P,BTC-USD-221014-15000-C,BTC-USD-221014-15000-P,BTC-USD-221014-16000-C,BTC-USD-221014-16000-P,BTC-USD-221014-17000-C,BTC-USD-221014-17000-P,BTC-USD-221014-18000-C,BTC-USD-221014-18000-P,BTC-USD-221014-19000-C,BTC-USD-221014-19000-P,BTC-USD-221014-20000-C,BTC-USD-221014-20000-P,BTC-USD-221014-21000-C,BTC-USD-221014-21000-P,BTC-USD-221014-22000-C,BTC-USD-221014-22000-P,BTC-USD-221014-23000-C,BTC-USD-221014-23000-P,BTC-USD-221014-24000-C,BTC-USD-221014-24000-P,BTC-USD-221014-26000-C,BTC-USD-221014-26000-P,BTC-USD-221014-28000-C,BTC-USD-221014-28000-P,BTC-USD-221014-30000-C,BTC-USD-221014-30000-P,BTC-USD-221028-10000-C,BTC-USD-221028-10000-P,BTC-USD-221028-12000-C,BTC-USD-221028-12000-P,BTC-USD-221028-14000-C,BTC-USD-221028-14000-P,BTC-USD-221028-16000-C,BTC-USD-221028-16000-P,BTC-USD-221028-17000-C,BTC-USD-221028-17000-P,BTC-USD-221028-18000-C,BTC-USD-221028-18000-P,BTC-USD-221028-19000-C,BTC-USD-221028-19000-P,BTC-USD-221028-20000-C,BTC-USD-221028-20000-P,BTC-USD-221028-21000-C,BTC-USD-221028-21000-P,BTC-USD-221028-22000-C,BTC-USD-221028-22000-P,BTC-USD-221028-24000-C,BTC-USD-221028-24000-P,BTC-USD-221028-26000-C,BTC-USD-221028-26000-P,BTC-USD-221028-28000-C,BTC-USD-221028-28000-P,BTC-USD-221028-30000-C,BTC-USD-221028-30000-P,BTC-USD-221028-32000-C,BTC-USD-221028-32000-P,BTC-USD-221028-35000-C,BTC-USD-220927-17000-C,BTC-USD-220927-17000-P,BTC-USD-220927-17500-C,BTC-USD-220927-17500-P,BTC-USD-220927-18000-C,BTC-USD-220927-18000-P,BTC-USD-220927-18200-C,BTC-USD-220927-18200-P,BTC-USD-220927-18400-C,BTC-USD-220927-18400-P,BTC-USD-220927-18600-C,BTC-USD-220927-18600-P,BTC-USD-220927-18800-C,BTC-USD-220927-18800-P,BTC-USD-220927-19000-C,BTC-USD-220927-19000-P,BTC-USD-220927-19200-C,BTC-USD-220927-19200-P,BTC-USD-220927-19400-C,BTC-USD-220927-19400-P,BTC-USD-220927-19600-C,BTC-USD-220927-19600-P,BTC-USD-220927-19800-C,BTC-USD-220927-19800-P,BTC-USD-220927-20000-C,BTC-USD-220927-20000-P,BTC-USD-220927-20500-C,BTC-USD-220927-20500-P,BTC-USD-220927-21000-C,BTC-USD-220927-21000-P,BTC-USD-220927-21500-C,BTC-USD-220927-21500-P,BTC-USD-220928-16500-C,BTC-USD-220928-16500-P,BTC-USD-220928-17000-C,BTC-USD-220928-17000-P,BTC-USD-220928-17500-C,BTC-USD-220928-17500-P,BTC-USD-220928-18000-C,BTC-USD-220928-18000-P,BTC-USD-220928-18200-C,BTC-USD-220928-18200-P,BTC-USD-220928-18400-C,BTC-USD-220928-18400-P,BTC-USD-220928-18600-C,BTC-USD-220928-18600-P,BTC-USD-220928-18800-C,BTC-USD-220928-18800-P,BTC-USD-220928-19000-C,BTC-USD-220928-19000-P,BTC-USD-220928-19200-C,BTC-USD-220928-19200-P,BTC-USD-220928-19400-C,BTC-USD-220928-19400-P,BTC-USD-220928-19600-C,BTC-USD-220928-19600-P,BTC-USD-220928-20000-C,BTC-USD-220928-20000-P,BTC-USD-220928-20500-C,BTC-USD-220928-20500-P,BTC-USD-220928-21000-C,BTC-USD-220928-21000-P,BTC-USD-220928-21500-C,BTC-USD-220928-21500-P,BTC-USD-220930-5000-C,BTC-USD-220930-5000-P,BTC-USD-220930-10000-C,BTC-USD-220930-10000-P,BTC-USD-220930-12000-C,BTC-USD-220930-12000-P,BTC-USD-220930-15000-C,BTC-USD-220930-15000-P,BTC-USD-220930-16000-C,BTC-USD-220930-16000-P,BTC-USD-220930-17000-C,BTC-USD-220930-17000-P,BTC-USD-220930-17500-C,BTC-USD-220930-17500-P,BTC-USD-220930-18000-C,BTC-USD-220930-18000-P,BTC-USD-220930-18500-C,BTC-USD-220930-18500-P,BTC-USD-220930-19000-C,BTC-USD-220930-19000-P,BTC-USD-220930-19500-C,BTC-USD-220930-19500-P,BTC-USD-220930-20000-C,BTC-USD-220930-20000-P,BTC-USD-220930-20500-C,BTC-USD-220930-20500-P,BTC-USD-220930-21000-C,BTC-USD-220930-21000-P,BTC-USD-220930-22000-C,BTC-USD-220930-22000-P,BTC-USD-220930-23000-C,BTC-USD-220930-23000-P,BTC-USD-220930-24000-C,BTC-USD-220930-24000-P,BTC-USD-220930-25000-C,BTC-USD-220930-25000-P,BTC-USD-220930-26000-C,BTC-USD-220930-26000-P,BTC-USD-220930-28000-C,BTC-USD-220930-28000-P,BTC-USD-220930-30000-C,BTC-USD-220930-30000-P,BTC-USD-220930-32000-C,BTC-USD-220930-32000-P,BTC-USD-220930-34000-C,BTC-USD-220930-34000-P,BTC-USD-220930-35000-C,BTC-USD-220930-35000-P,BTC-USD-220930-36000-C,BTC-USD-220930-36000-P,BTC-USD-220930-40000-C,BTC-USD-220930-40000-P,BTC-USD-220930-45000-C,BTC-USD-220930-45000-P,BTC-USD-220930-50000-C,BTC-USD-220930-50000-P,BTC-USD-220930-55000-C,BTC-USD-220930-55000-P,BTC-USD-220930-60000-C,BTC-USD-220930-60000-P,BTC-USD-220930-65000-C,BTC-USD-220930-65000-P,BTC-USD-220930-70000-C,BTC-USD-220930-70000-P,BTC-USD-220930-80000-C,BTC-USD-220930-80000-P,BTC-USD-220930-90000-C,BTC-USD-220930-90000-P,BTC-USD-220930-100000-C,BTC-USD-220930-100000-P,BTC-USD-220930-120000-C,BTC-USD-220930-120000-P,BTC-USD-221007-13000-C,BTC-USD-221007-13000-P,BTC-USD-221007-14000-C,BTC-USD-221007-14000-P,BTC-USD-221007-15000-C,BTC-USD-221007-15000-P,BTC-USD-221007-16000-C,BTC-USD-221007-16000-P,BTC-USD-221007-17000-C,BTC-USD-221007-17000-P,BTC-USD-221007-18000-C,BTC-USD-221007-18000-P,BTC-USD-221007-18500-C,BTC-USD-221007-18500-P,BTC-USD-221007-19000-C,BTC-USD-221007-19000-P,BTC-USD-221007-20000-C,BTC-USD-221007-20000-P,BTC-USD-221007-21000-C,BTC-USD-221007-21000-P,BTC-USD-221007-22000-C,BTC-USD-221007-22000-P,BTC-USD-221007-23000-C,BTC-USD-221007-23000-P,BTC-USD-221007-24000-C,BTC-USD-221007-24000-P,BTC-USD-221007-26000-C,BTC-USD-221007-26000-P,BTC-USD-221007-28000-C,BTC-USD-221007-28000-P,BTC-USD-221007-30000-C,BTC-USD-221007-30000-P,BTC-USD-221014-14000-C,BTC-USD-221014-14000-P,BTC-USD-221014-15000-C,BTC-USD-221014-15000-P,BTC-USD-221014-16000-C,BTC-USD-221014-16000-P,BTC-USD-221014-17000-C,BTC-USD-221014-17000-P,BTC-USD-221014-18000-C,BTC-USD-221014-18000-P,BTC-USD-221014-19000-C,BTC-USD-221014-19000-P,BTC-USD-221014-20000-C,BTC-USD-221014-20000-P,BTC-USD-221014-21000-C,BTC-USD-221014-21000-P,BTC-USD-221014-22000-C,BTC-USD-221014-22000-P,BTC-USD-221014-23000-C,BTC-USD-221014-23000-P,BTC-USD-221014-24000-C,BTC-USD-221014-24000-P,BTC-USD-221014-26000-C,BTC-USD-221014-26000-P,BTC-USD-221014-28000-C,BTC-USD-221014-28000-P,BTC-USD-221014-30000-C,BTC-USD-221014-30000-P,BTC-USD-221028-10000-C,BTC-USD-221028-10000-P,BTC-USD-221028-12000-C,BTC-USD-221028-12000-P,BTC-USD-221028-14000-C,BTC-USD-221028-14000-P,BTC-USD-221028-16000-C,BTC-USD-221028-16000-P,BTC-USD-221028-17000-C,BTC-USD-221028-17000-P,BTC-USD-221028-18000-C,BTC-USD-221028-18000-P,BTC-USD-221028-19000-C,BTC-USD-221028-19000-P,BTC-USD-221028-20000-C,BTC-USD-221028-20000-P,BTC-USD-221028-21000-C,BTC-USD-221028-21000-P,BTC-USD-221028-22000-C,BTC-USD-221028-22000-P,BTC-USD-221028-24000-C,BTC-USD-221028-24000-P,BTC-USD-221028-26000-C,BTC-USD-221028-26000-P,BTC-USD-221028-28000-C,BTC-USD-221028-28000-P,BTC-USD-221028-30000-C,BTC-USD-221028-30000-P,BTC-USD-221028-32000-C,BTC-USD-221028-32000-P,BTC-USD-221028-35000-C,BTC-USD-221028-35000-P,BTC-USD-221028-40000-C,BTC-USD-221028-40000-P,BTC-USD-221028-50000-C,BTC-USD-221028-50000-P,BTC-USD-221028-60000-C,BTC-USD-221028-60000-P,BTC-USD-221028-70000-C,BTC-USD-221028-70000-P,BTC-USD-221125-5000-C,BTC-USD-221125-5000-P,BTC-USD-221125-10000-C,BTC-USD-221125-10000-P,BTC-USD-221125-12000-C,BTC-USD-221125-12000-P,BTC-USD-221125-15000-C,BTC-USD-221125-15000-P,BTC-USD-221125-16000-C,BTC-USD-221125-16000-P,BTC-USD-221125-17000-C,BTC-USD-221125-17000-P,BTC-USD-221125-18000-C,BTC-USD-221125-18000-P,BTC-USD-221125-20000-C,BTC-USD-221125-20000-P,BTC-USD-221125-22000-C,BTC-USD-221125-22000-P,BTC-USD-221125-24000-C,BTC-USD-221125-24000-P,BTC-USD-221125-26000-C,BTC-USD-221125-26000-P,BTC-USD-221125-28000-C,BTC-USD-221125-28000-P,BTC-USD-221125-30000-C,BTC-USD-221125-30000-P,BTC-USD-221125-32000-C,BTC-USD-221125-32000-P,BTC-USD-221125-35000-C,BTC-USD-221125-35000-P,BTC-USD-221125-40000-C,BTC-USD-221125-40000-P,BTC-USD-221125-50000-C,BTC-USD-221125-50000-P,BTC-USD-221125-60000-C,BTC-USD-221125-60000-P,BTC-USD-221125-70000-C,BTC-USD-221125-70000-P,BTC-USD-221230-5000-C,BTC-USD-221230-5000-P,BTC-USD-221230-10000-C,BTC-USD-221230-10000-P,BTC-USD-221230-12000-C,BTC-USD-221230-12000-P,BTC-USD-221230-13000-C,BTC-USD-221230-13000-P,BTC-USD-221230-15000-C,BTC-USD-221230-15000-P,BTC-USD-221230-16000-C,BTC-USD-221230-16000-P,BTC-USD-221230-17000-C,BTC-USD-221230-17000-P,BTC-USD-221230-18000-C,BTC-USD-221230-18000-P,BTC-USD-221230-19000-C,BTC-USD-221230-19000-P,BTC-USD-221230-20000-C,BTC-USD-221230-20000-P,BTC-USD-221230-21000-C,BTC-USD-221230-21000-P,BTC-USD-221230-22000-C,BTC-USD-221230-22000-P,BTC-USD-221230-23000-C,BTC-USD-221230-23000-P,BTC-USD-221230-24000-C,BTC-USD-221230-24000-P,BTC-USD-221230-25000-C,BTC-USD-221230-25000-P,BTC-USD-221230-26000-C,BTC-USD-221230-26000-P,BTC-USD-221230-28000-C,BTC-USD-221230-28000-P,BTC-USD-221230-30000-C,BTC-USD-221230-30000-P,BTC-USD-221230-32000-C,BTC-USD-221230-32000-P,BTC-USD-221230-35000-C,BTC-USD-221230-35000-P" - }, - "perpetualswap": { - "assetEnabled": true, - "enabled": "BTC-USD-SWAP,ETH-USD-SWAP", - "available": "LTC-USD-SWAP,DOT-USD-SWAP,DOGE-USD-SWAP,MASK-USDT-SWAP,MATIC-USDT-SWAP,MINA-USDT-SWAP,MKR-USDT-SWAP,NEAR-USDT-SWAP,NEO-USDT-SWAP,NFT-USDT-SWAP,NYM-USDT-SWAP,OMG-USDT-SWAP,ONT-USDT-SWAP,OP-USDT-SWAP,PEOPLE-USDT-SWAP,BTC-USD-SWAP,ETH-USD-SWAP,LTC-USD-SWAP,DOT-USD-SWAP,DOGE-USD-SWAP,FIL-USD-SWAP,XRP-USD-SWAP,1INCH-USD-SWAP,ADA-USD-SWAP,ALGO-USD-SWAP,ATOM-USD-SWAP,AVAX-USD-SWAP,BCH-USD-SWAP,BSV-USD-SWAP,CRV-USD-SWAP,DASH-USD-SWAP,EOS-USD-SWAP,ETC-USD-SWAP,GRT-USD-SWAP,IOST-USD-SWAP,IOTA-USD-SWAP,KNC-USD-SWAP,KSM-USD-SWAP,LINK-USD-SWAP,MANA-USD-SWAP,NEO-USD-SWAP,ONT-USD-SWAP,QTUM-USD-SWAP,SAND-USD-SWAP,SOL-USD-SWAP,SUSHI-USD-SWAP,THETA-USD-SWAP,TRX-USD-SWAP,UNI-USD-SWAP,XLM-USD-SWAP,XMR-USD-SWAP,XTZ-USD-SWAP,YFI-USD-SWAP,YFII-USD-SWAP,ZEC-USD-SWAP,BTC-USDT-SWAP,ETH-USDT-SWAP,LTC-USDT-SWAP,DOT-USDT-SWAP,DOGE-USDT-SWAP,LUNC-USDT-SWAP,ETHW-USDT-SWAP,LUNA-USDT-SWAP,FIL-USDT-SWAP,XRP-USDT-SWAP,1INCH-USDT-SWAP,AAVE-USDT-SWAP,ADA-USDT-SWAP,AGLD-USDT-SWAP,ALGO-USDT-SWAP,ALPHA-USDT-SWAP,ANT-USDT-SWAP,APE-USDT-SWAP,API3-USDT-SWAP,ASTR-USDT-SWAP,ATOM-USDT-SWAP,AVAX-USDT-SWAP,AXS-USDT-SWAP,BABYDOGE-USDT-SWAP,BADGER-USDT-SWAP,BAL-USDT-SWAP,BAND-USDT-SWAP,BAT-USDT-SWAP,BCH-USDT-SWAP,BICO-USDT-SWAP,BNT-USDT-SWAP,BSV-USDT-SWAP,BTT-USDT-SWAP,CELO-USDT-SWAP,CEL-USDT-SWAP,CFX-USDT-SWAP,CHZ-USDT-SWAP,COMP-USDT-SWAP,CRO-USDT-SWAP,CRV-USDT-SWAP,CSPR-USDT-SWAP,CVC-USDT-SWAP,DASH-USDT-SWAP,DOME-USDT-SWAP,DORA-USDT-SWAP,DYDX-USDT-SWAP,EGLD-USDT-SWAP,ELON-USDT-SWAP,ENJ-USDT-SWAP,ENS-USDT-SWAP,EOS-USDT-SWAP,ETC-USDT-SWAP,FITFI-USDT-SWAP,FLM-USDT-SWAP,FTM-USDT-SWAP,GALA-USDT-SWAP,GMT-USDT-SWAP,GODS-USDT-SWAP,GRT-USDT-SWAP,ICP-USDT-SWAP,IMX-USDT-SWAP,IOST-USDT-SWAP,IOTA-USDT-SWAP,JST-USDT-SWAP,KISHU-USDT-SWAP,KNC-USDT-SWAP,KSM-USDT-SWAP,LINK-USDT-SWAP,LOOKS-USDT-SWAP,LPT-USDT-SWAP,LRC-USDT-SWAP,MANA-USDT-SWAP,MASK-USDT-SWAP,MATIC-USDT-SWAP,MINA-USDT-SWAP,MKR-USDT-SWAP,NEAR-USDT-SWAP,NEO-USDT-SWAP,NFT-USDT-SWAP,NYM-USDT-SWAP,OMG-USDT-SWAP,ONT-USDT-SWAP,OP-USDT-SWAP,PEOPLE-USDT-SWAP,PERP-USDT-SWAP,QTUM-USDT-SWAP,REN-USDT-SWAP,RSR-USDT-SWAP,RVN-USDT-SWAP,SAND-USDT-SWAP,SC-USDT-SWAP,SHIB-USDT-SWAP,SLP-USDT-SWAP,SNX-USDT-SWAP,SOL-USDT-SWAP,SOS-USDT-SWAP,SRM-USDT-SWAP,STARL-USDT-SWAP,STORJ-USDT-SWAP,SUSHI-USDT-SWAP,SWEAT-USDT-SWAP,THETA-USDT-SWAP,TRB-USDT-SWAP,TRX-USDT-SWAP,UMA-USDT-SWAP,UMEE-USDT-SWAP,UNI-USDT-SWAP,WAVES-USDT-SWAP,WNXM-USDT-SWAP,XCH-USDT-SWAP,XEM-USDT-SWAP,XLM-USDT-SWAP,XMR-USDT-SWAP,XTZ-USDT-SWAP,YFI-USDT-SWAP,YFII-USDT-SWAP,YGG-USDT-SWAP,ZEC-USDT-SWAP,ZEN-USDT-SWAP,ZIL-USDT-SWAP,ZRX-USDT-SWAP" - }, - "spot": { - "assetEnabled": true, - "enabled": "BTC-USDT,ETH-USDT,OKB-USDT", - "available": "OKT-USDT,LTC-USDT,DOT-USDT,DOGE-USDT,LUNC-USDT,ETHW-USDT,LUNA-USDT,FIL-USDT,XRP-USDT,CITY-USDT,MENGO-USDT,ARG-USDT,POR-USDT,1INCH-USDT,AAVE-USDT,ABT-USDT,ACA-USDT,ADA-USDT,AERGO-USDT,AGLD-USDT,XMR-ETH,YFI-ETH,ZEC-ETH,LTC-OKB,XRP-OKB,ETC-OKB,BTC-USDT,ETH-USDT,OKB-USDT,OKT-USDT,LTC-USDT,DOT-USDT,DOGE-USDT,LUNC-USDT,ETHW-USDT,LUNA-USDT,FIL-USDT,XRP-USDT,CITY-USDT,MENGO-USDT,ARG-USDT,POR-USDT,1INCH-USDT,AAVE-USDT,ABT-USDT,ACA-USDT,ADA-USDT,AERGO-USDT,AGLD-USDT,AKITA-USDT,ALCX-USDT,ALGO-USDT,ALPHA-USDT,ANC-USDT,ANT-USDT,ANW-USDT,APE-USDT,APIX-USDT,API3-USDT,APM-USDT,AR-USDT,ARK-USDT,AST-USDT,ASTR-USDT,ATOM-USDT,AUCTION-USDT,AVAX-USDT,AXS-USDT,AZY-USDT,BABYDOGE-USDT,BADGER-USDT,BAL-USDT,BAND-USDT,BAT-USDT,BCD-USDT,BCH-USDT,BETH-USDT,BHP-USDT,BICO-USDT,BLOK-USDT,BNT-USDT,BORING-USDT,BORA-USDT,BRWL-USDT,BSV-USDT,BTG-USDT,BTM-USDT,BTT-USDT,BZZ-USDT,CELO-USDT,CEL-USDT,CELR-USDT,CELT-USDT,CFG-USDT,CFX-USDT,CGS-USDT,CHAT-USDT,CHE-USDT,CHZ-USDT,CLV-USDT,CMT-USDT,CNTM-USDT,COMP-USDT,CONV-USDT,COVER-USDT,CQT-USDT,CRO-USDT,CRV-USDT,CSPR-USDT,CTC-USDT,CTXC-USDT,CVC-USDT,CVP-USDT,CVT-USDT,CVX-USDT,DAI-USDT,DAO-USDT,DASH-USDT,DCR-USDT,DEP-USDT,DEVT-USDT,DGB-USDT,DHT-USDT,DIA-USDT,DMD-USDT,DNA-USDT,DOME-USDT,DORA-USDT,DOSE-USDT,DYDX-USDT,EC-USDT,EDEN-USDT,EFI-USDT,EGLD-USDT,EGT-USDT,ELF-USDT,ELON-USDT,ELT-USDT,EM-USDT,ENJ-USDT,ENS-USDT,EOS-USDT,ERN-USDT,ETC-USDT,EURT-USDT,FAIR-USDT,FAME-USDT,FITFI-USDT,FLM-USDT,FLOW-USDT,FODL-USDT,FORTH-USDT,FRONT-USDT,FSN-USDT,FTM-USDT,GALA-USDT,GALFT-USDT,GARI-USDT,GAS-USDT,GF-USDT,GHST-USDT,GLM-USDT,GLMR-USDT,GM-USDT,GMT-USDT,GODS-USDT,GOG-USDT,GRT-USDT,GTO-USDT,GUSD-USDT,HBAR-USDT,HC-USDT,HDAO-USDT,HEGIC-USDT,HYC-USDT,ICP-USDT,ICX-USDT,ILV-USDT,IMX-USDT,INT-USDT,INX-USDT,IOST-USDT,IOTA-USDT,IQ-USDT,JFI-USDT,JOE-USDT,JST-USDT,KAN-USDT,KAR-USDT,KCASH-USDT,KDA-USDT,KINE-USDT,KISHU-USDT,KLAY-USDT,KNC-USDT,KOL-USDT,KONO-USDT,KP3R-USDT,KSM-USDT,LAMB-USDT,LAT-USDT,LBA-USDT,LDN-USDT,LDO-USDT,LEASH-USDT,LEO-USDT,LET-USDT,LINK-USDT,LING-USDT,LITH-USDT,LON-USDT,LOON-USDT,LOOKS-USDT,LPT-USDT,LRC-USDT,LSK-USDT,MAGIC-USDT,MANA-USDT,MASK-USDT,MATIC-USDT,MCO-USDT,MDA-USDT,MDT-USDT,MEME-USDT,METIS-USDT,MILO-USDT,MINA-USDT,MIR-USDT,MITH-USDT,MKR-USDT,MLN-USDT,MOF-USDT,MON-USDT,MOVR-USDT,MOVEZ-USDT,MXC-USDT,MXT-USDT,NAS-USDT,NEAR-USDT,NEO-USDT,NFT-USDT,NMR-USDT,NULS-USDT,NYM-USDT,OM-USDT,OMG-USDT,OMI-USDT,ONE-USDT,ONT-USDT,OP-USDT,ORBS-USDT,ORB-USDT,ORS-USDT,OXT-USDT,PAY-USDT,PCI-USDT,PEOPLE-USDT,PERP-USDT,PHA-USDT,PICKLE-USDT,PIT-USDT,PLG-USDT,PNK-USDT,POLS-USDT,POLYDOGE-USDT,PPT-USDT,PRQ-USDT,PST-USDT,PSTAKE-USDT,QOM-USDT,QTUM-USDT,RACA-USDT,RAY-USDT,REN-USDT,REP-USDT,REVV-USDT,RFUEL-USDT,RIO-USDT,RNT-USDT,ROAD-USDT,RON-USDT,RSR-USDT,RSS3-USDT,RVN-USDT,SAITAMA-USDT,SAMO-USDT,SAND-USDT,SC-USDT,SD-USDT,SFG-USDT,SHIB-USDT,SIS-USDT,SKEB-USDT,SKL-USDT,SLP-USDT,SNT-USDT,SNX-USDT,SOC-USDT,SOL-USDT,SOS-USDT,SPELL-USDT,SRM-USDT,STARL-USDT,STC-USDT,STORJ-USDT,STRK-USDT,STX-USDT,SUN-USDT,SUSHI-USDT,SWEAT-USDT,SWFTC-USDT,SWRV-USDT,T-USDT,TAI-USDT,TAKI-USDT,TCT-USDT,THETA-USDT,THG-USDT,TON-USDT,TOPC-USDT,TORN-USDT,TOWN-USDT,TRADE-USDT,TRA-USDT,TRB-USDT,TRUE-USDT,TRX-USDT,TUP-USDT,TUSD-USDT,UMA-USDT,UMEE-USDT,UNI-USDT,USDC-USDT,USDP-USDT,USTC-USDT,UTK-USDT,VALUE-USDT,VELO-USDT,VRA-USDT,VSYS-USDT,WAVES-USDT,WAXP-USDT,WBTC-USDT,WEMIX-USDT,WGRT-USDT,WING-USDT,WIN-USDT,WNCG-USDT,WNXM-USDT,WOO-USDT,WSB-USDT,WXT-USDT,XAUT-USDT,XCH-USDT,XEC-USDT,XEM-USDT,XETA-USDT,XLM-USDT,XMR-USDT,XNO-USDT,XPR-USDT,XTZ-USDT,YEE-USDT,YFI-USDT,YFII-USDT,YGG-USDT,YOU-USDT,YOYO-USDT,ZBC-USDT,ZEC-USDT,ZEN-USDT,ZIL-USDT,ZKS-USDT,ZRX-USDT,ZYRO-USDT,BTC-USDC,ETH-USDC,ETH-BTC,OKB-USDC,OKT-USDC,LTC-USDC,DOT-USDC,DOGE-USDC,LUNC-USDC,ETHW-USDC,LUNA-USDC,FIL-USDC,XRP-USDC,1INCH-USDC,AAVE-USDC,ADA-USDC,AGLD-USDC,ALGO-USDC,ANC-USDC,ANT-USDC,APE-USDC,API3-USDC,AR-USDC,ASTR-USDC,ATOM-USDC,AVAX-USDC,AXS-USDC,AZY-USDC,BABYDOGE-USDC,BAT-USDC,BCH-USDC,BICO-USDC,BSV-USDC,CEL-USDC,CELO-USDC,CELT-USDC,CHZ-USDC,COMP-USDC,CRO-USDC,CRV-USDC,CSPR-USDC,DASH-USDC,DEP-USDC,DOME-USDC,DYDX-USDC,EGLD-USDC,ELT-USDC,ENS-USDC,EOS-USDC,ETC-USDC,FITFI-USDC,FLM-USDC,FLOW-USDC,FTM-USDC,GALA-USDC,GALFT-USDC,GARI-USDC,GLMR-USDC,GMT-USDC,GODS-USDC,GRT-USDC,HBAR-USDC,ICP-USDC,IMX-USDC,IOST-USDC,JST-USDC,KISHU-USDC,KLAY-USDC,KNC-USDC,KSM-USDC,LINK-USDC,LOOKS-USDC,LRC-USDC,MANA-USDC,MASK-USDC,MATIC-USDC,MINA-USDC,MKR-USDC,MOF-USDC,MOVEZ-USDC,MXC-USDC,NEAR-USDC,NFT-USDC,NMR-USDC,NYM-USDC,OMG-USDC,OP-USDC,PEOPLE-USDC,PERP-USDC,RACA-USDC,RSR-USDC,SAITAMA-USDC,SAND-USDC,SHIB-USDC,SLP-USDC,SNX-USDC,SOC-USDC,SOL-USDC,SOS-USDC,SRM-USDC,STARL-USDC,STC-USDC,STORJ-USDC,STX-USDC,SUSHI-USDC,SWFTC-USDC,THETA-USDC,TON-USDC,TORN-USDC,TRB-USDC,TRX-USDC,UNI-USDC,USDP-USDC,USTC-USDC,VRA-USDC,WAVES-USDC,XCH-USDC,XEM-USDC,XLM-USDC,XMR-USDC,XNO-USDC,XTZ-USDC,YFI-USDC,YFII-USDC,YGG-USDC,ZEC-USDC,ZIL-USDC,BTC-DAI,ETH-DAI,BTC-USDK,ETH-USDK,USDT-USDK,OKB-BTC,OKT-BTC,LTC-BTC,DOT-BTC,DOGE-BTC,FIL-BTC,XRP-BTC,AAVE-BTC,ADA-BTC,ALGO-BTC,ALPHA-BTC,ANT-BTC,ATOM-BTC,AVAX-BTC,BADGER-BTC,BAT-BTC,BCD-BTC,BCH-BTC,BNT-BTC,BSV-BTC,BTG-BTC,BTM-BTC,CELO-BTC,CELT-BTC,CHZ-BTC,COMP-BTC,CQT-BTC,CRO-BTC,CRV-BTC,CTC-BTC,CVC-BTC,DASH-BTC,DCR-BTC,DGB-BTC,EGLD-BTC,ELF-BTC,ENJ-BTC,EOS-BTC,ETC-BTC,FLOW-BTC,GAS-BTC,GRT-BTC,GTO-BTC,HBAR-BTC,HC-BTC,ICP-BTC,ICX-BTC,INT-BTC,IOST-BTC,IOTA-BTC,KLAY-BTC,KNC-BTC,KSM-BTC,LINK-BTC,LRC-BTC,LSK-BTC,MANA-BTC,MITH-BTC,MKR-BTC,NAS-BTC,NEAR-BTC,NEO-BTC,NULS-BTC,OMG-BTC,ONT-BTC,PST-BTC,QTUM-BTC,REN-BTC,RSR-BTC,RVN-BTC,SC-BTC,SNT-BTC,SOL-BTC,SRM-BTC,STX-BTC,SWFTC-BTC,THETA-BTC,TRUE-BTC,TRX-BTC,UNI-BTC,VSYS-BTC,WAVES-BTC,WBTC-BTC,WXT-BTC,XCH-BTC,XEM-BTC,XLM-BTC,XMR-BTC,XTZ-BTC,YFI-BTC,YOU-BTC,ZEC-BTC,ZEN-BTC,ZIL-BTC,ZRX-BTC,OKB-ETH,OKT-ETH,LTC-ETH,DOT-ETH,DOGE-ETH,FIL-ETH,XRP-ETH,AAVE-ETH,ADA-ETH,API3-ETH,ATOM-ETH,AVAX-ETH,BETH-ETH,CRV-ETH,DASH-ETH,EOS-ETH,ETC-ETH,FLOW-ETH,GAS-ETH,GHST-ETH,HEGIC-ETH,INT-ETH,IOST-ETH,KSM-ETH,LINK-ETH,MANA-ETH,MKR-ETH,NEAR-ETH,NEO-ETH,NULS-ETH,OM-ETH,QTUM-ETH,SNX-ETH,SOL-ETH,SUSHI-ETH,SWFTC-ETH,TRX-ETH,UNI-ETH,WBTC-ETH,XLM-ETH,XMR-ETH,YFI-ETH,ZEC-ETH,LTC-OKB,XRP-OKB,ETC-OKB,OKDOT1-DOT,OKDOT2-DOT,BTC-EURT,ETH-EURT" - } - } - }, - "api": { - "authenticatedSupport": false, - "authenticatedWebsocketApiSupport": false, - "credentials": { - "key": "", - "secret": "", - "clientID": "" - }, - "credentialsValidator": { - "requiresKey": true, - "requiresSecret": true, - "requiresClientID": true - }, - "urlEndpoints": { - "RestSpotURL": "https://www.okx.com/api/v5/", - "WebsocketSpotURL": "wss://ws.okx.com:8443/ws/v5/public" - } - }, - "features": { - "supports": { - "restAPI": true, - "restCapabilities": { - "autoPairUpdates": true - }, - "websocketAPI": true, - "websocketCapabilities": {} - }, - "enabled": { - "autoPairUpdates": true, - "websocketAPI": true, - "saveTradeData": false, - "tradeFeed": false, - "fillsFeed": false - } - }, - "bankAccounts": [ - { - "enabled": false, - "bankName": "", - "bankAddress": "", - "bankPostalCode": "", - "bankPostalCity": "", - "bankCountry": "", - "accountName": "", - "accountNumber": "", - "swiftCode": "", - "iban": "", - "supportedCurrencies": "" - } + "bankAccounts": [ + { + "enabled": false, + "bankName": "", + "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ] + }, + { + "name": "Okx", + "enabled": true, + "verbose": false, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, + "baseCurrencies": "USD", + "currencyPairs": { + "bypassConfigFormatUpgrades": false, + "requestFormat": { + "uppercase": true, + "delimiter": "-" + }, + "configFormat": { + "uppercase": true, + "delimiter": "-" + }, + "useGlobalFormat": true, + "assetTypes": [ + "futures", + "margin", + "option", + "perpetualswap", + "spot" ], - "orderbook": { - "verificationBypass": false, - "websocketBufferLimit": 5, - "websocketBufferEnabled": false, - "publishPeriod": 10000000000 + "pairs": { + "futures": { + "assetEnabled": true, + "enabled": "BTC-USD-221007,BTC-USD-221014", + "available": "BTC-USD-221230,BTC-USD-230331,ETH-USD-221007,ETH-USD-221014,ETH-USD-221230,ETH-USD-230331,LTC-USD-221007,LTC-USD-221014,LTC-USD-221230,LTC-USD-230331,DOT-USD-221007,DOT-USD-221014,DOT-USD-221230,DOT-USD-230331,FIL-USD-221007,FIL-USD-221014,FIL-USD-221230,FIL-USD-230331,ADA-USD-221007,ADA-USD-221014,ADA-USD-221230,ADA-USD-230331,AVAX-USD-221007,AVAX-USD-221014,AVAX-USD-221230,BTC-USD-221007,BTC-USD-221014,BTC-USD-221230,BTC-USD-230331,ETH-USD-221007,ETH-USD-221014,ETH-USD-221230,ETH-USD-230331,LTC-USD-221007,LTC-USD-221014,LTC-USD-221230,LTC-USD-230331,DOT-USD-221007,DOT-USD-221014,DOT-USD-221230,DOT-USD-230331,FIL-USD-221007,FIL-USD-221014,FIL-USD-221230,FIL-USD-230331,ADA-USD-221007,ADA-USD-221014,ADA-USD-221230,ADA-USD-230331,AVAX-USD-221007,AVAX-USD-221014,AVAX-USD-221230,AVAX-USD-230331,BCH-USD-221007,BCH-USD-221014,BCH-USD-221230,BCH-USD-230331,EOS-USD-221007,EOS-USD-221014,EOS-USD-221230,EOS-USD-230331,ETC-USD-221007,ETC-USD-221014,ETC-USD-221230,ETC-USD-230331,LINK-USD-221007,LINK-USD-221014,LINK-USD-221230,LINK-USD-230331,SOL-USD-221007,SOL-USD-221014,SOL-USD-221230,SOL-USD-230331,TRX-USD-221007,TRX-USD-221014,TRX-USD-221230,TRX-USD-230331,XRP-USD-221007,XRP-USD-221014,XRP-USD-221230,XRP-USD-230331,BTC-USDT-221007,BTC-USDT-221014,BTC-USDT-221230,BTC-USDT-230331,ETH-USDT-221007,ETH-USDT-221014,ETH-USDT-221230,ETH-USDT-230331,LTC-USDT-221007,LTC-USDT-221014,LTC-USDT-221230,LTC-USDT-230331,DOT-USDT-221007,DOT-USDT-221014,DOT-USDT-221230,DOT-USDT-230331,FIL-USDT-221007,FIL-USDT-221014,FIL-USDT-221230,FIL-USDT-230331,ADA-USDT-221007,ADA-USDT-221014,ADA-USDT-221230,ADA-USDT-230331,BCH-USDT-221007,BCH-USDT-221014,BCH-USDT-221230,BCH-USDT-230331,EOS-USDT-221007,EOS-USDT-221014,EOS-USDT-221230,EOS-USDT-230331,ETC-USDT-221007,ETC-USDT-221014,ETC-USDT-221230,ETC-USDT-230331,LINK-USDT-221007,LINK-USDT-221014,LINK-USDT-221230,LINK-USDT-230331,TRX-USDT-221007,TRX-USDT-221014,TRX-USDT-221230,TRX-USDT-230331,XRP-USDT-221007,XRP-USDT-221014,XRP-USDT-221230,XRP-USDT-230331" + }, + "margin": { + "assetEnabled": true, + "enabled": "BTC-USDT,ETH-USDT,OKB-USDT", + "available": "LTC-USDT,DOT-USDT,DOGE-USDT,LUNC-USDT,ETHW-USDT,LUNA-USDT,FIL-USDT,XRP-USDT,1INCH-USDT,AAVE-USDT,ADA-USDT,AGLD-USDT,AKITA-USDT,ALGO-USDT,ALPHA-USDT,BTC-USDT,ETH-USDT,OKB-USDT,LTC-USDT,DOT-USDT,DOGE-USDT,LUNC-USDT,ETHW-USDT,LUNA-USDT,FIL-USDT,XRP-USDT,1INCH-USDT,AAVE-USDT,ADA-USDT,AGLD-USDT,AKITA-USDT,ALGO-USDT,ALPHA-USDT,ANT-USDT,APE-USDT,API3-USDT,ASTR-USDT,ATOM-USDT,AVAX-USDT,AXS-USDT,BABYDOGE-USDT,BADGER-USDT,BAL-USDT,BAND-USDT,BAT-USDT,BCH-USDT,BICO-USDT,BNT-USDT,BSV-USDT,BTM-USDT,BTT-USDT,BZZ-USDT,CELO-USDT,CEL-USDT,CELR-USDT,CFX-USDT,CHZ-USDT,CLV-USDT,COMP-USDT,CONV-USDT,CQT-USDT,CRO-USDT,CRV-USDT,CSPR-USDT,CVC-USDT,DASH-USDT,DOME-USDT,DORA-USDT,DYDX-USDT,EFI-USDT,EGLD-USDT,ELF-USDT,ENJ-USDT,ENS-USDT,EOS-USDT,ETC-USDT,FITFI-USDT,FLM-USDT,FLOW-USDT,FTM-USDT,GALA-USDT,GLMR-USDT,GMT-USDT,GODS-USDT,GRT-USDT,HBAR-USDT,HC-USDT,ICP-USDT,IMX-USDT,IOST-USDT,IOTA-USDT,JST-USDT,KAR-USDT,KISHU-USDT,KNC-USDT,KSM-USDT,LAMB-USDT,LAT-USDT,LINK-USDT,LON-USDT,LOOKS-USDT,LPT-USDT,LRC-USDT,MANA-USDT,MASK-USDT,MATIC-USDT,MINA-USDT,MKR-USDT,NAS-USDT,NEAR-USDT,NEO-USDT,NFT-USDT,NYM-USDT,OMG-USDT,ONT-USDT,OP-USDT,PEOPLE-USDT,PERP-USDT,QTUM-USDT,REN-USDT,RSR-USDT,RSS3-USDT,RVN-USDT,SAND-USDT,SC-USDT,SHIB-USDT,SKL-USDT,SLP-USDT,SNT-USDT,SNX-USDT,SOL-USDT,SOS-USDT,SRM-USDT,STARL-USDT,STORJ-USDT,SUSHI-USDT,SWEAT-USDT,SWRV-USDT,THETA-USDT,TORN-USDT,TRB-USDT,TRX-USDT,UMA-USDT,UMEE-USDT,UNI-USDT,USDC-USDT,VSYS-USDT,WAVES-USDT,WNCG-USDT,WNXM-USDT,XCH-USDT,XEM-USDT,XLM-USDT,XMR-USDT,XTZ-USDT,YFI-USDT,YFII-USDT,YGG-USDT,ZEC-USDT,ZEN-USDT,ZIL-USDT,ZRX-USDT,BTC-USDC,ETH-BTC,LTC-USDC,DOT-USDC,DOGE-USDC,LUNC-USDC,LUNA-USDC,XRP-USDC,ADA-USDC,ATOM-USDC,AVAX-USDC,NEAR-USDC,OP-USDC,SOL-USDC,OKB-BTC,LTC-BTC,DOT-BTC,DOGE-BTC,FIL-BTC,XRP-BTC,AAVE-BTC,ADA-BTC,ALGO-BTC,ANT-BTC,ATOM-BTC,AVAX-BTC,BADGER-BTC,BAT-BTC,BCH-BTC,BNT-BTC,BSV-BTC,BTM-BTC,CHZ-BTC,COMP-BTC,CRO-BTC,CRV-BTC,CVC-BTC,DASH-BTC,EGLD-BTC,ELF-BTC,ENJ-BTC,EOS-BTC,ETC-BTC,GRT-BTC,HBAR-BTC,HC-BTC,ICP-BTC,IOST-BTC,IOTA-BTC,KNC-BTC,KSM-BTC,LINK-BTC,LRC-BTC,MANA-BTC,MKR-BTC,NAS-BTC,NEAR-BTC,NEO-BTC,OMG-BTC,ONT-BTC,QTUM-BTC,REN-BTC,RSR-BTC,RVN-BTC,SNT-BTC,SOL-BTC,SRM-BTC,THETA-BTC,TRX-BTC,UNI-BTC,VSYS-BTC,WAVES-BTC,XCH-BTC,XEM-BTC,XLM-BTC,XMR-BTC,XTZ-BTC,ZEC-BTC,ZIL-BTC,ZRX-BTC" + }, + "option": { + "assetEnabled": true, + "enabled": "BTC-USD-220930-28000-P,BTC-USD-220930-30000-C", + "available": "BTC-USD-220930-30000-P,BTC-USD-220930-32000-C,BTC-USD-220930-32000-P,BTC-USD-220930-34000-C,BTC-USD-220930-34000-P,BTC-USD-220930-35000-C,BTC-USD-220930-35000-P,BTC-USD-220930-36000-C,BTC-USD-220930-36000-P,BTC-USD-220930-40000-C,BTC-USD-220930-40000-P,BTC-USD-220930-45000-C,BTC-USD-220930-45000-P,BTC-USD-220930-50000-C,BTC-USD-220930-50000-P,BTC-USD-220930-55000-C,BTC-USD-220930-55000-P,BTC-USD-220930-60000-C,BTC-USD-220930-60000-P,BTC-USD-220930-65000-C,BTC-USD-220930-65000-P,BTC-USD-220930-70000-C,BTC-USD-220930-70000-P,BTC-USD-220930-80000-C,BTC-USD-220930-80000-P,BTC-USD-220930-90000-C,BTC-USD-220930-90000-P,BTC-USD-220930-100000-C,BTC-USD-220930-100000-P,BTC-USD-220930-120000-C,BTC-USD-220930-120000-P,BTC-USD-221007-13000-C,BTC-USD-221007-13000-P,BTC-USD-221007-14000-C,BTC-USD-221007-14000-P,BTC-USD-221007-15000-C,BTC-USD-221007-15000-P,BTC-USD-221007-16000-C,BTC-USD-221007-16000-P,BTC-USD-221007-17000-C,BTC-USD-221007-17000-P,BTC-USD-221007-18000-C,BTC-USD-221007-18000-P,BTC-USD-221007-18500-C,BTC-USD-221007-18500-P,BTC-USD-221007-19000-C,BTC-USD-221007-19000-P,BTC-USD-221007-20000-C,BTC-USD-221007-20000-P,BTC-USD-221007-21000-C,BTC-USD-221007-21000-P,BTC-USD-221007-22000-C,BTC-USD-221007-22000-P,BTC-USD-221007-23000-C,BTC-USD-221007-23000-P,BTC-USD-221007-24000-C,BTC-USD-221007-24000-P,BTC-USD-221007-26000-C,BTC-USD-221007-26000-P,BTC-USD-221007-28000-C,BTC-USD-221007-28000-P,BTC-USD-221007-30000-C,BTC-USD-221007-30000-P,BTC-USD-221014-14000-C,BTC-USD-221014-14000-P,BTC-USD-221014-15000-C,BTC-USD-221014-15000-P,BTC-USD-221014-16000-C,BTC-USD-221014-16000-P,BTC-USD-221014-17000-C,BTC-USD-221014-17000-P,BTC-USD-221014-18000-C,BTC-USD-221014-18000-P,BTC-USD-221014-19000-C,BTC-USD-221014-19000-P,BTC-USD-221014-20000-C,BTC-USD-221014-20000-P,BTC-USD-221014-21000-C,BTC-USD-221014-21000-P,BTC-USD-221014-22000-C,BTC-USD-221014-22000-P,BTC-USD-221014-23000-C,BTC-USD-221014-23000-P,BTC-USD-221014-24000-C,BTC-USD-221014-24000-P,BTC-USD-221014-26000-C,BTC-USD-221014-26000-P,BTC-USD-221014-28000-C,BTC-USD-221014-28000-P,BTC-USD-221014-30000-C,BTC-USD-221014-30000-P,BTC-USD-221028-10000-C,BTC-USD-221028-10000-P,BTC-USD-221028-12000-C,BTC-USD-221028-12000-P,BTC-USD-221028-14000-C,BTC-USD-221028-14000-P,BTC-USD-221028-16000-C,BTC-USD-221028-16000-P,BTC-USD-221028-17000-C,BTC-USD-221028-17000-P,BTC-USD-221028-18000-C,BTC-USD-221028-18000-P,BTC-USD-221028-19000-C,BTC-USD-221028-19000-P,BTC-USD-221028-20000-C,BTC-USD-221028-20000-P,BTC-USD-221028-21000-C,BTC-USD-221028-21000-P,BTC-USD-221028-22000-C,BTC-USD-221028-22000-P,BTC-USD-221028-24000-C,BTC-USD-221028-24000-P,BTC-USD-221028-26000-C,BTC-USD-221028-26000-P,BTC-USD-221028-28000-C,BTC-USD-221028-28000-P,BTC-USD-221028-30000-C,BTC-USD-221028-30000-P,BTC-USD-221028-32000-C,BTC-USD-221028-32000-P,BTC-USD-221028-35000-C,BTC-USD-220927-17000-C,BTC-USD-220927-17000-P,BTC-USD-220927-17500-C,BTC-USD-220927-17500-P,BTC-USD-220927-18000-C,BTC-USD-220927-18000-P,BTC-USD-220927-18200-C,BTC-USD-220927-18200-P,BTC-USD-220927-18400-C,BTC-USD-220927-18400-P,BTC-USD-220927-18600-C,BTC-USD-220927-18600-P,BTC-USD-220927-18800-C,BTC-USD-220927-18800-P,BTC-USD-220927-19000-C,BTC-USD-220927-19000-P,BTC-USD-220927-19200-C,BTC-USD-220927-19200-P,BTC-USD-220927-19400-C,BTC-USD-220927-19400-P,BTC-USD-220927-19600-C,BTC-USD-220927-19600-P,BTC-USD-220927-19800-C,BTC-USD-220927-19800-P,BTC-USD-220927-20000-C,BTC-USD-220927-20000-P,BTC-USD-220927-20500-C,BTC-USD-220927-20500-P,BTC-USD-220927-21000-C,BTC-USD-220927-21000-P,BTC-USD-220927-21500-C,BTC-USD-220927-21500-P,BTC-USD-220928-16500-C,BTC-USD-220928-16500-P,BTC-USD-220928-17000-C,BTC-USD-220928-17000-P,BTC-USD-220928-17500-C,BTC-USD-220928-17500-P,BTC-USD-220928-18000-C,BTC-USD-220928-18000-P,BTC-USD-220928-18200-C,BTC-USD-220928-18200-P,BTC-USD-220928-18400-C,BTC-USD-220928-18400-P,BTC-USD-220928-18600-C,BTC-USD-220928-18600-P,BTC-USD-220928-18800-C,BTC-USD-220928-18800-P,BTC-USD-220928-19000-C,BTC-USD-220928-19000-P,BTC-USD-220928-19200-C,BTC-USD-220928-19200-P,BTC-USD-220928-19400-C,BTC-USD-220928-19400-P,BTC-USD-220928-19600-C,BTC-USD-220928-19600-P,BTC-USD-220928-20000-C,BTC-USD-220928-20000-P,BTC-USD-220928-20500-C,BTC-USD-220928-20500-P,BTC-USD-220928-21000-C,BTC-USD-220928-21000-P,BTC-USD-220928-21500-C,BTC-USD-220928-21500-P,BTC-USD-220930-5000-C,BTC-USD-220930-5000-P,BTC-USD-220930-10000-C,BTC-USD-220930-10000-P,BTC-USD-220930-12000-C,BTC-USD-220930-12000-P,BTC-USD-220930-15000-C,BTC-USD-220930-15000-P,BTC-USD-220930-16000-C,BTC-USD-220930-16000-P,BTC-USD-220930-17000-C,BTC-USD-220930-17000-P,BTC-USD-220930-17500-C,BTC-USD-220930-17500-P,BTC-USD-220930-18000-C,BTC-USD-220930-18000-P,BTC-USD-220930-18500-C,BTC-USD-220930-18500-P,BTC-USD-220930-19000-C,BTC-USD-220930-19000-P,BTC-USD-220930-19500-C,BTC-USD-220930-19500-P,BTC-USD-220930-20000-C,BTC-USD-220930-20000-P,BTC-USD-220930-20500-C,BTC-USD-220930-20500-P,BTC-USD-220930-21000-C,BTC-USD-220930-21000-P,BTC-USD-220930-22000-C,BTC-USD-220930-22000-P,BTC-USD-220930-23000-C,BTC-USD-220930-23000-P,BTC-USD-220930-24000-C,BTC-USD-220930-24000-P,BTC-USD-220930-25000-C,BTC-USD-220930-25000-P,BTC-USD-220930-26000-C,BTC-USD-220930-26000-P,BTC-USD-220930-28000-C,BTC-USD-220930-28000-P,BTC-USD-220930-30000-C,BTC-USD-220930-30000-P,BTC-USD-220930-32000-C,BTC-USD-220930-32000-P,BTC-USD-220930-34000-C,BTC-USD-220930-34000-P,BTC-USD-220930-35000-C,BTC-USD-220930-35000-P,BTC-USD-220930-36000-C,BTC-USD-220930-36000-P,BTC-USD-220930-40000-C,BTC-USD-220930-40000-P,BTC-USD-220930-45000-C,BTC-USD-220930-45000-P,BTC-USD-220930-50000-C,BTC-USD-220930-50000-P,BTC-USD-220930-55000-C,BTC-USD-220930-55000-P,BTC-USD-220930-60000-C,BTC-USD-220930-60000-P,BTC-USD-220930-65000-C,BTC-USD-220930-65000-P,BTC-USD-220930-70000-C,BTC-USD-220930-70000-P,BTC-USD-220930-80000-C,BTC-USD-220930-80000-P,BTC-USD-220930-90000-C,BTC-USD-220930-90000-P,BTC-USD-220930-100000-C,BTC-USD-220930-100000-P,BTC-USD-220930-120000-C,BTC-USD-220930-120000-P,BTC-USD-221007-13000-C,BTC-USD-221007-13000-P,BTC-USD-221007-14000-C,BTC-USD-221007-14000-P,BTC-USD-221007-15000-C,BTC-USD-221007-15000-P,BTC-USD-221007-16000-C,BTC-USD-221007-16000-P,BTC-USD-221007-17000-C,BTC-USD-221007-17000-P,BTC-USD-221007-18000-C,BTC-USD-221007-18000-P,BTC-USD-221007-18500-C,BTC-USD-221007-18500-P,BTC-USD-221007-19000-C,BTC-USD-221007-19000-P,BTC-USD-221007-20000-C,BTC-USD-221007-20000-P,BTC-USD-221007-21000-C,BTC-USD-221007-21000-P,BTC-USD-221007-22000-C,BTC-USD-221007-22000-P,BTC-USD-221007-23000-C,BTC-USD-221007-23000-P,BTC-USD-221007-24000-C,BTC-USD-221007-24000-P,BTC-USD-221007-26000-C,BTC-USD-221007-26000-P,BTC-USD-221007-28000-C,BTC-USD-221007-28000-P,BTC-USD-221007-30000-C,BTC-USD-221007-30000-P,BTC-USD-221014-14000-C,BTC-USD-221014-14000-P,BTC-USD-221014-15000-C,BTC-USD-221014-15000-P,BTC-USD-221014-16000-C,BTC-USD-221014-16000-P,BTC-USD-221014-17000-C,BTC-USD-221014-17000-P,BTC-USD-221014-18000-C,BTC-USD-221014-18000-P,BTC-USD-221014-19000-C,BTC-USD-221014-19000-P,BTC-USD-221014-20000-C,BTC-USD-221014-20000-P,BTC-USD-221014-21000-C,BTC-USD-221014-21000-P,BTC-USD-221014-22000-C,BTC-USD-221014-22000-P,BTC-USD-221014-23000-C,BTC-USD-221014-23000-P,BTC-USD-221014-24000-C,BTC-USD-221014-24000-P,BTC-USD-221014-26000-C,BTC-USD-221014-26000-P,BTC-USD-221014-28000-C,BTC-USD-221014-28000-P,BTC-USD-221014-30000-C,BTC-USD-221014-30000-P,BTC-USD-221028-10000-C,BTC-USD-221028-10000-P,BTC-USD-221028-12000-C,BTC-USD-221028-12000-P,BTC-USD-221028-14000-C,BTC-USD-221028-14000-P,BTC-USD-221028-16000-C,BTC-USD-221028-16000-P,BTC-USD-221028-17000-C,BTC-USD-221028-17000-P,BTC-USD-221028-18000-C,BTC-USD-221028-18000-P,BTC-USD-221028-19000-C,BTC-USD-221028-19000-P,BTC-USD-221028-20000-C,BTC-USD-221028-20000-P,BTC-USD-221028-21000-C,BTC-USD-221028-21000-P,BTC-USD-221028-22000-C,BTC-USD-221028-22000-P,BTC-USD-221028-24000-C,BTC-USD-221028-24000-P,BTC-USD-221028-26000-C,BTC-USD-221028-26000-P,BTC-USD-221028-28000-C,BTC-USD-221028-28000-P,BTC-USD-221028-30000-C,BTC-USD-221028-30000-P,BTC-USD-221028-32000-C,BTC-USD-221028-32000-P,BTC-USD-221028-35000-C,BTC-USD-221028-35000-P,BTC-USD-221028-40000-C,BTC-USD-221028-40000-P,BTC-USD-221028-50000-C,BTC-USD-221028-50000-P,BTC-USD-221028-60000-C,BTC-USD-221028-60000-P,BTC-USD-221028-70000-C,BTC-USD-221028-70000-P,BTC-USD-221125-5000-C,BTC-USD-221125-5000-P,BTC-USD-221125-10000-C,BTC-USD-221125-10000-P,BTC-USD-221125-12000-C,BTC-USD-221125-12000-P,BTC-USD-221125-15000-C,BTC-USD-221125-15000-P,BTC-USD-221125-16000-C,BTC-USD-221125-16000-P,BTC-USD-221125-17000-C,BTC-USD-221125-17000-P,BTC-USD-221125-18000-C,BTC-USD-221125-18000-P,BTC-USD-221125-20000-C,BTC-USD-221125-20000-P,BTC-USD-221125-22000-C,BTC-USD-221125-22000-P,BTC-USD-221125-24000-C,BTC-USD-221125-24000-P,BTC-USD-221125-26000-C,BTC-USD-221125-26000-P,BTC-USD-221125-28000-C,BTC-USD-221125-28000-P,BTC-USD-221125-30000-C,BTC-USD-221125-30000-P,BTC-USD-221125-32000-C,BTC-USD-221125-32000-P,BTC-USD-221125-35000-C,BTC-USD-221125-35000-P,BTC-USD-221125-40000-C,BTC-USD-221125-40000-P,BTC-USD-221125-50000-C,BTC-USD-221125-50000-P,BTC-USD-221125-60000-C,BTC-USD-221125-60000-P,BTC-USD-221125-70000-C,BTC-USD-221125-70000-P,BTC-USD-221230-5000-C,BTC-USD-221230-5000-P,BTC-USD-221230-10000-C,BTC-USD-221230-10000-P,BTC-USD-221230-12000-C,BTC-USD-221230-12000-P,BTC-USD-221230-13000-C,BTC-USD-221230-13000-P,BTC-USD-221230-15000-C,BTC-USD-221230-15000-P,BTC-USD-221230-16000-C,BTC-USD-221230-16000-P,BTC-USD-221230-17000-C,BTC-USD-221230-17000-P,BTC-USD-221230-18000-C,BTC-USD-221230-18000-P,BTC-USD-221230-19000-C,BTC-USD-221230-19000-P,BTC-USD-221230-20000-C,BTC-USD-221230-20000-P,BTC-USD-221230-21000-C,BTC-USD-221230-21000-P,BTC-USD-221230-22000-C,BTC-USD-221230-22000-P,BTC-USD-221230-23000-C,BTC-USD-221230-23000-P,BTC-USD-221230-24000-C,BTC-USD-221230-24000-P,BTC-USD-221230-25000-C,BTC-USD-221230-25000-P,BTC-USD-221230-26000-C,BTC-USD-221230-26000-P,BTC-USD-221230-28000-C,BTC-USD-221230-28000-P,BTC-USD-221230-30000-C,BTC-USD-221230-30000-P,BTC-USD-221230-32000-C,BTC-USD-221230-32000-P,BTC-USD-221230-35000-C,BTC-USD-221230-35000-P" + }, + "perpetualswap": { + "assetEnabled": true, + "enabled": "BTC-USD-SWAP,ETH-USD-SWAP", + "available": "LTC-USD-SWAP,DOT-USD-SWAP,DOGE-USD-SWAP,MASK-USDT-SWAP,MATIC-USDT-SWAP,MINA-USDT-SWAP,MKR-USDT-SWAP,NEAR-USDT-SWAP,NEO-USDT-SWAP,NFT-USDT-SWAP,NYM-USDT-SWAP,OMG-USDT-SWAP,ONT-USDT-SWAP,OP-USDT-SWAP,PEOPLE-USDT-SWAP,BTC-USD-SWAP,ETH-USD-SWAP,LTC-USD-SWAP,DOT-USD-SWAP,DOGE-USD-SWAP,FIL-USD-SWAP,XRP-USD-SWAP,1INCH-USD-SWAP,ADA-USD-SWAP,ALGO-USD-SWAP,ATOM-USD-SWAP,AVAX-USD-SWAP,BCH-USD-SWAP,BSV-USD-SWAP,CRV-USD-SWAP,DASH-USD-SWAP,EOS-USD-SWAP,ETC-USD-SWAP,GRT-USD-SWAP,IOST-USD-SWAP,IOTA-USD-SWAP,KNC-USD-SWAP,KSM-USD-SWAP,LINK-USD-SWAP,MANA-USD-SWAP,NEO-USD-SWAP,ONT-USD-SWAP,QTUM-USD-SWAP,SAND-USD-SWAP,SOL-USD-SWAP,SUSHI-USD-SWAP,THETA-USD-SWAP,TRX-USD-SWAP,UNI-USD-SWAP,XLM-USD-SWAP,XMR-USD-SWAP,XTZ-USD-SWAP,YFI-USD-SWAP,YFII-USD-SWAP,ZEC-USD-SWAP,BTC-USDT-SWAP,ETH-USDT-SWAP,LTC-USDT-SWAP,DOT-USDT-SWAP,DOGE-USDT-SWAP,LUNC-USDT-SWAP,ETHW-USDT-SWAP,LUNA-USDT-SWAP,FIL-USDT-SWAP,XRP-USDT-SWAP,1INCH-USDT-SWAP,AAVE-USDT-SWAP,ADA-USDT-SWAP,AGLD-USDT-SWAP,ALGO-USDT-SWAP,ALPHA-USDT-SWAP,ANT-USDT-SWAP,APE-USDT-SWAP,API3-USDT-SWAP,ASTR-USDT-SWAP,ATOM-USDT-SWAP,AVAX-USDT-SWAP,AXS-USDT-SWAP,BABYDOGE-USDT-SWAP,BADGER-USDT-SWAP,BAL-USDT-SWAP,BAND-USDT-SWAP,BAT-USDT-SWAP,BCH-USDT-SWAP,BICO-USDT-SWAP,BNT-USDT-SWAP,BSV-USDT-SWAP,BTT-USDT-SWAP,CELO-USDT-SWAP,CEL-USDT-SWAP,CFX-USDT-SWAP,CHZ-USDT-SWAP,COMP-USDT-SWAP,CRO-USDT-SWAP,CRV-USDT-SWAP,CSPR-USDT-SWAP,CVC-USDT-SWAP,DASH-USDT-SWAP,DOME-USDT-SWAP,DORA-USDT-SWAP,DYDX-USDT-SWAP,EGLD-USDT-SWAP,ELON-USDT-SWAP,ENJ-USDT-SWAP,ENS-USDT-SWAP,EOS-USDT-SWAP,ETC-USDT-SWAP,FITFI-USDT-SWAP,FLM-USDT-SWAP,FTM-USDT-SWAP,GALA-USDT-SWAP,GMT-USDT-SWAP,GODS-USDT-SWAP,GRT-USDT-SWAP,ICP-USDT-SWAP,IMX-USDT-SWAP,IOST-USDT-SWAP,IOTA-USDT-SWAP,JST-USDT-SWAP,KISHU-USDT-SWAP,KNC-USDT-SWAP,KSM-USDT-SWAP,LINK-USDT-SWAP,LOOKS-USDT-SWAP,LPT-USDT-SWAP,LRC-USDT-SWAP,MANA-USDT-SWAP,MASK-USDT-SWAP,MATIC-USDT-SWAP,MINA-USDT-SWAP,MKR-USDT-SWAP,NEAR-USDT-SWAP,NEO-USDT-SWAP,NFT-USDT-SWAP,NYM-USDT-SWAP,OMG-USDT-SWAP,ONT-USDT-SWAP,OP-USDT-SWAP,PEOPLE-USDT-SWAP,PERP-USDT-SWAP,QTUM-USDT-SWAP,REN-USDT-SWAP,RSR-USDT-SWAP,RVN-USDT-SWAP,SAND-USDT-SWAP,SC-USDT-SWAP,SHIB-USDT-SWAP,SLP-USDT-SWAP,SNX-USDT-SWAP,SOL-USDT-SWAP,SOS-USDT-SWAP,SRM-USDT-SWAP,STARL-USDT-SWAP,STORJ-USDT-SWAP,SUSHI-USDT-SWAP,SWEAT-USDT-SWAP,THETA-USDT-SWAP,TRB-USDT-SWAP,TRX-USDT-SWAP,UMA-USDT-SWAP,UMEE-USDT-SWAP,UNI-USDT-SWAP,WAVES-USDT-SWAP,WNXM-USDT-SWAP,XCH-USDT-SWAP,XEM-USDT-SWAP,XLM-USDT-SWAP,XMR-USDT-SWAP,XTZ-USDT-SWAP,YFI-USDT-SWAP,YFII-USDT-SWAP,YGG-USDT-SWAP,ZEC-USDT-SWAP,ZEN-USDT-SWAP,ZIL-USDT-SWAP,ZRX-USDT-SWAP" + }, + "spot": { + "assetEnabled": true, + "enabled": "BTC-USDT,ETH-USDT,OKB-USDT", + "available": "OKT-USDT,LTC-USDT,DOT-USDT,DOGE-USDT,LUNC-USDT,ETHW-USDT,LUNA-USDT,FIL-USDT,XRP-USDT,CITY-USDT,MENGO-USDT,ARG-USDT,POR-USDT,1INCH-USDT,AAVE-USDT,ABT-USDT,ACA-USDT,ADA-USDT,AERGO-USDT,AGLD-USDT,XMR-ETH,YFI-ETH,ZEC-ETH,LTC-OKB,XRP-OKB,ETC-OKB,BTC-USDT,ETH-USDT,OKB-USDT,OKT-USDT,LTC-USDT,DOT-USDT,DOGE-USDT,LUNC-USDT,ETHW-USDT,LUNA-USDT,FIL-USDT,XRP-USDT,CITY-USDT,MENGO-USDT,ARG-USDT,POR-USDT,1INCH-USDT,AAVE-USDT,ABT-USDT,ACA-USDT,ADA-USDT,AERGO-USDT,AGLD-USDT,AKITA-USDT,ALCX-USDT,ALGO-USDT,ALPHA-USDT,ANC-USDT,ANT-USDT,ANW-USDT,APE-USDT,APIX-USDT,API3-USDT,APM-USDT,AR-USDT,ARK-USDT,AST-USDT,ASTR-USDT,ATOM-USDT,AUCTION-USDT,AVAX-USDT,AXS-USDT,AZY-USDT,BABYDOGE-USDT,BADGER-USDT,BAL-USDT,BAND-USDT,BAT-USDT,BCD-USDT,BCH-USDT,BETH-USDT,BHP-USDT,BICO-USDT,BLOK-USDT,BNT-USDT,BORING-USDT,BORA-USDT,BRWL-USDT,BSV-USDT,BTG-USDT,BTM-USDT,BTT-USDT,BZZ-USDT,CELO-USDT,CEL-USDT,CELR-USDT,CELT-USDT,CFG-USDT,CFX-USDT,CGS-USDT,CHAT-USDT,CHE-USDT,CHZ-USDT,CLV-USDT,CMT-USDT,CNTM-USDT,COMP-USDT,CONV-USDT,COVER-USDT,CQT-USDT,CRO-USDT,CRV-USDT,CSPR-USDT,CTC-USDT,CTXC-USDT,CVC-USDT,CVP-USDT,CVT-USDT,CVX-USDT,DAI-USDT,DAO-USDT,DASH-USDT,DCR-USDT,DEP-USDT,DEVT-USDT,DGB-USDT,DHT-USDT,DIA-USDT,DMD-USDT,DNA-USDT,DOME-USDT,DORA-USDT,DOSE-USDT,DYDX-USDT,EC-USDT,EDEN-USDT,EFI-USDT,EGLD-USDT,EGT-USDT,ELF-USDT,ELON-USDT,ELT-USDT,EM-USDT,ENJ-USDT,ENS-USDT,EOS-USDT,ERN-USDT,ETC-USDT,EURT-USDT,FAIR-USDT,FAME-USDT,FITFI-USDT,FLM-USDT,FLOW-USDT,FODL-USDT,FORTH-USDT,FRONT-USDT,FSN-USDT,FTM-USDT,GALA-USDT,GALFT-USDT,GARI-USDT,GAS-USDT,GF-USDT,GHST-USDT,GLM-USDT,GLMR-USDT,GM-USDT,GMT-USDT,GODS-USDT,GOG-USDT,GRT-USDT,GTO-USDT,GUSD-USDT,HBAR-USDT,HC-USDT,HDAO-USDT,HEGIC-USDT,HYC-USDT,ICP-USDT,ICX-USDT,ILV-USDT,IMX-USDT,INT-USDT,INX-USDT,IOST-USDT,IOTA-USDT,IQ-USDT,JFI-USDT,JOE-USDT,JST-USDT,KAN-USDT,KAR-USDT,KCASH-USDT,KDA-USDT,KINE-USDT,KISHU-USDT,KLAY-USDT,KNC-USDT,KOL-USDT,KONO-USDT,KP3R-USDT,KSM-USDT,LAMB-USDT,LAT-USDT,LBA-USDT,LDN-USDT,LDO-USDT,LEASH-USDT,LEO-USDT,LET-USDT,LINK-USDT,LING-USDT,LITH-USDT,LON-USDT,LOON-USDT,LOOKS-USDT,LPT-USDT,LRC-USDT,LSK-USDT,MAGIC-USDT,MANA-USDT,MASK-USDT,MATIC-USDT,MCO-USDT,MDA-USDT,MDT-USDT,MEME-USDT,METIS-USDT,MILO-USDT,MINA-USDT,MIR-USDT,MITH-USDT,MKR-USDT,MLN-USDT,MOF-USDT,MON-USDT,MOVR-USDT,MOVEZ-USDT,MXC-USDT,MXT-USDT,NAS-USDT,NEAR-USDT,NEO-USDT,NFT-USDT,NMR-USDT,NULS-USDT,NYM-USDT,OM-USDT,OMG-USDT,OMI-USDT,ONE-USDT,ONT-USDT,OP-USDT,ORBS-USDT,ORB-USDT,ORS-USDT,OXT-USDT,PAY-USDT,PCI-USDT,PEOPLE-USDT,PERP-USDT,PHA-USDT,PICKLE-USDT,PIT-USDT,PLG-USDT,PNK-USDT,POLS-USDT,POLYDOGE-USDT,PPT-USDT,PRQ-USDT,PST-USDT,PSTAKE-USDT,QOM-USDT,QTUM-USDT,RACA-USDT,RAY-USDT,REN-USDT,REP-USDT,REVV-USDT,RFUEL-USDT,RIO-USDT,RNT-USDT,ROAD-USDT,RON-USDT,RSR-USDT,RSS3-USDT,RVN-USDT,SAITAMA-USDT,SAMO-USDT,SAND-USDT,SC-USDT,SD-USDT,SFG-USDT,SHIB-USDT,SIS-USDT,SKEB-USDT,SKL-USDT,SLP-USDT,SNT-USDT,SNX-USDT,SOC-USDT,SOL-USDT,SOS-USDT,SPELL-USDT,SRM-USDT,STARL-USDT,STC-USDT,STORJ-USDT,STRK-USDT,STX-USDT,SUN-USDT,SUSHI-USDT,SWEAT-USDT,SWFTC-USDT,SWRV-USDT,T-USDT,TAI-USDT,TAKI-USDT,TCT-USDT,THETA-USDT,THG-USDT,TON-USDT,TOPC-USDT,TORN-USDT,TOWN-USDT,TRADE-USDT,TRA-USDT,TRB-USDT,TRUE-USDT,TRX-USDT,TUP-USDT,TUSD-USDT,UMA-USDT,UMEE-USDT,UNI-USDT,USDC-USDT,USDP-USDT,USTC-USDT,UTK-USDT,VALUE-USDT,VELO-USDT,VRA-USDT,VSYS-USDT,WAVES-USDT,WAXP-USDT,WBTC-USDT,WEMIX-USDT,WGRT-USDT,WING-USDT,WIN-USDT,WNCG-USDT,WNXM-USDT,WOO-USDT,WSB-USDT,WXT-USDT,XAUT-USDT,XCH-USDT,XEC-USDT,XEM-USDT,XETA-USDT,XLM-USDT,XMR-USDT,XNO-USDT,XPR-USDT,XTZ-USDT,YEE-USDT,YFI-USDT,YFII-USDT,YGG-USDT,YOU-USDT,YOYO-USDT,ZBC-USDT,ZEC-USDT,ZEN-USDT,ZIL-USDT,ZKS-USDT,ZRX-USDT,ZYRO-USDT,BTC-USDC,ETH-USDC,ETH-BTC,OKB-USDC,OKT-USDC,LTC-USDC,DOT-USDC,DOGE-USDC,LUNC-USDC,ETHW-USDC,LUNA-USDC,FIL-USDC,XRP-USDC,1INCH-USDC,AAVE-USDC,ADA-USDC,AGLD-USDC,ALGO-USDC,ANC-USDC,ANT-USDC,APE-USDC,API3-USDC,AR-USDC,ASTR-USDC,ATOM-USDC,AVAX-USDC,AXS-USDC,AZY-USDC,BABYDOGE-USDC,BAT-USDC,BCH-USDC,BICO-USDC,BSV-USDC,CEL-USDC,CELO-USDC,CELT-USDC,CHZ-USDC,COMP-USDC,CRO-USDC,CRV-USDC,CSPR-USDC,DASH-USDC,DEP-USDC,DOME-USDC,DYDX-USDC,EGLD-USDC,ELT-USDC,ENS-USDC,EOS-USDC,ETC-USDC,FITFI-USDC,FLM-USDC,FLOW-USDC,FTM-USDC,GALA-USDC,GALFT-USDC,GARI-USDC,GLMR-USDC,GMT-USDC,GODS-USDC,GRT-USDC,HBAR-USDC,ICP-USDC,IMX-USDC,IOST-USDC,JST-USDC,KISHU-USDC,KLAY-USDC,KNC-USDC,KSM-USDC,LINK-USDC,LOOKS-USDC,LRC-USDC,MANA-USDC,MASK-USDC,MATIC-USDC,MINA-USDC,MKR-USDC,MOF-USDC,MOVEZ-USDC,MXC-USDC,NEAR-USDC,NFT-USDC,NMR-USDC,NYM-USDC,OMG-USDC,OP-USDC,PEOPLE-USDC,PERP-USDC,RACA-USDC,RSR-USDC,SAITAMA-USDC,SAND-USDC,SHIB-USDC,SLP-USDC,SNX-USDC,SOC-USDC,SOL-USDC,SOS-USDC,SRM-USDC,STARL-USDC,STC-USDC,STORJ-USDC,STX-USDC,SUSHI-USDC,SWFTC-USDC,THETA-USDC,TON-USDC,TORN-USDC,TRB-USDC,TRX-USDC,UNI-USDC,USDP-USDC,USTC-USDC,VRA-USDC,WAVES-USDC,XCH-USDC,XEM-USDC,XLM-USDC,XMR-USDC,XNO-USDC,XTZ-USDC,YFI-USDC,YFII-USDC,YGG-USDC,ZEC-USDC,ZIL-USDC,BTC-DAI,ETH-DAI,BTC-USDK,ETH-USDK,USDT-USDK,OKB-BTC,OKT-BTC,LTC-BTC,DOT-BTC,DOGE-BTC,FIL-BTC,XRP-BTC,AAVE-BTC,ADA-BTC,ALGO-BTC,ALPHA-BTC,ANT-BTC,ATOM-BTC,AVAX-BTC,BADGER-BTC,BAT-BTC,BCD-BTC,BCH-BTC,BNT-BTC,BSV-BTC,BTG-BTC,BTM-BTC,CELO-BTC,CELT-BTC,CHZ-BTC,COMP-BTC,CQT-BTC,CRO-BTC,CRV-BTC,CTC-BTC,CVC-BTC,DASH-BTC,DCR-BTC,DGB-BTC,EGLD-BTC,ELF-BTC,ENJ-BTC,EOS-BTC,ETC-BTC,FLOW-BTC,GAS-BTC,GRT-BTC,GTO-BTC,HBAR-BTC,HC-BTC,ICP-BTC,ICX-BTC,INT-BTC,IOST-BTC,IOTA-BTC,KLAY-BTC,KNC-BTC,KSM-BTC,LINK-BTC,LRC-BTC,LSK-BTC,MANA-BTC,MITH-BTC,MKR-BTC,NAS-BTC,NEAR-BTC,NEO-BTC,NULS-BTC,OMG-BTC,ONT-BTC,PST-BTC,QTUM-BTC,REN-BTC,RSR-BTC,RVN-BTC,SC-BTC,SNT-BTC,SOL-BTC,SRM-BTC,STX-BTC,SWFTC-BTC,THETA-BTC,TRUE-BTC,TRX-BTC,UNI-BTC,VSYS-BTC,WAVES-BTC,WBTC-BTC,WXT-BTC,XCH-BTC,XEM-BTC,XLM-BTC,XMR-BTC,XTZ-BTC,YFI-BTC,YOU-BTC,ZEC-BTC,ZEN-BTC,ZIL-BTC,ZRX-BTC,OKB-ETH,OKT-ETH,LTC-ETH,DOT-ETH,DOGE-ETH,FIL-ETH,XRP-ETH,AAVE-ETH,ADA-ETH,API3-ETH,ATOM-ETH,AVAX-ETH,BETH-ETH,CRV-ETH,DASH-ETH,EOS-ETH,ETC-ETH,FLOW-ETH,GAS-ETH,GHST-ETH,HEGIC-ETH,INT-ETH,IOST-ETH,KSM-ETH,LINK-ETH,MANA-ETH,MKR-ETH,NEAR-ETH,NEO-ETH,NULS-ETH,OM-ETH,QTUM-ETH,SNX-ETH,SOL-ETH,SUSHI-ETH,SWFTC-ETH,TRX-ETH,UNI-ETH,WBTC-ETH,XLM-ETH,XMR-ETH,YFI-ETH,ZEC-ETH,LTC-OKB,XRP-OKB,ETC-OKB,OKDOT1-DOT,OKDOT2-DOT,BTC-EURT,ETH-EURT" + } } }, - { - "name": "Poloniex", - "enabled": true, - "verbose": false, - "httpTimeout": 15000000000, - "websocketResponseCheckTimeout": 30000000, - "websocketResponseMaxLimit": 7000000000, - "websocketTrafficTimeout": 30000000000, - "websocketOrderbookBufferLimit": 5, - "baseCurrencies": "USD", - "currencyPairs": { - "requestFormat": { - "uppercase": true, - "delimiter": "_" - }, - "configFormat": { - "uppercase": true, - "delimiter": "_" - }, - "useGlobalFormat": true, - "assetTypes": [ - "spot" - ], - "pairs": { - "spot": { - "enabled": "BTC_LTC,BTC_ETH,BTC_DOGE,BTC_DASH,BTC_XRP", - "available": "USDC_GRIN,BTC_BCN,BTC_DGB,BTC_XMR,USDT_STR,BTC_SC,BTC_ZRX,USDC_XMR,BTC_TRX,BTC_STR,BTC_SNT,USDT_QTUM,USDC_BTC,BTC_NMR,BTC_DASH,BTC_NXT,USDT_LTC,BTC_DCR,USDT_ZRX,USDC_ZEC,USDT_REP,USDT_BAT,BTC_MANA,USDC_BCHABC,USDC_STR,BTC_XRP,USDT_ETH,BTC_REP,USDT_EOS,USDC_ATOM,USDT_XRP,BTC_ETH,USDT_LSK,USDT_SC,USDT_MANA,USDC_ETC,USDC_ETH,BTC_BTS,BTC_LTC,BTC_ETC,BTC_OMG,BTC_STORJ,USDC_XRP,USDT_GRIN,BTC_QTUM,BTC_MAID,BTC_XEM,USDT_BTC,USDT_DASH,ETH_REP,BTC_ZEC,BTC_STRAT,USDC_LTC,BTC_FOAM,USDC_TRX,BTC_DOGE,BTC_VIA,BTC_VTC,ETH_ETC,USDT_ETC,ETH_EOS,USDC_BCHSV,USDT_NXT,USDT_XMR,BTC_ARDR,BTC_CVC,ETH_BAT,USDC_DOGE,BTC_XPM,BTC_LOOM,BTC_LPT,USDC_EOS,USDT_DGB,USDT_BCHSV,BTC_OMNI,ETH_ZEC,BTC_EOS,BTC_KNC,BTC_BCHSV,BTC_POLY,USDC_DASH,USDT_GNT,BTC_BCHABC,BTC_GRIN,BTC_ATOM,USDT_ATOM,USDT_BCHABC,BTC_LSK,ETH_ZRX,BTC_GAS,BTC_BAT,BTC_BNT,USDT_TRX,BTC_FCT,USDT_ZEC,BTC_GNT,USDT_DOGE,USDC_USDT" - } - } - }, - "api": { - "authenticatedSupport": false, - "authenticatedWebsocketApiSupport": false, - "endpoints": { - "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" - }, - "credentials": { - "key": "Key", - "secret": "Secret" - }, - "credentialsValidator": { - "requiresKey": true, - "requiresSecret": true - } - }, - "features": { - "supports": { - "restAPI": true, - "restCapabilities": { - "tickerBatching": true, - "autoPairUpdates": true - }, - "websocketAPI": true, - "websocketCapabilities": {} - }, - "enabled": { - "autoPairUpdates": true, - "websocketAPI": false - } - }, - "bankAccounts": [ - { - "enabled": false, - "bankName": "", - "bankAddress": "", - "bankPostalCode": "", - "bankPostalCity": "", - "bankCountry": "", - "accountName": "", - "accountNumber": "", - "swiftCode": "", - "iban": "", - "supportedCurrencies": "" - } - ] + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "credentials": { + "key": "", + "secret": "", + "clientID": "" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true, + "requiresClientID": true + }, + "urlEndpoints": { + "RestSpotURL": "https://www.okx.com/api/v5/", + "WebsocketSpotURL": "wss://ws.okx.com:8443/ws/v5/public" + } }, - { - "name": "Yobit", - "enabled": true, - "verbose": false, - "httpTimeout": 15000000000, - "websocketResponseCheckTimeout": 30000000, - "websocketResponseMaxLimit": 7000000000, - "websocketTrafficTimeout": 30000000000, - "websocketOrderbookBufferLimit": 5, - "baseCurrencies": "USD", - "currencyPairs": { - "requestFormat": { - "uppercase": false, - "delimiter": "_", - "separator": "-" - }, - "configFormat": { - "uppercase": true, - "delimiter": "_" - }, - "useGlobalFormat": true, - "lastUpdated": 1566798411, - "assetTypes": [ - "spot" - ], - "pairs": { - "spot": { - "enabled": "LTC_BTC,ETH_BTC,BTC_USD,DASH_BTC", - "available": "DASH_BTC,WAVES_BTC,LSK_BTC,LIZA_BTC,BCC_BTC,ETH_BTC,LTC_BTC,TRX_BTC,DOGE_BTC,VNTX_BTC,SW_BTC,ZEC_BTC,DASH_ETH,WAVES_ETH,LSK_ETH,LIZA_ETH,BCC_ETH,LTC_ETH,TRX_ETH,DOGE_ETH,VNTX_ETH,SW_ETH,ZEC_ETH,DASH_DOGE,WAVES_DOGE,LSK_DOGE,LIZA_DOGE,BCC_DOGE,LTC_DOGE,TRX_DOGE,VNTX_DOGE,SW_DOGE,ZEC_DOGE,DASH_USD,WAVES_USD,LSK_USD,LIZA_USD,BCC_USD,LTC_USD,TRX_USD,VNTX_USD,SW_USD,ZEC_USD,ETH_USD,BTC_USD,DASH_RUR,WAVES_BTC,WAVES_RUR,LSK_RUR,LIZA_RUR,BCC_RUR,LTC_RUR,TRX_RUR,VNTX_RUR,SW_RUR,ETH_RUR,ZEC_RUR" - } - } - }, - "api": { - "authenticatedSupport": false, - "authenticatedWebsocketApiSupport": false, - "endpoints": { - "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" - }, - "credentials": { - "key": "Key", - "secret": "Secret" - }, - "credentialsValidator": { - "requiresKey": true, - "requiresSecret": true - } - }, - "features": { - "supports": { - "restAPI": true, - "restCapabilities": { - "tickerBatching": true, - "autoPairUpdates": true - }, - "websocketAPI": false, - "websocketCapabilities": {} - }, - "enabled": { - "autoPairUpdates": false, - "websocketAPI": false + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "autoPairUpdates": true + }, + "websocketAPI": true, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": true, + "saveTradeData": false, + "tradeFeed": false, + "fillsFeed": false + } + }, + "bankAccounts": [ + { + "enabled": false, + "bankName": "", + "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ], + "orderbook": { + "verificationBypass": false, + "websocketBufferLimit": 5, + "websocketBufferEnabled": false, + "publishPeriod": 10000000000 + } + }, + { + "name": "Poloniex", + "enabled": true, + "verbose": false, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, + "websocketOrderbookBufferLimit": 5, + "baseCurrencies": "USD", + "currencyPairs": { + "requestFormat": { + "uppercase": true, + "delimiter": "_" + }, + "configFormat": { + "uppercase": true, + "delimiter": "_" + }, + "useGlobalFormat": true, + "assetTypes": [ + "spot" + ], + "pairs": { + "spot": { + "enabled": "BTC_LTC,BTC_ETH,BTC_DOGE,BTC_DASH,BTC_XRP", + "available": "USDC_GRIN,BTC_BCN,BTC_DGB,BTC_XMR,USDT_STR,BTC_SC,BTC_ZRX,USDC_XMR,BTC_TRX,BTC_STR,BTC_SNT,USDT_QTUM,USDC_BTC,BTC_NMR,BTC_DASH,BTC_NXT,USDT_LTC,BTC_DCR,USDT_ZRX,USDC_ZEC,USDT_REP,USDT_BAT,BTC_MANA,USDC_BCHABC,USDC_STR,BTC_XRP,USDT_ETH,BTC_REP,USDT_EOS,USDC_ATOM,USDT_XRP,BTC_ETH,USDT_LSK,USDT_SC,USDT_MANA,USDC_ETC,USDC_ETH,BTC_BTS,BTC_LTC,BTC_ETC,BTC_OMG,BTC_STORJ,USDC_XRP,USDT_GRIN,BTC_QTUM,BTC_MAID,BTC_XEM,USDT_BTC,USDT_DASH,ETH_REP,BTC_ZEC,BTC_STRAT,USDC_LTC,BTC_FOAM,USDC_TRX,BTC_DOGE,BTC_VIA,BTC_VTC,ETH_ETC,USDT_ETC,ETH_EOS,USDC_BCHSV,USDT_NXT,USDT_XMR,BTC_ARDR,BTC_CVC,ETH_BAT,USDC_DOGE,BTC_XPM,BTC_LOOM,BTC_LPT,USDC_EOS,USDT_DGB,USDT_BCHSV,BTC_OMNI,ETH_ZEC,BTC_EOS,BTC_KNC,BTC_BCHSV,BTC_POLY,USDC_DASH,USDT_GNT,BTC_BCHABC,BTC_GRIN,BTC_ATOM,USDT_ATOM,USDT_BCHABC,BTC_LSK,ETH_ZRX,BTC_GAS,BTC_BAT,BTC_BNT,USDT_TRX,BTC_FCT,USDT_ZEC,BTC_GNT,USDT_DOGE,USDC_USDT" } - }, - "bankAccounts": [ - { - "enabled": false, - "bankName": "", - "bankAddress": "", - "bankPostalCode": "", - "bankPostalCity": "", - "bankCountry": "", - "accountName": "", - "accountNumber": "", - "swiftCode": "", - "iban": "", - "supportedCurrencies": "" + } + }, + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "tickerBatching": true, + "autoPairUpdates": true + }, + "websocketAPI": true, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": false + } + }, + "bankAccounts": [ + { + "enabled": false, + "bankName": "", + "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ] + }, + { + "name": "Yobit", + "enabled": true, + "verbose": false, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, + "websocketOrderbookBufferLimit": 5, + "baseCurrencies": "USD", + "currencyPairs": { + "requestFormat": { + "uppercase": false, + "delimiter": "_", + "separator": "-" + }, + "configFormat": { + "uppercase": true, + "delimiter": "_" + }, + "useGlobalFormat": true, + "lastUpdated": 1566798411, + "assetTypes": [ + "spot" + ], + "pairs": { + "spot": { + "enabled": "LTC_BTC,ETH_BTC,BTC_USD,DASH_BTC", + "available": "DASH_BTC,WAVES_BTC,LSK_BTC,LIZA_BTC,BCC_BTC,ETH_BTC,LTC_BTC,TRX_BTC,DOGE_BTC,VNTX_BTC,SW_BTC,ZEC_BTC,DASH_ETH,WAVES_ETH,LSK_ETH,LIZA_ETH,BCC_ETH,LTC_ETH,TRX_ETH,DOGE_ETH,VNTX_ETH,SW_ETH,ZEC_ETH,DASH_DOGE,WAVES_DOGE,LSK_DOGE,LIZA_DOGE,BCC_DOGE,LTC_DOGE,TRX_DOGE,VNTX_DOGE,SW_DOGE,ZEC_DOGE,DASH_USD,WAVES_USD,LSK_USD,LIZA_USD,BCC_USD,LTC_USD,TRX_USD,VNTX_USD,SW_USD,ZEC_USD,ETH_USD,BTC_USD,DASH_RUR,WAVES_BTC,WAVES_RUR,LSK_RUR,LIZA_RUR,BCC_RUR,LTC_RUR,TRX_RUR,VNTX_RUR,SW_RUR,ETH_RUR,ZEC_RUR" } - ] - } - ], - "bankAccounts": [ - { - "enabled": false, - "bankName": "test", - "bankAddress": "test", - "bankPostalCode": "", - "bankPostalCity": "", - "bankCountry": "", - "accountName": "TestAccount", - "accountNumber": "0234", - "swiftCode": "91272837", - "iban": "98218738671897", - "supportedCurrencies": "USD", - "supportedExchanges": "Kraken,Bitstamp" - } - ] - } - \ No newline at end of file + } + }, + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "endpoints": { + "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", + "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" + }, + "credentials": { + "key": "Key", + "secret": "Secret" + }, + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "tickerBatching": true, + "autoPairUpdates": true + }, + "websocketAPI": false, + "websocketCapabilities": {} + }, + "enabled": { + "autoPairUpdates": false, + "websocketAPI": false + } + }, + "bankAccounts": [ + { + "enabled": false, + "bankName": "", + "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ] + } + ], + "bankAccounts": [ + { + "enabled": false, + "bankName": "test", + "bankAddress": "test", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", + "accountName": "TestAccount", + "accountNumber": "0234", + "swiftCode": "91272837", + "iban": "98218738671897", + "supportedCurrencies": "USD", + "supportedExchanges": "Kraken,Bitstamp" + } + ] +} From 91d4b240a3c41a148121d25990a5e555f03d7121 Mon Sep 17 00:00:00 2001 From: Samuel Reid <43227667+cranktakular@users.noreply.github.com> Date: Thu, 30 May 2024 11:13:03 +1000 Subject: [PATCH 48/79] Little fix --- exchanges/coinbasepro/coinbasepro_test.go | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/exchanges/coinbasepro/coinbasepro_test.go b/exchanges/coinbasepro/coinbasepro_test.go index feb8d2b168b..3f63c5b7629 100644 --- a/exchanges/coinbasepro/coinbasepro_test.go +++ b/exchanges/coinbasepro/coinbasepro_test.go @@ -394,16 +394,9 @@ func TestMovePortfolioFunds(t *testing.T) { if len(portID) < 2 { t.Skip(skipInsufficientPortfolios) } - // TODO: The API's set up so that the funds on a portfolio can only be checked one portfolio at a time, - // so studiously checking for a valid portfolio to send funds from would take ages. And so, this aims to - // fail gracefully if there's not enough funds. However, as of this week, the exchange returns a 429 header, - // indicating the rate limit was hit when it wasn't, and so prompting GCT to keep ramming its head into it - // until something gives and it fails badly. I think either they'll need to fix that, we'll need to waste - // a bunch of time to fail gracefully, or we'll have to rewrite some core retry policy stuff to deal with - // cases like this. _, err = c.MovePortfolioFunds(context.Background(), testCrypto.String(), portID[0].UUID, portID[1].UUID, testAmount) - if err != nil && err.Error() != errPortTransferInsufFunds { + if err != nil { t.Error(err) } } From 75425fd1a88738979038aab28a736dbd58342cfa Mon Sep 17 00:00:00 2001 From: Samuel Reid <43227667+cranktakular@users.noreply.github.com> Date: Tue, 11 Jun 2024 13:36:01 +1000 Subject: [PATCH 49/79] New public endpoint incorporation --- .../exchange_wrapper_standards_test.go | 23 +- exchanges/coinbasepro/coinbasepro.go | 120 +++--- exchanges/coinbasepro/coinbasepro_test.go | 146 +++---- exchanges/coinbasepro/coinbasepro_wrapper.go | 373 +++++++----------- 4 files changed, 270 insertions(+), 392 deletions(-) diff --git a/cmd/exchange_wrapper_standards/exchange_wrapper_standards_test.go b/cmd/exchange_wrapper_standards/exchange_wrapper_standards_test.go index b459390aea9..8063cc2ebfa 100644 --- a/cmd/exchange_wrapper_standards/exchange_wrapper_standards_test.go +++ b/cmd/exchange_wrapper_standards/exchange_wrapper_standards_test.go @@ -94,15 +94,12 @@ func setupExchange(ctx context.Context, t *testing.T, name string, cfg *config.C t.Fatalf("Cannot setup %v UpdateTradablePairs %v", name, err) } b := exch.GetBase() - // Strange ordering since Setup must be run before UpdateTradablePairs, but Coinbase will fail if invalid - // credentials have been set, so we must set them after UpdateTradablePairs + // Idiosyncratic execution flow since Setup must be run before UpdateTradablePairs, but Coinbase will fail if + // invalid credentials have been set, so we must set them after UpdateTradablePairs creds := getExchangeCredentials(name) + b.SetCredentials(creds.Key, creds.Secret, creds.ClientID, creds.Subaccount, creds.Subaccount, + creds.OTPSecret) b.API.AuthenticatedSupport = true - b.API.SetClientID(creds.ClientID) - b.API.SetKey(creds.Key) - b.API.SetSecret(creds.Secret) - b.API.SetPEMKey(creds.PEMKey) - b.API.SetSubAccount(creds.Subaccount) // Lbank usually runs this during setup, but if keys aren't set then, it will fail, so we have to manually // recreate that here lbankExch, ok := exch.(*lbank.Lbank) @@ -717,18 +714,6 @@ func isFiat(t *testing.T, c string) bool { return false } -// func getTestableAssets(t *testing.T, exch exchange.IBotExchange) []asset.Item { -// t.Helper() -// var assets []asset.Item -// if exchange == coinbrasepro && asset.Ifutures { -// continue -// } -// for x := range exch.GetBase().CurrencyPairs.GetAssetTypes(false) { -// assets = append(assets, exch.GetBase().CurrencyPairs.GetAssetTypes(false)[x]) -// } -// return assets -// } - // disruptFormatting adds in an unused delimiter and strange casing features to // ensure format currency pair is used throughout the code base. func disruptFormatting(t *testing.T, p currency.Pair) (currency.Pair, error) { diff --git a/exchanges/coinbasepro/coinbasepro.go b/exchanges/coinbasepro/coinbasepro.go index b0b3dbbd1fc..7c599173997 100644 --- a/exchanges/coinbasepro/coinbasepro.go +++ b/exchanges/coinbasepro/coinbasepro.go @@ -79,6 +79,7 @@ const ( coinbaseTrades = "trades" coinbaseWrappedAssets = "wrapped-assets" coinbaseConversionRate = "conversion-rate" + coinbaseMarket = "market" pageNone = "" pageBefore = "before" @@ -98,6 +99,7 @@ const ( errIntervalNotSupported = "interval not supported" warnSequenceIssue = "Out of order sequence number. Received %v, expected %v" + warnAuth = "%v authenticated request failed, attempting unauthenticated" ) // Constants defining whether a transfer is a deposit or withdrawal, used to simplify @@ -107,11 +109,11 @@ const ( FiatWithdrawal FiatTransferType = true ) -// While the exchange's fee pages say the worst taker/maker fees are 0.002 lower than the ones listed +// While the exchange's fee pages say the worst taker/maker fees are lower than the ones listed // here, the data returned by the GetTransactionsSummary endpoint are consistent with these worst // case scenarios. The best case scenarios are untested, and assumed to be in line with the fee pages const ( - WorstCaseTakerFee = 0.01 + WorstCaseTakerFee = 0.012 WorstCaseMakerFee = 0.006 BestCaseTakerFee = 0.0005 BestCaseMakerFee = 0 @@ -161,7 +163,6 @@ var ( errFloatConvert = errors.New("unable to convert into float64 value") errNoCredsUser = errors.New("no credentials when attempting to subscribe to authenticated channel user") errWrappedAssetEmpty = errors.New("wrapped asset cannot be empty") - errAuthenticationNeeded = errors.New("authentication is needed to use this endpoint") ) // GetAllAccounts returns information on all trading accounts associated with the API key @@ -202,7 +203,7 @@ func (c *CoinbasePro) GetBestBidAsk(ctx context.Context, products []string) ([]P } // GetProductBookV3 returns a list of bids/asks for a single product -func (c *CoinbasePro) GetProductBookV3(ctx context.Context, productID string, limit uint16) (*ProductBook, error) { +func (c *CoinbasePro) GetProductBookV3(ctx context.Context, productID string, limit uint16, authenticated bool) (*ProductBook, error) { if productID == "" { return nil, errProductIDEmpty } @@ -212,12 +213,17 @@ func (c *CoinbasePro) GetProductBookV3(ctx context.Context, productID string, li vals.Set("limit", strconv.FormatInt(int64(limit), 10)) } var resp ProductBookResponse - return &resp.Pricebook, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - coinbaseV3+coinbaseProductBook, vals, nil, true, &resp, nil) + if authenticated { + return &resp.Pricebook, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, + coinbaseV3+coinbaseProductBook, vals, nil, true, &resp, nil) + } else { + path := coinbaseV3 + coinbaseMarket + "/" + coinbaseProductBook + return &resp.Pricebook, c.SendHTTPRequest(ctx, exchange.RestSpot, path, vals, &resp) + } } // 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) { +func (c *CoinbasePro) GetAllProducts(ctx context.Context, limit, offset int32, productType, contractExpiryType, expiringContractStatus string, productIDs []string, authenticated bool) (*AllProducts, error) { vals := url.Values{} if limit != 0 { vals.Set("limit", strconv.FormatInt(int64(limit), 10)) @@ -238,25 +244,35 @@ func (c *CoinbasePro) GetAllProducts(ctx context.Context, limit, offset int32, p vals.Add("product_ids", productIDs[x]) } var products AllProducts - return &products, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - coinbaseV3+coinbaseProducts, vals, nil, true, &products, nil) + if authenticated { + return &products, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, + coinbaseV3+coinbaseProducts, vals, nil, true, &products, nil) + } else { + path := coinbaseV3 + coinbaseMarket + "/" + coinbaseProducts + return &products, c.SendHTTPRequest(ctx, exchange.RestSpot, path, vals, &products) + } } // GetProductByID returns information on a single specified currency pair -func (c *CoinbasePro) GetProductByID(ctx context.Context, productID string) (*Product, error) { +func (c *CoinbasePro) GetProductByID(ctx context.Context, productID string, authenticated bool) (*Product, error) { if productID == "" { return nil, errProductIDEmpty } - path := coinbaseV3 + coinbaseProducts + "/" + productID - resp := Product{} - return &resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - path, nil, nil, true, &resp, nil) + var resp Product + if authenticated { + path := coinbaseV3 + coinbaseProducts + "/" + productID + return &resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, + path, nil, nil, true, &resp, nil) + } else { + path := coinbaseV3 + coinbaseMarket + "/" + coinbaseProducts + "/" + productID + return &resp, c.SendHTTPRequest(ctx, exchange.RestSpot, path, nil, &resp) + } } // 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) ([]CandleStruct, error) { +func (c *CoinbasePro) GetHistoricRates(ctx context.Context, productID, granularity string, startDate, endDate time.Time, authenticated bool) ([]CandleStruct, error) { var resp History if productID == "" { return nil, errProductIDEmpty @@ -272,19 +288,24 @@ func (c *CoinbasePro) GetHistoricRates(ctx context.Context, productID, granulari 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, vals, nil, true, &resp, nil) + var err error + if authenticated { + path := coinbaseV3 + coinbaseProducts + "/" + productID + "/" + coinbaseCandles + err = c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, + path, vals, nil, true, &resp, nil) + } else { + path := coinbaseV3 + coinbaseMarket + "/" + coinbaseProducts + "/" + productID + "/" + coinbaseCandles + err = c.SendHTTPRequest(ctx, exchange.RestSpot, path, vals, &resp) + } return resp.Candles, err } // GetTicker returns snapshot information about the last trades (ticks) and best bid/ask. // Contrary to documentation, this does not tell you the 24h volume -func (c *CoinbasePro) GetTicker(ctx context.Context, productID string, limit uint16, startDate, endDate time.Time) (*Ticker, error) { +func (c *CoinbasePro) GetTicker(ctx context.Context, productID string, limit uint16, startDate, endDate time.Time, authenticated bool) (*Ticker, error) { if productID == "" { return nil, errProductIDEmpty } - path := coinbaseV3 + coinbaseProducts + "/" + productID + "/" + coinbaseTicker vals := url.Values{} vals.Set("limit", strconv.FormatInt(int64(limit), 10)) if !startDate.IsZero() && !startDate.Equal(time.Time{}) { @@ -294,8 +315,14 @@ func (c *CoinbasePro) GetTicker(ctx context.Context, productID string, limit uin vals.Set("end", strconv.FormatInt(endDate.Unix(), 10)) } var resp Ticker - return &resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - path, vals, nil, true, &resp, nil) + if authenticated { + path := coinbaseV3 + coinbaseProducts + "/" + productID + "/" + coinbaseTicker + return &resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, + path, vals, nil, true, &resp, nil) + } else { + path := coinbaseV3 + coinbaseMarket + "/" + coinbaseProducts + "/" + productID + "/" + coinbaseTicker + return &resp, c.SendHTTPRequest(ctx, exchange.RestSpot, path, vals, &resp) + } } // PlaceOrder places either a limit, market, or stop order @@ -794,7 +821,7 @@ func (c *CoinbasePro) GetConvertTradeByID(ctx context.Context, tradeID, from, to // GetV3Time returns the current server time, calling V3 of the API func (c *CoinbasePro) GetV3Time(ctx context.Context) (*ServerTimeV3, error) { var resp *ServerTimeV3 - return resp, c.SendHTTPRequest(ctx, exchange.RestSpot, coinbaseV3+coinbaseTime, &resp) + return resp, c.SendHTTPRequest(ctx, exchange.RestSpot, coinbaseV3+coinbaseTime, nil, &resp) } // GetAllPaymentMethods returns a list of all payment methods associated with the user's account @@ -1094,14 +1121,14 @@ func (c *CoinbasePro) GetFiatTransferByID(ctx context.Context, walletID, deposit // GetFiatCurrencies lists currencies that Coinbase knows about func (c *CoinbasePro) GetFiatCurrencies(ctx context.Context) ([]FiatData, error) { var resp GetFiatCurrenciesResp - return resp.Data, c.SendHTTPRequest(ctx, exchange.RestSpot, coinbaseV2+coinbaseCurrencies, &resp) + return resp.Data, c.SendHTTPRequest(ctx, exchange.RestSpot, coinbaseV2+coinbaseCurrencies, nil, &resp) } // GetCryptocurrencies lists cryptocurrencies that Coinbase knows about func (c *CoinbasePro) GetCryptocurrencies(ctx context.Context) ([]CryptoData, error) { var resp GetCryptocurrenciesResp path := coinbaseV2 + coinbaseCurrencies + "/" + coinbaseCrypto - return resp.Data, c.SendHTTPRequest(ctx, exchange.RestSpot, path, &resp) + return resp.Data, c.SendHTTPRequest(ctx, exchange.RestSpot, path, nil, &resp) } // GetExchangeRates returns exchange rates for the specified currency. If none is specified, @@ -1110,8 +1137,7 @@ func (c *CoinbasePro) GetExchangeRates(ctx context.Context, currency string) (*G 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) + return resp, c.SendHTTPRequest(ctx, exchange.RestSpot, coinbaseV2+coinbaseExchangeRates, vals, &resp) } // GetPrice returns the price the spot/buy/sell price for the specified currency pair, @@ -1125,19 +1151,19 @@ func (c *CoinbasePro) GetPrice(ctx context.Context, currencyPair, priceType stri return nil, errInvalidPriceType } var resp *GetPriceResp - return resp, c.SendHTTPRequest(ctx, exchange.RestSpot, path, &resp) + return resp, c.SendHTTPRequest(ctx, exchange.RestSpot, path, nil, &resp) } // GetV2Time returns the current server time, calling V2 of the API func (c *CoinbasePro) GetV2Time(ctx context.Context) (*ServerTimeV2, error) { var resp *ServerTimeV2 - return resp, c.SendHTTPRequest(ctx, exchange.RestSpot, coinbaseV2+coinbaseTime, &resp) + return resp, c.SendHTTPRequest(ctx, exchange.RestSpot, coinbaseV2+coinbaseTime, nil, &resp) } // GetAllCurrencies returns a list of all currencies that Coinbase knows about. These aren't necessarily tradable func (c *CoinbasePro) GetAllCurrencies(ctx context.Context) ([]CurrencyData, error) { var resp []CurrencyData - return resp, c.SendHTTPRequest(ctx, exchange.RestSpotSupplementary, coinbaseCurrencies, &resp) + return resp, c.SendHTTPRequest(ctx, exchange.RestSpotSupplementary, coinbaseCurrencies, nil, &resp) } // GetACurrency returns information on a single currency specified by the user @@ -1147,7 +1173,7 @@ func (c *CoinbasePro) GetACurrency(ctx context.Context, currency string) (*Curre } var resp *CurrencyData path := coinbaseCurrencies + "/" + currency - return resp, c.SendHTTPRequest(ctx, exchange.RestSpotSupplementary, path, &resp) + return resp, c.SendHTTPRequest(ctx, exchange.RestSpotSupplementary, path, nil, &resp) } // GetAllTradingPairs returns a list of currency pairs which are available for trading @@ -1157,15 +1183,14 @@ func (c *CoinbasePro) GetAllTradingPairs(ctx context.Context, pairType string) ( if pairType != "" { vals.Set("type", pairType) } - path := common.EncodeURLValues(coinbaseProducts, vals) - return resp, c.SendHTTPRequest(ctx, exchange.RestSpotSupplementary, path, &resp) + return resp, c.SendHTTPRequest(ctx, exchange.RestSpotSupplementary, coinbaseProducts, vals, &resp) } // GetAllPairVolumes returns a list of currency pairs and their associated volumes func (c *CoinbasePro) GetAllPairVolumes(ctx context.Context) ([]PairVolumeData, error) { var resp []PairVolumeData path := coinbaseProducts + "/" + coinbaseVolumeSummary - return resp, c.SendHTTPRequest(ctx, exchange.RestSpotSupplementary, path, &resp) + return resp, c.SendHTTPRequest(ctx, exchange.RestSpotSupplementary, path, nil, &resp) } // GetPairDetails returns information on a single currency pair @@ -1175,7 +1200,7 @@ func (c *CoinbasePro) GetPairDetails(ctx context.Context, pair string) (*PairDat } var resp *PairData path := coinbaseProducts + "/" + pair - return resp, c.SendHTTPRequest(ctx, exchange.RestSpotSupplementary, path, &resp) + return resp, c.SendHTTPRequest(ctx, exchange.RestSpotSupplementary, path, nil, &resp) } // GetProductBookV1 returns the order book for the specified currency pair. Level 1 only returns the best bids and asks, @@ -1188,8 +1213,8 @@ func (c *CoinbasePro) GetProductBookV1(ctx context.Context, pair string, level u var resp OrderBookResp vals := url.Values{} vals.Set("level", strconv.FormatUint(uint64(level), 10)) - path := common.EncodeURLValues(coinbaseProducts+"/"+pair+"/"+coinbaseBook, vals) - err := c.SendHTTPRequest(ctx, exchange.RestSpotSupplementary, path, &resp) + path := coinbaseProducts + "/" + pair + "/" + coinbaseBook + err := c.SendHTTPRequest(ctx, exchange.RestSpotSupplementary, path, vals, &resp) if err != nil { return nil, err } @@ -1276,9 +1301,9 @@ func (c *CoinbasePro) GetProductCandles(ctx context.Context, pair string, granul if granularity != 0 { params.Values.Set("granularity", strconv.FormatUint(uint64(granularity), 10)) } - path := common.EncodeURLValues(coinbaseProducts+"/"+pair+"/"+coinbaseCandles, params.Values) + path := coinbaseProducts + "/" + pair + "/" + coinbaseCandles var resp []RawCandles - err = c.SendHTTPRequest(ctx, exchange.RestSpotSupplementary, path, &resp) + err = c.SendHTTPRequest(ctx, exchange.RestSpotSupplementary, path, params.Values, &resp) if err != nil { return nil, err } @@ -1329,7 +1354,7 @@ func (c *CoinbasePro) GetProductStats(ctx context.Context, pair string) (*Produc } path := coinbaseProducts + "/" + pair + "/" + coinbaseStats var resp *ProductStats - return resp, c.SendHTTPRequest(ctx, exchange.RestSpotSupplementary, path, &resp) + return resp, c.SendHTTPRequest(ctx, exchange.RestSpotSupplementary, path, nil, &resp) } // GetProductTicker returns the ticker for the specified currency pair @@ -1339,7 +1364,7 @@ func (c *CoinbasePro) GetProductTicker(ctx context.Context, pair string) (*Produ } path := coinbaseProducts + "/" + pair + "/" + coinbaseTicker var resp *ProductTicker - return resp, c.SendHTTPRequest(ctx, exchange.RestSpotSupplementary, path, &resp) + return resp, c.SendHTTPRequest(ctx, exchange.RestSpotSupplementary, path, nil, &resp) } // GetProductTrades returns a list of the latest traides for a pair @@ -1352,15 +1377,15 @@ func (c *CoinbasePro) GetProductTrades(ctx context.Context, pair, step, directio vals.Set(direction, step) } vals.Set("limit", strconv.FormatInt(limit, 10)) - path := common.EncodeURLValues(coinbaseProducts+"/"+pair+"/"+coinbaseTrades, vals) + path := coinbaseProducts + "/" + pair + "/" + coinbaseTrades var resp []ProductTrades - return resp, c.SendHTTPRequest(ctx, exchange.RestSpotSupplementary, path, &resp) + return resp, c.SendHTTPRequest(ctx, exchange.RestSpotSupplementary, path, vals, &resp) } // GetAllWrappedAssets returns a list of supported wrapped assets func (c *CoinbasePro) GetAllWrappedAssets(ctx context.Context) (*AllWrappedAssets, error) { var resp *AllWrappedAssets - return resp, c.SendHTTPRequest(ctx, exchange.RestSpotSupplementary, coinbaseWrappedAssets, &resp) + return resp, c.SendHTTPRequest(ctx, exchange.RestSpotSupplementary, coinbaseWrappedAssets, nil, &resp) } // GetWrappedAssetDetails returns information on a single wrapped asset @@ -1370,7 +1395,7 @@ func (c *CoinbasePro) GetWrappedAssetDetails(ctx context.Context, wrappedAsset s } var resp *WrappedAsset path := coinbaseWrappedAssets + "/" + wrappedAsset - return resp, c.SendHTTPRequest(ctx, exchange.RestSpotSupplementary, path, &resp) + return resp, c.SendHTTPRequest(ctx, exchange.RestSpotSupplementary, path, nil, &resp) } // GetWrappedAssetConversionRate returns the conversion rate for the specified wrapped asset @@ -1380,15 +1405,16 @@ func (c *CoinbasePro) GetWrappedAssetConversionRate(ctx context.Context, wrapped } var resp *WrappedAssetConversionRate path := coinbaseWrappedAssets + "/" + wrappedAsset + "/" + coinbaseConversionRate - return resp, c.SendHTTPRequest(ctx, exchange.RestSpotSupplementary, path, &resp) + return resp, c.SendHTTPRequest(ctx, exchange.RestSpotSupplementary, path, nil, &resp) } // SendHTTPRequest sends an unauthenticated HTTP request -func (c *CoinbasePro) SendHTTPRequest(ctx context.Context, ep exchange.URL, path string, result interface{}) error { +func (c *CoinbasePro) SendHTTPRequest(ctx context.Context, ep exchange.URL, path string, vals url.Values, result interface{}) error { endpoint, err := c.API.Endpoints.GetURL(ep) if err != nil { return err } + path = common.EncodeURLValues(path, vals) item := &request.Item{ Method: http.MethodGet, Path: endpoint + path, diff --git a/exchanges/coinbasepro/coinbasepro_test.go b/exchanges/coinbasepro/coinbasepro_test.go index 3f63c5b7629..68406e493cb 100644 --- a/exchanges/coinbasepro/coinbasepro_test.go +++ b/exchanges/coinbasepro/coinbasepro_test.go @@ -66,13 +66,12 @@ const ( skipInsufficientTransactions = "insufficient transactions for test, skipping" errExpectMismatch = "received: '%v' but expected: '%v'" - errExpectMismatch2 = "received: '%v' but expected: '%v' or '%v'" errExpectedNonEmpty = "expected non-empty response" errPortfolioNameDuplicate = `CoinbasePro unsuccessful HTTP status code: 409 raw response: {"error":"CONFLICT","error_details":"A portfolio with this name already exists.","message":"A portfolio with this name already exists."}, authenticated request failed` errPortTransferInsufFunds = `CoinbasePro unsuccessful HTTP status code: 429 raw response: {"error":"unknown","error_details":"[PORTFOLIO_ERROR_CODE_INSUFFICIENT_FUNDS] insufficient funds in source account","message":"[PORTFOLIO_ERROR_CODE_INSUFFICIENT_FUNDS] insufficient funds in source account"}, authenticated request failed` - errInvalidProductID = `CoinbasePro unsuccessful HTTP status code: 400 raw response: {"error":"INVALID_ARGUMENT","error_details":"valid product_id is required","message":"valid product_id is required"}, authenticated request failed` - errValidProductIDRequired = `CoinbasePro unsuccessful HTTP status code: 404 raw response: {"message":"NotFound"}` + errInvalidProductID = `CoinbasePro unsuccessful HTTP status code: 400 raw response: {"error":"INVALID_ARGUMENT","error_details":"valid product_id is required","message":"valid product_id is required"}` errExpectedFeeRange = "expected fee range of %v and %v, received %v" + errOptionInvalid = `CoinbasePro unsuccessful HTTP status code: 400 raw response: {"error":"unknown","error_details":"parsing field \"product_type\": \"OPTION\" is not a valid value","message":"parsing field \"product_type\": \"OPTION\" is not a valid value"}` expectedTimestamp = "1970-01-01 00:20:34 +0000 UTC" ) @@ -165,53 +164,72 @@ func TestGetBestBidAsk(t *testing.T) { func TestGetProductBookV3(t *testing.T) { t.Parallel() - _, err := c.GetProductBookV3(context.Background(), "", 0) + _, err := c.GetProductBookV3(context.Background(), "", 0, false) assert.ErrorIs(t, err, errProductIDEmpty) + resp, err := c.GetProductBookV3(context.Background(), testPair.String(), 2, false) + assert.NoError(t, err) + assert.NotEmpty(t, resp, errExpectedNonEmpty) sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - resp, err := c.GetProductBookV3(context.Background(), "BTC-PERP-INTX", 2) + resp, err = c.GetProductBookV3(context.Background(), testPair.String(), 2, true) assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) } func TestGetAllProducts(t *testing.T) { t.Parallel() - sharedtestvalues.SkipTestIfCredentialsUnset(t, c) testPairs := []string{testPair.String(), "ETH-USD"} resp, err := c.GetAllProducts(context.Background(), 30000, 1, "SPOT", "PERPETUAL", "STATUS_ALL", - testPairs) + testPairs, false) + assert.NoError(t, err) + assert.NotEmpty(t, resp, errExpectedNonEmpty) + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + resp, err = c.GetAllProducts(context.Background(), 30000, 1, "SPOT", "PERPETUAL", "STATUS_ALL", + nil, true) assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) } func TestGetProductByID(t *testing.T) { t.Parallel() - _, err := c.GetProductByID(context.Background(), "") + _, err := c.GetProductByID(context.Background(), "", false) assert.ErrorIs(t, err, errProductIDEmpty) + resp, err := c.GetProductByID(context.Background(), testPair.String(), false) + assert.NoError(t, err) + assert.NotEmpty(t, resp, errExpectedNonEmpty) sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - resp, err := c.GetProductByID(context.Background(), testPair.String()) + resp, err = c.GetProductByID(context.Background(), testPair.String(), true) assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) } func TestGetHistoricRates(t *testing.T) { t.Parallel() - _, err := c.GetHistoricRates(context.Background(), "", granUnknown, time.Time{}, time.Time{}) + _, err := c.GetHistoricRates(context.Background(), "", granUnknown, time.Time{}, time.Time{}, false) assert.ErrorIs(t, err, errProductIDEmpty) - _, err = c.GetHistoricRates(context.Background(), testPair.String(), "blorbo", time.Time{}, time.Time{}) + _, err = c.GetHistoricRates(context.Background(), testPair.String(), "blorbo", time.Time{}, time.Time{}, false) assert.ErrorIs(t, err, errInvalidGranularity) - sharedtestvalues.SkipTestIfCredentialsUnset(t, c) resp, err := c.GetHistoricRates(context.Background(), testPair.String(), granOneMin, - time.Now().Add(-5*time.Minute), time.Now()) + time.Now().Add(-5*time.Minute), time.Now(), false) + assert.NoError(t, err) + assert.NotEmpty(t, resp, errExpectedNonEmpty) + sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + resp, err = c.GetHistoricRates(context.Background(), testPair.String(), granOneMin, + time.Now().Add(-5*time.Minute), time.Now(), true) assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) } func TestGetTicker(t *testing.T) { t.Parallel() - _, err := c.GetTicker(context.Background(), "", 1, time.Time{}, time.Time{}) + _, err := c.GetTicker(context.Background(), "", 1, time.Time{}, time.Time{}, false) assert.ErrorIs(t, err, errProductIDEmpty) + resp, err := c.GetTicker(context.Background(), testPair.String(), 5, time.Now().Add(-time.Minute*5), time.Now(), + false) + assert.NoError(t, err) + assert.NotEmpty(t, resp, errExpectedNonEmpty) sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - resp, err := c.GetTicker(context.Background(), testPair.String(), 5, time.Now().Add(-time.Minute*5), time.Now()) + resp, err = c.GetTicker(context.Background(), testPair.String(), 5, time.Now().Add(-time.Minute*5), time.Now(), + true) assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) } @@ -396,9 +414,7 @@ func TestMovePortfolioFunds(t *testing.T) { } _, err = c.MovePortfolioFunds(context.Background(), testCrypto.String(), portID[0].UUID, portID[1].UUID, testAmount) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) } func TestGetPortfolioByID(t *testing.T) { @@ -789,17 +805,13 @@ func TestCommitTransfer(t *testing.T) { wID, pmID := transferTestHelper(t, wallets) depID, err := c.FiatTransfer(context.Background(), wID, testFiat.String(), pmID, testAmount, false, FiatDeposit) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) resp, err := c.CommitTransfer(context.Background(), wID, depID.Data.ID, FiatDeposit) assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) depID, err = c.FiatTransfer(context.Background(), wID, testFiat.String(), pmID, testAmount, false, FiatWithdrawal) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) resp, err = c.CommitTransfer(context.Background(), wID, depID.Data.ID, FiatWithdrawal) assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) @@ -812,9 +824,7 @@ func TestGetAllFiatTransfers(t *testing.T) { assert.ErrorIs(t, err, errWalletIDEmpty) sharedtestvalues.SkipTestIfCredentialsUnset(t, c) wID, err := c.GetWalletByID(context.Background(), "", "AUD") - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) assert.NotEmpty(t, wID, errExpectedNonEmpty) _, err = c.GetAllFiatTransfers(context.Background(), wID.Data.ID, pag, FiatDeposit) assert.NoError(t, err) @@ -830,9 +840,7 @@ func TestGetFiatTransferByID(t *testing.T) { assert.ErrorIs(t, err, errDepositIDEmpty) sharedtestvalues.SkipTestIfCredentialsUnset(t, c) wID, err := c.GetWalletByID(context.Background(), "", "AUD") - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) assert.NotEmpty(t, wID, errExpectedNonEmpty) dID, err := c.GetAllFiatTransfers(context.Background(), wID.Data.ID, PaginationInp{}, FiatDeposit) assert.NoError(t, err) @@ -961,7 +969,7 @@ func TestGetWrappedAssetConversionRate(t *testing.T) { func TestSendHTTPRequest(t *testing.T) { t.Parallel() - err := c.SendHTTPRequest(context.Background(), exchange.EdgeCase3, "", nil) + err := c.SendHTTPRequest(context.Background(), exchange.EdgeCase3, "", nil, nil) assert.ErrorIs(t, err, exchange.ErrEndpointPathNotFound) } @@ -1033,12 +1041,11 @@ func TestGetFee(t *testing.T) { func TestFetchTradablePairs(t *testing.T) { t.Parallel() - _, err := c.FetchTradablePairs(context.Background(), asset.Empty) - assert.ErrorIs(t, err, asset.ErrNotSupported) + _, err := c.FetchTradablePairs(context.Background(), asset.Options) + assert.EqualValues(t, errOptionInvalid, err.Error()) resp, err := c.FetchTradablePairs(context.Background(), asset.Spot) assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) - sharedtestvalues.SkipTestIfCredentialsUnset(t, c) resp, err = c.FetchTradablePairs(context.Background(), asset.Futures) assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) @@ -1047,11 +1054,7 @@ func TestFetchTradablePairs(t *testing.T) { func TestUpdateTradablePairs(t *testing.T) { t.Parallel() err := c.UpdateTradablePairs(context.Background(), false) - // if sharedtestvalues.AreAPICredentialsSet(c) { assert.NoError(t, err) - // } else { - // assert.ErrorIs(t, err, errAuthenticationNeeded) - // } } func TestUpdateAccountInfo(t *testing.T) { @@ -1072,10 +1075,9 @@ func TestFetchAccountInfo(t *testing.T) { func TestUpdateTickers(t *testing.T) { t.Parallel() - err := c.UpdateTickers(context.Background(), asset.Spot) - assert.NoError(t, err) - sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - err = c.UpdateTickers(context.Background(), asset.Futures) + err := c.UpdateTickers(context.Background(), asset.Options) + assert.ErrorIs(t, err, currency.ErrAssetNotFound) + err = c.UpdateTickers(context.Background(), asset.Spot) assert.NoError(t, err) } @@ -1106,11 +1108,11 @@ func TestUpdateOrderbook(t *testing.T) { t.Parallel() _, err := c.UpdateOrderbook(context.Background(), currency.Pair{}, asset.Empty) assert.ErrorIs(t, err, currency.ErrCurrencyPairEmpty) + _, err = c.UpdateOrderbook(context.Background(), testPair, asset.Empty) + assert.ErrorIs(t, err, asset.ErrNotSupported) _, err = c.UpdateOrderbook(context.Background(), currency.NewPairWithDelimiter("meow", "woof", "-"), asset.Spot) - if err.Error() != errInvalidProductID && err.Error() != errValidProductIDRequired { - t.Errorf(errExpectMismatch2, err, errInvalidProductID, errValidProductIDRequired) - } + assert.EqualValues(t, errInvalidProductID, err.Error()) resp, err := c.UpdateOrderbook(context.Background(), testPair, asset.Spot) assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) @@ -1220,9 +1222,7 @@ func TestGetOrderInfo(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) ordID, err := c.GetAllOrders(context.Background(), testPair.String(), "", "", "", "", asset.Spot.Upper(), "", "", "", nil, nil, 2, time.Time{}, time.Now()) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) if ordID == nil || len(ordID.Orders) == 0 { t.Skip(skipInsufficientOrders) } @@ -1394,8 +1394,8 @@ func TestGetFuturesContractDetails(t *testing.T) { func TestUpdateOrderExecutionLimits(t *testing.T) { t.Parallel() - err := c.UpdateOrderExecutionLimits(context.Background(), asset.UpsideProfitContract) - assert.ErrorIs(t, err, asset.ErrNotSupported) + err := c.UpdateOrderExecutionLimits(context.Background(), asset.Options) + assert.EqualValues(t, errOptionInvalid, err.Error()) err = c.UpdateOrderExecutionLimits(context.Background(), asset.Spot) assert.NoError(t, err) } @@ -1454,24 +1454,6 @@ func TestFormatExchangeKlineIntervalV3(t *testing.T) { } } -func TestFormatExchangeKlineIntervalV1(t *testing.T) { - t.Parallel() - testSequence := map[kline.Interval]uint32{ - kline.OneMin: 60, - kline.FiveMin: 300, - kline.FifteenMin: 900, - kline.OneHour: 3600, - kline.SixHour: 21600, - kline.OneDay: 86400, - kline.OneWeek: 1<<32 - 1} - for k := range testSequence { - resp := formatExchangeKlineIntervalV1(k) - if resp != testSequence[k] { - t.Errorf(errExpectMismatch, resp, testSequence[k]) - } - } -} - func TestStringToFloatPtr(t *testing.T) { t.Parallel() err := stringToFloatPtr(nil, "") @@ -1510,9 +1492,7 @@ func TestScheduleFuturesSweep(t *testing.T) { } if preCancel { _, err = c.CancelPendingFuturesSweep(context.Background()) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) } _, err = c.ScheduleFuturesSweep(context.Background(), 0.001337) assert.NoError(t, err) @@ -1532,9 +1512,7 @@ func TestCancelPendingFuturesSweep(t *testing.T) { } if !partialSkip { _, err = c.ScheduleFuturesSweep(context.Background(), 0.001337) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) } _, err = c.CancelPendingFuturesSweep(context.Background()) assert.NoError(t, err) @@ -1547,9 +1525,7 @@ func TestWsAuth(t *testing.T) { } var dialer websocket.Dialer err := c.Websocket.Conn.Dial(&dialer, http.Header{}) - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) go c.wsReadData() err = c.Subscribe([]subscription.Subscription{ @@ -1558,9 +1534,7 @@ func TestWsAuth(t *testing.T) { Pair: testPair, }, }) - if err != nil { - t.Error(err) - } + assert.NoError(t, err) timer := time.NewTimer(sharedtestvalues.WebsocketResponseDefaultTimeout) select { case badResponse := <-c.Websocket.DataHandler: @@ -1704,9 +1678,7 @@ func TestGenerateDefaultSubscriptions(t *testing.T) { comparison[i].Asset = asset.Spot } resp, err := c.GenerateDefaultSubscriptions() - if err != nil { - t.Fatal(err) - } + require.NoError(t, err) assert.ElementsMatch(t, comparison, resp) } @@ -1781,13 +1753,9 @@ func portfolioIDFromName(t *testing.T, targetName string) string { createResp, err := c.CreatePortfolio(context.Background(), targetName) var targetID string if err != nil { - if err.Error() != errPortfolioNameDuplicate { - t.Error(err) - } + assert.EqualValues(t, errPortfolioNameDuplicate, err.Error()) getResp, err := c.GetAllPortfolios(context.Background(), "") - if err != nil { - t.Error(err) - } + assert.NoError(t, err) if len(getResp) == 0 { t.Fatal(errExpectedNonEmpty) } diff --git a/exchanges/coinbasepro/coinbasepro_wrapper.go b/exchanges/coinbasepro/coinbasepro_wrapper.go index bf68a9695e2..92fcce12f26 100644 --- a/exchanges/coinbasepro/coinbasepro_wrapper.go +++ b/exchanges/coinbasepro/coinbasepro_wrapper.go @@ -171,52 +171,37 @@ 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 pairData []PairData - unverified, err := c.verificationCheck(ctx) + verified, err := c.verificationCheck(ctx) if err != nil { return nil, err } - switch a { - case asset.Spot: - if unverified { - pairData, err = c.GetAllTradingPairs(ctx, "") - } else { - products, err = c.GetAllProducts(ctx, 2<<30-1, 0, asset.Spot.Upper(), "", "", nil) - } - case asset.Futures: - if unverified { - return nil, fmt.Errorf("%w for %v", errAuthenticationNeeded, asset.Futures) + aString := a.Upper() + if len(aString) == 7 { + aString = aString[:6] + } + if verified { + products, err = c.GetAllProducts(ctx, 2<<30-1, 0, aString, "", "", nil, verified) + if err != nil { + log.Warnf(log.ExchangeSys, warnAuth, err) + verified = false } - products, err = c.fetchFutures(ctx) - default: - err = asset.ErrNotSupported } - if err != nil { - return nil, err + if !verified { + products, err = c.GetAllProducts(ctx, 2<<30-1, 0, aString, "", "", nil, verified) + if err != nil { + return nil, err + } } var pairs []currency.Pair - if unverified { - for i := range pairData { - if pairData[i].TradingDisabled { - continue - } - pair, err := currency.NewPairDelimiter(pairData[i].ID, currency.DashDelimiter) - if err != nil { - return nil, err - } - pairs = append(pairs, pair) + for x := range products.Products { + if products.Products[x].TradingDisabled { + continue } - } else { - for x := range products.Products { - if products.Products[x].TradingDisabled { - continue - } - pair, err := currency.NewPairDelimiter(products.Products[x].ID, currency.DashDelimiter) - if err != nil { - return nil, err - } - pairs = append(pairs, pair) + pair, err := currency.NewPairDelimiter(products.Products[x].ID, currency.DashDelimiter) + if err != nil { + return nil, err } + pairs = append(pairs, pair) } return pairs, nil } @@ -228,10 +213,6 @@ func (c *CoinbasePro) UpdateTradablePairs(ctx context.Context, forceUpdate bool) for i := range assets { pairs, err := c.FetchTradablePairs(ctx, assets[i]) if err != nil { - if errors.Is(err, errAuthenticationNeeded) && assets[i] == asset.Futures { - log.Warnf(log.ExchangeSys, "%v", err) - continue - } return err } err = c.UpdatePairs(pairs, assets[i], false, forceUpdate) @@ -305,19 +286,16 @@ 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 { - unverified, err := c.verificationCheck(ctx) + verified, err := c.verificationCheck(ctx) if err != nil { return err } - if unverified && assetType != asset.Spot { - return fmt.Errorf("%w for %v", errAuthenticationNeeded, assetType) - } products, err := c.GetEnabledPairs(assetType) if err != nil { return err } for x := range products { - err = c.tickerHelper(ctx, products[x].String(), assetType, unverified) + err = c.tickerHelper(ctx, products[x].String(), assetType, verified) if err != nil { return err } @@ -327,18 +305,15 @@ func (c *CoinbasePro) UpdateTickers(ctx context.Context, assetType asset.Item) e // UpdateTicker updates and returns the ticker for a currency pair func (c *CoinbasePro) UpdateTicker(ctx context.Context, p currency.Pair, a asset.Item) (*ticker.Price, error) { - unverified, err := c.verificationCheck(ctx) + verified, err := c.verificationCheck(ctx) if err != nil { return nil, err } - if unverified && a != asset.Spot { - return nil, fmt.Errorf("%w for %v", errAuthenticationNeeded, a) - } fPair, err := c.FormatExchangeCurrency(p, a) if err != nil { return nil, err } - err = c.tickerHelper(ctx, fPair.String(), a, unverified) + err = c.tickerHelper(ctx, fPair.String(), a, verified) if err != nil { return nil, err } @@ -373,7 +348,7 @@ func (c *CoinbasePro) FetchOrderbook(ctx context.Context, p currency.Pair, asset // UpdateOrderbook updates and returns the orderbook for a currency pair func (c *CoinbasePro) UpdateOrderbook(ctx context.Context, p currency.Pair, assetType asset.Item) (*orderbook.Base, error) { - unverified, err := c.verificationCheck(ctx) + verified, err := c.verificationCheck(ctx) if err != nil { return nil, err } @@ -381,9 +356,6 @@ func (c *CoinbasePro) UpdateOrderbook(ctx context.Context, p currency.Pair, asse if err != nil { return nil, err } - if p.IsEmpty() { - return nil, currency.ErrCurrencyPairEmpty - } err = c.CurrencyPairs.IsAssetEnabled(assetType) if err != nil { return nil, err @@ -398,48 +370,32 @@ func (c *CoinbasePro) UpdateOrderbook(ctx context.Context, p currency.Pair, asse Asset: assetType, VerifyOrderbook: c.CanVerifyOrderbook, } - if unverified { - if assetType != asset.Spot { - return nil, fmt.Errorf("%w for %v", errAuthenticationNeeded, assetType) - } - var obN *OrderBook - obN, err = c.GetProductBookV1(ctx, fPair.String(), 2) + var orderbookNew *ProductBook + if verified { + orderbookNew, err = c.GetProductBookV3(ctx, fPair.String(), 1000, true) if err != nil { - return book, err - } - book.Bids = make(orderbook.Tranches, len(obN.Bids)) - for x := range obN.Bids { - book.Bids[x] = orderbook.Tranche{ - Amount: obN.Bids[x].Size, - Price: obN.Bids[x].Price, - } - } - book.Asks = make(orderbook.Tranches, len(obN.Asks)) - for x := range obN.Asks { - book.Asks[x] = orderbook.Tranche{ - Amount: obN.Asks[x].Size, - Price: obN.Asks[x].Price, - } + log.Warnf(log.ExchangeSys, warnAuth, err) + verified = false } - } else { - var orderbookNew *ProductBook - orderbookNew, err = c.GetProductBookV3(ctx, fPair.String(), 1000) + } + if !verified { + orderbookNew, err = c.GetProductBookV3(ctx, fPair.String(), 1000, false) if err != nil { return book, err } - book.Bids = make(orderbook.Tranches, len(orderbookNew.Bids)) - for x := range orderbookNew.Bids { - book.Bids[x] = orderbook.Tranche{ - Amount: orderbookNew.Bids[x].Size, - Price: orderbookNew.Bids[x].Price, - } + } + book.Bids = make(orderbook.Tranches, len(orderbookNew.Bids)) + for x := range orderbookNew.Bids { + book.Bids[x] = orderbook.Tranche{ + Amount: orderbookNew.Bids[x].Size, + Price: orderbookNew.Bids[x].Price, } - book.Asks = make(orderbook.Tranches, len(orderbookNew.Asks)) - for x := range orderbookNew.Asks { - book.Asks[x] = orderbook.Tranche{ - Amount: orderbookNew.Asks[x].Size, - Price: orderbookNew.Asks[x].Price, - } + } + book.Asks = make(orderbook.Tranches, len(orderbookNew.Asks)) + for x := range orderbookNew.Asks { + book.Asks[x] = orderbook.Tranche{ + Amount: orderbookNew.Asks[x].Size, + Price: orderbookNew.Asks[x].Price, } } err = book.Process() @@ -901,14 +857,11 @@ func (c *CoinbasePro) GetHistoricCandles(ctx context.Context, pair currency.Pair if err != nil { return nil, err } - unverified, err := c.verificationCheck(ctx) + verified, err := c.verificationCheck(ctx) if err != nil { return nil, err } - if unverified && a != asset.Spot { - return nil, fmt.Errorf("%w for %v", errAuthenticationNeeded, a) - } - timeSeries, err := c.candleHelper(ctx, req.RequestFormatted.String(), interval, start, end, unverified) + timeSeries, err := c.candleHelper(ctx, req.RequestFormatted.String(), interval, start, end, verified) if err != nil { return nil, err } @@ -921,18 +874,15 @@ func (c *CoinbasePro) GetHistoricCandlesExtended(ctx context.Context, pair curre if err != nil { return nil, err } - unverified, err := c.verificationCheck(ctx) + verified, err := c.verificationCheck(ctx) if err != nil { return nil, err } - if unverified && a != asset.Spot { - return nil, fmt.Errorf("%w for %v", errAuthenticationNeeded, a) - } var timeSeries []kline.Candle for x := range req.RangeHolder.Ranges { hist, err := c.candleHelper(ctx, req.RequestFormatted.String(), interval, req.RangeHolder.Ranges[x].Start.Time.Add(-time.Nanosecond), - req.RangeHolder.Ranges[x].End.Time.Add(-time.Nanosecond), unverified) + req.RangeHolder.Ranges[x].End.Time.Add(-time.Nanosecond), verified) if err != nil { return nil, err } @@ -965,7 +915,11 @@ 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) + verified, err := c.verificationCheck(ctx) + if err != nil { + return nil, err + } + products, err := c.fetchFutures(ctx, verified) if err != nil { return nil, err } @@ -997,7 +951,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) + verified, err := c.verificationCheck(ctx) + if err != nil { + return nil, err + } + products, err := c.fetchFutures(ctx, verified) if err != nil { return nil, err } @@ -1029,67 +987,46 @@ func (c *CoinbasePro) GetFuturesContractDetails(ctx context.Context, item asset. // UpdateOrderExecutionLimits updates order execution limits func (c *CoinbasePro) UpdateOrderExecutionLimits(ctx context.Context, a asset.Item) error { var data *AllProducts - var pairData []PairData - unverified, err := c.verificationCheck(ctx) + verified, err := c.verificationCheck(ctx) if err != nil { return err } - switch a { - case asset.Spot: - if unverified { - pairData, err = c.GetAllTradingPairs(ctx, "") - } else { - data, err = c.GetAllProducts(ctx, 2<<30-1, 0, asset.Spot.Upper(), "", "", nil) - } - case asset.Futures: - if unverified { - return fmt.Errorf("%w for %v", errAuthenticationNeeded, asset.Futures) + aString := a.Upper() + if len(aString) == 7 { + aString = aString[:6] + } + if verified { + data, err = c.GetAllProducts(ctx, 2<<30-1, 0, aString, "", "", nil, true) + if err != nil { + log.Warnf(log.ExchangeSys, warnAuth, err) + verified = false } - data, err = c.fetchFutures(ctx) - default: - err = fmt.Errorf("%w %s", asset.ErrNotSupported, a) } - if err != nil { - return err + if !verified { + data, err = c.GetAllProducts(ctx, 2<<30-1, 0, aString, "", "", nil, false) + if err != nil { + return err + } } - var limits []order.MinMaxLevel - if unverified { - limits = make([]order.MinMaxLevel, len(pairData)) - for i := range pairData { - pair, err := currency.NewPairFromString(pairData[i].ID) - if err != nil { - return err - } - limits[i] = order.MinMaxLevel{ - Pair: pair, - Asset: a, - PriceStepIncrementSize: pairData[i].QuoteIncrement, - AmountStepIncrementSize: pairData[i].BaseIncrement, - QuoteStepIncrementSize: pairData[i].QuoteIncrement, - MaxTotalOrders: 1000, - } + 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 } - } else { - 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, - MinPrice: data.Products[i].QuoteMinSize.Float64(), - MaxPrice: data.Products[i].QuoteMaxSize.Float64(), - PriceStepIncrementSize: data.Products[i].PriceIncrement.Float64(), - MinimumBaseAmount: data.Products[i].BaseMinSize.Float64(), - MaximumBaseAmount: data.Products[i].BaseMaxSize.Float64(), - MinimumQuoteAmount: data.Products[i].QuoteMinSize.Float64(), - MaximumQuoteAmount: data.Products[i].QuoteMaxSize.Float64(), - AmountStepIncrementSize: data.Products[i].BaseIncrement.Float64(), - QuoteStepIncrementSize: data.Products[i].QuoteIncrement.Float64(), - MaxTotalOrders: 1000, - } + limits[i] = order.MinMaxLevel{ + Pair: pair, + Asset: a, + MinPrice: data.Products[i].QuoteMinSize.Float64(), + MaxPrice: data.Products[i].QuoteMaxSize.Float64(), + PriceStepIncrementSize: data.Products[i].PriceIncrement.Float64(), + MinimumBaseAmount: data.Products[i].BaseMinSize.Float64(), + MaximumBaseAmount: data.Products[i].BaseMaxSize.Float64(), + MinimumQuoteAmount: data.Products[i].QuoteMinSize.Float64(), + MaximumQuoteAmount: data.Products[i].QuoteMaxSize.Float64(), + AmountStepIncrementSize: data.Products[i].BaseIncrement.Float64(), + QuoteStepIncrementSize: data.Products[i].QuoteIncrement.Float64(), + MaxTotalOrders: 1000, } } return c.LoadLimits(limits) @@ -1098,13 +1035,19 @@ func (c *CoinbasePro) UpdateOrderExecutionLimits(ctx context.Context, a asset.It // 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) { - products, err := c.GetAllProducts(ctx, 2<<30-1, 0, "FUTURE", "", "", nil) +func (c *CoinbasePro) fetchFutures(ctx context.Context, verified bool) (*AllProducts, error) { + products, err := c.GetAllProducts(ctx, 2<<30-1, 0, "FUTURE", "", "", nil, verified) if err != nil { + if verified { + return c.fetchFutures(ctx, false) + } return nil, err } - products2, err := c.GetAllProducts(ctx, 2<<30-1, 0, "FUTURE", "PERPETUAL", "", nil) + products2, err := c.GetAllProducts(ctx, 2<<30-1, 0, "FUTURE", "PERPETUAL", "", nil, verified) if err != nil { + if verified { + return c.fetchFutures(ctx, false) + } return nil, err } products.Products = append(products.Products, products2.Products...) @@ -1234,26 +1177,6 @@ func formatExchangeKlineIntervalV3(interval kline.Interval) string { return errIntervalNotSupported } -// formatExchangeKlineIntervalV1 is a helper function used in GetHistoricCandles and GetHistoricCandlesExtended -// to convert kline.Interval to the uint format used by V1 of Coinbase's API -func formatExchangeKlineIntervalV1(interval kline.Interval) uint32 { - switch interval { - case kline.OneMin: - return 60 - case kline.FiveMin: - return 300 - case kline.FifteenMin: - return 900 - case kline.OneHour: - return 3600 - case kline.SixHour: - return 21600 - case kline.OneDay: - return 86400 - } - return 1<<32 - 1 -} - // getOrderRespToOrderDetail is a helper function used in GetOrderInfo, GetActiveOrders, and GetOrderHistory // to convert data returned by the Coinbase API into a format suitable for the exchange package func (c *CoinbasePro) getOrderRespToOrderDetail(genOrderDetail *GetOrderResponse, pair currency.Pair, assetItem asset.Item) (*order.Detail, error) { @@ -1411,15 +1334,15 @@ func (c *CoinbasePro) verificationCheck(ctx context.Context) (bool, error) { if err != nil { if errors.Is(err, exchange.ErrAuthenticationSupportNotEnabled) || errors.Is(err, exchange.ErrCredentialsAreEmpty) { - return true, nil + return false, nil } return false, err } - return false, nil + return true, nil } // TickerHelper fetches the ticker for a given currency pair, used by UpdateTickers and UpdateTicker -func (c *CoinbasePro) tickerHelper(ctx context.Context, name string, assetType asset.Item, unverified bool) error { +func (c *CoinbasePro) tickerHelper(ctx context.Context, name string, assetType asset.Item, verified bool) error { pair, err := currency.NewPairDelimiter(name, currency.DashDelimiter) if err != nil { return err @@ -1429,30 +1352,21 @@ func (c *CoinbasePro) tickerHelper(ctx context.Context, name string, assetType a ExchangeName: c.Name, AssetType: assetType, } - if unverified { - var tick *ProductTicker - tick, err = c.GetProductTicker(ctx, name) - if err != nil { - return err - } - newTick.Last = tick.Price - newTick.Volume = tick.Volume - newTick.Bid = tick.Bid - newTick.Ask = tick.Ask - } else { - var ticks *Ticker - ticks, err = c.GetTicker(ctx, name, 1, time.Time{}, time.Time{}) - if err != nil { - return err - } - var last float64 - if len(ticks.Trades) != 0 { - last = ticks.Trades[0].Price + var ticks *Ticker + ticks, err = c.GetTicker(ctx, name, 1, time.Time{}, time.Time{}, verified) + if err != nil { + if verified { + return c.tickerHelper(ctx, name, assetType, false) } - newTick.Last = last - newTick.Bid = ticks.BestBid.Float64() - newTick.Ask = ticks.BestAsk.Float64() + return err + } + var last float64 + if len(ticks.Trades) != 0 { + last = ticks.Trades[0].Price } + newTick.Last = last + newTick.Bid = ticks.BestBid.Float64() + newTick.Ask = ticks.BestAsk.Float64() err = ticker.ProcessTicker(newTick) if err != nil { return err @@ -1460,40 +1374,25 @@ func (c *CoinbasePro) tickerHelper(ctx context.Context, name string, assetType a return nil } -// CandleHelper handles calling the correct candle function, and doing preliminary work on the data -func (c *CoinbasePro) candleHelper(ctx context.Context, pair string, granularity kline.Interval, start, end time.Time, unverified bool) ([]kline.Candle, error) { +// CandleHelper handles calling the candle function, and doing preliminary work on the data +func (c *CoinbasePro) candleHelper(ctx context.Context, pair string, granularity kline.Interval, start, end time.Time, verified bool) ([]kline.Candle, error) { var timeSeries []kline.Candle - if unverified { - hist, err := c.GetProductCandles(ctx, pair, formatExchangeKlineIntervalV1(granularity), start, end) - if err != nil { - return nil, err - } - timeSeries = make([]kline.Candle, len(hist)) - for x := range hist { - timeSeries[x] = kline.Candle{ - Time: hist[x].Time, - Low: hist[x].Low, - High: hist[x].High, - Open: hist[x].Open, - Close: hist[x].Close, - Volume: hist[x].Volume, - } - } - } else { - history, err := c.GetHistoricRates(ctx, pair, formatExchangeKlineIntervalV3(granularity), start, end) - if err != nil { - return nil, err + history, err := c.GetHistoricRates(ctx, pair, formatExchangeKlineIntervalV3(granularity), start, end, verified) + if err != nil { + if verified { + return c.candleHelper(ctx, pair, granularity, start, end, false) } - timeSeries = make([]kline.Candle, len(history)) - for x := range history { - timeSeries[x] = kline.Candle{ - 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 nil, err + } + timeSeries = make([]kline.Candle, len(history)) + for x := range history { + timeSeries[x] = kline.Candle{ + 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 timeSeries, nil From eec8a5db1849c78af194dd46b5014469d3d04e43 Mon Sep 17 00:00:00 2001 From: Samuel Reid <43227667+cranktakular@users.noreply.github.com> Date: Tue, 11 Jun 2024 15:19:32 +1000 Subject: [PATCH 50/79] Additional fixes --- currency/pairs.go | 12 +- exchanges/coinbasepro/coinbasepro.go | 1 - exchanges/coinbasepro/coinbasepro_test.go | 2 +- exchanges/coinbasepro/coinbasepro_types.go | 414 ------------------ .../coinbasepro/coinbasepro_websocket.go | 100 ----- 5 files changed, 8 insertions(+), 521 deletions(-) diff --git a/currency/pairs.go b/currency/pairs.go index cd4d55c9b97..b2a4d5d2052 100644 --- a/currency/pairs.go +++ b/currency/pairs.go @@ -210,12 +210,14 @@ func (p Pairs) Remove(pair Pair) (Pairs, error) { return nil, fmt.Errorf("%s %w", pair, ErrPairNotFound) } -// Add adds a specified pair to the list of pairs if it doesn't exist -func (p Pairs) Add(pair Pair) Pairs { - if p.Contains(pair, true) { - return p +// Add adds specified pairs to the list of pairs if they don't exist +func (p Pairs) Add(pairs ...Pair) Pairs { + for x := range pairs { + if p.Contains(pairs[x], true) { + continue + } + p = append(p, pairs[x]) } - p = append(p, pair) return p } diff --git a/exchanges/coinbasepro/coinbasepro.go b/exchanges/coinbasepro/coinbasepro.go index 7c599173997..c48d70c510f 100644 --- a/exchanges/coinbasepro/coinbasepro.go +++ b/exchanges/coinbasepro/coinbasepro.go @@ -151,7 +151,6 @@ var ( errNoWalletForCurrency = errors.New("no wallet found for currency, address creation impossible") errChannelNameUnknown = errors.New("unknown channel name") errNoWalletsReturned = errors.New("no wallets returned") - errUnknownEndpointLimit = errors.New("unknown endpoint limit") errPayMethodNotFound = errors.New("payment method not found") errUnknownL2DataType = errors.New("unknown l2update data type") errUnknownSide = errors.New("unknown side") diff --git a/exchanges/coinbasepro/coinbasepro_test.go b/exchanges/coinbasepro/coinbasepro_test.go index 164c5aa6442..2a02ae474a6 100644 --- a/exchanges/coinbasepro/coinbasepro_test.go +++ b/exchanges/coinbasepro/coinbasepro_test.go @@ -1675,7 +1675,7 @@ func TestProcessSnapshotUpdate(t *testing.T) { } func TestGenerateDefaultSubscriptions(t *testing.T) { - comparison := []subscription.Subscription{{Channel: "heartbeats"}, {Channel: "status"}, {Channel: "ticker"}, + comparison := subscription.List{{Channel: "heartbeats"}, {Channel: "status"}, {Channel: "ticker"}, {Channel: "ticker_batch"}, {Channel: "candles"}, {Channel: "market_trades"}, {Channel: "level2"}, {Channel: "user"}} for i := range comparison { diff --git a/exchanges/coinbasepro/coinbasepro_types.go b/exchanges/coinbasepro/coinbasepro_types.go index 27caa1fc9d3..836e32e6756 100644 --- a/exchanges/coinbasepro/coinbasepro_types.go +++ b/exchanges/coinbasepro/coinbasepro_types.go @@ -1473,417 +1473,3 @@ type AllWrappedAssets struct { type WrappedAssetConversionRate struct { Amount float64 `json:"amount,string"` } - -// // AccountHolds contains the hold information about an account -// type AccountHolds struct { -// ID string `json:"id"` -// AccountID string `json:"account_id"` -// CreatedAt time.Time `json:"created_at"` -// UpdatedAt string `json:"updated_at"` -// Amount float64 `json:"amount,string"` -// Type string `json:"type"` -// Reference string `json:"ref"` -// } - -// // GeneralizedOrderResponse is the generalized return type across order -// // placement and information collation -// type GeneralizedOrderResponse struct { -// ID string `json:"id"` -// Price float64 `json:"price,string"` -// Size float64 `json:"size,string"` -// ProductID string `json:"product_id"` -// Side string `json:"side"` -// Stp string `json:"stp"` -// Type string `json:"type"` -// TimeInForce string `json:"time_in_force"` -// PostOnly bool `json:"post_only"` -// CreatedAt time.Time `json:"created_at"` -// FillFees float64 `json:"fill_fees,string"` -// FilledSize float64 `json:"filled_size,string"` -// ExecutedValue float64 `json:"executed_value,string"` -// Status string `json:"status"` -// Settled bool `json:"settled"` -// Funds float64 `json:"funds,string"` -// SpecifiedFunds float64 `json:"specified_funds,string"` -// DoneReason string `json:"done_reason"` -// DoneAt time.Time `json:"done_at"` -// } - -// // Funding holds funding data -// type Funding struct { -// ID string `json:"id"` -// OrderID string `json:"order_id"` -// ProfileID string `json:"profile_id"` -// Amount float64 `json:"amount,string"` -// Status string `json:"status"` -// CreatedAt time.Time `json:"created_at"` -// Currency string `json:"currency"` -// RepaidAmount float64 `json:"repaid_amount"` -// DefaultAmount float64 `json:"default_amount,string"` -// RepaidDefault bool `json:"repaid_default"` -// } - -// // MarginTransfer holds margin transfer details -// type MarginTransfer struct { -// CreatedAt time.Time `json:"created_at"` -// ID string `json:"id"` -// UserID string `json:"user_id"` -// ProfileID string `json:"profile_id"` -// MarginProfileID string `json:"margin_profile_id"` -// Type string `json:"type"` -// Amount float64 `json:"amount,string"` -// Currency string `json:"currency"` -// AccountID string `json:"account_id"` -// MarginAccountID string `json:"margin_account_id"` -// MarginProductID string `json:"margin_product_id"` -// Status string `json:"status"` -// Nonce int `json:"nonce"` -// } - -// // AccountOverview holds account information returned from position -// type AccountOverview struct { -// Status string `json:"status"` -// Funding struct { -// MaxFundingValue float64 `json:"max_funding_value,string"` -// FundingValue float64 `json:"funding_value,string"` -// OldestOutstanding struct { -// ID string `json:"id"` -// OrderID string `json:"order_id"` -// CreatedAt time.Time `json:"created_at"` -// Currency string `json:"currency"` -// AccountID string `json:"account_id"` -// Amount float64 `json:"amount,string"` -// } `json:"oldest_outstanding"` -// } `json:"funding"` -// Accounts struct { -// LTC Account `json:"LTC"` -// ETH Account `json:"ETH"` -// USD Account `json:"USD"` -// BTC Account `json:"BTC"` -// } `json:"accounts"` -// MarginCall struct { -// Active bool `json:"active"` -// Price float64 `json:"price,string"` -// Side string `json:"side"` -// Size float64 `json:"size,string"` -// Funds float64 `json:"funds,string"` -// } `json:"margin_call"` -// UserID string `json:"user_id"` -// ProfileID string `json:"profile_id"` -// Position struct { -// Type string `json:"type"` -// Size float64 `json:"size,string"` -// Complement float64 `json:"complement,string"` -// MaxSize float64 `json:"max_size,string"` -// } `json:"position"` -// ProductID string `json:"product_id"` -// } - -// // Account is a sub-type for account overview -// type Account struct { -// ID string `json:"id"` -// Balance float64 `json:"balance,string"` -// Hold float64 `json:"hold,string"` -// FundedAmount float64 `json:"funded_amount,string"` -// DefaultAmount float64 `json:"default_amount,string"` -// } - -// // PaymentMethod holds payment method information -// type PaymentMethod struct { -// ID string `json:"id"` -// Type string `json:"type"` -// Name string `json:"name"` -// Currency string `json:"currency"` -// PrimaryBuy bool `json:"primary_buy"` -// PrimarySell bool `json:"primary_sell"` -// AllowBuy bool `json:"allow_buy"` -// AllowSell bool `json:"allow_sell"` -// AllowDeposits bool `json:"allow_deposits"` -// AllowWithdraw bool `json:"allow_withdraw"` -// Limits struct { -// Buy []LimitInfo `json:"buy"` -// InstantBuy []LimitInfo `json:"instant_buy"` -// Sell []LimitInfo `json:"sell"` -// Deposit []LimitInfo `json:"deposit"` -// } `json:"limits"` -// } - -// // LimitInfo is a sub-type for payment method -// type LimitInfo struct { -// PeriodInDays int `json:"period_in_days"` -// Total struct { -// Amount float64 `json:"amount,string"` -// Currency string `json:"currency"` -// } `json:"total"` -// } - -// // DepositWithdrawalInfo holds returned deposit information -// type DepositWithdrawalInfo struct { -// ID string `json:"id"` -// Amount float64 `json:"amount,string"` -// Currency string `json:"currency"` -// PayoutAt time.Time `json:"payout_at"` -// } - -// // CoinbaseAccounts holds coinbase account information -// type CoinbaseAccounts struct { -// ID string `json:"id"` -// Name string `json:"name"` -// Balance float64 `json:"balance,string"` -// Currency string `json:"currency"` -// Type string `json:"type"` -// Primary bool `json:"primary"` -// Active bool `json:"active"` -// WireDepositInformation struct { -// AccountNumber string `json:"account_number"` -// RoutingNumber string `json:"routing_number"` -// BankName string `json:"bank_name"` -// BankAddress string `json:"bank_address"` -// BankCountry struct { -// Code string `json:"code"` -// Name string `json:"name"` -// } `json:"bank_country"` -// AccountName string `json:"account_name"` -// AccountAddress string `json:"account_address"` -// Reference string `json:"reference"` -// } `json:"wire_deposit_information"` -// SepaDepositInformation struct { -// Iban string `json:"iban"` -// Swift string `json:"swift"` -// BankName string `json:"bank_name"` -// BankAddress string `json:"bank_address"` -// BankCountryName string `json:"bank_country_name"` -// AccountName string `json:"account_name"` -// AccountAddress string `json:"account_address"` -// Reference string `json:"reference"` -// } `json:"sep_deposit_information"` -// } - -// // Report holds historical information -// type Report struct { -// ID string `json:"id"` -// Type string `json:"type"` -// Status string `json:"status"` -// CreatedAt time.Time `json:"created_at"` -// CompletedAt time.Time `json:"completed_at"` -// ExpiresAt time.Time `json:"expires_at"` -// FileURL string `json:"file_url"` -// Params struct { -// StartDate time.Time `json:"start_date"` -// EndDate time.Time `json:"end_date"` -// } `json:"params"` -// } - -// // Volume type contains trailing volume information -// type Volume struct { -// ProductID string `json:"product_id"` -// ExchangeVolume float64 `json:"exchange_volume,string"` -// Volume float64 `json:"volume,string"` -// RecordedAt string `json:"recorded_at"` -// } - -// // OrderL1L2 is a type used in layer conversion -// type OrderL1L2 struct { -// Price float64 -// Amount float64 -// NumOrders float64 -// } - -// // OrderL3 is a type used in layer conversion -// type OrderL3 struct { -// Price float64 -// Amount float64 -// OrderID string -// } - -// // OrderbookL1L2 holds level 1 and 2 order book information -// type OrderbookL1L2 struct { -// Sequence int64 `json:"sequence"` -// Bids []OrderL1L2 `json:"bids"` -// Asks []OrderL1L2 `json:"asks"` -// } - -// // OrderbookL3 holds level 3 order book information -// type OrderbookL3 struct { -// Sequence int64 `json:"sequence"` -// Bids []OrderL3 `json:"bids"` -// Asks []OrderL3 `json:"asks"` -// } - -// // OrderbookResponse is a generalized response for order books -// type OrderbookResponse struct { -// Sequence int64 `json:"sequence"` -// Bids [][3]interface{} `json:"bids"` -// Asks [][3]interface{} `json:"asks"` -// } - -// // FillResponse contains fill information from the exchange -// type FillResponse struct { -// TradeID int64 `json:"trade_id"` -// ProductID string `json:"product_id"` -// Price float64 `json:"price,string"` -// Size float64 `json:"size,string"` -// OrderID string `json:"order_id"` -// CreatedAt time.Time `json:"created_at"` -// Liquidity string `json:"liquidity"` -// Fee float64 `json:"fee,string"` -// Settled bool `json:"settled"` -// Side string `json:"side"` -// } - -// // WebsocketSubscribe takes in subscription information -// type WebsocketSubscribe struct { -// Type string `json:"type"` -// ProductIDs []string `json:"product_ids,omitempty"` -// Channels []any `json:"channels,omitempty"` -// Signature string `json:"signature,omitempty"` -// Key string `json:"key,omitempty"` -// Passphrase string `json:"passphrase,omitempty"` -// Timestamp string `json:"timestamp,omitempty"` -// } - -// // WsChannel defines a websocket subscription channel -// type WsChannel struct { -// Name string `json:"name"` -// ProductIDs []string `json:"product_ids,omitempty"` -// } - -// // wsOrderReceived holds websocket received values -// type wsOrderReceived struct { -// Type string `json:"type"` -// OrderID string `json:"order_id"` -// OrderType string `json:"order_type"` -// Size float64 `json:"size,string"` -// Price float64 `json:"price,omitempty,string"` -// Funds float64 `json:"funds,omitempty,string"` -// Side string `json:"side"` -// ClientOID string `json:"client_oid"` -// ProductID string `json:"product_id"` -// Sequence int64 `json:"sequence"` -// Time time.Time `json:"time"` -// RemainingSize float64 `json:"remaining_size,string"` -// NewSize float64 `json:"new_size,string"` -// OldSize float64 `json:"old_size,string"` -// Reason string `json:"reason"` -// Timestamp float64 `json:"timestamp,string"` -// UserID string `json:"user_id"` -// ProfileID string `json:"profile_id"` -// StopType string `json:"stop_type"` -// StopPrice float64 `json:"stop_price,string"` -// TakerFeeRate float64 `json:"taker_fee_rate,string"` -// Private bool `json:"private"` -// TradeID int64 `json:"trade_id"` -// MakerOrderID string `json:"maker_order_id"` -// TakerOrderID string `json:"taker_order_id"` -// TakerUserID string `json:"taker_user_id"` -// } - -// // WebsocketHeartBeat defines JSON response for a heart beat message -// type WebsocketHeartBeat struct { -// Type string `json:"type"` -// Sequence int64 `json:"sequence"` -// LastTradeID int64 `json:"last_trade_id"` -// ProductID string `json:"product_id"` -// Time string `json:"time"` -// } - -// // WebsocketTicker defines ticker websocket response -// type WebsocketTicker struct { -// Type string `json:"type"` -// Sequence int64 `json:"sequence"` -// ProductID currency.Pair `json:"product_id"` -// Price float64 `json:"price,string"` -// Open24H float64 `json:"open_24h,string"` -// Volume24H float64 `json:"volume_24h,string"` -// Low24H float64 `json:"low_24h,string"` -// High24H float64 `json:"high_24h,string"` -// Volume30D float64 `json:"volume_30d,string"` -// BestBid float64 `json:"best_bid,string"` -// BestAsk float64 `json:"best_ask,string"` -// Side string `json:"side"` -// Time time.Time `json:"time"` -// TradeID int64 `json:"trade_id"` -// LastSize float64 `json:"last_size,string"` -// } - -// // WebsocketOrderbookSnapshot defines a snapshot response -// type WebsocketOrderbookSnapshot struct { -// ProductID string `json:"product_id"` -// Type string `json:"type"` -// Bids [][2]string `json:"bids"` -// Asks [][2]string `json:"asks"` -// Time time.Time `json:"time"` -// } - -// // WebsocketL2Update defines an update on the L2 orderbooks -// type WebsocketL2Update struct { -// Type string `json:"type"` -// ProductID string `json:"product_id"` -// Time time.Time `json:"time"` -// Changes [][3]string `json:"changes"` -// } - -// type wsMsgType struct { -// Type string `json:"type"` -// Sequence int64 `json:"sequence"` -// ProductID string `json:"product_id"` -// } - -// type wsStatus struct { -// Currencies []struct { -// ConvertibleTo []string `json:"convertible_to"` -// Details struct{} `json:"details"` -// ID string `json:"id"` -// MaxPrecision float64 `json:"max_precision,string"` -// MinSize float64 `json:"min_size,string"` -// Name string `json:"name"` -// Status string `json:"status"` -// StatusMessage interface{} `json:"status_message"` -// } `json:"currencies"` -// Products []struct { -// BaseCurrency string `json:"base_currency"` -// BaseIncrement float64 `json:"base_increment,string"` -// BaseMaxSize float64 `json:"base_max_size,string"` -// BaseMinSize float64 `json:"base_min_size,string"` -// CancelOnly bool `json:"cancel_only"` -// DisplayName string `json:"display_name"` -// ID string `json:"id"` -// LimitOnly bool `json:"limit_only"` -// MaxMarketFunds float64 `json:"max_market_funds,string"` -// MinMarketFunds float64 `json:"min_market_funds,string"` -// PostOnly bool `json:"post_only"` -// QuoteCurrency string `json:"quote_currency"` -// QuoteIncrement float64 `json:"quote_increment,string"` -// Status string `json:"status"` -// StatusMessage interface{} `json:"status_message"` -// } `json:"products"` -// Type string `json:"type"` -// } - -// // RequestParamsTimeForceType Time in force -// type RequestParamsTimeForceType string - -// var ( -// // CoinbaseRequestParamsTimeGTC GTC -// CoinbaseRequestParamsTimeGTC = RequestParamsTimeForceType("GTC") - -// // CoinbaseRequestParamsTimeIOC IOC -// CoinbaseRequestParamsTimeIOC = RequestParamsTimeForceType("IOC") -// ) - -// // TransferHistory returns wallet transfer history -// type TransferHistory struct { -// ID string `json:"id"` -// Type string `json:"type"` -// CreatedAt string `json:"created_at"` -// CompletedAt string `json:"completed_at"` -// CanceledAt time.Time `json:"canceled_at"` -// ProcessedAt time.Time `json:"processed_at"` -// UserNonce int64 `json:"user_nonce"` -// Amount string `json:"amount"` -// Details struct { -// CoinbaseAccountID string `json:"coinbase_account_id"` -// CoinbaseTransactionID string `json:"coinbase_transaction_id"` -// CoinbasePaymentMethodID string `json:"coinbase_payment_method_id"` -// } `json:"details"` -// } diff --git a/exchanges/coinbasepro/coinbasepro_websocket.go b/exchanges/coinbasepro/coinbasepro_websocket.go index a0cb70a23ee..7334178aaf4 100644 --- a/exchanges/coinbasepro/coinbasepro_websocket.go +++ b/exchanges/coinbasepro/coinbasepro_websocket.go @@ -326,31 +326,6 @@ func (c *CoinbasePro) generateSubscriptions() (subscription.List, error) { return subscriptions, nil } -// // generateSubscriptions returns a list of subscriptions from the configured subscriptions feature -// func (c *CoinbasePro) generateSubscriptions() (subscription.List, error) { -// pairs, err := c.GetEnabledPairs(asset.Spot) -// if err != nil { -// return nil, err -// } -// pairFmt, err := c.GetPairFormat(asset.Spot, true) -// if err != nil { -// return nil, err -// } -// pairs = pairs.Format(pairFmt) -// authed := c.IsWebsocketAuthenticationSupported() -// subs := make(subscription.List, 0, len(c.Features.Subscriptions)) -// for _, baseSub := range c.Features.Subscriptions { -// if !authed && baseSub.Authenticated { -// continue -// } - -// s := baseSub.Clone() -// s.Asset = asset.Spot -// s.Pairs = pairs -// subs = append(subs, s) -// } -// } - // Subscribe sends a websocket message to receive data from the channel func (c *CoinbasePro) Subscribe(channelsToSubscribe subscription.List) error { chanKeys := make(map[string]currency.Pairs) @@ -368,63 +343,6 @@ func (c *CoinbasePro) Subscribe(channelsToSubscribe subscription.List) error { return nil } -// func (c *CoinbasePro) Subscribe(subs subscription.List) error { -// r := &WebsocketSubscribe{ -// Type: "subscribe", -// Channels: make([]any, 0, len(subs)), -// } -// // See if we have a consistent Pair list for all the subs that we can use globally -// // If all the subs have the same pairs then we can use the top level ProductIDs field -// // Otherwise each and every sub needs to have it's own list -// for i, s := range subs { -// if i == 0 { -// r.ProductIDs = s.Pairs.Strings() -// } else if !subs[0].Pairs.Equal(s.Pairs) { -// r.ProductIDs = nil -// break -// } -// } -// for _, s := range subs { -// if s.Authenticated && r.Key == "" && c.IsWebsocketAuthenticationSupported() { -// if err := c.authWsSubscibeReq(r); err != nil { -// return err -// } -// } -// if len(r.ProductIDs) == 0 { -// r.Channels = append(r.Channels, WsChannel{ -// Name: s.Channel, -// ProductIDs: s.Pairs.Strings(), -// }) -// } else { -// // Coinbase does not support using [WsChannel{Name:"x"}] unless each ProductIDs field is populated -// // Therefore we have to use Channels as an array of strings -// r.Channels = append(r.Channels, s.Channel) -// } -// } -// err := c.Websocket.Conn.SendJSONMessage(r) -// if err == nil { -// err = c.Websocket.AddSuccessfulSubscriptions(subs...) -// } -// return err -// } - -// func (c *CoinbasePro) authWsSubscibeReq(r *WebsocketSubscribe) error { -// creds, err := c.GetCredentials(context.TODO()) -// if err != nil { -// return err -// } -// r.Timestamp = strconv.FormatInt(time.Now().Unix(), 10) -// message := r.Timestamp + http.MethodGet + "/users/self/verify" -// hmac, err := crypto.GetHMAC(crypto.HashSHA256, []byte(message), []byte(creds.Secret)) -// if err != nil { -// return err -// } -// r.Signature = crypto.Base64Encode(hmac) -// r.Key = creds.Key -// r.Passphrase = creds.ClientID -// return nil -// } - // Unsubscribe sends a websocket message to stop receiving data from the channel func (c *CoinbasePro) Unsubscribe(channelsToUnsubscribe subscription.List) error { chanKeys := make(map[string]currency.Pairs) @@ -442,24 +360,6 @@ func (c *CoinbasePro) Unsubscribe(channelsToUnsubscribe subscription.List) error return nil } -// func (c *CoinbasePro) Unsubscribe(subs subscription.List) error { -// r := &WebsocketSubscribe{ -// Type: "unsubscribe", -// Channels: make([]any, 0, len(subs)), -// } -// for _, s := range subs { -// r.Channels = append(r.Channels, WsChannel{ -// Name: s.Channel, -// ProductIDs: s.Pairs.Strings(), -// }) -// } -// err := c.Websocket.Conn.SendJSONMessage(r) -// if err == nil { -// err = c.Websocket.RemoveSubscriptions(subs...) -// } -// return err -// } - // GetJWT checks if the current JWT is valid, returns it if it is, generates a new one if it isn't // Also suitable for use in REST requests, by checking for the presence of a URI, and always generating // a new JWT if one is not provided From bbcc27b358b3226f47eccf63a956bb1fc4c3abbc Mon Sep 17 00:00:00 2001 From: Samuel Reid <43227667+cranktakular@users.noreply.github.com> Date: Tue, 11 Jun 2024 15:46:36 +1000 Subject: [PATCH 51/79] Improvements & Appeasements --- currency/pairs.go | 3 +- exchanges/coinbasepro/coinbasepro.go | 32 ++++++++++++------- exchanges/coinbasepro/coinbasepro_test.go | 5 --- .../coinbasepro/coinbasepro_websocket.go | 1 - exchanges/coinbasepro/coinbasepro_wrapper.go | 4 +-- 5 files changed, 23 insertions(+), 22 deletions(-) diff --git a/currency/pairs.go b/currency/pairs.go index b2a4d5d2052..b9f1637fe73 100644 --- a/currency/pairs.go +++ b/currency/pairs.go @@ -214,9 +214,8 @@ func (p Pairs) Remove(pair Pair) (Pairs, error) { func (p Pairs) Add(pairs ...Pair) Pairs { for x := range pairs { if p.Contains(pairs[x], true) { - continue + p = append(p, pairs[x]) } - p = append(p, pairs[x]) } return p } diff --git a/exchanges/coinbasepro/coinbasepro.go b/exchanges/coinbasepro/coinbasepro.go index c48d70c510f..8ed674463af 100644 --- a/exchanges/coinbasepro/coinbasepro.go +++ b/exchanges/coinbasepro/coinbasepro.go @@ -212,13 +212,15 @@ func (c *CoinbasePro) GetProductBookV3(ctx context.Context, productID string, li vals.Set("limit", strconv.FormatInt(int64(limit), 10)) } var resp ProductBookResponse + var err error if authenticated { - return &resp.Pricebook, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - coinbaseV3+coinbaseProductBook, vals, nil, true, &resp, nil) + err = c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, coinbaseV3+coinbaseProductBook, + vals, nil, true, &resp, nil) } else { path := coinbaseV3 + coinbaseMarket + "/" + coinbaseProductBook - return &resp.Pricebook, c.SendHTTPRequest(ctx, exchange.RestSpot, path, vals, &resp) + err = c.SendHTTPRequest(ctx, exchange.RestSpot, path, vals, &resp) } + return &resp.Pricebook, err } // GetAllProducts returns information on all currency pairs that are available for trading @@ -243,13 +245,15 @@ func (c *CoinbasePro) GetAllProducts(ctx context.Context, limit, offset int32, p vals.Add("product_ids", productIDs[x]) } var products AllProducts + var err error if authenticated { - return &products, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - coinbaseV3+coinbaseProducts, vals, nil, true, &products, nil) + err = c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, coinbaseV3+coinbaseProducts, + vals, nil, true, &products, nil) } else { path := coinbaseV3 + coinbaseMarket + "/" + coinbaseProducts - return &products, c.SendHTTPRequest(ctx, exchange.RestSpot, path, vals, &products) + err = c.SendHTTPRequest(ctx, exchange.RestSpot, path, vals, &products) } + return &products, err } // GetProductByID returns information on a single specified currency pair @@ -258,14 +262,16 @@ func (c *CoinbasePro) GetProductByID(ctx context.Context, productID string, auth return nil, errProductIDEmpty } var resp Product + var err error if authenticated { path := coinbaseV3 + coinbaseProducts + "/" + productID - return &resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - path, nil, nil, true, &resp, nil) + err = c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, nil, true, &resp, + nil) } else { path := coinbaseV3 + coinbaseMarket + "/" + coinbaseProducts + "/" + productID - return &resp, c.SendHTTPRequest(ctx, exchange.RestSpot, path, nil, &resp) + err = c.SendHTTPRequest(ctx, exchange.RestSpot, path, nil, &resp) } + return &resp, err } // GetHistoricRates returns historic rates for a product. Rates are returned in @@ -314,14 +320,16 @@ func (c *CoinbasePro) GetTicker(ctx context.Context, productID string, limit uin vals.Set("end", strconv.FormatInt(endDate.Unix(), 10)) } var resp Ticker + var err error if authenticated { path := coinbaseV3 + coinbaseProducts + "/" + productID + "/" + coinbaseTicker - return &resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - path, vals, nil, true, &resp, nil) + err = c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, vals, nil, true, &resp, + nil) } else { path := coinbaseV3 + coinbaseMarket + "/" + coinbaseProducts + "/" + productID + "/" + coinbaseTicker - return &resp, c.SendHTTPRequest(ctx, exchange.RestSpot, path, vals, &resp) + err = c.SendHTTPRequest(ctx, exchange.RestSpot, path, vals, &resp) } + return &resp, err } // PlaceOrder places either a limit, market, or stop order diff --git a/exchanges/coinbasepro/coinbasepro_test.go b/exchanges/coinbasepro/coinbasepro_test.go index 2a02ae474a6..8e1724d8fe3 100644 --- a/exchanges/coinbasepro/coinbasepro_test.go +++ b/exchanges/coinbasepro/coinbasepro_test.go @@ -1535,11 +1535,6 @@ func TestWsAuth(t *testing.T) { }, }) assert.NoError(t, err) - // require.NoError(t, err, "Dial must not error") - // go c.wsReadData() - - // err = c.Subscribe(subscription.List{{Channel: "user", Pairs: currency.Pairs{testPair}}}) - // require.NoError(t, err, "Subscribe must not error") timer := time.NewTimer(sharedtestvalues.WebsocketResponseDefaultTimeout) select { case badResponse := <-c.Websocket.DataHandler: diff --git a/exchanges/coinbasepro/coinbasepro_websocket.go b/exchanges/coinbasepro/coinbasepro_websocket.go index 7334178aaf4..51198d11162 100644 --- a/exchanges/coinbasepro/coinbasepro_websocket.go +++ b/exchanges/coinbasepro/coinbasepro_websocket.go @@ -321,7 +321,6 @@ func (c *CoinbasePro) generateSubscriptions() (subscription.List, error) { Pairs: enabledPairs, Asset: asset.Spot, }) - } return subscriptions, nil } diff --git a/exchanges/coinbasepro/coinbasepro_wrapper.go b/exchanges/coinbasepro/coinbasepro_wrapper.go index e2fdb03a6d9..fe5f1cb6c25 100644 --- a/exchanges/coinbasepro/coinbasepro_wrapper.go +++ b/exchanges/coinbasepro/coinbasepro_wrapper.go @@ -200,7 +200,7 @@ func (c *CoinbasePro) FetchTradablePairs(ctx context.Context, a asset.Item) (cur return nil, err } } - var pairs []currency.Pair + pairs := make([]currency.Pair, len(products.Products)) for x := range products.Products { if products.Products[x].TradingDisabled { continue @@ -209,7 +209,7 @@ func (c *CoinbasePro) FetchTradablePairs(ctx context.Context, a asset.Item) (cur if err != nil { return nil, err } - pairs = append(pairs, pair) + pairs[x] = pair } return pairs, nil } From e8a06e81c22f5c6082926d0591d0aa0d37ca305b Mon Sep 17 00:00:00 2001 From: Samuel Reid <43227667+cranktakular@users.noreply.github.com> Date: Tue, 11 Jun 2024 16:31:09 +1000 Subject: [PATCH 52/79] LineSaver --- exchanges/coinbasepro/coinbasepro.go | 49 ++++++++++------------------ 1 file changed, 17 insertions(+), 32 deletions(-) diff --git a/exchanges/coinbasepro/coinbasepro.go b/exchanges/coinbasepro/coinbasepro.go index 8ed674463af..0bd3aeb5014 100644 --- a/exchanges/coinbasepro/coinbasepro.go +++ b/exchanges/coinbasepro/coinbasepro.go @@ -212,15 +212,12 @@ func (c *CoinbasePro) GetProductBookV3(ctx context.Context, productID string, li vals.Set("limit", strconv.FormatInt(int64(limit), 10)) } var resp ProductBookResponse - var err error if authenticated { - err = c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, coinbaseV3+coinbaseProductBook, + return &resp.Pricebook, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, coinbaseV3+coinbaseProductBook, vals, nil, true, &resp, nil) - } else { - path := coinbaseV3 + coinbaseMarket + "/" + coinbaseProductBook - err = c.SendHTTPRequest(ctx, exchange.RestSpot, path, vals, &resp) } - return &resp.Pricebook, err + path := coinbaseV3 + coinbaseMarket + "/" + coinbaseProductBook + return &resp.Pricebook, c.SendHTTPRequest(ctx, exchange.RestSpot, path, vals, &resp) } // GetAllProducts returns information on all currency pairs that are available for trading @@ -244,16 +241,13 @@ func (c *CoinbasePro) GetAllProducts(ctx context.Context, limit, offset int32, p for x := range productIDs { vals.Add("product_ids", productIDs[x]) } - var products AllProducts - var err error + var resp AllProducts if authenticated { - err = c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, coinbaseV3+coinbaseProducts, - vals, nil, true, &products, nil) - } else { - path := coinbaseV3 + coinbaseMarket + "/" + coinbaseProducts - err = c.SendHTTPRequest(ctx, exchange.RestSpot, path, vals, &products) + return &resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, coinbaseV3+coinbaseProducts, + vals, nil, true, &resp, nil) } - return &products, err + path := coinbaseV3 + coinbaseMarket + "/" + coinbaseProducts + return &resp, c.SendHTTPRequest(ctx, exchange.RestSpot, path, vals, &resp) } // GetProductByID returns information on a single specified currency pair @@ -262,16 +256,13 @@ func (c *CoinbasePro) GetProductByID(ctx context.Context, productID string, auth return nil, errProductIDEmpty } var resp Product - var err error if authenticated { path := coinbaseV3 + coinbaseProducts + "/" + productID - err = c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, nil, true, &resp, + return &resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, nil, true, &resp, nil) - } else { - path := coinbaseV3 + coinbaseMarket + "/" + coinbaseProducts + "/" + productID - err = c.SendHTTPRequest(ctx, exchange.RestSpot, path, nil, &resp) } - return &resp, err + path := coinbaseV3 + coinbaseMarket + "/" + coinbaseProducts + "/" + productID + return &resp, c.SendHTTPRequest(ctx, exchange.RestSpot, path, nil, &resp) } // GetHistoricRates returns historic rates for a product. Rates are returned in @@ -293,16 +284,13 @@ func (c *CoinbasePro) GetHistoricRates(ctx context.Context, productID, granulari vals.Set("start", strconv.FormatInt(startDate.Unix(), 10)) vals.Set("end", strconv.FormatInt(endDate.Unix(), 10)) vals.Set("granularity", granularity) - var err error if authenticated { path := coinbaseV3 + coinbaseProducts + "/" + productID + "/" + coinbaseCandles - err = c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, + return resp.Candles, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, vals, nil, true, &resp, nil) - } else { - path := coinbaseV3 + coinbaseMarket + "/" + coinbaseProducts + "/" + productID + "/" + coinbaseCandles - err = c.SendHTTPRequest(ctx, exchange.RestSpot, path, vals, &resp) } - return resp.Candles, err + path := coinbaseV3 + coinbaseMarket + "/" + coinbaseProducts + "/" + productID + "/" + coinbaseCandles + return resp.Candles, c.SendHTTPRequest(ctx, exchange.RestSpot, path, vals, &resp) } // GetTicker returns snapshot information about the last trades (ticks) and best bid/ask. @@ -320,16 +308,13 @@ func (c *CoinbasePro) GetTicker(ctx context.Context, productID string, limit uin vals.Set("end", strconv.FormatInt(endDate.Unix(), 10)) } var resp Ticker - var err error if authenticated { path := coinbaseV3 + coinbaseProducts + "/" + productID + "/" + coinbaseTicker - err = c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, vals, nil, true, &resp, + return &resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, vals, nil, true, &resp, nil) - } else { - path := coinbaseV3 + coinbaseMarket + "/" + coinbaseProducts + "/" + productID + "/" + coinbaseTicker - err = c.SendHTTPRequest(ctx, exchange.RestSpot, path, vals, &resp) } - return &resp, err + path := coinbaseV3 + coinbaseMarket + "/" + coinbaseProducts + "/" + productID + "/" + coinbaseTicker + return &resp, c.SendHTTPRequest(ctx, exchange.RestSpot, path, vals, &resp) } // PlaceOrder places either a limit, market, or stop order From d6996318e2a54bcb57fafd157fc0d22b6e335007 Mon Sep 17 00:00:00 2001 From: Samuel Reid <43227667+cranktakular@users.noreply.github.com> Date: Tue, 30 Jul 2024 11:56:15 +1000 Subject: [PATCH 53/79] Additional fixes --- exchanges/coinbasepro/coinbasepro_test.go | 34 +++++++++++++---------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/exchanges/coinbasepro/coinbasepro_test.go b/exchanges/coinbasepro/coinbasepro_test.go index 8e1724d8fe3..6aeadeffa66 100644 --- a/exchanges/coinbasepro/coinbasepro_test.go +++ b/exchanges/coinbasepro/coinbasepro_test.go @@ -559,6 +559,7 @@ func TestCommitConvertTrade(t *testing.T) { } func TestGetConvertTradeByID(t *testing.T) { + c.Verbose = true convertTestShared(t, c.GetConvertTradeByID) } @@ -1137,22 +1138,21 @@ func TestGetWithdrawalsHistory(t *testing.T) { func TestSubmitOrder(t *testing.T) { t.Parallel() _, err := c.SubmitOrder(context.Background(), nil) - assert.ErrorIs(t, err, common.ErrNilPointer) - var ord order.Submit - _, err = c.SubmitOrder(context.Background(), &ord) - assert.ErrorIs(t, err, common.ErrExchangeNameUnset) + assert.ErrorIs(t, err, order.ErrSubmissionIsNil) sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) skipTestIfLowOnFunds(t) - ord.Exchange = c.Name - ord.Pair = testPair - ord.AssetType = asset.Spot - ord.Side = order.Sell - ord.Type = order.StopLimit - ord.StopDirection = order.StopUp - ord.Amount = testAmount - ord.Price = testPrice - ord.RetrieveFees = true - ord.ClientOrderID = strconv.FormatInt(time.Now().UnixMilli(), 18) + "GCTSubmitOrderTest" + ord := order.Submit{ + Exchange: c.Name, + Pair: testPair, + AssetType: asset.Spot, + Side: order.Buy, + Type: order.Market, + StopDirection: order.StopUp, + Amount: testAmount, + Price: testPrice, + RetrieveFees: true, + ClientOrderID: strconv.FormatInt(time.Now().UnixMilli(), 18) + "GCTSubmitOrderTest", + } resp, err := c.SubmitOrder(context.Background(), &ord) assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) @@ -1669,6 +1669,11 @@ func TestProcessSnapshotUpdate(t *testing.T) { assert.NoError(t, err) } +// func TestSilly(t *testing.T) { +// ob := make(map[key.PairAsset]*orderbook.Base) +// book, ok := ob[key.PairAsset{Base: , Quote: , Asset: asset.Spot}] +// } + func TestGenerateDefaultSubscriptions(t *testing.T) { comparison := subscription.List{{Channel: "heartbeats"}, {Channel: "status"}, {Channel: "ticker"}, {Channel: "ticker_batch"}, {Channel: "candles"}, {Channel: "market_trades"}, {Channel: "level2"}, @@ -1911,6 +1916,7 @@ func convertTestShared(t *testing.T, f genConvertTestFunc) { fromAccID, toAccID := convertTestHelper(t) resp, err := c.CreateConvertQuote(context.Background(), fromAccID, toAccID, "", "", 0.01) assert.NoError(t, err) + require.NotNil(t, resp) resp, err = f(context.Background(), resp.Trade.ID, fromAccID, toAccID) assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) From 0f18f8c66027e5eb422f115098b694d90d458dba Mon Sep 17 00:00:00 2001 From: Samuel Reid <43227667+cranktakular@users.noreply.github.com> Date: Tue, 30 Jul 2024 12:46:48 +1000 Subject: [PATCH 54/79] Another fix --- exchanges/coinbasepro/coinbasepro_test.go | 5 ----- exchanges/coinbasepro/coinbasepro_websocket.go | 8 +++++--- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/exchanges/coinbasepro/coinbasepro_test.go b/exchanges/coinbasepro/coinbasepro_test.go index 6aeadeffa66..4e4e03924b7 100644 --- a/exchanges/coinbasepro/coinbasepro_test.go +++ b/exchanges/coinbasepro/coinbasepro_test.go @@ -1669,11 +1669,6 @@ func TestProcessSnapshotUpdate(t *testing.T) { assert.NoError(t, err) } -// func TestSilly(t *testing.T) { -// ob := make(map[key.PairAsset]*orderbook.Base) -// book, ok := ob[key.PairAsset{Base: , Quote: , Asset: asset.Spot}] -// } - func TestGenerateDefaultSubscriptions(t *testing.T) { comparison := subscription.List{{Channel: "heartbeats"}, {Channel: "status"}, {Channel: "ticker"}, {Channel: "ticker_batch"}, {Channel: "candles"}, {Channel: "market_trades"}, {Channel: "level2"}, diff --git a/exchanges/coinbasepro/coinbasepro_websocket.go b/exchanges/coinbasepro/coinbasepro_websocket.go index 51198d11162..459246fc197 100644 --- a/exchanges/coinbasepro/coinbasepro_websocket.go +++ b/exchanges/coinbasepro/coinbasepro_websocket.go @@ -469,9 +469,9 @@ func (c *CoinbasePro) sendRequest(msgType, channel string, productIDs currency.P // processBidAskArray is a helper function that turns WebsocketOrderbookDataHolder into arrays // of bids and asks -func processBidAskArray(data *WebsocketOrderbookDataHolder) (bids, asks []orderbook.Tranche, err error) { - bids = make([]orderbook.Tranche, 0, len(data.Changes)) - asks = make([]orderbook.Tranche, 0, len(data.Changes)) +func processBidAskArray(data *WebsocketOrderbookDataHolder) (bids, asks orderbook.Tranches, err error) { + bids = make(orderbook.Tranches, 0, len(data.Changes)) + asks = make(orderbook.Tranches, 0, len(data.Changes)) for i := range data.Changes { change := orderbook.Tranche{Price: data.Changes[i].PriceLevel, Amount: data.Changes[i].NewQuantity} switch data.Changes[i].Side { @@ -483,6 +483,8 @@ func processBidAskArray(data *WebsocketOrderbookDataHolder) (bids, asks []orderb return nil, nil, fmt.Errorf("%w %v", errUnknownSide, data.Changes[i].Side) } } + bids.SortBids() + asks.SortAsks() return bids, asks, nil } From c0aa9a48852caae9dc83119b999fff4a06261a64 Mon Sep 17 00:00:00 2001 From: Samuel Reid <43227667+cranktakular@users.noreply.github.com> Date: Wed, 7 Aug 2024 12:14:26 +1000 Subject: [PATCH 55/79] Fixing picked nits --- exchanges/coinbasepro/coinbasepro.go | 105 +++++----- exchanges/coinbasepro/coinbasepro_test.go | 12 +- exchanges/coinbasepro/coinbasepro_types.go | 76 +++---- .../coinbasepro/coinbasepro_websocket.go | 1 - exchanges/coinbasepro/coinbasepro_wrapper.go | 191 +++++++----------- 5 files changed, 167 insertions(+), 218 deletions(-) diff --git a/exchanges/coinbasepro/coinbasepro.go b/exchanges/coinbasepro/coinbasepro.go index 0bd3aeb5014..ecf31ac6cfc 100644 --- a/exchanges/coinbasepro/coinbasepro.go +++ b/exchanges/coinbasepro/coinbasepro.go @@ -21,6 +21,7 @@ import ( exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/request" + "github.com/thrasher-corp/gocryptotrader/types" ) const ( @@ -123,45 +124,45 @@ const ( ) var ( - errAccountIDEmpty = errors.New("account id cannot be empty") - errClientOrderIDEmpty = errors.New("client order id cannot be empty") - errProductIDEmpty = errors.New("product id cannot be empty") - errOrderIDEmpty = errors.New("order ids cannot be empty") - errOpenPairWithOtherTypes = errors.New("cannot pair open orders with other order types") - errSizeAndPriceZero = errors.New("size and price cannot both be 0") - errCurrencyEmpty = errors.New("currency cannot be empty") - errCurrWalletConflict = errors.New("exactly one of walletID and currency must be specified") - errWalletIDEmpty = errors.New("wallet id cannot be empty") - errAddressIDEmpty = errors.New("address id cannot be empty") - errTransactionTypeEmpty = errors.New("transaction type cannot be empty") - errToEmpty = errors.New("to cannot be empty") - errAmountEmpty = errors.New("amount cannot be empty") - errTransactionIDEmpty = errors.New("transaction id cannot be empty") - errPaymentMethodEmpty = errors.New("payment method cannot be empty") - errDepositIDEmpty = errors.New("deposit id cannot be empty") - errInvalidPriceType = errors.New("price type must be spot, buy, or sell") - errInvalidOrderType = errors.New("order type must be market, limit, or stop") - errNoMatchingWallets = errors.New("no matching wallets returned") - errOrderModFailNoRet = errors.New("order modification failed but no error returned") - errPointerNil = errors.New("relevant pointer is nil") - errNameEmpty = errors.New("name cannot be empty") - errPortfolioIDEmpty = errors.New("portfolio id cannot be empty") - errFeeTypeNotSupported = errors.New("fee type not supported") - errCantDecodePrivKey = errors.New("cannot decode private key") - errNoWalletForCurrency = errors.New("no wallet found for currency, address creation impossible") - errChannelNameUnknown = errors.New("unknown channel name") - errNoWalletsReturned = errors.New("no wallets returned") - errPayMethodNotFound = errors.New("payment method not found") - errUnknownL2DataType = errors.New("unknown l2update data type") - errUnknownSide = errors.New("unknown side") - errInvalidGranularity = errors.New("invalid granularity") - errOrderFailedToCancel = errors.New("failed to cancel order") - errUnrecognisedStatusType = errors.New("unrecognised status type") - errPairEmpty = errors.New("pair cannot be empty") - errStringConvert = errors.New("unable to convert into string value") - errFloatConvert = errors.New("unable to convert into float64 value") - errNoCredsUser = errors.New("no credentials when attempting to subscribe to authenticated channel user") - errWrappedAssetEmpty = errors.New("wrapped asset cannot be empty") + errAccountIDEmpty = errors.New("account id cannot be empty") + errClientOrderIDEmpty = errors.New("client order id cannot be empty") + errProductIDEmpty = errors.New("product id cannot be empty") + errOrderIDEmpty = errors.New("order ids cannot be empty") + errOpenPairWithOtherTypes = errors.New("cannot pair open orders with other order types") + errSizeAndPriceZero = errors.New("size and price cannot both be 0") + errCurrencyEmpty = errors.New("currency cannot be empty") + errCurrWalletConflict = errors.New("exactly one of walletID and currency must be specified") + errWalletIDEmpty = errors.New("wallet id cannot be empty") + errAddressIDEmpty = errors.New("address id cannot be empty") + errTransactionTypeEmpty = errors.New("transaction type cannot be empty") + errToEmpty = errors.New("to cannot be empty") + errAmountEmpty = errors.New("amount cannot be empty") + errTransactionIDEmpty = errors.New("transaction id cannot be empty") + errPaymentMethodEmpty = errors.New("payment method cannot be empty") + errDepositIDEmpty = errors.New("deposit id cannot be empty") + errInvalidPriceType = errors.New("price type must be spot, buy, or sell") + errInvalidOrderType = errors.New("order type must be market, limit, or stop") + errNoMatchingWallets = errors.New("no matching wallets returned") + errOrderModFailNoRet = errors.New("order modification failed but no error returned") + errNameEmpty = errors.New("name cannot be empty") + errPortfolioIDEmpty = errors.New("portfolio id cannot be empty") + errFeeTypeNotSupported = errors.New("fee type not supported") + errCantDecodePrivKey = errors.New("cannot decode private key") + errNoWalletForCurrency = errors.New("no wallet found for currency, address creation impossible") + errChannelNameUnknown = errors.New("unknown channel name") + errNoWalletsReturned = errors.New("no wallets returned") + errPayMethodNotFound = errors.New("payment method not found") + errUnknownL2DataType = errors.New("unknown l2update data type") + errUnknownSide = errors.New("unknown side") + errInvalidGranularity = errors.New("invalid granularity") + errOrderFailedToCancel = errors.New("failed to cancel order") + errUnrecognisedStatusType = errors.New("unrecognised status type") + errPairEmpty = errors.New("pair cannot be empty") + errStringConvert = errors.New("unable to convert into string value") + errFloatConvert = errors.New("unable to convert into float64 value") + errNoCredsUser = errors.New("no credentials when attempting to subscribe to authenticated channel user") + errWrappedAssetEmpty = errors.New("wrapped asset cannot be empty") + errExpectedOneTickerReturned = errors.New("expected one ticker to be returned") ) // GetAllAccounts returns information on all trading accounts associated with the API key @@ -1629,38 +1630,36 @@ func prepareOrderConfig(orderType, side, stopDirection string, amount, limitPric case order.Market.String(), order.ImmediateOrCancel.String(): orderConfig.MarketMarketIOC = &MarketMarketIOC{} if side == order.Buy.String() { - orderConfig.MarketMarketIOC.QuoteSize = strconv.FormatFloat(amount, 'f', -1, 64) + orderConfig.MarketMarketIOC.QuoteSize = types.Number(amount) } if side == order.Sell.String() { - orderConfig.MarketMarketIOC.BaseSize = strconv.FormatFloat(amount, 'f', -1, 64) + orderConfig.MarketMarketIOC.BaseSize = types.Number(amount) } case order.Limit.String(): if endTime == (time.Time{}) { orderConfig.LimitLimitGTC = &LimitLimitGTC{} - orderConfig.LimitLimitGTC.BaseSize = strconv.FormatFloat(amount, 'f', -1, 64) - orderConfig.LimitLimitGTC.LimitPrice = strconv.FormatFloat(limitPrice, 'f', -1, 64) + orderConfig.LimitLimitGTC.BaseSize = types.Number(amount) + orderConfig.LimitLimitGTC.LimitPrice = types.Number(limitPrice) orderConfig.LimitLimitGTC.PostOnly = postOnly } else { orderConfig.LimitLimitGTD = &LimitLimitGTD{} - orderConfig.LimitLimitGTD.BaseSize = strconv.FormatFloat(amount, 'f', -1, 64) - orderConfig.LimitLimitGTD.LimitPrice = strconv.FormatFloat(limitPrice, 'f', -1, 64) + orderConfig.LimitLimitGTD.BaseSize = types.Number(amount) + orderConfig.LimitLimitGTD.LimitPrice = types.Number(limitPrice) orderConfig.LimitLimitGTD.PostOnly = postOnly orderConfig.LimitLimitGTD.EndTime = endTime } case order.StopLimit.String(): if endTime == (time.Time{}) { orderConfig.StopLimitStopLimitGTC = &StopLimitStopLimitGTC{} - orderConfig.StopLimitStopLimitGTC.BaseSize = strconv.FormatFloat(amount, 'f', -1, 64) - orderConfig.StopLimitStopLimitGTC.LimitPrice = strconv.FormatFloat(limitPrice, 'f', -1, - 64) - orderConfig.StopLimitStopLimitGTC.StopPrice = strconv.FormatFloat(stopPrice, 'f', -1, 64) + orderConfig.StopLimitStopLimitGTC.BaseSize = types.Number(amount) + orderConfig.StopLimitStopLimitGTC.LimitPrice = types.Number(limitPrice) + orderConfig.StopLimitStopLimitGTC.StopPrice = types.Number(stopPrice) orderConfig.StopLimitStopLimitGTC.StopDirection = stopDirection } else { orderConfig.StopLimitStopLimitGTD = &StopLimitStopLimitGTD{} - orderConfig.StopLimitStopLimitGTD.BaseSize = strconv.FormatFloat(amount, 'f', -1, 64) - orderConfig.StopLimitStopLimitGTD.LimitPrice = strconv.FormatFloat(limitPrice, 'f', -1, - 64) - orderConfig.StopLimitStopLimitGTD.StopPrice = strconv.FormatFloat(stopPrice, 'f', -1, 64) + orderConfig.StopLimitStopLimitGTD.BaseSize = types.Number(amount) + orderConfig.StopLimitStopLimitGTD.LimitPrice = types.Number(limitPrice) + orderConfig.StopLimitStopLimitGTD.StopPrice = types.Number(stopPrice) orderConfig.StopLimitStopLimitGTD.StopDirection = stopDirection orderConfig.StopLimitStopLimitGTD.EndTime = endTime } diff --git a/exchanges/coinbasepro/coinbasepro_test.go b/exchanges/coinbasepro/coinbasepro_test.go index 4e4e03924b7..d64e478087f 100644 --- a/exchanges/coinbasepro/coinbasepro_test.go +++ b/exchanges/coinbasepro/coinbasepro_test.go @@ -1382,6 +1382,7 @@ func TestGetLatestFundingRates(t *testing.T) { func TestGetFuturesContractDetails(t *testing.T) { t.Parallel() + c.Verbose = true _, err := c.GetFuturesContractDetails(context.Background(), asset.Empty) assert.ErrorIs(t, err, futures.ErrNotFuturesAsset) _, err = c.GetFuturesContractDetails(context.Background(), asset.UpsideProfitContract) @@ -1454,17 +1455,6 @@ func TestFormatExchangeKlineIntervalV3(t *testing.T) { } } -func TestStringToFloatPtr(t *testing.T) { - t.Parallel() - err := stringToFloatPtr(nil, "") - assert.ErrorIs(t, err, errPointerNil) - var fl float64 - err = stringToFloatPtr(&fl, "") - assert.NoError(t, err) - err = stringToFloatPtr(&fl, "1.1") - assert.NoError(t, err) -} - func TestGetCurrencyTradeURL(t *testing.T) { t.Parallel() testexch.UpdatePairsOnce(t, c) diff --git a/exchanges/coinbasepro/coinbasepro_types.go b/exchanges/coinbasepro/coinbasepro_types.go index 836e32e6756..922eaf777d7 100644 --- a/exchanges/coinbasepro/coinbasepro_types.go +++ b/exchanges/coinbasepro/coinbasepro_types.go @@ -125,19 +125,23 @@ type Product struct { QuoteCurrencyID string `json:"quote_currency_id"` BaseCurrencyID string `json:"base_currency_id"` FCMTradingSessionDetails struct { - IsSessionOpen bool `json:"is_session_open"` - OpenTime time.Time `json:"open_time"` - CloseTime time.Time `json:"close_time"` + IsSessionOpen bool `json:"is_session_open"` + OpenTime time.Time `json:"open_time"` + CloseTime time.Time `json:"close_time"` + SessionState string `json:"session_state"` + AfterHoursOrderEntryDisabled bool `json:"after_hours_order_entry_disabled"` } `json:"fcm_trading_session_details"` - MidMarketPrice types.Number `json:"mid_market_price"` - Alias string `json:"alias"` - AliasTo []string `json:"alias_to"` - BaseDisplaySymbol string `json:"base_display_symbol"` - QuoteDisplaySymbol string `json:"quote_display_symbol"` - ViewOnly bool `json:"view_only"` - PriceIncrement types.Number `json:"price_increment"` - DisplayName string `json:"display_name"` - FutureProductDetails struct { + MidMarketPrice types.Number `json:"mid_market_price"` + Alias string `json:"alias"` + AliasTo []string `json:"alias_to"` + BaseDisplaySymbol string `json:"base_display_symbol"` + QuoteDisplaySymbol string `json:"quote_display_symbol"` + ViewOnly bool `json:"view_only"` + PriceIncrement types.Number `json:"price_increment"` + DisplayName string `json:"display_name"` + ProductVenue string `json:"product_venue"` + ApproximateQuote24HVolume types.Number `json:"approximate_quote_24h_volume"` + FutureProductDetails struct { Venue string `json:"venue"` ContractCode string `json:"contract_code"` ContractExpiry time.Time `json:"contract_expiry"` @@ -149,12 +153,16 @@ type Product struct { RiskManagedBy string `json:"risk_managed_by"` ContractExpiryType string `json:"contract_expiry_type"` PerpetualDetails struct { - OpenInterest types.Number `json:"open_interest"` - FundingRate types.Number `json:"funding_rate"` - FundingTime time.Time `json:"funding_time"` - MaxLeverage types.Number `json:"max_leverage"` + OpenInterest types.Number `json:"open_interest"` + FundingRate types.Number `json:"funding_rate"` + FundingTime time.Time `json:"funding_time"` + MaxLeverage types.Number `json:"max_leverage"` + BaseAssetUUID uuid.UUID `json:"base_asset_uuid"` } `json:"perpetual_details"` ContractDisplayName string `json:"contract_display_name"` + TimeToExpiryMS uint64 `json:"time_to_expiry_ms,string"` + NonCrypto bool `json:"non_crypto"` + ContractExpiryName string `json:"contract_expiry_name"` } `json:"future_product_details"` } @@ -203,40 +211,40 @@ type Ticker struct { // MarketMarketIOC is a sub-struct used in the type OrderConfiguration type MarketMarketIOC struct { - QuoteSize string `json:"quote_size,omitempty"` - BaseSize string `json:"base_size,omitempty"` + QuoteSize types.Number `json:"quote_size,omitempty"` + BaseSize types.Number `json:"base_size,omitempty"` } // LimitLimitGTC is a sub-struct used in the type OrderConfiguration type LimitLimitGTC struct { - BaseSize string `json:"base_size"` - LimitPrice string `json:"limit_price"` - PostOnly bool `json:"post_only"` + BaseSize types.Number `json:"base_size"` + LimitPrice types.Number `json:"limit_price"` + PostOnly bool `json:"post_only"` } // LimitLimitGTD is a sub-struct used in the type OrderConfiguration type LimitLimitGTD struct { - BaseSize string `json:"base_size"` - LimitPrice string `json:"limit_price"` - EndTime time.Time `json:"end_time"` - PostOnly bool `json:"post_only"` + BaseSize types.Number `json:"base_size"` + LimitPrice types.Number `json:"limit_price"` + EndTime time.Time `json:"end_time"` + PostOnly bool `json:"post_only"` } // StopLimitStopLimitGTC is a sub-struct used in the type OrderConfiguration type StopLimitStopLimitGTC struct { - BaseSize string `json:"base_size"` - LimitPrice string `json:"limit_price"` - StopPrice string `json:"stop_price"` - StopDirection string `json:"stop_direction"` + BaseSize types.Number `json:"base_size"` + LimitPrice types.Number `json:"limit_price"` + StopPrice types.Number `json:"stop_price"` + StopDirection string `json:"stop_direction"` } // StopLimitStopLimitGTD is a sub-struct used in the type OrderConfiguration type StopLimitStopLimitGTD struct { - BaseSize string `json:"base_size"` - LimitPrice string `json:"limit_price"` - StopPrice string `json:"stop_price"` - EndTime time.Time `json:"end_time"` - StopDirection string `json:"stop_direction"` + BaseSize types.Number `json:"base_size"` + LimitPrice types.Number `json:"limit_price"` + StopPrice types.Number `json:"stop_price"` + EndTime time.Time `json:"end_time"` + StopDirection string `json:"stop_direction"` } // OrderConfiguration is a struct used in the formation of requests in PrepareOrderConfig, and is diff --git a/exchanges/coinbasepro/coinbasepro_websocket.go b/exchanges/coinbasepro/coinbasepro_websocket.go index 459246fc197..f6b82f57099 100644 --- a/exchanges/coinbasepro/coinbasepro_websocket.go +++ b/exchanges/coinbasepro/coinbasepro_websocket.go @@ -308,7 +308,6 @@ func (c *CoinbasePro) generateSubscriptions() (subscription.List, error) { "candles", "market_trades", "level2", - "user", } enabledPairs, err := c.GetEnabledPairs(asset.Spot) if err != nil { diff --git a/exchanges/coinbasepro/coinbasepro_wrapper.go b/exchanges/coinbasepro/coinbasepro_wrapper.go index 4d0751946c9..8433ac9a186 100644 --- a/exchanges/coinbasepro/coinbasepro_wrapper.go +++ b/exchanges/coinbasepro/coinbasepro_wrapper.go @@ -608,10 +608,38 @@ func (c *CoinbasePro) CancelOrder(ctx context.Context, o *order.Cancel) error { // CancelBatchOrders cancels orders by their corresponding ID numbers func (c *CoinbasePro) CancelBatchOrders(ctx context.Context, o []order.Cancel) (*order.CancelBatchResponse, error) { var status order.CancelBatchResponse - var err error - status.Status, _, err = c.cancelOrdersReturnMapAndCount(ctx, o) - if err != nil { - return nil, err + ordToCancel := len(o) + if ordToCancel == 0 { + return nil, errOrderIDEmpty + } + status.Status = make(map[string]string) + ordIDSlice := make([]string, ordToCancel) + for i := range o { + err := o[i].Validate(o[i].StandardCancel()) + if err != nil { + return nil, err + } + ordIDSlice[i] = o[i].OrderID + status.Status[o[i].OrderID] = "Failed to cancel" + } + var resp CancelOrderResp + for i := 0; i < ordToCancel; i += 100 { + var tempOrdIDSlice []string + if ordToCancel-i < 100 { + tempOrdIDSlice = ordIDSlice[i:] + } else { + tempOrdIDSlice = ordIDSlice[i : i+100] + } + tempResp, err := c.CancelOrders(ctx, tempOrdIDSlice) + if err != nil { + return nil, err + } + resp.Results = append(resp.Results, tempResp...) + } + for i := range resp.Results { + if resp.Results[i].Success { + status.Status[resp.Results[i].OrderID] = order.Cancelled.String() + } } return &status, nil } @@ -924,12 +952,12 @@ func (c *CoinbasePro) GetLatestFundingRates(ctx context.Context, r *fundingrate. if err != nil { return nil, err } - products, err := c.fetchFutures(ctx, verified) + products, perpStart, err := c.fetchFutures(ctx, verified) if err != nil { return nil, err } funding := make([]fundingrate.LatestRateResponse, len(products.Products)) - for i := range products.Products { + for i := perpStart; i < len(products.Products); i++ { pair, err := currency.NewPairFromString(products.Products[i].ID) if err != nil { return nil, err @@ -960,7 +988,7 @@ func (c *CoinbasePro) GetFuturesContractDetails(ctx context.Context, item asset. if err != nil { return nil, err } - products, err := c.fetchFutures(ctx, verified) + products, perpStart, err := c.fetchFutures(ctx, verified) if err != nil { return nil, err } @@ -980,11 +1008,15 @@ func (c *CoinbasePro) GetFuturesContractDetails(ctx context.Context, item asset. EndDate: products.Products[i].FutureProductDetails.ContractExpiry, IsActive: !products.Products[i].IsDisabled, Status: products.Products[i].Status, - Type: futures.LongDated, SettlementCurrencies: []currency.Code{currency.NewCode(products.Products[i].QuoteCurrencyID)}, Multiplier: products.Products[i].BaseIncrement.Float64(), LatestRate: funRate, } + if i < perpStart { + contracts[i].Type = futures.LongDated + } else { + contracts[i].Type = futures.Perpetual + } } return contracts, nil } @@ -1040,64 +1072,24 @@ func (c *CoinbasePro) UpdateOrderExecutionLimits(ctx context.Context, a asset.It // 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, verified bool) (*AllProducts, error) { +func (c *CoinbasePro) fetchFutures(ctx context.Context, verified bool) (*AllProducts, int, error) { products, err := c.GetAllProducts(ctx, 2<<30-1, 0, "FUTURE", "", "", nil, verified) if err != nil { if verified { return c.fetchFutures(ctx, false) } - return nil, err + return nil, 0, err } products2, err := c.GetAllProducts(ctx, 2<<30-1, 0, "FUTURE", "PERPETUAL", "", nil, verified) if err != nil { if verified { return c.fetchFutures(ctx, false) } - return nil, err + return nil, 0, err } + perpStart := len(products.Products) products.Products = append(products.Products, products2.Products...) - return products, nil -} - -// cancelOrdersReturnMapAndCount is a helper function for CancelBatchOrders, calling the appropriate Coinbase -// endpoint, and returning useful information -func (c *CoinbasePro) cancelOrdersReturnMapAndCount(ctx context.Context, o []order.Cancel) (status map[string]string, count int64, err error) { - ordToCancel := len(o) - if ordToCancel == 0 { - return nil, 0, errOrderIDEmpty - } - status = make(map[string]string) - ordIDSlice := make([]string, ordToCancel) - for i := range o { - err := o[i].Validate(o[i].StandardCancel()) - if err != nil { - return nil, 0, err - } - 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 { - tempOrdIDSlice = ordIDSlice[i:] - } else { - tempOrdIDSlice = ordIDSlice[i : i+100] - } - tempResp, err := c.CancelOrders(ctx, tempOrdIDSlice) - if err != nil { - return nil, 0, err - } - resp.Results = append(resp.Results, tempResp...) - } - var counter int64 - for i := range resp.Results { - if resp.Results[i].Success { - status[resp.Results[i].OrderID] = order.Cancelled.String() - counter++ - } - } - return status, counter, nil + return products, perpStart, nil } // processFundingData is a helper function for GetAccountFundingHistory and GetWithdrawalsHistory, @@ -1142,9 +1134,18 @@ func (c *CoinbasePro) processFundingData(accHistory []DeposWithdrData, cryptoHis // iterativeGetAllOrders is a helper function used in GetActiveOrders and GetOrderHistory // to repeatedly call GetAllOrders until all orders have been retrieved func (c *CoinbasePro) iterativeGetAllOrders(ctx context.Context, productID, orderType, orderSide, productType string, orderStatus []string, limit int32, startDate, endDate time.Time) ([]GetOrderResponse, error) { - var hasNext bool + hasNext := true var resp []GetOrderResponse var cursor string + if orderSide == "ANY" { + orderSide = "" + } + if orderType == "ANY" { + orderType = "" + } + if productType == "FUTURES" { + productType = "FUTURE" + } for hasNext { interResp, err := c.GetAllOrders(ctx, productID, "", orderType, orderSide, cursor, productType, "", "", "", orderStatus, nil, limit, startDate, endDate) @@ -1188,73 +1189,36 @@ func (c *CoinbasePro) getOrderRespToOrderDetail(genOrderDetail *GetOrderResponse var amount float64 var quoteAmount float64 var orderType order.Type - var err error if genOrderDetail.OrderConfiguration.MarketMarketIOC != nil { - err = stringToFloatPtr("eAmount, genOrderDetail.OrderConfiguration.MarketMarketIOC.QuoteSize) - if err != nil { - return nil, err - } - err = stringToFloatPtr(&amount, genOrderDetail.OrderConfiguration.MarketMarketIOC.BaseSize) - if err != nil { - return nil, err - } + quoteAmount = genOrderDetail.OrderConfiguration.MarketMarketIOC.QuoteSize.Float64() + amount = genOrderDetail.OrderConfiguration.MarketMarketIOC.BaseSize.Float64() orderType = order.Market } var price float64 var postOnly bool if genOrderDetail.OrderConfiguration.LimitLimitGTC != nil { - err = stringToFloatPtr(&amount, genOrderDetail.OrderConfiguration.LimitLimitGTC.BaseSize) - if err != nil { - return nil, err - } - err = stringToFloatPtr(&price, genOrderDetail.OrderConfiguration.LimitLimitGTC.LimitPrice) - if err != nil { - return nil, err - } + amount = genOrderDetail.OrderConfiguration.LimitLimitGTC.BaseSize.Float64() + price = genOrderDetail.OrderConfiguration.LimitLimitGTC.LimitPrice.Float64() postOnly = genOrderDetail.OrderConfiguration.LimitLimitGTC.PostOnly orderType = order.Limit } if genOrderDetail.OrderConfiguration.LimitLimitGTD != nil { - err = stringToFloatPtr(&amount, genOrderDetail.OrderConfiguration.LimitLimitGTD.BaseSize) - if err != nil { - return nil, err - } - err = stringToFloatPtr(&price, genOrderDetail.OrderConfiguration.LimitLimitGTD.LimitPrice) - if err != nil { - return nil, err - } + amount = genOrderDetail.OrderConfiguration.LimitLimitGTD.BaseSize.Float64() + price = genOrderDetail.OrderConfiguration.LimitLimitGTD.LimitPrice.Float64() postOnly = genOrderDetail.OrderConfiguration.LimitLimitGTD.PostOnly orderType = order.Limit } var triggerPrice float64 if genOrderDetail.OrderConfiguration.StopLimitStopLimitGTC != nil { - err = stringToFloatPtr(&amount, genOrderDetail.OrderConfiguration.StopLimitStopLimitGTC.BaseSize) - if err != nil { - return nil, err - } - err = stringToFloatPtr(&price, genOrderDetail.OrderConfiguration.StopLimitStopLimitGTC.LimitPrice) - if err != nil { - return nil, err - } - err = stringToFloatPtr(&triggerPrice, genOrderDetail.OrderConfiguration.StopLimitStopLimitGTC.StopPrice) - if err != nil { - return nil, err - } + amount = genOrderDetail.OrderConfiguration.StopLimitStopLimitGTC.BaseSize.Float64() + price = genOrderDetail.OrderConfiguration.StopLimitStopLimitGTC.LimitPrice.Float64() + triggerPrice = genOrderDetail.OrderConfiguration.StopLimitStopLimitGTC.StopPrice.Float64() orderType = order.StopLimit } if genOrderDetail.OrderConfiguration.StopLimitStopLimitGTD != nil { - err = stringToFloatPtr(&amount, genOrderDetail.OrderConfiguration.StopLimitStopLimitGTD.BaseSize) - if err != nil { - return nil, err - } - err = stringToFloatPtr(&price, genOrderDetail.OrderConfiguration.StopLimitStopLimitGTD.LimitPrice) - if err != nil { - return nil, err - } - err = stringToFloatPtr(&triggerPrice, genOrderDetail.OrderConfiguration.StopLimitStopLimitGTD.StopPrice) - if err != nil { - return nil, err - } + amount = genOrderDetail.OrderConfiguration.StopLimitStopLimitGTD.BaseSize.Float64() + price = genOrderDetail.OrderConfiguration.StopLimitStopLimitGTD.LimitPrice.Float64() + triggerPrice = genOrderDetail.OrderConfiguration.StopLimitStopLimitGTD.StopPrice.Float64() orderType = order.StopLimit } var remainingAmount float64 @@ -1319,20 +1283,6 @@ func (c *CoinbasePro) getOrderRespToOrderDetail(genOrderDetail *GetOrderResponse return &response, nil } -// stringToFloatPtr essentially calls ParseFloat, but leaves the float alone instead of erroring out -// if the string is empty. -func stringToFloatPtr(outgoing *float64, incoming string) error { - if outgoing == nil { - return errPointerNil - } - var err error - if incoming != "" { - *outgoing, err = strconv.ParseFloat(incoming, 64) - return err - } - return nil -} - // VerificationCheck returns whether authentication support is enabled or not func (c *CoinbasePro) verificationCheck(ctx context.Context) (bool, error) { _, err := c.GetCredentials(ctx) @@ -1359,11 +1309,14 @@ func (c *CoinbasePro) tickerHelper(ctx context.Context, name string, assetType a } var ticks *Ticker ticks, err = c.GetTicker(ctx, name, 1, time.Time{}, time.Time{}, verified) - if err != nil { + if err != nil || len(ticks.Trades) != 1 { if verified { return c.tickerHelper(ctx, name, assetType, false) } - return err + if err != nil { + return err + } + return errExpectedOneTickerReturned } var last float64 if len(ticks.Trades) != 0 { From ac98019eeffca46afdec2dc02782fa004d0e1704 Mon Sep 17 00:00:00 2001 From: Samuel Reid <43227667+cranktakular@users.noreply.github.com> Date: Wed, 7 Aug 2024 12:26:51 +1000 Subject: [PATCH 56/79] Quick fixies --- exchanges/coinbasepro/coinbasepro_test.go | 6 ++---- exchanges/coinbasepro/coinbasepro_wrapper.go | 19 +++++-------------- 2 files changed, 7 insertions(+), 18 deletions(-) diff --git a/exchanges/coinbasepro/coinbasepro_test.go b/exchanges/coinbasepro/coinbasepro_test.go index d64e478087f..b689c428a09 100644 --- a/exchanges/coinbasepro/coinbasepro_test.go +++ b/exchanges/coinbasepro/coinbasepro_test.go @@ -69,7 +69,7 @@ const ( errExpectedNonEmpty = "expected non-empty response" errPortfolioNameDuplicate = `CoinbasePro unsuccessful HTTP status code: 409 raw response: {"error":"CONFLICT","error_details":"A portfolio with this name already exists.","message":"A portfolio with this name already exists."}, authenticated request failed` errPortTransferInsufFunds = `CoinbasePro unsuccessful HTTP status code: 429 raw response: {"error":"unknown","error_details":"[PORTFOLIO_ERROR_CODE_INSUFFICIENT_FUNDS] insufficient funds in source account","message":"[PORTFOLIO_ERROR_CODE_INSUFFICIENT_FUNDS] insufficient funds in source account"}, authenticated request failed` - errInvalidProductID = `CoinbasePro unsuccessful HTTP status code: 400 raw response: {"error":"INVALID_ARGUMENT","error_details":"valid product_id is required","message":"valid product_id is required"}` + errInvalidProductID = `CoinbasePro unsuccessful HTTP status code: 404 raw response: {"error":"NOT_FOUND","error_details":"valid product_id is required","message":"valid product_id is required"}` errExpectedFeeRange = "expected fee range of %v and %v, received %v" errOptionInvalid = `CoinbasePro unsuccessful HTTP status code: 400 raw response: {"error":"unknown","error_details":"parsing field \"product_type\": \"OPTION\" is not a valid value","message":"parsing field \"product_type\": \"OPTION\" is not a valid value"}` @@ -1382,7 +1382,6 @@ func TestGetLatestFundingRates(t *testing.T) { func TestGetFuturesContractDetails(t *testing.T) { t.Parallel() - c.Verbose = true _, err := c.GetFuturesContractDetails(context.Background(), asset.Empty) assert.ErrorIs(t, err, futures.ErrNotFuturesAsset) _, err = c.GetFuturesContractDetails(context.Background(), asset.UpsideProfitContract) @@ -1661,8 +1660,7 @@ func TestProcessSnapshotUpdate(t *testing.T) { func TestGenerateDefaultSubscriptions(t *testing.T) { comparison := subscription.List{{Channel: "heartbeats"}, {Channel: "status"}, {Channel: "ticker"}, - {Channel: "ticker_batch"}, {Channel: "candles"}, {Channel: "market_trades"}, {Channel: "level2"}, - {Channel: "user"}} + {Channel: "ticker_batch"}, {Channel: "candles"}, {Channel: "market_trades"}, {Channel: "level2"}} for i := range comparison { comparison[i].Pairs = currency.Pairs{ currency.NewPairWithDelimiter(testCrypto.String(), testFiat.String(), "-")} diff --git a/exchanges/coinbasepro/coinbasepro_wrapper.go b/exchanges/coinbasepro/coinbasepro_wrapper.go index 8433ac9a186..990cf434497 100644 --- a/exchanges/coinbasepro/coinbasepro_wrapper.go +++ b/exchanges/coinbasepro/coinbasepro_wrapper.go @@ -655,10 +655,7 @@ 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 - } + response := c.getOrderRespToOrderDetail(genOrderDetail, pair, assetItem) fillData, err := c.GetFills(ctx, orderID, "", "", time.Time{}, time.Now(), 2<<15-1) if err != nil { return nil, err @@ -826,10 +823,7 @@ func (c *CoinbasePro) GetActiveOrders(ctx context.Context, req *order.MultiOrder } orders := make([]order.Detail, len(respOrders)) for i := range respOrders { - orderRec, err := c.getOrderRespToOrderDetail(&respOrders[i], req.Pairs[i], asset.Spot) - if err != nil { - return nil, err - } + orderRec := c.getOrderRespToOrderDetail(&respOrders[i], req.Pairs[i], asset.Spot) orders[i] = *orderRec } return req.Filter(c.Name, orders), nil @@ -874,10 +868,7 @@ func (c *CoinbasePro) GetOrderHistory(ctx context.Context, req *order.MultiOrder } orders := make([]order.Detail, len(ord)) for i := range ord { - singleOrder, err := c.getOrderRespToOrderDetail(&ord[i], req.Pairs[0], req.AssetType) - if err != nil { - return nil, err - } + singleOrder := c.getOrderRespToOrderDetail(&ord[i], req.Pairs[0], req.AssetType) orders[i] = *singleOrder } return req.Filter(c.Name, orders), nil @@ -1185,7 +1176,7 @@ func formatExchangeKlineIntervalV3(interval kline.Interval) string { // getOrderRespToOrderDetail is a helper function used in GetOrderInfo, GetActiveOrders, and GetOrderHistory // to convert data returned by the Coinbase API into a format suitable for the exchange package -func (c *CoinbasePro) getOrderRespToOrderDetail(genOrderDetail *GetOrderResponse, pair currency.Pair, assetItem asset.Item) (*order.Detail, error) { +func (c *CoinbasePro) getOrderRespToOrderDetail(genOrderDetail *GetOrderResponse, pair currency.Pair, assetItem asset.Item) *order.Detail { var amount float64 var quoteAmount float64 var orderType order.Type @@ -1280,7 +1271,7 @@ func (c *CoinbasePro) getOrderRespToOrderDetail(genOrderDetail *GetOrderResponse LastUpdated: lastUpdateTime, Pair: pair, } - return &response, nil + return &response } // VerificationCheck returns whether authentication support is enabled or not From e372aeaac042dc988bafec5c27166f8273d4d09e Mon Sep 17 00:00:00 2001 From: Samuel Reid <43227667+cranktakular@users.noreply.github.com> Date: Wed, 14 Aug 2024 09:24:52 +1000 Subject: [PATCH 57/79] Lil fixes --- exchanges/coinbasepro/coinbasepro_test.go | 3 ++- exchanges/coinbasepro/coinbasepro_wrapper.go | 7 ++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/exchanges/coinbasepro/coinbasepro_test.go b/exchanges/coinbasepro/coinbasepro_test.go index b689c428a09..e227e788e81 100644 --- a/exchanges/coinbasepro/coinbasepro_test.go +++ b/exchanges/coinbasepro/coinbasepro_test.go @@ -559,7 +559,6 @@ func TestCommitConvertTrade(t *testing.T) { } func TestGetConvertTradeByID(t *testing.T) { - c.Verbose = true convertTestShared(t, c.GetConvertTradeByID) } @@ -1080,6 +1079,8 @@ func TestUpdateTickers(t *testing.T) { assert.ErrorIs(t, err, currency.ErrAssetNotFound) err = c.UpdateTickers(context.Background(), asset.Spot) assert.NoError(t, err) + err = c.UpdateTickers(context.Background(), asset.Futures) + assert.NoError(t, err) } func TestUpdateTicker(t *testing.T) { diff --git a/exchanges/coinbasepro/coinbasepro_wrapper.go b/exchanges/coinbasepro/coinbasepro_wrapper.go index 990cf434497..37e3eb7a208 100644 --- a/exchanges/coinbasepro/coinbasepro_wrapper.go +++ b/exchanges/coinbasepro/coinbasepro_wrapper.go @@ -1300,14 +1300,11 @@ func (c *CoinbasePro) tickerHelper(ctx context.Context, name string, assetType a } var ticks *Ticker ticks, err = c.GetTicker(ctx, name, 1, time.Time{}, time.Time{}, verified) - if err != nil || len(ticks.Trades) != 1 { + if err != nil { if verified { return c.tickerHelper(ctx, name, assetType, false) } - if err != nil { - return err - } - return errExpectedOneTickerReturned + return err } var last float64 if len(ticks.Trades) != 0 { From d384ec2540cdde169c0c6961e3a5ba16e80e2628 Mon Sep 17 00:00:00 2001 From: Gareth Kirwan Date: Fri, 23 Aug 2024 09:55:48 +0700 Subject: [PATCH 58/79] Subscriptions: Add List.Enabled --- exchanges/subscription/list.go | 11 +++++++++++ exchanges/subscription/list_test.go | 9 +++++++++ 2 files changed, 20 insertions(+) diff --git a/exchanges/subscription/list.go b/exchanges/subscription/list.go index f250010d6ee..f39272e1591 100644 --- a/exchanges/subscription/list.go +++ b/exchanges/subscription/list.go @@ -113,3 +113,14 @@ func (l List) assetPairs(e iExchange) (assetPairs, error) { } return ap, nil } + +// Enabled returns a new list of only enabled subscriptions +func (l List) Enabled() List { + n := make(List, 0, len(l)) + for _, s := range l { + if s.Enabled { + n = append(n, s) + } + } + return slices.Clip(n) +} diff --git a/exchanges/subscription/list_test.go b/exchanges/subscription/list_test.go index cd92d171546..b1b43063bb2 100644 --- a/exchanges/subscription/list_test.go +++ b/exchanges/subscription/list_test.go @@ -106,3 +106,12 @@ func TestListClone(t *testing.T) { l[0].Interval = kline.OneHour assert.NotEqual(t, n[0], l[0], "Subscriptions should be cloned") } + +func TestListEnabled(t *testing.T) { + t.Parallel() + l := List{{Channel: TickerChannel}, {Enabled: true, Channel: OrderbookChannel}, {Channel: MyAccountChannel}} + n := l.Enabled() + require.Len(t, l, 3, "Original should not be effected") + require.Len(t, n, 1, "New should be filtered") + require.Equal(t, OrderbookChannel, n[0].Channel, "New should be filtered") +} From 9205394f6bceda53bcd1cdb76ace448fc1aaed95 Mon Sep 17 00:00:00 2001 From: Gareth Kirwan Date: Wed, 21 Aug 2024 14:56:25 +0700 Subject: [PATCH 59/79] CoinbasePro: Add subscription templating --- exchanges/coinbasepro/coinbasepro.go | 1 - exchanges/coinbasepro/coinbasepro_test.go | 53 ++++- .../coinbasepro/coinbasepro_websocket.go | 201 ++++++++++-------- exchanges/coinbasepro/coinbasepro_wrapper.go | 12 +- exchanges/subscription/subscription.go | 3 + 5 files changed, 159 insertions(+), 111 deletions(-) diff --git a/exchanges/coinbasepro/coinbasepro.go b/exchanges/coinbasepro/coinbasepro.go index ecf31ac6cfc..fcf9e5bc626 100644 --- a/exchanges/coinbasepro/coinbasepro.go +++ b/exchanges/coinbasepro/coinbasepro.go @@ -160,7 +160,6 @@ var ( errPairEmpty = errors.New("pair cannot be empty") errStringConvert = errors.New("unable to convert into string value") errFloatConvert = errors.New("unable to convert into float64 value") - errNoCredsUser = errors.New("no credentials when attempting to subscribe to authenticated channel user") errWrappedAssetEmpty = errors.New("wrapped asset cannot be empty") errExpectedOneTickerReturned = errors.New("expected one ticker to be returned") ) diff --git a/exchanges/coinbasepro/coinbasepro_test.go b/exchanges/coinbasepro/coinbasepro_test.go index e227e788e81..ce7aacdfde0 100644 --- a/exchanges/coinbasepro/coinbasepro_test.go +++ b/exchanges/coinbasepro/coinbasepro_test.go @@ -29,6 +29,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/exchanges/stream" "github.com/thrasher-corp/gocryptotrader/exchanges/subscription" testexch "github.com/thrasher-corp/gocryptotrader/internal/testing/exchange" + testsubs "github.com/thrasher-corp/gocryptotrader/internal/testing/subscriptions" gctlog "github.com/thrasher-corp/gocryptotrader/log" "github.com/thrasher-corp/gocryptotrader/portfolio/withdraw" ) @@ -1659,17 +1660,30 @@ func TestProcessSnapshotUpdate(t *testing.T) { assert.NoError(t, err) } -func TestGenerateDefaultSubscriptions(t *testing.T) { - comparison := subscription.List{{Channel: "heartbeats"}, {Channel: "status"}, {Channel: "ticker"}, - {Channel: "ticker_batch"}, {Channel: "candles"}, {Channel: "market_trades"}, {Channel: "level2"}} - for i := range comparison { - comparison[i].Pairs = currency.Pairs{ - currency.NewPairWithDelimiter(testCrypto.String(), testFiat.String(), "-")} - comparison[i].Asset = asset.Spot +func TestGenerateSubscriptions(t *testing.T) { + t.Parallel() + c := new(CoinbasePro) //nolint:govet // Intentional shadow to avoid future copy/paste mistakes + if err := testexch.Setup(c); err != nil { + log.Fatal(err) } - resp, err := c.generateSubscriptions() + c.Websocket.SetCanUseAuthenticatedEndpoints(true) + p, err := c.GetEnabledPairs(asset.Spot) require.NoError(t, err) - assert.ElementsMatch(t, comparison, resp) + exp := subscription.List{} + for _, baseSub := range defaultSubscriptions.Enabled() { + s := baseSub.Clone() + s.QualifiedChannel = subscriptionNames[s.Channel] + if s.Asset != asset.Empty { + s.Pairs = p + } + exp = append(exp, s) + } + subs, err := c.generateSubscriptions() + require.NoError(t, err) + testsubs.EqualLists(t, exp, subs) + + _, err = subscription.List{{Channel: "wibble"}}.ExpandTemplates(c) + assert.ErrorContains(t, err, "subscription channel not supported: wibble") } func TestSubscribeUnsubscribe(t *testing.T) { @@ -1920,3 +1934,24 @@ func testGetOneArg[G getOneArgResp](t *testing.T, f getOneArgAssertNotEmpty[G], assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) } + +func TestCheckSubscriptions(t *testing.T) { + t.Parallel() + + c := &CoinbasePro{ + Base: exchange.Base{ + Config: &config.Exchange{ + Features: &config.FeaturesConfig{ + Subscriptions: subscription.List{ + {Enabled: true, Channel: "matches"}, + }, + }, + }, + Features: exchange.Features{}, + }, + } + + c.checkSubscriptions() + testsubs.EqualLists(t, defaultSubscriptions.Enabled(), c.Features.Subscriptions) + testsubs.EqualLists(t, defaultSubscriptions, c.Config.Features.Subscriptions) +} diff --git a/exchanges/coinbasepro/coinbasepro_websocket.go b/exchanges/coinbasepro/coinbasepro_websocket.go index f6b82f57099..62a450f05b2 100644 --- a/exchanges/coinbasepro/coinbasepro_websocket.go +++ b/exchanges/coinbasepro/coinbasepro_websocket.go @@ -13,6 +13,7 @@ import ( "net/http" "strconv" "strings" + "text/template" "time" "github.com/buger/jsonparser" @@ -20,8 +21,6 @@ import ( "github.com/pkg/errors" "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/common/crypto" - "github.com/thrasher-corp/gocryptotrader/currency" - exchange "github.com/thrasher-corp/gocryptotrader/exchanges" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" @@ -35,6 +34,34 @@ const ( coinbaseproWebsocketURL = "wss://advanced-trade-ws.coinbase.com" ) +var subscriptionNames = map[string]string{ + subscription.HeartbeatChannel: "heartbeats", + subscription.TickerChannel: "ticker", + subscription.CandlesChannel: "candles", + subscription.AllTradesChannel: "market_trades", + subscription.OrderbookChannel: "level2", + subscription.MyAccountChannel: "user", + "status": "status", + "ticker_batch": "ticker_batch", + /* Not Implemented: + "futures_balance_summary": "futures_balance_summary", + */ +} + +var defaultSubscriptions = subscription.List{ + {Enabled: true, Channel: subscription.HeartbeatChannel}, + {Enabled: true, Channel: "status"}, + {Enabled: true, Asset: asset.Spot, Channel: subscription.TickerChannel}, + {Enabled: true, Asset: asset.Spot, Channel: subscription.CandlesChannel}, + {Enabled: true, Asset: asset.Spot, Channel: subscription.AllTradesChannel}, + {Enabled: true, Asset: asset.Spot, Channel: subscription.OrderbookChannel}, + {Enabled: true, Channel: subscription.MyAccountChannel, Authenticated: true}, + {Enabled: false, Asset: asset.Spot, Channel: "ticker_batch"}, + /* Not Implemented: + {Enabled: false, Asset: asset.Spot, Channel: "futures_balance_summary", Authenticated: true}, + */ +} + // WsConnect initiates a websocket connection func (c *CoinbasePro) WsConnect() error { if !c.Websocket.IsEnabled() || !c.IsEnabled() { @@ -300,61 +327,67 @@ func (c *CoinbasePro) ProcessUpdate(update *WebsocketOrderbookDataHolder, timest // GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions() func (c *CoinbasePro) generateSubscriptions() (subscription.List, error) { - var channels = []string{ - "heartbeats", - "status", - "ticker", - "ticker_batch", - "candles", - "market_trades", - "level2", - } - enabledPairs, err := c.GetEnabledPairs(asset.Spot) - if err != nil { - return nil, err - } - var subscriptions subscription.List - for i := range channels { - subscriptions = append(subscriptions, &subscription.Subscription{ - Channel: channels[i], - Pairs: enabledPairs, - Asset: asset.Spot, - }) - } - return subscriptions, nil + return c.Features.Subscriptions.ExpandTemplates(c) } -// Subscribe sends a websocket message to receive data from the channel -func (c *CoinbasePro) Subscribe(channelsToSubscribe subscription.List) error { - chanKeys := make(map[string]currency.Pairs) - for i := range channelsToSubscribe { - chanKeys[channelsToSubscribe[i].Channel] = - chanKeys[channelsToSubscribe[i].Channel].Add(channelsToSubscribe[i].Pairs...) - } - for s := range chanKeys { - err := c.sendRequest("subscribe", s, chanKeys[s]) - if err != nil { - return err +// GetSubscriptionTemplate returns a subscription channel template +func (c *CoinbasePro) GetSubscriptionTemplate(_ *subscription.Subscription) (*template.Template, error) { + return template.New("master.tmpl").Funcs(template.FuncMap{"channelName": channelName}).Parse(subTplText) +} + +// Subscribe sends a websocket message to receive data from a list of channels +func (c *CoinbasePro) Subscribe(subs subscription.List) error { + return c.ParallelChanOp(subs, func(subs subscription.List) error { return c.manageSubs("subscribe", subs) }, 1) +} + +// Unsubscribe sends a websocket message to stop receiving data from a list of channels +func (c *CoinbasePro) Unsubscribe(subs subscription.List) error { + return c.ParallelChanOp(subs, func(subs subscription.List) error { return c.manageSubs("unsubscribe", subs) }, 1) +} + +// manageSub subscribes or unsubscribes from a list of websocket channels +func (c *CoinbasePro) manageSubs(op string, subs subscription.List) error { + var errs error + subs, errs = subs.ExpandTemplates(c) + for _, s := range subs { + r := &WebsocketRequest{ + Type: op, + ProductIDs: s.Pairs.Strings(), + Channel: s.QualifiedChannel, + Timestamp: strconv.FormatInt(time.Now().Unix(), 10), + } + var err error + limitType := WSUnauthRate + if s.Authenticated { + limitType = WSAuthRate + err = c.signWsRequest(r) + } + if err == nil { + err = c.InitiateRateLimit(context.Background(), limitType) + } + if err == nil { + if err = c.Websocket.Conn.SendJSONMessage(r); err == nil { + err = c.Websocket.AddSuccessfulSubscriptions(s) + } } - time.Sleep(time.Millisecond * 10) + errs = common.AppendError(errs, err) } return nil } -// Unsubscribe sends a websocket message to stop receiving data from the channel -func (c *CoinbasePro) Unsubscribe(channelsToUnsubscribe subscription.List) error { - chanKeys := make(map[string]currency.Pairs) - for i := range channelsToUnsubscribe { - chanKeys[channelsToUnsubscribe[i].Channel] = - chanKeys[channelsToUnsubscribe[i].Channel].Add(channelsToUnsubscribe[i].Pairs...) +func (c *CoinbasePro) signWsRequest(r *WebsocketRequest) error { + creds, err := c.GetCredentials(context.Background()) + if err != nil { + return err } - for s := range chanKeys { - err := c.sendRequest("unsubscribe", s, chanKeys[s]) - if err != nil { - return err - } - time.Sleep(time.Millisecond * 10) + hmac, err := crypto.GetHMAC(crypto.HashSHA256, []byte(r.Timestamp+r.Channel+strings.Join(r.ProductIDs, ",")), []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 + r.Key = creds.Key + r.Signature = hex.EncodeToString(hmac) return nil } @@ -421,51 +454,6 @@ func getTimestamp(rawData []byte) (time.Time, error) { return timestamp, nil } -// sendRequest is a helper function which sends a websocket message to the Coinbase server -func (c *CoinbasePro) sendRequest(msgType, channel string, productIDs currency.Pairs) error { - authenticated := true - creds, err := c.GetCredentials(context.Background()) - if err != nil { - if errors.Is(err, exchange.ErrCredentialsAreEmpty) || - errors.Is(err, exchange.ErrAuthenticationSupportNotEnabled) { - authenticated = false - if channel == "user" { - return errNoCredsUser - } - } else { - return err - } - } - n := strconv.FormatInt(time.Now().Unix(), 10) - req := WebsocketRequest{ - Type: msgType, - ProductIDs: productIDs.Strings(), - Channel: channel, - Timestamp: n, - } - if authenticated { - message := n + channel + productIDs.Join() - var hmac []byte - 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.Key = creds.Key - req.Signature = hex.EncodeToString(hmac) - err = c.InitiateRateLimit(context.Background(), WSAuthRate) - } else { - err = c.InitiateRateLimit(context.Background(), WSUnauthRate) - } - if err != nil { - return fmt.Errorf("failed to rate limit websocket request: %w", err) - } - return c.Websocket.Conn.SendJSONMessage(req) -} - // processBidAskArray is a helper function that turns WebsocketOrderbookDataHolder into arrays // of bids and asks func processBidAskArray(data *WebsocketOrderbookDataHolder) (bids, asks orderbook.Tranches, err error) { @@ -515,3 +503,30 @@ func base64URLEncode(b []byte) string { s = strings.ReplaceAll(s, "/", "_") return s } + +// checkSubscriptions looks for incompatible subscriptions and if found replaces all with defaults +// This should be unnecessary and removable by mid-2025 +func (c *CoinbasePro) checkSubscriptions() { + for _, s := range c.Config.Features.Subscriptions { + switch s.Channel { + case "heartbeat", "level2_batch", "matches": + c.Config.Features.Subscriptions = defaultSubscriptions.Clone() + c.Features.Subscriptions = c.Config.Features.Subscriptions.Enabled() + return + } + } +} + +func channelName(s *subscription.Subscription) string { + if n, ok := subscriptionNames[s.Channel]; ok { + return n + } + panic(fmt.Errorf("%w: %s", subscription.ErrNotSupported, s.Channel)) +} + +const subTplText = ` +{{ range $asset, $pairs := $.AssetPairs }} + {{- channelName $.S -}} + {{- $.AssetSeparator }} +{{- end }} +` diff --git a/exchanges/coinbasepro/coinbasepro_wrapper.go b/exchanges/coinbasepro/coinbasepro_wrapper.go index 37e3eb7a208..539f5989e39 100644 --- a/exchanges/coinbasepro/coinbasepro_wrapper.go +++ b/exchanges/coinbasepro/coinbasepro_wrapper.go @@ -25,7 +25,6 @@ import ( "github.com/thrasher-corp/gocryptotrader/exchanges/request" "github.com/thrasher-corp/gocryptotrader/exchanges/stream" "github.com/thrasher-corp/gocryptotrader/exchanges/stream/buffer" - "github.com/thrasher-corp/gocryptotrader/exchanges/subscription" "github.com/thrasher-corp/gocryptotrader/exchanges/ticker" "github.com/thrasher-corp/gocryptotrader/exchanges/trade" "github.com/thrasher-corp/gocryptotrader/log" @@ -106,13 +105,7 @@ func (c *CoinbasePro) SetDefaults() { GlobalResultLimit: 300, }, }, - Subscriptions: subscription.List{ - {Enabled: true, Channel: "heartbeat"}, - {Enabled: true, Channel: "level2_batch"}, // Other orderbook feeds require authentication; This is batched in 50ms lots - {Enabled: true, Channel: "ticker"}, - {Enabled: true, Channel: "user", Authenticated: true}, - {Enabled: true, Channel: "matches"}, - }, + Subscriptions: defaultSubscriptions.Clone(), } c.Requester, err = request.New(c.Name, common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout), @@ -150,6 +143,9 @@ func (c *CoinbasePro) Setup(exch *config.Exchange) error { if err != nil { return err } + + c.checkSubscriptions() + wsRunningURL, err := c.API.Endpoints.GetURL(exchange.WebsocketSpot) if err != nil { return err diff --git a/exchanges/subscription/subscription.go b/exchanges/subscription/subscription.go index 516466363eb..64db2c3e415 100644 --- a/exchanges/subscription/subscription.go +++ b/exchanges/subscription/subscription.go @@ -31,6 +31,8 @@ const ( AllTradesChannel = "allTrades" MyTradesChannel = "myTrades" MyOrdersChannel = "myOrders" + MyAccountChannel = "account" + HeartbeatChannel = "heartbeat" ) // Public errors @@ -40,6 +42,7 @@ var ( ErrInStateAlready = errors.New("subscription already in state") ErrInvalidState = errors.New("invalid subscription state") ErrDuplicate = errors.New("duplicate subscription") + ErrNotSupported = errors.New("subscription channel not supported") ) // State tracks the status of a subscription channel From 13a3faf5677ff0fef20b1df296cb49645c23bef5 Mon Sep 17 00:00:00 2001 From: Gareth Kirwan Date: Tue, 27 Aug 2024 10:29:55 +0700 Subject: [PATCH 60/79] fixup! CoinbasePro: Add subscription templating --- exchanges/coinbasepro/coinbasepro_test.go | 42 +++++++++++------------ 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/exchanges/coinbasepro/coinbasepro_test.go b/exchanges/coinbasepro/coinbasepro_test.go index ce7aacdfde0..7d6f1f3e144 100644 --- a/exchanges/coinbasepro/coinbasepro_test.go +++ b/exchanges/coinbasepro/coinbasepro_test.go @@ -1696,6 +1696,27 @@ func TestSubscribeUnsubscribe(t *testing.T) { assert.NoError(t, err) } +func TestCheckSubscriptions(t *testing.T) { + t.Parallel() + + c := &CoinbasePro{ + Base: exchange.Base{ + Config: &config.Exchange{ + Features: &config.FeaturesConfig{ + Subscriptions: subscription.List{ + {Enabled: true, Channel: "matches"}, + }, + }, + }, + Features: exchange.Features{}, + }, + } + + c.checkSubscriptions() + testsubs.EqualLists(t, defaultSubscriptions.Enabled(), c.Features.Subscriptions) + testsubs.EqualLists(t, defaultSubscriptions, c.Config.Features.Subscriptions) +} + func TestGetJWT(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) creds, err := c.GetCredentials(context.Background()) @@ -1934,24 +1955,3 @@ func testGetOneArg[G getOneArgResp](t *testing.T, f getOneArgAssertNotEmpty[G], assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) } - -func TestCheckSubscriptions(t *testing.T) { - t.Parallel() - - c := &CoinbasePro{ - Base: exchange.Base{ - Config: &config.Exchange{ - Features: &config.FeaturesConfig{ - Subscriptions: subscription.List{ - {Enabled: true, Channel: "matches"}, - }, - }, - }, - Features: exchange.Features{}, - }, - } - - c.checkSubscriptions() - testsubs.EqualLists(t, defaultSubscriptions.Enabled(), c.Features.Subscriptions) - testsubs.EqualLists(t, defaultSubscriptions, c.Config.Features.Subscriptions) -} From 62c48afb3d364967a8a6a32e9126d59bd66dd783 Mon Sep 17 00:00:00 2001 From: Gareth Kirwan Date: Tue, 27 Aug 2024 10:54:58 +0700 Subject: [PATCH 61/79] fixup! CoinbasePro: Add subscription templating --- exchanges/coinbasepro/coinbasepro_websocket.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exchanges/coinbasepro/coinbasepro_websocket.go b/exchanges/coinbasepro/coinbasepro_websocket.go index 62a450f05b2..a63b3aac1a1 100644 --- a/exchanges/coinbasepro/coinbasepro_websocket.go +++ b/exchanges/coinbasepro/coinbasepro_websocket.go @@ -372,7 +372,7 @@ func (c *CoinbasePro) manageSubs(op string, subs subscription.List) error { } errs = common.AppendError(errs, err) } - return nil + return errs } func (c *CoinbasePro) signWsRequest(r *WebsocketRequest) error { From 2e814d505a29b9e25d048453b0a1b8d821229ccb Mon Sep 17 00:00:00 2001 From: Samuel Reid <43227667+cranktakular@users.noreply.github.com> Date: Tue, 27 Aug 2024 14:28:25 +1000 Subject: [PATCH 62/79] Comment fix --- exchanges/coinbasepro/coinbasepro_websocket.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exchanges/coinbasepro/coinbasepro_websocket.go b/exchanges/coinbasepro/coinbasepro_websocket.go index f6b82f57099..eb7c16e092c 100644 --- a/exchanges/coinbasepro/coinbasepro_websocket.go +++ b/exchanges/coinbasepro/coinbasepro_websocket.go @@ -298,7 +298,7 @@ func (c *CoinbasePro) ProcessUpdate(update *WebsocketOrderbookDataHolder, timest return c.Websocket.Orderbook.Update(&obU) } -// GenerateDefaultSubscriptions Adds default subscriptions to websocket to be handled by ManageSubscriptions() +// GenerateSubscriptions adds default subscriptions to websocket to be handled by ManageSubscriptions() func (c *CoinbasePro) generateSubscriptions() (subscription.List, error) { var channels = []string{ "heartbeats", From 06f8a6a82809bbcf0d0a19ad36481c4e078fb9c4 Mon Sep 17 00:00:00 2001 From: Samuel Reid <43227667+cranktakular@users.noreply.github.com> Date: Tue, 27 Aug 2024 18:14:26 +1000 Subject: [PATCH 63/79] Subsequent fixes --- currency/pairs.go | 1 - exchanges/coinbasepro/coinbasepro_test.go | 30 +++++++++++++++---- .../coinbasepro/coinbasepro_websocket.go | 7 ++++- exchanges/stream/websocket.go | 17 +++++------ exchanges/subscription/template.go | 17 +++++------ exchanges/subscription/template_test.go | 3 -- 6 files changed, 46 insertions(+), 29 deletions(-) diff --git a/currency/pairs.go b/currency/pairs.go index 360bc8e9bb2..f2ebbf32798 100644 --- a/currency/pairs.go +++ b/currency/pairs.go @@ -143,7 +143,6 @@ list: if p.Contains(check[x], exact) { return fmt.Errorf("%s %w", check[x], ErrPairDuplication) } - return fmt.Errorf("%s %w", check[x], ErrPairNotContainedInAvailablePairs) } return nil diff --git a/exchanges/coinbasepro/coinbasepro_test.go b/exchanges/coinbasepro/coinbasepro_test.go index 7d6f1f3e144..dd6e69ddd56 100644 --- a/exchanges/coinbasepro/coinbasepro_test.go +++ b/exchanges/coinbasepro/coinbasepro_test.go @@ -99,6 +99,12 @@ func TestMain(m *testing.M) { if err != nil { log.Fatal(err) } + var dialer websocket.Dialer + err = c.Websocket.Conn.Dial(&dialer, http.Header{}) + if err != nil { + log.Fatal(err) + } + go c.wsReadData() os.Exit(m.Run()) } @@ -827,6 +833,8 @@ func TestGetAllFiatTransfers(t *testing.T) { wID, err := c.GetWalletByID(context.Background(), "", "AUD") require.NoError(t, err) assert.NotEmpty(t, wID, errExpectedNonEmpty) + // Fiat deposits/withdrawals aren't accepted for fiat currencies for Australian business accounts; the error + // "id not found" possibly reflects this _, err = c.GetAllFiatTransfers(context.Background(), wID.Data.ID, pag, FiatDeposit) assert.NoError(t, err) _, err = c.GetAllFiatTransfers(context.Background(), wID.Data.ID, pag, FiatWithdrawal) @@ -843,6 +851,8 @@ func TestGetFiatTransferByID(t *testing.T) { wID, err := c.GetWalletByID(context.Background(), "", "AUD") require.NoError(t, err) assert.NotEmpty(t, wID, errExpectedNonEmpty) + // Fiat deposits/withdrawals aren't accepted for fiat currencies for Australian business accounts; the error + // "id not found" possibly reflects this dID, err := c.GetAllFiatTransfers(context.Background(), wID.Data.ID, PaginationInp{}, FiatDeposit) assert.NoError(t, err) if dID == nil || len(dID.Data) == 0 { @@ -1511,6 +1521,12 @@ func TestCancelPendingFuturesSweep(t *testing.T) { // TestWsAuth dials websocket, sends login request. func TestWsAuth(t *testing.T) { + t.Parallel() + p := currency.Pairs{testPair} + for _, a := range c.GetAssetTypes(true) { + require.NoError(t, c.CurrencyPairs.StorePairs(a, p, false)) + require.NoError(t, c.CurrencyPairs.StorePairs(a, p, true)) + } if c.Websocket.IsEnabled() && !c.API.AuthenticatedWebsocketSupport || !sharedtestvalues.AreAPICredentialsSet(c) { t.Skip(stream.ErrWebsocketNotEnabled.Error()) } @@ -1518,11 +1534,11 @@ func TestWsAuth(t *testing.T) { err := c.Websocket.Conn.Dial(&dialer, http.Header{}) require.NoError(t, err) go c.wsReadData() - err = c.Subscribe(subscription.List{ { - Channel: "user", - Pairs: currency.Pairs{testPair}, + Channel: "account", + Asset: asset.All, + Pairs: p, }, }) assert.NoError(t, err) @@ -1536,6 +1552,7 @@ func TestWsAuth(t *testing.T) { } func TestStatusToStandardStatus(t *testing.T) { + t.Parallel() type TestCases struct { Case string Result order.Status @@ -1647,6 +1664,7 @@ func TestWsHandleData(t *testing.T) { } func TestProcessSnapshotUpdate(t *testing.T) { + t.Parallel() req := WebsocketOrderbookDataHolder{Changes: []WebsocketOrderbookData{{Side: "fakeside", PriceLevel: 1.1, NewQuantity: 2.2}}, ProductID: currency.NewBTCUSD()} err := c.ProcessSnapshot(&req, time.Time{}) @@ -1687,8 +1705,9 @@ func TestGenerateSubscriptions(t *testing.T) { } func TestSubscribeUnsubscribe(t *testing.T) { + t.Parallel() sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - req := subscription.List{{Channel: "heartbeats", Asset: asset.Spot, + req := subscription.List{{Channel: "heartbeat", Asset: asset.Spot, Pairs: currency.Pairs{currency.NewPairWithDelimiter(testCrypto.String(), testFiat.String(), "-")}}} err := c.Subscribe(req) assert.NoError(t, err) @@ -1698,7 +1717,6 @@ func TestSubscribeUnsubscribe(t *testing.T) { func TestCheckSubscriptions(t *testing.T) { t.Parallel() - c := &CoinbasePro{ Base: exchange.Base{ Config: &config.Exchange{ @@ -1711,13 +1729,13 @@ func TestCheckSubscriptions(t *testing.T) { Features: exchange.Features{}, }, } - c.checkSubscriptions() testsubs.EqualLists(t, defaultSubscriptions.Enabled(), c.Features.Subscriptions) testsubs.EqualLists(t, defaultSubscriptions, c.Config.Features.Subscriptions) } func TestGetJWT(t *testing.T) { + t.Parallel() sharedtestvalues.SkipTestIfCredentialsUnset(t, c) creds, err := c.GetCredentials(context.Background()) assert.NoError(t, err) diff --git a/exchanges/coinbasepro/coinbasepro_websocket.go b/exchanges/coinbasepro/coinbasepro_websocket.go index 9800adad0bb..feeb5fb4aeb 100644 --- a/exchanges/coinbasepro/coinbasepro_websocket.go +++ b/exchanges/coinbasepro/coinbasepro_websocket.go @@ -367,7 +367,12 @@ func (c *CoinbasePro) manageSubs(op string, subs subscription.List) error { } if err == nil { if err = c.Websocket.Conn.SendJSONMessage(r); err == nil { - err = c.Websocket.AddSuccessfulSubscriptions(s) + switch op { + case "subscribe": + err = c.Websocket.AddSuccessfulSubscriptions(s) + case "unsubscribe": + err = c.Websocket.RemoveSubscriptions(s) + } } } errs = common.AppendError(errs, err) diff --git a/exchanges/stream/websocket.go b/exchanges/stream/websocket.go index de7a0846180..662437f8fdf 100644 --- a/exchanges/stream/websocket.go +++ b/exchanges/stream/websocket.go @@ -23,15 +23,14 @@ const ( // Public websocket errors var ( - ErrWebsocketNotEnabled = errors.New("websocket not enabled") - ErrSubscriptionFailure = errors.New("subscription failure") - ErrSubscriptionNotSupported = errors.New("subscription channel not supported ") - ErrUnsubscribeFailure = errors.New("unsubscribe failure") - ErrAlreadyDisabled = errors.New("websocket already disabled") - ErrNotConnected = errors.New("websocket is not connected") - ErrWebsocketAlreadyEnabled = errors.New("websocket already enabled") - ErrNoMessageListener = errors.New("websocket listener not found for message") - ErrSignatureTimeout = errors.New("websocket timeout waiting for response with signature") + ErrWebsocketNotEnabled = errors.New("websocket not enabled") + ErrSubscriptionFailure = errors.New("subscription failure") + ErrUnsubscribeFailure = errors.New("unsubscribe failure") + ErrAlreadyDisabled = errors.New("websocket already disabled") + ErrNotConnected = errors.New("websocket is not connected") + ErrWebsocketAlreadyEnabled = errors.New("websocket already enabled") + ErrNoMessageListener = errors.New("websocket listener not found for message") + ErrSignatureTimeout = errors.New("websocket timeout waiting for response with signature") ) // Private websocket errors diff --git a/exchanges/subscription/template.go b/exchanges/subscription/template.go index ab0a18b4690..90bed7231aa 100644 --- a/exchanges/subscription/template.go +++ b/exchanges/subscription/template.go @@ -104,6 +104,14 @@ func expandTemplate(e iExchange, s *Subscription, ap assetPairs, assets asset.It subs := List{} + // We deliberately do not check Availability of sub Pairs because users have edge cases to subscribe to + // non-existent pairs + if len(s.Pairs) != 0 { + for a := range ap { + ap[a] = s.Pairs + } + } + switch s.Asset { case asset.All: subCtx.AssetPairs = ap @@ -118,15 +126,6 @@ func expandTemplate(e iExchange, s *Subscription, ap assetPairs, assets asset.It } } - if len(s.Pairs) != 0 { - for a, pairs := range subCtx.AssetPairs { - if err := pairs.ContainsAll(s.Pairs, true); err != nil { //nolint:govet // Shadow, or gocritic will complain sloppyReassign - return nil, err - } - subCtx.AssetPairs[a] = s.Pairs - } - } - buf := &bytes.Buffer{} if err := t.Execute(buf, subCtx); err != nil { //nolint:govet // Shadow, or gocritic will complain sloppyReassign return nil, err diff --git a/exchanges/subscription/template_test.go b/exchanges/subscription/template_test.go index f14e851ea8f..740c3a4023b 100644 --- a/exchanges/subscription/template_test.go +++ b/exchanges/subscription/template_test.go @@ -88,9 +88,6 @@ func TestExpandTemplates(t *testing.T) { _, err = List{{Channel: "nil"}}.ExpandTemplates(e) assert.ErrorIs(t, err, errInvalidTemplate, "Should get correct error on nil template") - _, err = List{{Channel: "single-channel", Asset: asset.Spot, Pairs: currency.Pairs{currency.NewPairWithDelimiter("NOPE", "POPE", "🐰")}}}.ExpandTemplates(e) - assert.ErrorIs(t, err, currency.ErrPairNotContainedInAvailablePairs, "Should error correctly when pair not available") - e.tpl = "errors.tmpl" _, err = List{{Channel: "error1"}}.ExpandTemplates(e) From 9edf0c9f127db0640c752925985c53b96f129e9e Mon Sep 17 00:00:00 2001 From: Samuel Reid <43227667+cranktakular@users.noreply.github.com> Date: Tue, 3 Sep 2024 12:59:26 +1000 Subject: [PATCH 64/79] Issues hopefully fixed --- exchanges/coinbasepro/coinbasepro.go | 4 ++-- exchanges/coinbasepro/coinbasepro_test.go | 21 ++++++++++++------- exchanges/coinbasepro/coinbasepro_types.go | 9 +++++++- .../coinbasepro/coinbasepro_websocket.go | 10 ++++++--- exchanges/coinbasepro/coinbasepro_wrapper.go | 16 ++++++++++---- 5 files changed, 43 insertions(+), 17 deletions(-) diff --git a/exchanges/coinbasepro/coinbasepro.go b/exchanges/coinbasepro/coinbasepro.go index fcf9e5bc626..f375b85d413 100644 --- a/exchanges/coinbasepro/coinbasepro.go +++ b/exchanges/coinbasepro/coinbasepro.go @@ -480,11 +480,11 @@ func (c *CoinbasePro) GetFills(ctx context.Context, orderID, productID, cursor s } // GetOrderByID returns a single order by order id. -func (c *CoinbasePro) GetOrderByID(ctx context.Context, orderID, clientOID, userNativeCurrency string) (*GetOrderResponse, error) { +func (c *CoinbasePro) GetOrderByID(ctx context.Context, orderID, clientOID, userNativeCurrency string) (*SingleOrder, error) { if orderID == "" { return nil, errOrderIDEmpty } - var resp GetOrderResponse + var resp SingleOrder vals := url.Values{} if clientOID != "" { vals.Set("client_order_id", clientOID) diff --git a/exchanges/coinbasepro/coinbasepro_test.go b/exchanges/coinbasepro/coinbasepro_test.go index dd6e69ddd56..8f6c4fcb3a7 100644 --- a/exchanges/coinbasepro/coinbasepro_test.go +++ b/exchanges/coinbasepro/coinbasepro_test.go @@ -274,7 +274,14 @@ func orderTestHelper(t *testing.T, orderSide string) *GetAllOrdersResp { if ordIDs == nil || len(ordIDs.Orders) == 0 { t.Skip(skipInsufficientOrders) } - return ordIDs + for i := range ordIDs.Orders { + if ordIDs.Orders[i].Status == order.Open.String() { + ordIDs.Orders = ordIDs.Orders[i : i+1] + return ordIDs + } + } + t.Skip(skipInsufficientOrders) + return nil } func TestCancelOrders(t *testing.T) { @@ -1580,9 +1587,12 @@ func TestWsHandleData(t *testing.T) { continue } }() - _, err := c.wsHandleData(nil, 0) + mockJSON := []byte(`{"type": "error"}`) + _, err := c.wsHandleData(mockJSON, 0) + assert.Error(t, err) + _, err = c.wsHandleData(nil, 0) assert.ErrorIs(t, err, jsonparser.KeyPathNotFoundError) - mockJSON := []byte(`{"sequence_num": "l"}`) + mockJSON = []byte(`{"sequence_num": "l"}`) _, err = c.wsHandleData(mockJSON, 0) assert.ErrorIs(t, err, strconv.ErrSyntax) mockJSON = []byte(`{"sequence_num": 1, /\\/"""}`) @@ -1601,9 +1611,6 @@ func TestWsHandleData(t *testing.T) { mockJSON = []byte(`{"sequence_num": 0, "channel": "status", "events": [{"type": "moo"}]}`) _, err = c.wsHandleData(mockJSON, 0) assert.NoError(t, err) - mockJSON = []byte(`{"sequence_num": 0, "channel": "error", "events": [{"type": "moo"}]}`) - _, err = c.wsHandleData(mockJSON, 0) - assert.NoError(t, err) mockJSON = []byte(`{"sequence_num": 0, "channel": "ticker", "events": ["type": ""}]}`) _, err = c.wsHandleData(mockJSON, 0) assert.ErrorAs(t, err, &targetErr) @@ -1717,7 +1724,7 @@ func TestSubscribeUnsubscribe(t *testing.T) { func TestCheckSubscriptions(t *testing.T) { t.Parallel() - c := &CoinbasePro{ + c := &CoinbasePro{ //nolint:govet // Intentional shadow to avoid future copy/paste mistakes Base: exchange.Base{ Config: &config.Exchange{ Features: &config.FeaturesConfig{ diff --git a/exchanges/coinbasepro/coinbasepro_types.go b/exchanges/coinbasepro/coinbasepro_types.go index 922eaf777d7..b5bb8ebb1c7 100644 --- a/exchanges/coinbasepro/coinbasepro_types.go +++ b/exchanges/coinbasepro/coinbasepro_types.go @@ -298,6 +298,10 @@ type EditOrderPreviewResp struct { AverageFilledPrice float64 `json:"average_filled_price,string"` } +type SingleOrder struct { + Order GetOrderResponse `json:"order"` +} + // GetOrderResponse contains information on an order, returned by GetOrderByID // and IterativeGetAllOrders, and used in GetAllOrdersResp type GetOrderResponse struct { @@ -313,7 +317,7 @@ type GetOrderResponse struct { CompletionPercentage float64 `json:"completion_percentage,string"` FilledSize float64 `json:"filled_size,string"` AverageFilledPrice float64 `json:"average_filled_price,string"` - Fee float64 `json:"fee,string"` + Fee types.Number `json:"fee"` NumberOfFills int64 `json:"num_fills,string"` FilledValue float64 `json:"filled_value,string"` PendingCancel bool `json:"pending_cancel"` @@ -337,6 +341,9 @@ type GetOrderResponse struct { Size float64 `json:"size,string"` ReplaceAcceptTimestamp time.Time `json:"replace_accept_timestamp"` } `json:"edit_history"` + Leverage types.Number `json:"leverage"` + MarginType string `json:"margin_type"` + RetailPortfolioID string `json:"retail_portfolio_id"` } // FillResponse contains fill information, returned by GetFills diff --git a/exchanges/coinbasepro/coinbasepro_websocket.go b/exchanges/coinbasepro/coinbasepro_websocket.go index 937fcb62cf8..c88d07eb91a 100644 --- a/exchanges/coinbasepro/coinbasepro_websocket.go +++ b/exchanges/coinbasepro/coinbasepro_websocket.go @@ -50,7 +50,9 @@ var subscriptionNames = map[string]string{ var defaultSubscriptions = subscription.List{ {Enabled: true, Channel: subscription.HeartbeatChannel}, - {Enabled: true, Channel: "status"}, + // Subscriptions to status return an "authentication failure" error, despite the endpoint not being authenticated + // and other authenticated channels working fine. + {Enabled: false, Channel: "status"}, {Enabled: true, Asset: asset.Spot, Channel: subscription.TickerChannel}, {Enabled: true, Asset: asset.Spot, Channel: subscription.CandlesChannel}, {Enabled: true, Asset: asset.Spot, Channel: subscription.AllTradesChannel}, @@ -108,6 +110,10 @@ func (c *CoinbasePro) wsReadData() { // wsHandleData handles all the websocket data coming from the websocket connection func (c *CoinbasePro) wsHandleData(respRaw []byte, seqCount uint64) (string, error) { var warnString string + ertype, _, _, err := jsonparser.Get(respRaw, "type") + if err == nil && string(ertype) == "error" { + return warnString, errors.New(string(respRaw)) + } seqData, _, _, err := jsonparser.Get(respRaw, "sequence_num") if err != nil { return warnString, err @@ -140,8 +146,6 @@ func (c *CoinbasePro) wsHandleData(respRaw []byte, seqCount uint64) (string, err return warnString, err } c.Websocket.DataHandler <- wsStatus - case "error": - c.Websocket.DataHandler <- errors.New(string(respRaw)) case "ticker", "ticker_batch": wsTicker := []WebsocketTickerHolder{} err = json.Unmarshal(data, &wsTicker) diff --git a/exchanges/coinbasepro/coinbasepro_wrapper.go b/exchanges/coinbasepro/coinbasepro_wrapper.go index 539f5989e39..665515258f8 100644 --- a/exchanges/coinbasepro/coinbasepro_wrapper.go +++ b/exchanges/coinbasepro/coinbasepro_wrapper.go @@ -555,7 +555,7 @@ func (c *CoinbasePro) SubmitOrder(ctx context.Context, s *order.Submit) (*order. if err != nil { return nil, err } - subResp.Fee = feeResp.TotalFees + subResp.Fee = feeResp.Order.TotalFees } return subResp, nil } @@ -651,7 +651,7 @@ func (c *CoinbasePro) GetOrderInfo(ctx context.Context, orderID string, pair cur if err != nil { return nil, err } - response := c.getOrderRespToOrderDetail(genOrderDetail, pair, assetItem) + response := c.getOrderRespToOrderDetail(&genOrderDetail.Order, pair, assetItem) fillData, err := c.GetFills(ctx, orderID, "", "", time.Time{}, time.Now(), 2<<15-1) if err != nil { return nil, err @@ -819,7 +819,11 @@ func (c *CoinbasePro) GetActiveOrders(ctx context.Context, req *order.MultiOrder } orders := make([]order.Detail, len(respOrders)) for i := range respOrders { - orderRec := c.getOrderRespToOrderDetail(&respOrders[i], req.Pairs[i], asset.Spot) + tempPair, err := currency.NewPairFromString(respOrders[i].ProductID) + if err != nil { + return nil, err + } + orderRec := c.getOrderRespToOrderDetail(&respOrders[i], tempPair, asset.Spot) orders[i] = *orderRec } return req.Filter(c.Name, orders), nil @@ -864,7 +868,11 @@ func (c *CoinbasePro) GetOrderHistory(ctx context.Context, req *order.MultiOrder } orders := make([]order.Detail, len(ord)) for i := range ord { - singleOrder := c.getOrderRespToOrderDetail(&ord[i], req.Pairs[0], req.AssetType) + tempPair, err := currency.NewPairFromString(ord[i].ProductID) + if err != nil { + return nil, err + } + singleOrder := c.getOrderRespToOrderDetail(&ord[i], tempPair, req.AssetType) orders[i] = *singleOrder } return req.Filter(c.Name, orders), nil From 7e30794a8081e42166fefe7607909024832f072b Mon Sep 17 00:00:00 2001 From: Samuel Reid <43227667+cranktakular@users.noreply.github.com> Date: Tue, 3 Sep 2024 13:18:24 +1000 Subject: [PATCH 65/79] Lint fix --- exchanges/coinbasepro/coinbasepro_types.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/exchanges/coinbasepro/coinbasepro_types.go b/exchanges/coinbasepro/coinbasepro_types.go index b5bb8ebb1c7..f0f66aa227d 100644 --- a/exchanges/coinbasepro/coinbasepro_types.go +++ b/exchanges/coinbasepro/coinbasepro_types.go @@ -298,12 +298,13 @@ type EditOrderPreviewResp struct { AverageFilledPrice float64 `json:"average_filled_price,string"` } +// SingleOrder provides information on an order in a format that the exchange provides for the GetOrderByID function type SingleOrder struct { Order GetOrderResponse `json:"order"` } -// GetOrderResponse contains information on an order, returned by GetOrderByID -// and IterativeGetAllOrders, and used in GetAllOrdersResp +// GetOrderResponse contains information on an order, returned by GetOrderByID IterativeGetAllOrders, and used in +// GetAllOrdersResp type GetOrderResponse struct { OrderID string `json:"order_id"` ProductID string `json:"product_id"` From 329e039470fc3a6e9cbad02a8bebb9a817d1ad59 Mon Sep 17 00:00:00 2001 From: Samuel Reid <43227667+cranktakular@users.noreply.github.com> Date: Tue, 17 Sep 2024 11:49:24 +1000 Subject: [PATCH 66/79] Glorious fixes --- .../exchange_wrapper_standards_test.go | 19 +- config/config.go | 5 +- config/config_test.go | 4 +- config_example.json | 203 +++++++++++------- exchanges/coinbasepro/coinbasepro.go | 82 ++++--- exchanges/coinbasepro/coinbasepro_test.go | 9 +- 6 files changed, 174 insertions(+), 148 deletions(-) diff --git a/cmd/exchange_wrapper_standards/exchange_wrapper_standards_test.go b/cmd/exchange_wrapper_standards/exchange_wrapper_standards_test.go index 21fb3a2ca44..db596098a13 100644 --- a/cmd/exchange_wrapper_standards/exchange_wrapper_standards_test.go +++ b/cmd/exchange_wrapper_standards/exchange_wrapper_standards_test.go @@ -23,7 +23,6 @@ import ( "github.com/thrasher-corp/gocryptotrader/exchanges/fundingrate" "github.com/thrasher-corp/gocryptotrader/exchanges/futures" "github.com/thrasher-corp/gocryptotrader/exchanges/kline" - "github.com/thrasher-corp/gocryptotrader/exchanges/lbank" "github.com/thrasher-corp/gocryptotrader/exchanges/margin" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/request" @@ -86,6 +85,8 @@ func setupExchange(ctx context.Context, t *testing.T, name string, cfg *config.C t.Fatalf("Cannot setup %v GetExchangeConfig %v", name, err) } exch.SetDefaults() + exchCfg.API.AuthenticatedSupport = true + exchCfg.API.Credentials = getExchangeCredentials(name) err = exch.Setup(exchCfg) if err != nil { t.Fatalf("Cannot setup %v exchange Setup %v", name, err) @@ -95,22 +96,6 @@ func setupExchange(ctx context.Context, t *testing.T, name string, cfg *config.C t.Fatalf("Cannot setup %v UpdateTradablePairs %v", name, err) } b := exch.GetBase() - // Idiosyncratic execution flow since Setup must be run before UpdateTradablePairs, but Coinbase will fail if - // invalid credentials have been set, so we must set them after UpdateTradablePairs - creds := getExchangeCredentials(name) - b.SetCredentials(creds.Key, creds.Secret, creds.ClientID, creds.Subaccount, creds.Subaccount, - creds.OTPSecret) - b.API.AuthenticatedSupport = true - // Lbank usually runs this during setup, but if keys aren't set then, it will fail, so we have to manually - // recreate that here - lbankExch, ok := exch.(*lbank.Lbank) - if ok { - err = lbankExch.LoadPrivKey(ctx) - if err != nil { - t.Fatalf("Cannot setup %v LoadPrivKey %v", name, err) - } - b.API.AuthenticatedSupport = true - } assets := b.CurrencyPairs.GetAssetTypes(false) if len(assets) == 0 { t.Fatalf("Cannot setup %v, exchange has no assets", name) diff --git a/config/config.go b/config/config.go index a0710b58f24..cd142264e1a 100644 --- a/config/config.go +++ b/config/config.go @@ -30,8 +30,7 @@ import ( ) var ( - // ErrExchangeConfigIsNil defines an error when the config is nil - ErrExchangeConfigIsNil = errors.New("exchange config is nil") + errExchangeConfigIsNil = errors.New("exchange config is nil") errPairsManagerIsNil = errors.New("currency pairs manager is nil") ) @@ -1891,7 +1890,7 @@ func (c *Config) GetDataPath(elem ...string) string { // Validate checks if exchange config is valid func (c *Exchange) Validate() error { if c == nil { - return ErrExchangeConfigIsNil + return errExchangeConfigIsNil } if c.ConnectionMonitorDelay <= 0 { diff --git a/config/config_test.go b/config/config_test.go index 96eefd0c2dd..76c63c5da56 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -2235,8 +2235,8 @@ func TestMigrateConfig(t *testing.T) { func TestExchangeConfigValidate(t *testing.T) { err := (*Exchange)(nil).Validate() - if !errors.Is(err, ErrExchangeConfigIsNil) { - t.Fatalf("received: '%v' but expected: '%v'", err, ErrExchangeConfigIsNil) + if !errors.Is(err, errExchangeConfigIsNil) { + t.Fatalf("received: '%v' but expected: '%v'", err, errExchangeConfigIsNil) } err = (&Exchange{}).Validate() diff --git a/config_example.json b/config_example.json index 67be13645e7..7d25a39ab17 100644 --- a/config_example.json +++ b/config_example.json @@ -1209,85 +1209,142 @@ ] }, { - "name": "CoinbasePro", - "enabled": true, - "verbose": false, - "httpTimeout": 15000000000, - "websocketResponseCheckTimeout": 30000000, - "websocketResponseMaxLimit": 7000000000, - "websocketTrafficTimeout": 30000000000, - "websocketOrderbookBufferLimit": 5, - "baseCurrencies": "USD,GBP,EUR", - "currencyPairs": { - "requestFormat": { - "uppercase": true, - "delimiter": "-" - }, - "configFormat": { - "uppercase": true, - "delimiter": "-" - }, - "useGlobalFormat": true, - "assetTypes": [ - "spot" - ], - "pairs": { - "spot": { - "enabled": "BTC-USD", - "available": "ETC-GBP,CVC-USDC,LINK-ETH,KNC-BTC,GNT-USDC,EOS-BTC,ETC-BTC,LTC-BTC,ZRX-USD,XRP-EUR,ZRX-EUR,ATOM-USD,BTC-USD,LTC-EUR,XRP-USD,MANA-USDC,XRP-BTC,LTC-GBP,DAI-USD,COMP-BTC,ETH-DAI,XTZ-USD,DASH-BTC,OMG-BTC,BTC-USDC,BCH-USD,DNT-USDC,COMP-USD,LOOM-USDC,OMG-GBP,BCH-GBP,ZRX-BTC,ATOM-BTC,EOS-EUR,ETH-USD,XLM-EUR,KNC-USD,OXT-USD,ETC-EUR,OMG-USD,BTC-GBP,OMG-EUR,DASH-USD,MKR-BTC,XTZ-BTC,BAT-ETH,REP-USD,XLM-BTC,ETH-USDC,REP-BTC,LTC-USD,ZEC-BTC,ZEC-USDC,EOS-USD,MKR-USD,ALGO-USD,LINK-USD,BCH-EUR,XLM-USD,ETH-GBP,ETC-USD,ETH-EUR,BCH-BTC,BTC-EUR,ETH-BTC,DAI-USDC,BAT-USDC" + "name": "CoinbasePro", + "enabled": false, + "verbose": false, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, + "connectionMonitorDelay": 2000000000, + "baseCurrencies": "USD,GBP,EUR", + "currencyPairs": { + "bypassConfigFormatUpgrades": false, + "requestFormat": { + "uppercase": true, + "delimiter": "-" + }, + "configFormat": { + "uppercase": true, + "delimiter": "-" + }, + "useGlobalFormat": true, + "pairs": { + "futures": { + "assetEnabled": true, + "enabled": "GOL-28JAN25-CDE", + "available": "BIT-27SEP24-CDE,ET-27SEP24-CDE,GOL-27NOV24-CDE,BIT-25OCT24-CDE,NOL-19SEP24-CDE,BCH-27SEP24-CDE,LC-27SEP24-CDE,AVA-27SEP24-CDE,LNK-27SEP24-CDE,LC-25OCT24-CDE,DOT-27SEP24-CDE,DOG-27SEP24-CDE,SHB-27SEP24-CDE,BIT-29NOV24-CDE,AVA-25OCT24-CDE,ET-25OCT24-CDE,DOT-25OCT24-CDE,ET-29NOV24-CDE,DOG-29NOV24-CDE,GOL-28JAN25-CDE,GOL-31MAR25-CDE,DOG-25OCT24-CDE,DOT-29NOV24-CDE,LC-29NOV24-CDE,LNK-25OCT24-CDE,BCH-29NOV24-CDE,LNK-29NOV24-CDE,NOL-19NOV24-CDE,BCH-25OCT24-CDE,NOL-21OCT24-CDE,SHB-25OCT24-CDE,AVA-29NOV24-CDE,SHB-29NOV24-CDE" + }, + "spot": { + "assetEnabled": true, + "enabled": "BTC-USD,BTC-USDC,USDT-USD,ETH-USD,ETH-USDC,SOL-USD", + "available": "BTC-USD,BTC-USDC,ETH-USDC,ETH-USD,USDT-USD,SOL-USDC,SOL-USD,USDT-USDC,FET-USDC,FET-USD,BTC-USDT,XRP-USD,XRP-USDC,DOGE-USDC,DOGE-USD,BONK-USDC,BONK-USD,USDT-EUR,ETH-USDT,BTC-EUR,USDC-EUR,AAVE-USDC,AAVE-USD,SUI-USDC,SUI-USD,LINK-USD,LINK-USDC,LTC-USD,LTC-USDC,SHIB-USDC,SHIB-USD,BTC-GBP,JASMY-USD,JASMY-USDC,ETH-EUR,AVAX-USDC,AVAX-USD,NEAR-USD,NEAR-USDC,ONDO-USD,ONDO-USDC,ADA-USD,ADA-USDC,SUPER-USDC,SUPER-USD,UNI-USD,UNI-USDC,RARE-USD,RARE-USDC,RNDR-USD,RNDR-USDC,INJ-USDC,INJ-USD,SEI-USDC,SEI-USD,HBAR-USDC,HBAR-USD,XLM-USDC,XLM-USD,SOL-EUR,HNT-USD,HNT-USDC,BCH-USD,BCH-USDC,STX-USDC,STX-USD,TRB-USDC,TRB-USD,PNG-USD,PNG-USDC,MATIC-USDC,MATIC-USD,MKR-USD,MKR-USDC,SOL-USDT,APT-USDC,APT-USD,EURC-USDC,ICP-USD,ICP-USDC,IDEX-USDC,IDEX-USD,AERO-USDC,AERO-USD,ETH-GBP,DAI-USD,DAI-USDC,00-USD,00-USDC,USDC-GBP,MASK-USD,MASK-USDC,USDT-GBP,RENDER-USD,RENDER-USDC,ZRO-USDC,ZRO-USD,CRV-USDC,CRV-USD,DOT-USDC,DOT-USD,TIA-USDC,TIA-USD,GRT-USD,GRT-USDC,FIL-USDC,FIL-USD,IMX-USDC,IMX-USD,OP-USD,OP-USDC,JTO-USD,JTO-USDC,FET-USDT,LDO-USD,LDO-USDC,SOL-GBP,ARB-USD,ARB-USDC,MSOL-USDC,MSOL-USD,GFI-USD,GFI-USDC,MINA-USD,MINA-USDC,ATOM-USD,ATOM-USDC,QNT-USDC,QNT-USD,NEAR-USDT,PRO-USD,PRO-USDC,FORT-USDC,FORT-USD,PRIME-USD,PRIME-USDC,ZEC-USDC,ZEC-USD,TVK-USD,TVK-USDC,XRP-USDT,DOGE-USDT,1INCH-USDC,1INCH-USD,AIOZ-USD,AIOZ-USDC,ABT-USD,ABT-USDC,ROSE-USDC,ROSE-USD,SKL-USDC,SKL-USD,COMP-USDC,COMP-USD,ZETA-USD,ZETA-USDC,TRU-USD,TRU-USDC,AXL-USDC,AXL-USD,AKT-USDC,AKT-USD,XRP-EUR,KARRAT-USD,KARRAT-USDC,CBETH-USD,CBETH-USDC,BICO-USDT,VARA-USD,VARA-USDC,AAVE-EUR,SUSHI-USD,SUSHI-USDC,ALGO-USDC,ALGO-USD,BICO-USDC,BICO-USD,VELO-USDC,VELO-USD,UMA-USD,UMA-USDC,ETC-USDC,ETC-USD,LRDS-USDC,LRDS-USD,ENS-USD,ENS-USDC,LTC-EUR,VET-USDC,VET-USD,SYN-USD,SYN-USDC,LPT-USDC,LPT-USD,OCEAN-USDC,OCEAN-USD,LQTY-USDC,LQTY-USD,AMP-USDC,AMP-USD,BIGTIME-USD,BIGTIME-USDC,LRC-USD,LRC-USDC,ACH-USD,ACH-USDC,VOXEL-USDC,VOXEL-USD,SAND-USDC,SAND-USD,MPL-USD,MPL-USDC,SHIB-EUR,GTC-USDC,GTC-USD,ARKM-USD,ARKM-USDC,COTI-USD,COTI-USDC,ILV-USD,ILV-USDC,FLR-USD,FLR-USDC,ICP-USDT,LCX-USD,LCX-USDC,MOBILE-USD,MOBILE-USDC,DRIFT-USDC,DRIFT-USD,DOGE-EUR,SHIB-USDT,APE-USD,APE-USDC,GODS-USDC,GODS-USD,EOS-USD,EOS-USDC,XTZ-USDC,XTZ-USD,BLUR-USD,BLUR-USDC,ZRX-USDC,ZRX-USD,AUDIO-USD,AUDIO-USDC,ANKR-USD,ANKR-USDC,UNI-EUR,GST-USD,GST-USDC,ORCA-USD,ORCA-USDC,PRQ-USDC,PRQ-USD,AVAX-EUR,SHPING-USDC,SHPING-USD,LOKA-USDC,LOKA-USD,NEON-USD,NEON-USDC,API3-USDC,API3-USD,STRK-USD,STRK-USDC,XCN-USD,XCN-USDC,AVAX-USDT,ADA-EUR,LTC-GBP,WBTC-USD,WBTC-USDC,METIS-USDC,METIS-USD,HIGH-USDC,HIGH-USD,EGLD-USD,EGLD-USDC,ETH-DAI,DESO-USDC,DESO-USD,BIT-USD,BIT-USDC,YFI-USDC,YFI-USD,MASK-EUR,CRO-USDC,CRO-USD,HOPR-USD,HOPR-USDC,CHZ-USD,CHZ-USDC,MATIC-EUR,JASMY-USDT,RBN-USDC,RBN-USD,ADA-USDT,RARI-USDC,RARI-USD,OP-USDT,POLS-USD,POLS-USDC,DIMO-USD,DIMO-USDC,OGN-USD,OGN-USDC,BAL-USD,BAL-USDC,SNX-USD,SNX-USDC,TRAC-USD,TRAC-USDC,QI-USD,QI-USDC,IOTX-USDC,IOTX-USD,MNDE-USD,MNDE-USDC,ACX-USDC,ACX-USD,LINK-EUR,MAGIC-USDC,MAGIC-USD,ALEPH-USDC,ALEPH-USD,T-USDC,T-USD,RONIN-USD,RONIN-USDC,RPL-USD,RPL-USDC,AAVE-GBP,CLV-USDC,CLV-USD,MANA-USDC,MANA-USD,BLZ-USDC,BLZ-USD,AVT-USD,AVT-USDC,PYR-USDC,PYR-USD,DYP-USDC,DYP-USD,OXT-USDC,OXT-USD,A8-USD,A8-USDC,SWFTC-USDC,SWFTC-USD,MATIC-USDT,KAVA-USD,KAVA-USDC,HONEY-USD,HONEY-USDC,ICP-EUR,NCT-USD,NCT-USDC,BLAST-USDC,BLAST-USD,NMR-USD,NMR-USDC,DASH-USDC,DASH-USD,DAR-USD,DAR-USDC,BTRST-USD,BTRST-USDC,CGLD-USDC,CGLD-USD,SPA-USDC,SPA-USD,RAD-USDC,RAD-USD,DNT-USD,DNT-USDC,PIRATE-USDC,PIRATE-USD,MDT-USD,MDT-USDC,CVX-USD,CVX-USDC,APE-EUR,BCH-EUR,AUCTION-USD,AUCTION-USDC,RNDR-USDT,WCFG-USDC,WCFG-USD,APT-USDT,MASK-USDT,CTSI-USD,CTSI-USDC,POND-USDC,POND-USD,DOT-EUR,ADA-GBP,INDEX-USD,INDEX-USDC,SPELL-USDC,SPELL-USD,BAT-USD,BAT-USDC,SUKU-USDC,SUKU-USD,ALGO-EUR,SEAM-USD,SEAM-USDC,BOBA-USDC,BOBA-USD,ALGO-GBP,ACS-USDC,ACS-USD,MANA-EUR,MLN-USD,MLN-USDC,TNSR-USDC,TNSR-USD,FIL-EUR,FLOW-USDC,FLOW-USD,XYO-USDC,XYO-USD,GUSD-USD,GUSD-USDC,VTHO-USD,VTHO-USDC,RLC-USDC,RLC-USD,1INCH-EUR,ALCX-USDC,ALCX-USD,CORECHAIN-USD,CORECHAIN-USDC,SD-USD,SD-USDC,STX-USDT,FX-USDC,FX-USD,XLM-EUR,SHDW-USDC,SHDW-USD,HFT-USD,HFT-USDC,STORJ-USDC,STORJ-USD,SAFE-USDC,SAFE-USD,FIDA-USD,FIDA-USDC,MATH-USD,MATH-USDC,NKN-USDC,NKN-USD,PYUSD-USDC,PYUSD-USD,CELR-USDC,CELR-USD,AXS-USD,AXS-USDC,ALICE-USDC,ALICE-USD,ICP-GBP,GLM-USDC,GLM-USD,FOX-USDC,FOX-USD,AURORA-USDC,AURORA-USD,FARM-USD,FARM-USDC,EOS-EUR,DOT-USDT,HBAR-USDT,GMT-USDC,GMT-USD,GRT-EUR,CTX-USDC,CTX-USD,CHZ-EUR,ETC-EUR,ORN-USDC,ORN-USD,MEDIA-USD,MEDIA-USDC,ATOM-EUR,ASM-USD,ASM-USDC,AGLD-USDC,AGLD-USD,LINK-USDT,KNC-USD,KNC-USDC,DOGE-GBP,SAND-USDT,INV-USDC,INV-USD,GAL-USD,GAL-USDC,PERP-USD,PERP-USDC,POWR-USDC,POWR-USD,KSM-USD,KSM-USDC,BAND-USD,BAND-USDC,ELA-USDC,ELA-USD,G-USD,G-USDC,PLU-USDC,PLU-USD,ROSE-USDT,QNT-USDT,APE-USDT,1INCH-GBP,REQ-USD,REQ-USDC,AXS-USDT,WAXL-USD,WAXL-USDC,ZEN-USD,ZEN-USDC,BCH-GBP,BADGER-USDC,BADGER-USD,FORTH-USD,FORTH-USDC,FIL-GBP,CVC-USDC,CVC-USD,DOT-GBP,LIT-USDC,LIT-USD,CRV-EUR,LINK-GBP,AST-USDC,AST-USD,FIS-USDC,FIS-USD,ENS-EUR,ATOM-USDT,IMX-USDT,SHIB-GBP,MATIC-GBP,DIA-USD,DIA-USDC,ERN-USDC,ERN-USD,OSMO-USD,OSMO-USDC,CHZ-USDT,ARPA-USD,ARPA-USDC,RNDR-EUR,TIME-USD,TIME-USDC,ENS-USDT,OMNI-USD,OMNI-USDC,ATOM-GBP,C98-USD,C98-USDC,PUNDIX-USD,PUNDIX-USDC,MUSE-USD,MUSE-USDC,LRC-USDT,MINA-EUR,GHST-USD,GHST-USDC,ETC-GBP,AXS-EUR,LSETH-USDC,LSETH-USD,GNO-USD,GNO-USDC,BNT-USDC,BNT-USD,AERGO-USD,AERGO-USDC,SNX-EUR,CHZ-GBP,UNI-GBP,FLOW-USDT,BAT-EUR,MINA-USDT,DEXT-USDC,DEXT-USD,XLM-USDT,GYEN-USDC,GYEN-USD,BICO-EUR,CRV-GBP,ANKR-EUR,CGLD-EUR,XTZ-GBP,MASK-GBP,KRL-USDC,KRL-USD,CRO-EUR,ANKR-GBP,XTZ-EUR,GRT-GBP,GMT-USDT,SNX-GBP,STG-USD,STG-USDC,CRO-USDT,PAX-USD,PAX-USDC,CGLD-GBP,SOL-ETH,ETH-BTC,ADA-ETH,SOL-BTC,AAVE-BTC,WBTC-BTC,ZEC-BTC,CBETH-ETH,LINK-ETH,LTC-BTC,UNI-BTC,BCH-BTC,DOGE-BTC,AVAX-BTC,ADA-BTC,LSETH-ETH,MKR-BTC,GRT-BTC,MATIC-BTC,COMP-BTC,LRC-BTC,DOT-BTC,XLM-BTC,FIL-BTC,ICP-BTC,EOS-BTC,BAT-ETH,LINK-BTC,CRV-BTC,ATOM-BTC,BAL-BTC,ALGO-BTC,1INCH-BTC,BAT-BTC,MANA-ETH,ETC-BTC,DASH-BTC,SNX-BTC,YFI-BTC,XTZ-BTC,AXS-BTC,ANKR-BTC,MANA-BTC,CGLD-BTC" + } } - } - }, - "api": { - "authenticatedSupport": false, - "authenticatedWebsocketApiSupport": false, - "endpoints": { - "url": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "urlSecondary": "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API", - "websocketURL": "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API" }, - "credentials": { - "key": "Key", - "secret": "Secret", - "clientID": "ClientID" - }, - "credentialsValidator": { - "requiresKey": true, - "requiresSecret": true, - "requiresClientID": true, - "requiresBase64DecodeSecret": true - } - }, - "features": { - "supports": { - "restAPI": true, - "restCapabilities": { - "autoPairUpdates": true + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "credentials": { + "key": "", + "secret": "", + "clientID": "" }, - "websocketAPI": true, - "websocketCapabilities": {} + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true, + "requiresClientID": true, + "requiresBase64DecodeSecret": true + }, + "urlEndpoints": { + "RestSandboxURL": "https://api-public.sandbox.exchange.coinbase.com/", + "RestSpotURL": "https://api.coinbase.com", + "WebsocketSpotURL": "wss://advanced-trade-ws.coinbase.com" + } }, - "enabled": { - "autoPairUpdates": true, - "websocketAPI": false + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "autoPairUpdates": true, + "fundingRateFetching": false + }, + "websocketAPI": true, + "websocketCapabilities": { + "fundingRateFetching": false + } + }, + "enabled": { + "autoPairUpdates": true, + "websocketAPI": true, + "saveTradeData": false, + "tradeFeed": false, + "fillsFeed": false + }, + "subscriptions": [ + { + "enabled": true, + "channel": "heartbeat" + }, + { + "enabled": false, + "channel": "status", + "authenticated": true + }, + { + "enabled": true, + "channel": "ticker", + "asset": "spot" + }, + { + "enabled": true, + "channel": "candles", + "asset": "spot" + }, + { + "enabled": true, + "channel": "allTrades", + "asset": "spot" + }, + { + "enabled": true, + "channel": "orderbook", + "asset": "spot" + }, + { + "enabled": true, + "channel": "account", + "authenticated": true + }, + { + "enabled": false, + "channel": "ticker_batch", + "asset": "spot" + } + ] + }, + "bankAccounts": [ + { + "enabled": false, + "bankName": "", + "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ], + "orderbook": { + "verificationBypass": false, + "websocketBufferLimit": 5, + "websocketBufferEnabled": false, + "publishPeriod": 10000000000 } }, - "bankAccounts": [ - { - "enabled": false, - "bankName": "", - "bankAddress": "", - "bankPostalCode": "", - "bankPostalCity": "", - "bankCountry": "", - "accountName": "", - "accountNumber": "", - "swiftCode": "", - "iban": "", - "supportedCurrencies": "" - } - ] - }, { "name": "Deribit", "enabled": true, diff --git a/exchanges/coinbasepro/coinbasepro.go b/exchanges/coinbasepro/coinbasepro.go index 52405501d08..10dca858f79 100644 --- a/exchanges/coinbasepro/coinbasepro.go +++ b/exchanges/coinbasepro/coinbasepro.go @@ -62,8 +62,6 @@ const ( coinbaseV2 = "/v2/" coinbaseNotifications = "notifications" coinbaseUser = "user" - coinbaseUsers = "users" - coinbaseAuth = "auth" coinbaseAddresses = "addresses" coinbaseTransactions = "transactions" coinbaseDeposits = "deposits" @@ -82,10 +80,6 @@ const ( coinbaseConversionRate = "conversion-rate" coinbaseMarket = "market" - pageNone = "" - pageBefore = "before" - pageAfter = "after" - unknownContract = "UNKNOWN_CONTRACT_EXPIRY_TYPE" granUnknown = "UNKNOWN_GRANULARITY" granOneMin = "ONE_MINUTE" granFiveMin = "FIVE_MINUTE" @@ -120,48 +114,46 @@ const ( BestCaseMakerFee = 0 StablePairMakerFee = 0 WorstCaseStablePairTakerFee = 0.000045 - BestCaseStablePairTakerFee = 0.00001 ) var ( - errAccountIDEmpty = errors.New("account id cannot be empty") - errClientOrderIDEmpty = errors.New("client order id cannot be empty") - errProductIDEmpty = errors.New("product id cannot be empty") - errOrderIDEmpty = errors.New("order ids cannot be empty") - errOpenPairWithOtherTypes = errors.New("cannot pair open orders with other order types") - errSizeAndPriceZero = errors.New("size and price cannot both be 0") - errCurrencyEmpty = errors.New("currency cannot be empty") - errCurrWalletConflict = errors.New("exactly one of walletID and currency must be specified") - errWalletIDEmpty = errors.New("wallet id cannot be empty") - errAddressIDEmpty = errors.New("address id cannot be empty") - errTransactionTypeEmpty = errors.New("transaction type cannot be empty") - errToEmpty = errors.New("to cannot be empty") - errAmountEmpty = errors.New("amount cannot be empty") - errTransactionIDEmpty = errors.New("transaction id cannot be empty") - errPaymentMethodEmpty = errors.New("payment method cannot be empty") - errDepositIDEmpty = errors.New("deposit id cannot be empty") - errInvalidPriceType = errors.New("price type must be spot, buy, or sell") - errInvalidOrderType = errors.New("order type must be market, limit, or stop") - errNoMatchingWallets = errors.New("no matching wallets returned") - errOrderModFailNoRet = errors.New("order modification failed but no error returned") - errNameEmpty = errors.New("name cannot be empty") - errPortfolioIDEmpty = errors.New("portfolio id cannot be empty") - errFeeTypeNotSupported = errors.New("fee type not supported") - errCantDecodePrivKey = errors.New("cannot decode private key") - errNoWalletForCurrency = errors.New("no wallet found for currency, address creation impossible") - errChannelNameUnknown = errors.New("unknown channel name") - errNoWalletsReturned = errors.New("no wallets returned") - errPayMethodNotFound = errors.New("payment method not found") - errUnknownL2DataType = errors.New("unknown l2update data type") - errUnknownSide = errors.New("unknown side") - errInvalidGranularity = errors.New("invalid granularity") - errOrderFailedToCancel = errors.New("failed to cancel order") - errUnrecognisedStatusType = errors.New("unrecognised status type") - errPairEmpty = errors.New("pair cannot be empty") - errStringConvert = errors.New("unable to convert into string value") - errFloatConvert = errors.New("unable to convert into float64 value") - errWrappedAssetEmpty = errors.New("wrapped asset cannot be empty") - errExpectedOneTickerReturned = errors.New("expected one ticker to be returned") + errAccountIDEmpty = errors.New("account id cannot be empty") + errClientOrderIDEmpty = errors.New("client order id cannot be empty") + errProductIDEmpty = errors.New("product id cannot be empty") + errOrderIDEmpty = errors.New("order ids cannot be empty") + errOpenPairWithOtherTypes = errors.New("cannot pair open orders with other order types") + errSizeAndPriceZero = errors.New("size and price cannot both be 0") + errCurrencyEmpty = errors.New("currency cannot be empty") + errCurrWalletConflict = errors.New("exactly one of walletID and currency must be specified") + errWalletIDEmpty = errors.New("wallet id cannot be empty") + errAddressIDEmpty = errors.New("address id cannot be empty") + errTransactionTypeEmpty = errors.New("transaction type cannot be empty") + errToEmpty = errors.New("to cannot be empty") + errAmountEmpty = errors.New("amount cannot be empty") + errTransactionIDEmpty = errors.New("transaction id cannot be empty") + errPaymentMethodEmpty = errors.New("payment method cannot be empty") + errDepositIDEmpty = errors.New("deposit id cannot be empty") + errInvalidPriceType = errors.New("price type must be spot, buy, or sell") + errInvalidOrderType = errors.New("order type must be market, limit, or stop") + errNoMatchingWallets = errors.New("no matching wallets returned") + errOrderModFailNoRet = errors.New("order modification failed but no error returned") + errNameEmpty = errors.New("name cannot be empty") + errPortfolioIDEmpty = errors.New("portfolio id cannot be empty") + errFeeTypeNotSupported = errors.New("fee type not supported") + errCantDecodePrivKey = errors.New("cannot decode private key") + errNoWalletForCurrency = errors.New("no wallet found for currency, address creation impossible") + errChannelNameUnknown = errors.New("unknown channel name") + errNoWalletsReturned = errors.New("no wallets returned") + errPayMethodNotFound = errors.New("payment method not found") + errUnknownL2DataType = errors.New("unknown l2update data type") + errUnknownSide = errors.New("unknown side") + errInvalidGranularity = errors.New("invalid granularity") + errOrderFailedToCancel = errors.New("failed to cancel order") + errUnrecognisedStatusType = errors.New("unrecognised status type") + errPairEmpty = errors.New("pair cannot be empty") + errStringConvert = errors.New("unable to convert into string value") + errFloatConvert = errors.New("unable to convert into float64 value") + errWrappedAssetEmpty = errors.New("wrapped asset cannot be empty") ) // GetAllAccounts returns information on all trading accounts associated with the API key diff --git a/exchanges/coinbasepro/coinbasepro_test.go b/exchanges/coinbasepro/coinbasepro_test.go index 8f6c4fcb3a7..f281ff817f9 100644 --- a/exchanges/coinbasepro/coinbasepro_test.go +++ b/exchanges/coinbasepro/coinbasepro_test.go @@ -7,7 +7,6 @@ import ( "net/http" "os" "strconv" - "strings" "testing" "time" @@ -1744,14 +1743,8 @@ func TestCheckSubscriptions(t *testing.T) { func TestGetJWT(t *testing.T) { t.Parallel() sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - creds, err := c.GetCredentials(context.Background()) + _, err := c.GetJWT(context.Background(), "") assert.NoError(t, err) - _, err = c.GetJWT(context.Background(), "") - if strings.HasPrefix(creds.Secret, "-----BEGIN EC PRIVATE KEY-----\n") { - assert.NoError(t, err) - } else { - assert.ErrorIs(t, err, errCantDecodePrivKey) - } } func exchangeBaseHelper(c *CoinbasePro) error { From d797e69722757403db4b5a0a56d6b9f3b9c6d519 Mon Sep 17 00:00:00 2001 From: Samuel Reid <43227667+cranktakular@users.noreply.github.com> Date: Tue, 17 Sep 2024 12:08:34 +1000 Subject: [PATCH 67/79] Json formatting --- config_example.json | 248 ++++++++++++++++++++++---------------------- 1 file changed, 124 insertions(+), 124 deletions(-) diff --git a/config_example.json b/config_example.json index 7d25a39ab17..4dfe2ad9da8 100644 --- a/config_example.json +++ b/config_example.json @@ -1209,142 +1209,142 @@ ] }, { - "name": "CoinbasePro", - "enabled": false, - "verbose": false, - "httpTimeout": 15000000000, - "websocketResponseCheckTimeout": 30000000, - "websocketResponseMaxLimit": 7000000000, - "websocketTrafficTimeout": 30000000000, - "connectionMonitorDelay": 2000000000, - "baseCurrencies": "USD,GBP,EUR", - "currencyPairs": { - "bypassConfigFormatUpgrades": false, - "requestFormat": { - "uppercase": true, - "delimiter": "-" - }, - "configFormat": { - "uppercase": true, - "delimiter": "-" + "name": "CoinbasePro", + "enabled": false, + "verbose": false, + "httpTimeout": 15000000000, + "websocketResponseCheckTimeout": 30000000, + "websocketResponseMaxLimit": 7000000000, + "websocketTrafficTimeout": 30000000000, + "connectionMonitorDelay": 2000000000, + "baseCurrencies": "USD,GBP,EUR", + "currencyPairs": { + "bypassConfigFormatUpgrades": false, + "requestFormat": { + "uppercase": true, + "delimiter": "-" + }, + "configFormat": { + "uppercase": true, + "delimiter": "-" + }, + "useGlobalFormat": true, + "pairs": { + "futures": { + "assetEnabled": true, + "enabled": "GOL-28JAN25-CDE", + "available": "BIT-27SEP24-CDE,ET-27SEP24-CDE,GOL-27NOV24-CDE,BIT-25OCT24-CDE,NOL-19SEP24-CDE,BCH-27SEP24-CDE,LC-27SEP24-CDE,AVA-27SEP24-CDE,LNK-27SEP24-CDE,LC-25OCT24-CDE,DOT-27SEP24-CDE,DOG-27SEP24-CDE,SHB-27SEP24-CDE,BIT-29NOV24-CDE,AVA-25OCT24-CDE,ET-25OCT24-CDE,DOT-25OCT24-CDE,ET-29NOV24-CDE,DOG-29NOV24-CDE,GOL-28JAN25-CDE,GOL-31MAR25-CDE,DOG-25OCT24-CDE,DOT-29NOV24-CDE,LC-29NOV24-CDE,LNK-25OCT24-CDE,BCH-29NOV24-CDE,LNK-29NOV24-CDE,NOL-19NOV24-CDE,BCH-25OCT24-CDE,NOL-21OCT24-CDE,SHB-25OCT24-CDE,AVA-29NOV24-CDE,SHB-29NOV24-CDE" }, - "useGlobalFormat": true, - "pairs": { - "futures": { - "assetEnabled": true, - "enabled": "GOL-28JAN25-CDE", - "available": "BIT-27SEP24-CDE,ET-27SEP24-CDE,GOL-27NOV24-CDE,BIT-25OCT24-CDE,NOL-19SEP24-CDE,BCH-27SEP24-CDE,LC-27SEP24-CDE,AVA-27SEP24-CDE,LNK-27SEP24-CDE,LC-25OCT24-CDE,DOT-27SEP24-CDE,DOG-27SEP24-CDE,SHB-27SEP24-CDE,BIT-29NOV24-CDE,AVA-25OCT24-CDE,ET-25OCT24-CDE,DOT-25OCT24-CDE,ET-29NOV24-CDE,DOG-29NOV24-CDE,GOL-28JAN25-CDE,GOL-31MAR25-CDE,DOG-25OCT24-CDE,DOT-29NOV24-CDE,LC-29NOV24-CDE,LNK-25OCT24-CDE,BCH-29NOV24-CDE,LNK-29NOV24-CDE,NOL-19NOV24-CDE,BCH-25OCT24-CDE,NOL-21OCT24-CDE,SHB-25OCT24-CDE,AVA-29NOV24-CDE,SHB-29NOV24-CDE" - }, - "spot": { - "assetEnabled": true, - "enabled": "BTC-USD,BTC-USDC,USDT-USD,ETH-USD,ETH-USDC,SOL-USD", - "available": "BTC-USD,BTC-USDC,ETH-USDC,ETH-USD,USDT-USD,SOL-USDC,SOL-USD,USDT-USDC,FET-USDC,FET-USD,BTC-USDT,XRP-USD,XRP-USDC,DOGE-USDC,DOGE-USD,BONK-USDC,BONK-USD,USDT-EUR,ETH-USDT,BTC-EUR,USDC-EUR,AAVE-USDC,AAVE-USD,SUI-USDC,SUI-USD,LINK-USD,LINK-USDC,LTC-USD,LTC-USDC,SHIB-USDC,SHIB-USD,BTC-GBP,JASMY-USD,JASMY-USDC,ETH-EUR,AVAX-USDC,AVAX-USD,NEAR-USD,NEAR-USDC,ONDO-USD,ONDO-USDC,ADA-USD,ADA-USDC,SUPER-USDC,SUPER-USD,UNI-USD,UNI-USDC,RARE-USD,RARE-USDC,RNDR-USD,RNDR-USDC,INJ-USDC,INJ-USD,SEI-USDC,SEI-USD,HBAR-USDC,HBAR-USD,XLM-USDC,XLM-USD,SOL-EUR,HNT-USD,HNT-USDC,BCH-USD,BCH-USDC,STX-USDC,STX-USD,TRB-USDC,TRB-USD,PNG-USD,PNG-USDC,MATIC-USDC,MATIC-USD,MKR-USD,MKR-USDC,SOL-USDT,APT-USDC,APT-USD,EURC-USDC,ICP-USD,ICP-USDC,IDEX-USDC,IDEX-USD,AERO-USDC,AERO-USD,ETH-GBP,DAI-USD,DAI-USDC,00-USD,00-USDC,USDC-GBP,MASK-USD,MASK-USDC,USDT-GBP,RENDER-USD,RENDER-USDC,ZRO-USDC,ZRO-USD,CRV-USDC,CRV-USD,DOT-USDC,DOT-USD,TIA-USDC,TIA-USD,GRT-USD,GRT-USDC,FIL-USDC,FIL-USD,IMX-USDC,IMX-USD,OP-USD,OP-USDC,JTO-USD,JTO-USDC,FET-USDT,LDO-USD,LDO-USDC,SOL-GBP,ARB-USD,ARB-USDC,MSOL-USDC,MSOL-USD,GFI-USD,GFI-USDC,MINA-USD,MINA-USDC,ATOM-USD,ATOM-USDC,QNT-USDC,QNT-USD,NEAR-USDT,PRO-USD,PRO-USDC,FORT-USDC,FORT-USD,PRIME-USD,PRIME-USDC,ZEC-USDC,ZEC-USD,TVK-USD,TVK-USDC,XRP-USDT,DOGE-USDT,1INCH-USDC,1INCH-USD,AIOZ-USD,AIOZ-USDC,ABT-USD,ABT-USDC,ROSE-USDC,ROSE-USD,SKL-USDC,SKL-USD,COMP-USDC,COMP-USD,ZETA-USD,ZETA-USDC,TRU-USD,TRU-USDC,AXL-USDC,AXL-USD,AKT-USDC,AKT-USD,XRP-EUR,KARRAT-USD,KARRAT-USDC,CBETH-USD,CBETH-USDC,BICO-USDT,VARA-USD,VARA-USDC,AAVE-EUR,SUSHI-USD,SUSHI-USDC,ALGO-USDC,ALGO-USD,BICO-USDC,BICO-USD,VELO-USDC,VELO-USD,UMA-USD,UMA-USDC,ETC-USDC,ETC-USD,LRDS-USDC,LRDS-USD,ENS-USD,ENS-USDC,LTC-EUR,VET-USDC,VET-USD,SYN-USD,SYN-USDC,LPT-USDC,LPT-USD,OCEAN-USDC,OCEAN-USD,LQTY-USDC,LQTY-USD,AMP-USDC,AMP-USD,BIGTIME-USD,BIGTIME-USDC,LRC-USD,LRC-USDC,ACH-USD,ACH-USDC,VOXEL-USDC,VOXEL-USD,SAND-USDC,SAND-USD,MPL-USD,MPL-USDC,SHIB-EUR,GTC-USDC,GTC-USD,ARKM-USD,ARKM-USDC,COTI-USD,COTI-USDC,ILV-USD,ILV-USDC,FLR-USD,FLR-USDC,ICP-USDT,LCX-USD,LCX-USDC,MOBILE-USD,MOBILE-USDC,DRIFT-USDC,DRIFT-USD,DOGE-EUR,SHIB-USDT,APE-USD,APE-USDC,GODS-USDC,GODS-USD,EOS-USD,EOS-USDC,XTZ-USDC,XTZ-USD,BLUR-USD,BLUR-USDC,ZRX-USDC,ZRX-USD,AUDIO-USD,AUDIO-USDC,ANKR-USD,ANKR-USDC,UNI-EUR,GST-USD,GST-USDC,ORCA-USD,ORCA-USDC,PRQ-USDC,PRQ-USD,AVAX-EUR,SHPING-USDC,SHPING-USD,LOKA-USDC,LOKA-USD,NEON-USD,NEON-USDC,API3-USDC,API3-USD,STRK-USD,STRK-USDC,XCN-USD,XCN-USDC,AVAX-USDT,ADA-EUR,LTC-GBP,WBTC-USD,WBTC-USDC,METIS-USDC,METIS-USD,HIGH-USDC,HIGH-USD,EGLD-USD,EGLD-USDC,ETH-DAI,DESO-USDC,DESO-USD,BIT-USD,BIT-USDC,YFI-USDC,YFI-USD,MASK-EUR,CRO-USDC,CRO-USD,HOPR-USD,HOPR-USDC,CHZ-USD,CHZ-USDC,MATIC-EUR,JASMY-USDT,RBN-USDC,RBN-USD,ADA-USDT,RARI-USDC,RARI-USD,OP-USDT,POLS-USD,POLS-USDC,DIMO-USD,DIMO-USDC,OGN-USD,OGN-USDC,BAL-USD,BAL-USDC,SNX-USD,SNX-USDC,TRAC-USD,TRAC-USDC,QI-USD,QI-USDC,IOTX-USDC,IOTX-USD,MNDE-USD,MNDE-USDC,ACX-USDC,ACX-USD,LINK-EUR,MAGIC-USDC,MAGIC-USD,ALEPH-USDC,ALEPH-USD,T-USDC,T-USD,RONIN-USD,RONIN-USDC,RPL-USD,RPL-USDC,AAVE-GBP,CLV-USDC,CLV-USD,MANA-USDC,MANA-USD,BLZ-USDC,BLZ-USD,AVT-USD,AVT-USDC,PYR-USDC,PYR-USD,DYP-USDC,DYP-USD,OXT-USDC,OXT-USD,A8-USD,A8-USDC,SWFTC-USDC,SWFTC-USD,MATIC-USDT,KAVA-USD,KAVA-USDC,HONEY-USD,HONEY-USDC,ICP-EUR,NCT-USD,NCT-USDC,BLAST-USDC,BLAST-USD,NMR-USD,NMR-USDC,DASH-USDC,DASH-USD,DAR-USD,DAR-USDC,BTRST-USD,BTRST-USDC,CGLD-USDC,CGLD-USD,SPA-USDC,SPA-USD,RAD-USDC,RAD-USD,DNT-USD,DNT-USDC,PIRATE-USDC,PIRATE-USD,MDT-USD,MDT-USDC,CVX-USD,CVX-USDC,APE-EUR,BCH-EUR,AUCTION-USD,AUCTION-USDC,RNDR-USDT,WCFG-USDC,WCFG-USD,APT-USDT,MASK-USDT,CTSI-USD,CTSI-USDC,POND-USDC,POND-USD,DOT-EUR,ADA-GBP,INDEX-USD,INDEX-USDC,SPELL-USDC,SPELL-USD,BAT-USD,BAT-USDC,SUKU-USDC,SUKU-USD,ALGO-EUR,SEAM-USD,SEAM-USDC,BOBA-USDC,BOBA-USD,ALGO-GBP,ACS-USDC,ACS-USD,MANA-EUR,MLN-USD,MLN-USDC,TNSR-USDC,TNSR-USD,FIL-EUR,FLOW-USDC,FLOW-USD,XYO-USDC,XYO-USD,GUSD-USD,GUSD-USDC,VTHO-USD,VTHO-USDC,RLC-USDC,RLC-USD,1INCH-EUR,ALCX-USDC,ALCX-USD,CORECHAIN-USD,CORECHAIN-USDC,SD-USD,SD-USDC,STX-USDT,FX-USDC,FX-USD,XLM-EUR,SHDW-USDC,SHDW-USD,HFT-USD,HFT-USDC,STORJ-USDC,STORJ-USD,SAFE-USDC,SAFE-USD,FIDA-USD,FIDA-USDC,MATH-USD,MATH-USDC,NKN-USDC,NKN-USD,PYUSD-USDC,PYUSD-USD,CELR-USDC,CELR-USD,AXS-USD,AXS-USDC,ALICE-USDC,ALICE-USD,ICP-GBP,GLM-USDC,GLM-USD,FOX-USDC,FOX-USD,AURORA-USDC,AURORA-USD,FARM-USD,FARM-USDC,EOS-EUR,DOT-USDT,HBAR-USDT,GMT-USDC,GMT-USD,GRT-EUR,CTX-USDC,CTX-USD,CHZ-EUR,ETC-EUR,ORN-USDC,ORN-USD,MEDIA-USD,MEDIA-USDC,ATOM-EUR,ASM-USD,ASM-USDC,AGLD-USDC,AGLD-USD,LINK-USDT,KNC-USD,KNC-USDC,DOGE-GBP,SAND-USDT,INV-USDC,INV-USD,GAL-USD,GAL-USDC,PERP-USD,PERP-USDC,POWR-USDC,POWR-USD,KSM-USD,KSM-USDC,BAND-USD,BAND-USDC,ELA-USDC,ELA-USD,G-USD,G-USDC,PLU-USDC,PLU-USD,ROSE-USDT,QNT-USDT,APE-USDT,1INCH-GBP,REQ-USD,REQ-USDC,AXS-USDT,WAXL-USD,WAXL-USDC,ZEN-USD,ZEN-USDC,BCH-GBP,BADGER-USDC,BADGER-USD,FORTH-USD,FORTH-USDC,FIL-GBP,CVC-USDC,CVC-USD,DOT-GBP,LIT-USDC,LIT-USD,CRV-EUR,LINK-GBP,AST-USDC,AST-USD,FIS-USDC,FIS-USD,ENS-EUR,ATOM-USDT,IMX-USDT,SHIB-GBP,MATIC-GBP,DIA-USD,DIA-USDC,ERN-USDC,ERN-USD,OSMO-USD,OSMO-USDC,CHZ-USDT,ARPA-USD,ARPA-USDC,RNDR-EUR,TIME-USD,TIME-USDC,ENS-USDT,OMNI-USD,OMNI-USDC,ATOM-GBP,C98-USD,C98-USDC,PUNDIX-USD,PUNDIX-USDC,MUSE-USD,MUSE-USDC,LRC-USDT,MINA-EUR,GHST-USD,GHST-USDC,ETC-GBP,AXS-EUR,LSETH-USDC,LSETH-USD,GNO-USD,GNO-USDC,BNT-USDC,BNT-USD,AERGO-USD,AERGO-USDC,SNX-EUR,CHZ-GBP,UNI-GBP,FLOW-USDT,BAT-EUR,MINA-USDT,DEXT-USDC,DEXT-USD,XLM-USDT,GYEN-USDC,GYEN-USD,BICO-EUR,CRV-GBP,ANKR-EUR,CGLD-EUR,XTZ-GBP,MASK-GBP,KRL-USDC,KRL-USD,CRO-EUR,ANKR-GBP,XTZ-EUR,GRT-GBP,GMT-USDT,SNX-GBP,STG-USD,STG-USDC,CRO-USDT,PAX-USD,PAX-USDC,CGLD-GBP,SOL-ETH,ETH-BTC,ADA-ETH,SOL-BTC,AAVE-BTC,WBTC-BTC,ZEC-BTC,CBETH-ETH,LINK-ETH,LTC-BTC,UNI-BTC,BCH-BTC,DOGE-BTC,AVAX-BTC,ADA-BTC,LSETH-ETH,MKR-BTC,GRT-BTC,MATIC-BTC,COMP-BTC,LRC-BTC,DOT-BTC,XLM-BTC,FIL-BTC,ICP-BTC,EOS-BTC,BAT-ETH,LINK-BTC,CRV-BTC,ATOM-BTC,BAL-BTC,ALGO-BTC,1INCH-BTC,BAT-BTC,MANA-ETH,ETC-BTC,DASH-BTC,SNX-BTC,YFI-BTC,XTZ-BTC,AXS-BTC,ANKR-BTC,MANA-BTC,CGLD-BTC" - } + "spot": { + "assetEnabled": true, + "enabled": "BTC-USD,BTC-USDC,USDT-USD,ETH-USD,ETH-USDC,SOL-USD", + "available": "BTC-USD,BTC-USDC,ETH-USDC,ETH-USD,USDT-USD,SOL-USDC,SOL-USD,USDT-USDC,FET-USDC,FET-USD,BTC-USDT,XRP-USD,XRP-USDC,DOGE-USDC,DOGE-USD,BONK-USDC,BONK-USD,USDT-EUR,ETH-USDT,BTC-EUR,USDC-EUR,AAVE-USDC,AAVE-USD,SUI-USDC,SUI-USD,LINK-USD,LINK-USDC,LTC-USD,LTC-USDC,SHIB-USDC,SHIB-USD,BTC-GBP,JASMY-USD,JASMY-USDC,ETH-EUR,AVAX-USDC,AVAX-USD,NEAR-USD,NEAR-USDC,ONDO-USD,ONDO-USDC,ADA-USD,ADA-USDC,SUPER-USDC,SUPER-USD,UNI-USD,UNI-USDC,RARE-USD,RARE-USDC,RNDR-USD,RNDR-USDC,INJ-USDC,INJ-USD,SEI-USDC,SEI-USD,HBAR-USDC,HBAR-USD,XLM-USDC,XLM-USD,SOL-EUR,HNT-USD,HNT-USDC,BCH-USD,BCH-USDC,STX-USDC,STX-USD,TRB-USDC,TRB-USD,PNG-USD,PNG-USDC,MATIC-USDC,MATIC-USD,MKR-USD,MKR-USDC,SOL-USDT,APT-USDC,APT-USD,EURC-USDC,ICP-USD,ICP-USDC,IDEX-USDC,IDEX-USD,AERO-USDC,AERO-USD,ETH-GBP,DAI-USD,DAI-USDC,00-USD,00-USDC,USDC-GBP,MASK-USD,MASK-USDC,USDT-GBP,RENDER-USD,RENDER-USDC,ZRO-USDC,ZRO-USD,CRV-USDC,CRV-USD,DOT-USDC,DOT-USD,TIA-USDC,TIA-USD,GRT-USD,GRT-USDC,FIL-USDC,FIL-USD,IMX-USDC,IMX-USD,OP-USD,OP-USDC,JTO-USD,JTO-USDC,FET-USDT,LDO-USD,LDO-USDC,SOL-GBP,ARB-USD,ARB-USDC,MSOL-USDC,MSOL-USD,GFI-USD,GFI-USDC,MINA-USD,MINA-USDC,ATOM-USD,ATOM-USDC,QNT-USDC,QNT-USD,NEAR-USDT,PRO-USD,PRO-USDC,FORT-USDC,FORT-USD,PRIME-USD,PRIME-USDC,ZEC-USDC,ZEC-USD,TVK-USD,TVK-USDC,XRP-USDT,DOGE-USDT,1INCH-USDC,1INCH-USD,AIOZ-USD,AIOZ-USDC,ABT-USD,ABT-USDC,ROSE-USDC,ROSE-USD,SKL-USDC,SKL-USD,COMP-USDC,COMP-USD,ZETA-USD,ZETA-USDC,TRU-USD,TRU-USDC,AXL-USDC,AXL-USD,AKT-USDC,AKT-USD,XRP-EUR,KARRAT-USD,KARRAT-USDC,CBETH-USD,CBETH-USDC,BICO-USDT,VARA-USD,VARA-USDC,AAVE-EUR,SUSHI-USD,SUSHI-USDC,ALGO-USDC,ALGO-USD,BICO-USDC,BICO-USD,VELO-USDC,VELO-USD,UMA-USD,UMA-USDC,ETC-USDC,ETC-USD,LRDS-USDC,LRDS-USD,ENS-USD,ENS-USDC,LTC-EUR,VET-USDC,VET-USD,SYN-USD,SYN-USDC,LPT-USDC,LPT-USD,OCEAN-USDC,OCEAN-USD,LQTY-USDC,LQTY-USD,AMP-USDC,AMP-USD,BIGTIME-USD,BIGTIME-USDC,LRC-USD,LRC-USDC,ACH-USD,ACH-USDC,VOXEL-USDC,VOXEL-USD,SAND-USDC,SAND-USD,MPL-USD,MPL-USDC,SHIB-EUR,GTC-USDC,GTC-USD,ARKM-USD,ARKM-USDC,COTI-USD,COTI-USDC,ILV-USD,ILV-USDC,FLR-USD,FLR-USDC,ICP-USDT,LCX-USD,LCX-USDC,MOBILE-USD,MOBILE-USDC,DRIFT-USDC,DRIFT-USD,DOGE-EUR,SHIB-USDT,APE-USD,APE-USDC,GODS-USDC,GODS-USD,EOS-USD,EOS-USDC,XTZ-USDC,XTZ-USD,BLUR-USD,BLUR-USDC,ZRX-USDC,ZRX-USD,AUDIO-USD,AUDIO-USDC,ANKR-USD,ANKR-USDC,UNI-EUR,GST-USD,GST-USDC,ORCA-USD,ORCA-USDC,PRQ-USDC,PRQ-USD,AVAX-EUR,SHPING-USDC,SHPING-USD,LOKA-USDC,LOKA-USD,NEON-USD,NEON-USDC,API3-USDC,API3-USD,STRK-USD,STRK-USDC,XCN-USD,XCN-USDC,AVAX-USDT,ADA-EUR,LTC-GBP,WBTC-USD,WBTC-USDC,METIS-USDC,METIS-USD,HIGH-USDC,HIGH-USD,EGLD-USD,EGLD-USDC,ETH-DAI,DESO-USDC,DESO-USD,BIT-USD,BIT-USDC,YFI-USDC,YFI-USD,MASK-EUR,CRO-USDC,CRO-USD,HOPR-USD,HOPR-USDC,CHZ-USD,CHZ-USDC,MATIC-EUR,JASMY-USDT,RBN-USDC,RBN-USD,ADA-USDT,RARI-USDC,RARI-USD,OP-USDT,POLS-USD,POLS-USDC,DIMO-USD,DIMO-USDC,OGN-USD,OGN-USDC,BAL-USD,BAL-USDC,SNX-USD,SNX-USDC,TRAC-USD,TRAC-USDC,QI-USD,QI-USDC,IOTX-USDC,IOTX-USD,MNDE-USD,MNDE-USDC,ACX-USDC,ACX-USD,LINK-EUR,MAGIC-USDC,MAGIC-USD,ALEPH-USDC,ALEPH-USD,T-USDC,T-USD,RONIN-USD,RONIN-USDC,RPL-USD,RPL-USDC,AAVE-GBP,CLV-USDC,CLV-USD,MANA-USDC,MANA-USD,BLZ-USDC,BLZ-USD,AVT-USD,AVT-USDC,PYR-USDC,PYR-USD,DYP-USDC,DYP-USD,OXT-USDC,OXT-USD,A8-USD,A8-USDC,SWFTC-USDC,SWFTC-USD,MATIC-USDT,KAVA-USD,KAVA-USDC,HONEY-USD,HONEY-USDC,ICP-EUR,NCT-USD,NCT-USDC,BLAST-USDC,BLAST-USD,NMR-USD,NMR-USDC,DASH-USDC,DASH-USD,DAR-USD,DAR-USDC,BTRST-USD,BTRST-USDC,CGLD-USDC,CGLD-USD,SPA-USDC,SPA-USD,RAD-USDC,RAD-USD,DNT-USD,DNT-USDC,PIRATE-USDC,PIRATE-USD,MDT-USD,MDT-USDC,CVX-USD,CVX-USDC,APE-EUR,BCH-EUR,AUCTION-USD,AUCTION-USDC,RNDR-USDT,WCFG-USDC,WCFG-USD,APT-USDT,MASK-USDT,CTSI-USD,CTSI-USDC,POND-USDC,POND-USD,DOT-EUR,ADA-GBP,INDEX-USD,INDEX-USDC,SPELL-USDC,SPELL-USD,BAT-USD,BAT-USDC,SUKU-USDC,SUKU-USD,ALGO-EUR,SEAM-USD,SEAM-USDC,BOBA-USDC,BOBA-USD,ALGO-GBP,ACS-USDC,ACS-USD,MANA-EUR,MLN-USD,MLN-USDC,TNSR-USDC,TNSR-USD,FIL-EUR,FLOW-USDC,FLOW-USD,XYO-USDC,XYO-USD,GUSD-USD,GUSD-USDC,VTHO-USD,VTHO-USDC,RLC-USDC,RLC-USD,1INCH-EUR,ALCX-USDC,ALCX-USD,CORECHAIN-USD,CORECHAIN-USDC,SD-USD,SD-USDC,STX-USDT,FX-USDC,FX-USD,XLM-EUR,SHDW-USDC,SHDW-USD,HFT-USD,HFT-USDC,STORJ-USDC,STORJ-USD,SAFE-USDC,SAFE-USD,FIDA-USD,FIDA-USDC,MATH-USD,MATH-USDC,NKN-USDC,NKN-USD,PYUSD-USDC,PYUSD-USD,CELR-USDC,CELR-USD,AXS-USD,AXS-USDC,ALICE-USDC,ALICE-USD,ICP-GBP,GLM-USDC,GLM-USD,FOX-USDC,FOX-USD,AURORA-USDC,AURORA-USD,FARM-USD,FARM-USDC,EOS-EUR,DOT-USDT,HBAR-USDT,GMT-USDC,GMT-USD,GRT-EUR,CTX-USDC,CTX-USD,CHZ-EUR,ETC-EUR,ORN-USDC,ORN-USD,MEDIA-USD,MEDIA-USDC,ATOM-EUR,ASM-USD,ASM-USDC,AGLD-USDC,AGLD-USD,LINK-USDT,KNC-USD,KNC-USDC,DOGE-GBP,SAND-USDT,INV-USDC,INV-USD,GAL-USD,GAL-USDC,PERP-USD,PERP-USDC,POWR-USDC,POWR-USD,KSM-USD,KSM-USDC,BAND-USD,BAND-USDC,ELA-USDC,ELA-USD,G-USD,G-USDC,PLU-USDC,PLU-USD,ROSE-USDT,QNT-USDT,APE-USDT,1INCH-GBP,REQ-USD,REQ-USDC,AXS-USDT,WAXL-USD,WAXL-USDC,ZEN-USD,ZEN-USDC,BCH-GBP,BADGER-USDC,BADGER-USD,FORTH-USD,FORTH-USDC,FIL-GBP,CVC-USDC,CVC-USD,DOT-GBP,LIT-USDC,LIT-USD,CRV-EUR,LINK-GBP,AST-USDC,AST-USD,FIS-USDC,FIS-USD,ENS-EUR,ATOM-USDT,IMX-USDT,SHIB-GBP,MATIC-GBP,DIA-USD,DIA-USDC,ERN-USDC,ERN-USD,OSMO-USD,OSMO-USDC,CHZ-USDT,ARPA-USD,ARPA-USDC,RNDR-EUR,TIME-USD,TIME-USDC,ENS-USDT,OMNI-USD,OMNI-USDC,ATOM-GBP,C98-USD,C98-USDC,PUNDIX-USD,PUNDIX-USDC,MUSE-USD,MUSE-USDC,LRC-USDT,MINA-EUR,GHST-USD,GHST-USDC,ETC-GBP,AXS-EUR,LSETH-USDC,LSETH-USD,GNO-USD,GNO-USDC,BNT-USDC,BNT-USD,AERGO-USD,AERGO-USDC,SNX-EUR,CHZ-GBP,UNI-GBP,FLOW-USDT,BAT-EUR,MINA-USDT,DEXT-USDC,DEXT-USD,XLM-USDT,GYEN-USDC,GYEN-USD,BICO-EUR,CRV-GBP,ANKR-EUR,CGLD-EUR,XTZ-GBP,MASK-GBP,KRL-USDC,KRL-USD,CRO-EUR,ANKR-GBP,XTZ-EUR,GRT-GBP,GMT-USDT,SNX-GBP,STG-USD,STG-USDC,CRO-USDT,PAX-USD,PAX-USDC,CGLD-GBP,SOL-ETH,ETH-BTC,ADA-ETH,SOL-BTC,AAVE-BTC,WBTC-BTC,ZEC-BTC,CBETH-ETH,LINK-ETH,LTC-BTC,UNI-BTC,BCH-BTC,DOGE-BTC,AVAX-BTC,ADA-BTC,LSETH-ETH,MKR-BTC,GRT-BTC,MATIC-BTC,COMP-BTC,LRC-BTC,DOT-BTC,XLM-BTC,FIL-BTC,ICP-BTC,EOS-BTC,BAT-ETH,LINK-BTC,CRV-BTC,ATOM-BTC,BAL-BTC,ALGO-BTC,1INCH-BTC,BAT-BTC,MANA-ETH,ETC-BTC,DASH-BTC,SNX-BTC,YFI-BTC,XTZ-BTC,AXS-BTC,ANKR-BTC,MANA-BTC,CGLD-BTC" } + } + }, + "api": { + "authenticatedSupport": false, + "authenticatedWebsocketApiSupport": false, + "credentials": { + "key": "", + "secret": "", + "clientID": "" }, - "api": { - "authenticatedSupport": false, - "authenticatedWebsocketApiSupport": false, - "credentials": { - "key": "", - "secret": "", - "clientID": "" - }, - "credentialsValidator": { - "requiresKey": true, - "requiresSecret": true, - "requiresClientID": true, - "requiresBase64DecodeSecret": true + "credentialsValidator": { + "requiresKey": true, + "requiresSecret": true, + "requiresClientID": true, + "requiresBase64DecodeSecret": true + }, + "urlEndpoints": { + "RestSandboxURL": "https://api-public.sandbox.exchange.coinbase.com/", + "RestSpotURL": "https://api.coinbase.com", + "WebsocketSpotURL": "wss://advanced-trade-ws.coinbase.com" + } + }, + "features": { + "supports": { + "restAPI": true, + "restCapabilities": { + "autoPairUpdates": true, + "fundingRateFetching": false }, - "urlEndpoints": { - "RestSandboxURL": "https://api-public.sandbox.exchange.coinbase.com/", - "RestSpotURL": "https://api.coinbase.com", - "WebsocketSpotURL": "wss://advanced-trade-ws.coinbase.com" + "websocketAPI": true, + "websocketCapabilities": { + "fundingRateFetching": false } }, - "features": { - "supports": { - "restAPI": true, - "restCapabilities": { - "autoPairUpdates": true, - "fundingRateFetching": false - }, - "websocketAPI": true, - "websocketCapabilities": { - "fundingRateFetching": false - } + "enabled": { + "autoPairUpdates": true, + "websocketAPI": true, + "saveTradeData": false, + "tradeFeed": false, + "fillsFeed": false + }, + "subscriptions": [ + { + "enabled": true, + "channel": "heartbeat" }, - "enabled": { - "autoPairUpdates": true, - "websocketAPI": true, - "saveTradeData": false, - "tradeFeed": false, - "fillsFeed": false + { + "enabled": false, + "channel": "status", + "authenticated": true + }, + { + "enabled": true, + "channel": "ticker", + "asset": "spot" + }, + { + "enabled": true, + "channel": "candles", + "asset": "spot" + }, + { + "enabled": true, + "channel": "allTrades", + "asset": "spot" + }, + { + "enabled": true, + "channel": "orderbook", + "asset": "spot" + }, + { + "enabled": true, + "channel": "account", + "authenticated": true }, - "subscriptions": [ - { - "enabled": true, - "channel": "heartbeat" - }, - { - "enabled": false, - "channel": "status", - "authenticated": true - }, - { - "enabled": true, - "channel": "ticker", - "asset": "spot" - }, - { - "enabled": true, - "channel": "candles", - "asset": "spot" - }, - { - "enabled": true, - "channel": "allTrades", - "asset": "spot" - }, - { - "enabled": true, - "channel": "orderbook", - "asset": "spot" - }, - { - "enabled": true, - "channel": "account", - "authenticated": true - }, - { - "enabled": false, - "channel": "ticker_batch", - "asset": "spot" - } - ] - }, - "bankAccounts": [ { "enabled": false, - "bankName": "", - "bankAddress": "", - "bankPostalCode": "", - "bankPostalCity": "", - "bankCountry": "", - "accountName": "", - "accountNumber": "", - "swiftCode": "", - "iban": "", - "supportedCurrencies": "" + "channel": "ticker_batch", + "asset": "spot" } - ], - "orderbook": { - "verificationBypass": false, - "websocketBufferLimit": 5, - "websocketBufferEnabled": false, - "publishPeriod": 10000000000 - } + ] }, + "bankAccounts": [ + { + "enabled": false, + "bankName": "", + "bankAddress": "", + "bankPostalCode": "", + "bankPostalCity": "", + "bankCountry": "", + "accountName": "", + "accountNumber": "", + "swiftCode": "", + "iban": "", + "supportedCurrencies": "" + } + ], + "orderbook": { + "verificationBypass": false, + "websocketBufferLimit": 5, + "websocketBufferEnabled": false, + "publishPeriod": 10000000000 + } + }, { "name": "Deribit", "enabled": true, From 9925c887b8c064c180842187ce6d3b65c106d264 Mon Sep 17 00:00:00 2001 From: Samuel Reid <43227667+cranktakular@users.noreply.github.com> Date: Tue, 24 Sep 2024 16:56:52 +1000 Subject: [PATCH 68/79] ShazNits --- engine/currency_state_manager.md | 2 +- exchanges/asset/asset_test.go | 10 +- exchanges/coinbasepro/coinbasepro.go | 557 +++---- exchanges/coinbasepro/coinbasepro_test.go | 202 +-- exchanges/coinbasepro/coinbasepro_types.go | 1332 ++++++++--------- .../coinbasepro/coinbasepro_websocket.go | 30 +- exchanges/coinbasepro/coinbasepro_wrapper.go | 279 ++-- exchanges/exchange.go | 3 +- exchanges/stream/buffer/buffer.go | 3 - 9 files changed, 1168 insertions(+), 1250 deletions(-) diff --git a/engine/currency_state_manager.md b/engine/currency_state_manager.md index b3ec29aa552..4620d74e51d 100644 --- a/engine/currency_state_manager.md +++ b/engine/currency_state_manager.md @@ -45,4 +45,4 @@ When submitting a PR, please abide by our coding guidelines: If this framework helped you in any way, or you would like to support the developers working on it, please donate Bitcoin to: -***bc1qk0jareu4jytc0cfrhr5wgshsq8282awpavfahc*** +***bc1qk0jareu4jytc0cfrhr5wgshsq8282awpavfahc*** \ No newline at end of file diff --git a/exchanges/asset/asset_test.go b/exchanges/asset/asset_test.go index 62f31bf26a0..25c8aeb46e6 100644 --- a/exchanges/asset/asset_test.go +++ b/exchanges/asset/asset_test.go @@ -6,6 +6,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestString(t *testing.T) { @@ -24,14 +25,9 @@ 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") - } - + require.Equal(t, "SPOT", a.Upper()) a = 0 - if a.Upper() != "" { - t.Fatal("TestUpper returned an unexpected result") - } + require.Empty(t, a.Upper()) } func TestStrings(t *testing.T) { diff --git a/exchanges/coinbasepro/coinbasepro.go b/exchanges/coinbasepro/coinbasepro.go index 10dca858f79..e94c6e66ded 100644 --- a/exchanges/coinbasepro/coinbasepro.go +++ b/exchanges/coinbasepro/coinbasepro.go @@ -19,6 +19,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/common/key" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" + "github.com/thrasher-corp/gocryptotrader/exchanges/kline" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/request" "github.com/thrasher-corp/gocryptotrader/types" @@ -95,6 +96,9 @@ const ( errIntervalNotSupported = "interval not supported" warnSequenceIssue = "Out of order sequence number. Received %v, expected %v" warnAuth = "%v authenticated request failed, attempting unauthenticated" + + ManyFills = 65535 + ManyOrds = 2147483647 ) // Constants defining whether a transfer is a deposit or withdrawal, used to simplify @@ -121,20 +125,20 @@ var ( errClientOrderIDEmpty = errors.New("client order id cannot be empty") errProductIDEmpty = errors.New("product id cannot be empty") errOrderIDEmpty = errors.New("order ids cannot be empty") + errCancelLimitExceeded = errors.New("100 order cancel limit exceeded") errOpenPairWithOtherTypes = errors.New("cannot pair open orders with other order types") errSizeAndPriceZero = errors.New("size and price cannot both be 0") - errCurrencyEmpty = errors.New("currency cannot be empty") errCurrWalletConflict = errors.New("exactly one of walletID and currency must be specified") errWalletIDEmpty = errors.New("wallet id cannot be empty") errAddressIDEmpty = errors.New("address id cannot be empty") errTransactionTypeEmpty = errors.New("transaction type cannot be empty") errToEmpty = errors.New("to cannot be empty") - errAmountEmpty = errors.New("amount cannot be empty") errTransactionIDEmpty = errors.New("transaction id cannot be empty") errPaymentMethodEmpty = errors.New("payment method cannot be empty") errDepositIDEmpty = errors.New("deposit id cannot be empty") errInvalidPriceType = errors.New("price type must be spot, buy, or sell") errInvalidOrderType = errors.New("order type must be market, limit, or stop") + errEndTimeInPast = errors.New("end time cannot be in the past") errNoMatchingWallets = errors.New("no matching wallets returned") errOrderModFailNoRet = errors.New("order modification failed but no error returned") errNameEmpty = errors.New("name cannot be empty") @@ -146,14 +150,15 @@ var ( errNoWalletsReturned = errors.New("no wallets returned") errPayMethodNotFound = errors.New("payment method not found") errUnknownL2DataType = errors.New("unknown l2update data type") - errUnknownSide = errors.New("unknown side") - errInvalidGranularity = errors.New("invalid granularity") errOrderFailedToCancel = errors.New("failed to cancel order") errUnrecognisedStatusType = errors.New("unrecognised status type") - errPairEmpty = errors.New("pair cannot be empty") errStringConvert = errors.New("unable to convert into string value") errFloatConvert = errors.New("unable to convert into float64 value") errWrappedAssetEmpty = errors.New("wrapped asset cannot be empty") + + allowedGranularities = []string{granOneMin, granFiveMin, granFifteenMin, granThirtyMin, granOneHour, granTwoHour, granSixHour, granOneDay} + closedStatuses = []string{"FILLED", "CANCELLED", "EXPIRED", "FAILED"} + openStatus = []string{"OPEN"} ) // GetAllAccounts returns information on all trading accounts associated with the API key @@ -166,8 +171,7 @@ func (c *CoinbasePro) GetAllAccounts(ctx context.Context, limit uint8, cursor st vals.Set("cursor", cursor) } var resp AllAccountsResponse - return &resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - coinbaseV3+coinbaseAccounts, vals, 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 @@ -176,9 +180,10 @@ func (c *CoinbasePro) GetAccountByID(ctx context.Context, accountID string) (*Ac return nil, errAccountIDEmpty } path := coinbaseV3 + coinbaseAccounts + "/" + accountID - resp := OneAccountResponse{} - return &resp.Account, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - path, nil, nil, true, &resp, nil) + resp := struct { + Account Account `json:"account"` + }{} + return &resp.Account, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, nil, true, &resp, nil) } // GetBestBidAsk returns the best bid/ask for all products. Can be filtered to certain products @@ -188,9 +193,10 @@ func (c *CoinbasePro) GetBestBidAsk(ctx context.Context, products []string) ([]P for x := range products { vals.Add("product_ids", products[x]) } - var resp BestBidAsk - return resp.Pricebooks, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - coinbaseV3+coinbaseBestBidAsk, vals, nil, true, &resp, nil) + resp := struct { + Pricebooks []ProductBook `json:"pricebooks"` + }{} + return resp.Pricebooks, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, coinbaseV3+coinbaseBestBidAsk, vals, nil, true, &resp, nil) } // GetProductBookV3 returns a list of bids/asks for a single product @@ -203,10 +209,11 @@ func (c *CoinbasePro) GetProductBookV3(ctx context.Context, productID string, li if limit != 0 { vals.Set("limit", strconv.FormatInt(int64(limit), 10)) } - var resp ProductBookResponse + resp := struct { + Pricebook ProductBook `json:"pricebook"` + }{} if authenticated { - return &resp.Pricebook, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, coinbaseV3+coinbaseProductBook, - vals, nil, true, &resp, nil) + return &resp.Pricebook, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, coinbaseV3+coinbaseProductBook, vals, nil, true, &resp, nil) } path := coinbaseV3 + coinbaseMarket + "/" + coinbaseProductBook return &resp.Pricebook, c.SendHTTPRequest(ctx, exchange.RestSpot, path, vals, &resp) @@ -215,9 +222,7 @@ func (c *CoinbasePro) GetProductBookV3(ctx context.Context, productID string, li // 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, authenticated bool) (*AllProducts, error) { vals := url.Values{} - if limit != 0 { - vals.Set("limit", strconv.FormatInt(int64(limit), 10)) - } + vals.Set("limit", strconv.FormatInt(int64(limit), 10)) if offset != 0 { vals.Set("offset", strconv.FormatInt(int64(offset), 10)) } @@ -235,8 +240,7 @@ func (c *CoinbasePro) GetAllProducts(ctx context.Context, limit, offset int32, p } var resp AllProducts if authenticated { - return &resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, coinbaseV3+coinbaseProducts, - vals, nil, true, &resp, nil) + return &resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, coinbaseV3+coinbaseProducts, vals, nil, true, &resp, nil) } path := coinbaseV3 + coinbaseMarket + "/" + coinbaseProducts return &resp, c.SendHTTPRequest(ctx, exchange.RestSpot, path, vals, &resp) @@ -250,8 +254,7 @@ func (c *CoinbasePro) GetProductByID(ctx context.Context, productID string, auth var resp Product if authenticated { path := coinbaseV3 + coinbaseProducts + "/" + productID - return &resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, nil, true, &resp, - nil) + return &resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, nil, true, &resp, nil) } path := coinbaseV3 + coinbaseMarket + "/" + coinbaseProducts + "/" + productID return &resp, c.SendHTTPRequest(ctx, exchange.RestSpot, path, nil, &resp) @@ -261,25 +264,23 @@ func (c *CoinbasePro) GetProductByID(ctx context.Context, productID string, auth // 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, authenticated bool) ([]CandleStruct, error) { - var resp History if productID == "" { return nil, errProductIDEmpty } - allowedGranularities := []string{granOneMin, granFiveMin, granFifteenMin, - granThirtyMin, granOneHour, granTwoHour, granSixHour, granOneDay} validGran := common.StringSliceContains(allowedGranularities, granularity) if !validGran { - return nil, fmt.Errorf("%w %v, allowed granularities are: %+v", errInvalidGranularity, - granularity, allowedGranularities) + return nil, fmt.Errorf("%w %v, allowed granularities are: %+v", kline.ErrUnsupportedInterval, granularity, allowedGranularities) } vals := url.Values{} vals.Set("start", strconv.FormatInt(startDate.Unix(), 10)) vals.Set("end", strconv.FormatInt(endDate.Unix(), 10)) vals.Set("granularity", granularity) + resp := struct { + Candles []CandleStruct `json:"candles"` + }{} if authenticated { path := coinbaseV3 + coinbaseProducts + "/" + productID + "/" + coinbaseCandles - return resp.Candles, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - path, vals, nil, true, &resp, nil) + return resp.Candles, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, vals, nil, true, &resp, nil) } path := coinbaseV3 + coinbaseMarket + "/" + coinbaseProducts + "/" + productID + "/" + coinbaseCandles return resp.Candles, c.SendHTTPRequest(ctx, exchange.RestSpot, path, vals, &resp) @@ -302,8 +303,7 @@ func (c *CoinbasePro) GetTicker(ctx context.Context, productID string, limit uin var resp Ticker if authenticated { path := coinbaseV3 + coinbaseProducts + "/" + productID + "/" + coinbaseTicker - return &resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, vals, nil, true, &resp, - nil) + return &resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, vals, nil, true, &resp, nil) } path := coinbaseV3 + coinbaseMarket + "/" + coinbaseProducts + "/" + productID + "/" + coinbaseTicker return &resp, c.SendHTTPRequest(ctx, exchange.RestSpot, path, vals, &resp) @@ -317,16 +317,14 @@ func (c *CoinbasePro) PlaceOrder(ctx context.Context, clientOID, productID, side if productID == "" { return nil, errProductIDEmpty } - if amount == 0 { - return nil, errAmountEmpty + if amount <= 0 { + return nil, order.ErrAmountIsInvalid } - orderConfig, err := prepareOrderConfig(orderType, side, stopDirection, amount, limitPrice, stopPrice, endTime, - postOnly) + orderConfig, err := prepareOrderConfig(orderType, side, stopDirection, amount, limitPrice, stopPrice, endTime, postOnly) if err != nil { return nil, err } - mt := formatMarginType(marginType) - req := map[string]interface{}{ + req := map[string]any{ "client_order_id": clientOID, "product_id": productID, "side": side, @@ -334,11 +332,10 @@ func (c *CoinbasePro) PlaceOrder(ctx context.Context, clientOID, productID, side "self_trade_prevention_id": stpID, "leverage": strconv.FormatFloat(leverage, 'f', -1, 64), "retail_portfolio_id": rpID, - "margin_type": mt} + "margin_type": FormatMarginType(marginType)} var resp PlaceOrderResp return &resp, - c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, - coinbaseV3+coinbaseOrders, nil, req, true, &resp, nil) + c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, coinbaseV3+coinbaseOrders, nil, req, true, &resp, nil) } // CancelOrders cancels orders by orderID. Can only cancel 100 orders per request @@ -346,11 +343,15 @@ func (c *CoinbasePro) CancelOrders(ctx context.Context, orderIDs []string) ([]Or if len(orderIDs) == 0 { return nil, errOrderIDEmpty } + if len(orderIDs) > 100 { + return nil, errCancelLimitExceeded + } path := coinbaseV3 + coinbaseOrders + "/" + coinbaseBatchCancel - req := map[string]interface{}{"order_ids": orderIDs} - var resp CancelOrderResp - return resp.Results, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, path, nil, - req, true, &resp, nil) + req := map[string]any{"order_ids": orderIDs} + resp := struct { + Results []OrderCancelDetail `json:"results"` + }{} + 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 @@ -363,13 +364,14 @@ func (c *CoinbasePro) EditOrder(ctx context.Context, orderID string, size, price return false, errSizeAndPriceZero } path := coinbaseV3 + coinbaseOrders + "/" + coinbaseEdit - req := map[string]interface{}{ + req := map[string]any{ "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) + resp := struct { + Success bool `json:"success"` + }{} + return resp.Success, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, path, nil, req, true, &resp, nil) } // EditOrderPreview simulates an edit order request, to preview the result. Only limit orders with a @@ -382,13 +384,12 @@ func (c *CoinbasePro) EditOrderPreview(ctx context.Context, orderID string, size return nil, errSizeAndPriceZero } path := coinbaseV3 + coinbaseOrders + "/" + coinbaseEditPreview - req := map[string]interface{}{ + req := map[string]any{ "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, nil, - req, true, &resp, nil) + return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, path, nil, req, true, &resp, nil) } // GetAllOrders lists orders, filtered by their status @@ -440,16 +441,14 @@ func (c *CoinbasePro) GetAllOrders(ctx context.Context, productID, userNativeCur } path := coinbaseV3 + coinbaseOrders + "/" + coinbaseHistorical + "/" + coinbaseBatch var resp GetAllOrdersResp - return &resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, - params.Values, nil, true, &resp, nil) + 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 params Params params.Values = url.Values{} - err := params.prepareDateString(startDate, endDate, "start_sequence_timestamp", - "end_sequence_timestamp") + err := params.prepareDateString(startDate, endDate, "start_sequence_timestamp", "end_sequence_timestamp") if err != nil { return nil, err } @@ -467,16 +466,17 @@ func (c *CoinbasePro) GetFills(ctx context.Context, orderID, productID, cursor s } path := coinbaseV3 + coinbaseOrders + "/" + coinbaseHistorical + "/" + coinbaseFills var resp FillResponse - return &resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, - params.Values, nil, true, &resp, nil) + return &resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, params.Values, nil, true, &resp, nil) } // GetOrderByID returns a single order by order id. -func (c *CoinbasePro) GetOrderByID(ctx context.Context, orderID, clientOID, userNativeCurrency string) (*SingleOrder, error) { +func (c *CoinbasePro) GetOrderByID(ctx context.Context, orderID, clientOID, userNativeCurrency string) (*GetOrderResponse, error) { if orderID == "" { return nil, errOrderIDEmpty } - var resp SingleOrder + resp := struct { + Order GetOrderResponse `json:"order"` + }{} vals := url.Values{} if clientOID != "" { vals.Set("client_order_id", clientOID) @@ -485,84 +485,81 @@ func (c *CoinbasePro) GetOrderByID(ctx context.Context, orderID, clientOID, user 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) + return &resp.Order, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, vals, nil, true, &resp, nil) } // PreviewOrder simulates the results of an order request func (c *CoinbasePro) PreviewOrder(ctx context.Context, productID, side, orderType, stopDirection, marginType string, commissionValue, amount, limitPrice, stopPrice, tradableBalance, leverage float64, postOnly, isMax, skipFCMRiskCheck bool, endTime time.Time) (*PreviewOrderResp, error) { if amount == 0 { - return nil, errAmountEmpty + return nil, order.ErrAmountIsInvalid } - orderConfig, err := prepareOrderConfig(orderType, side, stopDirection, amount, limitPrice, stopPrice, endTime, - postOnly) + 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)} - mt := formatMarginType(marginType) - req := map[string]interface{}{ + req := map[string]any{ "product_id": productID, "side": side, - "commission_rate": commissionRate, + "commission_rate": map[string]string{"value": strconv.FormatFloat(commissionValue, 'f', -1, 64)}, "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), } - if mt != "" { + if mt := FormatMarginType(marginType); mt != "" { req["margin_type"] = mt } var resp *PreviewOrderResp path := coinbaseV3 + coinbaseOrders + "/" + coinbasePreview - return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, path, nil, req, true, - &resp, nil) + 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) ([]SimplePortfolioData, error) { - var resp AllPortfolioResponse + resp := struct { + Portfolios []SimplePortfolioData `json:"portfolios"` + }{} vals := url.Values{} if portfolioType != "" { vals.Set("portfolio_type", portfolioType) } - return resp.Portfolios, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - coinbaseV3+coinbasePortfolios, vals, 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) { +func (c *CoinbasePro) CreatePortfolio(ctx context.Context, name string) (*SimplePortfolioData, error) { if name == "" { return nil, errNameEmpty } - req := map[string]interface{}{"name": name} - var resp *SimplePortfolioResponse - return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, - coinbaseV3+coinbasePortfolios, nil, req, true, &resp, nil) + req := map[string]any{"name": name} + resp := struct { + Portfolio SimplePortfolioData `json:"portfolio"` + }{} + return &resp.Portfolio, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, 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) { +func (c *CoinbasePro) MovePortfolioFunds(ctx context.Context, cur, from, to string, amount float64) (*MovePortfolioFundsResponse, error) { if from == "" || to == "" { return nil, errPortfolioIDEmpty } - if currency == "" { - return nil, errCurrencyEmpty + if cur == "" { + return nil, currency.ErrCurrencyCodeEmpty } if amount == 0 { - return nil, errAmountEmpty + return nil, order.ErrAmountIsInvalid } funds := FundsData{ Value: strconv.FormatFloat(amount, 'f', -1, 64), - Currency: currency} - req := map[string]interface{}{ + Currency: cur} + req := map[string]any{ "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, nil, req, true, &resp, nil) + return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, path, nil, req, true, &resp, nil) } // GetPortfolioByID provides detailed information on a single portfolio @@ -571,9 +568,10 @@ func (c *CoinbasePro) GetPortfolioByID(ctx context.Context, portfolioID string) return nil, errPortfolioIDEmpty } path := coinbaseV3 + coinbasePortfolios + "/" + portfolioID - var resp DetailedPortfolioResponse - return &resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - path, nil, nil, true, &resp, nil) + resp := struct { + Breakdown DetailedPortfolioResponse `json:"breakdown"` + }{} + return &resp.Breakdown, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, nil, true, &resp, nil) } // DeletePortfolio deletes a portfolio @@ -582,40 +580,42 @@ func (c *CoinbasePro) DeletePortfolio(ctx context.Context, portfolioID string) e return errPortfolioIDEmpty } path := coinbaseV3 + coinbasePortfolios + "/" + portfolioID - return c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodDelete, path, nil, nil, - true, nil, nil) + 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) { +func (c *CoinbasePro) EditPortfolio(ctx context.Context, portfolioID, name string) (*SimplePortfolioData, error) { if portfolioID == "" { return nil, errPortfolioIDEmpty } if name == "" { return nil, errNameEmpty } - req := map[string]interface{}{"name": name} + req := map[string]any{"name": name} path := coinbaseV3 + coinbasePortfolios + "/" + portfolioID - var resp *SimplePortfolioResponse - return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPut, - path, nil, req, true, &resp, nil) + resp := struct { + Portfolio SimplePortfolioData `json:"portfolio"` + }{} + return &resp.Portfolio, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPut, 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 + resp := struct { + BalanceSummary FuturesBalanceSummary `json:"balance_summary"` + }{} path := coinbaseV3 + coinbaseCFM + "/" + coinbaseBalanceSummary - return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - path, nil, nil, true, &resp, nil) + return &resp.BalanceSummary, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, 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) ([]FuturesPosition, error) { - var resp AllFuturesPositions + resp := struct { + Positions []FuturesPosition `json:"positions"` + }{} path := coinbaseV3 + coinbaseCFM + "/" + coinbasePositions - return resp.Positions, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - path, nil, nil, true, &resp, nil) + 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 @@ -625,8 +625,7 @@ func (c *CoinbasePro) GetFuturesPositionByID(ctx context.Context, productID stri } path := coinbaseV3 + coinbaseCFM + "/" + coinbasePositions + "/" + productID var resp *FuturesPosition - return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - path, nil, nil, true, &resp, nil) + return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, nil, true, &resp, nil) } // ScheduleFuturesSweep schedules a sweep of funds from a CFTC-regulated futures account to a @@ -636,64 +635,67 @@ func (c *CoinbasePro) GetFuturesPositionByID(ctx context.Context, productID stri // in the futures account. An amount of 0 will sweep all available excess funds func (c *CoinbasePro) ScheduleFuturesSweep(ctx context.Context, amount float64) (bool, error) { path := coinbaseV3 + coinbaseCFM + "/" + coinbaseSweeps + "/" + coinbaseSchedule - req := make(map[string]interface{}) + var req map[string]any if amount != 0 { - req["usd_amount"] = strconv.FormatFloat(amount, 'f', -1, 64) + req = map[string]any{"usd_amount": strconv.FormatFloat(amount, 'f', -1, 64)} } - var resp SuccessBool - return resp.Success, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, - path, nil, req, true, &resp, nil) + resp := struct { + Success bool `json:"success"` + }{} + 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) ([]SweepData, error) { - var resp ListFuturesSweepsResponse + resp := struct { + Sweeps []SweepData `json:"sweeps"` + }{} path := coinbaseV3 + coinbaseCFM + "/" + coinbaseSweeps - return resp.Sweeps, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - path, nil, nil, true, &resp, nil) + 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) (bool, error) { path := coinbaseV3 + coinbaseCFM + "/" + coinbaseSweeps - var resp SuccessBool - return resp.Success, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodDelete, - path, nil, nil, true, &resp, nil) + resp := struct { + Success bool `json:"success"` + }{} + 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 -func (c *CoinbasePro) AllocatePortfolio(ctx context.Context, portfolioID, productID, currency string, amount float64) error { +func (c *CoinbasePro) AllocatePortfolio(ctx context.Context, portfolioID, productID, cur string, amount float64) error { if portfolioID == "" { return errPortfolioIDEmpty } if productID == "" { return errProductIDEmpty } - if currency == "" { - return errCurrencyEmpty + if cur == "" { + return currency.ErrCurrencyCodeEmpty } if amount == 0 { - return errAmountEmpty + return order.ErrAmountIsInvalid } - req := map[string]interface{}{ + req := map[string]any{ "portfolio_uuid": portfolioID, "symbol": productID, - "currency": currency, + "currency": cur, "amount": strconv.FormatFloat(amount, 'f', -1, 64)} path := coinbaseV3 + coinbaseIntx + "/" + coinbaseAllocate - return c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, - path, nil, req, true, nil, nil) + return c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, 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) { +func (c *CoinbasePro) GetPerpetualsPortfolioSummary(ctx context.Context, portfolioID string) (*PerpetualsPortfolioSummary, error) { if portfolioID == "" { return nil, errPortfolioIDEmpty } path := coinbaseV3 + coinbaseIntx + "/" + coinbasePortfolio + "/" + portfolioID - var resp *PerpetualPortResponse - return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - path, nil, nil, true, &resp, nil) + resp := struct { + Summary PerpetualsPortfolioSummary `json:"summary"` + }{} + return &resp.Summary, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, nil, true, &resp, nil) } // GetAllPerpetualsPositions returns a list of all open positions in your perpetuals portfolio @@ -703,8 +705,7 @@ func (c *CoinbasePro) GetAllPerpetualsPositions(ctx context.Context, portfolioID } path := coinbaseV3 + coinbaseIntx + "/" + coinbasePositions + "/" + portfolioID var resp *AllPerpPosResponse - return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - path, nil, nil, true, &resp, nil) + return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, nil, true, &resp, nil) } // GetPerpetualsPositionByID returns information on a single open position in your perpetuals portfolio @@ -717,8 +718,7 @@ func (c *CoinbasePro) GetPerpetualsPositionByID(ctx context.Context, portfolioID } path := coinbaseV3 + coinbaseIntx + "/" + coinbasePositions + "/" + portfolioID + "/" + productID var resp *OnePerpPosResponse - return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - path, nil, nil, true, &resp, nil) + return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, nil, true, &resp, nil) } // GetTransactionSummary returns a summary of transactions with fee tiers, total volume, @@ -740,8 +740,7 @@ func (c *CoinbasePro) GetTransactionSummary(ctx context.Context, startDate, endD params.Values.Set("user_native_currency", userNativeCurrency) } var resp TransactionSummary - return &resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - coinbaseV3+coinbaseTransactionSummary, params.Values, nil, true, &resp, nil) + return &resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, coinbaseV3+coinbaseTransactionSummary, params.Values, nil, true, &resp, nil) } // CreateConvertQuote creates a quote for a conversion between two currencies. The trade_id returned @@ -751,20 +750,21 @@ func (c *CoinbasePro) CreateConvertQuote(ctx context.Context, from, to, userInce return nil, errAccountIDEmpty } if amount == 0 { - return nil, errAmountEmpty + return nil, order.ErrAmountIsInvalid } path := coinbaseV3 + coinbaseConvert + "/" + coinbaseQuote - tIM := map[string]interface{}{ + tIM := map[string]any{ "user_incentive_id": userIncentiveID, "code_val": codeVal} - req := map[string]interface{}{ + req := map[string]any{ "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, - nil, req, true, &resp, nil) + resp := struct { + Trade ConvertResponse `json:"trade"` + }{} + return &resp.Trade, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, path, nil, req, true, &resp, nil) } // CommitConvertTrade commits a conversion between two currencies, using the trade_id returned @@ -777,12 +777,13 @@ func (c *CoinbasePro) CommitConvertTrade(ctx context.Context, tradeID, from, to return nil, errAccountIDEmpty } path := coinbaseV3 + coinbaseConvert + "/" + coinbaseTrade + "/" + tradeID - req := map[string]interface{}{ + req := map[string]any{ "from_account": from, "to_account": to} - var resp *ConvertResponse - return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, path, - nil, req, true, &resp, nil) + resp := struct { + Trade ConvertResponse `json:"trade"` + }{} + return &resp.Trade, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, path, nil, req, true, &resp, nil) } // GetConvertTradeByID returns information on a conversion between two currencies @@ -794,12 +795,13 @@ func (c *CoinbasePro) GetConvertTradeByID(ctx context.Context, tradeID, from, to return nil, errAccountIDEmpty } path := coinbaseV3 + coinbaseConvert + "/" + coinbaseTrade + "/" + tradeID - req := map[string]interface{}{ + req := map[string]any{ "from_account": from, "to_account": to} - var resp *ConvertResponse - return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, - nil, req, true, &resp, nil) + resp := struct { + Trade ConvertResponse `json:"trade"` + }{} + return &resp.Trade, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, req, true, &resp, nil) } // GetV3Time returns the current server time, calling V3 of the API @@ -809,24 +811,26 @@ func (c *CoinbasePro) GetV3Time(ctx context.Context) (*ServerTimeV3, error) { } // GetAllPaymentMethods returns a list of all payment methods associated with the user's account -func (c *CoinbasePro) GetAllPaymentMethods(ctx context.Context) (*GetAllPaymentMethodsResp, error) { - var resp *GetAllPaymentMethodsResp - req := map[string]interface{}{"currency": "BTC"} +func (c *CoinbasePro) GetAllPaymentMethods(ctx context.Context) ([]PaymentMethodData, error) { + resp := struct { + PaymentMethods []PaymentMethodData `json:"payment_methods"` + }{} + req := map[string]any{"currency": "BTC"} path := coinbaseV3 + coinbasePaymentMethods - return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - path, nil, req, true, &resp, nil) + return resp.PaymentMethods, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, req, true, &resp, nil) } // GetPaymentMethodByID returns information on a single payment method associated with the user's // account -func (c *CoinbasePro) GetPaymentMethodByID(ctx context.Context, paymentMethodID string) (*GenPaymentMethodResp, error) { +func (c *CoinbasePro) GetPaymentMethodByID(ctx context.Context, paymentMethodID string) (*PaymentMethodData, error) { if paymentMethodID == "" { return nil, errPaymentMethodEmpty } path := coinbaseV3 + coinbasePaymentMethods + "/" + paymentMethodID - var resp *GenPaymentMethodResp - return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - path, nil, nil, true, &resp, nil) + resp := struct { + PaymentMethod PaymentMethodData `json:"payment_method"` + }{} + return &resp.PaymentMethod, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, nil, true, &resp, nil) } // ListNotifications lists the notifications the user is subscribed to @@ -835,15 +839,15 @@ func (c *CoinbasePro) ListNotifications(ctx context.Context, pag PaginationInp) var params Params params.Values = url.Values{} params.preparePagination(pag) - return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - coinbaseV2+coinbaseNotifications, params.Values, nil, false, &resp, nil) + return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, coinbaseV2+coinbaseNotifications, params.Values, 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, nil, false, &resp, nil) + resp := struct { + Data UserResponse `json:"data"` + }{} + return &resp.Data, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, coinbaseV2+coinbaseUser, nil, nil, false, &resp, nil) } // GetAllWallets lists all accounts associated with the API key @@ -852,13 +856,12 @@ func (c *CoinbasePro) GetAllWallets(ctx context.Context, pag PaginationInp) (*Ge var params Params params.Values = url.Values{} params.preparePagination(pag) - return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - coinbaseV2+coinbaseAccounts, params.Values, nil, false, &resp, nil) + return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, coinbaseV2+coinbaseAccounts, params.Values, nil, false, &resp, nil) } // GetWalletByID returns information about a single wallet. In lieu of a wallet ID, // a currency can be provided to get the primary account for that currency -func (c *CoinbasePro) GetWalletByID(ctx context.Context, walletID, currency string) (*GenWalletResponse, error) { +func (c *CoinbasePro) GetWalletByID(ctx context.Context, walletID, currency string) (*WalletData, error) { if (walletID == "" && currency == "") || (walletID != "" && currency != "") { return nil, errCurrWalletConflict } @@ -869,21 +872,23 @@ func (c *CoinbasePro) GetWalletByID(ctx context.Context, walletID, currency stri if currency != "" { path = coinbaseV2 + coinbaseAccounts + "/" + currency } - var resp *GenWalletResponse - return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - path, nil, nil, false, &resp, nil) + resp := struct { + Data WalletData `json:"data"` + }{} + return &resp.Data, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, nil, false, &resp, nil) } // CreateAddress generates a crypto address for depositing to the specified wallet -func (c *CoinbasePro) CreateAddress(ctx context.Context, walletID, name string) (*GenAddrResponse, error) { +func (c *CoinbasePro) CreateAddress(ctx context.Context, walletID, name string) (*AddressData, error) { if walletID == "" { return nil, errWalletIDEmpty } path := coinbaseV2 + coinbaseAccounts + "/" + walletID + "/" + coinbaseAddresses - req := map[string]interface{}{"name": name} - var resp *GenAddrResponse - return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, - path, nil, req, false, &resp, nil) + req := map[string]any{"name": name} + resp := struct { + Data AddressData `json:"data"` + }{} + return &resp.Data, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, path, nil, req, false, &resp, nil) } // GetAllAddresses returns information on all addresses associated with a wallet @@ -896,12 +901,11 @@ func (c *CoinbasePro) GetAllAddresses(ctx context.Context, walletID string, pag params.Values = url.Values{} params.preparePagination(pag) var resp *GetAllAddrResponse - return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - path, params.Values, nil, false, &resp, nil) + return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, params.Values, nil, false, &resp, nil) } // GetAddressByID returns information on a single address associated with the specified wallet -func (c *CoinbasePro) GetAddressByID(ctx context.Context, walletID, addressID string) (*GenAddrResponse, error) { +func (c *CoinbasePro) GetAddressByID(ctx context.Context, walletID, addressID string) (*AddressData, error) { if walletID == "" { return nil, errWalletIDEmpty } @@ -909,9 +913,10 @@ func (c *CoinbasePro) GetAddressByID(ctx context.Context, walletID, addressID st return nil, errAddressIDEmpty } path := coinbaseV2 + coinbaseAccounts + "/" + walletID + "/" + coinbaseAddresses + "/" + addressID - var resp *GenAddrResponse - return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - path, nil, nil, false, &resp, nil) + resp := struct { + Data AddressData `json:"data"` + }{} + return &resp.Data, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, nil, false, &resp, nil) } // GetAddressTransactions returns a list of transactions associated with the specified address @@ -922,14 +927,12 @@ func (c *CoinbasePro) GetAddressTransactions(ctx context.Context, walletID, addr if addressID == "" { return nil, errAddressIDEmpty } - path := coinbaseV2 + coinbaseAccounts + "/" + walletID + "/" + coinbaseAddresses + "/" + addressID + "/" + - coinbaseTransactions + path := coinbaseV2 + coinbaseAccounts + "/" + walletID + "/" + coinbaseAddresses + "/" + addressID + "/" + coinbaseTransactions var params Params params.Values = url.Values{} params.preparePagination(pag) var resp *ManyTransactionsResp - return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - path, params.Values, nil, false, &resp, nil) + return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, params.Values, nil, false, &resp, nil) } // SendMoney can send funds to an email or cryptocurrency address (if "traType" is set to "send"), @@ -938,7 +941,7 @@ func (c *CoinbasePro) GetAddressTransactions(ctx context.Context, walletID, addr // string for idempotency; a token with a max length of 100 characters, if a previous // transaction included the same token as a parameter, the new transaction won't be processed, // and information on the previous transaction will be returned instead -func (c *CoinbasePro) SendMoney(ctx context.Context, traType, walletID, to, currency, description, idem, financialInstitutionWebsite, destinationTag string, amount float64, skipNotifications, toFinancialInstitution bool) (*GenTransactionResp, error) { +func (c *CoinbasePro) SendMoney(ctx context.Context, traType, walletID, to, cur, description, idem, financialInstitutionWebsite, destinationTag string, amount float64, skipNotifications, toFinancialInstitution bool) (*TransactionData, error) { if traType == "" { return nil, errTransactionTypeEmpty } @@ -949,26 +952,27 @@ func (c *CoinbasePro) SendMoney(ctx context.Context, traType, walletID, to, curr return nil, errToEmpty } if amount == 0 { - return nil, errAmountEmpty + return nil, order.ErrAmountIsInvalid } - if currency == "" { - return nil, errCurrencyEmpty + if cur == "" { + return nil, currency.ErrCurrencyCodeEmpty } path := coinbaseV2 + coinbaseAccounts + "/" + walletID + "/" + coinbaseTransactions - req := map[string]interface{}{ + req := map[string]any{ "type": traType, "to": to, "amount": strconv.FormatFloat(amount, 'f', -1, 64), - "currency": currency, + "currency": cur, "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, nil, req, false, &resp, nil) + resp := struct { + Data TransactionData `json:"data"` + }{} + return &resp.Data, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, path, nil, req, false, &resp, nil) } // GetAllTransactions returns a list of transactions associated with the specified wallet @@ -981,13 +985,12 @@ func (c *CoinbasePro) GetAllTransactions(ctx context.Context, walletID string, p params.Values = url.Values{} params.preparePagination(pag) var resp *ManyTransactionsResp - return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - path, params.Values, nil, false, &resp, nil) + return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, params.Values, nil, false, &resp, nil) } // GetTransactionByID returns information on a single transaction associated with the // specified wallet -func (c *CoinbasePro) GetTransactionByID(ctx context.Context, walletID, transactionID string) (*GenTransactionResp, error) { +func (c *CoinbasePro) GetTransactionByID(ctx context.Context, walletID, transactionID string) (*TransactionData, error) { if walletID == "" { return nil, errWalletIDEmpty } @@ -995,23 +998,24 @@ func (c *CoinbasePro) GetTransactionByID(ctx context.Context, walletID, transact return nil, errTransactionIDEmpty } path := coinbaseV2 + coinbaseAccounts + "/" + walletID + "/" + coinbaseTransactions + "/" + transactionID - var resp *GenTransactionResp - return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - path, nil, nil, false, &resp, nil) + resp := struct { + Data TransactionData `json:"data"` + }{} + return &resp.Data, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, nil, false, &resp, nil) } // FiatTransfer prepares and optionally processes a transfer of funds between the exchange and a // fiat payment method. "Deposit" signifies funds going from exchange to bank, "withdraw" // signifies funds going from bank to exchange -func (c *CoinbasePro) FiatTransfer(ctx context.Context, walletID, currency, paymentMethod string, amount float64, commit bool, transferType FiatTransferType) (*GenDeposWithdrResp, error) { +func (c *CoinbasePro) FiatTransfer(ctx context.Context, walletID, cur, paymentMethod string, amount float64, commit bool, transferType FiatTransferType) (*DeposWithdrData, error) { if walletID == "" { return nil, errWalletIDEmpty } if amount == 0 { - return nil, errAmountEmpty + return nil, order.ErrAmountIsInvalid } - if currency == "" { - return nil, errCurrencyEmpty + if cur == "" { + return nil, currency.ErrCurrencyCodeEmpty } if paymentMethod == "" { return nil, errPaymentMethodEmpty @@ -1023,19 +1027,20 @@ func (c *CoinbasePro) FiatTransfer(ctx context.Context, walletID, currency, paym case FiatWithdrawal: path = coinbaseV2 + coinbaseAccounts + "/" + walletID + "/" + coinbaseWithdrawals } - req := map[string]interface{}{ - "currency": currency, + req := map[string]any{ + "currency": cur, "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, nil, req, false, &resp, nil) + resp := struct { + Data DeposWithdrData `json:"data"` + }{} + return &resp.Data, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, path, nil, req, false, &resp, nil) } // CommitTransfer processes a deposit/withdrawal that was created with the "commit" parameter set // to false -func (c *CoinbasePro) CommitTransfer(ctx context.Context, walletID, depositID string, transferType FiatTransferType) (*GenDeposWithdrResp, error) { +func (c *CoinbasePro) CommitTransfer(ctx context.Context, walletID, depositID string, transferType FiatTransferType) (*DeposWithdrData, error) { if walletID == "" { return nil, errWalletIDEmpty } @@ -1049,9 +1054,10 @@ func (c *CoinbasePro) CommitTransfer(ctx context.Context, walletID, depositID st case FiatWithdrawal: path = coinbaseV2 + coinbaseAccounts + "/" + walletID + "/" + coinbaseWithdrawals + "/" + depositID + "/" + coinbaseCommit } - var resp *GenDeposWithdrResp - return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, - path, nil, nil, false, &resp, nil) + resp := struct { + Data DeposWithdrData `json:"data"` + }{} + return &resp.Data, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, path, nil, nil, false, &resp, nil) } // GetAllFiatTransfers returns a list of transfers either to or from fiat payment methods and @@ -1071,8 +1077,7 @@ func (c *CoinbasePro) GetAllFiatTransfers(ctx context.Context, walletID string, params.Values = url.Values{} params.preparePagination(pag) var resp *ManyDeposWithdrResp - err := c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - path, params.Values, nil, false, &resp, nil) + err := c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, params.Values, nil, false, &resp, nil) if err != nil { return nil, err } @@ -1083,7 +1088,7 @@ func (c *CoinbasePro) GetAllFiatTransfers(ctx context.Context, walletID string, } // GetFiatTransferByID returns information on a single deposit/withdrawal associated with the specified wallet -func (c *CoinbasePro) GetFiatTransferByID(ctx context.Context, walletID, depositID string, transferType FiatTransferType) (*GenDeposWithdrResp, error) { +func (c *CoinbasePro) GetFiatTransferByID(ctx context.Context, walletID, depositID string, transferType FiatTransferType) (*DeposWithdrData, error) { if walletID == "" { return nil, errWalletIDEmpty } @@ -1097,20 +1102,25 @@ func (c *CoinbasePro) GetFiatTransferByID(ctx context.Context, walletID, deposit case FiatWithdrawal: path = coinbaseV2 + coinbaseAccounts + "/" + walletID + "/" + coinbaseWithdrawals + "/" + depositID } - var resp *GenDeposWithdrResp - return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, - path, nil, nil, false, &resp, nil) + resp := struct { + Data DeposWithdrData `json:"data"` + }{} + return &resp.Data, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, nil, false, &resp, nil) } // GetFiatCurrencies lists currencies that Coinbase knows about func (c *CoinbasePro) GetFiatCurrencies(ctx context.Context) ([]FiatData, error) { - var resp GetFiatCurrenciesResp + resp := struct { + Data []FiatData `json:"data"` + }{} return resp.Data, c.SendHTTPRequest(ctx, exchange.RestSpot, coinbaseV2+coinbaseCurrencies, nil, &resp) } // GetCryptocurrencies lists cryptocurrencies that Coinbase knows about func (c *CoinbasePro) GetCryptocurrencies(ctx context.Context) ([]CryptoData, error) { - var resp GetCryptocurrenciesResp + resp := struct { + Data []CryptoData `json:"data"` + }{} path := coinbaseV2 + coinbaseCurrencies + "/" + coinbaseCrypto return resp.Data, c.SendHTTPRequest(ctx, exchange.RestSpot, path, nil, &resp) } @@ -1118,10 +1128,12 @@ func (c *CoinbasePro) GetCryptocurrencies(ctx context.Context) ([]CryptoData, er // 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 + resp := struct { + Data GetExchangeRatesResp `json:"data"` + }{} vals := url.Values{} vals.Set("currency", currency) - return resp, c.SendHTTPRequest(ctx, exchange.RestSpot, coinbaseV2+coinbaseExchangeRates, vals, &resp) + return &resp.Data, c.SendHTTPRequest(ctx, exchange.RestSpot, coinbaseV2+coinbaseExchangeRates, vals, &resp) } // GetPrice returns the price the spot/buy/sell price for the specified currency pair, @@ -1134,14 +1146,18 @@ func (c *CoinbasePro) GetPrice(ctx context.Context, currencyPair, priceType stri default: return nil, errInvalidPriceType } - var resp *GetPriceResp - return resp, c.SendHTTPRequest(ctx, exchange.RestSpot, path, nil, &resp) + resp := struct { + Data GetPriceResp `json:"data"` + }{} + return &resp.Data, c.SendHTTPRequest(ctx, exchange.RestSpot, path, nil, &resp) } // GetV2Time returns the current server time, calling V2 of the API func (c *CoinbasePro) GetV2Time(ctx context.Context) (*ServerTimeV2, error) { - var resp *ServerTimeV2 - return resp, c.SendHTTPRequest(ctx, exchange.RestSpot, coinbaseV2+coinbaseTime, nil, &resp) + resp := struct { + Data ServerTimeV2 `json:"data"` + }{} + return &resp.Data, c.SendHTTPRequest(ctx, exchange.RestSpot, coinbaseV2+coinbaseTime, nil, &resp) } // GetAllCurrencies returns a list of all currencies that Coinbase knows about. These aren't necessarily tradable @@ -1151,12 +1167,12 @@ func (c *CoinbasePro) GetAllCurrencies(ctx context.Context) ([]CurrencyData, err } // GetACurrency returns information on a single currency specified by the user -func (c *CoinbasePro) GetACurrency(ctx context.Context, currency string) (*CurrencyData, error) { - if currency == "" { - return nil, errCurrencyEmpty +func (c *CoinbasePro) GetACurrency(ctx context.Context, cur string) (*CurrencyData, error) { + if cur == "" { + return nil, currency.ErrCurrencyCodeEmpty } var resp *CurrencyData - path := coinbaseCurrencies + "/" + currency + path := coinbaseCurrencies + "/" + cur return resp, c.SendHTTPRequest(ctx, exchange.RestSpotSupplementary, path, nil, &resp) } @@ -1180,7 +1196,7 @@ func (c *CoinbasePro) GetAllPairVolumes(ctx context.Context) ([]PairVolumeData, // GetPairDetails returns information on a single currency pair func (c *CoinbasePro) GetPairDetails(ctx context.Context, pair string) (*PairData, error) { if pair == "" { - return nil, errPairEmpty + return nil, currency.ErrCurrencyPairEmpty } var resp *PairData path := coinbaseProducts + "/" + pair @@ -1192,7 +1208,7 @@ func (c *CoinbasePro) GetPairDetails(ctx context.Context, pair string) (*PairDat // non-aggregated order book. func (c *CoinbasePro) GetProductBookV1(ctx context.Context, pair string, level uint8) (*OrderBook, error) { if pair == "" { - return nil, errPairEmpty + return nil, currency.ErrCurrencyPairEmpty } var resp OrderBookResp vals := url.Values{} @@ -1274,7 +1290,7 @@ func (c *CoinbasePro) GetProductBookV1(ctx context.Context, pair string, level u // GetProductCandles returns historical market data for the specified currency pair. func (c *CoinbasePro) GetProductCandles(ctx context.Context, pair string, granularity uint32, startTime, endTime time.Time) ([]Candle, error) { if pair == "" { - return nil, errPairEmpty + return nil, currency.ErrCurrencyPairEmpty } var params Params params.Values = url.Values{} @@ -1334,7 +1350,7 @@ func (c *CoinbasePro) GetProductCandles(ctx context.Context, pair string, granul // GetProductStats returns information on a specific pair's price and volume func (c *CoinbasePro) GetProductStats(ctx context.Context, pair string) (*ProductStats, error) { if pair == "" { - return nil, errPairEmpty + return nil, currency.ErrCurrencyPairEmpty } path := coinbaseProducts + "/" + pair + "/" + coinbaseStats var resp *ProductStats @@ -1344,7 +1360,7 @@ func (c *CoinbasePro) GetProductStats(ctx context.Context, pair string) (*Produc // GetProductTicker returns the ticker for the specified currency pair func (c *CoinbasePro) GetProductTicker(ctx context.Context, pair string) (*ProductTicker, error) { if pair == "" { - return nil, errPairEmpty + return nil, currency.ErrCurrencyPairEmpty } path := coinbaseProducts + "/" + pair + "/" + coinbaseTicker var resp *ProductTicker @@ -1354,7 +1370,7 @@ func (c *CoinbasePro) GetProductTicker(ctx context.Context, pair string) (*Produ // GetProductTrades returns a list of the latest traides for a pair func (c *CoinbasePro) GetProductTrades(ctx context.Context, pair, step, direction string, limit int64) ([]ProductTrades, error) { if pair == "" { - return nil, errPairEmpty + return nil, currency.ErrCurrencyPairEmpty } vals := url.Values{} if step != "" { @@ -1393,11 +1409,15 @@ func (c *CoinbasePro) GetWrappedAssetConversionRate(ctx context.Context, wrapped } // SendHTTPRequest sends an unauthenticated HTTP request -func (c *CoinbasePro) SendHTTPRequest(ctx context.Context, ep exchange.URL, path string, vals url.Values, result interface{}) error { +func (c *CoinbasePro) SendHTTPRequest(ctx context.Context, ep exchange.URL, path string, vals url.Values, result any) error { endpoint, err := c.API.Endpoints.GetURL(ep) if err != nil { return err } + rLim := PubRate + if strings.Contains(path, coinbaseV2) { + rLim = V2Rate + } path = common.EncodeURLValues(path, vals) item := &request.Item{ Method: http.MethodGet, @@ -1407,17 +1427,13 @@ func (c *CoinbasePro) SendHTTPRequest(ctx context.Context, ep exchange.URL, path HTTPDebugging: c.HTTPDebugging, HTTPRecording: c.HTTPRecording, } - rLim := PubRate - if strings.Contains(path, coinbaseV2) { - rLim = V2Rate - } return c.SendPayload(ctx, rLim, 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 string, queryParams url.Values, 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]any, isVersion3 bool, result any, returnHead *http.Header) (err error) { creds, err := c.GetCredentials(ctx) if err != nil { return err @@ -1450,12 +1466,17 @@ func (c *CoinbasePro) SendAuthenticatedHTTPRequest(ctx context.Context, ep excha return nil, err } // TODO: Implement JWT authentication once it's supported by all endpoints we care about + // jwt, err := c.GetJWT(ctx, method+" "+path) + // if err != nil { + // return nil, err + // } 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-27" + headers["CB-VERSION"] = "2024-09-24" + // headers["Authorization"] = "Bearer " + jwt // Version 3 only wants query params in the path when the request is sent if isVersion3 { path += queryString @@ -1492,9 +1513,7 @@ func (c *CoinbasePro) SendAuthenticatedHTTPRequest(ctx context.Context, ep excha }{} if err = json.Unmarshal(interim, &singleErrCap); err == nil { if singleErrCap.Message != "" { - 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 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) } } manyErrCap := struct { @@ -1510,11 +1529,8 @@ func (c *CoinbasePro) SendAuthenticatedHTTPRequest(ctx context.Context, ep excha if err == nil { 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 !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 != "" { @@ -1544,11 +1560,9 @@ func (c *CoinbasePro) GetFee(ctx context.Context, feeBuilder *exchange.FeeBuilde } else { fee = fees.FeeTier.TakerFeeRate } - case feeBuilder.IsMaker && isStablePair(feeBuilder.Pair) && - (feeBuilder.FeeType == exchange.CryptocurrencyTradeFee || feeBuilder.FeeType == exchange.OfflineTradeFee): + case feeBuilder.IsMaker && isStablePair(feeBuilder.Pair) && (feeBuilder.FeeType == exchange.CryptocurrencyTradeFee || feeBuilder.FeeType == exchange.OfflineTradeFee): fee = StablePairMakerFee - case !feeBuilder.IsMaker && isStablePair(feeBuilder.Pair) && - (feeBuilder.FeeType == exchange.CryptocurrencyTradeFee || feeBuilder.FeeType == exchange.OfflineTradeFee): + case !feeBuilder.IsMaker && isStablePair(feeBuilder.Pair) && (feeBuilder.FeeType == exchange.CryptocurrencyTradeFee || feeBuilder.FeeType == exchange.OfflineTradeFee): fee = WorstCaseStablePairTakerFee case feeBuilder.IsMaker && !isStablePair(feeBuilder.Pair) && feeBuilder.FeeType == exchange.OfflineTradeFee: fee = WorstCaseMakerFee @@ -1633,6 +1647,9 @@ func prepareOrderConfig(orderType, side, stopDirection string, amount, limitPric orderConfig.LimitLimitGTC.LimitPrice = types.Number(limitPrice) orderConfig.LimitLimitGTC.PostOnly = postOnly } else { + if endTime.Before(time.Now()) { + return orderConfig, errEndTimeInPast + } orderConfig.LimitLimitGTD = &LimitLimitGTD{} orderConfig.LimitLimitGTD.BaseSize = types.Number(amount) orderConfig.LimitLimitGTD.LimitPrice = types.Number(limitPrice) @@ -1647,6 +1664,9 @@ func prepareOrderConfig(orderType, side, stopDirection string, amount, limitPric orderConfig.StopLimitStopLimitGTC.StopPrice = types.Number(stopPrice) orderConfig.StopLimitStopLimitGTC.StopDirection = stopDirection } else { + if endTime.Before(time.Now()) { + return orderConfig, errEndTimeInPast + } orderConfig.StopLimitStopLimitGTD = &StopLimitStopLimitGTD{} orderConfig.StopLimitStopLimitGTD.BaseSize = types.Number(amount) orderConfig.StopLimitStopLimitGTD.LimitPrice = types.Number(limitPrice) @@ -1660,8 +1680,8 @@ func prepareOrderConfig(orderType, side, stopDirection string, amount, limitPric return orderConfig, nil } -// formatMarginType properly formats the margin type for the request -func formatMarginType(marginType string) string { +// FormatMarginType properly formats the margin type for the request +func FormatMarginType(marginType string) string { if marginType == "ISOLATED" || marginType == "CROSS" { return marginType } @@ -1703,3 +1723,28 @@ func (t *UnixTimestamp) String() string { func (t *UnixTimestamp) Time() time.Time { return time.Time(*t) } + +// UnmarshalJSON unmarshals the JSON input into a UnixTimestamp type +func (t *UnixTimestampMilli) UnmarshalJSON(b []byte) error { + var timestampStr string + err := json.Unmarshal(b, ×tampStr) + if err != nil { + return err + } + timestamp, err := strconv.ParseInt(timestampStr, 10, 64) + if err != nil { + return err + } + *t = UnixTimestampMilli(time.UnixMilli(timestamp).UTC()) + return nil +} + +// String implements the stringer interface +func (t *UnixTimestampMilli) String() string { + return t.Time().String() +} + +// Time returns the time.Time representation of the UnixTimestamp +func (t *UnixTimestampMilli) Time() time.Time { + return time.Time(*t) +} diff --git a/exchanges/coinbasepro/coinbasepro_test.go b/exchanges/coinbasepro/coinbasepro_test.go index f281ff817f9..eaf0a35e2d2 100644 --- a/exchanges/coinbasepro/coinbasepro_test.go +++ b/exchanges/coinbasepro/coinbasepro_test.go @@ -71,7 +71,7 @@ const ( errPortTransferInsufFunds = `CoinbasePro unsuccessful HTTP status code: 429 raw response: {"error":"unknown","error_details":"[PORTFOLIO_ERROR_CODE_INSUFFICIENT_FUNDS] insufficient funds in source account","message":"[PORTFOLIO_ERROR_CODE_INSUFFICIENT_FUNDS] insufficient funds in source account"}, authenticated request failed` errInvalidProductID = `CoinbasePro unsuccessful HTTP status code: 404 raw response: {"error":"NOT_FOUND","error_details":"valid product_id is required","message":"valid product_id is required"}` errExpectedFeeRange = "expected fee range of %v and %v, received %v" - errOptionInvalid = `CoinbasePro unsuccessful HTTP status code: 400 raw response: {"error":"unknown","error_details":"parsing field \"product_type\": \"OPTION\" is not a valid value","message":"parsing field \"product_type\": \"OPTION\" is not a valid value"}` + errOptionInvalid = `CoinbasePro unsuccessful HTTP status code: 400 raw response: {"error":"unknown","error_details":"parsing field \"product_type\": \"OPTIONS\" is not a valid value","message":"parsing field \"product_type\": \"OPTIONS\" is not a valid value"}` expectedTimestamp = "1970-01-01 00:20:34 +0000 UTC" ) @@ -184,13 +184,11 @@ func TestGetProductBookV3(t *testing.T) { func TestGetAllProducts(t *testing.T) { t.Parallel() testPairs := []string{testPair.String(), "ETH-USD"} - resp, err := c.GetAllProducts(context.Background(), 30000, 1, "SPOT", "PERPETUAL", "STATUS_ALL", - testPairs, false) + resp, err := c.GetAllProducts(context.Background(), 30000, 1, "SPOT", "PERPETUAL", "STATUS_ALL", testPairs, false) assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - resp, err = c.GetAllProducts(context.Background(), 30000, 1, "SPOT", "PERPETUAL", "STATUS_ALL", - nil, true) + resp, err = c.GetAllProducts(context.Background(), 0, 1, "SPOT", "PERPETUAL", "STATUS_ALL", nil, true) assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) } @@ -213,14 +211,12 @@ func TestGetHistoricRates(t *testing.T) { _, err := c.GetHistoricRates(context.Background(), "", granUnknown, time.Time{}, time.Time{}, false) assert.ErrorIs(t, err, errProductIDEmpty) _, err = c.GetHistoricRates(context.Background(), testPair.String(), "blorbo", time.Time{}, time.Time{}, false) - assert.ErrorIs(t, err, errInvalidGranularity) - resp, err := c.GetHistoricRates(context.Background(), testPair.String(), granOneMin, - time.Now().Add(-5*time.Minute), time.Now(), false) + assert.ErrorIs(t, err, kline.ErrUnsupportedInterval) + resp, err := c.GetHistoricRates(context.Background(), testPair.String(), granOneMin, time.Now().Add(-5*time.Minute), time.Now(), false) assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - resp, err = c.GetHistoricRates(context.Background(), testPair.String(), granOneMin, - time.Now().Add(-5*time.Minute), time.Now(), true) + resp, err = c.GetHistoricRates(context.Background(), testPair.String(), granOneMin, time.Now().Add(-5*time.Minute), time.Now(), true) assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) } @@ -229,13 +225,11 @@ func TestGetTicker(t *testing.T) { t.Parallel() _, err := c.GetTicker(context.Background(), "", 1, time.Time{}, time.Time{}, false) assert.ErrorIs(t, err, errProductIDEmpty) - resp, err := c.GetTicker(context.Background(), testPair.String(), 5, time.Now().Add(-time.Minute*5), time.Now(), - false) + resp, err := c.GetTicker(context.Background(), testPair.String(), 5, time.Now().Add(-time.Minute*5), time.Now(), false) assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - resp, err = c.GetTicker(context.Background(), testPair.String(), 5, time.Now().Add(-time.Minute*5), time.Now(), - true) + resp, err = c.GetTicker(context.Background(), testPair.String(), 5, time.Now().Add(-time.Minute*5), time.Now(), true) assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) } @@ -246,29 +240,25 @@ func TestPlaceOrder(t *testing.T) { assert.ErrorIs(t, err, errClientOrderIDEmpty) _, err = c.PlaceOrder(context.Background(), "meow", "", "", "", "", "", "", "", 0, 0, 0, 0, false, time.Time{}) assert.ErrorIs(t, err, errProductIDEmpty) - _, err = c.PlaceOrder(context.Background(), "meow", testPair.String(), order.Sell.String(), "", "", "", "", "", 0, - 0, 0, 0, false, time.Time{}) - assert.ErrorIs(t, err, errAmountEmpty) + _, err = c.PlaceOrder(context.Background(), "meow", testPair.String(), order.Sell.String(), "", "", "", "", "", 0, 0, 0, 0, false, time.Time{}) + assert.ErrorIs(t, err, order.ErrAmountIsInvalid) sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) skipTestIfLowOnFunds(t) id, err := uuid.NewV4() assert.NoError(t, err) - resp, err := c.PlaceOrder(context.Background(), id.String(), testPair.String(), order.Sell.String(), "", - order.Limit.String(), "", "CROSS", "", testAmount, testPrice, 0, 9999, false, time.Now().Add(time.Hour)) + resp, err := c.PlaceOrder(context.Background(), id.String(), testPair.String(), order.Sell.String(), "", order.Limit.String(), "", "CROSS", "", testAmount, testPrice, 0, 9999, false, time.Now().Add(time.Hour)) assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) id, err = uuid.NewV4() assert.NoError(t, err) - resp, err = c.PlaceOrder(context.Background(), id.String(), testPair.String(), order.Sell.String(), "", - order.Limit.String(), "", "MULTI", "", testAmount, testPrice, 0, 9999, false, time.Now().Add(time.Hour)) + resp, err = c.PlaceOrder(context.Background(), id.String(), testPair.String(), order.Sell.String(), "", order.Limit.String(), "", "MULTI", "", testAmount, testPrice, 0, 9999, false, time.Now().Add(time.Hour)) assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) } func orderTestHelper(t *testing.T, orderSide string) *GetAllOrdersResp { t.Helper() - ordIDs, err := c.GetAllOrders(context.Background(), "", "", "", orderSide, "", "", "", "", "", []string{}, - []string{}, 1000, time.Time{}, time.Time{}) + ordIDs, err := c.GetAllOrders(context.Background(), "", "", "", orderSide, "", "", "", "", "", []string{}, []string{}, 1000, time.Time{}, time.Time{}) assert.NoError(t, err) if ordIDs == nil || len(ordIDs.Orders) == 0 { t.Skip(skipInsufficientOrders) @@ -326,20 +316,17 @@ func TestGetAllOrders(t *testing.T) { t.Parallel() assets := []string{testFiat.String()} status := make([]string, 2) - _, err := c.GetAllOrders(context.Background(), "", "", "", "", "", "", "", "", "", status, assets, 0, - time.Unix(2, 2), time.Unix(1, 1)) + _, err := c.GetAllOrders(context.Background(), "", "", "", "", "", "", "", "", "", status, assets, 0, time.Unix(2, 2), time.Unix(1, 1)) assert.ErrorIs(t, err, common.ErrStartAfterEnd) status[0] = "CANCELLED" status[1] = "OPEN" - _, err = c.GetAllOrders(context.Background(), "", "", "", "", "", "", "", "", "", status, assets, 0, time.Time{}, - time.Time{}) + _, err = c.GetAllOrders(context.Background(), "", "", "", "", "", "", "", "", "", status, assets, 0, time.Time{}, time.Time{}) assert.ErrorIs(t, err, errOpenPairWithOtherTypes) sharedtestvalues.SkipTestIfCredentialsUnset(t, c) status = make([]string, 0) assets = make([]string, 1) assets[0] = testCrypto.String() - _, err = c.GetAllOrders(context.Background(), "", testFiat.String(), "LIMIT", "SELL", "", "SPOT", - "RETAIL_ADVANCED", "UNKNOWN_CONTRACT_EXPIRY_TYPE", "2", status, assets, 10, time.Time{}, time.Time{}) + _, err = c.GetAllOrders(context.Background(), "", testFiat.String(), "LIMIT", "SELL", "", "SPOT", "RETAIL_ADVANCED", "UNKNOWN_CONTRACT_EXPIRY_TYPE", "2", status, assets, 10, time.Time{}, time.Time{}) assert.NoError(t, err) } @@ -351,8 +338,7 @@ func TestGetFills(t *testing.T) { _, err = c.GetFills(context.Background(), "", testPair.String(), "", time.Unix(1, 1), time.Now(), 5) assert.NoError(t, err) status := []string{"OPEN"} - ordID, err := c.GetAllOrders(context.Background(), "", "", "", "", "", "", "", "", "", status, nil, 3, time.Time{}, - time.Time{}) + ordID, err := c.GetAllOrders(context.Background(), "", "", "", "", "", "", "", "", "", status, nil, 3, time.Time{}, time.Time{}) assert.NoError(t, err) if ordID == nil || len(ordID.Orders) == 0 { t.Skip(skipInsufficientOrders) @@ -366,14 +352,12 @@ func TestGetOrderByID(t *testing.T) { _, err := c.GetOrderByID(context.Background(), "", "", "") assert.ErrorIs(t, err, errOrderIDEmpty) sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - ordID, err := c.GetAllOrders(context.Background(), "", "", "", "", "", "", "", "", "", nil, nil, 10, - time.Time{}, time.Time{}) + ordID, err := c.GetAllOrders(context.Background(), "", "", "", "", "", "", "", "", "", nil, nil, 10, time.Time{}, time.Time{}) assert.NoError(t, err) if ordID == nil || len(ordID.Orders) == 0 { t.Skip(skipInsufficientOrders) } - resp, err := c.GetOrderByID(context.Background(), ordID.Orders[0].OrderID, ordID.Orders[0].ClientOID, - testFiat.String()) + 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) } @@ -381,13 +365,12 @@ func TestGetOrderByID(t *testing.T) { func TestPreviewOrder(t *testing.T) { t.Parallel() _, err := c.PreviewOrder(context.Background(), "", "", "", "", "", 0, 0, 0, 0, 0, 0, false, false, false, time.Time{}) - assert.ErrorIs(t, err, errAmountEmpty) + assert.ErrorIs(t, err, order.ErrAmountIsInvalid) _, err = c.PreviewOrder(context.Background(), "", "", "", "", "", 0, 1, 0, 0, 0, 0, false, false, false, time.Time{}) assert.ErrorIs(t, err, errInvalidOrderType) sharedtestvalues.SkipTestIfCredentialsUnset(t, c) skipTestIfLowOnFunds(t) - resp, err := c.PreviewOrder(context.Background(), testPair.String(), "BUY", "MARKET", "", "ISOLATED", 0, - testAmount, 0, 0, 0, 0, false, false, false, time.Time{}) + resp, err := c.PreviewOrder(context.Background(), testPair.String(), "BUY", "MARKET", "", "ISOLATED", 0, testAmount, 0, 0, 0, 0, false, false, false, time.Time{}) assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) } @@ -416,17 +399,16 @@ func TestMovePortfolioFunds(t *testing.T) { _, err := c.MovePortfolioFunds(context.Background(), "", "", "", 0) assert.ErrorIs(t, err, errPortfolioIDEmpty) _, err = c.MovePortfolioFunds(context.Background(), "", "meowPort", "woofPort", 0) - assert.ErrorIs(t, err, errCurrencyEmpty) + assert.ErrorIs(t, err, currency.ErrCurrencyCodeEmpty) _, err = c.MovePortfolioFunds(context.Background(), testCrypto.String(), "meowPort", "woofPort", 0) - assert.ErrorIs(t, err, errAmountEmpty) + assert.ErrorIs(t, err, order.ErrAmountIsInvalid) sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) portID, err := c.GetAllPortfolios(context.Background(), "") assert.NoError(t, err) if len(portID) < 2 { t.Skip(skipInsufficientPortfolios) } - _, err = c.MovePortfolioFunds(context.Background(), testCrypto.String(), portID[0].UUID, portID[1].UUID, - testAmount) + _, err = c.MovePortfolioFunds(context.Background(), testCrypto.String(), portID[0].UUID, portID[1].UUID, testAmount) assert.NoError(t, err) } @@ -442,8 +424,8 @@ func TestGetPortfolioByID(t *testing.T) { } resp, err := c.GetPortfolioByID(context.Background(), portID[0].UUID) assert.NoError(t, err) - if resp.Breakdown.Portfolio != portID[0] { - t.Errorf(errExpectMismatch, resp.Breakdown.Portfolio, portID[0]) + if resp.Portfolio != portID[0] { + t.Errorf(errExpectMismatch, resp.Portfolio, portID[0]) } } @@ -506,7 +488,7 @@ func TestAllocatePortfolio(t *testing.T) { err = c.AllocatePortfolio(context.Background(), "meow", "", "", 0) assert.ErrorIs(t, err, errProductIDEmpty) err = c.AllocatePortfolio(context.Background(), "meow", "bark", "", 0) - assert.ErrorIs(t, err, errCurrencyEmpty) + assert.ErrorIs(t, err, currency.ErrCurrencyCodeEmpty) sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) pID := getINTXPortfolio(t) err = c.AllocatePortfolio(context.Background(), pID, testCrypto.String(), testFiat.String(), 0.001337) @@ -548,8 +530,7 @@ 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(), testFiat.String(), - 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) } @@ -559,7 +540,7 @@ func TestCreateConvertQuote(t *testing.T) { _, err := c.CreateConvertQuote(context.Background(), "", "", "", "", 0) assert.ErrorIs(t, err, errAccountIDEmpty) _, err = c.CreateConvertQuote(context.Background(), "meow", "123", "", "", 0) - assert.ErrorIs(t, err, errAmountEmpty) + assert.ErrorIs(t, err, order.ErrAmountIsInvalid) sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) fromAccID, toAccID := convertTestHelper(t) resp, err := c.CreateConvertQuote(context.Background(), fromAccID, toAccID, "", "", 0.01) @@ -594,10 +575,10 @@ func TestGetPaymentMethodByID(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) pmID, err := c.GetAllPaymentMethods(context.Background()) assert.NoError(t, err) - if pmID == nil || len(pmID.PaymentMethods) == 0 { + if pmID == nil || len(pmID) == 0 { t.Skip(skipPayMethodNotFound) } - resp, err := c.GetPaymentMethodByID(context.Background(), pmID.PaymentMethods[0].ID) + resp, err := c.GetPaymentMethodByID(context.Background(), pmID[0].ID) assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) } @@ -639,7 +620,7 @@ func TestGetWalletByID(t *testing.T) { resp, err := c.GetWalletByID(context.Background(), "", testCrypto.String()) assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) - resp, err = c.GetWalletByID(context.Background(), resp.Data.ID, "") + resp, err = c.GetWalletByID(context.Background(), resp.ID, "") assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) } @@ -652,7 +633,7 @@ func TestCreateAddress(t *testing.T) { wID, err := c.GetWalletByID(context.Background(), "", testCrypto.String()) assert.NoError(t, err) assert.NotEmpty(t, wID, errExpectedNonEmpty) - resp, err := c.CreateAddress(context.Background(), wID.Data.ID, "") + resp, err := c.CreateAddress(context.Background(), wID.ID, "") assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) } @@ -666,7 +647,7 @@ func TestGetAllAddresses(t *testing.T) { wID, err := c.GetWalletByID(context.Background(), "", testCrypto.String()) assert.NoError(t, err) assert.NotEmpty(t, wID, errExpectedNonEmpty) - resp, err := c.GetAllAddresses(context.Background(), wID.Data.ID, pag) + resp, err := c.GetAllAddresses(context.Background(), wID.ID, pag) assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) } @@ -681,10 +662,10 @@ func TestGetAddressByID(t *testing.T) { wID, err := c.GetWalletByID(context.Background(), "", testCrypto.String()) assert.NoError(t, err) assert.NotEmpty(t, wID, errExpectedNonEmpty) - addID, err := c.GetAllAddresses(context.Background(), wID.Data.ID, PaginationInp{}) + addID, err := c.GetAllAddresses(context.Background(), wID.ID, PaginationInp{}) assert.NoError(t, err) require.NotEmpty(t, addID, errExpectedNonEmpty) - resp, err := c.GetAddressByID(context.Background(), wID.Data.ID, addID.Data[0].ID) + resp, err := c.GetAddressByID(context.Background(), wID.ID, addID.Data[0].ID) assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) } @@ -699,10 +680,10 @@ func TestGetAddressTransactions(t *testing.T) { wID, err := c.GetWalletByID(context.Background(), "", testCrypto.String()) assert.NoError(t, err) assert.NotEmpty(t, wID, errExpectedNonEmpty) - addID, err := c.GetAllAddresses(context.Background(), wID.Data.ID, PaginationInp{}) + addID, err := c.GetAllAddresses(context.Background(), wID.ID, PaginationInp{}) assert.NoError(t, err) require.NotEmpty(t, addID, errExpectedNonEmpty) - _, err = c.GetAddressTransactions(context.Background(), wID.Data.ID, addID.Data[0].ID, PaginationInp{}) + _, err = c.GetAddressTransactions(context.Background(), wID.ID, addID.Data[0].ID, PaginationInp{}) assert.NoError(t, err) } @@ -715,9 +696,9 @@ func TestSendMoney(t *testing.T) { _, err = c.SendMoney(context.Background(), "123", "123", "", "", "", "", "", "", 0, false, false) assert.ErrorIs(t, err, errToEmpty) _, err = c.SendMoney(context.Background(), "123", "123", "123", "", "", "", "", "", 0, false, false) - assert.ErrorIs(t, err, errAmountEmpty) + assert.ErrorIs(t, err, order.ErrAmountIsInvalid) _, err = c.SendMoney(context.Background(), "123", "123", "123", "", "", "", "", "", 1, false, false) - assert.ErrorIs(t, err, errCurrencyEmpty) + assert.ErrorIs(t, err, currency.ErrCurrencyCodeEmpty) sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) wID, err := c.GetAllWallets(context.Background(), PaginationInp{}) assert.NoError(t, err) @@ -743,8 +724,7 @@ func TestSendMoney(t *testing.T) { if fromID == "" || toID == "" { t.Skip(skipInsufficientFundsOrWallets) } - resp, err := c.SendMoney(context.Background(), "transfer", wID.Data[0].ID, wID.Data[1].ID, - testCrypto.String(), "GCT Test", "123", "", "", testAmount, false, false) + resp, err := c.SendMoney(context.Background(), "transfer", wID.Data[0].ID, wID.Data[1].ID, testCrypto.String(), "GCT Test", "123", "", "", testAmount, false, false) assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) } @@ -758,7 +738,7 @@ func TestGetAllTransactions(t *testing.T) { wID, err := c.GetWalletByID(context.Background(), "", testCrypto.String()) assert.NoError(t, err) assert.NotEmpty(t, wID, errExpectedNonEmpty) - _, err = c.GetAllTransactions(context.Background(), wID.Data.ID, pag) + _, err = c.GetAllTransactions(context.Background(), wID.ID, pag) assert.NoError(t, err) } @@ -772,12 +752,12 @@ func TestGetTransactionByID(t *testing.T) { wID, err := c.GetWalletByID(context.Background(), "", testCrypto.String()) assert.NoError(t, err) assert.NotEmpty(t, wID, errExpectedNonEmpty) - tID, err := c.GetAllTransactions(context.Background(), wID.Data.ID, PaginationInp{}) + tID, err := c.GetAllTransactions(context.Background(), wID.ID, PaginationInp{}) assert.NoError(t, err) if tID == nil || len(tID.Data) == 0 { t.Skip(skipInsufficientTransactions) } - resp, err := c.GetTransactionByID(context.Background(), wID.Data.ID, tID.Data[0].ID) + resp, err := c.GetTransactionByID(context.Background(), wID.ID, tID.Data[0].ID) assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) } @@ -787,9 +767,9 @@ func TestFiatTransfer(t *testing.T) { _, err := c.FiatTransfer(context.Background(), "", "", "", 0, false, FiatDeposit) assert.ErrorIs(t, err, errWalletIDEmpty) _, err = c.FiatTransfer(context.Background(), "123", "", "", 0, false, FiatDeposit) - assert.ErrorIs(t, err, errAmountEmpty) + assert.ErrorIs(t, err, order.ErrAmountIsInvalid) _, err = c.FiatTransfer(context.Background(), "123", "", "", 1, false, FiatDeposit) - assert.ErrorIs(t, err, errCurrencyEmpty) + assert.ErrorIs(t, err, currency.ErrCurrencyCodeEmpty) _, err = c.FiatTransfer(context.Background(), "123", "123", "", 1, false, FiatDeposit) assert.ErrorIs(t, err, errPaymentMethodEmpty) sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) @@ -816,16 +796,14 @@ func TestCommitTransfer(t *testing.T) { assert.NoError(t, err) assert.NotEmpty(t, wallets, errExpectedNonEmpty) wID, pmID := transferTestHelper(t, wallets) - depID, err := c.FiatTransfer(context.Background(), wID, testFiat.String(), pmID, testAmount, - false, FiatDeposit) + depID, err := c.FiatTransfer(context.Background(), wID, testFiat.String(), pmID, testAmount, false, FiatDeposit) require.NoError(t, err) - resp, err := c.CommitTransfer(context.Background(), wID, depID.Data.ID, FiatDeposit) + resp, err := c.CommitTransfer(context.Background(), wID, depID.ID, FiatDeposit) assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) - depID, err = c.FiatTransfer(context.Background(), wID, testFiat.String(), pmID, testAmount, - false, FiatWithdrawal) + depID, err = c.FiatTransfer(context.Background(), wID, testFiat.String(), pmID, testAmount, false, FiatWithdrawal) require.NoError(t, err) - resp, err = c.CommitTransfer(context.Background(), wID, depID.Data.ID, FiatWithdrawal) + resp, err = c.CommitTransfer(context.Background(), wID, depID.ID, FiatWithdrawal) assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) } @@ -841,9 +819,9 @@ func TestGetAllFiatTransfers(t *testing.T) { assert.NotEmpty(t, wID, errExpectedNonEmpty) // Fiat deposits/withdrawals aren't accepted for fiat currencies for Australian business accounts; the error // "id not found" possibly reflects this - _, err = c.GetAllFiatTransfers(context.Background(), wID.Data.ID, pag, FiatDeposit) + _, err = c.GetAllFiatTransfers(context.Background(), wID.ID, pag, FiatDeposit) assert.NoError(t, err) - _, err = c.GetAllFiatTransfers(context.Background(), wID.Data.ID, pag, FiatWithdrawal) + _, err = c.GetAllFiatTransfers(context.Background(), wID.ID, pag, FiatWithdrawal) assert.NoError(t, err) } @@ -859,15 +837,15 @@ func TestGetFiatTransferByID(t *testing.T) { assert.NotEmpty(t, wID, errExpectedNonEmpty) // Fiat deposits/withdrawals aren't accepted for fiat currencies for Australian business accounts; the error // "id not found" possibly reflects this - dID, err := c.GetAllFiatTransfers(context.Background(), wID.Data.ID, PaginationInp{}, FiatDeposit) + dID, err := c.GetAllFiatTransfers(context.Background(), wID.ID, PaginationInp{}, FiatDeposit) assert.NoError(t, err) if dID == nil || len(dID.Data) == 0 { t.Skip(skipInsufficientTransactions) } - resp, err := c.GetFiatTransferByID(context.Background(), wID.Data.ID, dID.Data[0].ID, FiatDeposit) + resp, err := c.GetFiatTransferByID(context.Background(), wID.ID, dID.Data[0].ID, FiatDeposit) assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) - resp, err = c.GetFiatTransferByID(context.Background(), wID.Data.ID, dID.Data[0].ID, FiatWithdrawal) + resp, err = c.GetFiatTransferByID(context.Background(), wID.ID, dID.Data[0].ID, FiatWithdrawal) assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) } @@ -910,7 +888,7 @@ func TestGetAllCurrencies(t *testing.T) { func TestGetACurrency(t *testing.T) { t.Parallel() - testGetOneArg(t, c.GetACurrency, testCrypto.String(), errCurrencyEmpty) + testGetOneArg(t, c.GetACurrency, testCrypto.String(), currency.ErrCurrencyCodeEmpty) } func TestGetAllTradingPairs(t *testing.T) { @@ -926,13 +904,13 @@ func TestGetAllPairVolumes(t *testing.T) { func TestGetPairDetails(t *testing.T) { t.Parallel() - testGetOneArg(t, c.GetPairDetails, testPair.String(), errPairEmpty) + testGetOneArg(t, c.GetPairDetails, testPair.String(), currency.ErrCurrencyPairEmpty) } func TestGetProductBookV1(t *testing.T) { t.Parallel() _, err := c.GetProductBookV1(context.Background(), "", 0) - assert.ErrorIs(t, err, errPairEmpty) + assert.ErrorIs(t, err, currency.ErrCurrencyPairEmpty) resp, err := c.GetProductBookV1(context.Background(), testPair.String(), 2) assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) @@ -944,7 +922,7 @@ func TestGetProductBookV1(t *testing.T) { func TestGetProductCandles(t *testing.T) { t.Parallel() _, err := c.GetProductCandles(context.Background(), "", 0, time.Time{}, time.Time{}) - assert.ErrorIs(t, err, errPairEmpty) + assert.ErrorIs(t, err, currency.ErrCurrencyPairEmpty) resp, err := c.GetProductCandles(context.Background(), testPair.String(), 300, time.Time{}, time.Time{}) assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) @@ -952,18 +930,18 @@ func TestGetProductCandles(t *testing.T) { func TestGetProductStats(t *testing.T) { t.Parallel() - testGetOneArg(t, c.GetProductStats, testPair.String(), errPairEmpty) + testGetOneArg(t, c.GetProductStats, testPair.String(), currency.ErrCurrencyPairEmpty) } func TestGetProductTicker(t *testing.T) { t.Parallel() - testGetOneArg(t, c.GetProductTicker, testPair.String(), errPairEmpty) + testGetOneArg(t, c.GetProductTicker, testPair.String(), currency.ErrCurrencyPairEmpty) } func TestGetProductTrades(t *testing.T) { t.Parallel() _, err := c.GetProductTrades(context.Background(), "", "", "", 0) - assert.ErrorIs(t, err, errPairEmpty) + assert.ErrorIs(t, err, currency.ErrCurrencyPairEmpty) resp, err := c.GetProductTrades(context.Background(), testPair.String(), "1", "before", 0) assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) @@ -1090,16 +1068,6 @@ func TestFetchAccountInfo(t *testing.T) { assert.NotEmpty(t, resp, errExpectedNonEmpty) } -func TestUpdateTickers(t *testing.T) { - t.Parallel() - err := c.UpdateTickers(context.Background(), asset.Options) - assert.ErrorIs(t, err, currency.ErrAssetNotFound) - err = c.UpdateTickers(context.Background(), asset.Spot) - assert.NoError(t, err) - err = c.UpdateTickers(context.Background(), asset.Futures) - assert.NoError(t, err) -} - func TestUpdateTicker(t *testing.T) { t.Parallel() _, err := c.UpdateTicker(context.Background(), currency.Pair{}, asset.Spot) @@ -1129,8 +1097,7 @@ func TestUpdateOrderbook(t *testing.T) { assert.ErrorIs(t, err, currency.ErrCurrencyPairEmpty) _, err = c.UpdateOrderbook(context.Background(), testPair, asset.Empty) assert.ErrorIs(t, err, asset.ErrNotSupported) - _, err = c.UpdateOrderbook(context.Background(), currency.NewPairWithDelimiter("meow", "woof", "-"), - asset.Spot) + _, err = c.UpdateOrderbook(context.Background(), currency.NewPairWithDelimiter("meow", "woof", "-"), asset.Spot) assert.EqualValues(t, errInvalidProductID, err.Error()) resp, err := c.UpdateOrderbook(context.Background(), testPair, asset.Spot) assert.NoError(t, err) @@ -1238,8 +1205,7 @@ func TestCancelBatchOrders(t *testing.T) { func TestGetOrderInfo(t *testing.T) { t.Parallel() sharedtestvalues.SkipTestIfCredentialsUnset(t, c, canManipulateRealOrders) - ordID, err := c.GetAllOrders(context.Background(), testPair.String(), "", "", "", "", - asset.Spot.Upper(), "", "", "", nil, nil, 2, time.Time{}, time.Now()) + ordID, err := c.GetAllOrders(context.Background(), testPair.String(), "", "", "", "", asset.Spot.Upper(), "", "", "", nil, nil, 2, time.Time{}, time.Now()) require.NoError(t, err) if ordID == nil || len(ordID.Orders) == 0 { t.Skip(skipInsufficientOrders) @@ -1252,8 +1218,7 @@ func TestGetOrderInfo(t *testing.T) { func TestGetDepositAddress(t *testing.T) { t.Parallel() sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - _, err := c.GetDepositAddress(context.Background(), currency.NewCode("fake currency that doesn't exist"), "", - "") + _, err := c.GetDepositAddress(context.Background(), currency.NewCode("fake currency that doesn't exist"), "", "") assert.ErrorIs(t, err, errNoWalletForCurrency) resp, err := c.GetDepositAddress(context.Background(), testCrypto, "", "") assert.NoError(t, err) @@ -1351,22 +1316,18 @@ func TestGetOrderHistory(t *testing.T) { func TestGetHistoricCandles(t *testing.T) { t.Parallel() - _, err := c.GetHistoricCandles(context.Background(), currency.Pair{}, asset.Empty, kline.OneYear, time.Time{}, - time.Time{}) + _, err := c.GetHistoricCandles(context.Background(), currency.Pair{}, asset.Empty, kline.OneYear, time.Time{}, time.Time{}) assert.ErrorIs(t, err, currency.ErrCurrencyPairEmpty) - resp, err := c.GetHistoricCandles(context.Background(), testPair, asset.Spot, kline.SixHour, - time.Now().Add(-time.Hour*60), time.Now()) + resp, err := c.GetHistoricCandles(context.Background(), testPair, asset.Spot, kline.SixHour, time.Now().Add(-time.Hour*60), time.Now()) assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) } func TestGetHistoricCandlesExtended(t *testing.T) { t.Parallel() - _, err := c.GetHistoricCandlesExtended(context.Background(), currency.Pair{}, asset.Empty, kline.OneYear, - time.Time{}, time.Time{}) + _, err := c.GetHistoricCandlesExtended(context.Background(), currency.Pair{}, asset.Empty, kline.OneYear, time.Time{}, time.Time{}) assert.ErrorIs(t, err, currency.ErrCurrencyPairEmpty) - resp, err := c.GetHistoricCandlesExtended(context.Background(), testPair, asset.Spot, kline.OneMin, - time.Now().Add(-time.Hour*9), time.Now()) + resp, err := c.GetHistoricCandlesExtended(context.Background(), testPair, asset.Spot, kline.OneMin, time.Now().Add(-time.Hour*9), time.Now()) assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) } @@ -1465,7 +1426,7 @@ func TestFormatExchangeKlineIntervalV3(t *testing.T) { kline.OneDay: granOneDay, kline.OneWeek: errIntervalNotSupported} for k := range testSequence { - resp := formatExchangeKlineIntervalV3(k) + resp := FormatExchangeKlineIntervalV3(k) if resp != testSequence[k] { t.Errorf(errExpectMismatch, resp, testSequence[k]) } @@ -1527,6 +1488,7 @@ func TestCancelPendingFuturesSweep(t *testing.T) { // TestWsAuth dials websocket, sends login request. func TestWsAuth(t *testing.T) { + c.Verbose = true t.Parallel() p := currency.Pairs{testPair} for _, a := range c.GetAssetTypes(true) { @@ -1671,12 +1633,11 @@ func TestWsHandleData(t *testing.T) { func TestProcessSnapshotUpdate(t *testing.T) { t.Parallel() - req := WebsocketOrderbookDataHolder{Changes: []WebsocketOrderbookData{{Side: "fakeside", PriceLevel: 1.1, - NewQuantity: 2.2}}, ProductID: currency.NewBTCUSD()} + req := WebsocketOrderbookDataHolder{Changes: []WebsocketOrderbookData{{Side: "fakeside", PriceLevel: 1.1, NewQuantity: 2.2}}, ProductID: currency.NewBTCUSD()} err := c.ProcessSnapshot(&req, time.Time{}) - assert.ErrorIs(t, err, errUnknownSide) + assert.ErrorIs(t, err, order.ErrSideIsInvalid) err = c.ProcessUpdate(&req, time.Time{}) - assert.ErrorIs(t, err, errUnknownSide) + assert.ErrorIs(t, err, order.ErrSideIsInvalid) req.Changes[0].Side = "offer" err = c.ProcessSnapshot(&req, time.Now()) assert.NoError(t, err) @@ -1705,7 +1666,6 @@ func TestGenerateSubscriptions(t *testing.T) { subs, err := c.generateSubscriptions() require.NoError(t, err) testsubs.EqualLists(t, exp, subs) - _, err = subscription.List{{Channel: "wibble"}}.ExpandTemplates(c) assert.ErrorContains(t, err, "subscription channel not supported: wibble") } @@ -1713,8 +1673,7 @@ func TestGenerateSubscriptions(t *testing.T) { func TestSubscribeUnsubscribe(t *testing.T) { t.Parallel() sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - req := subscription.List{{Channel: "heartbeat", Asset: asset.Spot, - Pairs: currency.Pairs{currency.NewPairWithDelimiter(testCrypto.String(), testFiat.String(), "-")}}} + req := subscription.List{{Channel: "heartbeat", Asset: asset.Spot, Pairs: currency.Pairs{currency.NewPairWithDelimiter(testCrypto.String(), testFiat.String(), "-")}}} err := c.Subscribe(req) assert.NoError(t, err) err = c.Unsubscribe(req) @@ -1809,7 +1768,7 @@ func portfolioIDFromName(t *testing.T, targetName string) string { } } } else { - targetID = createResp.Portfolio.UUID + targetID = createResp.UUID } return targetID } @@ -1873,10 +1832,10 @@ func transferTestHelper(t *testing.T, wallets *GetAllWalletsResponse) (srcWallet } pmID, err := c.GetAllPaymentMethods(context.Background()) assert.NoError(t, err) - if pmID == nil || len(pmID.PaymentMethods) == 0 { + if pmID == nil || len(pmID) == 0 { t.Skip(skipPayMethodNotFound) } - return srcWalletID, pmID.PaymentMethods[0].ID + return srcWalletID, pmID[0].ID } type withdrawFiatFunc func(context.Context, *withdraw.Request) (*withdraw.ExchangeResponse, error) @@ -1927,8 +1886,7 @@ func withdrawFiatFundsHelper(t *testing.T, fn withdrawFiatFunc) { } type getNoArgsResp interface { - *ServerTimeV3 | *GetAllPaymentMethodsResp | *UserResponse | []FiatData | []CryptoData | *ServerTimeV2 | - []CurrencyData | []PairVolumeData | *AllWrappedAssets + *ServerTimeV3 | []PaymentMethodData | *UserResponse | []FiatData | []CryptoData | *ServerTimeV2 | []CurrencyData | []PairVolumeData | *AllWrappedAssets } type getNoArgsAssertNotEmpty[G getNoArgsResp] func(context.Context) (G, error) @@ -1954,7 +1912,7 @@ func convertTestShared(t *testing.T, f genConvertTestFunc) { resp, err := c.CreateConvertQuote(context.Background(), fromAccID, toAccID, "", "", 0.01) assert.NoError(t, err) require.NotNil(t, resp) - resp, err = f(context.Background(), resp.Trade.ID, fromAccID, toAccID) + resp, err = f(context.Background(), resp.ID, fromAccID, toAccID) assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) } diff --git a/exchanges/coinbasepro/coinbasepro_types.go b/exchanges/coinbasepro/coinbasepro_types.go index f0f66aa227d..821bd2b980f 100644 --- a/exchanges/coinbasepro/coinbasepro_types.go +++ b/exchanges/coinbasepro/coinbasepro_types.go @@ -35,7 +35,7 @@ type ValCur struct { } // Account holds details for a trading account, returned by GetAccountByID and used as -// a sub-struct in the types AllAccountsResponse and OneAccountResponse +// a sub-struct in the type AllAccountsResponse type Account struct { UUID string `json:"uuid"` Name string `json:"name"` @@ -65,105 +65,97 @@ type Params struct { url.Values } -// OneAccountResponse is a temporary struct used for unmarshalling in GetAccountByID -type OneAccountResponse struct { - Account Account `json:"account"` -} - -// PriSiz is a sub-struct used in the type ProductBook -type PriSiz struct { +// PriceSize is a sub-struct used in the type ProductBook +type PriceSize struct { Price float64 `json:"price,string"` Size float64 `json:"size,string"` } // ProductBook holds bid and ask prices for a particular product, returned by GetProductBookV3 -// and used as a sub-struct in the types BestBidAsk and ProductBookResponse type ProductBook struct { - ProductID string `json:"product_id"` - Bids []PriSiz `json:"bids"` - Asks []PriSiz `json:"asks"` - Time time.Time `json:"time"` -} - -// 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"` + ProductID currency.Pair `json:"product_id"` + Bids []PriceSize `json:"bids"` + Asks []PriceSize `json:"asks"` + Time time.Time `json:"time"` } -// ProductBookResponse is a temporary struct used for unmarshalling in GetProductBookV3 -type ProductBookResponse struct { - Pricebook ProductBook `json:"pricebook"` +// FCMTradingSessionDetails is a sub-struct used in the type Product +type FCMTradingSessionDetails struct { + IsSessionOpen bool `json:"is_session_open"` + OpenTime time.Time `json:"open_time"` + CloseTime time.Time `json:"close_time"` + SessionState string `json:"session_state"` + AfterHoursOrderEntryDisabled bool `json:"after_hours_order_entry_disabled"` +} + +// PerpetualDetails is a sub-struct used in the type FutureProductDetails +type PerpetualDetails struct { + OpenInterest types.Number `json:"open_interest"` + FundingRate types.Number `json:"funding_rate"` + FundingTime time.Time `json:"funding_time"` + MaxLeverage types.Number `json:"max_leverage"` + BaseAssetUUID uuid.UUID `json:"base_asset_uuid"` +} + +// FutureProductDetails is a sub-struct used in the type Product +type FutureProductDetails struct { + Venue string `json:"venue"` + ContractCode string `json:"contract_code"` + ContractExpiry time.Time `json:"contract_expiry"` + ContractSize types.Number `json:"contract_size"` + ContractRootUnit string `json:"contract_root_unit"` + GroupDescription string `json:"group_description"` + ContractExpiryTimezone string `json:"contract_expiry_timezone"` + GroupShortDescription string `json:"group_short_description"` + RiskManagedBy string `json:"risk_managed_by"` + ContractExpiryType string `json:"contract_expiry_type"` + PerpetualDetails PerpetualDetails `json:"perpetual_details"` + ContractDisplayName string `json:"contract_display_name"` + TimeToExpiryMS uint64 `json:"time_to_expiry_ms,string"` + NonCrypto bool `json:"non_crypto"` + ContractExpiryName string `json:"contract_expiry_name"` } // Product holds product information, returned by GetProductByID, and used as a sub-struct // in the type AllProducts type Product struct { - ID string `json:"product_id"` - Price types.Number `json:"price"` - PricePercentageChange24H types.Number `json:"price_percentage_change_24h"` - Volume24H types.Number `json:"volume_24h"` - VolumePercentageChange24H types.Number `json:"volume_percentage_change_24h"` - BaseIncrement types.Number `json:"base_increment"` - QuoteIncrement types.Number `json:"quote_increment"` - QuoteMinSize types.Number `json:"quote_min_size"` - QuoteMaxSize types.Number `json:"quote_max_size"` - BaseMinSize types.Number `json:"base_min_size"` - BaseMaxSize types.Number `json:"base_max_size"` - BaseName string `json:"base_name"` - QuoteName string `json:"quote_name"` - Watched bool `json:"watched"` - IsDisabled bool `json:"is_disabled"` - New bool `json:"new"` - Status string `json:"status"` - CancelOnly bool `json:"cancel_only"` - LimitOnly bool `json:"limit_only"` - PostOnly bool `json:"post_only"` - TradingDisabled bool `json:"trading_disabled"` - AuctionMode bool `json:"auction_mode"` - ProductType string `json:"product_type"` - QuoteCurrencyID string `json:"quote_currency_id"` - BaseCurrencyID string `json:"base_currency_id"` - FCMTradingSessionDetails struct { - IsSessionOpen bool `json:"is_session_open"` - OpenTime time.Time `json:"open_time"` - CloseTime time.Time `json:"close_time"` - SessionState string `json:"session_state"` - AfterHoursOrderEntryDisabled bool `json:"after_hours_order_entry_disabled"` - } `json:"fcm_trading_session_details"` - MidMarketPrice types.Number `json:"mid_market_price"` - Alias string `json:"alias"` - AliasTo []string `json:"alias_to"` - BaseDisplaySymbol string `json:"base_display_symbol"` - QuoteDisplaySymbol string `json:"quote_display_symbol"` - ViewOnly bool `json:"view_only"` - PriceIncrement types.Number `json:"price_increment"` - DisplayName string `json:"display_name"` - ProductVenue string `json:"product_venue"` - ApproximateQuote24HVolume types.Number `json:"approximate_quote_24h_volume"` - FutureProductDetails struct { - Venue string `json:"venue"` - ContractCode string `json:"contract_code"` - ContractExpiry time.Time `json:"contract_expiry"` - ContractSize types.Number `json:"contract_size"` - ContractRootUnit string `json:"contract_root_unit"` - GroupDescription string `json:"group_description"` - ContractExpiryTimezone string `json:"contract_expiry_timezone"` - GroupShortDescription string `json:"group_short_description"` - RiskManagedBy string `json:"risk_managed_by"` - ContractExpiryType string `json:"contract_expiry_type"` - PerpetualDetails struct { - OpenInterest types.Number `json:"open_interest"` - FundingRate types.Number `json:"funding_rate"` - FundingTime time.Time `json:"funding_time"` - MaxLeverage types.Number `json:"max_leverage"` - BaseAssetUUID uuid.UUID `json:"base_asset_uuid"` - } `json:"perpetual_details"` - ContractDisplayName string `json:"contract_display_name"` - TimeToExpiryMS uint64 `json:"time_to_expiry_ms,string"` - NonCrypto bool `json:"non_crypto"` - ContractExpiryName string `json:"contract_expiry_name"` - } `json:"future_product_details"` + ID currency.Pair `json:"product_id"` + Price types.Number `json:"price"` + PricePercentageChange24H types.Number `json:"price_percentage_change_24h"` + Volume24H types.Number `json:"volume_24h"` + VolumePercentageChange24H types.Number `json:"volume_percentage_change_24h"` + BaseIncrement types.Number `json:"base_increment"` + QuoteIncrement types.Number `json:"quote_increment"` + QuoteMinSize types.Number `json:"quote_min_size"` + QuoteMaxSize types.Number `json:"quote_max_size"` + BaseMinSize types.Number `json:"base_min_size"` + BaseMaxSize types.Number `json:"base_max_size"` + BaseName string `json:"base_name"` + QuoteName string `json:"quote_name"` + Watched bool `json:"watched"` + IsDisabled bool `json:"is_disabled"` + New bool `json:"new"` + Status string `json:"status"` + CancelOnly bool `json:"cancel_only"` + LimitOnly bool `json:"limit_only"` + PostOnly bool `json:"post_only"` + TradingDisabled bool `json:"trading_disabled"` + AuctionMode bool `json:"auction_mode"` + ProductType string `json:"product_type"` + QuoteCurrencyID currency.Code `json:"quote_currency_id"` + BaseCurrencyID currency.Code `json:"base_currency_id"` + FCMTradingSessionDetails FCMTradingSessionDetails `json:"fcm_trading_session_details"` + MidMarketPrice types.Number `json:"mid_market_price"` + Alias string `json:"alias"` + AliasTo []string `json:"alias_to"` + BaseDisplaySymbol string `json:"base_display_symbol"` + QuoteDisplaySymbol string `json:"quote_display_symbol"` + ViewOnly bool `json:"view_only"` + PriceIncrement types.Number `json:"price_increment"` + DisplayName string `json:"display_name"` + ProductVenue string `json:"product_venue"` + ApproximateQuote24HVolume types.Number `json:"approximate_quote_24h_volume"` + FutureProductDetails FutureProductDetails `json:"future_product_details"` } // AllProducts holds information on a lot of available currency pairs, returned by @@ -177,8 +169,7 @@ type AllProducts struct { // the exchange, used in the types History and WebsocketCandle type UnixTimestamp time.Time -// CandleStruct holds historic trade information, used as a sub-struct in History, -// and returned by GetHistoricRates +// CandleStruct holds historic trade information, returned by GetHistoricRates type CandleStruct struct { Start UnixTimestamp `json:"start"` Low float64 `json:"low,string"` @@ -188,23 +179,21 @@ type CandleStruct struct { Volume float64 `json:"volume,string"` } -// History holds historic rate information, used for unmarshalling in GetHistoricRates -type History struct { - Candles []CandleStruct `json:"candles"` +// Trades is a sub-struct used in the type Ticker +type Trades struct { + TradeID string `json:"trade_id"` + ProductID currency.Pair `json:"product_id"` + Price float64 `json:"price,string"` + Size float64 `json:"size,string"` + Time time.Time `json:"time"` + Side string `json:"side"` + Bid types.Number `json:"bid"` + Ask types.Number `json:"ask"` } // Ticker holds basic ticker information, returned by GetTicker type Ticker struct { - Trades []struct { - TradeID string `json:"trade_id"` - ProductID string `json:"product_id"` - Price float64 `json:"price,string"` - Size float64 `json:"size,string"` - Time time.Time `json:"time"` - Side string `json:"side"` - Bid types.Number `json:"bid"` - Ask types.Number `json:"ask"` - } `json:"trades"` + Trades []Trades `json:"trades"` BestBid types.Number `json:"best_bid"` BestAsk types.Number `json:"best_ask"` } @@ -257,34 +246,30 @@ type OrderConfiguration struct { StopLimitStopLimitGTD *StopLimitStopLimitGTD `json:"stop_limit_stop_limit_gtd,omitempty"` } +// SuccessResponse is a sub-struct used in the type PlaceOrderResp +type SuccessResponse struct { + OrderID string `json:"order_id"` + ProductID currency.Pair `json:"product_id"` + Side string `json:"side"` + ClientOrderID string `json:"client_oid"` +} + // PlaceOrderResp contains information on an order, returned by PlaceOrder type PlaceOrderResp struct { - Success bool `json:"success"` - FailureReason string `json:"failure_reason"` - OrderID string `json:"order_id"` - SuccessResponse struct { - OrderID string `json:"order_id"` - ProductID string `json:"product_id"` - Side string `json:"side"` - ClientOrderID string `json:"client_oid"` - } `json:"success_response"` + Success bool `json:"success"` + FailureReason string `json:"failure_reason"` + OrderID string `json:"order_id"` + SuccessResponse SuccessResponse `json:"success_response"` OrderConfiguration OrderConfiguration `json:"order_configuration"` } -// OrderCancelDetail contains information on attempted order cancellations, used as a -// sub-struct by CancelOrdersResp, and returned by CancelOrders +// OrderCancelDetail contains information on attempted order cancellations, 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 []OrderCancelDetail `json:"results"` -} - // EditOrderPreviewResp contains information on the effects of editing an order, // returned by EditOrderPreview type EditOrderPreviewResp struct { @@ -298,16 +283,18 @@ type EditOrderPreviewResp struct { AverageFilledPrice float64 `json:"average_filled_price,string"` } -// SingleOrder provides information on an order in a format that the exchange provides for the GetOrderByID function -type SingleOrder struct { - Order GetOrderResponse `json:"order"` +// EditHistory is a sub-struct used in the type GetOrderResponse +type EditHistory struct { + Price float64 `json:"price,string"` + Size float64 `json:"size,string"` + ReplaceAcceptTimestamp time.Time `json:"replace_accept_timestamp"` } // GetOrderResponse contains information on an order, returned by GetOrderByID IterativeGetAllOrders, and used in // GetAllOrdersResp type GetOrderResponse struct { OrderID string `json:"order_id"` - ProductID string `json:"product_id"` + ProductID currency.Pair `json:"product_id"` UserID string `json:"user_id"` OrderConfiguration OrderConfiguration `json:"order_configuration"` Side string `json:"side"` @@ -337,35 +324,34 @@ type GetOrderResponse struct { OutstandingHoldAmount float64 `json:"outstanding_hold_amount,string"` IsLiquidation bool `json:"is_liquidation"` LastFillTime time.Time `json:"last_fill_time"` - EditHistory []struct { - Price float64 `json:"price,string"` - Size float64 `json:"size,string"` - ReplaceAcceptTimestamp time.Time `json:"replace_accept_timestamp"` - } `json:"edit_history"` - Leverage types.Number `json:"leverage"` - MarginType string `json:"margin_type"` - RetailPortfolioID string `json:"retail_portfolio_id"` + EditHistory []EditHistory `json:"edit_history"` + Leverage types.Number `json:"leverage"` + MarginType string `json:"margin_type"` + RetailPortfolioID string `json:"retail_portfolio_id"` +} + +// Fills is a sub-struct used in the type FillResponse +type Fills struct { + EntryID string `json:"entry_id"` + TradeID string `json:"trade_id"` + OrderID string `json:"order_id"` + TradeTime time.Time `json:"trade_time"` + TradeType string `json:"trade_type"` + Price float64 `json:"price,string"` + Size float64 `json:"size,string"` + Commission float64 `json:"commission,string"` + ProductID currency.Pair `json:"product_id"` + SequenceTimestamp time.Time `json:"sequence_timestamp"` + LiquidityIndicator string `json:"liquidity_indicator"` + SizeInQuote bool `json:"size_in_quote"` + UserID string `json:"user_id"` + Side string `json:"side"` } // FillResponse contains fill information, returned by GetFills type FillResponse struct { - Fills []struct { - EntryID string `json:"entry_id"` - TradeID string `json:"trade_id"` - OrderID string `json:"order_id"` - TradeTime time.Time `json:"trade_time"` - TradeType string `json:"trade_type"` - Price float64 `json:"price,string"` - Size float64 `json:"size,string"` - Commission float64 `json:"commission,string"` - ProductID string `json:"product_id"` - SequenceTimestamp time.Time `json:"sequence_timestamp"` - LiquidityIndicator string `json:"liquidity_indicator"` - SizeInQuote bool `json:"size_in_quote"` - UserID string `json:"user_id"` - Side string `json:"side"` - } `json:"fills"` - Cursor string `json:"cursor"` + Fills []Fills `json:"fills"` + Cursor string `json:"cursor"` } // PreviewOrderResp contains information on the effects of placing an order, returned by @@ -387,8 +373,7 @@ type PreviewOrderResp struct { Slippage float64 `json:"slippage,string"` } -// SimplePortfolioData is a sub-struct used in the types AllPortfolioResponse, -// SimplePortfolioResponse, and DetailedPortfolioResponse +// SimplePortfolioData is a sub-struct used in the type DetailedPortfolioResponse type SimplePortfolioData struct { Name string `json:"name"` UUID string `json:"uuid"` @@ -396,18 +381,6 @@ type SimplePortfolioData struct { Deleted bool `json:"deleted"` } -// AllPortfolioResponse contains a brief overview of the user's portfolios, used in unmarshalling -// for GetAllPortfolios -type AllPortfolioResponse struct { - Portfolios []SimplePortfolioData `json:"portfolios"` -} - -// SimplePortfolioResponse contains a small amount of information on a single portfolio. -// Returned by CreatePortfolio and EditPortfolio -type SimplePortfolioResponse struct { - Portfolio SimplePortfolioData `json:"portfolio"` -} - // MovePortfolioFundsResponse contains the UUIDs of the portfolios involved. Returned by // MovePortfolioFunds type MovePortfolioFundsResponse struct { @@ -427,119 +400,114 @@ type NativeAndRaw struct { RawCurrency ValCur `json:"rawCurrency"` } +// PortfolioBalances is a sub-struct used in the type DetailedPortfolioResponse +type PortfolioBalances struct { + TotalBalance ValCur `json:"total_balance"` + TotalFuturesBalance ValCur `json:"total_futures_balance"` + TotalCashEquivalentBalance ValCur `json:"total_cash_equivalent_balance"` + TotalCryptoBalance ValCur `json:"total_crypto_balance"` + FuturesUnrealizedPNL ValCur `json:"futures_unrealized_pnl"` + PerpUnrealizedPNL ValCur `json:"perp_unrealized_pnl"` +} + +// SpotPositions is a sub-struct used in the type DetailedPortfolioResponse +type SpotPositions struct { + Asset string `json:"asset"` + AccountUUID string `json:"account_uuid"` + TotalBalanceFiat float64 `json:"total_balance_fiat"` + TotalBalanceCrypto float64 `json:"total_balance_crypto"` + AvailableToTreadeFiat float64 `json:"available_to_trade_fiat"` + Allocation float64 `json:"allocation"` + OneDayChange float64 `json:"one_day_change"` + CostBasis ValCur `json:"cost_basis"` + AssetImgURL string `json:"asset_img_url"` + IsCash bool `json:"is_cash"` +} + +// PerpPositions is a sub-struct used in the type DetailedPortfolioResponse +type PerpPositions struct { + ProductID currency.Pair `json:"product_id"` + ProductUUID string `json:"product_uuid"` + Symbol string `json:"symbol"` + AssetImageURL string `json:"asset_image_url"` + VWAP NativeAndRaw `json:"vwap"` + PositionSide string `json:"position_side"` + NetSize float64 `json:"net_size,string"` + BuyOrderSize float64 `json:"buy_order_size,string"` + SellOrderSize float64 `json:"sell_order_size,string"` + IMContribution float64 `json:"im_contribution,string"` + UnrealizedPNL NativeAndRaw `json:"unrealized_pnl"` + MarkPrice NativeAndRaw `json:"mark_price"` + LiquidationPrice NativeAndRaw `json:"liquidation_price"` + Leverage float64 `json:"leverage,string"` + IMNotional NativeAndRaw `json:"im_notional"` + MMNotional NativeAndRaw `json:"mm_notional"` + PositionNotional NativeAndRaw `json:"position_notional"` + MarginType string `json:"margin_type"` + LiquidationBuffer float64 `json:"liquidation_buffer,string"` + LiquidationPercentage float64 `json:"liquidation_percentage,string"` +} + +// FuturesPositions is a sub-struct used in the type DetailedPortfolioResponse +type FuturesPositions []struct { + ProductID currency.Pair `json:"product_id"` + ContractSize float64 `json:"contract_size,string"` + Side string `json:"side"` + Amount float64 `json:"amount,string"` + AvgEntryPrice float64 `json:"avg_entry_price,string"` + CurrentPrice float64 `json:"current_price,string"` + UnrealizedPNL float64 `json:"unrealized_pnl,string"` + Expiry time.Time `json:"expiry"` + UnderlyingAsset string `json:"underlying_asset"` + AssetImgURL string `json:"asset_img_url"` + ProductName string `json:"product_name"` + Venue string `json:"venue"` + NotionalValue float64 `json:"notional_value,string"` +} + // DetailedPortfolioResponse contains a great deal of information on a single portfolio. // Returned by GetPortfolioByID type DetailedPortfolioResponse struct { - Breakdown struct { - Portfolio SimplePortfolioData `json:"portfolio"` - PortfolioBalances struct { - TotalBalance ValCur `json:"total_balance"` - TotalFuturesBalance ValCur `json:"total_futures_balance"` - TotalCashEquivalentBalance ValCur `json:"total_cash_equivalent_balance"` - TotalCryptoBalance ValCur `json:"total_crypto_balance"` - FuturesUnrealizedPNL ValCur `json:"futures_unrealized_pnl"` - PerpUnrealizedPNL ValCur `json:"perp_unrealized_pnl"` - } `json:"portfolio_balances"` - SpotPositions []struct { - Asset string `json:"asset"` - AccountUUID string `json:"account_uuid"` - TotalBalanceFiat float64 `json:"total_balance_fiat"` - TotalBalanceCrypto float64 `json:"total_balance_crypto"` - AvailableToTreadeFiat float64 `json:"available_to_trade_fiat"` - Allocation float64 `json:"allocation"` - OneDayChange float64 `json:"one_day_change"` - CostBasis ValCur `json:"cost_basis"` - AssetImgURL string `json:"asset_img_url"` - IsCash bool `json:"is_cash"` - } `json:"spot_positions"` - PerpPositions []struct { - ProductID string `json:"product_id"` - ProductUUID string `json:"product_uuid"` - Symbol string `json:"symbol"` - AssetImageURL string `json:"asset_image_url"` - VWAP NativeAndRaw `json:"vwap"` - PositionSide string `json:"position_side"` - NetSize float64 `json:"net_size,string"` - BuyOrderSize float64 `json:"buy_order_size,string"` - SellOrderSize float64 `json:"sell_order_size,string"` - IMContribution float64 `json:"im_contribution,string"` - UnrealizedPNL NativeAndRaw `json:"unrealized_pnl"` - MarkPrice NativeAndRaw `json:"mark_price"` - LiquidationPrice NativeAndRaw `json:"liquidation_price"` - Leverage float64 `json:"leverage,string"` - IMNotional NativeAndRaw `json:"im_notional"` - MMNotional NativeAndRaw `json:"mm_notional"` - PositionNotional NativeAndRaw `json:"position_notional"` - MarginType string `json:"margin_type"` - LiquidationBuffer float64 `json:"liquidation_buffer,string"` - LiquidationPercentage float64 `json:"liquidation_percentage,string"` - } `json:"perp_positions"` - FuturesPositions []struct { - ProductID string `json:"product_id"` - ContractSize float64 `json:"contract_size,string"` - Side string `json:"side"` - Amount float64 `json:"amount,string"` - AvgEntryPrice float64 `json:"avg_entry_price,string"` - CurrentPrice float64 `json:"current_price,string"` - UnrealizedPNL float64 `json:"unrealized_pnl,string"` - Expiry time.Time `json:"expiry"` - UnderlyingAsset string `json:"underlying_asset"` - AssetImgURL string `json:"asset_img_url"` - ProductName string `json:"product_name"` - Venue string `json:"venue"` - NotionalValue float64 `json:"notional_value,string"` - } `json:"futures_positions"` - } `json:"breakdown"` + Portfolio SimplePortfolioData `json:"portfolio"` + PortfolioBalances PortfolioBalances `json:"portfolio_balances"` + SpotPositions []SpotPositions `json:"spot_positions"` + PerpPositions []PerpPositions `json:"perp_positions"` + FuturesPositions []FuturesPositions `json:"futures_positions"` } // FuturesBalanceSummary contains information on futures balances, returned by // GetFuturesBalanceSummary type FuturesBalanceSummary struct { - BalanceSummary struct { - FuturesBuyingPower ValCur `json:"futures_buying_power"` - TotalUSDBalance ValCur `json:"total_usd_balance"` - CBIUSDBalance ValCur `json:"cbi_usd_balance"` - CFMUSDBalance ValCur `json:"cfm_usd_balance"` - TotalOpenOrdersHoldAmount ValCur `json:"total_open_orders_hold_amount"` - UnrealizedPNL ValCur `json:"unrealized_pnl"` - DailyRealizedPNL ValCur `json:"daily_realized_pnl"` - InitialMargin ValCur `json:"initial_margin"` - AvailableMargin ValCur `json:"available_margin"` - LiquidationThreshold ValCur `json:"liquidation_threshold"` - LiquidationBufferAmount ValCur `json:"liquidation_buffer_amount"` - LiquidationBufferPercentage float64 `json:"liquidation_buffer_percentage,string"` - } `json:"balance_summary"` + FuturesBuyingPower ValCur `json:"futures_buying_power"` + TotalUSDBalance ValCur `json:"total_usd_balance"` + CBIUSDBalance ValCur `json:"cbi_usd_balance"` + CFMUSDBalance ValCur `json:"cfm_usd_balance"` + TotalOpenOrdersHoldAmount ValCur `json:"total_open_orders_hold_amount"` + UnrealizedPNL ValCur `json:"unrealized_pnl"` + DailyRealizedPNL ValCur `json:"daily_realized_pnl"` + InitialMargin ValCur `json:"initial_margin"` + AvailableMargin ValCur `json:"available_margin"` + LiquidationThreshold ValCur `json:"liquidation_threshold"` + LiquidationBufferAmount ValCur `json:"liquidation_buffer_amount"` + LiquidationBufferPercentage float64 `json:"liquidation_buffer_percentage,string"` } // FuturesPosition contains information on a single futures position, returned by -// GetFuturesPositionByID and used as a sub-struct in the type AllFuturesPositions +// GetFuturesPositionByID type FuturesPosition struct { // This may belong in a struct of its own called "position", requiring a bit // more abstraction, but for the moment I'll assume it doesn't - ProductID string `json:"product_id"` - ExpirationTime time.Time `json:"expiration_time"` - Side string `json:"side"` - NumberOfContracts float64 `json:"number_of_contracts,string"` - CurrentPrice float64 `json:"current_price,string"` - AverageEntryPrice float64 `json:"avg_entry_price,string"` - UnrealizedPNL float64 `json:"unrealized_pnl,string"` - DailyRealizedPNL float64 `json:"daily_realized_pnl,string"` + ProductID currency.Pair `json:"product_id"` + ExpirationTime time.Time `json:"expiration_time"` + Side string `json:"side"` + NumberOfContracts float64 `json:"number_of_contracts,string"` + CurrentPrice float64 `json:"current_price,string"` + AverageEntryPrice float64 `json:"avg_entry_price,string"` + UnrealizedPNL float64 `json:"unrealized_pnl,string"` + DailyRealizedPNL float64 `json:"daily_realized_pnl,string"` } -// 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. 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 +// SweepData contains information on pending and processing sweep requests, returned by ListFuturesSweeps type SweepData struct { ID string `json:"id"` RequestedAmount ValCur `json:"requested_amount"` @@ -548,14 +516,8 @@ type SweepData struct { ScheduledTime time.Time `json:"scheduled_time"` } -// ListFuturesSweepsResponse contains information on pending and processing sweep -// requests. Used in unmarshalling by ListFuturesSweeps -type ListFuturesSweepsResponse struct { - Sweeps []SweepData `json:"sweeps"` -} - // PerpetualsPortfolioSummary contains information on perpetuals portfolio balances, used as -// a sub-struct in the types PerpetualPortResponse, PerpPositionDetail, AllPerpPosResponse, and +// a sub-struct in the types PerpPositionDetail, AllPerpPosResponse, and // OnePerpPosResponse type PerpetualsPortfolioSummary struct { PortfolioUUID string `json:"portfolio_uuid"` @@ -581,16 +543,10 @@ type PerpetualsPortfolioSummary struct { MaxWithDrawal ValCur `json:"max_withdrawal"` } -// PerpetualPortResponse contains information on perpetuals portfolio balances, returned by -// GetPerpetualsPortfolioSummary -type PerpetualPortResponse struct { - Summary PerpetualsPortfolioSummary `json:"summary"` -} - // PerpPositionDetail contains information on a single perpetuals position, used as a sub-struct // in the types AllPerpPosResponse and OnePerpPosResponse type PerpPositionDetail struct { - ProductID string `json:"product_id"` + ProductID currency.Pair `json:"product_id"` ProductUUID string `json:"product_uuid"` Symbol string `json:"symbol"` VWAP ValCur `json:"vwap"` @@ -626,33 +582,42 @@ type OnePerpPosResponse struct { PortfolioSummary PerpetualsPortfolioSummary `json:"portfolio_summary"` } +// FeeTier is a sub-struct used in the type TransactionSummary +type FeeTier struct { + PricingTier string `json:"pricing_tier"` + USDFrom float64 `json:"usd_from,string"` + USDTo float64 `json:"usd_to,string"` + TakerFeeRate float64 `json:"taker_fee_rate,string"` + MakerFeeRate float64 `json:"maker_fee_rate,string"` + AOPFrom types.Number `json:"aop_from"` + AOPTo types.Number `json:"aop_to"` +} + +// MarginRate is a sub-struct used in the type TransactionSummary +type MarginRate struct { + Value float64 `json:"value,string"` +} + +// GoodsAndServicesTax is a sub-struct used in the type TransactionSummary +type GoodsAndServicesTax struct { + Rate float64 `json:"rate,string"` + Type string `json:"type"` +} + // TransactionSummary contains a summary of transaction fees, volume, and the like. Returned // by GetTransactionSummary type TransactionSummary struct { - TotalVolume float64 `json:"total_volume"` - TotalFees float64 `json:"total_fees"` - FeeTier struct { - PricingTier string `json:"pricing_tier"` - USDFrom float64 `json:"usd_from,string"` - USDTo float64 `json:"usd_to,string"` - TakerFeeRate float64 `json:"taker_fee_rate,string"` - MakerFeeRate float64 `json:"maker_fee_rate,string"` - AOPFrom types.Number `json:"aop_from"` - AOPTo types.Number `json:"aop_to"` - } `json:"fee_tier"` - MarginRate struct { - Value float64 `json:"value,string"` - } `json:"margin_rate"` - GoodsAndServicesTax struct { - Rate float64 `json:"rate,string"` - Type string `json:"type"` - } `json:"goods_and_services_tax"` - AdvancedTradeOnlyVolume float64 `json:"advanced_trade_only_volume"` - AdvancedTradeOnlyFees float64 `json:"advanced_trade_only_fees"` - CoinbaseProVolume float64 `json:"coinbase_pro_volume"` - CoinbaseProFees float64 `json:"coinbase_pro_fees"` - TotalBalance types.Number `json:"total_balance"` - HasPromoFee bool `json:"has_promo_fee"` + TotalVolume float64 `json:"total_volume"` + TotalFees float64 `json:"total_fees"` + FeeTier FeeTier `json:"fee_tier"` + MarginRate MarginRate `json:"margin_rate"` + GoodsAndServicesTax GoodsAndServicesTax `json:"goods_and_services_tax"` + AdvancedTradeOnlyVolume float64 `json:"advanced_trade_only_volume"` + AdvancedTradeOnlyFees float64 `json:"advanced_trade_only_fees"` + CoinbaseProVolume float64 `json:"coinbase_pro_volume"` + CoinbaseProFees float64 `json:"coinbase_pro_fees"` + TotalBalance types.Number `json:"total_balance"` + HasPromoFee bool `json:"has_promo_fee"` } // GetAllOrdersResp contains information on a lot of orders, returned by GetAllOrders @@ -663,40 +628,49 @@ type GetAllOrdersResp struct { Cursor string `json:"cursor"` } -// LinkStruct is a sub-struct storing information on links, used in FeeStruct and +// LinkStruct is a sub-struct storing information on links, used in Disclosure and // ConvertResponse type LinkStruct struct { Text string `json:"text"` URL string `json:"url"` } +// Disclosure is a sub-struct used in FeeStruct +type Disclosure struct { + Title string `json:"title"` + Description string `json:"description"` + Link LinkStruct `json:"link"` +} + // FeeStruct is a sub-struct storing information on fees, used in ConvertResponse type FeeStruct struct { - Title string `json:"title"` - Description string `json:"description"` - Amount ValCur `json:"amount"` - Label string `json:"label"` - Disclosure struct { - Title string `json:"title"` - Description string `json:"description"` - Link LinkStruct `json:"link"` - } `json:"disclosure"` + Title string `json:"title"` + Description string `json:"description"` + Amount ValCur `json:"amount"` + Label string `json:"label"` + Disclosure Disclosure `json:"disclosure"` +} + +// Owner is a sub-struct, used in LedgerAccount +type Owner struct { + ID string `json:"id"` + UUID string `json:"uuid"` + UserUUID string `json:"user_uuid"` + Type string `json:"type"` +} + +// LedgerAccount is a sub-struct, used in AccountStruct +type LedgerAccount struct { + AccountID string `json:"account_id"` + Currency string `json:"currency"` + Owner Owner `json:"owner"` } // AccountStruct is a sub-struct storing information on accounts, used in ConvertResponse type AccountStruct struct { - Type string `json:"type"` - Network string `json:"network"` - LedgerAccount struct { - AccountID string `json:"account_id"` - Currency string `json:"currency"` - Owner struct { - ID string `json:"id"` - UUID string `json:"uuid"` - UserUUID string `json:"user_uuid"` - Type string `json:"type"` - } `json:"owner"` - } `json:"ledger_account"` + Type string `json:"type"` + Network string `json:"network"` + LedgerAccount LedgerAccount `json:"ledger_account"` } // AmScale is a sub-struct storing information on amounts and scales, used in ConvertResponse @@ -705,74 +679,93 @@ type AmScale struct { Scale int32 `json:"scale"` } +// UnitPrice is a sub-struct used in ConvertResponse +type UnitPrice struct { + TargetToFiat AmScale `json:"target_to_fiat"` + TargetToSource AmScale `json:"target_to_source"` + SourceToFiat AmScale `json:"source_to_fiat"` +} + +// Context is a sub-struct used in UserWarnings +type Context struct { + Details []string `json:"details"` + Title string `json:"title"` + LinkText string `json:"link_text"` +} + +// UserWarnings is a sub-struct used in ConvertResponse +type UserWarnings struct { + ID string `json:"id"` + Link LinkStruct `json:"link"` + Context Context `json:"context"` + Code string `json:"code"` + Message string `json:"message"` +} + +// CancellationReason is a sub-struct used in ConvertResponse +type CancellationReason struct { + Message string `json:"message"` + Code string `json:"code"` + ErrorCode string `json:"error_code"` + ErrorCTA string `json:"error_cta"` +} + +// TaxDetails is a sub-struct used in ConvertResponse +type TaxDetails struct { + Name string `json:"name"` + Amount ValCur `json:"amount"` +} + +// TradeIncentiveInfo is a sub-struct used in ConvertResponse +type TradeIncentiveInfo struct { + AppliedIncentive bool `json:"applied_incentive"` + UserIncentiveID string `json:"user_incentive_id"` + CodeVal string `json:"code_val"` + EndsAt time.Time `json:"ends_at"` + FeeWithoutIncentive ValCur `json:"fee_without_incentive"` + Redeemed bool `json:"redeemed"` +} + // ConvertResponse contains information on a convert trade, returned by CreateConvertQuote, // CommitConvertTrade, and GetConvertTradeByID type ConvertResponse struct { - Trade struct { - ID string `json:"id"` - Status string `json:"status"` - UserEnteredAmount ValCur `json:"user_entered_amount"` - Amount ValCur `json:"amount"` - Subtotal ValCur `json:"subtotal"` - Total ValCur `json:"total"` - Fees []FeeStruct `json:"fees"` - TotalFee FeeStruct `json:"total_fee"` - Source AccountStruct `json:"source"` - Target AccountStruct `json:"target"` - UnitPrice struct { - TargetToFiat AmScale `json:"target_to_fiat"` - TargetToSource AmScale `json:"target_to_source"` - SourceToFiat AmScale `json:"source_to_fiat"` - } `json:"unit_price"` - UserWarnings []struct { - ID string `json:"id"` - Link LinkStruct `json:"link"` - Context struct { - Details []string `json:"details"` - Title string `json:"title"` - LinkText string `json:"link_text"` - } `json:"context"` - Code string `json:"code"` - Message string `json:"message"` - } `json:"user_warnings"` - UserReference string `json:"user_reference"` - SourceCurrency string `json:"source_currency"` - TargetCurrency string `json:"target_currency"` - CancellationReason struct { - Message string `json:"message"` - Code string `json:"code"` - ErrorCode string `json:"error_code"` - ErrorCTA string `json:"error_cta"` - } `json:"cancellation_reason"` - SourceID string `json:"source_id"` - TargetID string `json:"target_id"` - ExchangeRate ValCur `json:"exchange_rate"` - TaxDetails []struct { - Name string `json:"name"` - Amount ValCur `json:"amount"` - } `json:"tax_details"` - TradeIncentiveInfo struct { - AppliedIncentive bool `json:"applied_incentive"` - UserIncentiveID string `json:"user_incentive_id"` - CodeVal string `json:"code_val"` - EndsAt time.Time `json:"ends_at"` - FeeWithoutIncentive ValCur `json:"fee_without_incentive"` - Redeemed bool `json:"redeemed"` - } `json:"trade_incentive_info"` - TotalFeeWithoutTax FeeStruct `json:"total_fee_without_tax"` - FiatDenotedTotal ValCur `json:"fiat_denoted_total"` - } `json:"trade"` -} + ID string `json:"id"` + Status string `json:"status"` + UserEnteredAmount ValCur `json:"user_entered_amount"` + Amount ValCur `json:"amount"` + Subtotal ValCur `json:"subtotal"` + Total ValCur `json:"total"` + Fees []FeeStruct `json:"fees"` + TotalFee FeeStruct `json:"total_fee"` + Source AccountStruct `json:"source"` + Target AccountStruct `json:"target"` + UnitPrice UnitPrice `json:"unit_price"` + UserWarnings []UserWarnings `json:"user_warnings"` + UserReference string `json:"user_reference"` + SourceCurrency string `json:"source_currency"` + TargetCurrency string `json:"target_currency"` + CancellationReason CancellationReason `json:"cancellation_reason"` + SourceID string `json:"source_id"` + TargetID string `json:"target_id"` + ExchangeRate ValCur `json:"exchange_rate"` + TaxDetails []TaxDetails `json:"tax_details"` + TradeIncentiveInfo TradeIncentiveInfo `json:"trade_incentive_info"` + TotalFeeWithoutTax FeeStruct `json:"total_fee_without_tax"` + FiatDenotedTotal ValCur `json:"fiat_denoted_total"` +} + +// UnixTimestampMilli is a type used to unmarshal unix millisecond timestamps returned from +// the exchange, used in the type ServerTimeV3 +type UnixTimestampMilli time.Time // ServerTimeV3 holds information on the server's time, returned by GetV3Time type ServerTimeV3 struct { - Iso time.Time `json:"iso"` - EpochSeconds int64 `json:"epochSeconds,string"` - EpochMilliseconds int64 `json:"epochMillis,string"` + Iso time.Time `json:"iso"` + EpochSeconds UnixTimestamp `json:"epochSeconds,string"` + EpochMilliseconds UnixTimestampMilli `json:"epochMillis,string"` } -// PaymentMethodData is a sub-type that holds information on a payment method. Used in -// GetAllPaymentMethodsResp and GenPaymentMethodResp +// PaymentMethodData is a sub-type that holds information on a payment method type PaymentMethodData struct { ID string `json:"id"` Type string `json:"type"` @@ -787,18 +780,6 @@ type PaymentMethodData struct { UpdatedAt time.Time `json:"updated_at"` } -// GetAllPaymentMethodsResp holds information on many payment methods. Returned by -// GetAllPaymentMethods -type GetAllPaymentMethodsResp struct { - PaymentMethods []PaymentMethodData `json:"payment_methods"` -} - -// GenPaymentMethodResp holds information on a payment method. Returned by -// GetPaymentMethodByID -type GenPaymentMethodResp struct { - PaymentMethod PaymentMethodData `json:"payment_method"` -} - // IDResource holds an ID, resource type, and associated data, used in ListNotificationsResponse, // TransactionData, DeposWithdrData, and PaymentMethodData type IDResource struct { @@ -832,55 +813,67 @@ type PaginationInp struct { EndingBefore string } -// AmCur is a sub-struct used in ListNotificationsResponse, WalletData, TransactionData, +// AmCur is a sub-struct used in ListNotificationsSubData, WalletData, TransactionData, // DeposWithdrData, and PaymentMethodData type AmCur struct { Amount float64 `json:"amount,string"` Currency string `json:"currency"` } +// Fees is a sub-struct used in ListNotificationsSubData +type Fees []struct { + Type string `json:"type"` + Amount AmCur `json:"amount"` +} + +// ListNotificationsSubData is a sub-struct used in ListNotificationsData +type ListNotificationsSubData struct { + ID string `json:"id"` + Address string `json:"address"` + Name string `json:"name"` + Status string `json:"status"` + PaymentMethod IDResource `json:"payment_method"` + Transaction IDResource `json:"transaction"` + Amount AmCur `json:"amount"` + Total AmCur `json:"total"` + Subtotal AmCur `json:"subtotal"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + Resource string `json:"resource"` + ResourcePath string `json:"resource_path"` + Committed bool `json:"committed"` + Instant bool `json:"instant"` + Fee AmCur `json:"fee"` + Fees []Fees `json:"fees"` + PayoutAt time.Time `json:"payout_at"` +} + +// AdditionalData is a sub-struct used in ListNotificationsData +type AdditionalData struct { + Hash string `json:"hash"` + Amount AmCur `json:"amount"` +} + +// ListNotificationsData is a sub-struct used in ListNotificationsResponse +type ListNotificationsData struct { + ID string `json:"id"` + Type string `json:"type"` + Data ListNotificationsSubData `json:"data"` + AdditionalData AdditionalData `json:"additional_data"` + User IDResource `json:"user"` + Account IDResource `json:"account"` + DeliveryAttempts int32 `json:"delivery_attempts"` + CreatedAt time.Time `json:"created_at"` + Resource string `json:"resource"` + ResourcePath string `json:"resource_path"` + Transaction IDResource `json:"transaction"` +} + // ListNotificationsResponse holds information on notifications that the user is subscribed // to. Returned by ListNotifications type ListNotificationsResponse struct { - Pagination PaginationResp `json:"pagination"` - Data []struct { - ID string `json:"id"` - Type string `json:"type"` - Data struct { - ID string `json:"id"` - Address string `json:"address"` - Name string `json:"name"` - Status string `json:"status"` - PaymentMethod IDResource `json:"payment_method"` - Transaction IDResource `json:"transaction"` - Amount AmCur `json:"amount"` - Total AmCur `json:"total"` - Subtotal AmCur `json:"subtotal"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` - Resource string `json:"resource"` - ResourcePath string `json:"resource_path"` - Committed bool `json:"committed"` - Instant bool `json:"instant"` - Fee AmCur `json:"fee"` - Fees []struct { - Type string `json:"type"` - Amount AmCur `json:"amount"` - } `json:"fees"` - PayoutAt time.Time `json:"payout_at"` - } `json:"data"` - AdditionalData struct { - Hash string `json:"hash"` - Amount AmCur `json:"amount"` - } `json:"additional_data"` - User IDResource `json:"user"` - Account IDResource `json:"account"` - DeliveryAttempts int32 `json:"delivery_attempts"` - CreatedAt time.Time `json:"created_at"` - Resource string `json:"resource"` - ResourcePath string `json:"resource_path"` - Transaction IDResource `json:"transaction"` - } `json:"data"` + Pagination PaginationResp `json:"pagination"` + Data []ListNotificationsData `json:"data"` } // CodeName is a sub-struct holding a code and a name, used in UserResponse @@ -889,77 +882,87 @@ type CodeName struct { Name string `json:"name"` } -// UserResponse holds information on a user, returned by GetUserByID and GetCurrentUser +// Country is a sub-struct, used in UserResponse +type Country struct { + Code string `json:"code"` + Name string `json:"name"` + IsInEurope bool `json:"is_in_europe"` +} + +// Tiers is a sub-struct, used in UserResponse +type Tiers struct { + CompletedDescription string `json:"completed_description"` + UpgradeButtonText string `json:"upgrade_button_text"` + Header string `json:"header"` + Body string `json:"body"` +} + +// ReferralMoney is a sub-struct, used in UserResponse +type ReferralMoney struct { + Amount float64 `json:"amount,string"` + Currency string `json:"currency"` + CurrencySymbol string `json:"currency_symbol"` + ReferralThreshold float64 `json:"referral_threshold,string"` +} + +// UserResponse holds information on a user, returned by GetCurrentUser type UserResponse struct { - Data struct { - ID string `json:"id"` - Name string `json:"name"` - Username string `json:"username"` - ProfileLocation string `json:"profile_location"` - ProfileBio string `json:"profile_bio"` - ProfileURL string `json:"profile_url"` - AvatarURL string `json:"avatar_url"` - Resource string `json:"resource"` - ResourcePath string `json:"resource_path"` - LegacyID string `json:"legacy_id"` - TimeZone string `json:"time_zone"` - NativeCurrency string `json:"native_currency"` - BitcoinUnit string `json:"bitcoin_unit"` - State string `json:"state"` - Country struct { - Code string `json:"code"` - Name string `json:"name"` - IsInEurope bool `json:"is_in_europe"` - } `json:"country"` - Nationality CodeName `json:"nationality"` - RegionSupportsFiatTransfers bool `json:"region_supports_fiat_transfers"` - RegionSupportsCryptoToCryptoTransfers bool `json:"region_supports_crypto_to_crypto_transfers"` - CreatedAt time.Time `json:"created_at"` - SupportsRewards bool `json:"supports_rewards"` - Tiers struct { - CompletedDescription string `json:"completed_description"` - UpgradeButtonText string `json:"upgrade_button_text"` - Header string `json:"header"` - Body string `json:"body"` - } `json:"tiers"` - ReferralMoney struct { - Amount float64 `json:"amount,string"` - Currency string `json:"currency"` - CurrencySymbol string `json:"currency_symbol"` - ReferralThreshold float64 `json:"referral_threshold,string"` - } `json:"referral_money"` - HasBlockingBuyRestrictions bool `json:"has_blocking_buy_restrictions"` - HasMadeAPurchase bool `json:"has_made_a_purchase"` - HasBuyDepositPaymentMethods bool `json:"has_buy_deposit_payment_methods"` - HasUnverifiedBuyDepositPaymentMethods bool `json:"has_unverified_buy_deposit_payment_methods"` - NeedsKYCRemediation bool `json:"needs_kyc_remediation"` - ShowInstantAchUx bool `json:"show_instant_ach_ux"` - UserType string `json:"user_type"` - Email string `json:"email"` - SendsDisabled bool `json:"sends_disabled"` - } `json:"data"` -} - -// WalletData is a sub-struct holding wallet information, used in GenWalletResponse and GetAllWalletsResponse + ID string `json:"id"` + Name string `json:"name"` + Username string `json:"username"` + ProfileLocation string `json:"profile_location"` + ProfileBio string `json:"profile_bio"` + ProfileURL string `json:"profile_url"` + AvatarURL string `json:"avatar_url"` + Resource string `json:"resource"` + ResourcePath string `json:"resource_path"` + LegacyID string `json:"legacy_id"` + TimeZone string `json:"time_zone"` + NativeCurrency string `json:"native_currency"` + BitcoinUnit string `json:"bitcoin_unit"` + State string `json:"state"` + Country Country `json:"country"` + Nationality CodeName `json:"nationality"` + RegionSupportsFiatTransfers bool `json:"region_supports_fiat_transfers"` + RegionSupportsCryptoToCryptoTransfers bool `json:"region_supports_crypto_to_crypto_transfers"` + CreatedAt time.Time `json:"created_at"` + SupportsRewards bool `json:"supports_rewards"` + Tiers Tiers `json:"tiers"` + ReferralMoney ReferralMoney `json:"referral_money"` + HasBlockingBuyRestrictions bool `json:"has_blocking_buy_restrictions"` + HasMadeAPurchase bool `json:"has_made_a_purchase"` + HasBuyDepositPaymentMethods bool `json:"has_buy_deposit_payment_methods"` + HasUnverifiedBuyDepositPaymentMethods bool `json:"has_unverified_buy_deposit_payment_methods"` + NeedsKYCRemediation bool `json:"needs_kyc_remediation"` + ShowInstantAchUx bool `json:"show_instant_ach_ux"` + UserType string `json:"user_type"` + Email string `json:"email"` + SendsDisabled bool `json:"sends_disabled"` +} + +// Currency is a sub-struct holding information on a currency, used in WalletData +type Currency struct { + Code string `json:"code"` + Name string `json:"name"` + Color string `json:"color"` + SortIndex int32 `json:"sort_index"` + Exponent int32 `json:"exponent"` + Type string `json:"type"` + AddressRegex string `json:"address_regex"` + AssetID string `json:"asset_id"` + DestinationTagName string `json:"destination_tag_name"` + DestinationTagRegex string `json:"destination_tag_regex"` + Slug string `json:"slug"` + Rewards any `json:"rewards"` +} + +// WalletData is a sub-struct holding wallet information, used in GetAllWalletsResponse type WalletData struct { - ID string `json:"id"` - Name string `json:"name"` - Primary bool `json:"primary"` - Type string `json:"type"` - Currency struct { - Code string `json:"code"` - Name string `json:"name"` - Color string `json:"color"` - SortIndex int32 `json:"sort_index"` - Exponent int32 `json:"exponent"` - Type string `json:"type"` - AddressRegex string `json:"address_regex"` - AssetID string `json:"asset_id"` - DestinationTagName string `json:"destination_tag_name"` - DestinationTagRegex string `json:"destination_tag_regex"` - Slug string `json:"slug"` - Rewards interface{} `json:"rewards"` - } `json:"currency"` + ID string `json:"id"` + Name string `json:"name"` + Primary bool `json:"primary"` + Type string `json:"type"` + Currency Currency `json:"currency"` Balance AmCur `json:"balance"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` @@ -969,11 +972,6 @@ type WalletData struct { AllowWithdrawals bool `json:"allow_withdrawals"` } -// GenWalletResponse holds information on a single wallet, returned by GetWalletByID -type GenWalletResponse struct { - Data WalletData `json:"data"` -} - // GetAllWalletsResponse holds information on many wallets, returned by GetAllWallets type GetAllWalletsResponse struct { Pagination *PaginationResp `json:"pagination"` @@ -992,50 +990,56 @@ type TitleSubtitle struct { Subtitle string `json:"subtitle"` } -// AddressData holds address information, used in GenAddrResponse and GetAllAddrResponse +// Options is a sub-struct used in Warnings +type Options struct { + Text string `json:"text"` + Style string `json:"style"` + ID string `json:"id"` +} + +// Warnings is a sub-struct used in AddressData +type Warnings struct { + Type string `json:"type"` + Title string `json:"title"` + Details string `json:"details"` + ImageURL string `json:"image_url"` + Options []Options `json:"options"` +} + +// ShareAddressCopy is a sub-struct used in AddressData +type ShareAddressCopy struct { + Line1 string `json:"line1"` + Line2 string `json:"line2"` +} + +// InlineWarning is a sub-struct used in AddressData +type InlineWarning struct { + Text string `json:"text"` + Tooltip TitleSubtitle `json:"tooltip"` +} + +// AddressData holds address information, used in GetAllAddrResponse type AddressData struct { - ID string `json:"id"` - Address string `json:"address"` - AddressInfo AddressInfo `json:"address_info"` - Name string `json:"name"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` - Network string `json:"network"` - URIScheme string `json:"uri_scheme"` - Resource string `json:"resource"` - ResourcePath string `json:"resource_path"` - Warnings []struct { - Type string `json:"type"` - Title string `json:"title"` - Details string `json:"details"` - ImageURL string `json:"image_url"` - Options []struct { - Text string `json:"text"` - Style string `json:"style"` - ID string `json:"id"` - } `json:"options"` - } `json:"warnings"` - QRCodeImageURL string `json:"qr_code_image_url"` - AddressLabel string `json:"address_label"` - DefaultReceive bool `json:"default_receive"` - DestinationTag string `json:"destination_tag"` - DepositURI string `json:"deposit_uri"` - CallbackURL string `json:"callback_url"` - ShareAddressCopy struct { - Line1 string `json:"line1"` - Line2 string `json:"line2"` - } `json:"share_address_copy"` - ReceiveSubtitle string `json:"receive_subtitle"` - InlineWarning struct { - Text string `json:"text"` - Tooltip TitleSubtitle `json:"tooltip"` - } `json:"inline_warning"` -} - -// GenAddrResponse holds information on a generated address, returned by CreateAddress and -// GetAddressByID -type GenAddrResponse struct { - Data AddressData `json:"data"` + ID string `json:"id"` + Address string `json:"address"` + AddressInfo AddressInfo `json:"address_info"` + Name string `json:"name"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + Network string `json:"network"` + URIScheme string `json:"uri_scheme"` + Resource string `json:"resource"` + ResourcePath string `json:"resource_path"` + Warnings []Warnings `json:"warnings"` + QRCodeImageURL string `json:"qr_code_image_url"` + AddressLabel string `json:"address_label"` + DefaultReceive bool `json:"default_receive"` + DestinationTag string `json:"destination_tag"` + DepositURI string `json:"deposit_uri"` + CallbackURL string `json:"callback_url"` + ShareAddressCopy ShareAddressCopy `json:"share_address_copy"` + ReceiveSubtitle string `json:"receive_subtitle"` + InlineWarning InlineWarning `json:"inline_warning"` } // GetAllAddrResponse holds information on many addresses, returned by GetAllAddresses @@ -1044,40 +1048,39 @@ type GetAllAddrResponse struct { Data []AddressData `json:"data"` } +// AdvancedTradeFill is a sub-struct used in TransactionData +type AdvancedTradeFill struct { + FillPrice float64 `json:"fill_price,string"` + ProductID currency.Pair `json:"product_id"` + OrderID string `json:"order_id"` + Commission float64 `json:"commission,string"` + OrderSide string `json:"order_side"` +} + +// Network is a sub-struct used in TransactionData +type Network struct { + Status string `json:"status"` + Hash string `json:"hash"` + Name string `json:"name"` +} + // TransactionData is a sub-type that holds information on a transaction. Used in -// ManyTransactionsResp and GenTransactionResp +// ManyTransactionsResp type TransactionData struct { - ID string `json:"id"` - Type string `json:"type"` - Status string `json:"status"` - Amount AmCur `json:"amount"` - NativeAmount AmCur `json:"native_amount"` - Description string `json:"description"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` - Resource string `json:"resource"` - ResourcePath string `json:"resource_path"` - InstantExchange bool `json:"instant_exchange"` - Buy IDResource `json:"buy"` - AdvancedTradeFill struct { - FillPrice float64 `json:"fill_price,string"` - ProductID string `json:"product_id"` - OrderID string `json:"order_id"` - Commission float64 `json:"commission,string"` - OrderSide string `json:"order_side"` - } `json:"advanced_trade_fill"` - Details TitleSubtitle `json:"details"` - Network struct { - Status string `json:"status"` - Hash string `json:"hash"` - Name string `json:"name"` - } `json:"network"` - To IDResource `json:"to"` - From IDResource `json:"from"` - Address struct { - } `json:"address"` - Application struct { - } `json:"application"` + ID string `json:"id"` + Type string `json:"type"` + Status string `json:"status"` + Amount AmCur `json:"amount"` + NativeAmount AmCur `json:"native_amount"` + Description string `json:"description"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + Resource string `json:"resource"` + ResourcePath string `json:"resource_path"` + Details TitleSubtitle `json:"details"` + Network Network `json:"network"` + To IDResource `json:"to"` + From IDResource `json:"from"` } // ManyTransactionsResp holds information on many transactions. Returned by @@ -1087,34 +1090,23 @@ type ManyTransactionsResp struct { Data []TransactionData `json:"data"` } -// GenTransactionResp holds information on one transaction. Returned by SendMoney and GetTransactionByID -type GenTransactionResp struct { - Data TransactionData `json:"data"` -} - // DeposWithdrData is a sub-type that holds information on a deposit/withdrawal. Used in -// GenDeposWithdrResp and ManyDeposWithdrResp +// ManyDeposWithdrResp type DeposWithdrData struct { - ID string `json:"id"` - Status string `json:"status"` - PaymentMethod IDResource `json:"payment_method"` - Transaction IDResource `json:"transaction"` - Amount AmCur `json:"amount"` - Subtotal AmCur `json:"subtotal"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` - Resource string `json:"resource"` - ResourcePath string `json:"resource_path"` - Committed bool `json:"committed"` - Fee AmCur `json:"fee"` - PayoutAt time.Time `json:"payout_at"` - TransferType FiatTransferType -} - -// GenDeposWithdrResp holds information on a deposit. Returned by FiatTransfer, CommitTransfer, and -// GetFiatTransferByID -type GenDeposWithdrResp struct { - Data DeposWithdrData `json:"data"` + ID string `json:"id"` + Status string `json:"status"` + PaymentMethod IDResource `json:"payment_method"` + Transaction IDResource `json:"transaction"` + Amount AmCur `json:"amount"` + Subtotal AmCur `json:"subtotal"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + Resource string `json:"resource"` + ResourcePath string `json:"resource_path"` + Committed bool `json:"committed"` + Fee AmCur `json:"fee"` + PayoutAt time.Time `json:"payout_at"` + TransferType FiatTransferType `json:"transfer_type"` } // ManyDeposWithdrResp holds information on many deposits. Returned by GetAllFiatTransfers @@ -1123,22 +1115,14 @@ type ManyDeposWithdrResp struct { Data []DeposWithdrData `json:"data"` } -// FiatData holds information on fiat currencies. Used as a sub-struct in -// GetFiatCurrenciesResp, and returned by GetFiatCurrencies +// FiatData holds information on fiat currencies. 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 []FiatData `json:"data"` -} - -// CryptoData holds information on cryptocurrencies. Used as a sub-struct in -// GetCryptocurrenciesResp, and returned by GetCryptocurrencies +// CryptoData holds information on cryptocurrencies. Returned by GetCryptocurrencies type CryptoData struct { Code string `json:"code"` Name string `json:"name"` @@ -1150,46 +1134,34 @@ type CryptoData struct { AssetID string `json:"asset_id"` } -// GetCryptocurrenciesResp holds information on cryptocurrencies. Used for -// unmarshalling in GetCryptocurrencies -type GetCryptocurrenciesResp struct { - Data []CryptoData `json:"data"` -} - // GetExchangeRatesResp holds information on exchange rates. Returned by GetExchangeRates type GetExchangeRatesResp struct { - Data struct { - Currency string `json:"currency"` - Rates map[string]types.Number `json:"rates"` - } `json:"data"` + Currency string `json:"currency"` + Rates map[string]types.Number `json:"rates"` } // GetPriceResp holds information on a price. Returned by GetPrice type GetPriceResp struct { - Data struct { - Amount float64 `json:"amount,string"` - Base string `json:"base"` - Currency string `json:"currency"` - } `json:"data"` + Amount float64 `json:"amount,string"` + Base string `json:"base"` + Currency string `json:"currency"` } // ServerTimeV2 holds current requested server time information, returned by GetV2Time type ServerTimeV2 struct { - Data struct { - ISO time.Time `json:"iso"` - Epoch uint64 `json:"epoch"` - } `json:"data"` + ISO time.Time `json:"iso"` + Epoch uint64 `json:"epoch"` } // WebsocketRequest is an aspect of constructing a request to the websocket server, used in sendRequest type WebsocketRequest struct { - Type string `json:"type"` - ProductIDs []string `json:"product_ids,omitempty"` - Channel string `json:"channel,omitempty"` - Signature string `json:"signature,omitempty"` - Key string `json:"api_key,omitempty"` - Timestamp string `json:"timestamp,omitempty"` - JWT string `json:"jwt,omitempty"` + Type string `json:"type"` + ProductIDs []currency.Pair `json:"product_ids,omitempty"` + Channel string `json:"channel,omitempty"` + Signature string `json:"signature,omitempty"` + Key string `json:"api_key,omitempty"` + Timestamp string `json:"timestamp,omitempty"` + JWT string `json:"jwt,omitempty"` } // WebsocketTicker defines a ticker websocket response, used in WebsocketTickerHolder @@ -1303,43 +1275,49 @@ type WebsocketOrderDataHolder struct { Orders []WebsocketOrderData `json:"orders"` } +// Details is a sub-struct used in CurrencyData +type Details struct { + Type string `json:"type"` + Symbol string `json:"symbol"` + NetworkConfirmations int32 `json:"network_confirmations"` + SortOrder int32 `json:"sort_order"` + CryptoAddressLink string `json:"crypto_address_link"` + CryptoTransactionLink string `json:"crypto_transaction_link"` + PushPaymentMethods []string `json:"push_payment_methods"` + GroupTypes []string `json:"group_types"` + DisplayName string `json:"display_name"` + ProcessingTimeSeconds int64 `json:"processing_time_seconds"` + MinWithdrawalAmount float64 `json:"min_withdrawal_amount"` + MaxWithdrawalAmount float64 `json:"max_withdrawal_amount"` +} + +// SupportedNetworks is a sub-struct used in CurrencyData +type SupportedNetworks struct { + ID string `json:"id"` + Name string `json:"name"` + Status string `json:"status"` + ContractAddress string `json:"contract_address"` + CryptoAddressLink string `json:"crypto_address_link"` + CryptoTransactionLink string `json:"crypto_transaction_link"` + MinWithdrawalAmount float64 `json:"min_withdrawal_amount"` + MaxWithdrawalAmount float64 `json:"max_withdrawal_amount"` + NetworkConfirmations int32 `json:"network_confirmations"` + ProcessingTimeSeconds int64 `json:"processing_time_seconds"` +} + // CurrencyData contains information on known currencies, used in GetAllCurrencies and GetACurrency type CurrencyData struct { - ID string `json:"id"` - Name string `json:"name"` - MinSize string `json:"min_size"` - Status string `json:"status"` - Message string `json:"message"` - MaxPrecision float64 `json:"max_precision,string"` - ConvertibleTo []string `json:"convertible_to"` - Details struct { - Type string `json:"type"` - Symbol string `json:"symbol"` - NetworkConfirmations int32 `json:"network_confirmations"` - SortOrder int32 `json:"sort_order"` - CryptoAddressLink string `json:"crypto_address_link"` - CryptoTransactionLink string `json:"crypto_transaction_link"` - PushPaymentMethods []string `json:"push_payment_methods"` - GroupTypes []string `json:"group_types"` - DisplayName string `json:"display_name"` - ProcessingTimeSeconds int64 `json:"processing_time_seconds"` - MinWithdrawalAmount float64 `json:"min_withdrawal_amount"` - MaxWithdrawalAmount float64 `json:"max_withdrawal_amount"` - } `json:"details"` - DefaultNetwork string `json:"default_network"` - SupportedNetworks []struct { - ID string `json:"id"` - Name string `json:"name"` - Status string `json:"status"` - ContractAddress string `json:"contract_address"` - CryptoAddressLink string `json:"crypto_address_link"` - CryptoTransactionLink string `json:"crypto_transaction_link"` - MinWithdrawalAmount float64 `json:"min_withdrawal_amount"` - MaxWithdrawalAmount float64 `json:"max_withdrawal_amount"` - NetworkConfirmations int32 `json:"network_confirmations"` - ProcessingTimeSeconds int64 `json:"processing_time_seconds"` - } `json:"supported_networks"` - DisplayName string `json:"display_name"` + ID string `json:"id"` + Name string `json:"name"` + MinSize string `json:"min_size"` + Status string `json:"status"` + Message string `json:"message"` + MaxPrecision float64 `json:"max_precision,string"` + ConvertibleTo []string `json:"convertible_to"` + Details Details `json:"details"` + DefaultNetwork string `json:"default_network"` + SupportedNetworks []SupportedNetworks `json:"supported_networks"` + DisplayName string `json:"display_name"` } // PairData contains information on available trading pairs, used in GetAllTradingPairs diff --git a/exchanges/coinbasepro/coinbasepro_websocket.go b/exchanges/coinbasepro/coinbasepro_websocket.go index 5fe64128b71..03f8823e959 100644 --- a/exchanges/coinbasepro/coinbasepro_websocket.go +++ b/exchanges/coinbasepro/coinbasepro_websocket.go @@ -21,6 +21,7 @@ import ( "github.com/pkg/errors" "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/common/crypto" + "github.com/thrasher-corp/gocryptotrader/currency" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" @@ -356,7 +357,7 @@ func (c *CoinbasePro) manageSubs(op string, subs subscription.List) error { for _, s := range subs { r := &WebsocketRequest{ Type: op, - ProductIDs: s.Pairs.Strings(), + ProductIDs: s.Pairs, Channel: s.QualifiedChannel, Timestamp: strconv.FormatInt(time.Now().Unix(), 10), } @@ -384,16 +385,21 @@ func (c *CoinbasePro) manageSubs(op string, subs subscription.List) error { } func (c *CoinbasePro) signWsRequest(r *WebsocketRequest) error { + // 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 + // jwt, err := c.GetJWT(context.Background(), "") + // if err != nil { + // return err + // } + // c.jwt, r.JWT = jwt, jwt creds, err := c.GetCredentials(context.Background()) if err != nil { return err } - hmac, err := crypto.GetHMAC(crypto.HashSHA256, []byte(r.Timestamp+r.Channel+strings.Join(r.ProductIDs, ",")), []byte(creds.Secret)) + hmac, err := crypto.GetHMAC(crypto.HashSHA256, []byte(r.Timestamp+r.Channel+currency.Pairs(r.ProductIDs).Join()), []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 r.Key = creds.Key r.Signature = hex.EncodeToString(hmac) return nil @@ -401,9 +407,9 @@ func (c *CoinbasePro) signWsRequest(r *WebsocketRequest) error { // GetJWT checks if the current JWT is valid, returns it if it is, generates a new one if it isn't // Also suitable for use in REST requests, by checking for the presence of a URI, and always generating -// a new JWT if one is not provided +// a new JWT if one is provided func (c *CoinbasePro) GetJWT(ctx context.Context, uri string) (string, error) { - if c.jwtLastRegen.Add(time.Minute*2).After(time.Now()) && uri != "" { + if c.jwtLastRegen.Add(time.Minute*2).After(time.Now()) && uri == "" { return c.jwt, nil } creds, err := c.GetCredentials(ctx) @@ -418,21 +424,21 @@ func (c *CoinbasePro) GetJWT(ctx context.Context, uri string) (string, error) { if err != nil { return "", err } - nonce, err := common.GenerateRandomString(64, "1234567890ABCDEF") + nonce, err := common.GenerateRandomString(16, "1234567890ABCDEF") if err != nil { return "", err } - head := map[string]interface{}{"kid": creds.ClientID, "typ": "JWT", "alg": "ES256", "nonce": nonce} + head := map[string]any{"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"} + body := map[string]any{"iss": "cdp", "nbf": time.Now().Unix(), "exp": time.Now().Add(time.Minute * 2).Unix(), "sub": creds.ClientID, "aud": "retail_rest_api_proxy"} if uri != "" { body["uri"] = uri + } else { + c.jwtLastRegen = time.Now() } bodyJSON, err := json.Marshal(body) if err != nil { @@ -475,7 +481,7 @@ func processBidAskArray(data *WebsocketOrderbookDataHolder) (bids, asks orderboo case "offer": asks = append(asks, change) default: - return nil, nil, fmt.Errorf("%w %v", errUnknownSide, data.Changes[i].Side) + return nil, nil, fmt.Errorf("%w %v", order.ErrSideIsInvalid, data.Changes[i].Side) } } bids.SortBids() diff --git a/exchanges/coinbasepro/coinbasepro_wrapper.go b/exchanges/coinbasepro/coinbasepro_wrapper.go index 665515258f8..e3c1da97e94 100644 --- a/exchanges/coinbasepro/coinbasepro_wrapper.go +++ b/exchanges/coinbasepro/coinbasepro_wrapper.go @@ -106,10 +106,11 @@ func (c *CoinbasePro) SetDefaults() { }, }, Subscriptions: defaultSubscriptions.Clone(), + TradingRequirements: protocol.TradingRequirements{ + SpotMarketOrderAmountPurchaseQuotationOnly: true, + }, } - c.Requester, err = request.New(c.Name, - common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout), - request.WithLimiter(GetRateLimit())) + c.Requester, err = request.New(c.Name, common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout), request.WithLimiter(GetRateLimit())) if err != nil { log.Errorln(log.ExchangeSys, err) } @@ -143,9 +144,7 @@ func (c *CoinbasePro) Setup(exch *config.Exchange) error { if err != nil { return err } - c.checkSubscriptions() - wsRunningURL, err := c.API.Endpoints.GetURL(exchange.WebsocketSpot) if err != nil { return err @@ -179,33 +178,26 @@ func (c *CoinbasePro) FetchTradablePairs(ctx context.Context, a asset.Item) (cur if err != nil { return nil, err } - aString := a.Upper() - if len(aString) == 7 { - aString = aString[:6] - } + aString := FormatAssetOutbound(a) if verified { - products, err = c.GetAllProducts(ctx, 2<<30-1, 0, aString, "", "", nil, verified) + products, err = c.GetAllProducts(ctx, 0, 0, aString, "", "", nil, verified) if err != nil { log.Warnf(log.ExchangeSys, warnAuth, err) verified = false } } if !verified { - products, err = c.GetAllProducts(ctx, 2<<30-1, 0, aString, "", "", nil, verified) + products, err = c.GetAllProducts(ctx, 0, 0, aString, "", "", nil, verified) if err != nil { return nil, err } } - pairs := make([]currency.Pair, len(products.Products)) + pairs := make([]currency.Pair, 0, len(products.Products)) for x := range products.Products { if products.Products[x].TradingDisabled { continue } - pair, err := currency.NewPairDelimiter(products.Products[x].ID, currency.DashDelimiter) - if err != nil { - return nil, err - } - pairs[x] = pair + pairs = append(pairs, products.Products[x].ID) } return pairs, nil } @@ -253,11 +245,10 @@ func (c *CoinbasePro) UpdateAccountInfo(ctx context.Context, assetType asset.Ite profileID := accountBalance[i].UUID currencies := accountCurrencies[profileID] accountCurrencies[profileID] = append(currencies, account.Balance{ - Currency: currency.NewCode(accountBalance[i].Currency), - Total: accountBalance[i].AvailableBalance.Value, - Hold: accountBalance[i].Hold.Value, - Free: accountBalance[i].AvailableBalance.Value - - accountBalance[i].Hold.Value, + Currency: currency.NewCode(accountBalance[i].Currency), + Total: accountBalance[i].AvailableBalance.Value, + Hold: accountBalance[i].Hold.Value, + Free: accountBalance[i].AvailableBalance.Value - accountBalance[i].Hold.Value, AvailableWithoutBorrow: accountBalance[i].AvailableBalance.Value, }) } @@ -289,22 +280,8 @@ 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 { - verified, err := c.verificationCheck(ctx) - if err != nil { - return err - } - products, err := c.GetEnabledPairs(assetType) - if err != nil { - return err - } - for x := range products { - err = c.tickerHelper(ctx, products[x].String(), assetType, verified) - if err != nil { - return err - } - } - return nil +func (c *CoinbasePro) UpdateTickers(_ context.Context, _ asset.Item) error { + return common.ErrFunctionNotSupported } // UpdateTicker updates and returns the ticker for a currency pair @@ -364,10 +341,6 @@ func (c *CoinbasePro) UpdateOrderbook(ctx context.Context, p currency.Pair, asse if err != nil { return nil, err } - fPair, err := c.FormatExchangeCurrency(p, assetType) - if err != nil { - return nil, err - } book := &orderbook.Base{ Exchange: c.Name, Pair: p, @@ -376,14 +349,14 @@ func (c *CoinbasePro) UpdateOrderbook(ctx context.Context, p currency.Pair, asse } var orderbookNew *ProductBook if verified { - orderbookNew, err = c.GetProductBookV3(ctx, fPair.String(), 1000, true) + orderbookNew, err = c.GetProductBookV3(ctx, p.String(), 1000, true) if err != nil { log.Warnf(log.ExchangeSys, warnAuth, err) verified = false } } if !verified { - orderbookNew, err = c.GetProductBookV3(ctx, fPair.String(), 1000, false) + orderbookNew, err = c.GetProductBookV3(ctx, p.String(), 1000, false) if err != nil { return book, err } @@ -540,8 +513,7 @@ func (c *CoinbasePro) SubmitOrder(ctx context.Context, s *order.Submit) (*order. 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) + 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 } @@ -555,7 +527,7 @@ func (c *CoinbasePro) SubmitOrder(ctx context.Context, s *order.Submit) (*order. if err != nil { return nil, err } - subResp.Fee = feeResp.Order.TotalFees + subResp.Fee = feeResp.TotalFees } return subResp, nil } @@ -577,7 +549,6 @@ func (c *CoinbasePro) ModifyOrder(ctx context.Context, m *order.Modify) (*order. if !success { return nil, errOrderModFailNoRet } - return m.DeriveModifyResponse() } @@ -618,7 +589,9 @@ func (c *CoinbasePro) CancelBatchOrders(ctx context.Context, o []order.Cancel) ( ordIDSlice[i] = o[i].OrderID status.Status[o[i].OrderID] = "Failed to cancel" } - var resp CancelOrderResp + resp := struct { + Results []OrderCancelDetail `json:"results"` + }{} for i := 0; i < ordToCancel; i += 100 { var tempOrdIDSlice []string if ordToCancel-i < 100 { @@ -651,14 +624,14 @@ func (c *CoinbasePro) GetOrderInfo(ctx context.Context, orderID string, pair cur if err != nil { return nil, err } - response := c.getOrderRespToOrderDetail(&genOrderDetail.Order, pair, assetItem) - fillData, err := c.GetFills(ctx, orderID, "", "", time.Time{}, time.Now(), 2<<15-1) + response := c.getOrderRespToOrderDetail(genOrderDetail, pair, assetItem) + fillData, err := c.GetFills(ctx, orderID, "", "", time.Time{}, time.Now(), ManyFills) if err != nil { return nil, err } cursor := fillData.Cursor for cursor != "" { - tempFillData, err := c.GetFills(ctx, orderID, "", cursor, time.Time{}, time.Now(), 2<<15-1) + tempFillData, err := c.GetFills(ctx, orderID, "", cursor, time.Time{}, time.Now(), ManyFills) if err != nil { return nil, err } @@ -709,9 +682,9 @@ func (c *CoinbasePro) GetDepositAddress(ctx context.Context, cryptocurrency curr return nil, err } return &deposit.Address{ - Address: resp.Data.Address, - Tag: resp.Data.Name, - Chain: resp.Data.Network, + Address: resp.Address, + Tag: resp.Name, + Chain: resp.Network, }, nil } @@ -724,17 +697,12 @@ func (c *CoinbasePro) WithdrawCryptocurrencyFunds(ctx context.Context, withdrawR if withdrawRequest.WalletID == "" { return nil, errWalletIDEmpty } - t := time.Now().UnixNano() - u := math.Float64bits(withdrawRequest.Amount) - 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) + message := generateIdempotency(withdrawRequest.Amount) + 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 + return &withdraw.ExchangeResponse{Name: resp.Network.Name, ID: resp.ID, Status: resp.Status}, nil } // WithdrawFiatFunds returns a withdrawal ID when a withdrawal is @@ -751,24 +719,23 @@ func (c *CoinbasePro) WithdrawFiatFunds(ctx context.Context, withdrawRequest *wi return nil, err } selectedWithdrawalMethod := PaymentMethodData{} - for i := range paymentMethods.PaymentMethods { - if withdrawRequest.Fiat.Bank.BankName == paymentMethods.PaymentMethods[i].Name { - selectedWithdrawalMethod = paymentMethods.PaymentMethods[i] + for i := range paymentMethods { + if withdrawRequest.Fiat.Bank.BankName == paymentMethods[i].Name { + selectedWithdrawalMethod = paymentMethods[i] break } } if selectedWithdrawalMethod.ID == "" { return nil, fmt.Errorf("%w %v", errPayMethodNotFound, withdrawRequest.Fiat.Bank.BankName) } - resp, err := c.FiatTransfer(ctx, withdrawRequest.WalletID, withdrawRequest.Currency.String(), - selectedWithdrawalMethod.ID, withdrawRequest.Amount, true, FiatWithdrawal) + 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, - Status: resp.Data.Status, + ID: resp.ID, + Status: resp.Status, }, nil } @@ -801,31 +768,22 @@ func (c *CoinbasePro) GetActiveOrders(ctx context.Context, req *order.MultiOrder var respOrders []GetOrderResponse ordStatus := []string{"OPEN"} pairIDs := req.Pairs.Strings() - if len(pairIDs) == 0 { - respOrders, err = c.iterativeGetAllOrders(ctx, "", req.Type.String(), req.Side.String(), - req.AssetType.Upper(), ordStatus, 1000, req.StartTime, req.EndTime) + if len(pairIDs) == 1 { + respOrders, err = c.iterativeGetAllOrders(ctx, pairIDs[0], req.Type.String(), req.Side.String(), req.AssetType.Upper(), ordStatus, 1000, req.StartTime, req.EndTime) + } else { + respOrders, err = c.iterativeGetAllOrders(ctx, "", req.Type.String(), req.Side.String(), req.AssetType.Upper(), ordStatus, 1000, req.StartTime, req.EndTime) if err != nil { return nil, err } - } else { - for i := range pairIDs { - interResp, err := c.iterativeGetAllOrders(ctx, pairIDs[i], req.Type.String(), req.Side.String(), - req.AssetType.Upper(), ordStatus, 1000, req.StartTime, req.EndTime) - if err != nil { - return nil, err - } - respOrders = append(respOrders, interResp...) - } } orders := make([]order.Detail, len(respOrders)) for i := range respOrders { - tempPair, err := currency.NewPairFromString(respOrders[i].ProductID) - if err != nil { - return nil, err - } - orderRec := c.getOrderRespToOrderDetail(&respOrders[i], tempPair, asset.Spot) + orderRec := c.getOrderRespToOrderDetail(&respOrders[i], respOrders[i].ProductID, asset.Spot) orders[i] = *orderRec } + if len(pairIDs) > 1 { + order.FilterOrdersByPairs(&orders, req.Pairs) + } return req.Filter(c.Name, orders), nil } @@ -836,45 +794,33 @@ 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 { - p = make([]string, len(req.Pairs)) - for i := range req.Pairs { - req.Pairs[i], err = c.FormatExchangeCurrency(req.Pairs[i], req.AssetType) - if err != nil { - return nil, err - } - 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) - if err != nil { - return nil, err - } - ord = append(ord, interOrd...) - interOrd, err = c.iterativeGetAllOrders(ctx, p[i], req.Type.String(), req.Side.String(), - req.AssetType.Upper(), openStatus, 2<<30-1, req.StartTime, req.EndTime) + var p string + if len(req.Pairs) == 1 { + req.Pairs[0], err = c.FormatExchangeCurrency(req.Pairs[0], req.AssetType) if err != nil { return nil, err } - ord = append(ord, interOrd...) + p = req.Pairs[0].String() + } + var ord []GetOrderResponse + interOrd, err := c.iterativeGetAllOrders(ctx, p, req.Type.String(), req.Side.String(), req.AssetType.Upper(), closedStatuses, ManyOrds, req.StartTime, req.EndTime) + if err != nil { + return nil, err + } + ord = append(ord, interOrd...) + interOrd, err = c.iterativeGetAllOrders(ctx, p, req.Type.String(), req.Side.String(), req.AssetType.Upper(), openStatus, ManyOrds, req.StartTime, req.EndTime) + if err != nil { + return nil, err } + ord = append(ord, interOrd...) orders := make([]order.Detail, len(ord)) for i := range ord { - tempPair, err := currency.NewPairFromString(ord[i].ProductID) - if err != nil { - return nil, err - } - singleOrder := c.getOrderRespToOrderDetail(&ord[i], tempPair, req.AssetType) + singleOrder := c.getOrderRespToOrderDetail(&ord[i], ord[i].ProductID, req.AssetType) orders[i] = *singleOrder } + if len(req.Pairs) > 1 { + order.FilterOrdersByPairs(&orders, req.Pairs) + } return req.Filter(c.Name, orders), nil } @@ -908,9 +854,7 @@ func (c *CoinbasePro) GetHistoricCandlesExtended(ctx context.Context, pair curre } var timeSeries []kline.Candle for x := range req.RangeHolder.Ranges { - hist, err := c.candleHelper(ctx, req.RequestFormatted.String(), interval, - req.RangeHolder.Ranges[x].Start.Time.Add(-time.Nanosecond), - req.RangeHolder.Ranges[x].End.Time.Add(-time.Nanosecond), verified) + hist, err := c.candleHelper(ctx, req.RequestFormatted.String(), interval, req.RangeHolder.Ranges[x].Start.Time.Add(-time.Nanosecond), req.RangeHolder.Ranges[x].End.Time.Add(-time.Nanosecond), verified) if err != nil { return nil, err } @@ -932,7 +876,7 @@ func (c *CoinbasePro) GetServerTime(ctx context.Context, _ asset.Item) (time.Tim if err != nil { return time.Time{}, err } - return st.Data.ISO, nil + return st.ISO, nil } // GetLatestFundingRates returns the latest funding rates data @@ -953,17 +897,14 @@ func (c *CoinbasePro) GetLatestFundingRates(ctx context.Context, r *fundingrate. } funding := make([]fundingrate.LatestRateResponse, len(products.Products)) for i := perpStart; i < len(products.Products); i++ { - pair, err := currency.NewPairFromString(products.Products[i].ID) - if err != nil { - return nil, err - } - funRate := fundingrate.Rate{Time: products.Products[i].FutureProductDetails.PerpetualDetails.FundingTime, + 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, - Pair: pair, + Pair: products.Products[i].ID, LatestRate: funRate, TimeChecked: time.Now(), } @@ -989,21 +930,18 @@ func (c *CoinbasePro) GetFuturesContractDetails(ctx context.Context, item asset. } contracts := make([]futures.Contract, 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, + funRate := fundingrate.Rate{ + Time: products.Products[i].FutureProductDetails.PerpetualDetails.FundingTime, Rate: decimal.NewFromFloat(products.Products[i].FutureProductDetails.PerpetualDetails.FundingRate.Float64()), } contracts[i] = futures.Contract{ Exchange: c.Name, - Name: pair, + Name: products.Products[i].ID, Asset: item, EndDate: products.Products[i].FutureProductDetails.ContractExpiry, IsActive: !products.Products[i].IsDisabled, Status: products.Products[i].Status, - SettlementCurrencies: []currency.Code{currency.NewCode(products.Products[i].QuoteCurrencyID)}, + SettlementCurrencies: currency.Currencies{products.Products[i].QuoteCurrencyID}, Multiplier: products.Products[i].BaseIncrement.Float64(), LatestRate: funRate, } @@ -1023,31 +961,24 @@ func (c *CoinbasePro) UpdateOrderExecutionLimits(ctx context.Context, a asset.It if err != nil { return err } - aString := a.Upper() - if len(aString) == 7 { - aString = aString[:6] - } + aString := FormatAssetOutbound(a) if verified { - data, err = c.GetAllProducts(ctx, 2<<30-1, 0, aString, "", "", nil, true) + data, err = c.GetAllProducts(ctx, 0, 0, aString, "", "", nil, true) if err != nil { log.Warnf(log.ExchangeSys, warnAuth, err) verified = false } } if !verified { - data, err = c.GetAllProducts(ctx, 2<<30-1, 0, aString, "", "", nil, false) + data, err = c.GetAllProducts(ctx, 0, 0, aString, "", "", nil, false) 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, + Pair: data.Products[i].ID, Asset: a, MinPrice: data.Products[i].QuoteMinSize.Float64(), MaxPrice: data.Products[i].QuoteMaxSize.Float64(), @@ -1064,18 +995,28 @@ func (c *CoinbasePro) UpdateOrderExecutionLimits(ctx context.Context, a asset.It return c.LoadLimits(limits) } +// GetCurrencyTradeURL returns the URL to the exchange's trade page for the given asset and currency pair +func (c *CoinbasePro) GetCurrencyTradeURL(_ context.Context, a asset.Item, cp currency.Pair) (string, error) { + _, err := c.CurrencyPairs.IsPairEnabled(cp, a) + if err != nil { + return "", err + } + cp.Delimiter = currency.DashDelimiter + return tradeBaseURL + cp.Upper().String(), nil +} + // 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, verified bool) (*AllProducts, int, error) { - products, err := c.GetAllProducts(ctx, 2<<30-1, 0, "FUTURE", "", "", nil, verified) + products, err := c.GetAllProducts(ctx, 0, 0, "FUTURE", "", "", nil, verified) if err != nil { if verified { return c.fetchFutures(ctx, false) } return nil, 0, err } - products2, err := c.GetAllProducts(ctx, 2<<30-1, 0, "FUTURE", "PERPETUAL", "", nil, verified) + products2, err := c.GetAllProducts(ctx, 0, 0, "FUTURE", "PERPETUAL", "", nil, verified) if err != nil { if verified { return c.fetchFutures(ctx, false) @@ -1142,8 +1083,7 @@ func (c *CoinbasePro) iterativeGetAllOrders(ctx context.Context, productID, orde productType = "FUTURE" } for hasNext { - interResp, err := c.GetAllOrders(ctx, productID, "", orderType, orderSide, cursor, productType, "", "", "", - orderStatus, nil, limit, startDate, endDate) + interResp, err := c.GetAllOrders(ctx, productID, "", orderType, orderSide, cursor, productType, "", "", "", orderStatus, nil, limit, startDate, endDate) if err != nil { return nil, err } @@ -1156,7 +1096,7 @@ func (c *CoinbasePro) iterativeGetAllOrders(ctx context.Context, productID, orde // formatExchangeKlineIntervalV3 is a helper function used in GetHistoricCandles and GetHistoricCandlesExtended // to convert kline.Interval to the string format used by V3 of Coinbase's API -func formatExchangeKlineIntervalV3(interval kline.Interval) string { +func FormatExchangeKlineIntervalV3(interval kline.Interval) string { switch interval { case kline.OneMin: return granOneMin @@ -1282,8 +1222,7 @@ func (c *CoinbasePro) getOrderRespToOrderDetail(genOrderDetail *GetOrderResponse func (c *CoinbasePro) verificationCheck(ctx context.Context) (bool, error) { _, err := c.GetCredentials(ctx) if err != nil { - if errors.Is(err, exchange.ErrAuthenticationSupportNotEnabled) || - errors.Is(err, exchange.ErrCredentialsAreEmpty) { + if errors.Is(err, exchange.ErrAuthenticationSupportNotEnabled) || errors.Is(err, exchange.ErrCredentialsAreEmpty) { return false, nil } return false, err @@ -1302,8 +1241,7 @@ func (c *CoinbasePro) tickerHelper(ctx context.Context, name string, assetType a ExchangeName: c.Name, AssetType: assetType, } - var ticks *Ticker - ticks, err = c.GetTicker(ctx, name, 1, time.Time{}, time.Time{}, verified) + ticks, err := c.GetTicker(ctx, name, 1, time.Time{}, time.Time{}, verified) if err != nil { if verified { return c.tickerHelper(ctx, name, assetType, false) @@ -1317,24 +1255,19 @@ func (c *CoinbasePro) tickerHelper(ctx context.Context, name string, assetType a newTick.Last = last newTick.Bid = ticks.BestBid.Float64() newTick.Ask = ticks.BestAsk.Float64() - err = ticker.ProcessTicker(newTick) - if err != nil { - return err - } - return nil + return ticker.ProcessTicker(newTick) } // CandleHelper handles calling the candle function, and doing preliminary work on the data func (c *CoinbasePro) candleHelper(ctx context.Context, pair string, granularity kline.Interval, start, end time.Time, verified bool) ([]kline.Candle, error) { - var timeSeries []kline.Candle - history, err := c.GetHistoricRates(ctx, pair, formatExchangeKlineIntervalV3(granularity), start, end, verified) + history, err := c.GetHistoricRates(ctx, pair, FormatExchangeKlineIntervalV3(granularity), start, end, verified) if err != nil { if verified { return c.candleHelper(ctx, pair, granularity, start, end, false) } return nil, err } - timeSeries = make([]kline.Candle, len(history)) + timeSeries := make([]kline.Candle, len(history)) for x := range history { timeSeries[x] = kline.Candle{ Time: history[x].Start.Time(), @@ -1348,12 +1281,18 @@ func (c *CoinbasePro) candleHelper(ctx context.Context, pair string, granularity return timeSeries, nil } -// GetCurrencyTradeURL returns the URL to the exchange's trade page for the given asset and currency pair -func (c *CoinbasePro) GetCurrencyTradeURL(_ context.Context, a asset.Item, cp currency.Pair) (string, error) { - _, err := c.CurrencyPairs.IsPairEnabled(cp, a) - if err != nil { - return "", err +// XOR's the current time with the amount to cheaply generate an idempotency token where unwanted collisions should be rare +func generateIdempotency(am float64) string { + t := time.Now().UnixNano() + u := math.Float64bits(am) + t ^= int64(u) + return strconv.FormatInt(t, 10) +} + +// Formats asset items for outbound requests +func FormatAssetOutbound(a asset.Item) string { + if a == asset.Futures { + return "FUTURE" } - cp.Delimiter = currency.DashDelimiter - return tradeBaseURL + cp.Upper().String(), nil + return a.Upper() } diff --git a/exchanges/exchange.go b/exchanges/exchange.go index 5815eb58a0b..80e699fc7ca 100644 --- a/exchanges/exchange.go +++ b/exchanges/exchange.go @@ -82,8 +82,7 @@ func (b *Base) SetClientProxyAddress(addr string) error { } proxy, err := url.Parse(addr) if err != nil { - return fmt.Errorf("%w %w", - ErrSettingProxyAddress, err) + return fmt.Errorf("%w %w", ErrSettingProxyAddress, err) } err = b.Requester.SetProxy(proxy) diff --git a/exchanges/stream/buffer/buffer.go b/exchanges/stream/buffer/buffer.go index 9ae782645fe..d2ce8a9c1ac 100644 --- a/exchanges/stream/buffer/buffer.go +++ b/exchanges/stream/buffer/buffer.go @@ -312,9 +312,6 @@ func (w *Orderbook) LoadSnapshot(book *orderbook.Base) error { w.mtx.Lock() defer w.mtx.Unlock() - if w.ob == nil { - w.ob = make(map[key.PairAsset]*orderbookHolder) - } holder, ok := w.ob[key.PairAsset{Base: book.Pair.Base.Item, Quote: book.Pair.Quote.Item, Asset: book.Asset}] if !ok { // Associate orderbook pointer with local exchange depth map From c88b0574e4ea243953e7ad0f4cb51ea22fb5c2bd Mon Sep 17 00:00:00 2001 From: Samuel Reid <43227667+cranktakular@users.noreply.github.com> Date: Mon, 30 Sep 2024 09:34:05 +1000 Subject: [PATCH 69/79] (L/N)i(n/)t --- exchanges/coinbasepro/coinbasepro.go | 4 ++-- exchanges/coinbasepro/coinbasepro_test.go | 4 ++-- exchanges/coinbasepro/coinbasepro_types.go | 4 ++-- exchanges/coinbasepro/coinbasepro_wrapper.go | 18 +++++++++--------- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/exchanges/coinbasepro/coinbasepro.go b/exchanges/coinbasepro/coinbasepro.go index e94c6e66ded..1e6a30bd3fb 100644 --- a/exchanges/coinbasepro/coinbasepro.go +++ b/exchanges/coinbasepro/coinbasepro.go @@ -97,8 +97,8 @@ const ( warnSequenceIssue = "Out of order sequence number. Received %v, expected %v" warnAuth = "%v authenticated request failed, attempting unauthenticated" - ManyFills = 65535 - ManyOrds = 2147483647 + manyFills = 65535 + manyOrds = 2147483647 ) // Constants defining whether a transfer is a deposit or withdrawal, used to simplify diff --git a/exchanges/coinbasepro/coinbasepro_test.go b/exchanges/coinbasepro/coinbasepro_test.go index eaf0a35e2d2..9114c7cde42 100644 --- a/exchanges/coinbasepro/coinbasepro_test.go +++ b/exchanges/coinbasepro/coinbasepro_test.go @@ -575,7 +575,7 @@ func TestGetPaymentMethodByID(t *testing.T) { sharedtestvalues.SkipTestIfCredentialsUnset(t, c) pmID, err := c.GetAllPaymentMethods(context.Background()) assert.NoError(t, err) - if pmID == nil || len(pmID) == 0 { + if len(pmID) == 0 { t.Skip(skipPayMethodNotFound) } resp, err := c.GetPaymentMethodByID(context.Background(), pmID[0].ID) @@ -1832,7 +1832,7 @@ func transferTestHelper(t *testing.T, wallets *GetAllWalletsResponse) (srcWallet } pmID, err := c.GetAllPaymentMethods(context.Background()) assert.NoError(t, err) - if pmID == nil || len(pmID) == 0 { + if len(pmID) == 0 { t.Skip(skipPayMethodNotFound) } return srcWalletID, pmID[0].ID diff --git a/exchanges/coinbasepro/coinbasepro_types.go b/exchanges/coinbasepro/coinbasepro_types.go index 821bd2b980f..ce5116502dd 100644 --- a/exchanges/coinbasepro/coinbasepro_types.go +++ b/exchanges/coinbasepro/coinbasepro_types.go @@ -761,8 +761,8 @@ type UnixTimestampMilli time.Time // ServerTimeV3 holds information on the server's time, returned by GetV3Time type ServerTimeV3 struct { Iso time.Time `json:"iso"` - EpochSeconds UnixTimestamp `json:"epochSeconds,string"` - EpochMilliseconds UnixTimestampMilli `json:"epochMillis,string"` + EpochSeconds UnixTimestamp `json:"epochSeconds"` + EpochMilliseconds UnixTimestampMilli `json:"epochMillis"` } // PaymentMethodData is a sub-type that holds information on a payment method diff --git a/exchanges/coinbasepro/coinbasepro_wrapper.go b/exchanges/coinbasepro/coinbasepro_wrapper.go index e3c1da97e94..deb81344a28 100644 --- a/exchanges/coinbasepro/coinbasepro_wrapper.go +++ b/exchanges/coinbasepro/coinbasepro_wrapper.go @@ -625,13 +625,13 @@ func (c *CoinbasePro) GetOrderInfo(ctx context.Context, orderID string, pair cur return nil, err } response := c.getOrderRespToOrderDetail(genOrderDetail, pair, assetItem) - fillData, err := c.GetFills(ctx, orderID, "", "", time.Time{}, time.Now(), ManyFills) + fillData, err := c.GetFills(ctx, orderID, "", "", time.Time{}, time.Now(), manyFills) if err != nil { return nil, err } cursor := fillData.Cursor for cursor != "" { - tempFillData, err := c.GetFills(ctx, orderID, "", cursor, time.Time{}, time.Now(), ManyFills) + tempFillData, err := c.GetFills(ctx, orderID, "", cursor, time.Time{}, time.Now(), manyFills) if err != nil { return nil, err } @@ -772,9 +772,9 @@ func (c *CoinbasePro) GetActiveOrders(ctx context.Context, req *order.MultiOrder respOrders, err = c.iterativeGetAllOrders(ctx, pairIDs[0], req.Type.String(), req.Side.String(), req.AssetType.Upper(), ordStatus, 1000, req.StartTime, req.EndTime) } else { respOrders, err = c.iterativeGetAllOrders(ctx, "", req.Type.String(), req.Side.String(), req.AssetType.Upper(), ordStatus, 1000, req.StartTime, req.EndTime) - if err != nil { - return nil, err - } + } + if err != nil { + return nil, err } orders := make([]order.Detail, len(respOrders)) for i := range respOrders { @@ -803,12 +803,12 @@ func (c *CoinbasePro) GetOrderHistory(ctx context.Context, req *order.MultiOrder p = req.Pairs[0].String() } var ord []GetOrderResponse - interOrd, err := c.iterativeGetAllOrders(ctx, p, req.Type.String(), req.Side.String(), req.AssetType.Upper(), closedStatuses, ManyOrds, req.StartTime, req.EndTime) + interOrd, err := c.iterativeGetAllOrders(ctx, p, req.Type.String(), req.Side.String(), req.AssetType.Upper(), closedStatuses, manyOrds, req.StartTime, req.EndTime) if err != nil { return nil, err } ord = append(ord, interOrd...) - interOrd, err = c.iterativeGetAllOrders(ctx, p, req.Type.String(), req.Side.String(), req.AssetType.Upper(), openStatus, ManyOrds, req.StartTime, req.EndTime) + interOrd, err = c.iterativeGetAllOrders(ctx, p, req.Type.String(), req.Side.String(), req.AssetType.Upper(), openStatus, manyOrds, req.StartTime, req.EndTime) if err != nil { return nil, err } @@ -1094,7 +1094,7 @@ func (c *CoinbasePro) iterativeGetAllOrders(ctx context.Context, productID, orde return resp, nil } -// formatExchangeKlineIntervalV3 is a helper function used in GetHistoricCandles and GetHistoricCandlesExtended +// FormatExchangeKlineIntervalV3 is a helper function used in GetHistoricCandles and GetHistoricCandlesExtended // to convert kline.Interval to the string format used by V3 of Coinbase's API func FormatExchangeKlineIntervalV3(interval kline.Interval) string { switch interval { @@ -1289,7 +1289,7 @@ func generateIdempotency(am float64) string { return strconv.FormatInt(t, 10) } -// Formats asset items for outbound requests +// FormatAssetOutbound formats asset items for outbound requests func FormatAssetOutbound(a asset.Item) string { if a == asset.Futures { return "FUTURE" From 59744219d7d40d39bd32f624edcb054210c978b1 Mon Sep 17 00:00:00 2001 From: Samuel Reid <43227667+cranktakular@users.noreply.github.com> Date: Wed, 16 Oct 2024 14:49:55 +1100 Subject: [PATCH 70/79] Adding a test --- exchanges/coinbasepro/coinbasepro_test.go | 43 +++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/exchanges/coinbasepro/coinbasepro_test.go b/exchanges/coinbasepro/coinbasepro_test.go index c2e732c93b9..ef06fc3aa3b 100644 --- a/exchanges/coinbasepro/coinbasepro_test.go +++ b/exchanges/coinbasepro/coinbasepro_test.go @@ -1372,6 +1372,49 @@ func TestUpdateOrderExecutionLimits(t *testing.T) { assert.NoError(t, err) } +func TestGetOrderRespToOrderDetail(t *testing.T) { + t.Parallel() + mockData := &GetOrderResponse{ + OrderConfiguration: OrderConfiguration{ + MarketMarketIOC: &MarketMarketIOC{}, + LimitLimitGTC: &LimitLimitGTC{}, + LimitLimitGTD: &LimitLimitGTD{}, + StopLimitStopLimitGTC: &StopLimitStopLimitGTC{}, + StopLimitStopLimitGTD: &StopLimitStopLimitGTD{}, + }, + SizeInQuote: false, + Side: "BUY", + Status: "OPEN", + Settled: true, + EditHistory: []EditHistory{(EditHistory{})}, + } + resp := c.getOrderRespToOrderDetail(mockData, testPair, asset.Spot) + expected := &order.Detail{ImmediateOrCancel: true, Exchange: "CoinbasePro", Type: 0x40, Side: 0x2, Status: 0x8000, AssetType: 0x2, Date: time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC), CloseTime: time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC), LastUpdated: time.Date(1, time.January, 1, 0, 0, 0, 0, time.UTC), Pair: testPair} + assert.EqualValues(t, expected, resp) + mockData.Side = "SELL" + mockData.Status = "FILLED" + resp = c.getOrderRespToOrderDetail(mockData, testPair, asset.Spot) + expected.Side = 0x4 + expected.Status = 0x80 + assert.EqualValues(t, expected, resp) + mockData.Status = "CANCELLED" + resp = c.getOrderRespToOrderDetail(mockData, testPair, asset.Spot) + expected.Status = 0x100 + assert.EqualValues(t, expected, resp) + mockData.Status = "EXPIRED" + resp = c.getOrderRespToOrderDetail(mockData, testPair, asset.Spot) + expected.Status = 0x2000 + assert.EqualValues(t, expected, resp) + mockData.Status = "FAILED" + resp = c.getOrderRespToOrderDetail(mockData, testPair, asset.Spot) + expected.Status = 0x1000 + assert.EqualValues(t, expected, resp) + mockData.Status = "UNKNOWN_ORDER_STATUS" + resp = c.getOrderRespToOrderDetail(mockData, testPair, asset.Spot) + expected.Status = 0x0 + assert.EqualValues(t, expected, resp) +} + func TestFiatTransferTypeString(t *testing.T) { t.Parallel() var f FiatTransferType From 2a90b5dc561533725c9d5cb8e213bd354a650cb4 Mon Sep 17 00:00:00 2001 From: Samuel Reid <43227667+cranktakular@users.noreply.github.com> Date: Wed, 16 Oct 2024 16:52:41 +1100 Subject: [PATCH 71/79] Tiny test improvement --- exchanges/coinbasepro/coinbasepro_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exchanges/coinbasepro/coinbasepro_test.go b/exchanges/coinbasepro/coinbasepro_test.go index ef06fc3aa3b..71ff5a09ab3 100644 --- a/exchanges/coinbasepro/coinbasepro_test.go +++ b/exchanges/coinbasepro/coinbasepro_test.go @@ -1092,7 +1092,7 @@ func TestUpdateOrderbook(t *testing.T) { assert.ErrorIs(t, err, asset.ErrNotSupported) _, err = c.UpdateOrderbook(context.Background(), currency.NewPairWithDelimiter("meow", "woof", "-"), asset.Spot) assert.EqualValues(t, errInvalidProductID, err.Error()) - resp, err := c.UpdateOrderbook(context.Background(), testPair, asset.Spot) + resp, err := c.UpdateOrderbook(context.Background(), testPair, asset.Futures) assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) } From 3eb15db75c91a2414585b16f2d3a57b13162c61b Mon Sep 17 00:00:00 2001 From: Samuel Reid <43227667+cranktakular@users.noreply.github.com> Date: Tue, 5 Nov 2024 12:03:07 +1100 Subject: [PATCH 72/79] Template patch testing --- exchanges/coinbasepro/coinbasepro_test.go | 9 ++++---- .../coinbasepro/coinbasepro_websocket.go | 12 +++++------ exchanges/subscription/template.go | 21 +++++++++++++------ 3 files changed, 26 insertions(+), 16 deletions(-) diff --git a/exchanges/coinbasepro/coinbasepro_test.go b/exchanges/coinbasepro/coinbasepro_test.go index 71ff5a09ab3..7bbe985e1e2 100644 --- a/exchanges/coinbasepro/coinbasepro_test.go +++ b/exchanges/coinbasepro/coinbasepro_test.go @@ -1526,10 +1526,11 @@ func TestCancelPendingFuturesSweep(t *testing.T) { func TestWsAuth(t *testing.T) { t.Parallel() p := currency.Pairs{testPair} - for _, a := range c.GetAssetTypes(true) { - require.NoError(t, c.CurrencyPairs.StorePairs(a, p, false)) - require.NoError(t, c.CurrencyPairs.StorePairs(a, p, true)) - } + // This for loop will prevent an error if the template.go file isn't patched + // for _, a := range c.GetAssetTypes(true) { + // require.NoError(t, c.CurrencyPairs.StorePairs(a, p, false)) + // require.NoError(t, c.CurrencyPairs.StorePairs(a, p, true)) + // } if c.Websocket.IsEnabled() && !c.API.AuthenticatedWebsocketSupport || !sharedtestvalues.AreAPICredentialsSet(c) { t.Skip(stream.ErrWebsocketNotEnabled.Error()) } diff --git a/exchanges/coinbasepro/coinbasepro_websocket.go b/exchanges/coinbasepro/coinbasepro_websocket.go index 8c22a8270e7..e711f00bc37 100644 --- a/exchanges/coinbasepro/coinbasepro_websocket.go +++ b/exchanges/coinbasepro/coinbasepro_websocket.go @@ -54,11 +54,11 @@ var defaultSubscriptions = subscription.List{ // Subscriptions to status return an "authentication failure" error, despite the endpoint not being authenticated // and other authenticated channels working fine. {Enabled: false, Channel: "status"}, - {Enabled: true, Asset: asset.Spot, Channel: subscription.TickerChannel}, - {Enabled: true, Asset: asset.Spot, Channel: subscription.CandlesChannel}, - {Enabled: true, Asset: asset.Spot, Channel: subscription.AllTradesChannel}, - {Enabled: true, Asset: asset.Spot, Channel: subscription.OrderbookChannel}, - {Enabled: true, Channel: subscription.MyAccountChannel, Authenticated: true}, + {Enabled: false, Asset: asset.Spot, Channel: subscription.TickerChannel}, + {Enabled: false, Asset: asset.Spot, Channel: subscription.CandlesChannel}, + {Enabled: false, Asset: asset.Spot, Channel: subscription.AllTradesChannel}, + {Enabled: false, Asset: asset.Spot, Channel: subscription.OrderbookChannel}, + {Enabled: true, Asset: asset.All, Channel: subscription.MyAccountChannel, Authenticated: true}, {Enabled: false, Asset: asset.Spot, Channel: "ticker_batch"}, /* Not Implemented: {Enabled: false, Asset: asset.Spot, Channel: "futures_balance_summary", Authenticated: true}, @@ -523,7 +523,7 @@ func base64URLEncode(b []byte) string { func (c *CoinbasePro) checkSubscriptions() { for _, s := range c.Config.Features.Subscriptions { switch s.Channel { - case "heartbeat", "level2_batch", "matches": + case "level2_batch", "matches": c.Config.Features.Subscriptions = defaultSubscriptions.Clone() c.Features.Subscriptions = c.Config.Features.Subscriptions.Enabled() return diff --git a/exchanges/subscription/template.go b/exchanges/subscription/template.go index ae994f7919d..f10b4f30291 100644 --- a/exchanges/subscription/template.go +++ b/exchanges/subscription/template.go @@ -104,12 +104,12 @@ func expandTemplate(e iExchange, s *Subscription, ap assetPairs, assets asset.It subs := List{} - if len(s.Pairs) != 0 { - // We deliberately do not check Availability of sub Pairs because users have edge cases to subscribe to non-existent pairs - for a := range ap { - ap[a] = s.Pairs - } - } + // if len(s.Pairs) != 0 { + // // We deliberately do not check Availability of sub Pairs because users have edge cases to subscribe to non-existent pairs + // for a := range ap { + // ap[a] = s.Pairs + // } + // } switch s.Asset { case asset.All: @@ -125,6 +125,15 @@ func expandTemplate(e iExchange, s *Subscription, ap assetPairs, assets asset.It } } + if len(s.Pairs) != 0 { + for a, pairs := range subCtx.AssetPairs { + if err := pairs.ContainsAll(s.Pairs, true); err != nil { //nolint:govet // Shadow, or gocritic will complain sloppyReassign + return nil, err + } + subCtx.AssetPairs[a] = s.Pairs + } + } + buf := &bytes.Buffer{} if err := t.Execute(buf, subCtx); err != nil { //nolint:govet // Shadow, or gocritic will complain sloppyReassign return nil, err From 38f27658f27964e76419094747d86f855963b20c Mon Sep 17 00:00:00 2001 From: Samuel Reid <43227667+cranktakular@users.noreply.github.com> Date: Tue, 5 Nov 2024 17:02:49 +1100 Subject: [PATCH 73/79] Fixes --- exchanges/coinbasepro/coinbasepro.go | 148 ++++++------- exchanges/coinbasepro/coinbasepro_test.go | 72 +++---- exchanges/coinbasepro/coinbasepro_types.go | 82 ++++++-- .../coinbasepro/coinbasepro_websocket.go | 197 ++++++++++++++---- exchanges/coinbasepro/coinbasepro_wrapper.go | 46 ++-- exchanges/protocol/features.go | 65 +++--- exchanges/subscription/template.go | 21 +- 7 files changed, 363 insertions(+), 268 deletions(-) diff --git a/exchanges/coinbasepro/coinbasepro.go b/exchanges/coinbasepro/coinbasepro.go index 3a294c38f7a..f9109574908 100644 --- a/exchanges/coinbasepro/coinbasepro.go +++ b/exchanges/coinbasepro/coinbasepro.go @@ -101,16 +101,13 @@ const ( manyOrds = 2147483647 ) -// Constants defining whether a transfer is a deposit or withdrawal, used to simplify -// interactions with a few endpoints +// Constants defining whether a transfer is a deposit or withdrawal, used to simplify interactions with a few endpoints const ( FiatDeposit FiatTransferType = false FiatWithdrawal FiatTransferType = true ) -// While the exchange's fee pages say the worst taker/maker fees are lower than the ones listed -// here, the data returned by the GetTransactionsSummary endpoint are consistent with these worst -// case scenarios. The best case scenarios are untested, and assumed to be in line with the fee pages +// While the exchange's fee pages say the worst taker/maker fees are lower than the ones listed here, the data returned by the GetTransactionsSummary endpoint are consistent with these worst case scenarios. The best case scenarios are untested, and assumed to be in line with the fee pages const ( WorstCaseTakerFee = 0.012 WorstCaseMakerFee = 0.006 @@ -121,40 +118,43 @@ const ( ) var ( - errAccountIDEmpty = errors.New("account id cannot be empty") - errClientOrderIDEmpty = errors.New("client order id cannot be empty") - errProductIDEmpty = errors.New("product id cannot be empty") - errOrderIDEmpty = errors.New("order ids cannot be empty") - errCancelLimitExceeded = errors.New("100 order cancel limit exceeded") - errOpenPairWithOtherTypes = errors.New("cannot pair open orders with other order types") - errSizeAndPriceZero = errors.New("size and price cannot both be 0") - errCurrWalletConflict = errors.New("exactly one of walletID and currency must be specified") - errWalletIDEmpty = errors.New("wallet id cannot be empty") - errAddressIDEmpty = errors.New("address id cannot be empty") - errTransactionTypeEmpty = errors.New("transaction type cannot be empty") - errToEmpty = errors.New("to cannot be empty") - errTransactionIDEmpty = errors.New("transaction id cannot be empty") - errPaymentMethodEmpty = errors.New("payment method cannot be empty") - errDepositIDEmpty = errors.New("deposit id cannot be empty") - errInvalidPriceType = errors.New("price type must be spot, buy, or sell") - errInvalidOrderType = errors.New("order type must be market, limit, or stop") - errEndTimeInPast = errors.New("end time cannot be in the past") - errNoMatchingWallets = errors.New("no matching wallets returned") - errOrderModFailNoRet = errors.New("order modification failed but no error returned") - errNameEmpty = errors.New("name cannot be empty") - errPortfolioIDEmpty = errors.New("portfolio id cannot be empty") - errFeeTypeNotSupported = errors.New("fee type not supported") - errCantDecodePrivKey = errors.New("cannot decode private key") - errNoWalletForCurrency = errors.New("no wallet found for currency, address creation impossible") - errChannelNameUnknown = errors.New("unknown channel name") - errNoWalletsReturned = errors.New("no wallets returned") - errPayMethodNotFound = errors.New("payment method not found") - errUnknownL2DataType = errors.New("unknown l2update data type") - errOrderFailedToCancel = errors.New("failed to cancel order") - errUnrecognisedStatusType = errors.New("unrecognised status type") - errStringConvert = errors.New("unable to convert into string value") - errFloatConvert = errors.New("unable to convert into float64 value") - errWrappedAssetEmpty = errors.New("wrapped asset cannot be empty") + errAccountIDEmpty = errors.New("account id cannot be empty") + errClientOrderIDEmpty = errors.New("client order id cannot be empty") + errProductIDEmpty = errors.New("product id cannot be empty") + errOrderIDEmpty = errors.New("order ids cannot be empty") + errCancelLimitExceeded = errors.New("100 order cancel limit exceeded") + errOpenPairWithOtherTypes = errors.New("cannot pair open orders with other order types") + errSizeAndPriceZero = errors.New("size and price cannot both be 0") + errCurrWalletConflict = errors.New("exactly one of walletID and currency must be specified") + errWalletIDEmpty = errors.New("wallet id cannot be empty") + errAddressIDEmpty = errors.New("address id cannot be empty") + errTransactionTypeEmpty = errors.New("transaction type cannot be empty") + errToEmpty = errors.New("to cannot be empty") + errTransactionIDEmpty = errors.New("transaction id cannot be empty") + errPaymentMethodEmpty = errors.New("payment method cannot be empty") + errDepositIDEmpty = errors.New("deposit id cannot be empty") + errInvalidPriceType = errors.New("price type must be spot, buy, or sell") + errInvalidOrderType = errors.New("order type must be market, limit, or stop") + errEndTimeInPast = errors.New("end time cannot be in the past") + errNoMatchingWallets = errors.New("no matching wallets returned") + errOrderModFailNoRet = errors.New("order modification failed but no error returned") + errNameEmpty = errors.New("name cannot be empty") + errPortfolioIDEmpty = errors.New("portfolio id cannot be empty") + errFeeTypeNotSupported = errors.New("fee type not supported") + errCantDecodePrivKey = errors.New("cannot decode private key") + errNoWalletForCurrency = errors.New("no wallet found for currency, address creation impossible") + errChannelNameUnknown = errors.New("unknown channel name") + errNoWalletsReturned = errors.New("no wallets returned") + errPayMethodNotFound = errors.New("payment method not found") + errUnknownL2DataType = errors.New("unknown l2update data type") + errOrderFailedToCancel = errors.New("failed to cancel order") + errUnrecognisedStatusType = errors.New("unrecognised status type") + errStringConvert = errors.New("unable to convert into string value") + errFloatConvert = errors.New("unable to convert into float64 value") + errWrappedAssetEmpty = errors.New("wrapped asset cannot be empty") + errUnrecognisedOrderType = errors.New("unrecognised order type") + errUnrecognisedAssetType = errors.New("unrecognised asset type") + errUnrecognisedStrategyType = errors.New("unrecognised strategy type") allowedGranularities = []string{granOneMin, granFiveMin, granFifteenMin, granThirtyMin, granOneHour, granTwoHour, granSixHour, granOneDay} closedStatuses = []string{"FILLED", "CANCELLED", "EXPIRED", "FAILED"} @@ -186,8 +186,7 @@ func (c *CoinbasePro) GetAccountByID(ctx context.Context, accountID string) (*Ac return &resp.Account, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, 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 +// 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) ([]ProductBook, error) { vals := url.Values{} for x := range products { @@ -260,9 +259,7 @@ func (c *CoinbasePro) GetProductByID(ctx context.Context, productID string, auth return &resp, c.SendHTTPRequest(ctx, exchange.RestSpot, path, nil, &resp) } -// 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 +// 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, authenticated bool) ([]CandleStruct, error) { if productID == "" { return nil, errProductIDEmpty @@ -286,8 +283,7 @@ func (c *CoinbasePro) GetHistoricRates(ctx context.Context, productID, granulari return resp.Candles, c.SendHTTPRequest(ctx, exchange.RestSpot, path, vals, &resp) } -// GetTicker returns snapshot information about the last trades (ticks) and best bid/ask. -// Contrary to documentation, this does not tell you the 24h volume +// GetTicker returns snapshot information about the last trades (ticks) and best bid/ask. Contrary to documentation, this does not tell you the 24h volume func (c *CoinbasePro) GetTicker(ctx context.Context, productID string, limit uint16, startDate, endDate time.Time, authenticated bool) (*Ticker, error) { if productID == "" { return nil, errProductIDEmpty @@ -354,8 +350,7 @@ func (c *CoinbasePro) CancelOrders(ctx context.Context, orderIDs []string) ([]Or 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 +// 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) (bool, error) { if orderID == "" { return false, errOrderIDEmpty @@ -374,8 +369,7 @@ func (c *CoinbasePro) EditOrder(ctx context.Context, orderID string, size, price return resp.Success, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, path, nil, req, true, &resp, nil) } -// EditOrderPreview simulates an edit order request, to preview the result. Only limit orders with a -// good-till-cancelled time in force can be edited. +// EditOrderPreview simulates an edit order request, to preview the result. Only limit orders with a good-till-cancelled time in force can be edited. func (c *CoinbasePro) EditOrderPreview(ctx context.Context, orderID string, size, price float64) (*EditOrderPreviewResp, error) { if orderID == "" { return nil, errOrderIDEmpty @@ -599,8 +593,7 @@ func (c *CoinbasePro) EditPortfolio(ctx context.Context, portfolioID, name strin return &resp.Portfolio, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPut, path, nil, req, true, &resp, nil) } -// GetFuturesBalanceSummary returns information on balances related to Coinbase Financial Markets -// futures trading +// GetFuturesBalanceSummary returns information on balances related to Coinbase Financial Markets futures trading func (c *CoinbasePro) GetFuturesBalanceSummary(ctx context.Context) (*FuturesBalanceSummary, error) { resp := struct { BalanceSummary FuturesBalanceSummary `json:"balance_summary"` @@ -628,11 +621,7 @@ func (c *CoinbasePro) GetFuturesPositionByID(ctx context.Context, productID stri return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, nil, true, &resp, nil) } -// ScheduleFuturesSweep schedules a sweep of funds from a CFTC-regulated futures account to a -// Coinbase USD Spot wallet. Request submitted before 5 pm ET are processed the following -// 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 +// ScheduleFuturesSweep schedules a sweep of funds from a CFTC-regulated futures account to a Coinbase USD Spot wallet. Request submitted before 5 pm ET are processed the following 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) (bool, error) { path := coinbaseV3 + coinbaseCFM + "/" + coinbaseSweeps + "/" + coinbaseSchedule var req map[string]any @@ -721,8 +710,7 @@ func (c *CoinbasePro) GetPerpetualsPositionByID(ctx context.Context, portfolioID return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, nil, true, &resp, nil) } -// GetTransactionSummary returns a summary of transactions with fee tiers, total volume, -// and fees +// 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.Values = url.Values{} @@ -743,8 +731,7 @@ func (c *CoinbasePro) GetTransactionSummary(ctx context.Context, startDate, endD return &resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, 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 +// 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) { if from == "" || to == "" { return nil, errAccountIDEmpty @@ -767,8 +754,7 @@ func (c *CoinbasePro) CreateConvertQuote(ctx context.Context, from, to, userInce return &resp.Trade, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, path, nil, req, true, &resp, nil) } -// CommitConvertTrade commits a conversion between two currencies, using the trade_id returned -// from CreateConvertQuote +// 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) { if tradeID == "" { return nil, errTransactionIDEmpty @@ -820,8 +806,7 @@ func (c *CoinbasePro) GetAllPaymentMethods(ctx context.Context) ([]PaymentMethod return resp.PaymentMethods, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, req, true, &resp, nil) } -// GetPaymentMethodByID returns information on a single payment method associated with the user's -// account +// GetPaymentMethodByID returns information on a single payment method associated with the user's account func (c *CoinbasePro) GetPaymentMethodByID(ctx context.Context, paymentMethodID string) (*PaymentMethodData, error) { if paymentMethodID == "" { return nil, errPaymentMethodEmpty @@ -850,8 +835,7 @@ func (c *CoinbasePro) GetAllWallets(ctx context.Context, pag PaginationInp) (*Ge return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, coinbaseV2+coinbaseAccounts, params.Values, nil, false, &resp, nil) } -// GetWalletByID returns information about a single wallet. In lieu of a wallet ID, -// a currency can be provided to get the primary account for that currency +// GetWalletByID returns information about a single wallet. In lieu of a wallet ID, a currency can be provided to get the primary account for that currency func (c *CoinbasePro) GetWalletByID(ctx context.Context, walletID, currency string) (*WalletData, error) { if (walletID == "" && currency == "") || (walletID != "" && currency != "") { return nil, errCurrWalletConflict @@ -926,12 +910,7 @@ func (c *CoinbasePro) GetAddressTransactions(ctx context.Context, walletID, addr return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, params.Values, nil, false, &resp, nil) } -// SendMoney can send funds to an email or cryptocurrency address (if "traType" is set to "send"), -// or to another one of the user's wallets or vaults (if "traType" is set to "transfer"). Coinbase -// may delay or cancel the transaction at their discretion. The "idem" parameter is an optional -// string for idempotency; a token with a max length of 100 characters, if a previous -// transaction included the same token as a parameter, the new transaction won't be processed, -// and information on the previous transaction will be returned instead +// SendMoney can send funds to an email or cryptocurrency address (if "traType" is set to "send"), or to another one of the user's wallets or vaults (if "traType" is set to "transfer"). Coinbase may delay or cancel the transaction at their discretion. The "idem" parameter is an optional string for idempotency; a token with a max length of 100 characters, if a previous transaction included the same token as a parameter, the new transaction won't be processed, and information on the previous transaction will be returned instead func (c *CoinbasePro) SendMoney(ctx context.Context, traType, walletID, to, cur, description, idem, financialInstitutionWebsite, destinationTag string, amount float64, skipNotifications, toFinancialInstitution bool) (*TransactionData, error) { if traType == "" { return nil, errTransactionTypeEmpty @@ -979,8 +958,7 @@ func (c *CoinbasePro) GetAllTransactions(ctx context.Context, walletID string, p return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, params.Values, nil, false, &resp, nil) } -// GetTransactionByID returns information on a single transaction associated with the -// specified wallet +// GetTransactionByID returns information on a single transaction associated with the specified wallet func (c *CoinbasePro) GetTransactionByID(ctx context.Context, walletID, transactionID string) (*TransactionData, error) { if walletID == "" { return nil, errWalletIDEmpty @@ -995,9 +973,7 @@ func (c *CoinbasePro) GetTransactionByID(ctx context.Context, walletID, transact return &resp.Data, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, nil, nil, false, &resp, nil) } -// FiatTransfer prepares and optionally processes a transfer of funds between the exchange and a -// fiat payment method. "Deposit" signifies funds going from exchange to bank, "withdraw" -// signifies funds going from bank to exchange +// FiatTransfer prepares and optionally processes a transfer of funds between the exchange and a fiat payment method. "Deposit" signifies funds going from exchange to bank, "withdraw" signifies funds going from bank to exchange func (c *CoinbasePro) FiatTransfer(ctx context.Context, walletID, cur, paymentMethod string, amount float64, commit bool, transferType FiatTransferType) (*DeposWithdrData, error) { if walletID == "" { return nil, errWalletIDEmpty @@ -1029,8 +1005,7 @@ func (c *CoinbasePro) FiatTransfer(ctx context.Context, walletID, cur, paymentMe return &resp.Data, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, path, nil, req, false, &resp, nil) } -// CommitTransfer processes a deposit/withdrawal that was created with the "commit" parameter set -// to false +// CommitTransfer processes a deposit/withdrawal that was created with the "commit" parameter set to false func (c *CoinbasePro) CommitTransfer(ctx context.Context, walletID, depositID string, transferType FiatTransferType) (*DeposWithdrData, error) { if walletID == "" { return nil, errWalletIDEmpty @@ -1051,8 +1026,7 @@ func (c *CoinbasePro) CommitTransfer(ctx context.Context, walletID, depositID st return &resp.Data, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodPost, path, nil, nil, false, &resp, nil) } -// GetAllFiatTransfers returns a list of transfers either to or from fiat payment methods and -// the specified wallet +// 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) { if walletID == "" { return nil, errWalletIDEmpty @@ -1116,8 +1090,7 @@ func (c *CoinbasePro) GetCryptocurrencies(ctx context.Context) ([]CryptoData, er return resp.Data, c.SendHTTPRequest(ctx, exchange.RestSpot, path, nil, &resp) } -// GetExchangeRates returns exchange rates for the specified currency. If none is specified, -// it defaults to USD +// 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) { resp := struct { Data GetExchangeRatesResp `json:"data"` @@ -1127,8 +1100,7 @@ func (c *CoinbasePro) GetExchangeRates(ctx context.Context, currency string) (*G return &resp.Data, c.SendHTTPRequest(ctx, exchange.RestSpot, coinbaseV2+coinbaseExchangeRates, vals, &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 +// 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 path string switch priceType { @@ -1194,9 +1166,7 @@ func (c *CoinbasePro) GetPairDetails(ctx context.Context, pair string) (*PairDat return resp, c.SendHTTPRequest(ctx, exchange.RestSpotSupplementary, path, nil, &resp) } -// GetProductBookV1 returns the order book for the specified currency pair. Level 1 only returns the best bids and asks, -// Level 2 returns the full order book with orders at the same price aggregated, Level 3 returns the full -// non-aggregated order book. +// GetProductBookV1 returns the order book for the specified currency pair. Level 1 only returns the best bids and asks, Level 2 returns the full order book with orders at the same price aggregated, Level 3 returns the full non-aggregated order book. func (c *CoinbasePro) GetProductBookV1(ctx context.Context, pair string, level uint8) (*OrderBook, error) { if pair == "" { return nil, currency.ErrCurrencyPairEmpty diff --git a/exchanges/coinbasepro/coinbasepro_test.go b/exchanges/coinbasepro/coinbasepro_test.go index 7bbe985e1e2..c1361835e22 100644 --- a/exchanges/coinbasepro/coinbasepro_test.go +++ b/exchanges/coinbasepro/coinbasepro_test.go @@ -1524,13 +1524,8 @@ func TestCancelPendingFuturesSweep(t *testing.T) { // TestWsAuth dials websocket, sends login request. func TestWsAuth(t *testing.T) { - t.Parallel() + // t.Parallel() p := currency.Pairs{testPair} - // This for loop will prevent an error if the template.go file isn't patched - // for _, a := range c.GetAssetTypes(true) { - // require.NoError(t, c.CurrencyPairs.StorePairs(a, p, false)) - // require.NoError(t, c.CurrencyPairs.StorePairs(a, p, true)) - // } if c.Websocket.IsEnabled() && !c.API.AuthenticatedWebsocketSupport || !sharedtestvalues.AreAPICredentialsSet(c) { t.Skip(stream.ErrWebsocketNotEnabled.Error()) } @@ -1555,33 +1550,19 @@ func TestWsAuth(t *testing.T) { timer.Stop() } -func TestStatusToStandardStatus(t *testing.T) { - t.Parallel() - type TestCases struct { - Case string - Result order.Status - } - testCases := []TestCases{ - {Case: "received", Result: order.New}, - {Case: "open", Result: order.Active}, - {Case: "done", Result: order.Filled}, - {Case: "match", Result: order.PartiallyFilled}, - {Case: "change", Result: order.Active}, - {Case: "activate", Result: order.Active}, - {Case: "LOL", Result: order.UnknownStatus}, - } - for i := range testCases { - result, _ := statusToStandardStatus(testCases[i].Case) - if result != testCases[i].Result { - t.Errorf("Expected: %v, received: %v", testCases[i].Result, result) - } - } -} - func TestWsHandleData(t *testing.T) { + done := make(chan struct{}) + t.Cleanup(func() { + close(done) + }) go func() { - for range c.Websocket.DataHandler { - continue + for { + select { + case <-c.Websocket.DataHandler: + continue + case <-done: + return + } } }() mockJSON := []byte(`{"type": "error"}`) @@ -1650,16 +1631,7 @@ func TestWsHandleData(t *testing.T) { mockJSON = []byte(`{"sequence_num": 0, "channel": "user", "events": ["type": ""}]}`) _, err = c.wsHandleData(mockJSON, 0) assert.ErrorAs(t, err, &targetErr) - mockJSON = []byte(`{"sequence_num": 0, "channel": "user", "events": [{"type": "moo", "orders": [{"total_fees": "1.1"}]}]}`) - _, err = c.wsHandleData(mockJSON, 0) - assert.ErrorIs(t, err, order.ErrUnrecognisedOrderType) - mockJSON = []byte(`{"sequence_num": 0, "channel": "user", "events": [{"type": "moo", "orders": [{"total_fees": "1.1", "order_type": "ioc"}]}]}`) - _, err = c.wsHandleData(mockJSON, 0) - assert.ErrorIs(t, err, order.ErrSideIsInvalid) - mockJSON = []byte(`{"sequence_num": 0, "channel": "user", "events": [{"type": "moo", "orders": [{"total_fees": "1.1", "order_type": "ioc", "order_side": "buy"}]}]}`) - _, err = c.wsHandleData(mockJSON, 0) - assert.ErrorIs(t, err, errUnrecognisedStatusType) - mockJSON = []byte(`{"sequence_num": 0, "channel": "user", "events": [{"type": "moo", "orders": [{"total_fees": "1.1", "order_type": "ioc", "order_side": "buy", "status": "done"}]}]}`) + mockJSON = []byte(`{"sequence_num": 0, "channel": "user", "events": [{"type": "moo", "orders": [{"limit_price": "2.2", "total_fees": "1.1"}], "positions": {"perpetual_futures_positions": [{"margin_type": "fakeMarginType"}], "expiring_futures_positions": [{}]}}]}`) _, err = c.wsHandleData(mockJSON, 0) assert.NoError(t, err) mockJSON = []byte(`{"sequence_num": 0, "channel": "fakechan", "events": ["type": ""}]}`) @@ -1688,14 +1660,26 @@ func TestGenerateSubscriptions(t *testing.T) { log.Fatal(err) } c.Websocket.SetCanUseAuthenticatedEndpoints(true) - p, err := c.GetEnabledPairs(asset.Spot) + p1, err := c.GetEnabledPairs(asset.Spot) + require.NoError(t, err) + p2, err := c.GetEnabledPairs(asset.Futures) require.NoError(t, err) exp := subscription.List{} for _, baseSub := range defaultSubscriptions.Enabled() { s := baseSub.Clone() s.QualifiedChannel = subscriptionNames[s.Channel] - if s.Asset != asset.Empty { - s.Pairs = p + switch s.Asset { + case asset.Spot: + s.Pairs = p1 + case asset.Futures: + s.Pairs = p2 + case asset.All: + s2 := s.Clone() + s2.Asset = asset.Futures + s2.Pairs = p2 + exp = append(exp, s2) + s.Asset = asset.Spot + s.Pairs = p1 } exp = append(exp, s) } diff --git a/exchanges/coinbasepro/coinbasepro_types.go b/exchanges/coinbasepro/coinbasepro_types.go index 3e9c59dd673..bbed46b1824 100644 --- a/exchanges/coinbasepro/coinbasepro_types.go +++ b/exchanges/coinbasepro/coinbasepro_types.go @@ -1256,23 +1256,79 @@ type WebsocketOrderbookDataHolder struct { // WebsocketOrderData defines a websocket order response, used in WebsocketOrderDataHolder type WebsocketOrderData struct { - OrderID string `json:"order_id"` - ClientOrderID string `json:"client_order_id"` - CumulativeQuantity float64 `json:"cumulative_quantity,string"` - LeavesQuantity float64 `json:"leaves_quantity,string"` - AveragePrice float64 `json:"avg_price,string"` - TotalFees float64 `json:"total_fees,string"` - Status string `json:"status"` - ProductID currency.Pair `json:"product_id"` - CreationTime time.Time `json:"creation_time"` - OrderSide string `json:"order_side"` - OrderType string `json:"order_type"` + AveragePrice float64 `json:"avg_price,string"` + CancelReason string `json:"cancel_reason"` + ClientOrderID string `json:"client_order_id"` + CompletionPercentage float64 `json:"completion_percentage,string"` + ContractExpiryType string `json:"contract_expiry_type"` + CumulativeQuantity float64 `json:"cumulative_quantity,string"` + FilledValue float64 `json:"filled_value,string"` + LeavesQuantity float64 `json:"leaves_quantity,string"` + LimitPrice float64 `json:"limit_price,string"` + NumberOfFills int64 `json:"number_of_fills"` + OrderID string `json:"order_id"` + OrderSide string `json:"order_side"` + OrderType string `json:"order_type"` + OutstandingHoldAmount float64 `json:"outstanding_hold_amount,string"` + PostOnly bool `json:"post_only"` + ProductID currency.Pair `json:"product_id"` + ProductType string `json:"product_type"` + RejectReason string `json:"reject_reason"` + RetailPortfolioID string `json:"retail_portfolio_id"` + RiskManagedBy string `json:"risk_managed_by"` + Status string `json:"status"` + StopPrice float64 `json:"stop_price,string"` + TimeInForce string `json:"time_in_force"` + TotalFees float64 `json:"total_fees,string"` + TotalValueAfterFees float64 `json:"total_value_after_fees,string"` + TriggerStatus string `json:"trigger_status"` + CreationTime time.Time `json:"creation_time"` + EndTime time.Time `json:"end_time"` + StartTime time.Time `json:"start_time"` +} + +// WebsocketPerpData defines a websocket perpetual position response, used in WebsocketPositionStruct +type WebsocketPerpData struct { + ProductID currency.Pair `json:"product_id"` + PortfolioUUID string `json:"portfolio_uuid"` + VWAP float64 `json:"vwap,string"` + EntryVWAP float64 `json:"entry_vwap,string"` + PositionSide string `json:"position_side"` + MarginType string `json:"margin_type"` + NetSize float64 `json:"net_size,string"` + BuyOrderSize float64 `json:"buy_order_size,string"` + SellOrderSize float64 `json:"sell_order_size,string"` + Leverage float64 `json:"leverage,string"` + MarkPrice float64 `json:"mark_price,string"` + LiquidationPrice float64 `json:"liquidation_price,string"` + IMNotional float64 `json:"im_notional,string"` + MMNotional float64 `json:"mm_notional,string"` + PositionNotional float64 `json:"position_notional,string"` + UnrealizedPNL float64 `json:"unrealized_pnl,string"` + AggregatedPNL float64 `json:"aggregated_pnl,string"` +} + +// WebsocketExpData defines a websocket expiring position response, used in WebsocketPositionStruct +type WebsocketExpData struct { + ProductID currency.Pair `json:"product_id"` + Side string `json:"side"` + NumberOfContracts float64 `json:"number_of_contracts,string"` + RealizedPNL float64 `json:"realized_pnl,string"` + UnrealizedPNL float64 `json:"unrealized_pnl,string"` + EntryPrice float64 `json:"entry_price,string"` +} + +// WebsocketPositionStruct holds position data, used in WebsocketOrderDataHolder +type WebsocketPositionStruct struct { + PerpetualFuturesPositions []WebsocketPerpData `json:"perpetual_futures_positions"` + ExpiringFuturesPositions []WebsocketExpData `json:"expiring_futures_positions"` } // WebsocketOrderDataHolder holds a variety of order responses, used when wsHandleData processes orders type WebsocketOrderDataHolder struct { - Type string `json:"type"` - Orders []WebsocketOrderData `json:"orders"` + Type string `json:"type"` + Orders []WebsocketOrderData `json:"orders"` + Positions WebsocketPositionStruct `json:"positions"` } // Details is a sub-struct used in CurrencyData diff --git a/exchanges/coinbasepro/coinbasepro_websocket.go b/exchanges/coinbasepro/coinbasepro_websocket.go index e711f00bc37..cc57655bed3 100644 --- a/exchanges/coinbasepro/coinbasepro_websocket.go +++ b/exchanges/coinbasepro/coinbasepro_websocket.go @@ -23,6 +23,7 @@ import ( "github.com/thrasher-corp/gocryptotrader/common/crypto" "github.com/thrasher-corp/gocryptotrader/currency" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" + "github.com/thrasher-corp/gocryptotrader/exchanges/margin" "github.com/thrasher-corp/gocryptotrader/exchanges/order" "github.com/thrasher-corp/gocryptotrader/exchanges/orderbook" "github.com/thrasher-corp/gocryptotrader/exchanges/stream" @@ -51,13 +52,12 @@ var subscriptionNames = map[string]string{ var defaultSubscriptions = subscription.List{ {Enabled: true, Channel: subscription.HeartbeatChannel}, - // Subscriptions to status return an "authentication failure" error, despite the endpoint not being authenticated - // and other authenticated channels working fine. + // Subscriptions to status return an "authentication failure" error, despite the endpoint not being authenticated and other authenticated channels working fine. {Enabled: false, Channel: "status"}, - {Enabled: false, Asset: asset.Spot, Channel: subscription.TickerChannel}, - {Enabled: false, Asset: asset.Spot, Channel: subscription.CandlesChannel}, - {Enabled: false, Asset: asset.Spot, Channel: subscription.AllTradesChannel}, - {Enabled: false, Asset: asset.Spot, Channel: subscription.OrderbookChannel}, + {Enabled: true, Asset: asset.Spot, Channel: subscription.TickerChannel}, + {Enabled: true, Asset: asset.Spot, Channel: subscription.CandlesChannel}, + {Enabled: true, Asset: asset.Spot, Channel: subscription.AllTradesChannel}, + {Enabled: true, Asset: asset.Spot, Channel: subscription.OrderbookChannel}, {Enabled: true, Asset: asset.All, Channel: subscription.MyAccountChannel, Authenticated: true}, {Enabled: false, Asset: asset.Spot, Channel: "ticker_batch"}, /* Not Implemented: @@ -258,35 +258,112 @@ func (c *CoinbasePro) wsHandleData(respRaw []byte, seqCount uint64) (string, err for i := range wsUser { for j := range wsUser[i].Orders { var oType order.Type - oType, err = order.StringToOrderType(wsUser[i].Orders[j].OrderType) + oType, err = stringToStandardType(wsUser[i].Orders[j].OrderType) if err != nil { - return warnString, err + c.Websocket.DataHandler <- order.ClassificationError{ + Exchange: c.Name, + Err: err, + } } var oSide order.Side oSide, err = order.StringToOrderSide(wsUser[i].Orders[j].OrderSide) if err != nil { - return warnString, err + c.Websocket.DataHandler <- order.ClassificationError{ + Exchange: c.Name, + Err: err, + } } var oStatus order.Status oStatus, err = statusToStandardStatus(wsUser[i].Orders[j].Status) if err != nil { - return warnString, err + c.Websocket.DataHandler <- order.ClassificationError{ + Exchange: c.Name, + Err: err, + } + } + price := wsUser[i].Orders[j].AveragePrice + if wsUser[i].Orders[j].LimitPrice != 0 { + price = wsUser[i].Orders[j].LimitPrice + } + var asset asset.Item + asset, err = stringToStandardAsset(wsUser[i].Orders[j].ProductType) + if err != nil { + c.Websocket.DataHandler <- order.ClassificationError{ + Exchange: c.Name, + Err: err, + } + } + var ioc, fok bool + ioc, fok, err = strategyDecoder(wsUser[i].Orders[j].TimeInForce) + if err != nil { + c.Websocket.DataHandler <- order.ClassificationError{ + Exchange: c.Name, + Err: err, + } } sliToSend = append(sliToSend, order.Detail{ - Price: wsUser[i].Orders[j].AveragePrice, - Amount: wsUser[i].Orders[j].CumulativeQuantity + wsUser[i].Orders[j].LeavesQuantity, - ExecutedAmount: wsUser[i].Orders[j].CumulativeQuantity, - RemainingAmount: wsUser[i].Orders[j].LeavesQuantity, - Fee: wsUser[i].Orders[j].TotalFees, - Exchange: c.Name, - OrderID: wsUser[i].Orders[j].OrderID, - ClientOrderID: wsUser[i].Orders[j].ClientOrderID, - Type: oType, - Side: oSide, - Status: oStatus, - AssetType: asset.Spot, - Date: wsUser[i].Orders[j].CreationTime, - Pair: wsUser[i].Orders[j].ProductID, + Price: price, + ClientOrderID: wsUser[i].Orders[j].ClientOrderID, + ExecutedAmount: wsUser[i].Orders[j].CumulativeQuantity, + RemainingAmount: wsUser[i].Orders[j].LeavesQuantity, + Amount: wsUser[i].Orders[j].CumulativeQuantity + wsUser[i].Orders[j].LeavesQuantity, + OrderID: wsUser[i].Orders[j].OrderID, + Side: oSide, + Type: oType, + PostOnly: wsUser[i].Orders[j].PostOnly, + Pair: wsUser[i].Orders[j].ProductID, + AssetType: asset, + Status: oStatus, + TriggerPrice: wsUser[i].Orders[j].StopPrice, + ImmediateOrCancel: ioc, + FillOrKill: fok, + Fee: wsUser[i].Orders[j].TotalFees, + Date: wsUser[i].Orders[j].CreationTime, + CloseTime: wsUser[i].Orders[j].EndTime, + Exchange: c.Name, + }) + } + for j := range wsUser[i].Positions.PerpetualFuturesPositions { + var oSide order.Side + oSide, err = order.StringToOrderSide(wsUser[i].Positions.PerpetualFuturesPositions[j].PositionSide) + if err != nil { + c.Websocket.DataHandler <- order.ClassificationError{ + Exchange: c.Name, + Err: err, + } + } + var mType margin.Type + mType, err = margin.StringToMarginType(wsUser[i].Positions.PerpetualFuturesPositions[j].MarginType) + if err != nil { + c.Websocket.DataHandler <- order.ClassificationError{ + Exchange: c.Name, + Err: err, + } + } + sliToSend = append(sliToSend, order.Detail{ + Pair: wsUser[i].Positions.PerpetualFuturesPositions[j].ProductID, + Side: oSide, + MarginType: mType, + Amount: wsUser[i].Positions.PerpetualFuturesPositions[j].NetSize, + Leverage: wsUser[i].Positions.PerpetualFuturesPositions[j].Leverage, + AssetType: asset.Futures, + Exchange: c.Name, + }) + } + for j := range wsUser[i].Positions.ExpiringFuturesPositions { + var oSide order.Side + oSide, err = order.StringToOrderSide(wsUser[i].Positions.ExpiringFuturesPositions[j].Side) + if err != nil { + c.Websocket.DataHandler <- order.ClassificationError{ + Exchange: c.Name, + Err: err, + } + } + sliToSend = append(sliToSend, order.Detail{ + Pair: wsUser[i].Positions.ExpiringFuturesPositions[j].ProductID, + Side: oSide, + ContractAmount: wsUser[i].Positions.ExpiringFuturesPositions[j].NumberOfContracts, + Price: wsUser[i].Positions.ExpiringFuturesPositions[j].EntryPrice, }) } } @@ -405,9 +482,7 @@ func (c *CoinbasePro) signWsRequest(r *WebsocketRequest) error { return nil } -// GetJWT checks if the current JWT is valid, returns it if it is, generates a new one if it isn't -// Also suitable for use in REST requests, by checking for the presence of a URI, and always generating -// a new JWT if one is provided +// GetJWT checks if the current JWT is valid, returns it if it is, generates a new one if it isn't. Also suitable for use in REST requests, by checking for the presence of a URI, and always generating a new JWT if one is provided 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 @@ -454,8 +529,7 @@ func (c *CoinbasePro) GetJWT(ctx context.Context, uri string) (string, error) { return headEncode + "." + bodyEncode + "." + sigEncode, nil } -// getTimestamp is a helper function which pulls a RFC3339-formatted timestamp from a byte slice -// of JSON data +// getTimestamp is a helper function which pulls a RFC3339-formatted timestamp from a byte slice of JSON data func getTimestamp(rawData []byte) (time.Time, error) { data, _, _, err := jsonparser.Get(rawData, "timestamp") if err != nil { @@ -468,8 +542,7 @@ func getTimestamp(rawData []byte) (time.Time, error) { return timestamp, nil } -// processBidAskArray is a helper function that turns WebsocketOrderbookDataHolder into arrays -// of bids and asks +// processBidAskArray is a helper function that turns WebsocketOrderbookDataHolder into arrays of bids and asks func processBidAskArray(data *WebsocketOrderbookDataHolder) (bids, asks orderbook.Tranches, err error) { bids = make(orderbook.Tranches, 0, len(data.Changes)) asks = make(orderbook.Tranches, 0, len(data.Changes)) @@ -489,27 +562,67 @@ func processBidAskArray(data *WebsocketOrderbookDataHolder) (bids, asks orderboo return bids, asks, nil } -// statusToStandardStatus is a helper function that converts a Coinbase Pro status string to a -// standardised order.Status type +// statusToStandardStatus is a helper function that converts a Coinbase Pro status string to a standardised order.Status type func statusToStandardStatus(stat string) (order.Status, error) { switch stat { - case "received": + case "PENDING": return order.New, nil - case "open": + case "OPEN": return order.Active, nil - case "done": + case "FILLED": return order.Filled, nil - case "match": - return order.PartiallyFilled, nil - case "change", "activate": - return order.Active, nil + case "CANCELLED": + return order.Cancelled, nil + case "EXPIRED": + return order.Expired, nil + case "FAILED": + return order.Rejected, nil default: return order.UnknownStatus, fmt.Errorf("%w %v", errUnrecognisedStatusType, stat) } } -// Base64URLEncode is a helper function that does some tweaks to standard Base64 encoding, in a way -// which JWT requires +// stringToStandardType is a helper function that converts a Coinbase Pro side string to a standardised order.Type type +func stringToStandardType(str string) (order.Type, error) { + switch str { + case "LIMIT_ORDER_TYPE": + return order.Limit, nil + case "MARKET_ORDER_TYPE": + return order.Market, nil + case "STOP_LIMIT_ORDER_TYPE": + return order.StopLimit, nil + default: + return order.UnknownType, fmt.Errorf("%w %v", errUnrecognisedOrderType, str) + } +} + +// stringToStandardAsset is a helper function that converts a Coinbase Pro asset string to a standardised asset.Item type +func stringToStandardAsset(str string) (asset.Item, error) { + switch str { + case "SPOT": + return asset.Spot, nil + case "FUTURE": + return asset.Futures, nil + default: + return asset.Empty, fmt.Errorf("%w %v", errUnrecognisedAssetType, str) + } +} + +// strategyDecoder is a helper function that converts a Coinbase Pro time in force string to a few standardised bools +func strategyDecoder(str string) (bool, bool, error) { + switch str { + case "IMMEDIATE_OR_CANCEL": + return true, false, nil + case "FILL_OR_KILL": + return false, true, nil + case "GOOD_UNTIL_CANCELLED", "GOOD_UNTIL_DATE_TIME": + return false, false, nil + default: + return false, false, fmt.Errorf("%w %v", errUnrecognisedStrategyType, str) + } +} + +// Base64URLEncode is a helper function that does some tweaks to standard Base64 encoding, in a way which JWT requires func base64URLEncode(b []byte) string { s := crypto.Base64Encode(b) s = strings.Split(s, "=")[0] diff --git a/exchanges/coinbasepro/coinbasepro_wrapper.go b/exchanges/coinbasepro/coinbasepro_wrapper.go index a16ece44f3c..c3ce3a819f1 100644 --- a/exchanges/coinbasepro/coinbasepro_wrapper.go +++ b/exchanges/coinbasepro/coinbasepro_wrapper.go @@ -203,8 +203,7 @@ func (c *CoinbasePro) FetchTradablePairs(ctx context.Context, a asset.Item) (cur return pairs, nil } -// UpdateTradablePairs updates the exchanges available pairs and stores -// them in the exchanges config +// 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(false) for i := range assets { @@ -220,8 +219,7 @@ func (c *CoinbasePro) UpdateTradablePairs(ctx context.Context, forceUpdate bool) return c.EnsureOnePairEnabled() } -// UpdateAccountInfo retrieves balances for all enabled currencies for the -// coinbasepro exchange +// UpdateAccountInfo retrieves balances for all enabled currencies for the coinbasepro exchange func (c *CoinbasePro) UpdateAccountInfo(ctx context.Context, assetType asset.Item) (account.Holdings, error) { var ( response account.Holdings @@ -383,8 +381,7 @@ func (c *CoinbasePro) UpdateOrderbook(ctx context.Context, p currency.Pair, asse return orderbook.Get(c.Name, p, assetType) } -// GetAccountFundingHistory returns funding history, deposits and -// withdrawals +// GetAccountFundingHistory returns funding history, deposits and withdrawals func (c *CoinbasePro) GetAccountFundingHistory(ctx context.Context) ([]exchange.FundingHistory, error) { wallIDs, err := c.GetAllWallets(ctx, PaginationInp{}) if err != nil { @@ -533,8 +530,7 @@ func (c *CoinbasePro) SubmitOrder(ctx context.Context, s *order.Submit) (*order. return subResp, nil } -// ModifyOrder will allow of changing orderbook placement and limit to -// market conversion +// ModifyOrder will allow of changing orderbook placement and limit to market conversion func (c *CoinbasePro) ModifyOrder(ctx context.Context, m *order.Modify) (*order.ModifyResponse, error) { if m == nil { return nil, common.ErrNilPointer @@ -689,8 +685,7 @@ func (c *CoinbasePro) GetDepositAddress(ctx context.Context, cryptocurrency curr }, nil } -// WithdrawCryptocurrencyFunds returns a withdrawal ID when a withdrawal is -// submitted +// WithdrawCryptocurrencyFunds returns a withdrawal ID when a withdrawal is submitted func (c *CoinbasePro) WithdrawCryptocurrencyFunds(ctx context.Context, withdrawRequest *withdraw.Request) (*withdraw.ExchangeResponse, error) { if err := withdrawRequest.Validate(); err != nil { return nil, err @@ -706,8 +701,7 @@ func (c *CoinbasePro) WithdrawCryptocurrencyFunds(ctx context.Context, withdrawR return &withdraw.ExchangeResponse{Name: resp.Network.Name, ID: resp.ID, Status: resp.Status}, nil } -// WithdrawFiatFunds returns a withdrawal ID when a withdrawal is -// submitted +// WithdrawFiatFunds returns a withdrawal ID when a withdrawal is submitted func (c *CoinbasePro) WithdrawFiatFunds(ctx context.Context, withdrawRequest *withdraw.Request) (*withdraw.ExchangeResponse, error) { if err := withdrawRequest.Validate(); err != nil { return nil, err @@ -740,8 +734,7 @@ func (c *CoinbasePro) WithdrawFiatFunds(ctx context.Context, withdrawRequest *wi }, nil } -// WithdrawFiatFundsToInternationalBank returns a withdrawal ID when a -// withdrawal is submitted +// WithdrawFiatFundsToInternationalBank returns a withdrawal ID when a withdrawal is submitted func (c *CoinbasePro) WithdrawFiatFundsToInternationalBank(ctx context.Context, withdrawRequest *withdraw.Request) (*withdraw.ExchangeResponse, error) { return c.WithdrawFiatFunds(ctx, withdrawRequest) } @@ -788,8 +781,7 @@ func (c *CoinbasePro) GetActiveOrders(ctx context.Context, req *order.MultiOrder return req.Filter(c.Name, orders), nil } -// GetOrderHistory retrieves account order information -// Can Limit response to specific order status +// GetOrderHistory retrieves account order information. Can Limit response to specific order status func (c *CoinbasePro) GetOrderHistory(ctx context.Context, req *order.MultiOrderRequest) (order.FilteredOrders, error) { err := req.Validate() if err != nil { @@ -825,8 +817,7 @@ func (c *CoinbasePro) GetOrderHistory(ctx context.Context, req *order.MultiOrder return req.Filter(c.Name, orders), nil } -// GetHistoricCandles returns a set of candle between two time periods for a -// designated time period +// GetHistoricCandles returns a set of candle between two time periods for a designated time period func (c *CoinbasePro) GetHistoricCandles(ctx context.Context, pair currency.Pair, a asset.Item, interval kline.Interval, start, end time.Time) (*kline.Item, error) { req, err := c.GetKlineRequest(pair, a, interval, start, end, false) if err != nil { @@ -864,8 +855,7 @@ func (c *CoinbasePro) GetHistoricCandlesExtended(ctx context.Context, pair curre return req.ProcessResponse(timeSeries) } -// ValidateAPICredentials validates current credentials used for wrapper -// functionality +// ValidateAPICredentials validates current credentials used for wrapper functionality func (c *CoinbasePro) ValidateAPICredentials(ctx context.Context, assetType asset.Item) error { _, err := c.UpdateAccountInfo(ctx, assetType) return c.CheckTransientError(err) @@ -1006,9 +996,7 @@ func (c *CoinbasePro) GetCurrencyTradeURL(_ context.Context, a asset.Item, cp cu return tradeBaseURL + cp.Upper().String(), nil } -// 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 +// 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, verified bool) (*AllProducts, int, error) { products, err := c.GetAllProducts(ctx, 0, 0, "FUTURE", "", "", nil, verified) if err != nil { @@ -1029,8 +1017,7 @@ func (c *CoinbasePro) fetchFutures(ctx context.Context, verified bool) (*AllProd return products, perpStart, nil } -// processFundingData is a helper function for GetAccountFundingHistory and GetWithdrawalsHistory, -// transforming the data returned by the Coinbase API into a format suitable for the exchange package +// processFundingData is a helper function for GetAccountFundingHistory and GetWithdrawalsHistory, transforming the data returned by the Coinbase API into a format suitable for the exchange package func (c *CoinbasePro) processFundingData(accHistory []DeposWithdrData, cryptoHistory []TransactionData) []exchange.FundingHistory { fundingData := make([]exchange.FundingHistory, len(accHistory)+len(cryptoHistory)) for i := range accHistory { @@ -1068,8 +1055,7 @@ func (c *CoinbasePro) processFundingData(accHistory []DeposWithdrData, cryptoHis return fundingData } -// iterativeGetAllOrders is a helper function used in GetActiveOrders and GetOrderHistory -// to repeatedly call GetAllOrders until all orders have been retrieved +// iterativeGetAllOrders is a helper function used in GetActiveOrders and GetOrderHistory to repeatedly call GetAllOrders until all orders have been retrieved func (c *CoinbasePro) iterativeGetAllOrders(ctx context.Context, productID, orderType, orderSide, productType string, orderStatus []string, limit int32, startDate, endDate time.Time) ([]GetOrderResponse, error) { hasNext := true var resp []GetOrderResponse @@ -1095,8 +1081,7 @@ func (c *CoinbasePro) iterativeGetAllOrders(ctx context.Context, productID, orde return resp, nil } -// FormatExchangeKlineIntervalV3 is a helper function used in GetHistoricCandles and GetHistoricCandlesExtended -// to convert kline.Interval to the string format used by V3 of Coinbase's API +// FormatExchangeKlineIntervalV3 is a helper function used in GetHistoricCandles and GetHistoricCandlesExtended to convert kline.Interval to the string format used by V3 of Coinbase's API func FormatExchangeKlineIntervalV3(interval kline.Interval) string { switch interval { case kline.OneMin: @@ -1119,8 +1104,7 @@ func FormatExchangeKlineIntervalV3(interval kline.Interval) string { return errIntervalNotSupported } -// getOrderRespToOrderDetail is a helper function used in GetOrderInfo, GetActiveOrders, and GetOrderHistory -// to convert data returned by the Coinbase API into a format suitable for the exchange package +// getOrderRespToOrderDetail is a helper function used in GetOrderInfo, GetActiveOrders, and GetOrderHistory to convert data returned by the Coinbase API into a format suitable for the exchange package func (c *CoinbasePro) getOrderRespToOrderDetail(genOrderDetail *GetOrderResponse, pair currency.Pair, assetItem asset.Item) *order.Detail { var amount float64 var quoteAmount float64 diff --git a/exchanges/protocol/features.go b/exchanges/protocol/features.go index e6ad6ad3eae..8649d8d1ee9 100644 --- a/exchanges/protocol/features.go +++ b/exchanges/protocol/features.go @@ -4,40 +4,37 @@ package protocol // for a protocol (e.g REST or Websocket) type Features struct { // TickerBatching allows the REST endpoint to fetch the entire ticker list available to the exchange - TickerBatching bool `json:"tickerBatching,omitempty"` - AutoPairUpdates bool `json:"autoPairUpdates,omitempty"` - AccountBalance bool `json:"accountBalance,omitempty"` - CryptoDeposit bool `json:"cryptoDeposit,omitempty"` - CryptoWithdrawal bool `json:"cryptoWithdrawal,omitempty"` - FiatWithdraw bool `json:"fiatWithdraw,omitempty"` - GetOrder bool `json:"getOrder,omitempty"` - GetOrders bool `json:"getOrders,omitempty"` - CancelOrders bool `json:"cancelOrders,omitempty"` - CancelOrder bool `json:"cancelOrder,omitempty"` - SubmitOrder bool `json:"submitOrder,omitempty"` - SubmitOrders bool `json:"submitOrders,omitempty"` - ModifyOrder bool `json:"modifyOrder,omitempty"` - DepositHistory bool `json:"depositHistory,omitempty"` - WithdrawalHistory bool `json:"withdrawalHistory,omitempty"` - TradeHistory bool `json:"tradeHistory,omitempty"` - UserTradeHistory bool `json:"userTradeHistory,omitempty"` - TradeFee bool `json:"tradeFee,omitempty"` - FiatDepositFee bool `json:"fiatDepositFee,omitempty"` - FiatWithdrawalFee bool `json:"fiatWithdrawalFee,omitempty"` - CryptoDepositFee bool `json:"cryptoDepositFee,omitempty"` - CryptoWithdrawalFee bool `json:"cryptoWithdrawalFee,omitempty"` - TickerFetching bool `json:"tickerFetching,omitempty"` - KlineFetching bool `json:"klineFetching,omitempty"` - TradeFetching bool `json:"tradeFetching,omitempty"` - OrderbookFetching bool `json:"orderbookFetching,omitempty"` - AccountInfo bool `json:"accountInfo,omitempty"` - FiatDeposit bool `json:"fiatDeposit,omitempty"` - DeadMansSwitch bool `json:"deadMansSwitch,omitempty"` - FundingRateFetching bool `json:"fundingRateFetching"` - PredictedFundingRate bool `json:"predictedFundingRate,omitempty"` - // FullPayloadSubscribe flushes and changes full subscription on websocket - // connection by subscribing with full default stream channel list - FullPayloadSubscribe bool `json:"fullPayloadSubscribe,omitempty"` + TickerBatching bool `json:"tickerBatching,omitempty"` + AutoPairUpdates bool `json:"autoPairUpdates,omitempty"` + AccountBalance bool `json:"accountBalance,omitempty"` + CryptoDeposit bool `json:"cryptoDeposit,omitempty"` + CryptoWithdrawal bool `json:"cryptoWithdrawal,omitempty"` + FiatWithdraw bool `json:"fiatWithdraw,omitempty"` + GetOrder bool `json:"getOrder,omitempty"` + GetOrders bool `json:"getOrders,omitempty"` + CancelOrders bool `json:"cancelOrders,omitempty"` + CancelOrder bool `json:"cancelOrder,omitempty"` + SubmitOrder bool `json:"submitOrder,omitempty"` + SubmitOrders bool `json:"submitOrders,omitempty"` + ModifyOrder bool `json:"modifyOrder,omitempty"` + DepositHistory bool `json:"depositHistory,omitempty"` + WithdrawalHistory bool `json:"withdrawalHistory,omitempty"` + TradeHistory bool `json:"tradeHistory,omitempty"` + UserTradeHistory bool `json:"userTradeHistory,omitempty"` + TradeFee bool `json:"tradeFee,omitempty"` + FiatDepositFee bool `json:"fiatDepositFee,omitempty"` + FiatWithdrawalFee bool `json:"fiatWithdrawalFee,omitempty"` + CryptoDepositFee bool `json:"cryptoDepositFee,omitempty"` + CryptoWithdrawalFee bool `json:"cryptoWithdrawalFee,omitempty"` + TickerFetching bool `json:"tickerFetching,omitempty"` + KlineFetching bool `json:"klineFetching,omitempty"` + TradeFetching bool `json:"tradeFetching,omitempty"` + OrderbookFetching bool `json:"orderbookFetching,omitempty"` + AccountInfo bool `json:"accountInfo,omitempty"` + FiatDeposit bool `json:"fiatDeposit,omitempty"` + DeadMansSwitch bool `json:"deadMansSwitch,omitempty"` + FundingRateFetching bool `json:"fundingRateFetching"` + PredictedFundingRate bool `json:"predictedFundingRate,omitempty"` Subscribe bool `json:"subscribe,omitempty"` Unsubscribe bool `json:"unsubscribe,omitempty"` AuthenticatedEndpoints bool `json:"authenticatedEndpoints,omitempty"` diff --git a/exchanges/subscription/template.go b/exchanges/subscription/template.go index f10b4f30291..ae994f7919d 100644 --- a/exchanges/subscription/template.go +++ b/exchanges/subscription/template.go @@ -104,12 +104,12 @@ func expandTemplate(e iExchange, s *Subscription, ap assetPairs, assets asset.It subs := List{} - // if len(s.Pairs) != 0 { - // // We deliberately do not check Availability of sub Pairs because users have edge cases to subscribe to non-existent pairs - // for a := range ap { - // ap[a] = s.Pairs - // } - // } + if len(s.Pairs) != 0 { + // We deliberately do not check Availability of sub Pairs because users have edge cases to subscribe to non-existent pairs + for a := range ap { + ap[a] = s.Pairs + } + } switch s.Asset { case asset.All: @@ -125,15 +125,6 @@ func expandTemplate(e iExchange, s *Subscription, ap assetPairs, assets asset.It } } - if len(s.Pairs) != 0 { - for a, pairs := range subCtx.AssetPairs { - if err := pairs.ContainsAll(s.Pairs, true); err != nil { //nolint:govet // Shadow, or gocritic will complain sloppyReassign - return nil, err - } - subCtx.AssetPairs[a] = s.Pairs - } - } - buf := &bytes.Buffer{} if err := t.Execute(buf, subCtx); err != nil { //nolint:govet // Shadow, or gocritic will complain sloppyReassign return nil, err From 2fd8bb1b0bfc810d3668b02e67fcad3e5d6f9749 Mon Sep 17 00:00:00 2001 From: Samuel Reid <43227667+cranktakular@users.noreply.github.com> Date: Tue, 12 Nov 2024 11:52:01 +1100 Subject: [PATCH 74/79] Further shaznits --- exchanges/coinbasepro/coinbasepro.go | 73 +--- exchanges/coinbasepro/coinbasepro_test.go | 43 +- exchanges/coinbasepro/coinbasepro_types.go | 384 +++++++++--------- .../coinbasepro/coinbasepro_websocket.go | 8 +- exchanges/coinbasepro/coinbasepro_wrapper.go | 48 ++- 5 files changed, 244 insertions(+), 312 deletions(-) diff --git a/exchanges/coinbasepro/coinbasepro.go b/exchanges/coinbasepro/coinbasepro.go index f9109574908..b2ecca9c796 100644 --- a/exchanges/coinbasepro/coinbasepro.go +++ b/exchanges/coinbasepro/coinbasepro.go @@ -93,9 +93,8 @@ const ( startDateString = "start_date" endDateString = "end_date" - errIntervalNotSupported = "interval not supported" - warnSequenceIssue = "Out of order sequence number. Received %v, expected %v" - warnAuth = "%v authenticated request failed, attempting unauthenticated" + warnSequenceIssue = "Out of order sequence number. Received %v, expected %v" + warnAuth = "%v authenticated request failed, attempting unauthenticated" manyFills = 65535 manyOrds = 2147483647 @@ -155,6 +154,7 @@ var ( errUnrecognisedOrderType = errors.New("unrecognised order type") errUnrecognisedAssetType = errors.New("unrecognised asset type") errUnrecognisedStrategyType = errors.New("unrecognised strategy type") + errIntervalNotSupported = errors.New("interval not supported") allowedGranularities = []string{granOneMin, granFiveMin, granFifteenMin, granThirtyMin, granOneHour, granTwoHour, granSixHour, granOneDay} closedStatuses = []string{"FILLED", "CANCELLED", "EXPIRED", "FAILED"} @@ -199,23 +199,24 @@ func (c *CoinbasePro) GetBestBidAsk(ctx context.Context, products []string) ([]P } // GetProductBookV3 returns a list of bids/asks for a single product -func (c *CoinbasePro) GetProductBookV3(ctx context.Context, productID string, limit uint16, authenticated bool) (*ProductBook, error) { - if productID == "" { +func (c *CoinbasePro) GetProductBookV3(ctx context.Context, productID currency.Pair, limit uint16, aggregationIncrement float64, authenticated bool) (*ProductBookResp, error) { + if productID.IsEmpty() { return nil, errProductIDEmpty } vals := url.Values{} - vals.Set("product_id", productID) + vals.Set("product_id", productID.String()) if limit != 0 { vals.Set("limit", strconv.FormatInt(int64(limit), 10)) } - resp := struct { - Pricebook ProductBook `json:"pricebook"` - }{} + if aggregationIncrement != 0 { + vals.Set("aggregation_price_increment", strconv.FormatFloat(aggregationIncrement, 'f', -1, 64)) + } + var resp *ProductBookResp if authenticated { - return &resp.Pricebook, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, coinbaseV3+coinbaseProductBook, vals, nil, true, &resp, nil) + return resp, c.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, coinbaseV3+coinbaseProductBook, vals, nil, true, &resp, nil) } path := coinbaseV3 + coinbaseMarket + "/" + coinbaseProductBook - return &resp.Pricebook, c.SendHTTPRequest(ctx, exchange.RestSpot, path, vals, &resp) + return resp, c.SendHTTPRequest(ctx, exchange.RestSpot, path, vals, &resp) } // GetAllProducts returns information on all currency pairs that are available for trading @@ -1659,53 +1660,3 @@ func (f FiatTransferType) String() string { } return "deposit" } - -// UnmarshalJSON unmarshals the JSON input into a UnixTimestamp type -func (t *UnixTimestamp) UnmarshalJSON(b []byte) error { - var timestampStr string - err := json.Unmarshal(b, ×tampStr) - if err != nil { - return err - } - timestamp, err := strconv.ParseInt(timestampStr, 10, 64) - if err != nil { - return err - } - *t = UnixTimestamp(time.Unix(timestamp, 0).UTC()) - return nil -} - -// String implements the stringer interface -func (t *UnixTimestamp) String() string { - return t.Time().String() -} - -// Time returns the time.Time representation of the UnixTimestamp -func (t *UnixTimestamp) Time() time.Time { - return time.Time(*t) -} - -// UnmarshalJSON unmarshals the JSON input into a UnixTimestamp type -func (t *UnixTimestampMilli) UnmarshalJSON(b []byte) error { - var timestampStr string - err := json.Unmarshal(b, ×tampStr) - if err != nil { - return err - } - timestamp, err := strconv.ParseInt(timestampStr, 10, 64) - if err != nil { - return err - } - *t = UnixTimestampMilli(time.UnixMilli(timestamp).UTC()) - return nil -} - -// String implements the stringer interface -func (t *UnixTimestampMilli) String() string { - return t.Time().String() -} - -// Time returns the time.Time representation of the UnixTimestamp -func (t *UnixTimestampMilli) Time() time.Time { - return time.Time(*t) -} diff --git a/exchanges/coinbasepro/coinbasepro_test.go b/exchanges/coinbasepro/coinbasepro_test.go index c1361835e22..85110eaff57 100644 --- a/exchanges/coinbasepro/coinbasepro_test.go +++ b/exchanges/coinbasepro/coinbasepro_test.go @@ -170,13 +170,13 @@ func TestGetBestBidAsk(t *testing.T) { func TestGetProductBookV3(t *testing.T) { t.Parallel() - _, err := c.GetProductBookV3(context.Background(), "", 0, false) + _, err := c.GetProductBookV3(context.Background(), currency.Pair{}, 0, 0, false) assert.ErrorIs(t, err, errProductIDEmpty) - resp, err := c.GetProductBookV3(context.Background(), testPair.String(), 2, false) + resp, err := c.GetProductBookV3(context.Background(), testPair, 4, -1, false) assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - resp, err = c.GetProductBookV3(context.Background(), testPair.String(), 2, true) + resp, err = c.GetProductBookV3(context.Background(), testPair, 4, -1, true) assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) } @@ -970,7 +970,7 @@ func TestSendAuthenticatedHTTPRequest(t *testing.T) { err = c.SendAuthenticatedHTTPRequest(context.Background(), exchange.EdgeCase3, "", "", nil, nil, false, nil, nil) assert.ErrorIs(t, err, exchange.ErrEndpointPathNotFound) ch := make(chan struct{}) - body := map[string]interface{}{"Unmarshalable": ch} + body := map[string]any{"Unmarshalable": ch} err = c.SendAuthenticatedHTTPRequest(context.Background(), exchange.RestSpot, "", "", nil, body, false, nil, nil) var targetErr *json.UnsupportedTypeError assert.ErrorAs(t, err, &targetErr) @@ -1072,6 +1072,8 @@ func TestUpdateTicker(t *testing.T) { func TestFetchTicker(t *testing.T) { t.Parallel() + _, err := c.FetchTicker(context.Background(), currency.Pair{}, asset.Spot) + assert.ErrorIs(t, err, currency.ErrCurrencyPairEmpty) resp, err := c.FetchTicker(context.Background(), testPair, asset.Spot) assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) @@ -1079,6 +1081,8 @@ func TestFetchTicker(t *testing.T) { func TestFetchOrderbook(t *testing.T) { t.Parallel() + _, err := c.FetchOrderbook(context.Background(), currency.Pair{}, asset.Empty) + assert.ErrorIs(t, err, currency.ErrCurrencyPairEmpty) resp, err := c.FetchOrderbook(context.Background(), testPair, asset.Spot) assert.NoError(t, err) assert.NotEmpty(t, resp, errExpectedNonEmpty) @@ -1427,29 +1431,6 @@ func TestFiatTransferTypeString(t *testing.T) { } } -func TestUnixTimestampUnmarshalJSON(t *testing.T) { - t.Parallel() - var u UnixTimestamp - err := u.UnmarshalJSON([]byte("0")) - var targetErr *json.UnmarshalTypeError - assert.ErrorAs(t, err, &targetErr) - err = u.UnmarshalJSON([]byte("\"922337203685477580700\"")) - assert.ErrorIs(t, err, strconv.ErrRange) - err = u.UnmarshalJSON([]byte("\"1234\"")) - assert.NoError(t, err) -} - -func TestUnixTimestampString(t *testing.T) { - t.Parallel() - var u UnixTimestamp - err := u.UnmarshalJSON([]byte("\"1234\"")) - assert.NoError(t, err) - s := u.String() - if s != expectedTimestamp { - t.Errorf(errExpectMismatch, s, expectedTimestamp) - } -} - func TestFormatExchangeKlineIntervalV3(t *testing.T) { t.Parallel() testSequence := map[kline.Interval]string{ @@ -1460,12 +1441,15 @@ func TestFormatExchangeKlineIntervalV3(t *testing.T) { kline.TwoHour: granTwoHour, kline.SixHour: granSixHour, kline.OneDay: granOneDay, - kline.OneWeek: errIntervalNotSupported} + kline.OneWeek: ""} for k := range testSequence { - resp := FormatExchangeKlineIntervalV3(k) + resp, err := FormatExchangeKlineIntervalV3(k) if resp != testSequence[k] { t.Errorf(errExpectMismatch, resp, testSequence[k]) } + if resp == "" { + assert.ErrorIs(t, err, errIntervalNotSupported) + } } } @@ -1524,7 +1508,6 @@ func TestCancelPendingFuturesSweep(t *testing.T) { // TestWsAuth dials websocket, sends login request. func TestWsAuth(t *testing.T) { - // t.Parallel() p := currency.Pairs{testPair} if c.Websocket.IsEnabled() && !c.API.AuthenticatedWebsocketSupport || !sharedtestvalues.AreAPICredentialsSet(c) { t.Skip(stream.ErrWebsocketNotEnabled.Error()) diff --git a/exchanges/coinbasepro/coinbasepro_types.go b/exchanges/coinbasepro/coinbasepro_types.go index bbed46b1824..2eea519200d 100644 --- a/exchanges/coinbasepro/coinbasepro_types.go +++ b/exchanges/coinbasepro/coinbasepro_types.go @@ -26,10 +26,10 @@ type Version bool // endpoints under version 2 of the API type FiatTransferType bool -// ValCur is a sub-struct used in the types Account, NativeAndRaw, DetailedPortfolioResponse, +// ValueWithCurrency is a sub-struct used in the types Account, NativeAndRaw, DetailedPortfolioResponse, // FuturesBalanceSummary, ListFuturesSweepsResponse, PerpetualsPortfolioSummary, PerpPositionDetail, // FeeStruct, AmScale, and ConvertResponse -type ValCur struct { +type ValueWithCurrency struct { Value float64 `json:"value,string"` Currency string `json:"currency"` } @@ -37,18 +37,18 @@ type ValCur struct { // Account holds details for a trading account, returned by GetAccountByID and used as // a sub-struct in the type AllAccountsResponse type Account struct { - UUID string `json:"uuid"` - Name string `json:"name"` - Currency string `json:"currency"` - AvailableBalance ValCur `json:"available_balance"` - Default bool `json:"default"` - Active bool `json:"active"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` - DeletedAt time.Time `json:"deleted_at"` - Type string `json:"type"` - Ready bool `json:"ready"` - Hold ValCur `json:"hold"` + UUID string `json:"uuid"` + Name string `json:"name"` + Currency string `json:"currency"` + AvailableBalance ValueWithCurrency `json:"available_balance"` + Default bool `json:"default"` + Active bool `json:"active"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + DeletedAt time.Time `json:"deleted_at"` + Type string `json:"type"` + Ready bool `json:"ready"` + Hold ValueWithCurrency `json:"hold"` } // AllAccountsResponse holds many Account structs, as well as pagination information, @@ -71,7 +71,7 @@ type PriceSize struct { Size float64 `json:"size,string"` } -// ProductBook holds bid and ask prices for a particular product, returned by GetProductBookV3 +// ProductBook holds bid and ask prices for a particular product, returned by GetBestBidAsk and used in ProductBookResp type ProductBook struct { ProductID currency.Pair `json:"product_id"` Bids []PriceSize `json:"bids"` @@ -79,6 +79,15 @@ type ProductBook struct { Time time.Time `json:"time"` } +// ProductBookResp holds a ProductBook struct, and associated information, returned by GetProductBookV3 +type ProductBookResp struct { + Pricebook ProductBook `json:"pricebook"` + Last float64 `json:"last,string"` + MidMarket float64 `json:"mid_market,string"` + SpreadBPs float64 `json:"spread_bps,string"` + SpreadAbsolute float64 `json:"spread_absolute,string"` +} + // FCMTradingSessionDetails is a sub-struct used in the type Product type FCMTradingSessionDetails struct { IsSessionOpen bool `json:"is_session_open"` @@ -111,7 +120,7 @@ type FutureProductDetails struct { ContractExpiryType string `json:"contract_expiry_type"` PerpetualDetails PerpetualDetails `json:"perpetual_details"` ContractDisplayName string `json:"contract_display_name"` - TimeToExpiryMS uint64 `json:"time_to_expiry_ms,string"` + TimeToExpiryMS time.Duration `json:"time_to_expiry_ms,string"` NonCrypto bool `json:"non_crypto"` ContractExpiryName string `json:"contract_expiry_name"` } @@ -165,18 +174,14 @@ type AllProducts struct { NumProducts int32 `json:"num_products"` } -// UnixTimestamp is a type used to unmarshal unix timestamps returned from -// the exchange, used in the types History and WebsocketCandle -type UnixTimestamp time.Time - // CandleStruct holds historic trade information, 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"` + Start types.Time `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"` } // Trades is a sub-struct used in the type Ticker @@ -396,32 +401,32 @@ type FundsData struct { // NativeAndRaw is a sub-struct used in the type DetailedPortfolioResponse type NativeAndRaw struct { - UserNativeCurrency ValCur `json:"userNativeCurrency"` - RawCurrency ValCur `json:"rawCurrency"` + UserNativeCurrency ValueWithCurrency `json:"userNativeCurrency"` + RawCurrency ValueWithCurrency `json:"rawCurrency"` } // PortfolioBalances is a sub-struct used in the type DetailedPortfolioResponse type PortfolioBalances struct { - TotalBalance ValCur `json:"total_balance"` - TotalFuturesBalance ValCur `json:"total_futures_balance"` - TotalCashEquivalentBalance ValCur `json:"total_cash_equivalent_balance"` - TotalCryptoBalance ValCur `json:"total_crypto_balance"` - FuturesUnrealizedPNL ValCur `json:"futures_unrealized_pnl"` - PerpUnrealizedPNL ValCur `json:"perp_unrealized_pnl"` + TotalBalance ValueWithCurrency `json:"total_balance"` + TotalFuturesBalance ValueWithCurrency `json:"total_futures_balance"` + TotalCashEquivalentBalance ValueWithCurrency `json:"total_cash_equivalent_balance"` + TotalCryptoBalance ValueWithCurrency `json:"total_crypto_balance"` + FuturesUnrealizedPNL ValueWithCurrency `json:"futures_unrealized_pnl"` + PerpUnrealizedPNL ValueWithCurrency `json:"perp_unrealized_pnl"` } // SpotPositions is a sub-struct used in the type DetailedPortfolioResponse type SpotPositions struct { - Asset string `json:"asset"` - AccountUUID string `json:"account_uuid"` - TotalBalanceFiat float64 `json:"total_balance_fiat"` - TotalBalanceCrypto float64 `json:"total_balance_crypto"` - AvailableToTreadeFiat float64 `json:"available_to_trade_fiat"` - Allocation float64 `json:"allocation"` - OneDayChange float64 `json:"one_day_change"` - CostBasis ValCur `json:"cost_basis"` - AssetImgURL string `json:"asset_img_url"` - IsCash bool `json:"is_cash"` + Asset string `json:"asset"` + AccountUUID string `json:"account_uuid"` + TotalBalanceFiat float64 `json:"total_balance_fiat"` + TotalBalanceCrypto float64 `json:"total_balance_crypto"` + AvailableToTreadeFiat float64 `json:"available_to_trade_fiat"` + Allocation float64 `json:"allocation"` + OneDayChange float64 `json:"one_day_change"` + CostBasis ValueWithCurrency `json:"cost_basis"` + AssetImgURL string `json:"asset_img_url"` + IsCash bool `json:"is_cash"` } // PerpPositions is a sub-struct used in the type DetailedPortfolioResponse @@ -478,18 +483,18 @@ type DetailedPortfolioResponse struct { // FuturesBalanceSummary contains information on futures balances, returned by // GetFuturesBalanceSummary type FuturesBalanceSummary struct { - FuturesBuyingPower ValCur `json:"futures_buying_power"` - TotalUSDBalance ValCur `json:"total_usd_balance"` - CBIUSDBalance ValCur `json:"cbi_usd_balance"` - CFMUSDBalance ValCur `json:"cfm_usd_balance"` - TotalOpenOrdersHoldAmount ValCur `json:"total_open_orders_hold_amount"` - UnrealizedPNL ValCur `json:"unrealized_pnl"` - DailyRealizedPNL ValCur `json:"daily_realized_pnl"` - InitialMargin ValCur `json:"initial_margin"` - AvailableMargin ValCur `json:"available_margin"` - LiquidationThreshold ValCur `json:"liquidation_threshold"` - LiquidationBufferAmount ValCur `json:"liquidation_buffer_amount"` - LiquidationBufferPercentage float64 `json:"liquidation_buffer_percentage,string"` + FuturesBuyingPower ValueWithCurrency `json:"futures_buying_power"` + TotalUSDBalance ValueWithCurrency `json:"total_usd_balance"` + CBIUSDBalance ValueWithCurrency `json:"cbi_usd_balance"` + CFMUSDBalance ValueWithCurrency `json:"cfm_usd_balance"` + TotalOpenOrdersHoldAmount ValueWithCurrency `json:"total_open_orders_hold_amount"` + UnrealizedPNL ValueWithCurrency `json:"unrealized_pnl"` + DailyRealizedPNL ValueWithCurrency `json:"daily_realized_pnl"` + InitialMargin ValueWithCurrency `json:"initial_margin"` + AvailableMargin ValueWithCurrency `json:"available_margin"` + LiquidationThreshold ValueWithCurrency `json:"liquidation_threshold"` + LiquidationBufferAmount ValueWithCurrency `json:"liquidation_buffer_amount"` + LiquidationBufferPercentage float64 `json:"liquidation_buffer_percentage,string"` } // FuturesPosition contains information on a single futures position, returned by @@ -509,38 +514,38 @@ type FuturesPosition struct { // SweepData contains information on pending and processing sweep requests, 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"` + ID string `json:"id"` + RequestedAmount ValueWithCurrency `json:"requested_amount"` + ShouldSweepAll bool `json:"should_sweep_all"` + Status string `json:"status"` + ScheduledTime time.Time `json:"scheduled_time"` } // PerpetualsPortfolioSummary contains information on perpetuals portfolio balances, used as // a sub-struct in the types PerpPositionDetail, AllPerpPosResponse, and // OnePerpPosResponse type PerpetualsPortfolioSummary struct { - PortfolioUUID string `json:"portfolio_uuid"` - Collateral float64 `json:"collateral,string"` - PositionNotional float64 `json:"position_notional,string"` - OpenPositionNotional float64 `json:"open_position_notional,string"` - PendingFees float64 `json:"pending_fees,string"` - Borrow float64 `json:"borrow,string"` - AccruedInterest float64 `json:"accrued_interest,string"` - RollingDebt float64 `json:"rolling_debt,string"` - PortfolioInitialMargin float64 `json:"portfolio_initial_margin,string"` - PortfolioIMNotional ValCur `json:"portfolio_im_notional"` - PortfolioMaintenanceMargin float64 `json:"portfolio_maintenance_margin,string"` - PortfolioMMNotional ValCur `json:"portfolio_mm_notional"` - LiquidationPercentage float64 `json:"liquidation_percentage,string"` - LiquidationBuffer float64 `json:"liquidation_buffer,string"` - MarginType string `json:"margin_type"` - MarginFlags string `json:"margin_flags"` - LiquidationStatus string `json:"liquidation_status"` - UnrealizedPNL ValCur `json:"unrealized_pnl"` - BuyingPower ValCur `json:"buying_power"` - TotalBalance ValCur `json:"total_balance"` - MaxWithDrawal ValCur `json:"max_withdrawal"` + PortfolioUUID string `json:"portfolio_uuid"` + Collateral float64 `json:"collateral,string"` + PositionNotional float64 `json:"position_notional,string"` + OpenPositionNotional float64 `json:"open_position_notional,string"` + PendingFees float64 `json:"pending_fees,string"` + Borrow float64 `json:"borrow,string"` + AccruedInterest float64 `json:"accrued_interest,string"` + RollingDebt float64 `json:"rolling_debt,string"` + PortfolioInitialMargin float64 `json:"portfolio_initial_margin,string"` + PortfolioIMNotional ValueWithCurrency `json:"portfolio_im_notional"` + PortfolioMaintenanceMargin float64 `json:"portfolio_maintenance_margin,string"` + PortfolioMMNotional ValueWithCurrency `json:"portfolio_mm_notional"` + LiquidationPercentage float64 `json:"liquidation_percentage,string"` + LiquidationBuffer float64 `json:"liquidation_buffer,string"` + MarginType string `json:"margin_type"` + MarginFlags string `json:"margin_flags"` + LiquidationStatus string `json:"liquidation_status"` + UnrealizedPNL ValueWithCurrency `json:"unrealized_pnl"` + BuyingPower ValueWithCurrency `json:"buying_power"` + TotalBalance ValueWithCurrency `json:"total_balance"` + MaxWithDrawal ValueWithCurrency `json:"max_withdrawal"` } // PerpPositionDetail contains information on a single perpetuals position, used as a sub-struct @@ -549,19 +554,19 @@ type PerpPositionDetail struct { ProductID currency.Pair `json:"product_id"` ProductUUID string `json:"product_uuid"` Symbol string `json:"symbol"` - VWAP ValCur `json:"vwap"` + VWAP ValueWithCurrency `json:"vwap"` PositionSide string `json:"position_side"` NetSize float64 `json:"net_size,string"` BuyOrderSize float64 `json:"buy_order_size,string"` SellOrderSize float64 `json:"sell_order_size,string"` IMContribution float64 `json:"im_contribution,string"` - UnrealizedPNL ValCur `json:"unrealized_pnl"` - MarkPrice ValCur `json:"mark_price"` - LiquidationPrice ValCur `json:"liquidation_price"` + UnrealizedPNL ValueWithCurrency `json:"unrealized_pnl"` + MarkPrice ValueWithCurrency `json:"mark_price"` + LiquidationPrice ValueWithCurrency `json:"liquidation_price"` Leverage float64 `json:"leverage,string"` - IMNotional ValCur `json:"im_notional"` - MMNotional ValCur `json:"mm_notional"` - PositionNotional ValCur `json:"position_notional"` + IMNotional ValueWithCurrency `json:"im_notional"` + MMNotional ValueWithCurrency `json:"mm_notional"` + PositionNotional ValueWithCurrency `json:"position_notional"` MarginType string `json:"margin_type"` LiquidationBuffer float64 `json:"liquidation_buffer,string"` LiquidationPercentage float64 `json:"liquidation_percentage,string"` @@ -644,11 +649,11 @@ type Disclosure struct { // FeeStruct is a sub-struct storing information on fees, used in ConvertResponse type FeeStruct struct { - Title string `json:"title"` - Description string `json:"description"` - Amount ValCur `json:"amount"` - Label string `json:"label"` - Disclosure Disclosure `json:"disclosure"` + Title string `json:"title"` + Description string `json:"description"` + Amount ValueWithCurrency `json:"amount"` + Label string `json:"label"` + Disclosure Disclosure `json:"disclosure"` } // Owner is a sub-struct, used in LedgerAccount @@ -675,8 +680,8 @@ type AccountStruct struct { // AmScale is a sub-struct storing information on amounts and scales, used in ConvertResponse type AmScale struct { - Amount ValCur `json:"amount"` - Scale int32 `json:"scale"` + Amount ValueWithCurrency `json:"amount"` + Scale int32 `json:"scale"` } // UnitPrice is a sub-struct used in ConvertResponse @@ -712,18 +717,18 @@ type CancellationReason struct { // TaxDetails is a sub-struct used in ConvertResponse type TaxDetails struct { - Name string `json:"name"` - Amount ValCur `json:"amount"` + Name string `json:"name"` + Amount ValueWithCurrency `json:"amount"` } // TradeIncentiveInfo is a sub-struct used in ConvertResponse type TradeIncentiveInfo struct { - AppliedIncentive bool `json:"applied_incentive"` - UserIncentiveID string `json:"user_incentive_id"` - CodeVal string `json:"code_val"` - EndsAt time.Time `json:"ends_at"` - FeeWithoutIncentive ValCur `json:"fee_without_incentive"` - Redeemed bool `json:"redeemed"` + AppliedIncentive bool `json:"applied_incentive"` + UserIncentiveID string `json:"user_incentive_id"` + CodeVal string `json:"code_val"` + EndsAt time.Time `json:"ends_at"` + FeeWithoutIncentive ValueWithCurrency `json:"fee_without_incentive"` + Redeemed bool `json:"redeemed"` } // ConvertResponse contains information on a convert trade, returned by CreateConvertQuote, @@ -731,10 +736,10 @@ type TradeIncentiveInfo struct { type ConvertResponse struct { ID string `json:"id"` Status string `json:"status"` - UserEnteredAmount ValCur `json:"user_entered_amount"` - Amount ValCur `json:"amount"` - Subtotal ValCur `json:"subtotal"` - Total ValCur `json:"total"` + UserEnteredAmount ValueWithCurrency `json:"user_entered_amount"` + Amount ValueWithCurrency `json:"amount"` + Subtotal ValueWithCurrency `json:"subtotal"` + Total ValueWithCurrency `json:"total"` Fees []FeeStruct `json:"fees"` TotalFee FeeStruct `json:"total_fee"` Source AccountStruct `json:"source"` @@ -747,22 +752,18 @@ type ConvertResponse struct { CancellationReason CancellationReason `json:"cancellation_reason"` SourceID string `json:"source_id"` TargetID string `json:"target_id"` - ExchangeRate ValCur `json:"exchange_rate"` + ExchangeRate ValueWithCurrency `json:"exchange_rate"` TaxDetails []TaxDetails `json:"tax_details"` TradeIncentiveInfo TradeIncentiveInfo `json:"trade_incentive_info"` TotalFeeWithoutTax FeeStruct `json:"total_fee_without_tax"` - FiatDenotedTotal ValCur `json:"fiat_denoted_total"` + FiatDenotedTotal ValueWithCurrency `json:"fiat_denoted_total"` } -// UnixTimestampMilli is a type used to unmarshal unix millisecond timestamps returned from -// the exchange, used in the type ServerTimeV3 -type UnixTimestampMilli time.Time - // ServerTimeV3 holds information on the server's time, returned by GetV3Time type ServerTimeV3 struct { - Iso time.Time `json:"iso"` - EpochSeconds UnixTimestamp `json:"epochSeconds"` - EpochMilliseconds UnixTimestampMilli `json:"epochMillis"` + Iso time.Time `json:"iso"` + EpochSeconds types.Time `json:"epochSeconds"` + EpochMilliseconds types.Time `json:"epochMillis"` } // PaymentMethodData is a sub-type that holds information on a payment method @@ -803,9 +804,7 @@ type PaginationResp struct { NextURI string `json:"next_uri"` } -// PaginationInp holds information needed to engage in pagination with Sign in With -// Coinbase. Used in ListNotifications, GetAllWallets, GetAllAddresses, GetAddressTransactions, -// GetAllTransactions, GetAllFiatTransfers, GetAllPaymentMethods, and preparePagination +// PaginationInp holds information needed to engage in pagination with Sign in With Coinbase. Used in ListNotifications, GetAllWallets, GetAllAddresses, GetAddressTransactions, GetAllTransactions, GetAllFiatTransfers, GetAllPaymentMethods, and preparePagination type PaginationInp struct { Limit uint8 OrderAscend bool @@ -813,45 +812,44 @@ type PaginationInp struct { EndingBefore string } -// AmCur is a sub-struct used in ListNotificationsSubData, WalletData, TransactionData, -// DeposWithdrData, and PaymentMethodData -type AmCur struct { +// AmountWithCurrency is a sub-struct used in ListNotificationsSubData, WalletData, TransactionData, DeposWithdrData, and PaymentMethodData +type AmountWithCurrency struct { Amount float64 `json:"amount,string"` Currency string `json:"currency"` } // Fees is a sub-struct used in ListNotificationsSubData type Fees []struct { - Type string `json:"type"` - Amount AmCur `json:"amount"` + Type string `json:"type"` + Amount AmountWithCurrency `json:"amount"` } // ListNotificationsSubData is a sub-struct used in ListNotificationsData type ListNotificationsSubData struct { - ID string `json:"id"` - Address string `json:"address"` - Name string `json:"name"` - Status string `json:"status"` - PaymentMethod IDResource `json:"payment_method"` - Transaction IDResource `json:"transaction"` - Amount AmCur `json:"amount"` - Total AmCur `json:"total"` - Subtotal AmCur `json:"subtotal"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` - Resource string `json:"resource"` - ResourcePath string `json:"resource_path"` - Committed bool `json:"committed"` - Instant bool `json:"instant"` - Fee AmCur `json:"fee"` - Fees []Fees `json:"fees"` - PayoutAt time.Time `json:"payout_at"` + ID string `json:"id"` + Address string `json:"address"` + Name string `json:"name"` + Status string `json:"status"` + PaymentMethod IDResource `json:"payment_method"` + Transaction IDResource `json:"transaction"` + Amount AmountWithCurrency `json:"amount"` + Total AmountWithCurrency `json:"total"` + Subtotal AmountWithCurrency `json:"subtotal"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + Resource string `json:"resource"` + ResourcePath string `json:"resource_path"` + Committed bool `json:"committed"` + Instant bool `json:"instant"` + Fee AmountWithCurrency `json:"fee"` + Fees []Fees `json:"fees"` + PayoutAt time.Time `json:"payout_at"` } // AdditionalData is a sub-struct used in ListNotificationsData type AdditionalData struct { - Hash string `json:"hash"` - Amount AmCur `json:"amount"` + Hash string `json:"hash"` + Amount AmountWithCurrency `json:"amount"` } // ListNotificationsData is a sub-struct used in ListNotificationsResponse @@ -958,18 +956,18 @@ type Currency struct { // WalletData is a sub-struct holding wallet information, used in GetAllWalletsResponse type WalletData struct { - ID string `json:"id"` - Name string `json:"name"` - Primary bool `json:"primary"` - Type string `json:"type"` - Currency Currency `json:"currency"` - Balance AmCur `json:"balance"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` - Resource string `json:"resource"` - ResourcePath string `json:"resource_path"` - AllowDeposits bool `json:"allow_deposits"` - AllowWithdrawals bool `json:"allow_withdrawals"` + ID string `json:"id"` + Name string `json:"name"` + Primary bool `json:"primary"` + Type string `json:"type"` + Currency Currency `json:"currency"` + Balance AmountWithCurrency `json:"balance"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + Resource string `json:"resource"` + ResourcePath string `json:"resource_path"` + AllowDeposits bool `json:"allow_deposits"` + AllowWithdrawals bool `json:"allow_withdrawals"` } // GetAllWalletsResponse holds information on many wallets, returned by GetAllWallets @@ -1067,20 +1065,20 @@ type Network struct { // TransactionData is a sub-type that holds information on a transaction. Used in // ManyTransactionsResp type TransactionData struct { - ID string `json:"id"` - Type string `json:"type"` - Status string `json:"status"` - Amount AmCur `json:"amount"` - NativeAmount AmCur `json:"native_amount"` - Description string `json:"description"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` - Resource string `json:"resource"` - ResourcePath string `json:"resource_path"` - Details TitleSubtitle `json:"details"` - Network Network `json:"network"` - To IDResource `json:"to"` - From IDResource `json:"from"` + ID string `json:"id"` + Type string `json:"type"` + Status string `json:"status"` + Amount AmountWithCurrency `json:"amount"` + NativeAmount AmountWithCurrency `json:"native_amount"` + Description string `json:"description"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + Resource string `json:"resource"` + ResourcePath string `json:"resource_path"` + Details TitleSubtitle `json:"details"` + Network Network `json:"network"` + To IDResource `json:"to"` + From IDResource `json:"from"` } // ManyTransactionsResp holds information on many transactions. Returned by @@ -1093,20 +1091,20 @@ type ManyTransactionsResp struct { // DeposWithdrData is a sub-type that holds information on a deposit/withdrawal. Used in // ManyDeposWithdrResp type DeposWithdrData struct { - ID string `json:"id"` - Status string `json:"status"` - PaymentMethod IDResource `json:"payment_method"` - Transaction IDResource `json:"transaction"` - Amount AmCur `json:"amount"` - Subtotal AmCur `json:"subtotal"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` - Resource string `json:"resource"` - ResourcePath string `json:"resource_path"` - Committed bool `json:"committed"` - Fee AmCur `json:"fee"` - PayoutAt time.Time `json:"payout_at"` - TransferType FiatTransferType `json:"transfer_type"` + ID string `json:"id"` + Status string `json:"status"` + PaymentMethod IDResource `json:"payment_method"` + Transaction IDResource `json:"transaction"` + Amount AmountWithCurrency `json:"amount"` + Subtotal AmountWithCurrency `json:"subtotal"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + Resource string `json:"resource"` + ResourcePath string `json:"resource_path"` + Committed bool `json:"committed"` + Fee AmountWithCurrency `json:"fee"` + PayoutAt time.Time `json:"payout_at"` + TransferType FiatTransferType `json:"transfer_type"` } // ManyDeposWithdrResp holds information on many deposits. Returned by GetAllFiatTransfers @@ -1185,7 +1183,7 @@ type WebsocketTickerHolder struct { // WebsocketCandle defines a candle websocket response, used in WebsocketCandleHolder type WebsocketCandle struct { - Start UnixTimestamp `json:"start"` + Start types.Time `json:"start"` Low float64 `json:"low,string"` High float64 `json:"high,string"` Open float64 `json:"open,string"` @@ -1429,12 +1427,12 @@ type Auction struct { // OrderBookResp holds information on bids and asks for a particular currency pair, used for unmarshalling in // GetProductBookV1 type OrderBookResp struct { - Bids [][3]interface{} `json:"bids"` - Asks [][3]interface{} `json:"asks"` - Sequence float64 `json:"sequence"` - AuctionMode bool `json:"auction_mode"` - Auction Auction `json:"auction"` - Time time.Time `json:"time"` + Bids [][3]any `json:"bids"` + Asks [][3]any `json:"asks"` + Sequence float64 `json:"sequence"` + AuctionMode bool `json:"auction_mode"` + Auction Auction `json:"auction"` + Time time.Time `json:"time"` } // Orders holds information on orders, used as a sub-struct in OrderBook @@ -1456,7 +1454,7 @@ type OrderBook struct { } // RawCandles holds raw candle data, used in unmarshalling for GetProductCandles -type RawCandles [6]interface{} +type RawCandles [6]any // Candle holds properly formatted candle data, returned by GetProductCandles type Candle struct { diff --git a/exchanges/coinbasepro/coinbasepro_websocket.go b/exchanges/coinbasepro/coinbasepro_websocket.go index cc57655bed3..723b80fd284 100644 --- a/exchanges/coinbasepro/coinbasepro_websocket.go +++ b/exchanges/coinbasepro/coinbasepro_websocket.go @@ -535,11 +535,7 @@ func getTimestamp(rawData []byte) (time.Time, error) { if err != nil { return time.Time{}, err } - timestamp, err := time.Parse(time.RFC3339, string(data)) - if err != nil { - return time.Time{}, err - } - return timestamp, nil + return time.Parse(time.RFC3339, string(data)) } // processBidAskArray is a helper function that turns WebsocketOrderbookDataHolder into arrays of bids and asks @@ -609,7 +605,7 @@ func stringToStandardAsset(str string) (asset.Item, error) { } // strategyDecoder is a helper function that converts a Coinbase Pro time in force string to a few standardised bools -func strategyDecoder(str string) (bool, bool, error) { +func strategyDecoder(str string) (ioc, fok bool, err error) { switch str { case "IMMEDIATE_OR_CANCEL": return true, false, nil diff --git a/exchanges/coinbasepro/coinbasepro_wrapper.go b/exchanges/coinbasepro/coinbasepro_wrapper.go index c3ce3a819f1..a675e7ba905 100644 --- a/exchanges/coinbasepro/coinbasepro_wrapper.go +++ b/exchanges/coinbasepro/coinbasepro_wrapper.go @@ -346,32 +346,32 @@ func (c *CoinbasePro) UpdateOrderbook(ctx context.Context, p currency.Pair, asse Asset: assetType, VerifyOrderbook: c.CanVerifyOrderbook, } - var orderbookNew *ProductBook + var orderbookNew *ProductBookResp if verified { - orderbookNew, err = c.GetProductBookV3(ctx, p.String(), 1000, true) + orderbookNew, err = c.GetProductBookV3(ctx, p, 1000, 0, true) if err != nil { log.Warnf(log.ExchangeSys, warnAuth, err) verified = false } } if !verified { - orderbookNew, err = c.GetProductBookV3(ctx, p.String(), 1000, false) + orderbookNew, err = c.GetProductBookV3(ctx, p, 1000, 0, false) if err != nil { return book, err } } - book.Bids = make(orderbook.Tranches, len(orderbookNew.Bids)) - for x := range orderbookNew.Bids { + book.Bids = make(orderbook.Tranches, len(orderbookNew.Pricebook.Bids)) + for x := range orderbookNew.Pricebook.Bids { book.Bids[x] = orderbook.Tranche{ - Amount: orderbookNew.Bids[x].Size, - Price: orderbookNew.Bids[x].Price, + Amount: orderbookNew.Pricebook.Bids[x].Size, + Price: orderbookNew.Pricebook.Bids[x].Price, } } - book.Asks = make(orderbook.Tranches, len(orderbookNew.Asks)) - for x := range orderbookNew.Asks { + book.Asks = make(orderbook.Tranches, len(orderbookNew.Pricebook.Asks)) + for x := range orderbookNew.Pricebook.Asks { book.Asks[x] = orderbook.Tranche{ - Amount: orderbookNew.Asks[x].Size, - Price: orderbookNew.Asks[x].Price, + Amount: orderbookNew.Pricebook.Asks[x].Size, + Price: orderbookNew.Pricebook.Asks[x].Price, } } err = book.Process() @@ -1082,26 +1082,26 @@ func (c *CoinbasePro) iterativeGetAllOrders(ctx context.Context, productID, orde } // FormatExchangeKlineIntervalV3 is a helper function used in GetHistoricCandles and GetHistoricCandlesExtended to convert kline.Interval to the string format used by V3 of Coinbase's API -func FormatExchangeKlineIntervalV3(interval kline.Interval) string { +func FormatExchangeKlineIntervalV3(interval kline.Interval) (string, error) { switch interval { case kline.OneMin: - return granOneMin + return granOneMin, nil case kline.FiveMin: - return granFiveMin + return granFiveMin, nil case kline.FifteenMin: - return granFifteenMin + return granFifteenMin, nil case kline.ThirtyMin: - return granThirtyMin + return granThirtyMin, nil case kline.OneHour: - return granOneHour + return granOneHour, nil case kline.TwoHour: - return granTwoHour + return granTwoHour, nil case kline.SixHour: - return granSixHour + return granSixHour, nil case kline.OneDay: - return granOneDay + return granOneDay, nil } - return errIntervalNotSupported + return "", errIntervalNotSupported } // getOrderRespToOrderDetail is a helper function used in GetOrderInfo, GetActiveOrders, and GetOrderHistory to convert data returned by the Coinbase API into a format suitable for the exchange package @@ -1245,7 +1245,11 @@ func (c *CoinbasePro) tickerHelper(ctx context.Context, name string, assetType a // CandleHelper handles calling the candle function, and doing preliminary work on the data func (c *CoinbasePro) candleHelper(ctx context.Context, pair string, granularity kline.Interval, start, end time.Time, verified bool) ([]kline.Candle, error) { - history, err := c.GetHistoricRates(ctx, pair, FormatExchangeKlineIntervalV3(granularity), start, end, verified) + granString, err := FormatExchangeKlineIntervalV3(granularity) + if err != nil { + return nil, err + } + history, err := c.GetHistoricRates(ctx, pair, granString, start, end, verified) if err != nil { if verified { return c.candleHelper(ctx, pair, granularity, start, end, false) From a4252ec167df33f77643348a3c7472dbf53bb5b7 Mon Sep 17 00:00:00 2001 From: Samuel Reid <43227667+cranktakular@users.noreply.github.com> Date: Tue, 12 Nov 2024 12:31:39 +1100 Subject: [PATCH 75/79] Lint nit --- exchanges/coinbasepro/coinbasepro_types.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exchanges/coinbasepro/coinbasepro_types.go b/exchanges/coinbasepro/coinbasepro_types.go index 2eea519200d..727ba55d342 100644 --- a/exchanges/coinbasepro/coinbasepro_types.go +++ b/exchanges/coinbasepro/coinbasepro_types.go @@ -120,7 +120,7 @@ type FutureProductDetails struct { ContractExpiryType string `json:"contract_expiry_type"` PerpetualDetails PerpetualDetails `json:"perpetual_details"` ContractDisplayName string `json:"contract_display_name"` - TimeToExpiryMS time.Duration `json:"time_to_expiry_ms,string"` + TimeToExpiry time.Duration `json:"time_to_expiry_ms,string"` NonCrypto bool `json:"non_crypto"` ContractExpiryName string `json:"contract_expiry_name"` } From ff6ce5f85cf57ebe63367f5a04d495729e74aa61 Mon Sep 17 00:00:00 2001 From: Samuel Reid <43227667+cranktakular@users.noreply.github.com> Date: Wed, 27 Nov 2024 18:03:39 +1100 Subject: [PATCH 76/79] JWT move and other fixes --- exchanges/coinbasepro/coinbasepro.go | 101 ++++++++++++----- exchanges/coinbasepro/coinbasepro_test.go | 22 ++-- exchanges/coinbasepro/coinbasepro_types.go | 107 ++++++------------ .../coinbasepro/coinbasepro_websocket.go | 85 +++----------- exchanges/coinbasepro/coinbasepro_wrapper.go | 6 +- go.mod | 1 + go.sum | 2 + 7 files changed, 141 insertions(+), 183 deletions(-) diff --git a/exchanges/coinbasepro/coinbasepro.go b/exchanges/coinbasepro/coinbasepro.go index b2ecca9c796..d8c884b5ed4 100644 --- a/exchanges/coinbasepro/coinbasepro.go +++ b/exchanges/coinbasepro/coinbasepro.go @@ -3,8 +3,9 @@ package coinbasepro import ( "bytes" "context" - "encoding/hex" + "crypto/x509" "encoding/json" + "encoding/pem" "errors" "fmt" "net/http" @@ -14,8 +15,8 @@ import ( "time" "github.com/gofrs/uuid" + "github.com/golang-jwt/jwt" "github.com/thrasher-corp/gocryptotrader/common" - "github.com/thrasher-corp/gocryptotrader/common/crypto" "github.com/thrasher-corp/gocryptotrader/common/key" "github.com/thrasher-corp/gocryptotrader/currency" exchange "github.com/thrasher-corp/gocryptotrader/exchanges" @@ -155,6 +156,7 @@ var ( errUnrecognisedAssetType = errors.New("unrecognised asset type") errUnrecognisedStrategyType = errors.New("unrecognised strategy type") errIntervalNotSupported = errors.New("interval not supported") + errEndpointPathInvalid = errors.New("endpoint path invalid, should start with https://") allowedGranularities = []string{granOneMin, granFiveMin, granFifteenMin, granThirtyMin, granOneHour, granTwoHour, granSixHour, granOneDay} closedStatuses = []string{"FILLED", "CANCELLED", "EXPIRED", "FAILED"} @@ -428,6 +430,7 @@ func (c *CoinbasePro) GetAllOrders(ctx context.Context, productID, userNativeCur if contractExpiryType != "" { params.Values.Set("contract_expiry_type", contractExpiryType) } + // This functionality has been deprecated, and only works for legacy API keys if retailPortfolioID != "" { params.Values.Set("retail_portfolio_id", retailPortfolioID) } @@ -1396,19 +1399,14 @@ func (c *CoinbasePro) SendHTTPRequest(ctx context.Context, ep exchange.URL, path // SendAuthenticatedHTTPRequest sends an authenticated HTTP request func (c *CoinbasePro) SendAuthenticatedHTTPRequest(ctx context.Context, ep exchange.URL, method, path string, queryParams url.Values, bodyParams map[string]any, isVersion3 bool, result any, returnHead *http.Header) (err error) { - creds, err := c.GetCredentials(ctx) - if err != nil { - return err - } endpoint, err := c.API.Endpoints.GetURL(ep) if err != nil { return err } - queryString := common.EncodeURLValues("", queryParams) - // Version 2 wants query params in the path during signing - if !isVersion3 { - path += queryString + if len(endpoint) < 8 { + return errEndpointPathInvalid } + queryString := common.EncodeURLValues("", queryParams) interim := json.RawMessage{} newRequest := func() (*request.Item, error) { payload := []byte("") @@ -1418,31 +1416,15 @@ 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), - []byte(creds.Secret)) + jwt, _, err := c.GetJWT(ctx, method+" "+endpoint[8:]+path) if err != nil { return nil, err } - // TODO: Implement JWT authentication once it's supported by all endpoints we care about - // jwt, err := c.GetJWT(ctx, method+" "+path) - // if err != nil { - // return nil, err - // } 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-09-24" - // headers["Authorization"] = "Bearer " + jwt - // Version 3 only wants query params in the path when the request is sent - if isVersion3 { - path += queryString - } + headers["CB-VERSION"] = "2024-11-27" + headers["Authorization"] = "Bearer " + jwt + path += queryString return &request.Item{ Method: method, Path: endpoint + path, @@ -1505,6 +1487,65 @@ func (c *CoinbasePro) SendAuthenticatedHTTPRequest(ctx context.Context, ep excha return json.Unmarshal(interim, result) } +// GetJWT generates a new JWT +func (c *CoinbasePro) GetJWT(ctx context.Context, uri string) (string, time.Time, error) { + creds, err := c.GetCredentials(ctx) + if err != nil { + return "", time.Time{}, err + } + block, _ := pem.Decode([]byte(creds.Secret)) + if block == nil { + return "", time.Time{}, errCantDecodePrivKey + } + key, err := x509.ParseECPrivateKey(block.Bytes) + if err != nil { + return "", time.Time{}, err + } + nonce, err := common.GenerateRandomString(16, "1234567890ABCDEF") + if err != nil { + return "", time.Time{}, err + } + regTime := time.Now() + mapClaims := jwt.MapClaims{ + "iss": "cdp", + "nbf": regTime.Unix(), + "exp": regTime.Add(time.Minute * 2).Unix(), + "sub": creds.Key, + } + if uri != "" { + mapClaims["uri"] = uri + } + tok := jwt.NewWithClaims(jwt.SigningMethodES256, mapClaims) + tok.Header["kid"] = creds.Key + tok.Header["nonce"] = nonce + sign, err := tok.SignedString(key) + return sign, regTime, err + // The code below mostly works, but seems to lead to bad results on the signature step. Deferring until later + // head := map[string]any{"kid": creds.Key, "typ": "JWT", "alg": "ES256", "nonce": nonce} + // headJSON, err := json.Marshal(head) + // if err != nil { + // return "", time.Time{}, err + // } + // headEncode := base64URLEncode(headJSON) + // regTime := time.Now() + // body := map[string]any{"iss": "cdp", "nbf": regTime.Unix(), "exp": regTime.Add(time.Minute * 2).Unix(), "sub": creds.Key /*, "aud": "retail_rest_api_proxy"*/} + // if uri != "" { + // body["uri"] = uri + // } + // bodyJSON, err := json.Marshal(body) + // if err != nil { + // return "", time.Time{}, err + // } + // bodyEncode := base64URLEncode(bodyJSON) + // hash := sha256.Sum256([]byte(headEncode + "." + bodyEncode)) + // sig, err := ecdsa.SignASN1(rand.Reader, key, hash[:]) + // if err != nil { + // return "", time.Time{}, err + // } + // sigEncode := base64URLEncode(sig) + // return headEncode + "." + bodyEncode + "." + sigEncode, regTime, nil +} + // GetFee returns an estimate of fee based on type of transaction func (c *CoinbasePro) GetFee(ctx context.Context, feeBuilder *exchange.FeeBuilder) (float64, error) { if feeBuilder == nil { diff --git a/exchanges/coinbasepro/coinbasepro_test.go b/exchanges/coinbasepro/coinbasepro_test.go index b2a96c5bc5f..303ad47c45d 100644 --- a/exchanges/coinbasepro/coinbasepro_test.go +++ b/exchanges/coinbasepro/coinbasepro_test.go @@ -326,7 +326,7 @@ func TestGetAllOrders(t *testing.T) { status = make([]string, 0) assets = make([]string, 1) assets[0] = testCrypto.String() - _, err = c.GetAllOrders(context.Background(), "", testFiat.String(), "LIMIT", "SELL", "", "SPOT", "RETAIL_ADVANCED", "UNKNOWN_CONTRACT_EXPIRY_TYPE", "2", status, assets, 10, time.Time{}, time.Time{}) + _, err = c.GetAllOrders(context.Background(), "", testFiat.String(), "LIMIT", "SELL", "", "SPOT", "RETAIL_ADVANCED", "UNKNOWN_CONTRACT_EXPIRY_TYPE", "", status, assets, 10, time.Time{}, time.Time{}) assert.NoError(t, err) } @@ -435,6 +435,7 @@ func TestDeletePortfolio(t *testing.T) { assert.ErrorIs(t, err, errPortfolioIDEmpty) pID := portfolioIDFromName(t, "GCT Test Portfolio To-Delete") err = c.DeletePortfolio(context.Background(), pID) + // The new JWT-based keys don't have permissions to delete portfolios they aren't assigned to, causing this to fail assert.NoError(t, err) } @@ -446,6 +447,7 @@ func TestEditPortfolio(t *testing.T) { assert.ErrorIs(t, err, errNameEmpty) pID := portfolioIDFromName(t, "GCT Test Portfolio To-Edit") _, err = c.EditPortfolio(context.Background(), pID, "GCT Test Portfolio Edited") + // The new JWT-based keys don't have permissions to edit portfolios they aren't assigned to, causing this to fail if err != nil && err.Error() != errPortfolioNameDuplicate { t.Error(err) } @@ -586,6 +588,7 @@ func TestGetPaymentMethodByID(t *testing.T) { func TestGetCurrentUser(t *testing.T) { t.Parallel() sharedtestvalues.SkipTestIfCredentialsUnset(t, c) + // This intermittently fails with the message "Unauthorized", for no clear reason testGetNoArgs(t, c.GetCurrentUser) } @@ -595,7 +598,7 @@ func TestGetAllWallets(t *testing.T) { pagIn := PaginationInp{Limit: 2} resp, err := c.GetAllWallets(context.Background(), pagIn) assert.NoError(t, err) - assert.NotEmpty(t, resp, errExpectedNonEmpty) + require.NotEmpty(t, resp, errExpectedNonEmpty) if resp.Pagination.NextStartingAfter == "" { t.Skip(skipInsufficientWallets) } @@ -963,11 +966,7 @@ func TestSendHTTPRequest(t *testing.T) { func TestSendAuthenticatedHTTPRequest(t *testing.T) { t.Parallel() - fc := &CoinbasePro{} - 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, nil, false, nil, nil) + err := c.SendAuthenticatedHTTPRequest(context.Background(), exchange.EdgeCase3, "", "", nil, nil, false, nil, nil) assert.ErrorIs(t, err, exchange.ErrEndpointPathNotFound) ch := make(chan struct{}) body := map[string]any{"Unmarshalable": ch} @@ -1518,9 +1517,10 @@ func TestWsAuth(t *testing.T) { go c.wsReadData() err = c.Subscribe(subscription.List{ { - Channel: "myAccount", - Asset: asset.All, - Pairs: p, + Channel: "myAccount", + Asset: asset.All, + Pairs: p, + Authenticated: true, }, }) assert.NoError(t, err) @@ -1705,7 +1705,7 @@ func TestCheckSubscriptions(t *testing.T) { func TestGetJWT(t *testing.T) { t.Parallel() sharedtestvalues.SkipTestIfCredentialsUnset(t, c) - _, err := c.GetJWT(context.Background(), "") + _, _, err := c.GetJWT(context.Background(), "") assert.NoError(t, err) } diff --git a/exchanges/coinbasepro/coinbasepro_types.go b/exchanges/coinbasepro/coinbasepro_types.go index 727ba55d342..a5c3664ec7f 100644 --- a/exchanges/coinbasepro/coinbasepro_types.go +++ b/exchanges/coinbasepro/coinbasepro_types.go @@ -2,6 +2,7 @@ package coinbasepro import ( "net/url" + "sync" "time" "github.com/gofrs/uuid" @@ -16,26 +17,22 @@ type CoinbasePro struct { exchange.Base jwt string jwtLastRegen time.Time + mut sync.RWMutex } -// Version is used for the niche cases where the Version of the API must be specified and passed -// around for proper functionality +// Version is used for the niche cases where the Version of the API must be specified and passed around for proper functionality type Version bool -// FiatTransferType is used so that we don't need to duplicate the four fiat transfer-related -// endpoints under version 2 of the API +// FiatTransferType is used so that we don't need to duplicate the four fiat transfer-related endpoints under version 2 of the API type FiatTransferType bool -// ValueWithCurrency is a sub-struct used in the types Account, NativeAndRaw, DetailedPortfolioResponse, -// FuturesBalanceSummary, ListFuturesSweepsResponse, PerpetualsPortfolioSummary, PerpPositionDetail, -// FeeStruct, AmScale, and ConvertResponse +// ValueWithCurrency is a sub-struct used in the types Account, NativeAndRaw, DetailedPortfolioResponse, FuturesBalanceSummary, ListFuturesSweepsResponse, PerpetualsPortfolioSummary, PerpPositionDetail, FeeStruct, AmScale, and ConvertResponse type ValueWithCurrency struct { Value float64 `json:"value,string"` Currency string `json:"currency"` } -// Account holds details for a trading account, returned by GetAccountByID and used as -// a sub-struct in the type AllAccountsResponse +// Account holds details for a trading account, returned by GetAccountByID and used as a sub-struct in the type AllAccountsResponse type Account struct { UUID string `json:"uuid"` Name string `json:"name"` @@ -51,8 +48,7 @@ type Account struct { Hold ValueWithCurrency `json:"hold"` } -// AllAccountsResponse holds many Account structs, as well as pagination information, -// returned by GetAllAccounts +// AllAccountsResponse holds many Account structs, as well as pagination information, returned by GetAllAccounts type AllAccountsResponse struct { Accounts []Account `json:"accounts"` HasNext bool `json:"has_next"` @@ -125,8 +121,7 @@ type FutureProductDetails struct { ContractExpiryName string `json:"contract_expiry_name"` } -// Product holds product information, returned by GetProductByID, and used as a sub-struct -// in the type AllProducts +// Product holds product information, returned by GetProductByID, and used as a sub-struct in the type AllProducts type Product struct { ID currency.Pair `json:"product_id"` Price types.Number `json:"price"` @@ -167,8 +162,7 @@ type Product struct { FutureProductDetails FutureProductDetails `json:"future_product_details"` } -// AllProducts holds information on a lot of available currency pairs, returned by -// GetAllProducts +// AllProducts holds information on a lot of available currency pairs, returned by GetAllProducts type AllProducts struct { Products []Product `json:"products"` NumProducts int32 `json:"num_products"` @@ -241,8 +235,7 @@ type StopLimitStopLimitGTD struct { StopDirection string `json:"stop_direction"` } -// OrderConfiguration is a struct used in the formation of requests in PrepareOrderConfig, and is -// a sub-struct used in the types PlaceOrderResp and GetOrderResponse +// OrderConfiguration is a struct used in the formation of requests in PrepareOrderConfig, and is a sub-struct used in the types PlaceOrderResp and GetOrderResponse type OrderConfiguration struct { MarketMarketIOC *MarketMarketIOC `json:"market_market_ioc,omitempty"` LimitLimitGTC *LimitLimitGTC `json:"limit_limit_gtc,omitempty"` @@ -275,8 +268,7 @@ type OrderCancelDetail struct { OrderID string `json:"order_id"` } -// EditOrderPreviewResp contains information on the effects of editing an order, -// returned by EditOrderPreview +// EditOrderPreviewResp contains information on the effects of editing an order, returned by EditOrderPreview type EditOrderPreviewResp struct { Slippage float64 `json:"slippage,string"` OrderTotal float64 `json:"order_total,string"` @@ -295,8 +287,7 @@ type EditHistory struct { ReplaceAcceptTimestamp time.Time `json:"replace_accept_timestamp"` } -// GetOrderResponse contains information on an order, returned by GetOrderByID IterativeGetAllOrders, and used in -// GetAllOrdersResp +// GetOrderResponse contains information on an order, returned by GetOrderByID IterativeGetAllOrders, and used in GetAllOrdersResp type GetOrderResponse struct { OrderID string `json:"order_id"` ProductID currency.Pair `json:"product_id"` @@ -359,8 +350,7 @@ type FillResponse struct { Cursor string `json:"cursor"` } -// PreviewOrderResp contains information on the effects of placing an order, returned by -// PreviewOrder +// PreviewOrderResp contains information on the effects of placing an order, returned by PreviewOrder type PreviewOrderResp struct { OrderTotal float64 `json:"order_total,string"` CommissionTotal float64 `json:"commission_total,string"` @@ -386,8 +376,7 @@ type SimplePortfolioData struct { Deleted bool `json:"deleted"` } -// MovePortfolioFundsResponse contains the UUIDs of the portfolios involved. Returned by -// MovePortfolioFunds +// MovePortfolioFundsResponse contains the UUIDs of the portfolios involved. Returned by MovePortfolioFunds type MovePortfolioFundsResponse struct { SourcePortfolioUUID string `json:"source_portfolio_uuid"` TargetPortfolioUUID string `json:"target_portfolio_uuid"` @@ -470,8 +459,7 @@ type FuturesPositions []struct { NotionalValue float64 `json:"notional_value,string"` } -// DetailedPortfolioResponse contains a great deal of information on a single portfolio. -// Returned by GetPortfolioByID +// DetailedPortfolioResponse contains a great deal of information on a single portfolio. Returned by GetPortfolioByID type DetailedPortfolioResponse struct { Portfolio SimplePortfolioData `json:"portfolio"` PortfolioBalances PortfolioBalances `json:"portfolio_balances"` @@ -480,8 +468,7 @@ type DetailedPortfolioResponse struct { FuturesPositions []FuturesPositions `json:"futures_positions"` } -// FuturesBalanceSummary contains information on futures balances, returned by -// GetFuturesBalanceSummary +// FuturesBalanceSummary contains information on futures balances, returned by GetFuturesBalanceSummary type FuturesBalanceSummary struct { FuturesBuyingPower ValueWithCurrency `json:"futures_buying_power"` TotalUSDBalance ValueWithCurrency `json:"total_usd_balance"` @@ -497,11 +484,9 @@ type FuturesBalanceSummary struct { LiquidationBufferPercentage float64 `json:"liquidation_buffer_percentage,string"` } -// FuturesPosition contains information on a single futures position, returned by -// GetFuturesPositionByID +// FuturesPosition contains information on a single futures position, returned by GetFuturesPositionByID type FuturesPosition struct { - // This may belong in a struct of its own called "position", requiring a bit - // more abstraction, but for the moment I'll assume it doesn't + // This may belong in a struct of its own called "position", requiring a bit more abstraction, but for the moment I'll assume it doesn't ProductID currency.Pair `json:"product_id"` ExpirationTime time.Time `json:"expiration_time"` Side string `json:"side"` @@ -521,9 +506,7 @@ type SweepData struct { ScheduledTime time.Time `json:"scheduled_time"` } -// PerpetualsPortfolioSummary contains information on perpetuals portfolio balances, used as -// a sub-struct in the types PerpPositionDetail, AllPerpPosResponse, and -// OnePerpPosResponse +// PerpetualsPortfolioSummary contains information on perpetuals portfolio balances, used as a sub-struct in the types PerpPositionDetail, AllPerpPosResponse, and OnePerpPosResponse type PerpetualsPortfolioSummary struct { PortfolioUUID string `json:"portfolio_uuid"` Collateral float64 `json:"collateral,string"` @@ -548,8 +531,7 @@ type PerpetualsPortfolioSummary struct { MaxWithDrawal ValueWithCurrency `json:"max_withdrawal"` } -// PerpPositionDetail contains information on a single perpetuals position, used as a sub-struct -// in the types AllPerpPosResponse and OnePerpPosResponse +// PerpPositionDetail contains information on a single perpetuals position, used as a sub-struct in the types AllPerpPosResponse and OnePerpPosResponse type PerpPositionDetail struct { ProductID currency.Pair `json:"product_id"` ProductUUID string `json:"product_uuid"` @@ -573,15 +555,13 @@ type PerpPositionDetail struct { PortfolioSummary PerpetualsPortfolioSummary `json:"portfolio_summary"` } -// AllPerpPosResponse contains information on perpetuals positions, returned by -// GetAllPerpetualsPositions +// AllPerpPosResponse contains information on perpetuals positions, returned by GetAllPerpetualsPositions type AllPerpPosResponse struct { Positions []PerpPositionDetail `json:"positions"` PortfolioSummary PerpetualsPortfolioSummary `json:"portfolio_summary"` } -// OnePerpPosResponse contains information on a single perpetuals position, returned by -// GetPerpetualsPositionByID +// OnePerpPosResponse contains information on a single perpetuals position, returned by GetPerpetualsPositionByID type OnePerpPosResponse struct { Position PerpPositionDetail `json:"position"` PortfolioSummary PerpetualsPortfolioSummary `json:"portfolio_summary"` @@ -609,8 +589,7 @@ type GoodsAndServicesTax struct { Type string `json:"type"` } -// TransactionSummary contains a summary of transaction fees, volume, and the like. Returned -// by GetTransactionSummary +// TransactionSummary contains a summary of transaction fees, volume, and the like. Returned by GetTransactionSummary type TransactionSummary struct { TotalVolume float64 `json:"total_volume"` TotalFees float64 `json:"total_fees"` @@ -633,8 +612,7 @@ type GetAllOrdersResp struct { Cursor string `json:"cursor"` } -// LinkStruct is a sub-struct storing information on links, used in Disclosure and -// ConvertResponse +// LinkStruct is a sub-struct storing information on links, used in Disclosure and ConvertResponse type LinkStruct struct { Text string `json:"text"` URL string `json:"url"` @@ -731,8 +709,7 @@ type TradeIncentiveInfo struct { Redeemed bool `json:"redeemed"` } -// ConvertResponse contains information on a convert trade, returned by CreateConvertQuote, -// CommitConvertTrade, and GetConvertTradeByID +// ConvertResponse contains information on a convert trade, returned by CreateConvertQuote, CommitConvertTrade, and GetConvertTradeByID type ConvertResponse struct { ID string `json:"id"` Status string `json:"status"` @@ -781,8 +758,7 @@ type PaymentMethodData struct { UpdatedAt time.Time `json:"updated_at"` } -// IDResource holds an ID, resource type, and associated data, used in ListNotificationsResponse, -// TransactionData, DeposWithdrData, and PaymentMethodData +// IDResource holds an ID, resource type, and associated data, used in ListNotificationsResponse, TransactionData, DeposWithdrData, and PaymentMethodData type IDResource struct { ID string `json:"id"` Resource string `json:"resource"` @@ -790,9 +766,7 @@ type IDResource struct { Email string `json:"email"` } -// PaginationResp holds pagination information, used in ListNotificationsResponse, -// GetAllWalletsResponse, GetAllAddrResponse, ManyTransactionsResp, ManyDeposWithdrResp, -// and GetAllPaymentMethodsResp +// PaginationResp holds pagination information, used in ListNotificationsResponse, GetAllWalletsResponse, GetAllAddrResponse, ManyTransactionsResp, ManyDeposWithdrResp, and GetAllPaymentMethodsResp type PaginationResp struct { EndingBefore string `json:"ending_before"` StartingAfter string `json:"starting_after"` @@ -867,8 +841,7 @@ type ListNotificationsData struct { Transaction IDResource `json:"transaction"` } -// ListNotificationsResponse holds information on notifications that the user is subscribed -// to. Returned by ListNotifications +// ListNotificationsResponse holds information on notifications that the user is subscribed to. Returned by ListNotifications type ListNotificationsResponse struct { Pagination PaginationResp `json:"pagination"` Data []ListNotificationsData `json:"data"` @@ -1062,8 +1035,7 @@ type Network struct { Name string `json:"name"` } -// TransactionData is a sub-type that holds information on a transaction. Used in -// ManyTransactionsResp +// TransactionData is a sub-type that holds information on a transaction. Used in ManyTransactionsResp type TransactionData struct { ID string `json:"id"` Type string `json:"type"` @@ -1081,15 +1053,13 @@ type TransactionData struct { From IDResource `json:"from"` } -// ManyTransactionsResp holds information on many transactions. Returned by -// GetAddressTransactions and GetAllTransactions +// ManyTransactionsResp holds information on many transactions. Returned by GetAddressTransactions and GetAllTransactions type ManyTransactionsResp struct { Pagination PaginationResp `json:"pagination"` Data []TransactionData `json:"data"` } -// DeposWithdrData is a sub-type that holds information on a deposit/withdrawal. Used in -// ManyDeposWithdrResp +// DeposWithdrData is a sub-type that holds information on a deposit/withdrawal. Used in ManyDeposWithdrResp type DeposWithdrData struct { ID string `json:"id"` Status string `json:"status"` @@ -1208,8 +1178,7 @@ type WebsocketMarketTrade struct { Time time.Time `json:"time"` } -// WebsocketMarketTradeHolder holds a variety of market trade responses, used when wsHandleData -// processes trades +// WebsocketMarketTradeHolder holds a variety of market trade responses, used when wsHandleData processes trades type WebsocketMarketTradeHolder struct { Type string `json:"type"` Trades []WebsocketMarketTrade `json:"trades"` @@ -1229,8 +1198,7 @@ type WebsocketProduct struct { MinMarketFunds float64 `json:"min_market_funds,string"` } -// WebsocketProductHolder holds a variety of product responses, used when wsHandleData processes -// an update on a product's status +// WebsocketProductHolder holds a variety of product responses, used when wsHandleData processes an update on a product's status type WebsocketProductHolder struct { Type string `json:"type"` Products []WebsocketProduct `json:"products"` @@ -1244,8 +1212,7 @@ type WebsocketOrderbookData struct { NewQuantity float64 `json:"new_quantity,string"` } -// WebsocketOrderbookDataHolder holds a variety of orderbook responses, used when wsHandleData processes -// orderbooks, as well as under typical operation of ProcessSnapshot, ProcessUpdate, and processBidAskArray +// WebsocketOrderbookDataHolder holds a variety of orderbook responses, used when wsHandleData processes orderbooks, as well as under typical operation of ProcessSnapshot, ProcessUpdate, and processBidAskArray type WebsocketOrderbookDataHolder struct { Type string `json:"type"` ProductID currency.Pair `json:"product_id"` @@ -1424,8 +1391,7 @@ type Auction struct { Time time.Time `json:"time"` } -// OrderBookResp holds information on bids and asks for a particular currency pair, used for unmarshalling in -// GetProductBookV1 +// OrderBookResp holds information on bids and asks for a particular currency pair, used for unmarshalling in GetProductBookV1 type OrderBookResp struct { Bids [][3]any `json:"bids"` Asks [][3]any `json:"asks"` @@ -1516,8 +1482,7 @@ type AllWrappedAssets struct { WrappedAssets []WrappedAsset `json:"wrapped_assets"` } -// WrappedAssetConversionRate holds information on a wrapped asset's conversion rate, returned by -// GetWrappedAssetConversionRate +// WrappedAssetConversionRate holds information on a wrapped asset's conversion rate, returned by GetWrappedAssetConversionRate type WrappedAssetConversionRate struct { Amount float64 `json:"amount,string"` } diff --git a/exchanges/coinbasepro/coinbasepro_websocket.go b/exchanges/coinbasepro/coinbasepro_websocket.go index 723b80fd284..7fec5fd0bde 100644 --- a/exchanges/coinbasepro/coinbasepro_websocket.go +++ b/exchanges/coinbasepro/coinbasepro_websocket.go @@ -2,13 +2,7 @@ package coinbasepro import ( "context" - "crypto/ecdsa" - "crypto/rand" - "crypto/sha256" - "crypto/x509" - "encoding/hex" "encoding/json" - "encoding/pem" "fmt" "net/http" "strconv" @@ -21,7 +15,6 @@ import ( "github.com/pkg/errors" "github.com/thrasher-corp/gocryptotrader/common" "github.com/thrasher-corp/gocryptotrader/common/crypto" - "github.com/thrasher-corp/gocryptotrader/currency" "github.com/thrasher-corp/gocryptotrader/exchanges/asset" "github.com/thrasher-corp/gocryptotrader/exchanges/margin" "github.com/thrasher-corp/gocryptotrader/exchanges/order" @@ -124,8 +117,7 @@ func (c *CoinbasePro) wsHandleData(respRaw []byte, seqCount uint64) (string, err return warnString, err } if seqNum != seqCount { - warnString = fmt.Sprintf(warnSequenceIssue, seqNum, - seqCount) + warnString = fmt.Sprintf(warnSequenceIssue, seqNum, seqCount) } channelRaw, _, _, err := jsonparser.Get(respRaw, "channel") if err != nil { @@ -462,71 +454,28 @@ func (c *CoinbasePro) manageSubs(op string, subs subscription.List) error { } func (c *CoinbasePro) signWsRequest(r *WebsocketRequest) error { - // 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 - // jwt, err := c.GetJWT(context.Background(), "") - // if err != nil { - // return err - // } - // c.jwt, r.JWT = jwt, jwt - creds, err := c.GetCredentials(context.Background()) + jwt, err := c.GetWSJWT() if err != nil { return err } - hmac, err := crypto.GetHMAC(crypto.HashSHA256, []byte(r.Timestamp+r.Channel+currency.Pairs(r.ProductIDs).Join()), []byte(creds.Secret)) - if err != nil { - return err - } - r.Key = creds.Key - r.Signature = hex.EncodeToString(hmac) + r.JWT = jwt return nil } -// GetJWT checks if the current JWT is valid, returns it if it is, generates a new one if it isn't. Also suitable for use in REST requests, by checking for the presence of a URI, and always generating a new JWT if one is provided -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(16, "1234567890ABCDEF") - if err != nil { - return "", err - } - head := map[string]any{"kid": creds.ClientID, "typ": "JWT", "alg": "ES256", "nonce": nonce} - headJSON, err := json.Marshal(head) - if err != nil { - return "", err - } - headEncode := base64URLEncode(headJSON) - body := map[string]any{"iss": "cdp", "nbf": time.Now().Unix(), "exp": time.Now().Add(time.Minute * 2).Unix(), "sub": creds.ClientID, "aud": "retail_rest_api_proxy"} - if uri != "" { - body["uri"] = uri - } else { - c.jwtLastRegen = time.Now() - } - bodyJSON, err := json.Marshal(body) - if err != nil { - 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 +// GetWSJWT returns a JWT, using a stored one of it's provided, and generating a new one otherwise +func (c *CoinbasePro) GetWSJWT() (string, error) { + c.mut.RLock() + if c.jwtLastRegen.Add(time.Minute * 2).After(time.Now()) { + retStr := c.jwt + c.mut.RUnlock() + return retStr, nil + } + go c.mut.RUnlock() + c.mut.Lock() + defer c.mut.Unlock() + var err error + c.jwt, c.jwtLastRegen, err = c.GetJWT(context.Background(), "") + return c.jwt, err } // getTimestamp is a helper function which pulls a RFC3339-formatted timestamp from a byte slice of JSON data diff --git a/exchanges/coinbasepro/coinbasepro_wrapper.go b/exchanges/coinbasepro/coinbasepro_wrapper.go index a675e7ba905..2a82891f343 100644 --- a/exchanges/coinbasepro/coinbasepro_wrapper.go +++ b/exchanges/coinbasepro/coinbasepro_wrapper.go @@ -279,7 +279,7 @@ func (c *CoinbasePro) FetchAccountInfo(ctx context.Context, assetType asset.Item } // UpdateTickers updates all currency pairs of a given asset type -func (c *CoinbasePro) UpdateTickers(_ context.Context, _ asset.Item) error { +func (c *CoinbasePro) UpdateTickers(context.Context, asset.Item) error { return common.ErrFunctionNotSupported } @@ -479,7 +479,7 @@ func (c *CoinbasePro) GetWithdrawalsHistory(ctx context.Context, cur currency.Co } // GetRecentTrades returns the most recent trades for a currency and asset -func (c *CoinbasePro) GetRecentTrades(_ context.Context, _ currency.Pair, _ asset.Item) ([]trade.Data, error) { +func (c *CoinbasePro) GetRecentTrades(context.Context, currency.Pair, asset.Item) ([]trade.Data, error) { return nil, common.ErrFunctionNotSupported } @@ -611,7 +611,7 @@ func (c *CoinbasePro) CancelBatchOrders(ctx context.Context, o []order.Cancel) ( } // CancelAllOrders cancels all orders associated with a currency pair -func (c *CoinbasePro) CancelAllOrders(_ context.Context, _ *order.Cancel) (order.CancelAllResponse, error) { +func (c *CoinbasePro) CancelAllOrders(context.Context, *order.Cancel) (order.CancelAllResponse, error) { return order.CancelAllResponse{}, common.ErrFunctionNotSupported } diff --git a/go.mod b/go.mod index 2f251831137..399a71355c7 100644 --- a/go.mod +++ b/go.mod @@ -42,6 +42,7 @@ require ( github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/friendsofgo/errors v0.9.2 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect + github.com/golang-jwt/jwt v3.2.2+incompatible github.com/google/uuid v1.6.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/huandu/xstrings v1.5.0 // indirect diff --git a/go.sum b/go.sum index 4f1da921738..504622d7bd8 100644 --- a/go.sum +++ b/go.sum @@ -77,6 +77,8 @@ github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRx github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= +github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= From 19fbf09744347c322d4d8c33df1ff0c283dd9e79 Mon Sep 17 00:00:00 2001 From: Samuel Reid <43227667+cranktakular@users.noreply.github.com> Date: Tue, 3 Dec 2024 12:25:53 +1100 Subject: [PATCH 77/79] Small nits --- exchanges/coinbasepro/coinbasepro.go | 3 ++- exchanges/exchange.go | 7 ++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/exchanges/coinbasepro/coinbasepro.go b/exchanges/coinbasepro/coinbasepro.go index d8c884b5ed4..12aa01ee433 100644 --- a/exchanges/coinbasepro/coinbasepro.go +++ b/exchanges/coinbasepro/coinbasepro.go @@ -1416,7 +1416,8 @@ func (c *CoinbasePro) SendAuthenticatedHTTPRequest(ctx context.Context, ep excha return nil, err } } - jwt, _, err := c.GetJWT(ctx, method+" "+endpoint[8:]+path) + var jwt string + jwt, _, err = c.GetJWT(ctx, method+" "+endpoint[8:]+path) if err != nil { return nil, err } diff --git a/exchanges/exchange.go b/exchanges/exchange.go index 2dfd0b1692a..547fd63faa0 100644 --- a/exchanges/exchange.go +++ b/exchanges/exchange.go @@ -51,11 +51,8 @@ const ( // Public Errors var ( - // ErrExchangeNameIsEmpty is returned when the exchange name is empty - ErrExchangeNameIsEmpty = errors.New("exchange name is empty") - // ErrSettingProxyAddress is returned when setting a proxy address fails - ErrSettingProxyAddress = errors.New("setting proxy address error") - // ErrEndpointPathNotFound is returned when an endpoint path is not found for a particular key + ErrExchangeNameIsEmpty = errors.New("exchange name is empty") + ErrSettingProxyAddress = errors.New("setting proxy address error") ErrEndpointPathNotFound = errors.New("no endpoint path found for the given key") ErrSymbolCannotBeMatched = errors.New("symbol cannot be matched") ) From e1ba339dec36882db3043bd250ab4e5755be3b9b Mon Sep 17 00:00:00 2001 From: Samuel Reid <43227667+cranktakular@users.noreply.github.com> Date: Thu, 12 Dec 2024 10:16:59 +1100 Subject: [PATCH 78/79] Shaznit, singular --- exchanges/coinbasepro/coinbasepro.go | 4 ++-- exchanges/coinbasepro/coinbasepro_types.go | 6 +++--- exchanges/coinbasepro/coinbasepro_websocket.go | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/exchanges/coinbasepro/coinbasepro.go b/exchanges/coinbasepro/coinbasepro.go index 12aa01ee433..fead064361b 100644 --- a/exchanges/coinbasepro/coinbasepro.go +++ b/exchanges/coinbasepro/coinbasepro.go @@ -1520,7 +1520,7 @@ func (c *CoinbasePro) GetJWT(ctx context.Context, uri string) (string, time.Time tok.Header["kid"] = creds.Key tok.Header["nonce"] = nonce sign, err := tok.SignedString(key) - return sign, regTime, err + return sign, regTime.Add(time.Minute * 2), err // The code below mostly works, but seems to lead to bad results on the signature step. Deferring until later // head := map[string]any{"kid": creds.Key, "typ": "JWT", "alg": "ES256", "nonce": nonce} // headJSON, err := json.Marshal(head) @@ -1544,7 +1544,7 @@ func (c *CoinbasePro) GetJWT(ctx context.Context, uri string) (string, time.Time // return "", time.Time{}, err // } // sigEncode := base64URLEncode(sig) - // return headEncode + "." + bodyEncode + "." + sigEncode, regTime, nil + // return headEncode + "." + bodyEncode + "." + sigEncode, regTime.Add(time.Minute * 2), nil } // GetFee returns an estimate of fee based on type of transaction diff --git a/exchanges/coinbasepro/coinbasepro_types.go b/exchanges/coinbasepro/coinbasepro_types.go index a5c3664ec7f..99b8c325344 100644 --- a/exchanges/coinbasepro/coinbasepro_types.go +++ b/exchanges/coinbasepro/coinbasepro_types.go @@ -15,9 +15,9 @@ import ( // CoinbasePro is the overarching type across the coinbasepro package type CoinbasePro struct { exchange.Base - jwt string - jwtLastRegen time.Time - mut sync.RWMutex + jwt string + jwtExpire time.Time + mut sync.RWMutex } // Version is used for the niche cases where the Version of the API must be specified and passed around for proper functionality diff --git a/exchanges/coinbasepro/coinbasepro_websocket.go b/exchanges/coinbasepro/coinbasepro_websocket.go index 7fec5fd0bde..80ec72588b0 100644 --- a/exchanges/coinbasepro/coinbasepro_websocket.go +++ b/exchanges/coinbasepro/coinbasepro_websocket.go @@ -465,7 +465,7 @@ func (c *CoinbasePro) signWsRequest(r *WebsocketRequest) error { // GetWSJWT returns a JWT, using a stored one of it's provided, and generating a new one otherwise func (c *CoinbasePro) GetWSJWT() (string, error) { c.mut.RLock() - if c.jwtLastRegen.Add(time.Minute * 2).After(time.Now()) { + if c.jwtExpire.After(time.Now()) { retStr := c.jwt c.mut.RUnlock() return retStr, nil @@ -474,7 +474,7 @@ func (c *CoinbasePro) GetWSJWT() (string, error) { c.mut.Lock() defer c.mut.Unlock() var err error - c.jwt, c.jwtLastRegen, err = c.GetJWT(context.Background(), "") + c.jwt, c.jwtExpire, err = c.GetJWT(context.Background(), "") return c.jwt, err } From 80cb272508b6acd21eff989a0463ee4abad8ecf0 Mon Sep 17 00:00:00 2001 From: Samuel Reid <43227667+cranktakular@users.noreply.github.com> Date: Thu, 12 Dec 2024 13:41:44 +1100 Subject: [PATCH 79/79] Post-merge fix --- exchanges/subscription/subscription.go | 1 - 1 file changed, 1 deletion(-) diff --git a/exchanges/subscription/subscription.go b/exchanges/subscription/subscription.go index 923e935babb..6b2cfc24ae1 100644 --- a/exchanges/subscription/subscription.go +++ b/exchanges/subscription/subscription.go @@ -32,7 +32,6 @@ const ( MyTradesChannel = "myTrades" MyOrdersChannel = "myOrders" MyWalletChannel = "myWallet" - HeartbeatChannel = "heartbeat" MyAccountChannel = "myAccount" HeartbeatChannel = "heartbeat" )