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

@@ -15,6 +15,7 @@ import (
"github.com/thrasher-corp/gocryptotrader/common"
"github.com/thrasher-corp/gocryptotrader/common/crypto"
"github.com/thrasher-corp/gocryptotrader/currency"
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
"github.com/thrasher-corp/gocryptotrader/exchanges/request"
@@ -26,11 +27,11 @@ type Coinbene struct {
}
const (
coinbeneAPIURL = "https://openapi-exchange.coinbene.com/api/exchange/"
coinbeneSwapAPIURL = "https://openapi-contract.coinbene.com/api/usdt/"
coinbeneAuthPath = "/api/exchange/v2"
coinbeneSwapAuthPath = "/api/usdt/v2"
coinbeneAPIVersion = "v2"
coinbeneAPIURL = "https://openapi-exchange.coinbene.com"
coinbeneSwapAPIURL = "https://openapi-contract.coinbene.com"
coinbeneSpotPath = "/api/exchange/v2"
coinbeneSwapPath = "/api/usdt/v2"
coinbeneCapitalPath = "/api/capital/v1"
// Public endpoints
coinbeneGetTicker = "/market/ticker/one"
@@ -64,6 +65,8 @@ const (
coinbeneClosedOrdersByPage = "/order/closedOrdersByPage"
coinbeneListSwapPositions = "/position/list"
coinbenePositionFeeRate = "/position/feeRate"
coinbeneDepositAddress = "/deposit/address/list"
coinbeneWithdraw = "/withdraw/apply"
limitOrder = "1"
marketOrder = "2"
@@ -81,8 +84,7 @@ func (c *Coinbene) GetAllPairs(ctx context.Context) ([]PairData, error) {
resp := struct {
Data []PairData `json:"data"`
}{}
path := coinbeneAPIVersion + coinbeneGetAllPairs
return resp.Data, c.SendHTTPRequest(ctx, exchange.RestSpot, path, spotPairs, &resp)
return resp.Data, c.SendHTTPRequest(ctx, exchange.RestSpot, coinbeneGetAllPairs, spotPairs, &resp)
}
// GetPairInfo gets info about a single pair
@@ -92,7 +94,7 @@ func (c *Coinbene) GetPairInfo(ctx context.Context, symbol string) (PairData, er
}{}
params := url.Values{}
params.Set("symbol", symbol)
path := common.EncodeURLValues(coinbeneAPIVersion+coinbenePairInfo, params)
path := common.EncodeURLValues(coinbenePairInfo, params)
return resp.Data, c.SendHTTPRequest(ctx, exchange.RestSpot, path, spotPairInfo, &resp)
}
@@ -109,7 +111,7 @@ func (c *Coinbene) GetOrderbook(ctx context.Context, symbol string, size int64)
params := url.Values{}
params.Set("symbol", symbol)
params.Set("depth", strconv.FormatInt(size, 10))
path := common.EncodeURLValues(coinbeneAPIVersion+coinbeneGetOrderBook, params)
path := common.EncodeURLValues(coinbeneGetOrderBook, params)
err := c.SendHTTPRequest(ctx, exchange.RestSpot, path, spotOrderbook, &resp)
if err != nil {
return Orderbook{}, err
@@ -155,7 +157,7 @@ func (c *Coinbene) GetTicker(ctx context.Context, symbol string) (TickerData, er
}{}
params := url.Values{}
params.Set("symbol", symbol)
path := common.EncodeURLValues(coinbeneAPIVersion+coinbeneGetTicker, params)
path := common.EncodeURLValues(coinbeneGetTicker, params)
return resp.TickerData, c.SendHTTPRequest(ctx, exchange.RestSpot, path, spotSpecificTicker, &resp)
}
@@ -165,8 +167,7 @@ func (c *Coinbene) GetTickers(ctx context.Context) ([]TickerData, error) {
TickerData []TickerData `json:"data"`
}{}
path := coinbeneAPIVersion + coinbeneGetTickersSpot
return resp.TickerData, c.SendHTTPRequest(ctx, exchange.RestSpot, path, spotTickerList, &resp)
return resp.TickerData, c.SendHTTPRequest(ctx, exchange.RestSpot, coinbeneGetTickersSpot, spotTickerList, &resp)
}
// GetTrades gets recent trades from the exchange
@@ -178,7 +179,7 @@ func (c *Coinbene) GetTrades(ctx context.Context, symbol string, limit int64) (T
params := url.Values{}
params.Set("symbol", symbol)
params.Set("limit", strconv.FormatInt(limit, 10))
path := common.EncodeURLValues(coinbeneAPIVersion+coinbeneGetTrades, params)
path := common.EncodeURLValues(coinbeneGetTrades, params)
err := c.SendHTTPRequest(ctx, exchange.RestSpot, path, spotMarketTrades, &resp)
if err != nil {
return nil, err
@@ -214,11 +215,11 @@ func (c *Coinbene) GetAccountBalances(ctx context.Context) ([]UserBalanceData, e
resp := struct {
Data []UserBalanceData `json:"data"`
}{}
path := coinbeneAPIVersion + coinbeneGetUserBalance
err := c.SendAuthHTTPRequest(ctx, exchange.RestSpot, http.MethodGet,
path,
err := c.SendAuthHTTPRequest(ctx,
exchange.RestSpot,
http.MethodGet,
coinbeneGetUserBalance,
false,
APISpotPath,
nil,
&resp,
spotAccountInfo)
@@ -235,13 +236,11 @@ func (c *Coinbene) GetAccountAssetBalance(ctx context.Context, symbol string) (U
resp := struct {
Data UserBalanceData `json:"data"`
}{}
path := coinbeneAPIVersion + coinbeneAccountBalanceOne
err := c.SendAuthHTTPRequest(ctx,
exchange.RestSpot,
http.MethodGet,
path,
coinbeneAccountBalanceOne,
false,
APISpotPath,
v,
&resp,
spotAccountAssetInfo)
@@ -292,11 +291,11 @@ func (c *Coinbene) PlaceSpotOrder(ctx context.Context, price, quantity float64,
if notional != 0 {
params.Set("notional", strconv.Itoa(notional))
}
path := coinbeneAPIVersion + coinbenePlaceOrder
err := c.SendAuthHTTPRequest(ctx, exchange.RestSpot, http.MethodPost,
path,
err := c.SendAuthHTTPRequest(ctx,
exchange.RestSpot,
http.MethodPost,
coinbenePlaceOrder,
false,
APISpotPath,
params,
&resp,
spotPlaceOrder)
@@ -362,13 +361,11 @@ func (c *Coinbene) PlaceSpotOrders(ctx context.Context, orders []PlaceOrderReque
resp := struct {
Data []OrderPlacementResponse `json:"data"`
}{}
path := coinbeneAPIVersion + coinbeneBatchPlaceOrder
err := c.SendAuthHTTPRequest(ctx,
exchange.RestSpot,
http.MethodPost,
path,
coinbeneBatchPlaceOrder,
false,
APISpotPath,
reqOrders,
&resp,
spotBatchOrder)
@@ -382,17 +379,17 @@ func (c *Coinbene) PlaceSpotOrders(ctx context.Context, orders []PlaceOrderReque
func (c *Coinbene) FetchOpenSpotOrders(ctx context.Context, symbol string) (OrdersInfo, error) {
params := url.Values{}
params.Set("symbol", symbol)
path := coinbeneAPIVersion + coinbeneOpenOrders
var orders OrdersInfo
for i := int64(1); ; i++ {
temp := struct {
Data OrdersInfo `json:"data"`
}{}
params.Set("pageNum", strconv.FormatInt(i, 10))
err := c.SendAuthHTTPRequest(ctx, exchange.RestSpot, http.MethodGet,
path,
err := c.SendAuthHTTPRequest(ctx,
exchange.RestSpot,
http.MethodGet,
coinbeneOpenOrders,
false,
APISpotPath,
params,
&temp,
spotQueryOpenOrders)
@@ -415,7 +412,6 @@ func (c *Coinbene) FetchClosedOrders(ctx context.Context, symbol, latestID strin
params := url.Values{}
params.Set("symbol", symbol)
params.Set("latestOrderId", latestID)
path := coinbeneAPIVersion + coinbeneClosedOrders
var orders OrdersInfo
for i := int64(1); ; i++ {
temp := struct {
@@ -425,9 +421,8 @@ func (c *Coinbene) FetchClosedOrders(ctx context.Context, symbol, latestID strin
err := c.SendAuthHTTPRequest(ctx,
exchange.RestSpot,
http.MethodGet,
path,
coinbeneClosedOrders,
false,
APISpotPath,
params,
&temp,
spotQueryClosedOrders)
@@ -451,13 +446,11 @@ func (c *Coinbene) FetchSpotOrderInfo(ctx context.Context, orderID string) (Orde
}{}
params := url.Values{}
params.Set("orderId", orderID)
path := coinbeneAPIVersion + coinbeneOrderInfo
err := c.SendAuthHTTPRequest(ctx,
exchange.RestSpot,
http.MethodGet,
path,
coinbeneOrderInfo,
false,
APISpotPath,
params,
&resp,
spotQuerySpecficOrder)
@@ -478,13 +471,11 @@ func (c *Coinbene) GetSpotOrderFills(ctx context.Context, orderID string) ([]Ord
}{}
params := url.Values{}
params.Set("orderId", orderID)
path := coinbeneAPIVersion + coinbeneTradeFills
err := c.SendAuthHTTPRequest(ctx,
exchange.RestSpot,
http.MethodGet,
path,
coinbeneTradeFills,
false,
APISpotPath,
params,
&resp,
spotQueryTradeFills)
@@ -501,13 +492,11 @@ func (c *Coinbene) CancelSpotOrder(ctx context.Context, orderID string) (string,
}{}
req := make(map[string]interface{})
req["orderId"] = orderID
path := coinbeneAPIVersion + coinbeneCancelOrder
err := c.SendAuthHTTPRequest(ctx,
exchange.RestSpot,
http.MethodPost,
path,
coinbeneCancelOrder,
false,
APISpotPath,
req,
&resp,
spotCancelOrder)
@@ -526,13 +515,12 @@ func (c *Coinbene) CancelSpotOrders(ctx context.Context, orderIDs []string) ([]O
}
var r resp
path := coinbeneAPIVersion + coinbeneBatchCancel
err := c.SendAuthHTTPRequest(ctx,
err := c.SendAuthHTTPRequest(
ctx,
exchange.RestSpot,
http.MethodPost,
path,
coinbeneBatchCancel,
false,
APISpotPath,
req,
&r,
spotCancelOrdersBatch)
@@ -548,8 +536,7 @@ func (c *Coinbene) GetSwapTickers(ctx context.Context) (SwapTickers, error) {
Data SwapTickers `json:"data"`
}
var r resp
path := coinbeneAPIVersion + coinbeneGetTickers
err := c.SendHTTPRequest(ctx, exchange.RestSwap, path, contractTickers, &r)
err := c.SendHTTPRequest(ctx, exchange.RestSwap, coinbeneGetTickers, contractTickers, &r)
if err != nil {
return nil, err
}
@@ -575,12 +562,8 @@ func (c *Coinbene) GetSwapInstruments(ctx context.Context) ([]Instrument, error)
resp := struct {
Data []Instrument `json:"data"`
}{}
return resp.Data,
c.SendHTTPRequest(ctx,
exchange.RestSwap,
coinbeneAPIVersion+coinbeneGetInstruments,
contractInstruments,
&resp)
return resp.Data, c.SendHTTPRequest(ctx, exchange.RestSwap,
coinbeneGetInstruments, contractInstruments, &resp)
}
// GetSwapOrderbook returns an orderbook for the specified currency
@@ -606,7 +589,7 @@ func (c *Coinbene) GetSwapOrderbook(ctx context.Context, symbol string, size int
}
var r resp
path := common.EncodeURLValues(coinbeneAPIVersion+coinbeneGetOrderBook, v)
path := common.EncodeURLValues(coinbeneGetOrderBook, v)
err := c.SendHTTPRequest(ctx, exchange.RestSwap, path, contractOrderbook, &r)
if err != nil {
return s, err
@@ -662,7 +645,7 @@ func (c *Coinbene) GetKlines(ctx context.Context, pair string, start, end time.T
}
v.Add("period", period)
path := common.EncodeURLValues(coinbeneAPIVersion+coinbeneSpotKlines, v)
path := common.EncodeURLValues(coinbeneSpotKlines, v)
if err = c.SendHTTPRequest(ctx, exchange.RestSpot, path, contractKline, &resp); err != nil {
return
}
@@ -686,7 +669,7 @@ func (c *Coinbene) GetSwapKlines(ctx context.Context, symbol string, start, end
}
v.Set("resolution", resolution)
path := common.EncodeURLValues(coinbeneAPIVersion+coinbeneGetKlines, v)
path := common.EncodeURLValues(coinbeneGetKlines, v)
if err = c.SendHTTPRequest(ctx, exchange.RestSwap, path, contractKline, &resp); err != nil {
return
}
@@ -705,7 +688,7 @@ func (c *Coinbene) GetSwapTrades(ctx context.Context, symbol string, limit int)
Data [][]string `json:"data"`
}
var r resp
path := common.EncodeURLValues(coinbeneAPIVersion+coinbeneGetTrades, v)
path := common.EncodeURLValues(coinbeneGetTrades, v)
if err := c.SendHTTPRequest(ctx, exchange.RestSwap, path, contractTrades, &r); err != nil {
return nil, err
}
@@ -744,11 +727,11 @@ func (c *Coinbene) GetSwapAccountInfo(ctx context.Context) (SwapAccountInfo, err
Data SwapAccountInfo `json:"data"`
}
var r resp
path := coinbeneAPIVersion + coinbeneAccountInfo
err := c.SendAuthHTTPRequest(ctx, exchange.RestSwap, http.MethodGet,
path,
err := c.SendAuthHTTPRequest(ctx,
exchange.RestSwap,
http.MethodGet,
coinbeneAccountInfo,
true,
APISwapPath,
nil,
&r,
contractAccountInfo)
@@ -766,11 +749,11 @@ func (c *Coinbene) GetSwapPositions(ctx context.Context, symbol string) (SwapPos
Data SwapPositions `json:"data"`
}
var r resp
path := coinbeneAPIVersion + coinbeneListSwapPositions
err := c.SendAuthHTTPRequest(ctx, exchange.RestSwap, http.MethodGet,
path,
err := c.SendAuthHTTPRequest(ctx,
exchange.RestSwap,
http.MethodGet,
coinbeneListSwapPositions,
true,
APISwapPath,
v,
&r,
contractPositionInfo)
@@ -821,13 +804,11 @@ func (c *Coinbene) PlaceSwapOrder(ctx context.Context, symbol, direction, orderT
Data SwapPlaceOrderResponse `json:"data"`
}
var r resp
path := coinbeneAPIVersion + coinbenePlaceOrder
err := c.SendAuthHTTPRequest(ctx,
exchange.RestSwap,
http.MethodPost,
path,
coinbenePlaceOrder,
true,
APISwapPath,
v,
&r,
contractPlaceOrder)
@@ -845,11 +826,11 @@ func (c *Coinbene) CancelSwapOrder(ctx context.Context, orderID string) (string,
Data string `json:"data"`
}
var r resp
path := coinbeneAPIVersion + coinbeneCancelOrder
err := c.SendAuthHTTPRequest(ctx, exchange.RestSwap, http.MethodPost,
path,
err := c.SendAuthHTTPRequest(ctx,
exchange.RestSwap,
http.MethodPost,
coinbeneCancelOrder,
true,
APISwapPath,
params,
&r,
contractCancelOrder)
@@ -873,13 +854,11 @@ func (c *Coinbene) GetSwapOpenOrders(ctx context.Context, symbol string, pageNum
Data SwapOrders `json:"data"`
}
var r resp
path := coinbeneAPIVersion + coinbeneOpenOrders
err := c.SendAuthHTTPRequest(ctx,
exchange.RestSwap,
http.MethodGet,
path,
coinbeneOpenOrders,
true,
APISwapPath,
v,
&r,
contractGetOpenOrders)
@@ -902,13 +881,11 @@ func (c *Coinbene) GetSwapOpenOrdersByPage(ctx context.Context, symbol string, l
Data SwapOrders `json:"data"`
}
var r resp
path := coinbeneAPIVersion + coinbeneOpenOrdersByPage
err := c.SendAuthHTTPRequest(ctx,
exchange.RestSwap,
http.MethodGet,
path,
coinbeneOpenOrdersByPage,
true,
APISwapPath,
v,
&r,
contractOpenOrdersByPage)
@@ -926,13 +903,11 @@ func (c *Coinbene) GetSwapOrderInfo(ctx context.Context, orderID string) (SwapOr
Data SwapOrder `json:"data"`
}
var r resp
path := coinbeneAPIVersion + coinbeneOrderInfo
err := c.SendAuthHTTPRequest(ctx,
exchange.RestSwap,
http.MethodGet,
path,
coinbeneOrderInfo,
true,
APISwapPath,
v,
&r,
contractGetOrderInfo)
@@ -971,11 +946,11 @@ func (c *Coinbene) GetSwapOrderHistory(ctx context.Context, beginTime, endTime,
}
var r resp
path := coinbeneAPIVersion + coinbeneClosedOrders
err := c.SendAuthHTTPRequest(ctx, exchange.RestSwap, http.MethodGet,
path,
err := c.SendAuthHTTPRequest(ctx,
exchange.RestSwap,
http.MethodGet,
coinbeneClosedOrders,
true,
APISwapPath,
v,
&r,
contractGetClosedOrders)
@@ -1009,13 +984,11 @@ func (c *Coinbene) GetSwapOrderHistoryByOrderID(ctx context.Context, beginTime,
}
var r resp
path := coinbeneAPIVersion + coinbeneClosedOrdersByPage
err := c.SendAuthHTTPRequest(ctx,
exchange.RestSwap,
http.MethodGet,
path,
coinbeneClosedOrdersByPage,
true,
APISwapPath,
v,
&r,
contractGetClosedOrdersbyPage)
@@ -1037,13 +1010,11 @@ func (c *Coinbene) CancelSwapOrders(ctx context.Context, orderIDs []string) ([]O
}
var r resp
path := coinbeneAPIVersion + coinbeneBatchCancel
err := c.SendAuthHTTPRequest(ctx,
exchange.RestSwap,
http.MethodPost,
path,
coinbeneBatchCancel,
true,
APISwapPath,
req,
&r,
contractCancelMultipleOrders)
@@ -1070,13 +1041,11 @@ func (c *Coinbene) GetSwapOrderFills(ctx context.Context, symbol, orderID string
}
var r resp
path := coinbeneAPIVersion + coinbeneOrderFills
err := c.SendAuthHTTPRequest(ctx,
exchange.RestSwap,
http.MethodGet,
path,
coinbeneOrderFills,
true,
APISwapPath,
v,
&r,
contractGetOrderFills)
@@ -1100,13 +1069,11 @@ func (c *Coinbene) GetSwapFundingRates(ctx context.Context, pageNum, pageSize in
}
var r resp
path := coinbeneAPIVersion + coinbenePositionFeeRate
err := c.SendAuthHTTPRequest(ctx,
exchange.RestSwap,
http.MethodGet,
path,
coinbenePositionFeeRate,
true,
APISwapPath,
v,
&r,
contractGetFundingRates)
@@ -1123,10 +1090,15 @@ func (c *Coinbene) SendHTTPRequest(ctx context.Context, ep exchange.URL, path st
return err
}
epPath := coinbeneSpotPath
if ep == exchange.RestSwap {
epPath = coinbeneSwapPath
}
var resp json.RawMessage
item := &request.Item{
Method: http.MethodGet,
Path: endpoint + path,
Path: endpoint + epPath + path,
Result: &resp,
Verbose: c.Verbose,
HTTPDebugging: c.HTTPDebugging,
@@ -1151,7 +1123,7 @@ func (c *Coinbene) SendHTTPRequest(ctx context.Context, ep exchange.URL, path st
}
// SendAuthHTTPRequest sends an authenticated HTTP request
func (c *Coinbene) SendAuthHTTPRequest(ctx context.Context, ep exchange.URL, method, path, epPath string, isSwap bool,
func (c *Coinbene) SendAuthHTTPRequest(ctx context.Context, ep exchange.URL, method, epPath string, epAuthPath uint8,
params, result interface{}, f request.EndpointLimit) error {
if !c.AllowAuthenticatedRequest() {
return fmt.Errorf("%s %w", c.Name, exchange.ErrAuthenticatedRequestWithoutCredentialsSet)
@@ -1160,9 +1132,17 @@ func (c *Coinbene) SendAuthHTTPRequest(ctx context.Context, ep exchange.URL, met
if err != nil {
return err
}
authPath := coinbeneAuthPath
if isSwap {
authPath = coinbeneSwapAuthPath
var authPath string
switch epAuthPath {
case APISpotPath:
authPath = coinbeneSpotPath
case APISwapPath:
authPath = coinbeneSwapPath
case APICapitalPath:
authPath = coinbeneCapitalPath
default:
return errors.New("unsupported auth path")
}
var resp json.RawMessage
@@ -1170,15 +1150,15 @@ func (c *Coinbene) SendAuthHTTPRequest(ctx context.Context, ep exchange.URL, met
timestamp := time.Now().UTC().Format("2006-01-02T15:04:05.999Z")
var finalBody io.Reader
var preSign string
var fullPath = path
fullPath := authPath + epPath
switch {
case params != nil && method == http.MethodGet:
p, ok := params.(url.Values)
if !ok {
return nil, errors.New("params is not of type url.Values")
}
preSign = timestamp + method + authPath + epPath + "?" + p.Encode()
fullPath = common.EncodeURLValues(path, p)
preSign = common.EncodeURLValues(timestamp+method+authPath+epPath, p)
fullPath = common.EncodeURLValues(authPath+epPath, p)
case params != nil:
var i interface{}
switch p := params.(type) {
@@ -1242,3 +1222,63 @@ func (c *Coinbene) SendAuthHTTPRequest(ctx context.Context, ep exchange.URL, met
}
return json.Unmarshal(resp, result)
}
// ListDepositAddress returns a list of deposit addresses for a given cryptocurrency
func (c *Coinbene) ListDepositAddress(ctx context.Context, crypto currency.Code) ([]DepositAddress, error) {
vals := url.Values{}
if crypto.IsEmpty() {
return nil, errors.New("crypto asset must be specified")
}
vals.Set("asset", crypto.Upper().String())
resp := struct {
Data []DepositAddress `json:"data"`
}{}
err := c.SendAuthHTTPRequest(ctx,
exchange.RestSpot,
http.MethodGet,
coinbeneDepositAddress,
APICapitalPath,
vals,
&resp,
capitalDeposit)
if err != nil {
return nil, err
}
return resp.Data, nil
}
// Withdraw issues a withdrawawl request based on the supplied parameters
func (c *Coinbene) Withdraw(ctx context.Context, curr currency.Code, address, tag, chain string, amount float64) (*WithdrawResponse, error) {
if curr.IsEmpty() || address == "" || amount == 0 {
return nil, errors.New("asset, address and amount must be specified")
}
vals := url.Values{}
vals.Set("asset", curr.Upper().String())
vals.Set("address", address)
vals.Set("amount", strconv.FormatFloat(amount, 'f', -1, 64))
if tag != "" {
vals.Set("tag", tag)
}
if chain != "" {
vals.Set("chain", chain)
}
resp := struct {
Data WithdrawResponse `json:"data"`
}{}
err := c.SendAuthHTTPRequest(ctx,
exchange.RestSpot,
http.MethodPost,
coinbeneWithdraw,
APICapitalPath,
vals,
&resp,
capitalWithdraw)
if err != nil {
return nil, err
}
return &resp.Data, nil
}

View File

@@ -9,11 +9,13 @@ import (
"github.com/thrasher-corp/gocryptotrader/common"
"github.com/thrasher-corp/gocryptotrader/config"
"github.com/thrasher-corp/gocryptotrader/core"
"github.com/thrasher-corp/gocryptotrader/currency"
"github.com/thrasher-corp/gocryptotrader/exchanges/asset"
"github.com/thrasher-corp/gocryptotrader/exchanges/kline"
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
"github.com/thrasher-corp/gocryptotrader/exchanges/sharedtestvalues"
"github.com/thrasher-corp/gocryptotrader/portfolio/withdraw"
)
// Please supply your own keys here for due diligence testing
@@ -191,6 +193,42 @@ func TestFetchOrderInfo(t *testing.T) {
}
}
func TestGetDepositAddress(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() {
t.Skip("API keys required but not set, skipping test")
}
_, err := c.GetDepositAddress(context.Background(), currency.USDT, "", "ETH")
if err != nil {
t.Error(err)
}
}
func TestWithdraw(t *testing.T) {
t.Parallel()
if areTestAPIKeysSet() && !canManipulateRealOrders {
t.Skip("API keys set, canManipulateRealOrders false, skipping test")
}
withdrawCryptoRequest := withdraw.Request{
Exchange: c.Name,
Amount: -1,
Currency: currency.BTC,
Description: "WITHDRAW IT ALL",
Crypto: withdraw.CryptoRequest{
Address: core.BitcoinDonationAddress,
},
}
_, err := c.WithdrawCryptocurrencyFunds(context.Background(), &withdrawCryptoRequest)
if !areTestAPIKeysSet() && err == nil {
t.Error("Expecting an error when no keys are set")
}
if areTestAPIKeysSet() && err != nil {
t.Errorf("Withdraw failed to be placed: %v", err)
}
}
func TestGetSpotOrderFills(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() {
@@ -814,3 +852,25 @@ func TestGetHistoricTrades(t *testing.T) {
t.Error(err)
}
}
func TestListDepositAddress(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() {
t.Skip("api keys not set")
}
_, err := c.ListDepositAddress(context.Background(), currency.USDT)
if err != nil {
t.Fatal(err)
}
}
func TestGetAvailableTransferCurrencies(t *testing.T) {
t.Parallel()
if !areTestAPIKeysSet() {
t.Skip("api keys not set")
}
_, err := c.GetAvailableTransferChains(context.Background(), currency.USDT)
if err != nil {
t.Fatal(err)
}
}

View File

@@ -7,6 +7,13 @@ import (
"github.com/thrasher-corp/gocryptotrader/exchanges/order"
)
// Coinbene path vals
const (
APISpotPath uint8 = iota
APISwapPath
APICapitalPath
)
// TickerData stores ticker data
type TickerData struct {
Symbol string `json:"symbol"`
@@ -410,3 +417,23 @@ type CandleResponse struct {
Message string `json:"message"`
Data [][]interface{} `json:"data"`
}
// DepositAddress stores the deposit address data
type DepositAddress struct {
Asset string `json:"asset"`
Chain string `json:"chain"`
Address string `json:"address"`
AddressTag string `json:"addressTag"`
DepositLimit float64 `json:"depositLimit,string"`
BlockNumber uint8 `json:"blockNumber,string"`
}
// WithdrawResponse stores the withdrawal request response data
type WithdrawResponse struct {
ID string `json:"id"`
Amount float64 `json:"amount"`
Asset string `json:"asset"`
Address string `json:"address"`
Tag string `json:"tag"`
Chain string `json:"chain"`
}

View File

@@ -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"
@@ -92,17 +93,21 @@ func (c *Coinbene) SetDefaults() {
REST: true,
Websocket: true,
RESTCapabilities: protocol.Features{
TickerFetching: true,
TradeFetching: true,
OrderbookFetching: true,
AccountBalance: true,
AutoPairUpdates: true,
GetOrder: true,
GetOrders: true,
CancelOrder: true,
CancelOrders: true,
SubmitOrder: true,
TradeFee: true,
TickerFetching: true,
TradeFetching: true,
OrderbookFetching: true,
AccountBalance: true,
AutoPairUpdates: true,
GetOrder: true,
GetOrders: true,
CancelOrder: true,
CancelOrders: true,
SubmitOrder: true,
TradeFee: true,
CryptoDeposit: true,
CryptoWithdrawal: true,
MultiChainDeposits: true,
MultiChainWithdrawals: true,
},
WebsocketCapabilities: protocol.Features{
TickerFetching: true,
@@ -118,7 +123,7 @@ func (c *Coinbene) SetDefaults() {
GetOrder: true,
},
WithdrawPermissions: exchange.NoFiatWithdrawals |
exchange.WithdrawCryptoViaWebsiteOnly,
exchange.WithdrawCryptoWithAPIPermission,
Kline: kline.ExchangeCapabilitiesSupported{
DateRanges: true,
Intervals: true,
@@ -677,14 +682,47 @@ func (c *Coinbene) GetOrderInfo(ctx context.Context, orderID string, pair curren
}
// GetDepositAddress returns a deposit address for a specified currency
func (c *Coinbene) GetDepositAddress(_ context.Context, _ currency.Code, _ string) (string, error) {
return "", common.ErrFunctionNotSupported
func (c *Coinbene) GetDepositAddress(ctx context.Context, curr currency.Code, _, chain string) (*deposit.Address, error) {
d, err := c.ListDepositAddress(ctx, curr)
if err != nil {
return nil, err
}
if len(d) == 0 {
return nil, errors.New("no address(es) returned, please create one via the Coinbene website")
}
if chain != "" {
for x := range d {
if strings.EqualFold(d[x].Chain, chain) {
return &deposit.Address{Address: d[x].Address, Tag: d[x].AddressTag}, nil
}
}
return nil, fmt.Errorf("no chain %s found", chain)
}
return &deposit.Address{Address: d[0].Address, Tag: d[0].AddressTag}, nil
}
// WithdrawCryptocurrencyFunds returns a withdrawal ID when a withdrawal is
// submitted
func (c *Coinbene) WithdrawCryptocurrencyFunds(_ context.Context, _ *withdraw.Request) (*withdraw.ExchangeResponse, error) {
return nil, common.ErrFunctionNotSupported
func (c *Coinbene) WithdrawCryptocurrencyFunds(ctx context.Context, withdrawRequest *withdraw.Request) (*withdraw.ExchangeResponse, error) {
if err := withdrawRequest.Validate(); err != nil {
return nil, err
}
resp, err := c.Withdraw(ctx,
withdrawRequest.Currency,
withdrawRequest.Crypto.Address,
withdrawRequest.Crypto.AddressTag,
withdrawRequest.Crypto.Chain,
withdrawRequest.Amount)
if err != nil {
return nil, err
}
return &withdraw.ExchangeResponse{
ID: resp.ID,
}, err
}
// WithdrawFiatFunds returns a withdrawal ID when a withdrawal is
@@ -960,3 +998,20 @@ func (c *Coinbene) GetHistoricCandles(ctx context.Context, pair currency.Pair, a
func (c *Coinbene) GetHistoricCandlesExtended(ctx context.Context, pair currency.Pair, a asset.Item, start, end time.Time, interval kline.Interval) (kline.Item, error) {
return c.GetHistoricCandles(ctx, pair, a, start, end, interval)
}
// GetAvailableTransferChains returns the available transfer blockchains for the specific
// cryptocurrency
func (c *Coinbene) GetAvailableTransferChains(ctx context.Context, cryptocurrency currency.Code) ([]string, error) {
r, err := c.ListDepositAddress(ctx, cryptocurrency)
if err != nil {
return nil, err
}
var availableChains []string
for x := range r {
if r[x].Chain != "" {
availableChains = append(availableChains, r[x].Chain)
}
}
return availableChains, nil
}

View File

@@ -50,6 +50,8 @@ const (
queryTradeFillsSpotReqRate = 3
cancelOrderSpotReqRate = 6
cancelOrdersBatchSpotReqRate = 3
capitalDepositReqRate = 1
capitalWithdrawReqRate = 1
// Rate limit functionality
contractOrderbook request.EndpointLimit = iota
@@ -88,6 +90,8 @@ const (
spotQueryTradeFills
spotCancelOrder
spotCancelOrdersBatch
capitalDeposit
capitalWithdraw
)
// RateLimit implements the request.Limiter interface
@@ -127,6 +131,8 @@ type RateLimit struct {
SpotQueryTradeFills *rate.Limiter
SpotCancelOrder *rate.Limiter
SpotCancelOrdersBatch *rate.Limiter
CapitalDeposit *rate.Limiter
CapitalWithdraw *rate.Limiter
}
// Limit limits outbound requests
@@ -178,6 +184,10 @@ func (r *RateLimit) Limit(ctx context.Context, f request.EndpointLimit) error {
return r.SpotSpecificTicker.Wait(ctx)
case spotMarketTrades:
return r.SpotMarketTrades.Wait(ctx)
case capitalDeposit:
return r.CapitalDeposit.Wait(ctx)
case capitalWithdraw:
return r.CapitalWithdraw.Wait(ctx)
// case spotKline: // Not implemented yet
// return r.SpotKline.Wait(ctx)
// case spotExchangeRate:
@@ -243,5 +253,7 @@ func SetRateLimit() *RateLimit {
SpotQueryTradeFills: request.NewRateLimit(spotRateInterval, queryTradeFillsSpotReqRate),
SpotCancelOrder: request.NewRateLimit(spotRateInterval, cancelOrderSpotReqRate),
SpotCancelOrdersBatch: request.NewRateLimit(spotRateInterval, cancelOrdersBatchSpotReqRate),
CapitalDeposit: request.NewRateLimit(spotRateInterval, capitalDepositReqRate),
CapitalWithdraw: request.NewRateLimit(spotRateInterval, capitalWithdrawReqRate),
}
}