mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-19 15:10:05 +00:00
* Improves subscribing by not allowing duplicates. Adds bitmex auth support * Adds coinbase pro support. Partial BTCC support. Adds WebsocketAuthenticatedEndpointsSupported websocket feature. Adds GateIO support * Adds Coinut support * Moves Coinut WS types to file. Implements Gemini's secure WS endpoint * Adds HitBTC ws authenticated support. Fixes var names * Adds huobi and hadax authenticated websocket support * Adds auth to okgroup (okex, okcoin). Fixes some linting * Adds Poloniex support * Adds ZB support * Adds proper bitmex support * Improves bitfinex support, improves websocket functionality definitions * Fixes coinbasepro auth * Tests all endpoints * go formatting, importing, linting run * Adds wrapper supports * General clean up. Data race destruction * Improves testing on all exchanges except ZB * Fixes ZB hashing, parsing and tests * minor nits before someone else sees them <_< * Fixes some nits pertaining to variable usage, comments, typos and rate limiting * Addresses nits regarding types and test responses where applicable * fmt import * Fixes linting issues * No longer returns an error on failure to authenticate, just logs. Adds new AuthenticatedWebsocketAPISupport config value to allow a user to seperate auth from REST and WS. Prevents WS auth if AuthenticatedWebsocketAPISupport is false, adds additional login check 'CanUseAuthenticatedEndpoints' for when login only occurs once (not per request). Removes unnecessary time.Sleeps from code. Moves WS auth error logic to auth function so that wrappers can get involved in all the auth fun. New-fandangled shared test package, used exclusively in testing, will be the store of all the constant boilerplate things like timeout values. Moves WS test setup function to only run once when there are multiple WS endpoint tests. Cleans up some struct types * Increases test coverage with tests for config.areAuthenticatedCredentialsValid config.CheckExchangeConfigValues, exchange.SetAPIKeys, exchange.GetAuthenticatedAPISupport, exchange_websocket.CanUseAuthenticatedEndpoitns and exchange_websocket.SetCanUseAuthenticatedEndpoints. Adds b.Websocket.SetCanUseAuthenticatedEndpoints(false) when bitfinex fails to authenticate Fixes a typo. gofmt and goimport * Trim Test Typos * Reformats various websocket types. Adds more specific error messaging to config.areAuthenticatedCredentialsValid
1475 lines
46 KiB
Go
1475 lines
46 KiB
Go
package config
|
|
|
|
import (
|
|
"bufio"
|
|
"encoding/json"
|
|
"errors"
|
|
"flag"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/thrasher-/gocryptotrader/common"
|
|
"github.com/thrasher-/gocryptotrader/connchecker"
|
|
"github.com/thrasher-/gocryptotrader/currency"
|
|
"github.com/thrasher-/gocryptotrader/currency/forexprovider"
|
|
"github.com/thrasher-/gocryptotrader/currency/forexprovider/base"
|
|
log "github.com/thrasher-/gocryptotrader/logger"
|
|
"github.com/thrasher-/gocryptotrader/portfolio"
|
|
)
|
|
|
|
// Constants declared here are filename strings and test strings
|
|
const (
|
|
FXProviderFixer = "fixer"
|
|
EncryptedConfigFile = "config.dat"
|
|
ConfigFile = "config.json"
|
|
ConfigTestFile = "../testdata/configtest.json"
|
|
configFileEncryptionPrompt = 0
|
|
configFileEncryptionEnabled = 1
|
|
configFileEncryptionDisabled = -1
|
|
configPairsLastUpdatedWarningThreshold = 30 // 30 days
|
|
configDefaultHTTPTimeout = time.Second * 15
|
|
configMaxAuthFailres = 3
|
|
defaultNTPAllowedDifference = 50000000
|
|
defaultNTPAllowedNegativeDifference = 50000000
|
|
)
|
|
|
|
// Constants here hold some messages
|
|
const (
|
|
ErrExchangeNameEmpty = "exchange #%d name is empty"
|
|
ErrExchangeAvailablePairsEmpty = "exchange %s avaiable pairs is empty"
|
|
ErrExchangeEnabledPairsEmpty = "exchange %s enabled pairs is empty"
|
|
ErrExchangeBaseCurrenciesEmpty = "exchange %s base currencies is empty"
|
|
ErrExchangeNotFound = "exchange %s not found"
|
|
ErrNoEnabledExchanges = "no exchanges enabled"
|
|
ErrCryptocurrenciesEmpty = "cryptocurrencies variable is empty"
|
|
ErrFailureOpeningConfig = "fatal error opening %s file. Error: %s"
|
|
ErrCheckingConfigValues = "fatal error checking config values. Error: %s"
|
|
ErrSavingConfigBytesMismatch = "config file %q bytes comparison doesn't match, read %s expected %s"
|
|
WarningWebserverCredentialValuesEmpty = "webserver support disabled due to empty Username/Password values"
|
|
WarningWebserverListenAddressInvalid = "webserver support disabled due to invalid listen address"
|
|
WarningExchangeAuthAPIDefaultOrEmptyValues = "exchange %s authenticated API support disabled due to default/empty APIKey/Secret/ClientID values"
|
|
WarningPairsLastUpdatedThresholdExceeded = "exchange %s last manual update of available currency pairs has exceeded %d days. Manual update required!"
|
|
)
|
|
|
|
// Constants here define unset default values displayed in the config.json
|
|
// file
|
|
const (
|
|
APIURLNonDefaultMessage = "NON_DEFAULT_HTTP_LINK_TO_EXCHANGE_API"
|
|
WebsocketURLNonDefaultMessage = "NON_DEFAULT_HTTP_LINK_TO_WEBSOCKET_EXCHANGE_API"
|
|
DefaultUnsetAPIKey = "Key"
|
|
DefaultUnsetAPISecret = "Secret"
|
|
DefaultUnsetAccountPlan = "accountPlan"
|
|
DefaultForexProviderExchangeRatesAPI = "ExchangeRates"
|
|
)
|
|
|
|
// Variables here are used for configuration
|
|
var (
|
|
Cfg Config
|
|
IsInitialSetup bool
|
|
testBypass bool
|
|
m sync.Mutex
|
|
)
|
|
|
|
// WebserverConfig struct holds the prestart variables for the webserver.
|
|
type WebserverConfig struct {
|
|
Enabled bool `json:"enabled"`
|
|
AdminUsername string `json:"adminUsername"`
|
|
AdminPassword string `json:"adminPassword"`
|
|
ListenAddress string `json:"listenAddress"`
|
|
WebsocketConnectionLimit int `json:"websocketConnectionLimit"`
|
|
WebsocketMaxAuthFailures int `json:"websocketMaxAuthFailures"`
|
|
WebsocketAllowInsecureOrigin bool `json:"websocketAllowInsecureOrigin"`
|
|
}
|
|
|
|
// Post holds the bot configuration data
|
|
type Post struct {
|
|
Data Config `json:"data"`
|
|
}
|
|
|
|
// CurrencyPairFormatConfig stores the users preferred currency pair display
|
|
type CurrencyPairFormatConfig struct {
|
|
Uppercase bool `json:"uppercase"`
|
|
Delimiter string `json:"delimiter,omitempty"`
|
|
Separator string `json:"separator,omitempty"`
|
|
Index string `json:"index,omitempty"`
|
|
}
|
|
|
|
// Config is the overarching object that holds all the information for
|
|
// prestart management of Portfolio, Communications, Webserver and Enabled
|
|
// Exchanges
|
|
type Config struct {
|
|
Name string `json:"name"`
|
|
EncryptConfig int `json:"encryptConfig"`
|
|
GlobalHTTPTimeout time.Duration `json:"globalHTTPTimeout"`
|
|
Logging log.Logging `json:"logging"`
|
|
Profiler ProfilerConfig `json:"profiler"`
|
|
NTPClient NTPClientConfig `json:"ntpclient"`
|
|
Currency CurrencyConfig `json:"currencyConfig"`
|
|
Communications CommunicationsConfig `json:"communications"`
|
|
Portfolio portfolio.Base `json:"portfolioAddresses"`
|
|
Webserver WebserverConfig `json:"webserver"`
|
|
Exchanges []ExchangeConfig `json:"exchanges"`
|
|
BankAccounts []BankAccount `json:"bankAccounts"`
|
|
ConnectionMonitor ConnectionMonitorConfig `json:"connectionMonitor"`
|
|
|
|
// Deprecated config settings, will be removed at a future date
|
|
CurrencyPairFormat *CurrencyPairFormatConfig `json:"currencyPairFormat,omitempty"`
|
|
FiatDisplayCurrency currency.Code `json:"fiatDispayCurrency,omitempty"`
|
|
Cryptocurrencies currency.Currencies `json:"cryptocurrencies,omitempty"`
|
|
SMS *SMSGlobalConfig `json:"smsGlobal,omitempty"`
|
|
}
|
|
|
|
// ConnectionMonitorConfig defines the connection monitor variables to ensure
|
|
// that there is internet connectivity
|
|
type ConnectionMonitorConfig struct {
|
|
DNSList []string `json:"preferredDNSList"`
|
|
PublicDomainList []string `json:"preferredDomainList"`
|
|
CheckInterval time.Duration `json:"checkInterval"`
|
|
}
|
|
|
|
// ProfilerConfig defines the profiler configuration to enable pprof
|
|
type ProfilerConfig struct {
|
|
Enabled bool `json:"enabled"`
|
|
}
|
|
|
|
// NTPClientConfig defines a network time protocol configuration to allow for
|
|
// positive and negative differences
|
|
type NTPClientConfig struct {
|
|
Level int `json:"enabled"`
|
|
Pool []string `json:"pool"`
|
|
AllowedDifference *time.Duration `json:"allowedDifference"`
|
|
AllowedNegativeDifference *time.Duration `json:"allowedNegativeDifference"`
|
|
}
|
|
|
|
// ExchangeConfig holds all the information needed for each enabled Exchange.
|
|
type ExchangeConfig struct {
|
|
Name string `json:"name"`
|
|
Enabled bool `json:"enabled"`
|
|
Verbose bool `json:"verbose"`
|
|
Websocket bool `json:"websocket"`
|
|
UseSandbox bool `json:"useSandbox"`
|
|
RESTPollingDelay time.Duration `json:"restPollingDelay"`
|
|
HTTPTimeout time.Duration `json:"httpTimeout"`
|
|
HTTPUserAgent string `json:"httpUserAgent"`
|
|
HTTPDebugging bool `json:"httpDebugging"`
|
|
AuthenticatedAPISupport bool `json:"authenticatedApiSupport"`
|
|
AuthenticatedWebsocketAPISupport bool `json:"authenticatedWebsocketApiSupport"`
|
|
APIKey string `json:"apiKey"`
|
|
APISecret string `json:"apiSecret"`
|
|
APIAuthPEMKeySupport bool `json:"apiAuthPemKeySupport,omitempty"`
|
|
APIAuthPEMKey string `json:"apiAuthPemKey,omitempty"`
|
|
APIURL string `json:"apiUrl"`
|
|
APIURLSecondary string `json:"apiUrlSecondary"`
|
|
ProxyAddress string `json:"proxyAddress"`
|
|
WebsocketURL string `json:"websocketUrl"`
|
|
ClientID string `json:"clientId,omitempty"`
|
|
AvailablePairs currency.Pairs `json:"availablePairs"`
|
|
EnabledPairs currency.Pairs `json:"enabledPairs"`
|
|
BaseCurrencies currency.Currencies `json:"baseCurrencies"`
|
|
AssetTypes string `json:"assetTypes"`
|
|
SupportsAutoPairUpdates bool `json:"supportsAutoPairUpdates"`
|
|
PairsLastUpdated int64 `json:"pairsLastUpdated,omitempty"`
|
|
ConfigCurrencyPairFormat *CurrencyPairFormatConfig `json:"configCurrencyPairFormat"`
|
|
RequestCurrencyPairFormat *CurrencyPairFormatConfig `json:"requestCurrencyPairFormat"`
|
|
BankAccounts []BankAccount `json:"bankAccounts"`
|
|
}
|
|
|
|
// BankAccount holds differing bank account details by supported funding
|
|
// currency
|
|
type BankAccount struct {
|
|
Enabled bool `json:"enabled,omitempty"`
|
|
BankName string `json:"bankName"`
|
|
BankAddress string `json:"bankAddress"`
|
|
AccountName string `json:"accountName"`
|
|
AccountNumber string `json:"accountNumber"`
|
|
SWIFTCode string `json:"swiftCode"`
|
|
IBAN string `json:"iban"`
|
|
BSBNumber string `json:"bsbNumber,omitempty"`
|
|
SupportedCurrencies string `json:"supportedCurrencies"`
|
|
SupportedExchanges string `json:"supportedExchanges,omitempty"`
|
|
}
|
|
|
|
// BankTransaction defines a related banking transaction
|
|
type BankTransaction struct {
|
|
ReferenceNumber string `json:"referenceNumber"`
|
|
TransactionNumber string `json:"transactionNumber"`
|
|
PaymentInstructions string `json:"paymentInstructions"`
|
|
}
|
|
|
|
// CurrencyConfig holds all the information needed for currency related manipulation
|
|
type CurrencyConfig struct {
|
|
ForexProviders []base.Settings `json:"forexProviders"`
|
|
CryptocurrencyProvider CryptocurrencyProvider `json:"cryptocurrencyProvider"`
|
|
Cryptocurrencies currency.Currencies `json:"cryptocurrencies"`
|
|
CurrencyPairFormat *CurrencyPairFormatConfig `json:"currencyPairFormat"`
|
|
FiatDisplayCurrency currency.Code `json:"fiatDisplayCurrency"`
|
|
CurrencyFileUpdateDuration time.Duration `json:"currencyFileUpdateDuration"`
|
|
ForeignExchangeUpdateDuration time.Duration `json:"foreignExchangeUpdateDuration"`
|
|
}
|
|
|
|
// CryptocurrencyProvider defines coinmarketcap tools
|
|
type CryptocurrencyProvider struct {
|
|
Name string `json:"name"`
|
|
Enabled bool `json:"enabled"`
|
|
Verbose bool `json:"verbose"`
|
|
APIkey string `json:"apiKey"`
|
|
AccountPlan string `json:"accountPlan"`
|
|
}
|
|
|
|
// CommunicationsConfig holds all the information needed for each
|
|
// enabled communication package
|
|
type CommunicationsConfig struct {
|
|
SlackConfig SlackConfig `json:"slack"`
|
|
SMSGlobalConfig SMSGlobalConfig `json:"smsGlobal"`
|
|
SMTPConfig SMTPConfig `json:"smtp"`
|
|
TelegramConfig TelegramConfig `json:"telegram"`
|
|
}
|
|
|
|
// SlackConfig holds all variables to start and run the Slack package
|
|
type SlackConfig struct {
|
|
Name string `json:"name"`
|
|
Enabled bool `json:"enabled"`
|
|
Verbose bool `json:"verbose"`
|
|
TargetChannel string `json:"targetChannel"`
|
|
VerificationToken string `json:"verificationToken"`
|
|
}
|
|
|
|
// SMSContact stores the SMS contact info
|
|
type SMSContact struct {
|
|
Name string `json:"name"`
|
|
Number string `json:"number"`
|
|
Enabled bool `json:"enabled"`
|
|
}
|
|
|
|
// SMSGlobalConfig structure holds all the variables you need for instant
|
|
// messaging and broadcast used by SMSGlobal
|
|
type SMSGlobalConfig struct {
|
|
Name string `json:"name"`
|
|
Enabled bool `json:"enabled"`
|
|
Verbose bool `json:"verbose"`
|
|
Username string `json:"username"`
|
|
Password string `json:"password"`
|
|
Contacts []SMSContact `json:"contacts"`
|
|
}
|
|
|
|
// SMTPConfig holds all variables to start and run the SMTP package
|
|
type SMTPConfig struct {
|
|
Name string `json:"name"`
|
|
Enabled bool `json:"enabled"`
|
|
Verbose bool `json:"verbose"`
|
|
Host string `json:"host"`
|
|
Port string `json:"port"`
|
|
AccountName string `json:"accountName"`
|
|
AccountPassword string `json:"accountPassword"`
|
|
RecipientList string `json:"recipientList"`
|
|
}
|
|
|
|
// TelegramConfig holds all variables to start and run the Telegram package
|
|
type TelegramConfig struct {
|
|
Name string `json:"name"`
|
|
Enabled bool `json:"enabled"`
|
|
Verbose bool `json:"verbose"`
|
|
VerificationToken string `json:"verificationToken"`
|
|
}
|
|
|
|
// GetCurrencyConfig returns currency configurations
|
|
func (c *Config) GetCurrencyConfig() CurrencyConfig {
|
|
return c.Currency
|
|
}
|
|
|
|
// GetExchangeBankAccounts returns banking details associated with an exchange
|
|
// for depositing funds
|
|
func (c *Config) GetExchangeBankAccounts(exchangeName, depositingCurrency string) (BankAccount, error) {
|
|
m.Lock()
|
|
defer m.Unlock()
|
|
|
|
for x := range c.Exchanges {
|
|
if strings.EqualFold(c.Exchanges[x].Name, exchangeName) {
|
|
for y := range c.Exchanges[x].BankAccounts {
|
|
if common.StringContains(c.Exchanges[x].BankAccounts[y].SupportedCurrencies,
|
|
depositingCurrency) {
|
|
return c.Exchanges[x].BankAccounts[y], nil
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return BankAccount{}, fmt.Errorf("exchange %s bank details not found for %s",
|
|
exchangeName,
|
|
depositingCurrency)
|
|
}
|
|
|
|
// UpdateExchangeBankAccounts updates the configuration for the associated
|
|
// exchange bank
|
|
func (c *Config) UpdateExchangeBankAccounts(exchangeName string, bankCfg []BankAccount) error {
|
|
m.Lock()
|
|
defer m.Unlock()
|
|
|
|
for i := range c.Exchanges {
|
|
if strings.EqualFold(c.Exchanges[i].Name, exchangeName) {
|
|
c.Exchanges[i].BankAccounts = bankCfg
|
|
return nil
|
|
}
|
|
}
|
|
return fmt.Errorf("exchange %s not found",
|
|
exchangeName)
|
|
}
|
|
|
|
// GetClientBankAccounts returns banking details used for a given exchange
|
|
// and currency
|
|
func (c *Config) GetClientBankAccounts(exchangeName, targetCurrency string) (BankAccount, error) {
|
|
m.Lock()
|
|
defer m.Unlock()
|
|
|
|
for x := range c.BankAccounts {
|
|
if (common.StringContains(c.BankAccounts[x].SupportedExchanges, exchangeName) ||
|
|
c.BankAccounts[x].SupportedExchanges == "ALL") &&
|
|
common.StringContains(c.BankAccounts[x].SupportedCurrencies, targetCurrency) {
|
|
return c.BankAccounts[x], nil
|
|
|
|
}
|
|
}
|
|
return BankAccount{}, fmt.Errorf("client banking details not found for %s and currency %s",
|
|
exchangeName,
|
|
targetCurrency)
|
|
}
|
|
|
|
// UpdateClientBankAccounts updates the configuration for a bank
|
|
func (c *Config) UpdateClientBankAccounts(bankCfg *BankAccount) error {
|
|
m.Lock()
|
|
defer m.Unlock()
|
|
|
|
for i := range c.BankAccounts {
|
|
if c.BankAccounts[i].BankName == bankCfg.BankName && c.BankAccounts[i].AccountNumber == bankCfg.AccountNumber {
|
|
c.BankAccounts[i] = *bankCfg
|
|
return nil
|
|
}
|
|
}
|
|
return fmt.Errorf("client banking details for %s not found, update not applied",
|
|
bankCfg.BankName)
|
|
}
|
|
|
|
// CheckClientBankAccounts checks client bank details
|
|
func (c *Config) CheckClientBankAccounts() error {
|
|
m.Lock()
|
|
defer m.Unlock()
|
|
|
|
if len(c.BankAccounts) == 0 {
|
|
c.BankAccounts = append(c.BankAccounts,
|
|
BankAccount{
|
|
BankName: "test",
|
|
BankAddress: "test",
|
|
AccountName: "TestAccount",
|
|
AccountNumber: "0234",
|
|
SWIFTCode: "91272837",
|
|
IBAN: "98218738671897",
|
|
SupportedCurrencies: "USD",
|
|
SupportedExchanges: "ANX,Kraken",
|
|
},
|
|
)
|
|
return nil
|
|
}
|
|
|
|
for i := range c.BankAccounts {
|
|
if c.BankAccounts[i].Enabled {
|
|
if c.BankAccounts[i].BankName == "" || c.BankAccounts[i].BankAddress == "" {
|
|
return fmt.Errorf("banking details for %s is enabled but variables not set correctly",
|
|
c.BankAccounts[i].BankName)
|
|
}
|
|
|
|
if c.BankAccounts[i].AccountName == "" || c.BankAccounts[i].AccountNumber == "" {
|
|
return fmt.Errorf("banking account details for %s variables not set correctly",
|
|
c.BankAccounts[i].BankName)
|
|
}
|
|
if c.BankAccounts[i].IBAN == "" && c.BankAccounts[i].SWIFTCode == "" && c.BankAccounts[i].BSBNumber == "" {
|
|
return fmt.Errorf("critical banking numbers not set for %s in %s account",
|
|
c.BankAccounts[i].BankName,
|
|
c.BankAccounts[i].AccountName)
|
|
}
|
|
|
|
if c.BankAccounts[i].SupportedExchanges == "" {
|
|
c.BankAccounts[i].SupportedExchanges = "ALL"
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// GetCommunicationsConfig returns the communications configuration
|
|
func (c *Config) GetCommunicationsConfig() CommunicationsConfig {
|
|
m.Lock()
|
|
defer m.Unlock()
|
|
return c.Communications
|
|
}
|
|
|
|
// UpdateCommunicationsConfig sets a new updated version of a Communications
|
|
// configuration
|
|
func (c *Config) UpdateCommunicationsConfig(config *CommunicationsConfig) {
|
|
m.Lock()
|
|
c.Communications = *config
|
|
m.Unlock()
|
|
}
|
|
|
|
// GetCryptocurrencyProviderConfig returns the communications configuration
|
|
func (c *Config) GetCryptocurrencyProviderConfig() CryptocurrencyProvider {
|
|
m.Lock()
|
|
defer m.Unlock()
|
|
return c.Currency.CryptocurrencyProvider
|
|
}
|
|
|
|
// UpdateCryptocurrencyProviderConfig returns the communications configuration
|
|
func (c *Config) UpdateCryptocurrencyProviderConfig(config CryptocurrencyProvider) {
|
|
m.Lock()
|
|
c.Currency.CryptocurrencyProvider = config
|
|
m.Unlock()
|
|
}
|
|
|
|
// CheckCommunicationsConfig checks to see if the variables are set correctly
|
|
// from config.json
|
|
func (c *Config) CheckCommunicationsConfig() {
|
|
m.Lock()
|
|
defer m.Unlock()
|
|
|
|
// If the communications config hasn't been populated, populate
|
|
// with example settings
|
|
|
|
if c.Communications.SlackConfig.Name == "" {
|
|
c.Communications.SlackConfig = SlackConfig{
|
|
Name: "Slack",
|
|
TargetChannel: "general",
|
|
VerificationToken: "testtest",
|
|
}
|
|
}
|
|
|
|
if c.Communications.SMSGlobalConfig.Name == "" {
|
|
if c.SMS != nil {
|
|
if c.SMS.Contacts != nil {
|
|
c.Communications.SMSGlobalConfig = SMSGlobalConfig{
|
|
Name: "SMSGlobal",
|
|
Enabled: c.SMS.Enabled,
|
|
Verbose: c.SMS.Verbose,
|
|
Username: c.SMS.Username,
|
|
Password: c.SMS.Password,
|
|
Contacts: c.SMS.Contacts,
|
|
}
|
|
// flush old SMS config
|
|
c.SMS = nil
|
|
} else {
|
|
c.Communications.SMSGlobalConfig = SMSGlobalConfig{
|
|
Name: "SMSGlobal",
|
|
Username: "main",
|
|
Password: "test",
|
|
|
|
Contacts: []SMSContact{
|
|
{
|
|
Name: "bob",
|
|
Number: "1234",
|
|
Enabled: false,
|
|
},
|
|
},
|
|
}
|
|
}
|
|
} else {
|
|
c.Communications.SMSGlobalConfig = SMSGlobalConfig{
|
|
Name: "SMSGlobal",
|
|
Username: "main",
|
|
Password: "test",
|
|
|
|
Contacts: []SMSContact{
|
|
{
|
|
Name: "bob",
|
|
Number: "1234",
|
|
Enabled: false,
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
} else {
|
|
if c.SMS != nil {
|
|
// flush old SMS config
|
|
c.SMS = nil
|
|
}
|
|
}
|
|
|
|
if c.Communications.SMTPConfig.Name == "" {
|
|
c.Communications.SMTPConfig = SMTPConfig{
|
|
Name: "SMTP",
|
|
Host: "smtp.google.com",
|
|
Port: "537",
|
|
AccountName: "some",
|
|
AccountPassword: "password",
|
|
RecipientList: "lol123@gmail.com",
|
|
}
|
|
}
|
|
|
|
if c.Communications.TelegramConfig.Name == "" {
|
|
c.Communications.TelegramConfig = TelegramConfig{
|
|
Name: "Telegram",
|
|
VerificationToken: "testest",
|
|
}
|
|
}
|
|
|
|
if c.Communications.SlackConfig.Name != "Slack" ||
|
|
c.Communications.SMSGlobalConfig.Name != "SMSGlobal" ||
|
|
c.Communications.SMTPConfig.Name != "SMTP" ||
|
|
c.Communications.TelegramConfig.Name != "Telegram" {
|
|
log.Warn("Communications config name/s not set correctly")
|
|
}
|
|
if c.Communications.SlackConfig.Enabled {
|
|
if c.Communications.SlackConfig.TargetChannel == "" ||
|
|
c.Communications.SlackConfig.VerificationToken == "" ||
|
|
c.Communications.SlackConfig.VerificationToken == "testtest" {
|
|
c.Communications.SlackConfig.Enabled = false
|
|
log.Warn("Slack enabled in config but variable data not set, disabling.")
|
|
}
|
|
}
|
|
if c.Communications.SMSGlobalConfig.Enabled {
|
|
if c.Communications.SMSGlobalConfig.Username == "" ||
|
|
c.Communications.SMSGlobalConfig.Password == "" ||
|
|
len(c.Communications.SMSGlobalConfig.Contacts) == 0 {
|
|
c.Communications.SMSGlobalConfig.Enabled = false
|
|
log.Warn("SMSGlobal enabled in config but variable data not set, disabling.")
|
|
}
|
|
}
|
|
if c.Communications.SMTPConfig.Enabled {
|
|
if c.Communications.SMTPConfig.Host == "" ||
|
|
c.Communications.SMTPConfig.Port == "" ||
|
|
c.Communications.SMTPConfig.AccountName == "" ||
|
|
c.Communications.SMTPConfig.AccountPassword == "" {
|
|
c.Communications.SMTPConfig.Enabled = false
|
|
log.Warn("SMTP enabled in config but variable data not set, disabling.")
|
|
}
|
|
}
|
|
if c.Communications.TelegramConfig.Enabled {
|
|
if c.Communications.TelegramConfig.VerificationToken == "" {
|
|
c.Communications.TelegramConfig.Enabled = false
|
|
log.Warn("Telegram enabled in config but variable data not set, disabling.")
|
|
}
|
|
}
|
|
}
|
|
|
|
// CheckPairConsistency checks to see if the enabled pair exists in the
|
|
// available pairs list
|
|
func (c *Config) CheckPairConsistency(exchName string) error {
|
|
enabledPairs, err := c.GetEnabledPairs(exchName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
availPairs, err := c.GetAvailablePairs(exchName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var pairs, pairsRemoved currency.Pairs
|
|
update := false
|
|
for x := range enabledPairs {
|
|
if !availPairs.Contains(enabledPairs[x], true) {
|
|
update = true
|
|
pairsRemoved = append(pairsRemoved, enabledPairs[x])
|
|
continue
|
|
}
|
|
pairs = append(pairs, enabledPairs[x])
|
|
}
|
|
|
|
if !update {
|
|
return nil
|
|
}
|
|
|
|
exchCfg, err := c.GetExchangeConfig(exchName)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if len(pairs) == 0 {
|
|
exchCfg.EnabledPairs = append(exchCfg.EnabledPairs,
|
|
availPairs.GetRandomPair())
|
|
log.Debugf("Exchange %s: No enabled pairs found in available pairs, randomly added %v\n",
|
|
exchName,
|
|
exchCfg.EnabledPairs)
|
|
} else {
|
|
exchCfg.EnabledPairs = pairs
|
|
}
|
|
|
|
err = c.UpdateExchangeConfig(&exchCfg)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
log.Debugf("Exchange %s: Removing enabled pair(s) %v from enabled pairs as it isn't an available pair",
|
|
exchName,
|
|
pairsRemoved.Strings())
|
|
return nil
|
|
}
|
|
|
|
// SupportsPair returns true or not whether the exchange supports the supplied
|
|
// pair
|
|
func (c *Config) SupportsPair(exchName string, p currency.Pair) (bool, error) {
|
|
pairs, err := c.GetAvailablePairs(exchName)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
return pairs.Contains(p, false), nil
|
|
}
|
|
|
|
// GetAvailablePairs returns a list of currency pairs for a specifc exchange
|
|
func (c *Config) GetAvailablePairs(exchName string) (currency.Pairs, error) {
|
|
exchCfg, err := c.GetExchangeConfig(exchName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return exchCfg.AvailablePairs.Format(exchCfg.ConfigCurrencyPairFormat.Delimiter,
|
|
exchCfg.ConfigCurrencyPairFormat.Index,
|
|
exchCfg.ConfigCurrencyPairFormat.Uppercase), nil
|
|
}
|
|
|
|
// GetEnabledPairs returns a list of currency pairs for a specifc exchange
|
|
func (c *Config) GetEnabledPairs(exchName string) (currency.Pairs, error) {
|
|
exchCfg, err := c.GetExchangeConfig(exchName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return exchCfg.EnabledPairs.Format(exchCfg.ConfigCurrencyPairFormat.Delimiter,
|
|
exchCfg.ConfigCurrencyPairFormat.Index,
|
|
exchCfg.ConfigCurrencyPairFormat.Uppercase), nil
|
|
}
|
|
|
|
// GetEnabledExchanges returns a list of enabled exchanges
|
|
func (c *Config) GetEnabledExchanges() []string {
|
|
var enabledExchs []string
|
|
for i := range c.Exchanges {
|
|
if c.Exchanges[i].Enabled {
|
|
enabledExchs = append(enabledExchs, c.Exchanges[i].Name)
|
|
}
|
|
}
|
|
return enabledExchs
|
|
}
|
|
|
|
// GetDisabledExchanges returns a list of disabled exchanges
|
|
func (c *Config) GetDisabledExchanges() []string {
|
|
var disabledExchs []string
|
|
for i := range c.Exchanges {
|
|
if !c.Exchanges[i].Enabled {
|
|
disabledExchs = append(disabledExchs, c.Exchanges[i].Name)
|
|
}
|
|
}
|
|
return disabledExchs
|
|
}
|
|
|
|
// CountEnabledExchanges returns the number of exchanges that are enabled.
|
|
func (c *Config) CountEnabledExchanges() int {
|
|
counter := 0
|
|
for i := range c.Exchanges {
|
|
if c.Exchanges[i].Enabled {
|
|
counter++
|
|
}
|
|
}
|
|
return counter
|
|
}
|
|
|
|
// GetConfigCurrencyPairFormat returns the config currency pair format
|
|
// for a specific exchange
|
|
func (c *Config) GetConfigCurrencyPairFormat(exchName string) (*CurrencyPairFormatConfig, error) {
|
|
exchCfg, err := c.GetExchangeConfig(exchName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return exchCfg.ConfigCurrencyPairFormat, nil
|
|
}
|
|
|
|
// GetRequestCurrencyPairFormat returns the request currency pair format
|
|
// for a specific exchange
|
|
func (c *Config) GetRequestCurrencyPairFormat(exchName string) (*CurrencyPairFormatConfig, error) {
|
|
exchCfg, err := c.GetExchangeConfig(exchName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return exchCfg.RequestCurrencyPairFormat, nil
|
|
}
|
|
|
|
// GetCurrencyPairDisplayConfig retrieves the currency pair display preference
|
|
func (c *Config) GetCurrencyPairDisplayConfig() *CurrencyPairFormatConfig {
|
|
return c.Currency.CurrencyPairFormat
|
|
}
|
|
|
|
// GetAllExchangeConfigs returns all exchange configurations
|
|
func (c *Config) GetAllExchangeConfigs() []ExchangeConfig {
|
|
m.Lock()
|
|
defer m.Unlock()
|
|
return c.Exchanges
|
|
}
|
|
|
|
// GetExchangeConfig returns exchange configurations by its indivdual name
|
|
func (c *Config) GetExchangeConfig(name string) (ExchangeConfig, error) {
|
|
m.Lock()
|
|
defer m.Unlock()
|
|
for i := range c.Exchanges {
|
|
if strings.EqualFold(c.Exchanges[i].Name, name) {
|
|
return c.Exchanges[i], nil
|
|
}
|
|
}
|
|
return ExchangeConfig{}, fmt.Errorf(ErrExchangeNotFound, name)
|
|
}
|
|
|
|
// GetForexProviderConfig returns a forex provider configuration by its name
|
|
func (c *Config) GetForexProviderConfig(name string) (base.Settings, error) {
|
|
m.Lock()
|
|
defer m.Unlock()
|
|
for i := range c.Currency.ForexProviders {
|
|
if strings.EqualFold(c.Currency.ForexProviders[i].Name, name) {
|
|
return c.Currency.ForexProviders[i], nil
|
|
}
|
|
}
|
|
return base.Settings{}, errors.New("provider not found")
|
|
}
|
|
|
|
// GetPrimaryForexProvider returns the primary forex provider
|
|
func (c *Config) GetPrimaryForexProvider() string {
|
|
m.Lock()
|
|
defer m.Unlock()
|
|
for i := range c.Currency.ForexProviders {
|
|
if c.Currency.ForexProviders[i].PrimaryProvider {
|
|
return c.Currency.ForexProviders[i].Name
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// UpdateExchangeConfig updates exchange configurations
|
|
func (c *Config) UpdateExchangeConfig(e *ExchangeConfig) error {
|
|
m.Lock()
|
|
defer m.Unlock()
|
|
for i := range c.Exchanges {
|
|
if strings.EqualFold(c.Exchanges[i].Name, e.Name) {
|
|
c.Exchanges[i] = *e
|
|
return nil
|
|
}
|
|
}
|
|
return fmt.Errorf(ErrExchangeNotFound, e.Name)
|
|
}
|
|
|
|
// CheckExchangeConfigValues returns configuation values for all enabled
|
|
// exchanges
|
|
func (c *Config) CheckExchangeConfigValues() error {
|
|
exchanges := 0
|
|
for i := range c.Exchanges {
|
|
if strings.EqualFold(c.Exchanges[i].Name, "GDAX") {
|
|
c.Exchanges[i].Name = "CoinbasePro"
|
|
}
|
|
|
|
if c.Exchanges[i].WebsocketURL != WebsocketURLNonDefaultMessage {
|
|
if c.Exchanges[i].WebsocketURL == "" {
|
|
c.Exchanges[i].WebsocketURL = WebsocketURLNonDefaultMessage
|
|
}
|
|
}
|
|
|
|
if c.Exchanges[i].APIURL != APIURLNonDefaultMessage {
|
|
if c.Exchanges[i].APIURL == "" {
|
|
// Set default if nothing set
|
|
c.Exchanges[i].APIURL = APIURLNonDefaultMessage
|
|
}
|
|
}
|
|
|
|
if c.Exchanges[i].APIURLSecondary != APIURLNonDefaultMessage {
|
|
if c.Exchanges[i].APIURLSecondary == "" {
|
|
// Set default if nothing set
|
|
c.Exchanges[i].APIURLSecondary = APIURLNonDefaultMessage
|
|
}
|
|
}
|
|
|
|
if c.Exchanges[i].Enabled {
|
|
if c.Exchanges[i].Name == "" {
|
|
return fmt.Errorf(ErrExchangeNameEmpty, i)
|
|
}
|
|
if len(c.Exchanges[i].AvailablePairs) == 0 {
|
|
return fmt.Errorf(ErrExchangeAvailablePairsEmpty, c.Exchanges[i].Name)
|
|
}
|
|
if len(c.Exchanges[i].EnabledPairs) == 0 {
|
|
return fmt.Errorf(ErrExchangeEnabledPairsEmpty, c.Exchanges[i].Name)
|
|
}
|
|
if len(c.Exchanges[i].BaseCurrencies) == 0 {
|
|
return fmt.Errorf(ErrExchangeBaseCurrenciesEmpty, c.Exchanges[i].Name)
|
|
}
|
|
|
|
var areAuthenticatedCredentialsValid bool
|
|
if c.Exchanges[i].AuthenticatedWebsocketAPISupport || c.Exchanges[i].AuthenticatedAPISupport {
|
|
areAuthenticatedCredentialsValid = c.areAuthenticatedCredentialsValid(i)
|
|
}
|
|
if c.Exchanges[i].AuthenticatedWebsocketAPISupport {
|
|
c.Exchanges[i].AuthenticatedWebsocketAPISupport = areAuthenticatedCredentialsValid
|
|
}
|
|
if c.Exchanges[i].AuthenticatedAPISupport {
|
|
c.Exchanges[i].AuthenticatedAPISupport = areAuthenticatedCredentialsValid
|
|
}
|
|
|
|
if !c.Exchanges[i].SupportsAutoPairUpdates {
|
|
lastUpdated := common.UnixTimestampToTime(c.Exchanges[i].PairsLastUpdated)
|
|
lastUpdated = lastUpdated.AddDate(0, 0, configPairsLastUpdatedWarningThreshold)
|
|
if lastUpdated.Unix() <= time.Now().Unix() {
|
|
log.Warnf(WarningPairsLastUpdatedThresholdExceeded, c.Exchanges[i].Name, configPairsLastUpdatedWarningThreshold)
|
|
}
|
|
}
|
|
|
|
if c.Exchanges[i].HTTPTimeout <= 0 {
|
|
log.Warnf("Exchange %s HTTP Timeout value not set, defaulting to %v.", c.Exchanges[i].Name, configDefaultHTTPTimeout)
|
|
c.Exchanges[i].HTTPTimeout = configDefaultHTTPTimeout
|
|
}
|
|
|
|
err := c.CheckPairConsistency(c.Exchanges[i].Name)
|
|
if err != nil {
|
|
log.Errorf("Exchange %s: CheckPairConsistency error: %s", c.Exchanges[i].Name, err)
|
|
}
|
|
|
|
if len(c.Exchanges[i].BankAccounts) == 0 {
|
|
c.Exchanges[i].BankAccounts = append(c.Exchanges[i].BankAccounts, BankAccount{})
|
|
} else {
|
|
for y := range c.Exchanges[i].BankAccounts {
|
|
bankAccount := &c.Exchanges[i].BankAccounts[y]
|
|
if bankAccount.Enabled {
|
|
if bankAccount.BankName == "" || bankAccount.BankAddress == "" {
|
|
log.Warnf("banking details for %s is enabled but variables not set",
|
|
c.Exchanges[i].Name)
|
|
bankAccount.Enabled = false
|
|
}
|
|
|
|
if bankAccount.AccountName == "" || bankAccount.AccountNumber == "" {
|
|
log.Warnf("banking account details for %s variables not set",
|
|
c.Exchanges[i].Name)
|
|
bankAccount.Enabled = false
|
|
}
|
|
|
|
if bankAccount.SupportedCurrencies == "" {
|
|
log.Warnf("banking account details for %s acceptable funding currencies not set",
|
|
c.Exchanges[i].Name)
|
|
bankAccount.Enabled = false
|
|
}
|
|
|
|
if bankAccount.BSBNumber == "" && bankAccount.IBAN == "" &&
|
|
bankAccount.SWIFTCode == "" {
|
|
log.Warnf("banking account details for %s critical banking numbers not set",
|
|
c.Exchanges[i].Name)
|
|
bankAccount.Enabled = false
|
|
}
|
|
}
|
|
}
|
|
}
|
|
exchanges++
|
|
}
|
|
}
|
|
if exchanges == 0 {
|
|
return errors.New(ErrNoEnabledExchanges)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *Config) areAuthenticatedCredentialsValid(i int) bool {
|
|
if c.Exchanges == nil {
|
|
log.Error("Config: Failed to check exchange authenticated credentials due to c.Exchanges not setup")
|
|
return false
|
|
}
|
|
if i < 0 || c.Exchanges == nil || len(c.Exchanges) < i {
|
|
log.Error("Config: Failed to check exchange authenticated credentials due to invalid index")
|
|
return false
|
|
}
|
|
|
|
resp := true
|
|
if c.Exchanges[i].APIKey == "" || c.Exchanges[i].APIKey == DefaultUnsetAPIKey {
|
|
resp = false
|
|
}
|
|
|
|
if (c.Exchanges[i].APISecret == "" || c.Exchanges[i].APISecret == DefaultUnsetAPISecret) &&
|
|
c.Exchanges[i].Name != "COINUT" {
|
|
resp = false
|
|
}
|
|
|
|
if (c.Exchanges[i].ClientID == "ClientID" || c.Exchanges[i].ClientID == "") &&
|
|
(c.Exchanges[i].Name == "ITBIT" || c.Exchanges[i].Name == "Bitstamp" || c.Exchanges[i].Name == "COINUT" || c.Exchanges[i].Name == "CoinbasePro") {
|
|
resp = false
|
|
}
|
|
// non-fatal error
|
|
if !resp {
|
|
log.Warnf(WarningExchangeAuthAPIDefaultOrEmptyValues, c.Exchanges[i].Name)
|
|
}
|
|
return resp
|
|
}
|
|
|
|
// CheckWebserverConfigValues checks information before webserver starts and
|
|
// returns an error if values are incorrect.
|
|
func (c *Config) CheckWebserverConfigValues() error {
|
|
if c.Webserver.AdminUsername == "" || c.Webserver.AdminPassword == "" {
|
|
return errors.New(WarningWebserverCredentialValuesEmpty)
|
|
}
|
|
|
|
if !common.StringContains(c.Webserver.ListenAddress, ":") {
|
|
return errors.New(WarningWebserverListenAddressInvalid)
|
|
}
|
|
|
|
portStr := common.SplitStrings(c.Webserver.ListenAddress, ":")[1]
|
|
port, err := strconv.Atoi(portStr)
|
|
if err != nil {
|
|
return errors.New(WarningWebserverListenAddressInvalid)
|
|
}
|
|
|
|
if port < 1 || port > 65355 {
|
|
return errors.New(WarningWebserverListenAddressInvalid)
|
|
}
|
|
|
|
if c.Webserver.WebsocketConnectionLimit <= 0 {
|
|
c.Webserver.WebsocketConnectionLimit = 1
|
|
}
|
|
|
|
if c.Webserver.WebsocketMaxAuthFailures <= 0 {
|
|
c.Webserver.WebsocketMaxAuthFailures = 3
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// CheckCurrencyConfigValues checks to see if the currency config values are correct or not
|
|
func (c *Config) CheckCurrencyConfigValues() error {
|
|
fxProviders := forexprovider.GetAvailableForexProviders()
|
|
if len(fxProviders) == 0 {
|
|
return errors.New("no forex providers available")
|
|
}
|
|
|
|
if len(fxProviders) != len(c.Currency.ForexProviders) {
|
|
for x := range fxProviders {
|
|
_, err := c.GetForexProviderConfig(fxProviders[x])
|
|
if err != nil {
|
|
log.Warnf("%s forex provider not found, adding to config..", fxProviders[x])
|
|
c.Currency.ForexProviders = append(c.Currency.ForexProviders, base.Settings{
|
|
Name: fxProviders[x],
|
|
RESTPollingDelay: 600,
|
|
APIKey: DefaultUnsetAPIKey,
|
|
APIKeyLvl: -1,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
count := 0
|
|
for i := range c.Currency.ForexProviders {
|
|
if c.Currency.ForexProviders[i].Enabled {
|
|
if c.Currency.ForexProviders[i].APIKey == DefaultUnsetAPIKey && c.Currency.ForexProviders[i].Name != DefaultForexProviderExchangeRatesAPI {
|
|
log.Warnf("%s enabled forex provider API key not set. Please set this in your config.json file", c.Currency.ForexProviders[i].Name)
|
|
c.Currency.ForexProviders[i].Enabled = false
|
|
c.Currency.ForexProviders[i].PrimaryProvider = false
|
|
continue
|
|
}
|
|
|
|
if c.Currency.ForexProviders[i].Name == "CurrencyConverter" {
|
|
if c.Currency.ForexProviders[i].Enabled &&
|
|
c.Currency.ForexProviders[i].PrimaryProvider &&
|
|
(c.Currency.ForexProviders[i].APIKey == "" ||
|
|
c.Currency.ForexProviders[i].APIKey == DefaultUnsetAPIKey) {
|
|
log.Warnf("CurrencyConverter forex provider no longer supports unset API key requests. Switching to ExchangeRates FX provider..")
|
|
c.Currency.ForexProviders[i].Enabled = false
|
|
c.Currency.ForexProviders[i].PrimaryProvider = false
|
|
c.Currency.ForexProviders[i].APIKey = DefaultUnsetAPIKey
|
|
c.Currency.ForexProviders[i].APIKeyLvl = -1
|
|
continue
|
|
}
|
|
}
|
|
|
|
if c.Currency.ForexProviders[i].APIKeyLvl == -1 && c.Currency.ForexProviders[i].Name != DefaultForexProviderExchangeRatesAPI {
|
|
log.Warnf("%s APIKey Level not set, functions limited. Please set this in your config.json file",
|
|
c.Currency.ForexProviders[i].Name)
|
|
}
|
|
count++
|
|
}
|
|
}
|
|
|
|
if count == 0 {
|
|
for x := range c.Currency.ForexProviders {
|
|
if c.Currency.ForexProviders[x].Name == DefaultForexProviderExchangeRatesAPI {
|
|
c.Currency.ForexProviders[x].Enabled = true
|
|
c.Currency.ForexProviders[x].PrimaryProvider = true
|
|
log.Warn("Using ExchangeRatesAPI for default forex provider.")
|
|
}
|
|
}
|
|
}
|
|
|
|
if c.Currency.CryptocurrencyProvider == (CryptocurrencyProvider{}) {
|
|
c.Currency.CryptocurrencyProvider.Name = "CoinMarketCap"
|
|
c.Currency.CryptocurrencyProvider.Enabled = false
|
|
c.Currency.CryptocurrencyProvider.Verbose = false
|
|
c.Currency.CryptocurrencyProvider.AccountPlan = DefaultUnsetAccountPlan
|
|
c.Currency.CryptocurrencyProvider.APIkey = DefaultUnsetAPIKey
|
|
}
|
|
|
|
if c.Currency.CryptocurrencyProvider.Enabled {
|
|
if c.Currency.CryptocurrencyProvider.APIkey == "" ||
|
|
c.Currency.CryptocurrencyProvider.APIkey == DefaultUnsetAPIKey {
|
|
log.Warnf("CryptocurrencyProvider enabled but api key is unset please set this in your config.json file")
|
|
}
|
|
if c.Currency.CryptocurrencyProvider.AccountPlan == "" ||
|
|
c.Currency.CryptocurrencyProvider.AccountPlan == DefaultUnsetAccountPlan {
|
|
log.Warnf("CryptocurrencyProvider enabled but account plan is unset please set this in your config.json file")
|
|
}
|
|
} else {
|
|
if c.Currency.CryptocurrencyProvider.APIkey == "" {
|
|
c.Currency.CryptocurrencyProvider.APIkey = DefaultUnsetAPIKey
|
|
}
|
|
if c.Currency.CryptocurrencyProvider.AccountPlan == "" {
|
|
c.Currency.CryptocurrencyProvider.AccountPlan = DefaultUnsetAccountPlan
|
|
}
|
|
}
|
|
|
|
if c.Currency.Cryptocurrencies.Join() == "" {
|
|
if c.Cryptocurrencies.Join() != "" {
|
|
c.Currency.Cryptocurrencies = c.Cryptocurrencies
|
|
c.Cryptocurrencies = nil
|
|
} else {
|
|
c.Currency.Cryptocurrencies = currency.GetDefaultCryptocurrencies()
|
|
}
|
|
}
|
|
|
|
if c.Currency.CurrencyPairFormat == nil {
|
|
if c.CurrencyPairFormat != nil {
|
|
c.Currency.CurrencyPairFormat = c.CurrencyPairFormat
|
|
c.CurrencyPairFormat = nil
|
|
} else {
|
|
c.Currency.CurrencyPairFormat = &CurrencyPairFormatConfig{
|
|
Delimiter: "-",
|
|
Uppercase: true,
|
|
}
|
|
}
|
|
}
|
|
|
|
if c.Currency.FiatDisplayCurrency.IsEmpty() {
|
|
if c.FiatDisplayCurrency.IsEmpty() {
|
|
c.Currency.FiatDisplayCurrency = c.FiatDisplayCurrency
|
|
c.FiatDisplayCurrency = currency.NewCode("")
|
|
} else {
|
|
c.Currency.FiatDisplayCurrency = currency.USD
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// RetrieveConfigCurrencyPairs splits, assigns and verifies enabled currency
|
|
// pairs either cryptoCurrencies or fiatCurrencies
|
|
func (c *Config) RetrieveConfigCurrencyPairs(enabledOnly bool) error {
|
|
cryptoCurrencies := c.Currency.Cryptocurrencies
|
|
fiatCurrencies := currency.GetFiatCurrencies()
|
|
|
|
for x := range c.Exchanges {
|
|
if !c.Exchanges[x].Enabled && enabledOnly {
|
|
continue
|
|
}
|
|
|
|
baseCurrencies := c.Exchanges[x].BaseCurrencies
|
|
for y := range baseCurrencies {
|
|
if !fiatCurrencies.Contains(baseCurrencies[y]) {
|
|
fiatCurrencies = append(fiatCurrencies, baseCurrencies[y])
|
|
}
|
|
}
|
|
}
|
|
|
|
for x := range c.Exchanges {
|
|
var pairs []currency.Pair
|
|
var err error
|
|
if !c.Exchanges[x].Enabled && enabledOnly {
|
|
pairs, err = c.GetEnabledPairs(c.Exchanges[x].Name)
|
|
} else {
|
|
pairs, err = c.GetAvailablePairs(c.Exchanges[x].Name)
|
|
}
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for y := range pairs {
|
|
if !fiatCurrencies.Contains(pairs[y].Base) &&
|
|
!cryptoCurrencies.Contains(pairs[y].Base) {
|
|
cryptoCurrencies = append(cryptoCurrencies, pairs[y].Base)
|
|
}
|
|
|
|
if !fiatCurrencies.Contains(pairs[y].Quote) &&
|
|
!cryptoCurrencies.Contains(pairs[y].Quote) {
|
|
cryptoCurrencies = append(cryptoCurrencies, pairs[y].Quote)
|
|
}
|
|
}
|
|
}
|
|
|
|
currency.UpdateCurrencies(fiatCurrencies, false)
|
|
currency.UpdateCurrencies(cryptoCurrencies, true)
|
|
return nil
|
|
}
|
|
|
|
// CheckLoggerConfig checks to see logger values are present and valid in config
|
|
// if not creates a default instance of the logger
|
|
func (c *Config) CheckLoggerConfig() error {
|
|
m.Lock()
|
|
defer m.Unlock()
|
|
|
|
// check if enabled is nil or level is a blank string
|
|
if c.Logging.Enabled == nil || c.Logging.Level == "" {
|
|
// Creates a new pointer to bool and sets it as true
|
|
t := func(t bool) *bool { return &t }(true)
|
|
|
|
log.Warn("Missing or invalid config settings using safe defaults")
|
|
|
|
// Set logger to safe defaults
|
|
|
|
c.Logging = log.Logging{
|
|
Enabled: t,
|
|
Level: "DEBUG|INFO|WARN|ERROR|FATAL",
|
|
ColourOutput: false,
|
|
File: "debug.txt",
|
|
Rotate: false,
|
|
}
|
|
log.Logger = &c.Logging
|
|
} else {
|
|
log.Logger = &c.Logging
|
|
}
|
|
|
|
if len(c.Logging.File) > 0 {
|
|
logPath := filepath.Join(common.GetDefaultDataDir(runtime.GOOS), "logs")
|
|
err := common.CreateDir(logPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
log.LogPath = logPath
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// CheckNTPConfig checks for missing or incorrectly configured NTPClient and recreates with known safe defaults
|
|
func (c *Config) CheckNTPConfig() {
|
|
m.Lock()
|
|
defer m.Unlock()
|
|
|
|
if c.NTPClient.AllowedDifference == nil || *c.NTPClient.AllowedDifference == 0 {
|
|
c.NTPClient.AllowedDifference = new(time.Duration)
|
|
*c.NTPClient.AllowedDifference = defaultNTPAllowedDifference
|
|
}
|
|
|
|
if c.NTPClient.AllowedNegativeDifference == nil || *c.NTPClient.AllowedNegativeDifference <= 0 {
|
|
c.NTPClient.AllowedNegativeDifference = new(time.Duration)
|
|
*c.NTPClient.AllowedNegativeDifference = defaultNTPAllowedNegativeDifference
|
|
}
|
|
|
|
if len(c.NTPClient.Pool) < 1 {
|
|
log.Warn("NTPClient enabled with no servers configured enabling default pool")
|
|
c.NTPClient.Pool = []string{"pool.ntp.org:123"}
|
|
}
|
|
}
|
|
|
|
// DisableNTPCheck allows the user to change how they are prompted for timesync alerts
|
|
func (c *Config) DisableNTPCheck(input io.Reader) (string, error) {
|
|
m.Lock()
|
|
defer m.Unlock()
|
|
|
|
reader := bufio.NewReader(input)
|
|
log.Warn("Your system time is out of sync this may cause issues with trading")
|
|
log.Warn("How would you like to show future notifications? (a)lert / (w)arn / (d)isable \n")
|
|
|
|
var answered = false
|
|
for ok := true; ok; ok = (!answered) {
|
|
answer, err := reader.ReadString('\n')
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
answer = strings.TrimRight(answer, "\r\n")
|
|
switch answer {
|
|
case "a":
|
|
c.NTPClient.Level = 0
|
|
answered = true
|
|
return "Time sync has been set to alert", nil
|
|
case "w":
|
|
c.NTPClient.Level = 1
|
|
answered = true
|
|
return "Time sync has been set to warn only", nil
|
|
case "d":
|
|
c.NTPClient.Level = -1
|
|
answered = true
|
|
return "Future notications for out time sync have been disabled", nil
|
|
}
|
|
}
|
|
return "", errors.New("something went wrong NTPCheck should never make it this far")
|
|
}
|
|
|
|
// CheckConnectionMonitorConfig checks and if zero value assigns default values
|
|
func (c *Config) CheckConnectionMonitorConfig() {
|
|
m.Lock()
|
|
defer m.Unlock()
|
|
|
|
if c.ConnectionMonitor.CheckInterval == 0 {
|
|
c.ConnectionMonitor.CheckInterval = connchecker.DefaultCheckInterval
|
|
}
|
|
|
|
if len(c.ConnectionMonitor.DNSList) == 0 {
|
|
c.ConnectionMonitor.DNSList = connchecker.DefaultDNSList
|
|
}
|
|
|
|
if len(c.ConnectionMonitor.PublicDomainList) == 0 {
|
|
c.ConnectionMonitor.PublicDomainList = connchecker.DefaultDomainList
|
|
}
|
|
}
|
|
|
|
// GetFilePath returns the desired config file or the default config file name
|
|
// based on if the application is being run under test or normal mode.
|
|
func GetFilePath(file string) (string, error) {
|
|
if file != "" {
|
|
return file, nil
|
|
}
|
|
|
|
if flag.Lookup("test.v") != nil && !testBypass {
|
|
return ConfigTestFile, nil
|
|
}
|
|
|
|
exePath, err := common.GetExecutablePath()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
oldDir := exePath + common.GetOSPathSlash()
|
|
oldDirs := []string{oldDir + ConfigFile, oldDir + EncryptedConfigFile}
|
|
|
|
newDir := common.GetDefaultDataDir(runtime.GOOS) + common.GetOSPathSlash()
|
|
err = common.CreateDir(newDir)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
newDirs := []string{newDir + ConfigFile, newDir + EncryptedConfigFile}
|
|
|
|
// First upgrade the old dir config file if it exists to the corresponding new one
|
|
for x := range oldDirs {
|
|
_, err := os.Stat(oldDirs[x])
|
|
if os.IsNotExist(err) {
|
|
continue
|
|
}
|
|
if filepath.Ext(oldDirs[x]) == ".json" {
|
|
err = os.Rename(oldDirs[x], newDirs[0])
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
log.Debugf("Renamed old config file %s to %s", oldDirs[x], newDirs[0])
|
|
} else {
|
|
err = os.Rename(oldDirs[x], newDirs[1])
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
log.Debugf("Renamed old config file %s to %s", oldDirs[x], newDirs[1])
|
|
}
|
|
}
|
|
|
|
// Secondly check to see if the new config file extension is correct or not
|
|
for x := range newDirs {
|
|
_, err := os.Stat(newDirs[x])
|
|
if os.IsNotExist(err) {
|
|
continue
|
|
}
|
|
|
|
data, err := common.ReadFile(newDirs[x])
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
if ConfirmECS(data) {
|
|
if filepath.Ext(newDirs[x]) == ".dat" {
|
|
return newDirs[x], nil
|
|
}
|
|
|
|
err = os.Rename(newDirs[x], newDirs[1])
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return newDirs[1], nil
|
|
}
|
|
|
|
if filepath.Ext(newDirs[x]) == ".json" {
|
|
return newDirs[x], nil
|
|
}
|
|
|
|
err = os.Rename(newDirs[x], newDirs[0])
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
return newDirs[0], nil
|
|
}
|
|
|
|
return "", errors.New("config default file path error")
|
|
}
|
|
|
|
// ReadConfig verifies and checks for encryption and verifies the unencrypted
|
|
// file contains JSON.
|
|
func (c *Config) ReadConfig(configPath string) error {
|
|
defaultPath, err := GetFilePath(configPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
file, err := common.ReadFile(defaultPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if !ConfirmECS(file) {
|
|
err = ConfirmConfigJSON(file, &c)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if c.EncryptConfig == configFileEncryptionDisabled {
|
|
return nil
|
|
}
|
|
|
|
if c.EncryptConfig == configFileEncryptionPrompt {
|
|
m.Lock()
|
|
IsInitialSetup = true
|
|
m.Unlock()
|
|
if c.PromptForConfigEncryption() {
|
|
c.EncryptConfig = configFileEncryptionEnabled
|
|
return c.SaveConfig(defaultPath)
|
|
}
|
|
}
|
|
} else {
|
|
errCounter := 0
|
|
for {
|
|
if errCounter >= configMaxAuthFailres {
|
|
return errors.New("failed to decrypt config after 3 attempts")
|
|
}
|
|
key, err := PromptForConfigKey(IsInitialSetup)
|
|
if err != nil {
|
|
log.Errorf("PromptForConfigKey err: %s", err)
|
|
errCounter++
|
|
continue
|
|
}
|
|
|
|
var f []byte
|
|
f = append(f, file...)
|
|
data, err := DecryptConfigFile(f, key)
|
|
if err != nil {
|
|
log.Errorf("DecryptConfigFile err: %s", err)
|
|
errCounter++
|
|
continue
|
|
}
|
|
|
|
err = ConfirmConfigJSON(data, &c)
|
|
if err != nil {
|
|
if errCounter < configMaxAuthFailres {
|
|
log.Errorf("Invalid password.")
|
|
}
|
|
errCounter++
|
|
continue
|
|
}
|
|
break
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// SaveConfig saves your configuration to your desired path
|
|
func (c *Config) SaveConfig(configPath string) error {
|
|
defaultPath, err := GetFilePath(configPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
payload, err := json.MarshalIndent(c, "", " ")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if c.EncryptConfig == configFileEncryptionEnabled {
|
|
var key []byte
|
|
|
|
if IsInitialSetup {
|
|
key, err = PromptForConfigKey(true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
IsInitialSetup = false
|
|
}
|
|
|
|
payload, err = EncryptConfigFile(payload, key)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return common.WriteFile(defaultPath, payload)
|
|
}
|
|
|
|
// CheckConfig checks all config settings
|
|
func (c *Config) CheckConfig() error {
|
|
err := c.CheckExchangeConfigValues()
|
|
if err != nil {
|
|
return fmt.Errorf(ErrCheckingConfigValues, err)
|
|
}
|
|
|
|
c.CheckConnectionMonitorConfig()
|
|
c.CheckCommunicationsConfig()
|
|
|
|
if c.Webserver.Enabled {
|
|
err = c.CheckWebserverConfigValues()
|
|
if err != nil {
|
|
log.Warnf(ErrCheckingConfigValues, err)
|
|
c.Webserver.Enabled = false
|
|
}
|
|
}
|
|
|
|
err = c.CheckCurrencyConfigValues()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if c.GlobalHTTPTimeout <= 0 {
|
|
log.Warnf("Global HTTP Timeout value not set, defaulting to %v.", configDefaultHTTPTimeout)
|
|
c.GlobalHTTPTimeout = configDefaultHTTPTimeout
|
|
}
|
|
|
|
return c.CheckClientBankAccounts()
|
|
}
|
|
|
|
// LoadConfig loads your configuration file into your configuration object
|
|
func (c *Config) LoadConfig(configPath string) error {
|
|
err := c.ReadConfig(configPath)
|
|
if err != nil {
|
|
return fmt.Errorf(ErrFailureOpeningConfig, configPath, err)
|
|
}
|
|
|
|
return c.CheckConfig()
|
|
}
|
|
|
|
// UpdateConfig updates the config with a supplied config file
|
|
func (c *Config) UpdateConfig(configPath string, newCfg *Config) error {
|
|
err := newCfg.CheckConfig()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
c.Name = newCfg.Name
|
|
c.EncryptConfig = newCfg.EncryptConfig
|
|
c.Currency = newCfg.Currency
|
|
c.GlobalHTTPTimeout = newCfg.GlobalHTTPTimeout
|
|
c.Portfolio = newCfg.Portfolio
|
|
c.Communications = newCfg.Communications
|
|
c.Webserver = newCfg.Webserver
|
|
c.Exchanges = newCfg.Exchanges
|
|
|
|
err = c.SaveConfig(configPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return c.LoadConfig(configPath)
|
|
}
|
|
|
|
// GetConfig returns a pointer to a configuration object
|
|
func GetConfig() *Config {
|
|
return &Cfg
|
|
}
|