mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-14 07:26:47 +00:00
* Improve exchange pair loading/setting/formatting * Update configs * Link up forceUpdate for OKEX * Fix linter issues * Rearrange exchPS * Fix typerino * Config updates
784 lines
26 KiB
Go
784 lines
26 KiB
Go
package exchange
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/thrasher-corp/gocryptotrader/common/crypto"
|
|
"github.com/thrasher-corp/gocryptotrader/config"
|
|
"github.com/thrasher-corp/gocryptotrader/currency"
|
|
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
|
|
"github.com/thrasher-corp/gocryptotrader/exchanges/protocol"
|
|
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
|
|
log "github.com/thrasher-corp/gocryptotrader/logger"
|
|
)
|
|
|
|
const (
|
|
warningBase64DecryptSecretKeyFailed = "exchange %s unable to base64 decode secret key.. Disabling Authenticated API support" // nolint:gosec
|
|
// WarningAuthenticatedRequestWithoutCredentialsSet error message for authenticated request without credentials set
|
|
WarningAuthenticatedRequestWithoutCredentialsSet = "exchange %s authenticated HTTP request called but not supported due to unset/default API keys"
|
|
// DefaultHTTPTimeout is the default HTTP/HTTPS Timeout for exchange requests
|
|
DefaultHTTPTimeout = time.Second * 15
|
|
// DefaultWebsocketResponseCheckTimeout is the default delay in checking for an expected websocket response
|
|
DefaultWebsocketResponseCheckTimeout = time.Millisecond * 30
|
|
// DefaultWebsocketResponseMaxLimit is the default max wait for an expected websocket response before a timeout
|
|
DefaultWebsocketResponseMaxLimit = time.Second * 7
|
|
// DefaultWebsocketOrderbookBufferLimit is the maximum number of orderbook updates that get stored before being applied
|
|
DefaultWebsocketOrderbookBufferLimit = 5
|
|
)
|
|
|
|
func (e *Base) checkAndInitRequester() {
|
|
if e.Requester == nil {
|
|
e.Requester = request.New(e.Name,
|
|
request.NewRateLimit(time.Second, 0),
|
|
request.NewRateLimit(time.Second, 0),
|
|
new(http.Client))
|
|
}
|
|
}
|
|
|
|
// SetHTTPClientTimeout sets the timeout value for the exchanges
|
|
// HTTP Client
|
|
func (e *Base) SetHTTPClientTimeout(t time.Duration) {
|
|
e.checkAndInitRequester()
|
|
e.Requester.HTTPClient.Timeout = t
|
|
}
|
|
|
|
// SetHTTPClient sets exchanges HTTP client
|
|
func (e *Base) SetHTTPClient(h *http.Client) {
|
|
e.checkAndInitRequester()
|
|
e.Requester.HTTPClient = h
|
|
}
|
|
|
|
// GetHTTPClient gets the exchanges HTTP client
|
|
func (e *Base) GetHTTPClient() *http.Client {
|
|
e.checkAndInitRequester()
|
|
return e.Requester.HTTPClient
|
|
}
|
|
|
|
// SetHTTPClientUserAgent sets the exchanges HTTP user agent
|
|
func (e *Base) SetHTTPClientUserAgent(ua string) {
|
|
e.checkAndInitRequester()
|
|
e.Requester.UserAgent = ua
|
|
e.HTTPUserAgent = ua
|
|
}
|
|
|
|
// GetHTTPClientUserAgent gets the exchanges HTTP user agent
|
|
func (e *Base) GetHTTPClientUserAgent() string {
|
|
return e.HTTPUserAgent
|
|
}
|
|
|
|
// SetClientProxyAddress sets a proxy address for REST and websocket requests
|
|
func (e *Base) SetClientProxyAddress(addr string) error {
|
|
if addr != "" {
|
|
proxy, err := url.Parse(addr)
|
|
if err != nil {
|
|
return fmt.Errorf("exchange.go - setting proxy address error %s",
|
|
err)
|
|
}
|
|
|
|
// No needs to check err here as the only err condition is an empty
|
|
// string which is already checked above
|
|
_ = e.Requester.SetProxy(proxy)
|
|
|
|
if e.Websocket != nil {
|
|
err = e.Websocket.SetProxyAddress(addr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// SetFeatureDefaults sets the exchanges default feature
|
|
// support set
|
|
func (e *Base) SetFeatureDefaults() {
|
|
if e.Config.Features == nil {
|
|
s := &config.FeaturesConfig{
|
|
Supports: config.FeaturesSupportedConfig{
|
|
Websocket: e.Features.Supports.Websocket,
|
|
REST: e.Features.Supports.REST,
|
|
RESTCapabilities: protocol.Features{
|
|
AutoPairUpdates: e.Features.Supports.RESTCapabilities.AutoPairUpdates,
|
|
},
|
|
},
|
|
}
|
|
|
|
if e.Config.SupportsAutoPairUpdates != nil {
|
|
s.Supports.RESTCapabilities.AutoPairUpdates = *e.Config.SupportsAutoPairUpdates
|
|
s.Enabled.AutoPairUpdates = *e.Config.SupportsAutoPairUpdates
|
|
} else {
|
|
s.Supports.RESTCapabilities.AutoPairUpdates = e.Features.Supports.RESTCapabilities.AutoPairUpdates
|
|
s.Enabled.AutoPairUpdates = e.Features.Supports.RESTCapabilities.AutoPairUpdates
|
|
if !s.Supports.RESTCapabilities.AutoPairUpdates {
|
|
e.Config.CurrencyPairs.LastUpdated = time.Now().Unix()
|
|
e.CurrencyPairs.LastUpdated = e.Config.CurrencyPairs.LastUpdated
|
|
}
|
|
}
|
|
e.Config.Features = s
|
|
e.Config.SupportsAutoPairUpdates = nil
|
|
} else {
|
|
if e.Features.Supports.RESTCapabilities.AutoPairUpdates != e.Config.Features.Supports.RESTCapabilities.AutoPairUpdates {
|
|
e.Config.Features.Supports.RESTCapabilities.AutoPairUpdates = e.Features.Supports.RESTCapabilities.AutoPairUpdates
|
|
|
|
if !e.Config.Features.Supports.RESTCapabilities.AutoPairUpdates {
|
|
e.Config.CurrencyPairs.LastUpdated = time.Now().Unix()
|
|
}
|
|
}
|
|
|
|
if e.Features.Supports.REST != e.Config.Features.Supports.REST {
|
|
e.Config.Features.Supports.REST = e.Features.Supports.REST
|
|
}
|
|
|
|
if e.Features.Supports.RESTCapabilities.TickerBatching != e.Config.Features.Supports.RESTCapabilities.TickerBatching {
|
|
e.Config.Features.Supports.RESTCapabilities.TickerBatching = e.Features.Supports.RESTCapabilities.TickerBatching
|
|
}
|
|
|
|
if e.Features.Supports.Websocket != e.Config.Features.Supports.Websocket {
|
|
e.Config.Features.Supports.Websocket = e.Features.Supports.Websocket
|
|
}
|
|
|
|
e.Features.Enabled.AutoPairUpdates = e.Config.Features.Enabled.AutoPairUpdates
|
|
}
|
|
}
|
|
|
|
// SetAPICredentialDefaults sets the API Credential validator defaults
|
|
func (e *Base) SetAPICredentialDefaults() {
|
|
// Exchange hardcoded settings take precedence and overwrite the config settings
|
|
if e.Config.API.CredentialsValidator == nil {
|
|
e.Config.API.CredentialsValidator = new(config.APICredentialsValidatorConfig)
|
|
}
|
|
if e.Config.API.CredentialsValidator.RequiresKey != e.API.CredentialsValidator.RequiresKey {
|
|
e.Config.API.CredentialsValidator.RequiresKey = e.API.CredentialsValidator.RequiresKey
|
|
}
|
|
|
|
if e.Config.API.CredentialsValidator.RequiresSecret != e.API.CredentialsValidator.RequiresSecret {
|
|
e.Config.API.CredentialsValidator.RequiresSecret = e.API.CredentialsValidator.RequiresSecret
|
|
}
|
|
|
|
if e.Config.API.CredentialsValidator.RequiresBase64DecodeSecret != e.API.CredentialsValidator.RequiresBase64DecodeSecret {
|
|
e.Config.API.CredentialsValidator.RequiresBase64DecodeSecret = e.API.CredentialsValidator.RequiresBase64DecodeSecret
|
|
}
|
|
|
|
if e.Config.API.CredentialsValidator.RequiresClientID != e.API.CredentialsValidator.RequiresClientID {
|
|
e.Config.API.CredentialsValidator.RequiresClientID = e.API.CredentialsValidator.RequiresClientID
|
|
}
|
|
|
|
if e.Config.API.CredentialsValidator.RequiresPEM != e.API.CredentialsValidator.RequiresPEM {
|
|
e.Config.API.CredentialsValidator.RequiresPEM = e.API.CredentialsValidator.RequiresPEM
|
|
}
|
|
}
|
|
|
|
// SetHTTPRateLimiter sets the exchanges default HTTP rate limiter and updates the exchange's config
|
|
// to default settings if it doesn't exist
|
|
func (e *Base) SetHTTPRateLimiter() {
|
|
e.checkAndInitRequester()
|
|
|
|
if e.RequiresRateLimiter() {
|
|
if e.Config.HTTPRateLimiter == nil {
|
|
e.Config.HTTPRateLimiter = new(config.HTTPRateLimitConfig)
|
|
e.Config.HTTPRateLimiter.Authenticated.Duration = e.GetRateLimit(true).Duration
|
|
e.Config.HTTPRateLimiter.Authenticated.Rate = e.GetRateLimit(true).Rate
|
|
e.Config.HTTPRateLimiter.Unauthenticated.Duration = e.GetRateLimit(false).Duration
|
|
e.Config.HTTPRateLimiter.Unauthenticated.Rate = e.GetRateLimit(false).Rate
|
|
} else {
|
|
e.SetRateLimit(true, e.Config.HTTPRateLimiter.Authenticated.Duration,
|
|
e.Config.HTTPRateLimiter.Authenticated.Rate)
|
|
e.SetRateLimit(false, e.Config.HTTPRateLimiter.Unauthenticated.Duration,
|
|
e.Config.HTTPRateLimiter.Unauthenticated.Rate)
|
|
}
|
|
}
|
|
}
|
|
|
|
// SupportsRESTTickerBatchUpdates returns whether or not the
|
|
// exhange supports REST batch ticker fetching
|
|
func (e *Base) SupportsRESTTickerBatchUpdates() bool {
|
|
return e.Features.Supports.RESTCapabilities.TickerBatching
|
|
}
|
|
|
|
// SupportsAutoPairUpdates returns whether or not the exchange supports
|
|
// auto currency pair updating
|
|
func (e *Base) SupportsAutoPairUpdates() bool {
|
|
if e.Features.Supports.RESTCapabilities.AutoPairUpdates || e.Features.Supports.WebsocketCapabilities.AutoPairUpdates {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// GetLastPairsUpdateTime returns the unix timestamp of when the exchanges
|
|
// currency pairs were last updated
|
|
func (e *Base) GetLastPairsUpdateTime() int64 {
|
|
return e.CurrencyPairs.LastUpdated
|
|
}
|
|
|
|
// SetAssetTypes checks the exchange asset types (whether it supports SPOT,
|
|
// Binary or Futures) and sets it to a default setting if it doesn't exist
|
|
func (e *Base) SetAssetTypes() {
|
|
if e.Config.CurrencyPairs.AssetTypes.JoinToString(",") == "" {
|
|
e.Config.CurrencyPairs.AssetTypes = e.CurrencyPairs.AssetTypes
|
|
} else if e.Config.CurrencyPairs.AssetTypes.JoinToString(",") != e.CurrencyPairs.AssetTypes.JoinToString(",") {
|
|
e.Config.CurrencyPairs.AssetTypes = e.CurrencyPairs.AssetTypes
|
|
}
|
|
}
|
|
|
|
// GetAssetTypes returns the available asset types for an individual exchange
|
|
func (e *Base) GetAssetTypes() asset.Items {
|
|
return e.CurrencyPairs.AssetTypes
|
|
}
|
|
|
|
// GetPairAssetType returns the associated asset type for the currency pair
|
|
func (e *Base) GetPairAssetType(c currency.Pair) (asset.Item, error) {
|
|
for i := range e.GetAssetTypes() {
|
|
if e.GetEnabledPairs(e.GetAssetTypes()[i]).Contains(c, true) {
|
|
return e.GetAssetTypes()[i], nil
|
|
}
|
|
}
|
|
return "", errors.New("asset type not associated with currency pair")
|
|
}
|
|
|
|
// GetClientBankAccounts returns banking details associated with
|
|
// a client for withdrawal purposes
|
|
func (e *Base) GetClientBankAccounts(exchangeName, withdrawalCurrency string) (config.BankAccount, error) {
|
|
cfg := config.GetConfig()
|
|
return cfg.GetClientBankAccounts(exchangeName, withdrawalCurrency)
|
|
}
|
|
|
|
// GetExchangeBankAccounts returns banking details associated with an
|
|
// exchange for funding purposes
|
|
func (e *Base) GetExchangeBankAccounts(exchangeName, depositCurrency string) (config.BankAccount, error) {
|
|
cfg := config.GetConfig()
|
|
return cfg.GetExchangeBankAccounts(exchangeName, depositCurrency)
|
|
}
|
|
|
|
// SetCurrencyPairFormat checks the exchange request and config currency pair
|
|
// formats and syncs it with the exchanges SetDefault settings
|
|
func (e *Base) SetCurrencyPairFormat() {
|
|
if e.Config.CurrencyPairs == nil {
|
|
e.Config.CurrencyPairs = new(currency.PairsManager)
|
|
}
|
|
|
|
e.Config.CurrencyPairs.UseGlobalFormat = e.CurrencyPairs.UseGlobalFormat
|
|
if e.Config.CurrencyPairs.UseGlobalFormat {
|
|
e.Config.CurrencyPairs.RequestFormat = e.CurrencyPairs.RequestFormat
|
|
e.Config.CurrencyPairs.ConfigFormat = e.CurrencyPairs.ConfigFormat
|
|
return
|
|
}
|
|
|
|
if e.Config.CurrencyPairs.ConfigFormat != nil {
|
|
e.Config.CurrencyPairs.ConfigFormat = nil
|
|
}
|
|
if e.Config.CurrencyPairs.RequestFormat != nil {
|
|
e.Config.CurrencyPairs.RequestFormat = nil
|
|
}
|
|
|
|
assetTypes := e.GetAssetTypes()
|
|
for x := range assetTypes {
|
|
if e.Config.CurrencyPairs.Get(assetTypes[x]) == nil {
|
|
r := e.CurrencyPairs.Get(assetTypes[x])
|
|
if r == nil {
|
|
continue
|
|
}
|
|
e.Config.CurrencyPairs.Store(assetTypes[x], *e.CurrencyPairs.Get(assetTypes[x]))
|
|
}
|
|
}
|
|
}
|
|
|
|
// SetConfigPairs sets the exchanges currency pairs to the pairs set in the config
|
|
func (e *Base) SetConfigPairs() {
|
|
assetTypes := e.GetAssetTypes()
|
|
for x := range assetTypes {
|
|
cfgPS := e.Config.CurrencyPairs.Get(assetTypes[x])
|
|
if cfgPS == nil {
|
|
continue
|
|
}
|
|
if e.Config.CurrencyPairs.UseGlobalFormat {
|
|
e.CurrencyPairs.StorePairs(assetTypes[x], cfgPS.Available, false)
|
|
e.CurrencyPairs.StorePairs(assetTypes[x], cfgPS.Enabled, true)
|
|
continue
|
|
}
|
|
exchPS := e.CurrencyPairs.Get(assetTypes[x])
|
|
cfgPS.ConfigFormat = exchPS.ConfigFormat
|
|
cfgPS.RequestFormat = exchPS.RequestFormat
|
|
e.CurrencyPairs.StorePairs(assetTypes[x], cfgPS.Available, false)
|
|
e.CurrencyPairs.StorePairs(assetTypes[x], cfgPS.Enabled, true)
|
|
}
|
|
}
|
|
|
|
// GetAuthenticatedAPISupport returns whether the exchange supports
|
|
// authenticated API requests
|
|
func (e *Base) GetAuthenticatedAPISupport(endpoint uint8) bool {
|
|
switch endpoint {
|
|
case RestAuthentication:
|
|
return e.API.AuthenticatedSupport
|
|
case WebsocketAuthentication:
|
|
return e.API.AuthenticatedWebsocketSupport
|
|
}
|
|
return false
|
|
}
|
|
|
|
// GetName is a method that returns the name of the exchange base
|
|
func (e *Base) GetName() string {
|
|
return e.Name
|
|
}
|
|
|
|
// GetEnabledFeatures returns the exchanges enabled features
|
|
func (e *Base) GetEnabledFeatures() FeaturesEnabled {
|
|
return e.Features.Enabled
|
|
}
|
|
|
|
// GetSupportedFeatures returns the exchanges supported features
|
|
func (e *Base) GetSupportedFeatures() FeaturesSupported {
|
|
return e.Features.Supports
|
|
}
|
|
|
|
// GetPairFormat returns the pair format based on the exchange and
|
|
// asset type
|
|
func (e *Base) GetPairFormat(assetType asset.Item, requestFormat bool) currency.PairFormat {
|
|
if e.CurrencyPairs.UseGlobalFormat {
|
|
if requestFormat {
|
|
return *e.CurrencyPairs.RequestFormat
|
|
}
|
|
return *e.CurrencyPairs.ConfigFormat
|
|
}
|
|
|
|
if requestFormat {
|
|
return *e.CurrencyPairs.Get(assetType).RequestFormat
|
|
}
|
|
return *e.CurrencyPairs.Get(assetType).ConfigFormat
|
|
}
|
|
|
|
// GetEnabledPairs is a method that returns the enabled currency pairs of
|
|
// the exchange by asset type
|
|
func (e *Base) GetEnabledPairs(assetType asset.Item) currency.Pairs {
|
|
format := e.GetPairFormat(assetType, false)
|
|
pairs := e.CurrencyPairs.GetPairs(assetType, true)
|
|
return pairs.Format(format.Delimiter, format.Index, format.Uppercase)
|
|
}
|
|
|
|
// GetAvailablePairs is a method that returns the available currency pairs
|
|
// of the exchange by asset type
|
|
func (e *Base) GetAvailablePairs(assetType asset.Item) currency.Pairs {
|
|
format := e.GetPairFormat(assetType, false)
|
|
pairs := e.CurrencyPairs.GetPairs(assetType, false)
|
|
return pairs.Format(format.Delimiter, format.Index, format.Uppercase)
|
|
}
|
|
|
|
// SupportsPair returns true or not whether a currency pair exists in the
|
|
// exchange available currencies or not
|
|
func (e *Base) SupportsPair(p currency.Pair, enabledPairs bool, assetType asset.Item) bool {
|
|
if enabledPairs {
|
|
return e.GetEnabledPairs(assetType).Contains(p, false)
|
|
}
|
|
return e.GetAvailablePairs(assetType).Contains(p, false)
|
|
}
|
|
|
|
// FormatExchangeCurrencies returns a string containing
|
|
// the exchanges formatted currency pairs
|
|
func (e *Base) FormatExchangeCurrencies(pairs []currency.Pair, assetType asset.Item) (string, error) {
|
|
var currencyItems strings.Builder
|
|
pairFmt := e.GetPairFormat(assetType, true)
|
|
|
|
for x := range pairs {
|
|
currencyItems.WriteString(e.FormatExchangeCurrency(pairs[x], assetType).String())
|
|
if x == len(pairs)-1 {
|
|
continue
|
|
}
|
|
currencyItems.WriteString(pairFmt.Separator)
|
|
}
|
|
|
|
if currencyItems.Len() == 0 {
|
|
return "", errors.New("returned empty string")
|
|
}
|
|
return currencyItems.String(), nil
|
|
}
|
|
|
|
// FormatExchangeCurrency is a method that formats and returns a currency pair
|
|
// based on the user currency display preferences
|
|
func (e *Base) FormatExchangeCurrency(p currency.Pair, assetType asset.Item) currency.Pair {
|
|
pairFmt := e.GetPairFormat(assetType, true)
|
|
return p.Format(pairFmt.Delimiter, pairFmt.Uppercase)
|
|
}
|
|
|
|
// SetEnabled is a method that sets if the exchange is enabled
|
|
func (e *Base) SetEnabled(enabled bool) {
|
|
e.Enabled = enabled
|
|
}
|
|
|
|
// IsEnabled is a method that returns if the current exchange is enabled
|
|
func (e *Base) IsEnabled() bool {
|
|
return e.Enabled
|
|
}
|
|
|
|
// SetAPIKeys is a method that sets the current API keys for the exchange
|
|
func (e *Base) SetAPIKeys(apiKey, apiSecret, clientID string) {
|
|
e.API.Credentials.Key = apiKey
|
|
e.API.Credentials.ClientID = clientID
|
|
|
|
if e.API.CredentialsValidator.RequiresBase64DecodeSecret {
|
|
result, err := crypto.Base64Decode(apiSecret)
|
|
if err != nil {
|
|
e.API.AuthenticatedSupport = false
|
|
e.API.AuthenticatedWebsocketSupport = false
|
|
log.Warnf(log.ExchangeSys, warningBase64DecryptSecretKeyFailed, e.Name)
|
|
return
|
|
}
|
|
e.API.Credentials.Secret = string(result)
|
|
} else {
|
|
e.API.Credentials.Secret = apiSecret
|
|
}
|
|
}
|
|
|
|
// SetupDefaults sets the exchange settings based on the supplied config
|
|
func (e *Base) SetupDefaults(exch *config.ExchangeConfig) error {
|
|
e.Enabled = true
|
|
e.LoadedByConfig = true
|
|
e.Config = exch
|
|
e.Verbose = exch.Verbose
|
|
|
|
e.API.AuthenticatedSupport = exch.API.AuthenticatedSupport
|
|
e.API.AuthenticatedWebsocketSupport = exch.API.AuthenticatedWebsocketSupport
|
|
if e.API.AuthenticatedSupport || e.API.AuthenticatedWebsocketSupport {
|
|
e.SetAPIKeys(exch.API.Credentials.Key, exch.API.Credentials.Secret, exch.API.Credentials.ClientID)
|
|
}
|
|
|
|
if exch.HTTPTimeout <= time.Duration(0) {
|
|
exch.HTTPTimeout = DefaultHTTPTimeout
|
|
} else {
|
|
e.SetHTTPClientTimeout(exch.HTTPTimeout)
|
|
}
|
|
|
|
if exch.CurrencyPairs == nil {
|
|
exch.CurrencyPairs = new(currency.PairsManager)
|
|
}
|
|
|
|
e.HTTPDebugging = exch.HTTPDebugging
|
|
e.SetHTTPClientUserAgent(exch.HTTPUserAgent)
|
|
e.SetHTTPRateLimiter()
|
|
e.SetAssetTypes()
|
|
e.SetCurrencyPairFormat()
|
|
e.SetConfigPairs()
|
|
e.SetFeatureDefaults()
|
|
e.SetAPIURL()
|
|
e.SetAPICredentialDefaults()
|
|
e.SetClientProxyAddress(exch.ProxyAddress)
|
|
e.SetHTTPRateLimiter()
|
|
|
|
e.BaseCurrencies = exch.BaseCurrencies
|
|
|
|
if e.Features.Supports.Websocket {
|
|
return e.Websocket.Initialise()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// AllowAuthenticatedRequest checks to see if the required fields have been set before sending an authenticated
|
|
// API request
|
|
func (e *Base) AllowAuthenticatedRequest() bool {
|
|
// Skip auth check
|
|
if e.SkipAuthCheck {
|
|
return true
|
|
}
|
|
|
|
// Individual package usage, allow request if API credentials are valid a
|
|
// and without needing to set AuthenticatedSupport to true
|
|
if !e.LoadedByConfig && !e.ValidateAPICredentials() {
|
|
return false
|
|
}
|
|
|
|
// Bot usage, AuthenticatedSupport can be disabled by user if desired, so don't
|
|
// allow authenticated requests.
|
|
if (!e.API.AuthenticatedSupport && !e.API.AuthenticatedWebsocketSupport) && e.LoadedByConfig {
|
|
return false
|
|
}
|
|
|
|
// Check to see if the user has enabled AuthenticatedSupport, but has invalid
|
|
// API credentials set and loaded by config
|
|
if (e.API.AuthenticatedSupport || e.API.AuthenticatedWebsocketSupport) && e.LoadedByConfig && !e.ValidateAPICredentials() {
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// ValidateAPICredentials validates the exchanges API credentials
|
|
func (e *Base) ValidateAPICredentials() bool {
|
|
if e.API.CredentialsValidator.RequiresKey {
|
|
if e.API.Credentials.Key == "" ||
|
|
e.API.Credentials.Key == config.DefaultAPIKey {
|
|
log.Warnf(log.ExchangeSys,
|
|
"exchange %s requires API key but default/empty one set",
|
|
e.Name)
|
|
return false
|
|
}
|
|
}
|
|
|
|
if e.API.CredentialsValidator.RequiresSecret {
|
|
if e.API.Credentials.Secret == "" ||
|
|
e.API.Credentials.Secret == config.DefaultAPISecret {
|
|
log.Warnf(log.ExchangeSys,
|
|
"exchange %s requires API secret but default/empty one set",
|
|
e.Name)
|
|
return false
|
|
}
|
|
}
|
|
|
|
if e.API.CredentialsValidator.RequiresPEM {
|
|
if e.API.Credentials.PEMKey == "" ||
|
|
strings.Contains(e.API.Credentials.PEMKey, "JUSTADUMMY") {
|
|
log.Warnf(log.ExchangeSys,
|
|
"exchange %s requires API PEM key but default/empty one set",
|
|
e.Name)
|
|
return false
|
|
}
|
|
}
|
|
|
|
if e.API.CredentialsValidator.RequiresClientID {
|
|
if e.API.Credentials.ClientID == "" ||
|
|
e.API.Credentials.ClientID == config.DefaultAPIClientID {
|
|
log.Warnf(log.ExchangeSys,
|
|
"exchange %s requires API ClientID but default/empty one set",
|
|
e.Name)
|
|
return false
|
|
}
|
|
}
|
|
|
|
if e.API.CredentialsValidator.RequiresBase64DecodeSecret && !e.LoadedByConfig {
|
|
_, err := crypto.Base64Decode(e.API.Credentials.Secret)
|
|
if err != nil {
|
|
log.Warnf(log.ExchangeSys,
|
|
"exchange %s API secret base64 decode failed: %s",
|
|
e.Name, err)
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// SetPairs sets the exchange currency pairs for either enabledPairs or
|
|
// availablePairs
|
|
func (e *Base) SetPairs(pairs currency.Pairs, assetType asset.Item, enabled bool) error {
|
|
if len(pairs) == 0 {
|
|
return fmt.Errorf("%s SetPairs error - pairs is empty", e.Name)
|
|
}
|
|
|
|
pairFmt := e.GetPairFormat(assetType, false)
|
|
var newPairs currency.Pairs
|
|
for x := range pairs {
|
|
newPairs = append(newPairs, pairs[x].Format(pairFmt.Delimiter,
|
|
pairFmt.Uppercase))
|
|
}
|
|
|
|
e.CurrencyPairs.StorePairs(assetType, newPairs, enabled)
|
|
e.Config.CurrencyPairs.StorePairs(assetType, newPairs, enabled)
|
|
return nil
|
|
}
|
|
|
|
// UpdatePairs updates the exchange currency pairs for either enabledPairs or
|
|
// availablePairs
|
|
func (e *Base) UpdatePairs(exchangeProducts currency.Pairs, assetType asset.Item, enabled, force bool) error {
|
|
if len(exchangeProducts) == 0 {
|
|
return fmt.Errorf("%s UpdatePairs error - exchangeProducts is empty", e.Name)
|
|
}
|
|
|
|
exchangeProducts = exchangeProducts.Upper()
|
|
var products currency.Pairs
|
|
for x := range exchangeProducts {
|
|
if exchangeProducts[x].String() == "" {
|
|
continue
|
|
}
|
|
products = append(products, exchangeProducts[x])
|
|
}
|
|
|
|
var newPairs, removedPairs currency.Pairs
|
|
var updateType string
|
|
targetPairs := e.CurrencyPairs.GetPairs(assetType, enabled)
|
|
|
|
if enabled {
|
|
newPairs, removedPairs = targetPairs.FindDifferences(products)
|
|
updateType = "enabled"
|
|
} else {
|
|
newPairs, removedPairs = targetPairs.FindDifferences(products)
|
|
updateType = "available"
|
|
}
|
|
|
|
if force || len(newPairs) > 0 || len(removedPairs) > 0 {
|
|
if force {
|
|
log.Debugf(log.ExchangeSys,
|
|
"%s forced update of %s [%v] pairs.", e.Name, updateType,
|
|
strings.ToUpper(assetType.String()))
|
|
} else {
|
|
if len(newPairs) > 0 {
|
|
log.Debugf(log.ExchangeSys,
|
|
"%s Updating pairs [%v] - New: %s.\n", e.Name,
|
|
strings.ToUpper(assetType.String()), newPairs)
|
|
}
|
|
if len(removedPairs) > 0 {
|
|
log.Debugf(log.ExchangeSys,
|
|
"%s Updating pairs [%v] - Removed: %s.\n", e.Name,
|
|
strings.ToUpper(assetType.String()), removedPairs)
|
|
}
|
|
}
|
|
e.Config.CurrencyPairs.StorePairs(assetType, products, enabled)
|
|
e.CurrencyPairs.StorePairs(assetType, products, enabled)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// SetAPIURL sets configuration API URL for an exchange
|
|
func (e *Base) SetAPIURL() error {
|
|
if e.Config.API.Endpoints.URL == "" || e.Config.API.Endpoints.URLSecondary == "" {
|
|
return fmt.Errorf("exchange %s: SetAPIURL error. URL vals are empty", e.Name)
|
|
}
|
|
|
|
checkInsecureEndpoint := func(endpoint string) {
|
|
if !strings.Contains(endpoint, "https") {
|
|
return
|
|
}
|
|
log.Warnf(log.ExchangeSys,
|
|
"%s is using HTTP instead of HTTPS [%s] for API functionality, an"+
|
|
" attacker could eavesdrop on this connection. Use at your"+
|
|
" own risk.",
|
|
e.Name, endpoint)
|
|
}
|
|
|
|
if e.Config.API.Endpoints.URL != config.APIURLNonDefaultMessage {
|
|
e.API.Endpoints.URL = e.Config.API.Endpoints.URL
|
|
checkInsecureEndpoint(e.API.Endpoints.URL)
|
|
}
|
|
if e.Config.API.Endpoints.URLSecondary != config.APIURLNonDefaultMessage {
|
|
e.API.Endpoints.URLSecondary = e.Config.API.Endpoints.URLSecondary
|
|
checkInsecureEndpoint(e.API.Endpoints.URLSecondary)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// GetAPIURL returns the set API URL
|
|
func (e *Base) GetAPIURL() string {
|
|
return e.API.Endpoints.URL
|
|
}
|
|
|
|
// GetSecondaryAPIURL returns the set Secondary API URL
|
|
func (e *Base) GetSecondaryAPIURL() string {
|
|
return e.API.Endpoints.URLSecondary
|
|
}
|
|
|
|
// GetAPIURLDefault returns exchange default URL
|
|
func (e *Base) GetAPIURLDefault() string {
|
|
return e.API.Endpoints.URLDefault
|
|
}
|
|
|
|
// GetAPIURLSecondaryDefault returns exchange default secondary URL
|
|
func (e *Base) GetAPIURLSecondaryDefault() string {
|
|
return e.API.Endpoints.URLSecondaryDefault
|
|
}
|
|
|
|
// SupportsWebsocket returns whether or not the exchange supports
|
|
// websocket
|
|
func (e *Base) SupportsWebsocket() bool {
|
|
return e.Features.Supports.Websocket
|
|
}
|
|
|
|
// SupportsREST returns whether or not the exchange supports
|
|
// REST
|
|
func (e *Base) SupportsREST() bool {
|
|
return e.Features.Supports.REST
|
|
}
|
|
|
|
// IsWebsocketEnabled returns whether or not the exchange has its
|
|
// websocket client enabled
|
|
func (e *Base) IsWebsocketEnabled() bool {
|
|
if e.Websocket != nil {
|
|
return e.Websocket.IsEnabled()
|
|
}
|
|
return false
|
|
}
|
|
|
|
// GetWithdrawPermissions passes through the exchange's withdraw permissions
|
|
func (e *Base) GetWithdrawPermissions() uint32 {
|
|
return e.Features.Supports.WithdrawPermissions
|
|
}
|
|
|
|
// SupportsWithdrawPermissions compares the supplied permissions with the exchange's to verify they're supported
|
|
func (e *Base) SupportsWithdrawPermissions(permissions uint32) bool {
|
|
exchangePermissions := e.GetWithdrawPermissions()
|
|
return permissions&exchangePermissions == permissions
|
|
}
|
|
|
|
// FormatWithdrawPermissions will return each of the exchange's compatible withdrawal methods in readable form
|
|
func (e *Base) FormatWithdrawPermissions() string {
|
|
var services []string
|
|
for i := 0; i < 32; i++ {
|
|
var check uint32 = 1 << uint32(i)
|
|
if e.GetWithdrawPermissions()&check != 0 {
|
|
switch check {
|
|
case AutoWithdrawCrypto:
|
|
services = append(services, AutoWithdrawCryptoText)
|
|
case AutoWithdrawCryptoWithAPIPermission:
|
|
services = append(services, AutoWithdrawCryptoWithAPIPermissionText)
|
|
case AutoWithdrawCryptoWithSetup:
|
|
services = append(services, AutoWithdrawCryptoWithSetupText)
|
|
case WithdrawCryptoWith2FA:
|
|
services = append(services, WithdrawCryptoWith2FAText)
|
|
case WithdrawCryptoWithSMS:
|
|
services = append(services, WithdrawCryptoWithSMSText)
|
|
case WithdrawCryptoWithEmail:
|
|
services = append(services, WithdrawCryptoWithEmailText)
|
|
case WithdrawCryptoWithWebsiteApproval:
|
|
services = append(services, WithdrawCryptoWithWebsiteApprovalText)
|
|
case WithdrawCryptoWithAPIPermission:
|
|
services = append(services, WithdrawCryptoWithAPIPermissionText)
|
|
case AutoWithdrawFiat:
|
|
services = append(services, AutoWithdrawFiatText)
|
|
case AutoWithdrawFiatWithAPIPermission:
|
|
services = append(services, AutoWithdrawFiatWithAPIPermissionText)
|
|
case AutoWithdrawFiatWithSetup:
|
|
services = append(services, AutoWithdrawFiatWithSetupText)
|
|
case WithdrawFiatWith2FA:
|
|
services = append(services, WithdrawFiatWith2FAText)
|
|
case WithdrawFiatWithSMS:
|
|
services = append(services, WithdrawFiatWithSMSText)
|
|
case WithdrawFiatWithEmail:
|
|
services = append(services, WithdrawFiatWithEmailText)
|
|
case WithdrawFiatWithWebsiteApproval:
|
|
services = append(services, WithdrawFiatWithWebsiteApprovalText)
|
|
case WithdrawFiatWithAPIPermission:
|
|
services = append(services, WithdrawFiatWithAPIPermissionText)
|
|
case WithdrawCryptoViaWebsiteOnly:
|
|
services = append(services, WithdrawCryptoViaWebsiteOnlyText)
|
|
case WithdrawFiatViaWebsiteOnly:
|
|
services = append(services, WithdrawFiatViaWebsiteOnlyText)
|
|
case NoFiatWithdrawals:
|
|
services = append(services, NoFiatWithdrawalsText)
|
|
default:
|
|
services = append(services, fmt.Sprintf("%s[1<<%v]", UnknownWithdrawalTypeText, i))
|
|
}
|
|
}
|
|
}
|
|
if len(services) > 0 {
|
|
return strings.Join(services, " & ")
|
|
}
|
|
|
|
return NoAPIWithdrawalMethodsText
|
|
}
|
|
|
|
// SupportsAsset whether or not the supplied asset is supported
|
|
// by the exchange
|
|
func (e *Base) SupportsAsset(a asset.Item) bool {
|
|
return e.CurrencyPairs.AssetTypes.Contains(a)
|
|
}
|
|
|
|
// PrintEnabledPairs prints the exchanges enabled asset pairs
|
|
func (e *Base) PrintEnabledPairs() {
|
|
for k, v := range e.CurrencyPairs.Pairs {
|
|
log.Infof(log.ExchangeSys, "%s Asset type %v:\n\t Enabled pairs: %v",
|
|
e.Name, strings.ToUpper(k.String()), v.Enabled)
|
|
}
|
|
}
|
|
|
|
// GetBase returns the exchange base
|
|
func (e *Base) GetBase() *Base { return e }
|