mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-19 23:16:48 +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:
@@ -21,25 +21,27 @@ const (
|
||||
exmoAPIURL = "https://api.exmo.com"
|
||||
exmoAPIVersion = "1"
|
||||
|
||||
exmoTrades = "trades"
|
||||
exmoOrderbook = "order_book"
|
||||
exmoTicker = "ticker"
|
||||
exmoPairSettings = "pair_settings"
|
||||
exmoCurrency = "currency"
|
||||
exmoUserInfo = "user_info"
|
||||
exmoOrderCreate = "order_create"
|
||||
exmoOrderCancel = "order_cancel"
|
||||
exmoOpenOrders = "user_open_orders"
|
||||
exmoUserTrades = "user_trades"
|
||||
exmoCancelledOrders = "user_cancelled_orders"
|
||||
exmoOrderTrades = "order_trades"
|
||||
exmoRequiredAmount = "required_amount"
|
||||
exmoDepositAddress = "deposit_address"
|
||||
exmoWithdrawCrypt = "withdraw_crypt"
|
||||
exmoGetWithdrawTXID = "withdraw_get_txid"
|
||||
exmoExcodeCreate = "excode_create"
|
||||
exmoExcodeLoad = "excode_load"
|
||||
exmoWalletHistory = "wallet_history"
|
||||
exmoTrades = "trades"
|
||||
exmoOrderbook = "order_book"
|
||||
exmoTicker = "ticker"
|
||||
exmoPairSettings = "pair_settings"
|
||||
exmoCurrency = "currency"
|
||||
|
||||
exmoUserInfo = "user_info"
|
||||
exmoOrderCreate = "order_create"
|
||||
exmoOrderCancel = "order_cancel"
|
||||
exmoOpenOrders = "user_open_orders"
|
||||
exmoUserTrades = "user_trades"
|
||||
exmoCancelledOrders = "user_cancelled_orders"
|
||||
exmoOrderTrades = "order_trades"
|
||||
exmoRequiredAmount = "required_amount"
|
||||
exmoDepositAddress = "deposit_address"
|
||||
exmoWithdrawCrypt = "withdraw_crypt"
|
||||
exmoGetWithdrawTXID = "withdraw_get_txid"
|
||||
exmoExcodeCreate = "excode_create"
|
||||
exmoExcodeLoad = "excode_load"
|
||||
exmoWalletHistory = "wallet_history"
|
||||
exmoCryptoPaymentProviderList = "payments/providers/crypto/list"
|
||||
|
||||
// Rate limit: 180 per/minute
|
||||
exmoRateInterval = time.Minute
|
||||
@@ -220,7 +222,6 @@ func (e *EXMO) GetCryptoDepositAddress(ctx context.Context) (map[string]string,
|
||||
mapString[key] = v
|
||||
}
|
||||
return mapString, nil
|
||||
|
||||
default:
|
||||
return nil, errors.New("no addresses found, generate required addresses via site")
|
||||
}
|
||||
@@ -228,7 +229,7 @@ func (e *EXMO) GetCryptoDepositAddress(ctx context.Context) (map[string]string,
|
||||
|
||||
// WithdrawCryptocurrency withdraws a cryptocurrency from the exchange to the desired address
|
||||
// NOTE: This API function is available only after request to their tech support team
|
||||
func (e *EXMO) WithdrawCryptocurrency(ctx context.Context, currency, address, invoice string, amount float64) (int64, error) {
|
||||
func (e *EXMO) WithdrawCryptocurrency(ctx context.Context, currency, address, invoice, transport string, amount float64) (int64, error) {
|
||||
type response struct {
|
||||
TaskID int64 `json:"task_id,string"`
|
||||
Result bool `json:"result"`
|
||||
@@ -240,8 +241,12 @@ func (e *EXMO) WithdrawCryptocurrency(ctx context.Context, currency, address, in
|
||||
v.Set("currency", currency)
|
||||
v.Set("address", address)
|
||||
|
||||
if strings.EqualFold(currency, "XRP") {
|
||||
v.Set(invoice, invoice)
|
||||
if invoice != "" {
|
||||
v.Set("invoice", invoice)
|
||||
}
|
||||
|
||||
if transport != "" {
|
||||
v.Set("transport", strings.ToUpper(transport))
|
||||
}
|
||||
|
||||
v.Set("amount", strconv.FormatFloat(amount, 'f', -1, 64))
|
||||
@@ -526,3 +531,10 @@ func getInternationalBankDepositFee(c currency.Code, amount float64, bankTransac
|
||||
|
||||
return fee
|
||||
}
|
||||
|
||||
// GetCryptoPaymentProvidersList returns a map of all the supported cryptocurrency transfer settings
|
||||
func (e *EXMO) GetCryptoPaymentProvidersList(ctx context.Context) (map[string][]CryptoPaymentProvider, error) {
|
||||
var result map[string][]CryptoPaymentProvider
|
||||
path := "/v" + exmoAPIVersion + "/" + exmoCryptoPaymentProviderList
|
||||
return result, e.SendHTTPRequest(ctx, exchange.RestSpot, path, &result)
|
||||
}
|
||||
|
||||
@@ -383,6 +383,7 @@ func TestWithdraw(t *testing.T) {
|
||||
}
|
||||
|
||||
withdrawCryptoRequest := withdraw.Request{
|
||||
Exchange: e.Name,
|
||||
Amount: -1,
|
||||
Currency: currency.BTC,
|
||||
Description: "WITHDRAW IT ALL",
|
||||
@@ -428,12 +429,12 @@ func TestWithdrawInternationalBank(t *testing.T) {
|
||||
|
||||
func TestGetDepositAddress(t *testing.T) {
|
||||
if areTestAPIKeysSet() {
|
||||
_, err := e.GetDepositAddress(context.Background(), currency.LTC, "")
|
||||
_, err := e.GetDepositAddress(context.Background(), currency.USDT, "", "ERC20")
|
||||
if err != nil {
|
||||
t.Error("GetDepositAddress() error", err)
|
||||
}
|
||||
} else {
|
||||
_, err := e.GetDepositAddress(context.Background(), currency.LTC, "")
|
||||
_, err := e.GetDepositAddress(context.Background(), currency.LTC, "", "")
|
||||
if err == nil {
|
||||
t.Error("GetDepositAddress() error cannot be nil")
|
||||
}
|
||||
@@ -483,3 +484,18 @@ func TestUpdateTickers(t *testing.T) {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetCryptoPaymentProvidersList(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, err := e.GetCryptoPaymentProvidersList(context.Background())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetAvailableTransferChains(t *testing.T) {
|
||||
_, err := e.GetAvailableTransferChains(context.Background(), currency.USDT)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,3 +175,16 @@ var WithdrawalFees = map[currency.Code]float64{
|
||||
currency.ZRX: 1,
|
||||
currency.GNT: 1,
|
||||
}
|
||||
|
||||
// CryptoPaymentProvider stores the cryptocurrency transfer settings
|
||||
type CryptoPaymentProvider struct {
|
||||
Type string `json:"type"`
|
||||
Name string `json:"name"`
|
||||
CurrencyName string `json:"currency_name"`
|
||||
Min float64 `json:"min,string"`
|
||||
Max float64 `json:"max,string"`
|
||||
Enabled bool `json:"enabled"`
|
||||
Comment string `json:"comment"`
|
||||
CommissionDescription string `json:"commission_desc"`
|
||||
CurrencyConfirmations uint16 `json:"currency_confirmations"`
|
||||
}
|
||||
|
||||
@@ -16,6 +16,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"
|
||||
@@ -77,26 +78,29 @@ func (e *EXMO) SetDefaults() {
|
||||
REST: true,
|
||||
Websocket: false,
|
||||
RESTCapabilities: protocol.Features{
|
||||
TickerBatching: true,
|
||||
TickerFetching: true,
|
||||
TradeFetching: true,
|
||||
OrderbookFetching: true,
|
||||
AutoPairUpdates: true,
|
||||
AccountInfo: true,
|
||||
GetOrder: true,
|
||||
GetOrders: true,
|
||||
CancelOrder: true,
|
||||
SubmitOrder: true,
|
||||
DepositHistory: true,
|
||||
WithdrawalHistory: true,
|
||||
UserTradeHistory: true,
|
||||
CryptoDeposit: true,
|
||||
CryptoWithdrawal: true,
|
||||
TradeFee: true,
|
||||
FiatDepositFee: true,
|
||||
FiatWithdrawalFee: true,
|
||||
CryptoDepositFee: true,
|
||||
CryptoWithdrawalFee: true,
|
||||
TickerBatching: true,
|
||||
TickerFetching: true,
|
||||
TradeFetching: true,
|
||||
OrderbookFetching: true,
|
||||
AutoPairUpdates: true,
|
||||
AccountInfo: true,
|
||||
GetOrder: true,
|
||||
GetOrders: true,
|
||||
CancelOrder: true,
|
||||
SubmitOrder: true,
|
||||
DepositHistory: true,
|
||||
WithdrawalHistory: true,
|
||||
UserTradeHistory: true,
|
||||
CryptoDeposit: true,
|
||||
CryptoWithdrawal: true,
|
||||
TradeFee: true,
|
||||
FiatDepositFee: true,
|
||||
FiatWithdrawalFee: true,
|
||||
CryptoDepositFee: true,
|
||||
CryptoWithdrawalFee: true,
|
||||
MultiChainDeposits: true,
|
||||
MultiChainWithdrawals: true,
|
||||
MultiChainDepositRequiresChainSet: true,
|
||||
},
|
||||
WithdrawPermissions: exchange.AutoWithdrawCryptoWithSetup |
|
||||
exchange.NoFiatWithdrawals,
|
||||
@@ -524,19 +528,42 @@ func (e *EXMO) GetOrderInfo(ctx context.Context, orderID string, pair currency.P
|
||||
}
|
||||
|
||||
// GetDepositAddress returns a deposit address for a specified currency
|
||||
func (e *EXMO) GetDepositAddress(ctx context.Context, cryptocurrency currency.Code, _ string) (string, error) {
|
||||
func (e *EXMO) GetDepositAddress(ctx context.Context, cryptocurrency currency.Code, _, chain string) (*deposit.Address, error) {
|
||||
fullAddr, err := e.GetCryptoDepositAddress(ctx)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO: Protect map with mutex
|
||||
addr, ok := fullAddr[cryptocurrency.String()]
|
||||
curr := cryptocurrency.Upper().String()
|
||||
if chain != "" && !strings.EqualFold(chain, curr) {
|
||||
curr += strings.ToUpper(chain)
|
||||
}
|
||||
|
||||
addr, ok := fullAddr[curr]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("currency %s could not be found, please generate via the exmo website", cryptocurrency.String())
|
||||
chains, err := e.GetAvailableTransferChains(ctx, cryptocurrency)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(chains) > 1 {
|
||||
// rather than assume, return an error
|
||||
return nil, fmt.Errorf("currency %s has %v chains available, one must be specified", cryptocurrency, chains)
|
||||
}
|
||||
return nil, fmt.Errorf("deposit address for %s could not be found, please generate via the exmo website", cryptocurrency.String())
|
||||
}
|
||||
|
||||
return addr, nil
|
||||
var tag string
|
||||
if strings.Contains(addr, ",") {
|
||||
split := strings.Split(addr, ",")
|
||||
addr, tag = split[0], split[1]
|
||||
}
|
||||
|
||||
return &deposit.Address{
|
||||
Address: addr,
|
||||
Tag: tag,
|
||||
Chain: chain,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// WithdrawCryptocurrencyFunds returns a withdrawal ID when a withdrawal is
|
||||
@@ -549,6 +576,7 @@ func (e *EXMO) WithdrawCryptocurrencyFunds(ctx context.Context, withdrawRequest
|
||||
withdrawRequest.Currency.String(),
|
||||
withdrawRequest.Crypto.Address,
|
||||
withdrawRequest.Crypto.AddressTag,
|
||||
withdrawRequest.Crypto.Chain,
|
||||
withdrawRequest.Amount)
|
||||
|
||||
return &withdraw.ExchangeResponse{
|
||||
@@ -680,3 +708,30 @@ func (e *EXMO) GetHistoricCandles(ctx context.Context, pair currency.Pair, a ass
|
||||
func (e *EXMO) GetHistoricCandlesExtended(ctx context.Context, pair currency.Pair, a asset.Item, start, end time.Time, interval kline.Interval) (kline.Item, error) {
|
||||
return kline.Item{}, common.ErrFunctionNotSupported
|
||||
}
|
||||
|
||||
// GetAvailableTransferChains returns the available transfer blockchains for the specific
|
||||
// cryptocurrency
|
||||
func (e *EXMO) GetAvailableTransferChains(ctx context.Context, cryptocurrency currency.Code) ([]string, error) {
|
||||
chains, err := e.GetCryptoPaymentProvidersList(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
methods, ok := chains[cryptocurrency.Upper().String()]
|
||||
if !ok {
|
||||
return nil, errors.New("no available chains")
|
||||
}
|
||||
|
||||
var availChains []string
|
||||
for x := range methods {
|
||||
if methods[x].Type == "deposit" && methods[x].Enabled {
|
||||
chain := methods[x].Name
|
||||
if strings.Contains(chain, "(") && strings.Contains(chain, ")") {
|
||||
chain = chain[strings.Index(chain, "(")+1 : strings.Index(chain, ")")]
|
||||
}
|
||||
availChains = append(availChains, chain)
|
||||
}
|
||||
}
|
||||
|
||||
return availChains, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user