exchanges/engine: Add multichain deposit/withdrawal support (#794)

* Add exchange multichain support

* Start tidying up

* Add multichain transfer support for Bitfinex and fix poloniex bug

* Add Coinbene multichain support

* Start adjusting the deposit address manager

* Fix deposit tests and further enhancements

* Cleanup

* Add bypass flag, expand tests plus error coverage for Huobi

Adjust helpers

* Address nitterinos

* BFX wd changes

* Address nitterinos

* Minor fixes rebasing on master

* Fix BFX acceptableMethods test

* Add some TO-DOs for 2 tests WRT races

* Fix acceptableMethods test round 2

* Address nitterinos
This commit is contained in:
Adrian Gallagher
2021-10-15 15:55:38 +11:00
committed by GitHub
parent b093a7df19
commit 0c00b7e1df
145 changed files with 46329 additions and 5507 deletions

View File

@@ -16,6 +16,7 @@ import (
"os"
"path/filepath"
"strings"
"sync"
"time"
"github.com/pquerna/otp/totp"
@@ -27,6 +28,7 @@ import (
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
"github.com/thrasher-corp/gocryptotrader/exchanges/account"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"github.com/thrasher-corp/gocryptotrader/exchanges/deposit"
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
"github.com/thrasher-corp/gocryptotrader/exchanges/stats"
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
@@ -668,12 +670,15 @@ func (bot *Engine) GetCryptocurrenciesByExchange(exchangeName string, enabledExc
}
// GetCryptocurrencyDepositAddressesByExchange returns the cryptocurrency deposit addresses for a particular exchange
func (bot *Engine) GetCryptocurrencyDepositAddressesByExchange(exchName string) (map[string]string, error) {
func (bot *Engine) GetCryptocurrencyDepositAddressesByExchange(exchName string) (map[string][]deposit.Address, error) {
if bot.DepositAddressManager != nil {
return bot.DepositAddressManager.GetDepositAddressesByExchange(exchName)
if bot.DepositAddressManager.IsSynced() {
return bot.DepositAddressManager.GetDepositAddressesByExchange(exchName)
}
return nil, errors.New("deposit address manager has not yet synced all exchange deposit addresses")
}
result := bot.GetExchangeCryptocurrencyDepositAddresses()
result := bot.GetAllExchangeCryptocurrencyDepositAddresses()
r, ok := result[exchName]
if !ok {
return nil, ErrExchangeNotFound
@@ -683,50 +688,112 @@ func (bot *Engine) GetCryptocurrencyDepositAddressesByExchange(exchName string)
// GetExchangeCryptocurrencyDepositAddress returns the cryptocurrency deposit address for a particular
// exchange
func (bot *Engine) GetExchangeCryptocurrencyDepositAddress(ctx context.Context, exchName, accountID string, item currency.Code) (string, error) {
if bot.DepositAddressManager != nil {
return bot.DepositAddressManager.GetDepositAddressByExchangeAndCurrency(exchName, item)
func (bot *Engine) GetExchangeCryptocurrencyDepositAddress(ctx context.Context, exchName, accountID, chain string, item currency.Code, bypassCache bool) (*deposit.Address, error) {
if bot.DepositAddressManager != nil && bot.DepositAddressManager.IsSynced() && !bypassCache {
resp, err := bot.DepositAddressManager.GetDepositAddressByExchangeAndCurrency(exchName, chain, item)
if err != nil {
return nil, err
}
return &resp, nil
}
exch, err := bot.GetExchangeByName(exchName)
if err != nil {
return "", err
return nil, err
}
return exch.GetDepositAddress(ctx, item, accountID)
return exch.GetDepositAddress(ctx, item, accountID, chain)
}
// GetExchangeCryptocurrencyDepositAddresses obtains an exchanges deposit cryptocurrency list
func (bot *Engine) GetExchangeCryptocurrencyDepositAddresses() map[string]map[string]string {
result := make(map[string]map[string]string)
// GetAllExchangeCryptocurrencyDepositAddresses obtains an exchanges deposit cryptocurrency list
func (bot *Engine) GetAllExchangeCryptocurrencyDepositAddresses() map[string]map[string][]deposit.Address {
result := make(map[string]map[string][]deposit.Address)
exchanges := bot.GetExchanges()
var depositSyncer sync.WaitGroup
depositSyncer.Add(len(exchanges))
var m sync.Mutex
for x := range exchanges {
exchName := exchanges[x].GetName()
if !exchanges[x].GetAuthenticatedAPISupport(exchange.RestAuthentication) {
if bot.Settings.Verbose {
log.Debugf(log.ExchangeSys, "GetExchangeCryptocurrencyDepositAddresses: Skippping %s due to disabled authenticated API support.\n", exchName)
go func(x int) {
defer depositSyncer.Done()
exchName := exchanges[x].GetName()
if !exchanges[x].GetAuthenticatedAPISupport(exchange.RestAuthentication) {
if bot.Settings.Verbose {
log.Debugf(log.ExchangeSys, "GetAllExchangeCryptocurrencyDepositAddresses: Skippping %s due to disabled authenticated API support.\n", exchName)
}
return
}
continue
}
cryptoCurrencies, err := bot.GetCryptocurrenciesByExchange(exchName, true, true, asset.Spot)
if err != nil {
log.Debugf(log.ExchangeSys, "%s failed to get cryptocurrency deposit addresses. Err: %s\n", exchName, err)
continue
}
cryptoAddr := make(map[string]string)
for y := range cryptoCurrencies {
cryptocurrency := cryptoCurrencies[y]
depositAddr, err := exchanges[x].GetDepositAddress(context.TODO(),
currency.NewCode(cryptocurrency),
"")
cryptoCurrencies, err := bot.GetCryptocurrenciesByExchange(exchName, true, true, asset.Spot)
if err != nil {
log.Errorf(log.Global, "%s failed to get cryptocurrency deposit addresses. Err: %s\n", exchName, err)
continue
log.Errorf(log.ExchangeSys, "%s failed to get cryptocurrency deposit addresses. Err: %s\n", exchName, err)
return
}
cryptoAddr[cryptocurrency] = depositAddr
}
result[exchName] = cryptoAddr
supportsMultiChain := exchanges[x].GetBase().Features.Supports.RESTCapabilities.MultiChainDeposits
requiresChainSet := exchanges[x].GetBase().Features.Supports.RESTCapabilities.MultiChainDepositRequiresChainSet
cryptoAddr := make(map[string][]deposit.Address)
for y := range cryptoCurrencies {
cryptocurrency := cryptoCurrencies[y]
isSingular := false
var depositAddrs []deposit.Address
if supportsMultiChain {
availChains, err := exchanges[x].GetAvailableTransferChains(context.TODO(), currency.NewCode(cryptocurrency))
if err != nil {
log.Errorf(log.Global, "%s failed to get cryptocurrency available transfer chains. Err: %s\n", exchName, err)
continue
}
if len(availChains) > 0 {
// store the default non-chain specified address for a specified crypto
chainContainsItself := common.StringDataCompareInsensitive(availChains, cryptocurrency)
if !chainContainsItself && !requiresChainSet {
depositAddr, err := exchanges[x].GetDepositAddress(context.TODO(), currency.NewCode(cryptocurrency), "", "")
if err != nil {
log.Errorf(log.Global, "%s failed to get cryptocurrency deposit address for %s. Err: %s\n",
exchName,
cryptocurrency,
err)
continue
}
depositAddr.Chain = cryptocurrency
depositAddrs = append(depositAddrs, *depositAddr)
}
for z := range availChains {
depositAddr, err := exchanges[x].GetDepositAddress(context.TODO(), currency.NewCode(cryptocurrency), "", availChains[z])
if err != nil {
log.Errorf(log.Global, "%s failed to get cryptocurrency deposit address for %s [chain %s]. Err: %s\n",
exchName,
cryptocurrency,
availChains[z],
err)
continue
}
depositAddr.Chain = availChains[z]
depositAddrs = append(depositAddrs, *depositAddr)
}
} else {
// cryptocurrency doesn't support multichain transfers
isSingular = true
}
}
if !supportsMultiChain || isSingular {
depositAddr, err := exchanges[x].GetDepositAddress(context.TODO(), currency.NewCode(cryptocurrency), "", "")
if err != nil {
log.Errorf(log.Global, "%s failed to get cryptocurrency deposit address for %s. Err: %s\n",
exchName,
cryptocurrency,
err)
continue
}
depositAddrs = append(depositAddrs, *depositAddr)
}
cryptoAddr[cryptocurrency] = depositAddrs
}
m.Lock()
result[exchName] = cryptoAddr
m.Unlock()
}(x)
}
depositSyncer.Wait()
if len(result) > 0 {
log.Infoln(log.Global, "Deposit addresses synced")
}
return result
}