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

@@ -38,19 +38,9 @@ const (
bitstampAPIMarket = "market"
bitstampAPIWithdrawalRequests = "withdrawal_requests"
bitstampAPIOpenWithdrawal = "withdrawal/open"
bitstampAPIBitcoinWithdrawal = "bitcoin_withdrawal"
bitstampAPILTCWithdrawal = "ltc_withdrawal"
bitstampAPIETHWithdrawal = "eth_withdrawal"
bitstampAPIBCHWithdrawal = "bch_withdrawal"
bitstampAPIBitcoinDeposit = "bitcoin_deposit_address"
bitstampAPILitecoinDeposit = "ltc_address"
bitstampAPIEthereumDeposit = "eth_address"
bitstampAPIBitcoinCashDeposit = "bch_address"
bitstampAPIUnconfirmedBitcoin = "unconfirmed_btc"
bitstampAPITransferToMain = "transfer-to-main"
bitstampAPITransferFromMain = "transfer-from-main"
bitstampAPIXrpWithdrawal = "xrp_withdrawal"
bitstampAPIXrpDeposit = "xrp_address"
bitstampAPIReturnType = "string"
bitstampAPITradingPairsInfo = "trading-pairs-info"
bitstampOHLC = "ohlc"
@@ -421,38 +411,26 @@ func (b *Bitstamp) GetWithdrawalRequests(ctx context.Context, timedelta int64) (
// address - The wallet address of the cryptocurrency
// symbol - the type of crypto ie "ltc", "btc", "eth"
// destTag - only for XRP default to ""
// instant - only for bitcoins
func (b *Bitstamp) CryptoWithdrawal(ctx context.Context, amount float64, address, symbol, destTag string, instant bool) (CryptoWithdrawalResponse, error) {
func (b *Bitstamp) CryptoWithdrawal(ctx context.Context, amount float64, address, symbol, destTag string) (*CryptoWithdrawalResponse, error) {
var req = url.Values{}
req.Add("amount", strconv.FormatFloat(amount, 'f', -1, 64))
req.Add("address", address)
resp := CryptoWithdrawalResponse{}
var endpoint string
switch strings.ToLower(symbol) {
case currency.BTC.Lower().String():
if instant {
req.Add("instant", "1")
} else {
req.Add("instant", "0")
var endpoint string
switch strings.ToUpper(symbol) {
case currency.XLM.String():
if destTag != "" {
req.Add("memo_id", destTag)
}
endpoint = bitstampAPIBitcoinWithdrawal
case currency.LTC.Lower().String():
endpoint = bitstampAPILTCWithdrawal
case currency.ETH.Lower().String():
endpoint = bitstampAPIETHWithdrawal
case currency.XRP.Lower().String():
case currency.XRP.String():
if destTag != "" {
req.Add("destination_tag", destTag)
}
endpoint = bitstampAPIXrpWithdrawal
case currency.BCH.Lower().String():
endpoint = bitstampAPIBCHWithdrawal
default:
return resp, errors.New("incorrect symbol")
}
return resp, b.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, endpoint, false, req, &resp)
var resp CryptoWithdrawalResponse
endpoint = strings.ToLower(symbol) + "_withdrawal"
return &resp, b.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, endpoint, true, req, &resp)
}
// OpenBankWithdrawal Opens a bank withdrawal request (SEPA or international)
@@ -506,36 +484,10 @@ func (b *Bitstamp) OpenInternationalBankWithdrawal(ctx context.Context, amount f
// GetCryptoDepositAddress returns a depositing address by crypto
// crypto - example "btc", "ltc", "eth", "xrp" or "bch"
func (b *Bitstamp) GetCryptoDepositAddress(ctx context.Context, crypto currency.Code) (string, error) {
var resp string
v2Resp := struct {
Address string `json:"address"`
}{}
switch crypto {
case currency.BTC:
return resp,
b.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, bitstampAPIBitcoinDeposit, false, nil, &resp)
case currency.LTC:
return v2Resp.Address,
b.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, bitstampAPILitecoinDeposit, true, nil, &v2Resp)
case currency.ETH:
return v2Resp.Address,
b.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, bitstampAPIEthereumDeposit, true, nil, &v2Resp)
case currency.XRP:
return v2Resp.Address,
b.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, bitstampAPIXrpDeposit, true, nil, &v2Resp)
case currency.BCH:
return v2Resp.Address,
b.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, bitstampAPIBitcoinCashDeposit, true, nil, &v2Resp)
default:
return resp, fmt.Errorf("unsupported cryptocurrency string %s", crypto)
}
func (b *Bitstamp) GetCryptoDepositAddress(ctx context.Context, crypto currency.Code) (*DepositAddress, error) {
path := crypto.Lower().String() + "_address"
var resp DepositAddress
return &resp, b.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, path, true, nil, &resp)
}
// GetUnconfirmedBitcoinDeposits returns unconfirmed transactions
@@ -673,31 +625,29 @@ func (b *Bitstamp) SendAuthenticatedHTTPRequest(ctx context.Context, ep exchange
}
errCap := struct {
Error string `json:"error"`
Status string `json:"status"`
Reason interface{} `json:"reason"`
Error string `json:"error"` // v1 errors
Status string `json:"status"` // v2 errors
Reason interface{} `json:"reason"` // v2 errors
}{}
if err := json.Unmarshal(interim, &errCap); err == nil {
if errCap.Error != "" {
return errors.New(errCap.Error)
}
if data, ok := errCap.Reason.(map[string][]string); ok {
var details strings.Builder
for x := range data {
details.WriteString(strings.Join(data[x], ""))
if errCap.Error != "" || errCap.Status == errStr {
if errCap.Error != "" { // v1 errors
return errors.New(errCap.Error)
}
switch data := errCap.Reason.(type) { // v2 errors
case map[string]interface{}:
var details strings.Builder
for k, v := range data {
details.WriteString(fmt.Sprintf("%s: %v", k, v))
}
return errors.New(details.String())
case string:
return errors.New(data)
default:
return errors.New(errCap.Status)
}
return errors.New(details.String())
}
if data, ok := errCap.Reason.(string); ok {
return errors.New(data)
}
if errCap.Status != "" {
return errors.New(errCap.Status)
}
}
return json.Unmarshal(interim, result)
}

View File

@@ -466,6 +466,7 @@ func TestWithdraw(t *testing.T) {
}
withdrawCryptoRequest := withdraw.Request{
Exchange: b.Name,
Amount: -1,
Currency: currency.BTC,
Description: "WITHDRAW IT ALL",
@@ -575,7 +576,7 @@ func TestWithdrawInternationalBank(t *testing.T) {
func TestGetDepositAddress(t *testing.T) {
t.Parallel()
_, err := b.GetDepositAddress(context.Background(), currency.BTC, "")
_, err := b.GetDepositAddress(context.Background(), currency.XRP, "", "")
switch {
case areTestAPIKeysSet() && customerID != "" && err != nil && !mockTests:
t.Error("GetDepositAddress error", err)

View File

@@ -133,6 +133,12 @@ type CancelOrder struct {
ID int64 `json:"id"`
}
// DepositAddress holds the deposit info
type DepositAddress struct {
Address string `json:"address"`
DestinationTag int64 `json:"destination_tag"`
}
// WithdrawalRequests holds request information on withdrawals
type WithdrawalRequests struct {
OrderID int64 `json:"id"`
@@ -147,15 +153,12 @@ type WithdrawalRequests struct {
// CryptoWithdrawalResponse response from a crypto withdrawal request
type CryptoWithdrawalResponse struct {
ID string `json:"id"`
Error map[string][]string `json:"error"`
ID int64 `json:"id"`
}
// FIATWithdrawalResponse response from a fiat withdrawal request
type FIATWithdrawalResponse struct {
ID string `json:"id"`
Status string `json:"status"`
Reason map[string][]string `json:"reason"`
ID int64 `json:"id"`
}
// UnconfirmedBTCTransactions holds address information about unconfirmed

View File

@@ -5,7 +5,6 @@ import (
"errors"
"sort"
"strconv"
"strings"
"sync"
"time"
@@ -15,6 +14,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"
@@ -590,8 +590,21 @@ func (b *Bitstamp) GetOrderInfo(ctx context.Context, orderID string, pair curren
}
// GetDepositAddress returns a deposit address for a specified currency
func (b *Bitstamp) GetDepositAddress(ctx context.Context, cryptocurrency currency.Code, _ string) (string, error) {
return b.GetCryptoDepositAddress(ctx, cryptocurrency)
func (b *Bitstamp) GetDepositAddress(ctx context.Context, cryptocurrency currency.Code, _, _ string) (*deposit.Address, error) {
addr, err := b.GetCryptoDepositAddress(ctx, cryptocurrency)
if err != nil {
return nil, err
}
var tag string
if addr.DestinationTag != 0 {
tag = strconv.FormatInt(addr.DestinationTag, 10)
}
return &deposit.Address{
Address: addr.Address,
Tag: tag,
}, nil
}
// WithdrawCryptocurrencyFunds returns a withdrawal ID when a withdrawal is
@@ -604,21 +617,13 @@ func (b *Bitstamp) WithdrawCryptocurrencyFunds(ctx context.Context, withdrawRequ
withdrawRequest.Amount,
withdrawRequest.Crypto.Address,
withdrawRequest.Currency.String(),
withdrawRequest.Crypto.AddressTag,
true)
withdrawRequest.Crypto.AddressTag)
if err != nil {
return nil, err
}
if len(resp.Error) != 0 {
var details strings.Builder
for x := range resp.Error {
details.WriteString(strings.Join(resp.Error[x], ""))
}
return nil, errors.New(details.String())
}
return &withdraw.ExchangeResponse{
ID: resp.ID,
ID: strconv.FormatInt(resp.ID, 10),
}, nil
}
@@ -643,17 +648,9 @@ func (b *Bitstamp) WithdrawFiatFunds(ctx context.Context, withdrawRequest *withd
if err != nil {
return nil, err
}
if resp.Status == errStr {
var details strings.Builder
for x := range resp.Reason {
details.WriteString(strings.Join(resp.Reason[x], ""))
}
return nil, errors.New(details.String())
}
return &withdraw.ExchangeResponse{
ID: resp.ID,
Status: resp.Status,
ID: strconv.FormatInt(resp.ID, 10),
}, nil
}
@@ -684,17 +681,9 @@ func (b *Bitstamp) WithdrawFiatFundsToInternationalBank(ctx context.Context, wit
if err != nil {
return nil, err
}
if resp.Status == errStr {
var details strings.Builder
for x := range resp.Reason {
details.WriteString(strings.Join(resp.Reason[x], ""))
}
return nil, errors.New(details.String())
}
return &withdraw.ExchangeResponse{
ID: resp.ID,
Status: resp.Status,
ID: strconv.FormatInt(resp.ID, 10),
}, nil
}