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

@@ -1071,8 +1071,13 @@ func (k *Kraken) SendAuthenticatedHTTPRequest(ctx context.Context, ep exchange.U
}
var errCap SpotAuthError
if err = json.Unmarshal(interim, &errCap); err == nil {
if len(errCap.Error) != 0 {
return errors.New(errCap.Error[0])
if errCap.Error != nil {
switch e := errCap.Error.(type) {
case []string:
return errors.New(e[0])
case string:
return errors.New(e)
}
}
}
return json.Unmarshal(interim, result)
@@ -1148,7 +1153,7 @@ func calculateTradingFee(currency string, feePair map[string]TradeVolumeFee, pur
}
// GetCryptoDepositAddress returns a deposit address for a cryptocurrency
func (k *Kraken) GetCryptoDepositAddress(ctx context.Context, method, code string) (string, error) {
func (k *Kraken) GetCryptoDepositAddress(ctx context.Context, method, code string, createNew bool) ([]DepositAddress, error) {
var resp = struct {
Error []string `json:"error"`
Result []DepositAddress `json:"result"`
@@ -1158,16 +1163,19 @@ func (k *Kraken) GetCryptoDepositAddress(ctx context.Context, method, code strin
values.Set("asset", code)
values.Set("method", method)
if createNew {
values.Set("new", "1")
}
err := k.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, krakenDepositAddresses, values, &resp)
if err != nil {
return "", err
return nil, err
}
for _, a := range resp.Result {
return a.Address, nil
if len(resp.Result) == 0 {
return nil, errors.New("no addresses returned")
}
return "", errors.New("no addresses returned")
return resp.Result, nil
}
// WithdrawStatus gets the status of recent withdrawals

View File

@@ -560,6 +560,18 @@ func TestGetBalance(t *testing.T) {
}
}
// TestGetTradeBalance API endpoint test
func TestGetDepositMethods(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() {
t.Skip("no api keys set")
}
_, err := k.GetDepositMethods(context.Background(), "USDT")
if err != nil {
t.Error(err)
}
}
// TestGetTradeBalance API endpoint test
func TestGetTradeBalance(t *testing.T) {
t.Parallel()
@@ -1082,16 +1094,34 @@ func TestWithdrawInternationalBank(t *testing.T) {
}
}
func TestGetCryptoDepositAddress(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() {
t.Skip("API keys not set")
}
_, err := k.GetCryptoDepositAddress(context.Background(), "Bitcoin", "XBT", false)
if err != nil {
t.Error(err)
}
if !canManipulateRealOrders {
t.Skip("canManipulateRealOrders not set, skipping test")
}
_, err = k.GetCryptoDepositAddress(context.Background(), "Bitcoin", "XBT", true)
if err != nil {
t.Error(err)
}
}
// TestGetDepositAddress wrapper test
func TestGetDepositAddress(t *testing.T) {
t.Parallel()
if areTestAPIKeysSet() {
_, err := k.GetDepositAddress(context.Background(), currency.BTC, "")
_, err := k.GetDepositAddress(context.Background(), currency.USDT, "", "")
if err != nil {
t.Error("GetDepositAddress() error", err)
}
} else {
_, err := k.GetDepositAddress(context.Background(), currency.BTC, "")
_, err := k.GetDepositAddress(context.Background(), currency.BTC, "", "")
if err == nil {
t.Error("GetDepositAddress() error can not be nil")
}

View File

@@ -90,7 +90,7 @@ type AuthErrorData struct {
// SpotAuthError stores authenticated error messages
type SpotAuthError struct {
Error []string `json:"error"`
Error interface{} `json:"error"` // can be a []string or string
}
// Asset holds asset information
@@ -456,9 +456,10 @@ var WithdrawalFees = map[currency.Code]float64{
// DepositAddress defines a deposit address
type DepositAddress struct {
Address string `json:"address"`
ExpireTime int64 `json:"expiretm,string"`
New bool `json:"new"`
Address string `json:"address"`
ExpireTime interface{} `json:"expiretm"` // this is an int when new is specified
Tag string `json:"tag"`
New bool `json:"new"`
}
// WithdrawStatusResponse defines a withdrawal status response

View File

@@ -17,6 +17,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/kline"
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
"github.com/thrasher-corp/gocryptotrader/exchanges/orderbook"
@@ -104,27 +105,29 @@ func (k *Kraken) SetDefaults() {
REST: true,
Websocket: true,
RESTCapabilities: protocol.Features{
TickerBatching: true,
TickerFetching: true,
KlineFetching: true,
TradeFetching: true,
OrderbookFetching: true,
AutoPairUpdates: true,
AccountInfo: true,
GetOrder: true,
GetOrders: true,
CancelOrder: true,
SubmitOrder: true,
UserTradeHistory: true,
CryptoDeposit: true,
CryptoWithdrawal: true,
FiatDeposit: true,
FiatWithdraw: true,
TradeFee: true,
FiatDepositFee: true,
FiatWithdrawalFee: true,
CryptoDepositFee: true,
CryptoWithdrawalFee: true,
TickerBatching: true,
TickerFetching: true,
KlineFetching: true,
TradeFetching: true,
OrderbookFetching: true,
AutoPairUpdates: true,
AccountInfo: true,
GetOrder: true,
GetOrders: true,
CancelOrder: true,
SubmitOrder: true,
UserTradeHistory: true,
CryptoDeposit: true,
CryptoWithdrawal: true,
FiatDeposit: true,
FiatWithdraw: true,
TradeFee: true,
FiatDepositFee: true,
FiatWithdrawalFee: true,
CryptoDepositFee: true,
CryptoWithdrawalFee: true,
MultiChainDeposits: true,
MultiChainWithdrawals: true,
},
WebsocketCapabilities: protocol.Features{
TickerFetching: true,
@@ -975,19 +978,33 @@ func (k *Kraken) GetOrderInfo(ctx context.Context, orderID string, pair currency
}
// GetDepositAddress returns a deposit address for a specified currency
func (k *Kraken) GetDepositAddress(ctx context.Context, cryptocurrency currency.Code, _ string) (string, error) {
methods, err := k.GetDepositMethods(ctx, cryptocurrency.String())
func (k *Kraken) GetDepositAddress(ctx context.Context, cryptocurrency currency.Code, _, chain string) (*deposit.Address, error) {
if chain == "" {
methods, err := k.GetDepositMethods(ctx, cryptocurrency.String())
if err != nil {
return nil, err
}
if len(methods) == 0 {
return nil, errors.New("unable to get any deposit methods")
}
chain = methods[0].Method
}
depositAddr, err := k.GetCryptoDepositAddress(ctx, chain, cryptocurrency.String(), false)
if err != nil {
return "", err
if strings.Contains(err.Error(), "no addresses returned") {
depositAddr, err = k.GetCryptoDepositAddress(ctx, chain, cryptocurrency.String(), true)
if err != nil {
return nil, err
}
} else {
return nil, err
}
}
var method string
for _, m := range methods {
method = m.Method
}
if method == "" {
return "", errors.New("method not found")
}
return k.GetCryptoDepositAddress(ctx, method, cryptocurrency.String())
return &deposit.Address{
Address: depositAddr[0].Address,
Tag: depositAddr[0].Tag,
}, nil
}
// WithdrawCryptocurrencyFunds returns a withdrawal ID when a withdrawal
@@ -1501,3 +1518,18 @@ func compatibleFillOrderType(fillType string) (order.Type, error) {
}
return resp, nil
}
// GetAvailableTransferChains returns the available transfer blockchains for the specific
// cryptocurrency
func (k *Kraken) GetAvailableTransferChains(ctx context.Context, cryptocurrency currency.Code) ([]string, error) {
methods, err := k.GetDepositMethods(ctx, cryptocurrency.String())
if err != nil {
return nil, err
}
var availableChains []string
for x := range methods {
availableChains = append(availableChains, methods[x].Method)
}
return availableChains, nil
}