mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-14 07:26:47 +00:00
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:
@@ -654,8 +654,7 @@ func TestCompareJobsToData(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunJob(t *testing.T) {
|
||||
t.Parallel()
|
||||
func TestRunJob(t *testing.T) { // nolint // TO-DO: Fix race t.Parallel() usage
|
||||
testCases := []*DataHistoryJob{
|
||||
{
|
||||
Nickname: "TestRunJobDataHistoryCandleDataType",
|
||||
|
||||
@@ -7,55 +7,86 @@ import (
|
||||
"sync"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/deposit"
|
||||
)
|
||||
|
||||
// vars related to the deposit address helpers
|
||||
var (
|
||||
ErrDepositAddressStoreIsNil = errors.New("deposit address store is nil")
|
||||
ErrDepositAddressNotFound = errors.New("deposit address does not exist")
|
||||
ErrDepositAddressStoreIsNil = errors.New("deposit address store is nil")
|
||||
ErrDepositAddressNotFound = errors.New("deposit address does not exist")
|
||||
errDepositAddressChainNotFound = errors.New("deposit address for specified chain not found")
|
||||
errNoDepositAddressesRetrieved = errors.New("no deposit addresses retrieved")
|
||||
)
|
||||
|
||||
// DepositAddressManager manages the exchange deposit address store
|
||||
type DepositAddressManager struct {
|
||||
m sync.Mutex
|
||||
store map[string]map[string]string
|
||||
m sync.RWMutex
|
||||
store map[string]map[string][]deposit.Address
|
||||
}
|
||||
|
||||
// IsSynced returns whether or not the deposit address store has synced its data
|
||||
func (m *DepositAddressManager) IsSynced() bool {
|
||||
if m.store == nil {
|
||||
return false
|
||||
}
|
||||
m.m.RLock()
|
||||
defer m.m.RUnlock()
|
||||
return len(m.store) > 0
|
||||
}
|
||||
|
||||
// SetupDepositAddressManager returns a DepositAddressManager
|
||||
func SetupDepositAddressManager() *DepositAddressManager {
|
||||
return &DepositAddressManager{
|
||||
store: make(map[string]map[string]string),
|
||||
store: make(map[string]map[string][]deposit.Address),
|
||||
}
|
||||
}
|
||||
|
||||
// GetDepositAddressByExchangeAndCurrency returns a deposit address for the specified exchange and cryptocurrency
|
||||
// if it exists
|
||||
func (m *DepositAddressManager) GetDepositAddressByExchangeAndCurrency(exchName string, currencyItem currency.Code) (string, error) {
|
||||
m.m.Lock()
|
||||
defer m.m.Unlock()
|
||||
func (m *DepositAddressManager) GetDepositAddressByExchangeAndCurrency(exchName, chain string, currencyItem currency.Code) (deposit.Address, error) {
|
||||
m.m.RLock()
|
||||
defer m.m.RUnlock()
|
||||
|
||||
if len(m.store) == 0 {
|
||||
return "", ErrDepositAddressStoreIsNil
|
||||
return deposit.Address{}, ErrDepositAddressStoreIsNil
|
||||
}
|
||||
|
||||
r, ok := m.store[strings.ToUpper(exchName)]
|
||||
if !ok {
|
||||
return "", ErrExchangeNotFound
|
||||
return deposit.Address{}, ErrExchangeNotFound
|
||||
}
|
||||
|
||||
addr, ok := r[strings.ToUpper(currencyItem.String())]
|
||||
if !ok {
|
||||
return "", ErrDepositAddressNotFound
|
||||
return deposit.Address{}, ErrDepositAddressNotFound
|
||||
}
|
||||
|
||||
return addr, nil
|
||||
if len(addr) == 0 {
|
||||
return deposit.Address{}, errNoDepositAddressesRetrieved
|
||||
}
|
||||
|
||||
if chain != "" {
|
||||
for x := range addr {
|
||||
if strings.EqualFold(addr[x].Chain, chain) {
|
||||
return addr[x], nil
|
||||
}
|
||||
}
|
||||
return deposit.Address{}, errDepositAddressChainNotFound
|
||||
}
|
||||
|
||||
for x := range addr {
|
||||
if strings.EqualFold(addr[x].Chain, currencyItem.String()) {
|
||||
return addr[x], nil
|
||||
}
|
||||
}
|
||||
return addr[0], nil
|
||||
}
|
||||
|
||||
// GetDepositAddressesByExchange returns a list of cryptocurrency addresses for the specified
|
||||
// exchange if they exist
|
||||
func (m *DepositAddressManager) GetDepositAddressesByExchange(exchName string) (map[string]string, error) {
|
||||
m.m.Lock()
|
||||
defer m.m.Unlock()
|
||||
func (m *DepositAddressManager) GetDepositAddressesByExchange(exchName string) (map[string][]deposit.Address, error) {
|
||||
m.m.RLock()
|
||||
defer m.m.RUnlock()
|
||||
|
||||
if len(m.store) == 0 {
|
||||
return nil, ErrDepositAddressStoreIsNil
|
||||
@@ -66,11 +97,15 @@ func (m *DepositAddressManager) GetDepositAddressesByExchange(exchName string) (
|
||||
return nil, ErrDepositAddressNotFound
|
||||
}
|
||||
|
||||
return r, nil
|
||||
cpy := make(map[string][]deposit.Address, len(r))
|
||||
for k, v := range r {
|
||||
cpy[k] = v
|
||||
}
|
||||
return cpy, nil
|
||||
}
|
||||
|
||||
// Sync synchronises all deposit addresses
|
||||
func (m *DepositAddressManager) Sync(addresses map[string]map[string]string) error {
|
||||
func (m *DepositAddressManager) Sync(addresses map[string]map[string][]deposit.Address) error {
|
||||
if m == nil {
|
||||
return fmt.Errorf("deposit address manager %w", ErrNilSubsystem)
|
||||
}
|
||||
@@ -81,7 +116,7 @@ func (m *DepositAddressManager) Sync(addresses map[string]map[string]string) err
|
||||
}
|
||||
|
||||
for k, v := range addresses {
|
||||
r := make(map[string]string)
|
||||
r := make(map[string][]deposit.Address)
|
||||
for w, x := range v {
|
||||
r[strings.ToUpper(w)] = x
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/deposit"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -13,7 +14,32 @@ const (
|
||||
btc = "BTC"
|
||||
)
|
||||
|
||||
func TestIsSynced(t *testing.T) {
|
||||
t.Parallel()
|
||||
var d DepositAddressManager
|
||||
if d.IsSynced() {
|
||||
t.Error("should be false")
|
||||
}
|
||||
m := SetupDepositAddressManager()
|
||||
err := m.Sync(map[string]map[string][]deposit.Address{
|
||||
bitStamp: {
|
||||
btc: []deposit.Address{
|
||||
{
|
||||
Address: address,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if !m.IsSynced() {
|
||||
t.Error("should be synced")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetupDepositAddressManager(t *testing.T) {
|
||||
t.Parallel()
|
||||
m := SetupDepositAddressManager()
|
||||
if m.store == nil {
|
||||
t.Fatal("expected store")
|
||||
@@ -21,27 +47,36 @@ func TestSetupDepositAddressManager(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSync(t *testing.T) {
|
||||
t.Parallel()
|
||||
m := SetupDepositAddressManager()
|
||||
err := m.Sync(map[string]map[string]string{
|
||||
err := m.Sync(map[string]map[string][]deposit.Address{
|
||||
bitStamp: {
|
||||
btc: address,
|
||||
btc: []deposit.Address{
|
||||
{
|
||||
Address: address,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
r, err := m.GetDepositAddressByExchangeAndCurrency(bitStamp, currency.BTC)
|
||||
r, err := m.GetDepositAddressByExchangeAndCurrency(bitStamp, "", currency.BTC)
|
||||
if err != nil {
|
||||
t.Error("unexpected result")
|
||||
}
|
||||
if r != address {
|
||||
if r.Address != address {
|
||||
t.Error("unexpected result")
|
||||
}
|
||||
|
||||
m.store = nil
|
||||
err = m.Sync(map[string]map[string]string{
|
||||
err = m.Sync(map[string]map[string][]deposit.Address{
|
||||
bitStamp: {
|
||||
btc: address,
|
||||
btc: []deposit.Address{
|
||||
{
|
||||
Address: address,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
if !errors.Is(err, ErrDepositAddressStoreIsNil) {
|
||||
@@ -49,9 +84,13 @@ func TestSync(t *testing.T) {
|
||||
}
|
||||
|
||||
m = nil
|
||||
err = m.Sync(map[string]map[string]string{
|
||||
err = m.Sync(map[string]map[string][]deposit.Address{
|
||||
bitStamp: {
|
||||
btc: address,
|
||||
btc: []deposit.Address{
|
||||
{
|
||||
Address: address,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
if !errors.Is(err, ErrNilSubsystem) {
|
||||
@@ -60,35 +99,91 @@ func TestSync(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestGetDepositAddressByExchangeAndCurrency(t *testing.T) {
|
||||
t.Parallel()
|
||||
m := SetupDepositAddressManager()
|
||||
_, err := m.GetDepositAddressByExchangeAndCurrency("", currency.BTC)
|
||||
_, err := m.GetDepositAddressByExchangeAndCurrency("", "", currency.BTC)
|
||||
if !errors.Is(err, ErrDepositAddressStoreIsNil) {
|
||||
t.Errorf("received %v, expected %v", err, ErrDepositAddressStoreIsNil)
|
||||
}
|
||||
|
||||
m.store = map[string]map[string]string{
|
||||
m.store = map[string]map[string][]deposit.Address{
|
||||
bitStamp: {
|
||||
btc: address,
|
||||
btc: []deposit.Address{
|
||||
{
|
||||
Address: address,
|
||||
},
|
||||
},
|
||||
"USDT": []deposit.Address{
|
||||
{
|
||||
Address: "ABsdZ",
|
||||
Chain: "SOL",
|
||||
},
|
||||
{
|
||||
Address: "0x1b",
|
||||
Chain: "ERC20",
|
||||
},
|
||||
{
|
||||
Address: "1asdad",
|
||||
Chain: "USDT",
|
||||
},
|
||||
},
|
||||
"BNB": nil,
|
||||
},
|
||||
}
|
||||
_, err = m.GetDepositAddressByExchangeAndCurrency(bitStamp, currency.BTC)
|
||||
_, err = m.GetDepositAddressByExchangeAndCurrency("asdf", "", currency.BTC)
|
||||
if !errors.Is(err, ErrExchangeNotFound) {
|
||||
t.Errorf("received %v, expected %v", err, ErrExchangeNotFound)
|
||||
}
|
||||
_, err = m.GetDepositAddressByExchangeAndCurrency(bitStamp, "", currency.LTC)
|
||||
if !errors.Is(err, ErrDepositAddressNotFound) {
|
||||
t.Errorf("received %v, expected %v", err, ErrDepositAddressNotFound)
|
||||
}
|
||||
_, err = m.GetDepositAddressByExchangeAndCurrency(bitStamp, "", currency.BNB)
|
||||
if !errors.Is(err, errNoDepositAddressesRetrieved) {
|
||||
t.Errorf("received %v, expected %v", err, errNoDepositAddressesRetrieved)
|
||||
}
|
||||
_, err = m.GetDepositAddressByExchangeAndCurrency(bitStamp, "NON-EXISTENT-CHAIN", currency.USDT)
|
||||
if !errors.Is(err, errDepositAddressChainNotFound) {
|
||||
t.Errorf("received %v, expected %v", err, errDepositAddressChainNotFound)
|
||||
}
|
||||
|
||||
if r, _ := m.GetDepositAddressByExchangeAndCurrency(bitStamp, "ErC20", currency.USDT); r.Address != "0x1b" && r.Chain != "ERC20" {
|
||||
t.Error("unexpected values")
|
||||
}
|
||||
if r, _ := m.GetDepositAddressByExchangeAndCurrency(bitStamp, "sOl", currency.USDT); r.Address != "ABsdZ" && r.Chain != "SOL" {
|
||||
t.Error("unexpected values")
|
||||
}
|
||||
if r, _ := m.GetDepositAddressByExchangeAndCurrency(bitStamp, "", currency.USDT); r.Address != "1asdad" && r.Chain != "USDT" {
|
||||
t.Error("unexpected values")
|
||||
}
|
||||
_, err = m.GetDepositAddressByExchangeAndCurrency(bitStamp, "", currency.BTC)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Errorf("received %v, expected %v", err, nil)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetDepositAddressesByExchange(t *testing.T) {
|
||||
t.Parallel()
|
||||
m := SetupDepositAddressManager()
|
||||
_, err := m.GetDepositAddressesByExchange("")
|
||||
if !errors.Is(err, ErrDepositAddressStoreIsNil) {
|
||||
t.Errorf("received %v, expected %v", err, ErrDepositAddressStoreIsNil)
|
||||
}
|
||||
|
||||
m.store = map[string]map[string]string{
|
||||
m.store = map[string]map[string][]deposit.Address{
|
||||
bitStamp: {
|
||||
btc: address,
|
||||
btc: []deposit.Address{
|
||||
{
|
||||
Address: address,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
_, err = m.GetDepositAddressesByExchange("non-existent")
|
||||
if !errors.Is(err, ErrDepositAddressNotFound) {
|
||||
t.Errorf("received %v, expected %v", err, ErrDepositAddressNotFound)
|
||||
}
|
||||
|
||||
_, err = m.GetDepositAddressesByExchange(bitStamp)
|
||||
if !errors.Is(err, nil) {
|
||||
t.Errorf("received %v, expected %v", err, nil)
|
||||
|
||||
@@ -494,7 +494,7 @@ func (bot *Engine) Start() error {
|
||||
if bot.Settings.EnableDepositAddressManager {
|
||||
bot.DepositAddressManager = SetupDepositAddressManager()
|
||||
go func() {
|
||||
err = bot.DepositAddressManager.Sync(bot.GetExchangeCryptocurrencyDepositAddresses())
|
||||
err = bot.DepositAddressManager.Sync(bot.GetAllExchangeCryptocurrencyDepositAddresses())
|
||||
if err != nil {
|
||||
gctlog.Errorf(gctlog.Global, "Deposit address manager unable to setup: %s", err)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -24,9 +24,12 @@ import (
|
||||
"github.com/thrasher-corp/gocryptotrader/currency"
|
||||
"github.com/thrasher-corp/gocryptotrader/database"
|
||||
"github.com/thrasher-corp/gocryptotrader/dispatch"
|
||||
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/protocol"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/stats"
|
||||
"github.com/thrasher-corp/gocryptotrader/exchanges/ticker"
|
||||
"github.com/thrasher-corp/gocryptotrader/gctscript/vm"
|
||||
@@ -115,8 +118,7 @@ func TestGetRPCEndpoints(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetSubsystem(t *testing.T) {
|
||||
t.Parallel()
|
||||
func TestSetSubsystem(t *testing.T) { // nolint // TO-DO: Fix race t.Parallel() usage
|
||||
testCases := []struct {
|
||||
Subsystem string
|
||||
Engine *Engine
|
||||
@@ -983,13 +985,191 @@ func TestGetExchangeLowestPriceByCurrencyPair(t *testing.T) {
|
||||
func TestGetCryptocurrenciesByExchange(t *testing.T) {
|
||||
t.Parallel()
|
||||
e := CreateTestBot(t)
|
||||
|
||||
_, err := e.GetCryptocurrenciesByExchange("Bitfinex", false, false, asset.Spot)
|
||||
if err != nil {
|
||||
t.Fatalf("Err %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
type fakeDepositExchangeOpts struct {
|
||||
SupportsAuth bool
|
||||
SupportsMultiChain bool
|
||||
RequiresChainSet bool
|
||||
ReturnMultipleChains bool
|
||||
ThrowPairError bool
|
||||
ThrowTransferChainError bool
|
||||
ThrowDepositAddressError bool
|
||||
}
|
||||
|
||||
type fakeDepositExchange struct {
|
||||
exchange.IBotExchange
|
||||
*fakeDepositExchangeOpts
|
||||
}
|
||||
|
||||
func (f fakeDepositExchange) GetName() string {
|
||||
return "fake"
|
||||
}
|
||||
|
||||
func (f fakeDepositExchange) GetAuthenticatedAPISupport(endpoint uint8) bool {
|
||||
return f.SupportsAuth
|
||||
}
|
||||
|
||||
func (f fakeDepositExchange) GetBase() *exchange.Base {
|
||||
return &exchange.Base{
|
||||
Features: exchange.Features{Supports: exchange.FeaturesSupported{
|
||||
RESTCapabilities: protocol.Features{
|
||||
MultiChainDeposits: f.SupportsMultiChain,
|
||||
MultiChainDepositRequiresChainSet: f.RequiresChainSet,
|
||||
},
|
||||
}},
|
||||
}
|
||||
}
|
||||
|
||||
func (f fakeDepositExchange) GetAvailableTransferChains(_ context.Context, c currency.Code) ([]string, error) {
|
||||
if f.ThrowTransferChainError {
|
||||
return nil, errors.New("unable to get available transfer chains")
|
||||
}
|
||||
if c.Match(currency.XRP) {
|
||||
return nil, nil
|
||||
}
|
||||
if c.Match(currency.USDT) {
|
||||
return []string{"sol", "btc", "usdt"}, nil
|
||||
}
|
||||
return []string{"BITCOIN"}, nil
|
||||
}
|
||||
|
||||
func (f fakeDepositExchange) GetDepositAddress(_ context.Context, c currency.Code, chain, accountID string) (*deposit.Address, error) {
|
||||
if f.ThrowDepositAddressError {
|
||||
return nil, errors.New("unable to get deposit address")
|
||||
}
|
||||
return &deposit.Address{Address: "fakeaddr"}, nil
|
||||
}
|
||||
|
||||
func createDepositEngine(opts *fakeDepositExchangeOpts) *Engine {
|
||||
ps := currency.PairStore{
|
||||
AssetEnabled: convert.BoolPtr(true),
|
||||
Enabled: currency.Pairs{
|
||||
currency.NewPair(currency.BTC, currency.USDT),
|
||||
currency.NewPair(currency.XRP, currency.USDT),
|
||||
},
|
||||
Available: currency.Pairs{
|
||||
currency.NewPair(currency.BTC, currency.USDT),
|
||||
currency.NewPair(currency.XRP, currency.USDT),
|
||||
},
|
||||
}
|
||||
if opts.ThrowPairError {
|
||||
ps.Available = nil
|
||||
}
|
||||
return &Engine{
|
||||
Settings: Settings{Verbose: true},
|
||||
Config: &config.Config{
|
||||
Exchanges: []config.ExchangeConfig{
|
||||
{
|
||||
Name: "fake",
|
||||
Enabled: true,
|
||||
CurrencyPairs: ¤cy.PairsManager{
|
||||
UseGlobalFormat: true,
|
||||
ConfigFormat: ¤cy.PairFormat{},
|
||||
Pairs: map[asset.Item]*currency.PairStore{
|
||||
asset.Spot: &ps,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
ExchangeManager: &ExchangeManager{
|
||||
exchanges: map[string]exchange.IBotExchange{
|
||||
"fake": fakeDepositExchange{
|
||||
fakeDepositExchangeOpts: opts,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetCryptocurrencyDepositAddressesByExchange(t *testing.T) {
|
||||
t.Parallel()
|
||||
const exchName = "fake"
|
||||
e := createDepositEngine(&fakeDepositExchangeOpts{SupportsAuth: true, SupportsMultiChain: true})
|
||||
_, err := e.GetCryptocurrencyDepositAddressesByExchange(exchName)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if _, err = e.GetCryptocurrencyDepositAddressesByExchange("non-existent"); !errors.Is(err, ErrExchangeNotFound) {
|
||||
t.Errorf("received %s, expected: %s", err, ErrExchangeNotFound)
|
||||
}
|
||||
e.DepositAddressManager = SetupDepositAddressManager()
|
||||
_, err = e.GetCryptocurrencyDepositAddressesByExchange(exchName)
|
||||
if err == nil {
|
||||
t.Error("expected error")
|
||||
}
|
||||
if err = e.DepositAddressManager.Sync(e.GetAllExchangeCryptocurrencyDepositAddresses()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = e.GetCryptocurrencyDepositAddressesByExchange(exchName)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetExchangeCryptocurrencyDepositAddress(t *testing.T) {
|
||||
t.Parallel()
|
||||
e := createDepositEngine(&fakeDepositExchangeOpts{SupportsAuth: true, SupportsMultiChain: true})
|
||||
const exchName = "fake"
|
||||
if _, err := e.GetExchangeCryptocurrencyDepositAddress(context.Background(), "non-existent", "", "", currency.BTC, false); !errors.Is(err, ErrExchangeNotFound) {
|
||||
t.Errorf("received %s, expected: %s", err, ErrExchangeNotFound)
|
||||
}
|
||||
r, err := e.GetExchangeCryptocurrencyDepositAddress(context.Background(), exchName, "", "", currency.BTC, false)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if r.Address != "fakeaddr" {
|
||||
t.Error("unexpected address")
|
||||
}
|
||||
e.DepositAddressManager = SetupDepositAddressManager()
|
||||
if err := e.DepositAddressManager.Sync(e.GetAllExchangeCryptocurrencyDepositAddresses()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := e.GetExchangeCryptocurrencyDepositAddress(context.Background(), "meow", "", "", currency.BTC, false); !errors.Is(err, ErrExchangeNotFound) {
|
||||
t.Errorf("received %s, expected: %s", err, ErrExchangeNotFound)
|
||||
}
|
||||
if _, err := e.GetExchangeCryptocurrencyDepositAddress(context.Background(), exchName, "", "", currency.BTC, false); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetAllExchangeCryptocurrencyDepositAddresses(t *testing.T) {
|
||||
t.Parallel()
|
||||
e := createDepositEngine(&fakeDepositExchangeOpts{})
|
||||
if r := e.GetAllExchangeCryptocurrencyDepositAddresses(); len(r) > 0 {
|
||||
t.Error("should have no addresses returned for an unauthenticated exchange")
|
||||
}
|
||||
e = createDepositEngine(&fakeDepositExchangeOpts{SupportsAuth: true, ThrowPairError: true})
|
||||
if r := e.GetAllExchangeCryptocurrencyDepositAddresses(); len(r) > 0 {
|
||||
t.Error("should have no cryptos returned for no enabled pairs")
|
||||
}
|
||||
e = createDepositEngine(&fakeDepositExchangeOpts{SupportsAuth: true, SupportsMultiChain: true, ThrowTransferChainError: true})
|
||||
if r := e.GetAllExchangeCryptocurrencyDepositAddresses(); len(r["fake"]) != 0 {
|
||||
t.Error("should have returned no deposit addresses for a fake exchange with transfer error")
|
||||
}
|
||||
e = createDepositEngine(&fakeDepositExchangeOpts{SupportsAuth: true, SupportsMultiChain: true, ThrowDepositAddressError: true})
|
||||
if r := e.GetAllExchangeCryptocurrencyDepositAddresses(); len(r["fake"]["btc"]) != 0 {
|
||||
t.Error("should have returned no deposit addresses for fake exchange with deposit error, with multichain support enabled")
|
||||
}
|
||||
e = createDepositEngine(&fakeDepositExchangeOpts{SupportsAuth: true, SupportsMultiChain: true, RequiresChainSet: true})
|
||||
if r := e.GetAllExchangeCryptocurrencyDepositAddresses(); len(r["fake"]["btc"]) == 0 {
|
||||
t.Error("should of returned a BTC address")
|
||||
}
|
||||
e = createDepositEngine(&fakeDepositExchangeOpts{SupportsAuth: true, SupportsMultiChain: true})
|
||||
if r := e.GetAllExchangeCryptocurrencyDepositAddresses(); len(r["fake"]["btc"]) == 0 {
|
||||
t.Error("should of returned a BTC address")
|
||||
}
|
||||
e = createDepositEngine(&fakeDepositExchangeOpts{SupportsAuth: true})
|
||||
if r := e.GetAllExchangeCryptocurrencyDepositAddresses(); len(r["fake"]["xrp"]) == 0 {
|
||||
t.Error("should have returned a XRP address")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetExchangeNames(t *testing.T) {
|
||||
t.Parallel()
|
||||
bot := CreateTestBot(t)
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
"github.com/gofrs/uuid"
|
||||
grpcauth "github.com/grpc-ecosystem/go-grpc-middleware/auth"
|
||||
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
|
||||
"github.com/pquerna/otp/totp"
|
||||
"github.com/thrasher-corp/gocryptotrader/common"
|
||||
"github.com/thrasher-corp/gocryptotrader/common/crypto"
|
||||
"github.com/thrasher-corp/gocryptotrader/common/file"
|
||||
@@ -62,6 +63,7 @@ var (
|
||||
errAssetTypeUnset = errors.New("asset type unset")
|
||||
errDispatchSystem = errors.New("dispatch system offline")
|
||||
errCurrencyNotEnabled = errors.New("currency not enabled")
|
||||
errCurrencyNotSpecified = errors.New("a currency must be specified")
|
||||
errCurrencyPairInvalid = errors.New("currency provided is not found in the available pairs list")
|
||||
errNoTrades = errors.New("no trades returned from supplied params")
|
||||
errNilRequestData = errors.New("nil request data received, cannot continue")
|
||||
@@ -1439,6 +1441,7 @@ func (s *RPCServer) CancelAllOrders(ctx context.Context, r *gctrpc.CancelAllOrde
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ModifyOrder modifies an existing order if it exists
|
||||
func (s *RPCServer) ModifyOrder(ctx context.Context, r *gctrpc.ModifyOrderRequest) (*gctrpc.ModifyOrderResponse, error) {
|
||||
assetType, err := asset.New(r.Asset)
|
||||
if err != nil {
|
||||
@@ -1531,28 +1534,90 @@ func (s *RPCServer) RemoveEvent(ctx context.Context, r *gctrpc.RemoveEventReques
|
||||
// GetCryptocurrencyDepositAddresses returns a list of cryptocurrency deposit
|
||||
// addresses specified by an exchange
|
||||
func (s *RPCServer) GetCryptocurrencyDepositAddresses(ctx context.Context, r *gctrpc.GetCryptocurrencyDepositAddressesRequest) (*gctrpc.GetCryptocurrencyDepositAddressesResponse, error) {
|
||||
_, err := s.GetExchangeByName(r.Exchange)
|
||||
exch, err := s.GetExchangeByName(r.Exchange)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !exch.GetAuthenticatedAPISupport(exchange.RestAuthentication) {
|
||||
return nil, exchange.ErrAuthenticatedRequestWithoutCredentialsSet
|
||||
}
|
||||
|
||||
result, err := s.GetCryptocurrencyDepositAddressesByExchange(r.Exchange)
|
||||
return &gctrpc.GetCryptocurrencyDepositAddressesResponse{Addresses: result}, err
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var resp gctrpc.GetCryptocurrencyDepositAddressesResponse
|
||||
resp.Addresses = make(map[string]*gctrpc.DepositAddresses)
|
||||
for k, v := range result {
|
||||
var depositAddrs []*gctrpc.DepositAddress
|
||||
for a := range v {
|
||||
depositAddrs = append(depositAddrs, &gctrpc.DepositAddress{
|
||||
Address: v[a].Address,
|
||||
Tag: v[a].Tag,
|
||||
Chain: v[a].Chain,
|
||||
})
|
||||
}
|
||||
resp.Addresses[k] = &gctrpc.DepositAddresses{Addresses: depositAddrs}
|
||||
}
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
// GetCryptocurrencyDepositAddress returns a cryptocurrency deposit address
|
||||
// specified by exchange and cryptocurrency
|
||||
func (s *RPCServer) GetCryptocurrencyDepositAddress(ctx context.Context, r *gctrpc.GetCryptocurrencyDepositAddressRequest) (*gctrpc.GetCryptocurrencyDepositAddressResponse, error) {
|
||||
_, err := s.GetExchangeByName(r.Exchange)
|
||||
exch, err := s.GetExchangeByName(r.Exchange)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !exch.GetAuthenticatedAPISupport(exchange.RestAuthentication) {
|
||||
return nil, exchange.ErrAuthenticatedRequestWithoutCredentialsSet
|
||||
}
|
||||
|
||||
addr, err := s.GetExchangeCryptocurrencyDepositAddress(ctx,
|
||||
r.Exchange,
|
||||
"",
|
||||
currency.NewCode(r.Cryptocurrency))
|
||||
return &gctrpc.GetCryptocurrencyDepositAddressResponse{Address: addr}, err
|
||||
r.Chain,
|
||||
currency.NewCode(r.Cryptocurrency),
|
||||
r.Bypass,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &gctrpc.GetCryptocurrencyDepositAddressResponse{
|
||||
Address: addr.Address,
|
||||
Tag: addr.Tag,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetAvailableTransferChains returns the supported transfer chains specified by
|
||||
// exchange and cryptocurrency
|
||||
func (s *RPCServer) GetAvailableTransferChains(ctx context.Context, r *gctrpc.GetAvailableTransferChainsRequest) (*gctrpc.GetAvailableTransferChainsResponse, error) {
|
||||
exch, err := s.GetExchangeByName(r.Exchange)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
curr := currency.NewCode(r.Cryptocurrency)
|
||||
if curr.IsEmpty() {
|
||||
return nil, errCurrencyNotSpecified
|
||||
}
|
||||
|
||||
resp, err := exch.GetAvailableTransferChains(ctx, curr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(resp) == 0 {
|
||||
return nil, errors.New("no available transfer chains found")
|
||||
}
|
||||
|
||||
return &gctrpc.GetAvailableTransferChainsResponse{
|
||||
Chains: resp,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// WithdrawCryptocurrencyFunds withdraws cryptocurrency funds specified by
|
||||
@@ -1573,9 +1638,38 @@ func (s *RPCServer) WithdrawCryptocurrencyFunds(ctx context.Context, r *gctrpc.W
|
||||
Address: r.Address,
|
||||
AddressTag: r.AddressTag,
|
||||
FeeAmount: r.Fee,
|
||||
Chain: r.Chain,
|
||||
},
|
||||
}
|
||||
|
||||
exchCfg, err := s.Config.GetExchangeConfig(r.Exchange)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if exchCfg.API.Credentials.OTPSecret != "" {
|
||||
code, errOTP := totp.GenerateCode(exchCfg.API.Credentials.OTPSecret, time.Now())
|
||||
if errOTP != nil {
|
||||
return nil, errOTP
|
||||
}
|
||||
|
||||
codeNum, errOTP := strconv.ParseInt(code, 10, 64)
|
||||
if errOTP != nil {
|
||||
return nil, errOTP
|
||||
}
|
||||
request.OneTimePassword = codeNum
|
||||
}
|
||||
|
||||
if exchCfg.API.Credentials.PIN != "" {
|
||||
pinCode, errPin := strconv.ParseInt(exchCfg.API.Credentials.PIN, 10, 64)
|
||||
if err != nil {
|
||||
return nil, errPin
|
||||
}
|
||||
request.PIN = pinCode
|
||||
}
|
||||
|
||||
request.TradePassword = exchCfg.API.Credentials.TradePassword
|
||||
|
||||
resp, err := s.Engine.WithdrawManager.SubmitWithdrawal(ctx, request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -1618,6 +1712,34 @@ func (s *RPCServer) WithdrawFiatFunds(ctx context.Context, r *gctrpc.WithdrawFia
|
||||
},
|
||||
}
|
||||
|
||||
exchCfg, err := s.Config.GetExchangeConfig(r.Exchange)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if exchCfg.API.Credentials.OTPSecret != "" {
|
||||
code, errOTP := totp.GenerateCode(exchCfg.API.Credentials.OTPSecret, time.Now())
|
||||
if err != nil {
|
||||
return nil, errOTP
|
||||
}
|
||||
|
||||
codeNum, errOTP := strconv.ParseInt(code, 10, 64)
|
||||
if err != nil {
|
||||
return nil, errOTP
|
||||
}
|
||||
request.OneTimePassword = codeNum
|
||||
}
|
||||
|
||||
if exchCfg.API.Credentials.PIN != "" {
|
||||
pinCode, errPIN := strconv.ParseInt(exchCfg.API.Credentials.PIN, 10, 64)
|
||||
if err != nil {
|
||||
return nil, errPIN
|
||||
}
|
||||
request.PIN = pinCode
|
||||
}
|
||||
|
||||
request.TradePassword = exchCfg.API.Credentials.TradePassword
|
||||
|
||||
resp, err := s.Engine.WithdrawManager.SubmitWithdrawal(ctx, request)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -86,10 +86,10 @@ func (m *WithdrawManager) SubmitWithdrawal(ctx context.Context, req *withdraw.Re
|
||||
}
|
||||
}
|
||||
}
|
||||
dbwithdraw.Event(resp)
|
||||
if err == nil {
|
||||
withdraw.Cache.Add(resp.ID, resp)
|
||||
}
|
||||
dbwithdraw.Event(resp)
|
||||
return resp, err
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user