mirror of
https://github.com/d0zingcat/gocryptotrader.git
synced 2026-05-22 15:10:13 +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:
@@ -8,6 +8,7 @@ import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/thrasher-corp/gocryptotrader/common/convert"
|
||||
@@ -35,6 +36,7 @@ const (
|
||||
zbGetOrdersGet = "getOrders"
|
||||
zbWithdraw = "withdraw"
|
||||
zbDepositAddress = "getUserAddress"
|
||||
zbMultiChainDepositAddress = "getPayinAddress"
|
||||
)
|
||||
|
||||
// ZB is the overarching type across this package
|
||||
@@ -267,15 +269,55 @@ func (z *ZB) GetSpotKline(ctx context.Context, arg KlinesRequestParams) (KLineRe
|
||||
// NOTE - PLEASE BE AWARE THAT YOU NEED TO GENERATE A DEPOSIT ADDRESS VIA
|
||||
// LOGGING IN AND NOT BY USING THIS ENDPOINT OTHERWISE THIS WILL GIVE YOU A
|
||||
// GENERAL ERROR RESPONSE.
|
||||
func (z *ZB) GetCryptoAddress(ctx context.Context, currency currency.Code) (UserAddress, error) {
|
||||
func (z *ZB) GetCryptoAddress(ctx context.Context, currency currency.Code) (*UserAddress, error) {
|
||||
var resp UserAddress
|
||||
|
||||
vals := url.Values{}
|
||||
vals.Set("method", zbDepositAddress)
|
||||
vals.Set("currency", currency.Lower().String())
|
||||
|
||||
return resp,
|
||||
z.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpotSupplementary, http.MethodGet, vals, &resp, request.Auth)
|
||||
if err := z.SendAuthenticatedHTTPRequest(ctx,
|
||||
exchange.RestSpotSupplementary,
|
||||
http.MethodGet,
|
||||
vals,
|
||||
&resp,
|
||||
request.Auth); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !resp.Message.IsSuccessful {
|
||||
return nil, errors.New(resp.Message.Description)
|
||||
}
|
||||
|
||||
if strings.Contains(resp.Message.Data.Address, "_") {
|
||||
splitter := strings.Split(resp.Message.Data.Address, "_")
|
||||
resp.Message.Data.Address, resp.Message.Data.Tag = splitter[0], splitter[1]
|
||||
}
|
||||
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
// GetMultiChainDepositAddress returns deposit addresses for a given currency
|
||||
func (z *ZB) GetMultiChainDepositAddress(ctx context.Context, currency currency.Code) ([]MultiChainDepositAddress, error) {
|
||||
var resp MultiChainDepositAddressResponse
|
||||
|
||||
vals := url.Values{}
|
||||
vals.Set("method", zbMultiChainDepositAddress)
|
||||
vals.Set("currency", currency.Lower().String())
|
||||
|
||||
if err := z.SendAuthenticatedHTTPRequest(ctx,
|
||||
exchange.RestSpotSupplementary,
|
||||
http.MethodGet,
|
||||
vals,
|
||||
&resp,
|
||||
request.Auth); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !resp.Message.IsSuccessful {
|
||||
return nil, errors.New(resp.Message.Description)
|
||||
}
|
||||
return resp.Message.Data, nil
|
||||
}
|
||||
|
||||
// SendHTTPRequest sends an unauthenticated HTTP request
|
||||
@@ -324,8 +366,7 @@ func (z *ZB) SendAuthenticatedHTTPRequest(ctx context.Context, ep exchange.URL,
|
||||
|
||||
var intermediary json.RawMessage
|
||||
newRequest := func() (*request.Item, error) {
|
||||
now := time.Now()
|
||||
params.Set("reqTime", strconv.FormatInt(now.UnixMilli(), 10))
|
||||
params.Set("reqTime", strconv.FormatInt(time.Now().UnixMilli(), 10))
|
||||
params.Set("sign", fmt.Sprintf("%x", hmac))
|
||||
|
||||
urlPath := fmt.Sprintf("%s/%s?%s",
|
||||
@@ -357,9 +398,10 @@ func (z *ZB) SendAuthenticatedHTTPRequest(ctx context.Context, ep exchange.URL,
|
||||
err = json.Unmarshal(intermediary, &errCap)
|
||||
if err == nil {
|
||||
if errCap.Code > 1000 {
|
||||
return fmt.Errorf("sendAuthenticatedHTTPRequest error code: %d message %s",
|
||||
return fmt.Errorf("error code: %d error code message: %s error message: %s",
|
||||
errCap.Code,
|
||||
errorCode[errCap.Code])
|
||||
errorCode[errCap.Code],
|
||||
errCap.Message)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
//+build mock_test_off
|
||||
//go:build mock_test_off
|
||||
// +build mock_test_off
|
||||
|
||||
// This will build if build tag mock_test_off is parsed and will do live testing
|
||||
// using all tests in (exchange)_test.go
|
||||
|
||||
@@ -394,6 +394,7 @@ func TestWithdraw(t *testing.T) {
|
||||
}
|
||||
|
||||
withdrawCryptoRequest := withdraw.Request{
|
||||
Exchange: z.Name,
|
||||
Crypto: withdraw.CryptoRequest{
|
||||
Address: core.BitcoinDonationAddress,
|
||||
FeeAmount: 1,
|
||||
@@ -448,13 +449,31 @@ func TestGetDepositAddress(t *testing.T) {
|
||||
t.Skip("skipping authenticated function for mock testing")
|
||||
}
|
||||
if z.ValidateAPICredentials() {
|
||||
_, err := z.GetDepositAddress(context.Background(), currency.BTC, "")
|
||||
_, err := z.GetDepositAddress(context.Background(), currency.XRP, "", "")
|
||||
if err != nil {
|
||||
t.Error("GetDepositAddress() error PLEASE MAKE SURE YOU CREATE DEPOSIT ADDRESSES VIA ZB.COM",
|
||||
err)
|
||||
}
|
||||
} else {
|
||||
_, err := z.GetDepositAddress(context.Background(), currency.BTC, "")
|
||||
_, err := z.GetDepositAddress(context.Background(), currency.BTC, "", "")
|
||||
if err == nil {
|
||||
t.Error("GetDepositAddress() Expected error")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetMultiChainDepositAddress(t *testing.T) {
|
||||
if mockTests {
|
||||
t.Skip("skipping authenticated function for mock testing")
|
||||
}
|
||||
if z.ValidateAPICredentials() {
|
||||
_, err := z.GetMultiChainDepositAddress(context.Background(), currency.USDT)
|
||||
if err != nil {
|
||||
t.Error("GetDepositAddress() error PLEASE MAKE SURE YOU CREATE DEPOSIT ADDRESSES VIA ZB.COM",
|
||||
err)
|
||||
}
|
||||
} else {
|
||||
_, err := z.GetMultiChainDepositAddress(context.Background(), currency.USDT)
|
||||
if err == nil {
|
||||
t.Error("GetDepositAddress() Expected error")
|
||||
}
|
||||
@@ -1025,8 +1044,25 @@ func TestUpdateTicker(t *testing.T) {
|
||||
|
||||
func TestUpdateTickers(t *testing.T) {
|
||||
t.Parallel()
|
||||
err := z.UpdateTickers(context.Background(), asset.Spot)
|
||||
if err := z.UpdateTickers(context.Background(), asset.Spot); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetAvailableTransferChains(t *testing.T) {
|
||||
t.Parallel()
|
||||
if !z.ValidateAPICredentials() {
|
||||
t.Skip("api keys not set")
|
||||
}
|
||||
_, err := z.GetAvailableTransferChains(context.Background(), currency.BTC)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
r, err := z.GetAvailableTransferChains(context.Background(), currency.USDT)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if len(r) != 3 {
|
||||
t.Error("expected 3 results")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,11 +143,33 @@ type UserAddress struct {
|
||||
Description string `json:"des"`
|
||||
IsSuccessful bool `json:"isSuc"`
|
||||
Data struct {
|
||||
Key string `json:"key"`
|
||||
Address string `json:"key"`
|
||||
Tag string // custom field we populate
|
||||
} `json:"datas"`
|
||||
} `json:"message"`
|
||||
}
|
||||
|
||||
// MultiChainDepositAddress stores an individual multichain deposit item
|
||||
type MultiChainDepositAddress struct {
|
||||
Blockchain string `json:"blockChain"`
|
||||
IsUseMemo bool `json:"isUseMemo"`
|
||||
Account string `json:"account"`
|
||||
Address string `json:"address"`
|
||||
Memo string `json:"memo"`
|
||||
CanDeposit bool `json:"canDeposit"`
|
||||
CanWithdraw bool `json:"canWithdraw"`
|
||||
}
|
||||
|
||||
// MultiChainDepositAddressResponse stores the multichain deposit address response
|
||||
type MultiChainDepositAddressResponse struct {
|
||||
Code int64 `json:"code"`
|
||||
Message struct {
|
||||
Description string `json:"des"`
|
||||
IsSuccessful bool `json:"isSuc"`
|
||||
Data []MultiChainDepositAddress `json:"datas"`
|
||||
} `json:"message"`
|
||||
}
|
||||
|
||||
// WithdrawalFees the large list of predefined withdrawal fees
|
||||
// Prone to change, using highest value
|
||||
var WithdrawalFees = map[currency.Code]float64{
|
||||
|
||||
@@ -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"
|
||||
@@ -85,6 +86,7 @@ func (z *ZB) SetDefaults() {
|
||||
TradeFee: true,
|
||||
CryptoDepositFee: true,
|
||||
CryptoWithdrawalFee: true,
|
||||
MultiChainDeposits: true,
|
||||
},
|
||||
WebsocketCapabilities: protocol.Features{
|
||||
TickerFetching: true,
|
||||
@@ -622,13 +624,31 @@ func (z *ZB) GetOrderInfo(ctx context.Context, orderID string, pair currency.Pai
|
||||
}
|
||||
|
||||
// GetDepositAddress returns a deposit address for a specified currency
|
||||
func (z *ZB) GetDepositAddress(ctx context.Context, cryptocurrency currency.Code, _ string) (string, error) {
|
||||
func (z *ZB) GetDepositAddress(ctx context.Context, cryptocurrency currency.Code, _, chain string) (*deposit.Address, error) {
|
||||
if chain != "" {
|
||||
addresses, err := z.GetMultiChainDepositAddress(ctx, cryptocurrency)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for x := range addresses {
|
||||
if strings.EqualFold(addresses[x].Blockchain, chain) {
|
||||
return &deposit.Address{
|
||||
Address: addresses[x].Address,
|
||||
Tag: addresses[x].Memo,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("%s does not support chain %s", cryptocurrency.String(), chain)
|
||||
}
|
||||
address, err := z.GetCryptoAddress(ctx, cryptocurrency)
|
||||
if err != nil {
|
||||
return "", err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return address.Message.Data.Key, nil
|
||||
return &deposit.Address{
|
||||
Address: address.Message.Data.Address,
|
||||
Tag: address.Message.Data.Tag,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// WithdrawCryptocurrencyFunds returns a withdrawal ID when a withdrawal is
|
||||
@@ -959,3 +979,25 @@ func (z *ZB) validateCandlesRequest(p currency.Pair, a asset.Item, start, end ti
|
||||
Interval: interval,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetAvailableTransferChains returns the available transfer blockchains for the specific
|
||||
// cryptocurrency
|
||||
func (z *ZB) GetAvailableTransferChains(ctx context.Context, cryptocurrency currency.Code) ([]string, error) {
|
||||
chains, err := z.GetMultiChainDepositAddress(ctx, cryptocurrency)
|
||||
if err != nil {
|
||||
// returned on valid currencies like BTC, despite having a deposit
|
||||
// address created it will advise the user to create one via their
|
||||
// app or website. In this case, we'll just return nil transfer
|
||||
// chains and no error message
|
||||
if strings.Contains(err.Error(), "APP") {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var availableChains []string
|
||||
for x := range chains {
|
||||
availableChains = append(availableChains, chains[x].Blockchain)
|
||||
}
|
||||
return availableChains, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user