From e8c7bf9af43a03821677b45cd212c51b60ae66ce Mon Sep 17 00:00:00 2001 From: Ryan O'Hara-Reid Date: Tue, 12 Sep 2017 08:37:18 +1000 Subject: [PATCH] In the common package added JSONDecode error check. Added verbosity in SendHTTPGetRequest. Updated Nonce package function. Fixed issues in ItBit package and expanded test coverage. --- common/common.go | 22 +- common/common_test.go | 6 +- currency/currency.go | 2 +- exchanges/anx/anx.go | 2 +- exchanges/bitfinex/bitfinex.go | 18 +- exchanges/bitstamp/bitstamp.go | 8 +- exchanges/bittrex/bittrex.go | 2 +- exchanges/btcc/btcc.go | 8 +- exchanges/btcmarkets/btcmarkets.go | 6 +- exchanges/gdax/gdax.go | 18 +- exchanges/gemini/gemini.go | 12 +- exchanges/huobi/huobi.go | 4 +- exchanges/itbit/itbit.go | 258 ++++++++++++----------- exchanges/itbit/itbit_test.go | 145 +++++++++++++ exchanges/itbit/itbit_types.go | 155 ++++++++++++-- exchanges/kraken/kraken.go | 16 +- exchanges/lakebtc/lakebtc.go | 6 +- exchanges/liqui/liqui.go | 8 +- exchanges/localbitcoins/localbitcoins.go | 8 +- exchanges/nonce/nonce.go | 40 +++- exchanges/nonce/nonce_test.go | 11 + exchanges/okcoin/okcoin.go | 26 +-- exchanges/poloniex/poloniex.go | 14 +- portfolio/portfolio.go | 47 ++++- 24 files changed, 601 insertions(+), 241 deletions(-) create mode 100644 exchanges/itbit/itbit_test.go diff --git a/common/common.go b/common/common.go index aa762ead..1d4da4ce 100644 --- a/common/common.go +++ b/common/common.go @@ -20,6 +20,7 @@ import ( "net/http" "net/url" "os" + "reflect" "regexp" "strconv" "strings" @@ -294,16 +295,24 @@ func SendHTTPRequest(method, path string, headers map[string]string, body io.Rea // SendHTTPGetRequest sends a simple get request using a url string & JSON // decodes the response into a struct pointer you have supplied. Returns an error // on failure. -func SendHTTPGetRequest(url string, jsonDecode bool, result interface{}) error { +func SendHTTPGetRequest(url string, jsonDecode, isVerbose bool, result interface{}) error { + if isVerbose { + log.Println("Raw URL: ", url) + } + res, err := http.Get(url) if err != nil { return err } if res.StatusCode != 200 { +<<<<<<< dae90a2eaa109648bdb85f8298d805e00ad4e974 log.Printf("HTTP status code: %d\n", res.StatusCode) log.Printf("URL: %s\n", url) return errors.New("status code was not 200") +======= + return fmt.Errorf("common.SendHTTPGetRequest() error: HTTP status code %d", res.StatusCode) +>>>>>>> In the common package added JSONDecode error check. Added verbosity in SendHTTPGetRequest. Updated Nonce package function. Fixed issues in ItBit package and expanded test coverage. } contents, err := ioutil.ReadAll(res.Body) @@ -311,16 +320,18 @@ func SendHTTPGetRequest(url string, jsonDecode bool, result interface{}) error { return err } + if isVerbose { + log.Println("Raw Resp: ", string(contents[:])) + } + defer res.Body.Close() if jsonDecode { - err := JSONDecode(contents, &result) + err := JSONDecode(contents, result) if err != nil { log.Println(string(contents[:])) return err } - } else { - result = &contents } return nil @@ -333,6 +344,9 @@ func JSONEncode(v interface{}) ([]byte, error) { // JSONDecode decodes JSON data into a structure func JSONDecode(data []byte, to interface{}) error { + if !StringContains(reflect.ValueOf(to).Type().String(), "*") { + return errors.New("json decode error - memory address not supplied") + } return json.Unmarshal(data, to) } diff --git a/common/common_test.go b/common/common_test.go index 31b17a0c..583b7025 100644 --- a/common/common_test.go +++ b/common/common_test.go @@ -488,15 +488,15 @@ func TestSendHTTPGetRequest(t *testing.T) { url := `https://etherchain.org/api/account/multiple/0xde0B295669a9FD93d5F28D9Ec85E40f4cb697BAe` result := test{} - err := SendHTTPGetRequest(url, true, &result) + err := SendHTTPGetRequest(url, true, false, &result) if err != nil { t.Errorf("Test failed - common SendHTTPGetRequest error: %s", err) } - err = SendHTTPGetRequest("DINGDONG", true, &result) + err = SendHTTPGetRequest("DINGDONG", true, false, &result) if err == nil { t.Error("Test failed - common SendHTTPGetRequest error") } - err = SendHTTPGetRequest(url, false, &result) + err = SendHTTPGetRequest(url, false, false, &result) if err != nil { t.Error("Test failed - common SendHTTPGetRequest error") } diff --git a/currency/currency.go b/currency/currency.go index 79c05d3b..0201f529 100644 --- a/currency/currency.go +++ b/currency/currency.go @@ -314,7 +314,7 @@ func FetchFixerCurrencyData() error { CurrencyStoreFixer = make(map[string]float64) - err := common.SendHTTPGetRequest(url, true, &result) + err := common.SendHTTPGetRequest(url, true, false, &result) if err != nil { return err } diff --git a/exchanges/anx/anx.go b/exchanges/anx/anx.go index 1843a3c8..b77b9240 100644 --- a/exchanges/anx/anx.go +++ b/exchanges/anx/anx.go @@ -83,7 +83,7 @@ func (a *ANX) GetFee(maker bool) float64 { func (a *ANX) GetTicker(currency string) (ANXTicker, error) { var ticker ANXTicker - err := common.SendHTTPGetRequest(fmt.Sprintf("%sapi/2/%s/%s", ANX_API_URL, currency, ANX_TICKER), true, &ticker) + err := common.SendHTTPGetRequest(fmt.Sprintf("%sapi/2/%s/%s", ANX_API_URL, currency, ANX_TICKER), true, a.Verbose, &ticker) if err != nil { return ANXTicker{}, err } diff --git a/exchanges/bitfinex/bitfinex.go b/exchanges/bitfinex/bitfinex.go index 1c6185a4..b25ad80f 100644 --- a/exchanges/bitfinex/bitfinex.go +++ b/exchanges/bitfinex/bitfinex.go @@ -119,7 +119,7 @@ func (b *Bitfinex) GetTicker(symbol string, values url.Values) (Ticker, error) { response := Ticker{} path := common.EncodeURLValues(bitfinexAPIURL+bitfinexTicker+symbol, values) - return response, common.SendHTTPGetRequest(path, true, &response) + return response, common.SendHTTPGetRequest(path, true, b.Verbose, &response) } // GetStats returns various statistics about the requested pair @@ -127,7 +127,7 @@ func (b *Bitfinex) GetStats(symbol string) ([]Stat, error) { response := []Stat{} path := fmt.Sprint(bitfinexAPIURL + bitfinexStats + symbol) - return response, common.SendHTTPGetRequest(path, true, &response) + return response, common.SendHTTPGetRequest(path, true, b.Verbose, &response) } // GetFundingBook the entire margin funding book for both bids and asks sides @@ -137,7 +137,7 @@ func (b *Bitfinex) GetFundingBook(symbol string) (FundingBook, error) { response := FundingBook{} path := fmt.Sprint(bitfinexAPIURL + bitfinexLendbook + symbol) - return response, common.SendHTTPGetRequest(path, true, &response) + return response, common.SendHTTPGetRequest(path, true, b.Verbose, &response) } // GetOrderbook retieves the entire orderbook bid and ask price on a currency @@ -149,7 +149,7 @@ func (b *Bitfinex) GetOrderbook(currencyPair string, values url.Values) (Orderbo bitfinexAPIURL+bitfinexOrderbook+currencyPair, values, ) - return response, common.SendHTTPGetRequest(path, true, &response) + return response, common.SendHTTPGetRequest(path, true, b.Verbose, &response) } // GetTrades returns a list of the most recent trades for the given curencyPair @@ -160,7 +160,7 @@ func (b *Bitfinex) GetTrades(currencyPair string, values url.Values) ([]TradeStr bitfinexAPIURL+bitfinexTrades+currencyPair, values, ) - return response, common.SendHTTPGetRequest(path, true, &response) + return response, common.SendHTTPGetRequest(path, true, b.Verbose, &response) } // GetLendbook returns a list of the most recent funding data for the given @@ -174,7 +174,7 @@ func (b *Bitfinex) GetLendbook(symbol string, values url.Values) (Lendbook, erro } path := common.EncodeURLValues(bitfinexAPIURL+bitfinexLendbook+symbol, values) - return response, common.SendHTTPGetRequest(path, true, &response) + return response, common.SendHTTPGetRequest(path, true, b.Verbose, &response) } // GetLends returns a list of the most recent funding data for the given @@ -185,7 +185,7 @@ func (b *Bitfinex) GetLends(symbol string, values url.Values) ([]Lends, error) { response := []Lends{} path := common.EncodeURLValues(bitfinexAPIURL+bitfinexLends+symbol, values) - return response, common.SendHTTPGetRequest(path, true, &response) + return response, common.SendHTTPGetRequest(path, true, b.Verbose, &response) } // GetSymbols returns the avaliable currency pairs on the exchange @@ -193,7 +193,7 @@ func (b *Bitfinex) GetSymbols() ([]string, error) { products := []string{} path := fmt.Sprint(bitfinexAPIURL + bitfinexSymbols) - return products, common.SendHTTPGetRequest(path, true, &products) + return products, common.SendHTTPGetRequest(path, true, b.Verbose, &products) } // GetSymbolsDetails a list of valid symbol IDs and the pair details @@ -201,7 +201,7 @@ func (b *Bitfinex) GetSymbolsDetails() ([]SymbolDetails, error) { response := []SymbolDetails{} path := fmt.Sprint(bitfinexAPIURL + bitfinexSymbolsDetails) - return response, common.SendHTTPGetRequest(path, true, &response) + return response, common.SendHTTPGetRequest(path, true, b.Verbose, &response) } // GetAccountInfo returns information about your account incl. trading fees diff --git a/exchanges/bitstamp/bitstamp.go b/exchanges/bitstamp/bitstamp.go index cafffa04..fa385413 100644 --- a/exchanges/bitstamp/bitstamp.go +++ b/exchanges/bitstamp/bitstamp.go @@ -127,7 +127,7 @@ func (b *Bitstamp) GetTicker(currency string, hourly bool) (Ticker, error) { tickerEndpoint, common.StringToLower(currency), ) - return response, common.SendHTTPGetRequest(path, true, &response) + return response, common.SendHTTPGetRequest(path, true, b.Verbose, &response) } // GetOrderbook Returns a JSON dictionary with "bids" and "asks". Each is a list @@ -149,7 +149,7 @@ func (b *Bitstamp) GetOrderbook(currency string) (Orderbook, error) { common.StringToLower(currency), ) - err := common.SendHTTPGetRequest(path, true, &resp) + err := common.SendHTTPGetRequest(path, true, b.Verbose, &resp) if err != nil { return Orderbook{}, err } @@ -204,7 +204,7 @@ func (b *Bitstamp) GetTransactions(currencyPair string, values url.Values) ([]Tr values, ) - return transactions, common.SendHTTPGetRequest(path, true, &transactions) + return transactions, common.SendHTTPGetRequest(path, true, b.Verbose, &transactions) } // GetEURUSDConversionRate returns the conversion rate between Euro and USD @@ -212,7 +212,7 @@ func (b *Bitstamp) GetEURUSDConversionRate() (EURUSDConversionRate, error) { rate := EURUSDConversionRate{} path := fmt.Sprintf("%s/%s", bitstampAPIURL, bitstampAPIEURUSD) - return rate, common.SendHTTPGetRequest(path, true, &rate) + return rate, common.SendHTTPGetRequest(path, true, b.Verbose, &rate) } // GetBalance returns full balance of currency held on the exchange diff --git a/exchanges/bittrex/bittrex.go b/exchanges/bittrex/bittrex.go index ab5791fb..13c878c5 100644 --- a/exchanges/bittrex/bittrex.go +++ b/exchanges/bittrex/bittrex.go @@ -373,7 +373,7 @@ func (b *Bittrex) HTTPRequest(path string, auth bool, values url.Values, v inter return err } } else { - if err := common.SendHTTPGetRequest(path, true, &response); err != nil { + if err := common.SendHTTPGetRequest(path, true, b.Verbose, &response); err != nil { return err } } diff --git a/exchanges/btcc/btcc.go b/exchanges/btcc/btcc.go index 639499e9..dd5b12d4 100644 --- a/exchanges/btcc/btcc.go +++ b/exchanges/btcc/btcc.go @@ -99,7 +99,7 @@ func (b *BTCC) GetTicker(currencyPair string) (Ticker, error) { resp := Response{} req := fmt.Sprintf("%sdata/ticker?market=%s", btccAPIUrl, currencyPair) - return resp.Ticker, common.SendHTTPGetRequest(req, true, &resp) + return resp.Ticker, common.SendHTTPGetRequest(req, true, b.Verbose, &resp) } // GetTradesLast24h returns the trades executed on the exchange over the past @@ -109,7 +109,7 @@ func (b *BTCC) GetTradesLast24h(currencyPair string) ([]Trade, error) { trades := []Trade{} req := fmt.Sprintf("%sdata/trades?market=%s", btccAPIUrl, currencyPair) - return trades, common.SendHTTPGetRequest(req, true, &trades) + return trades, common.SendHTTPGetRequest(req, true, b.Verbose, &trades) } // GetTradeHistory returns trade history data @@ -136,7 +136,7 @@ func (b *BTCC) GetTradeHistory(currencyPair string, limit, sinceTid int64, time req = common.EncodeURLValues(req, v) - return trades, common.SendHTTPGetRequest(req, true, &trades) + return trades, common.SendHTTPGetRequest(req, true, b.Verbose, &trades) } // GetOrderBook returns current market order book @@ -151,7 +151,7 @@ func (b *BTCC) GetOrderBook(currencyPair string, limit int) (Orderbook, error) { req = fmt.Sprintf("%sdata/orderbook?market=%s", btccAPIUrl, currencyPair) } - return result, common.SendHTTPGetRequest(req, true, &result) + return result, common.SendHTTPGetRequest(req, true, b.Verbose, &result) } func (b *BTCC) GetAccountInfo(infoType string) error { diff --git a/exchanges/btcmarkets/btcmarkets.go b/exchanges/btcmarkets/btcmarkets.go index 4668fedd..1f6bc8e8 100644 --- a/exchanges/btcmarkets/btcmarkets.go +++ b/exchanges/btcmarkets/btcmarkets.go @@ -97,7 +97,7 @@ func (b *BTCMarkets) GetTicker(symbol string) (Ticker, error) { path := fmt.Sprintf("/market/%s/AUD/tick", common.StringToUpper(symbol)) return ticker, - common.SendHTTPGetRequest(btcMarketsAPIURL+path, true, &ticker) + common.SendHTTPGetRequest(btcMarketsAPIURL+path, true, b.Verbose, &ticker) } // GetOrderbook returns current orderbook @@ -107,7 +107,7 @@ func (b *BTCMarkets) GetOrderbook(symbol string) (Orderbook, error) { path := fmt.Sprintf("/market/%s/AUD/orderbook", common.StringToUpper(symbol)) return orderbook, - common.SendHTTPGetRequest(btcMarketsAPIURL+path, true, &orderbook) + common.SendHTTPGetRequest(btcMarketsAPIURL+path, true, b.Verbose, &orderbook) } // GetTrades returns executed trades on the exchange @@ -117,7 +117,7 @@ func (b *BTCMarkets) GetTrades(symbol string, values url.Values) ([]Trade, error trades := []Trade{} path := common.EncodeURLValues(fmt.Sprintf("%s/market/%s/AUD/trades", btcMarketsAPIURL, symbol), values) - return trades, common.SendHTTPGetRequest(path, true, &trades) + return trades, common.SendHTTPGetRequest(path, true, b.Verbose, &trades) } // NewOrder requests a new order and returns an ID diff --git a/exchanges/gdax/gdax.go b/exchanges/gdax/gdax.go index f135b3e6..0749ade9 100644 --- a/exchanges/gdax/gdax.go +++ b/exchanges/gdax/gdax.go @@ -110,7 +110,7 @@ func (g *GDAX) GetProducts() ([]Product, error) { products := []Product{} return products, - common.SendHTTPGetRequest(gdaxAPIURL+gdaxProducts, true, &products) + common.SendHTTPGetRequest(gdaxAPIURL+gdaxProducts, true, g.Verbose, &products) } // GetOrderbook returns orderbook by currency pair and level @@ -123,7 +123,7 @@ func (g *GDAX) GetOrderbook(symbol string, level int) (interface{}, error) { path = fmt.Sprintf("%s/%s/%s?level=%s", gdaxAPIURL+gdaxProducts, symbol, gdaxOrderbook, levelStr) } - if err := common.SendHTTPGetRequest(path, true, &orderbook); err != nil { + if err := common.SendHTTPGetRequest(path, true, g.Verbose, &orderbook); err != nil { return nil, err } @@ -193,7 +193,7 @@ func (g *GDAX) GetTicker(currencyPair string) (Ticker, error) { "%s/%s/%s", gdaxAPIURL+gdaxProducts, currencyPair, gdaxTicker) log.Println(path) - return ticker, common.SendHTTPGetRequest(path, true, &ticker) + return ticker, common.SendHTTPGetRequest(path, true, g.Verbose, &ticker) } // GetTrades listd the latest trades for a product @@ -203,7 +203,7 @@ func (g *GDAX) GetTrades(currencyPair string) ([]Trade, error) { path := fmt.Sprintf( "%s/%s/%s", gdaxAPIURL+gdaxProducts, currencyPair, gdaxTrades) - return trades, common.SendHTTPGetRequest(path, true, &trades) + return trades, common.SendHTTPGetRequest(path, true, g.Verbose, &trades) } // GetHistoricRates returns historic rates for a product. Rates are returned in @@ -229,7 +229,7 @@ func (g *GDAX) GetHistoricRates(currencyPair string, start, end, granularity int fmt.Sprintf("%s/%s/%s", gdaxAPIURL+gdaxProducts, currencyPair, gdaxHistory), values) - if err := common.SendHTTPGetRequest(path, true, &resp); err != nil { + if err := common.SendHTTPGetRequest(path, true, g.Verbose, &resp); err != nil { return history, err } @@ -255,7 +255,7 @@ func (g *GDAX) GetStats(currencyPair string) (Stats, error) { path := fmt.Sprintf( "%s/%s/%s", gdaxAPIURL+gdaxProducts, currencyPair, gdaxStats) - return stats, common.SendHTTPGetRequest(path, true, &stats) + return stats, common.SendHTTPGetRequest(path, true, g.Verbose, &stats) } // GetCurrencies returns a list of supported currency on the exchange @@ -264,7 +264,7 @@ func (g *GDAX) GetCurrencies() ([]Currency, error) { currencies := []Currency{} return currencies, - common.SendHTTPGetRequest(gdaxAPIURL+gdaxCurrencies, true, ¤cies) + common.SendHTTPGetRequest(gdaxAPIURL+gdaxCurrencies, true, g.Verbose, ¤cies) } // GetServerTime returns the API server time @@ -272,7 +272,7 @@ func (g *GDAX) GetServerTime() (ServerTime, error) { serverTime := ServerTime{} return serverTime, - common.SendHTTPGetRequest(gdaxAPIURL+gdaxTime, true, &serverTime) + common.SendHTTPGetRequest(gdaxAPIURL+gdaxTime, true, g.Verbose, &serverTime) } // GetAccounts returns a list of trading accounts associated with the APIKEYS @@ -772,7 +772,7 @@ func (g *GDAX) SendAuthenticatedHTTPRequest(method, path string, params map[stri } } - nonce := g.Nonce.Evaluate() + nonce := g.Nonce.GetValue(g.Name, false).String() message := nonce + method + "/" + path + string(payload) hmac := common.GetHMAC(common.HashSHA256, []byte(message), []byte(g.APISecret)) headers := make(map[string]string) diff --git a/exchanges/gemini/gemini.go b/exchanges/gemini/gemini.go index 7f78eac6..e83585f5 100644 --- a/exchanges/gemini/gemini.go +++ b/exchanges/gemini/gemini.go @@ -160,7 +160,7 @@ func (g *Gemini) GetSymbols() ([]string, error) { symbols := []string{} path := fmt.Sprintf("%s/v%s/%s", geminiAPIURL, geminiAPIVersion, geminiSymbols) - return symbols, common.SendHTTPGetRequest(path, true, &symbols) + return symbols, common.SendHTTPGetRequest(path, true, g.Verbose, &symbols) } // GetTicker returns information about recent trading activity for the symbol @@ -177,7 +177,7 @@ func (g *Gemini) GetTicker(currencyPair string) (Ticker, error) { resp := TickerResponse{} path := fmt.Sprintf("%s/v%s/%s/%s", geminiAPIURL, geminiAPIVersion, geminiTicker, currencyPair) - err := common.SendHTTPGetRequest(path, true, &resp) + err := common.SendHTTPGetRequest(path, true, g.Verbose, &resp) if err != nil { return ticker, err } @@ -204,7 +204,7 @@ func (g *Gemini) GetOrderbook(currencyPair string, params url.Values) (Orderbook path := common.EncodeURLValues(fmt.Sprintf("%s/v%s/%s/%s", geminiAPIURL, geminiAPIVersion, geminiOrderbook, currencyPair), params) orderbook := Orderbook{} - return orderbook, common.SendHTTPGetRequest(path, true, &orderbook) + return orderbook, common.SendHTTPGetRequest(path, true, g.Verbose, &orderbook) } // GetTrades eturn the trades that have executed since the specified timestamp. @@ -220,7 +220,7 @@ func (g *Gemini) GetTrades(currencyPair string, params url.Values) ([]Trade, err path := common.EncodeURLValues(fmt.Sprintf("%s/v%s/%s/%s", geminiAPIURL, geminiAPIVersion, geminiTrades, currencyPair), params) trades := []Trade{} - return trades, common.SendHTTPGetRequest(path, true, &trades) + return trades, common.SendHTTPGetRequest(path, true, g.Verbose, &trades) } // GetAuction returns auction infomation @@ -228,7 +228,7 @@ func (g *Gemini) GetAuction(currencyPair string) (Auction, error) { path := fmt.Sprintf("%s/v%s/%s/%s", geminiAPIURL, geminiAPIVersion, geminiAuction, currencyPair) auction := Auction{} - return auction, common.SendHTTPGetRequest(path, true, &auction) + return auction, common.SendHTTPGetRequest(path, true, g.Verbose, &auction) } // GetAuctionHistory returns the auction events, optionally including @@ -246,7 +246,7 @@ func (g *Gemini) GetAuctionHistory(currencyPair string, params url.Values) ([]Au path := common.EncodeURLValues(fmt.Sprintf("%s/v%s/%s/%s/%s", geminiAPIURL, geminiAPIVersion, geminiAuction, currencyPair, geminiAuctionHistory), params) auctionHist := []AuctionHistory{} - return auctionHist, common.SendHTTPGetRequest(path, true, &auctionHist) + return auctionHist, common.SendHTTPGetRequest(path, true, g.Verbose, &auctionHist) } func (g *Gemini) isCorrectSession(role string) error { diff --git a/exchanges/huobi/huobi.go b/exchanges/huobi/huobi.go index f03517c4..9d31a34a 100644 --- a/exchanges/huobi/huobi.go +++ b/exchanges/huobi/huobi.go @@ -68,7 +68,7 @@ func (h *HUOBI) GetFee() float64 { func (h *HUOBI) GetTicker(symbol string) (HuobiTicker, error) { resp := HuobiTickerResponse{} path := fmt.Sprintf("https://api.huobi.com/staticmarket/ticker_%s_json.js", symbol) - err := common.SendHTTPGetRequest(path, true, &resp) + err := common.SendHTTPGetRequest(path, true, h.Verbose, &resp) if err != nil { return HuobiTicker{}, err @@ -79,7 +79,7 @@ func (h *HUOBI) GetTicker(symbol string) (HuobiTicker, error) { func (h *HUOBI) GetOrderBook(symbol string) (HuobiOrderbook, error) { path := fmt.Sprintf("https://api.huobi.com/staticmarket/depth_%s_json.js", symbol) resp := HuobiOrderbook{} - err := common.SendHTTPGetRequest(path, true, &resp) + err := common.SendHTTPGetRequest(path, true, h.Verbose, &resp) if err != nil { return resp, err } diff --git a/exchanges/itbit/itbit.go b/exchanges/itbit/itbit.go index 80aec2b0..49fc986a 100644 --- a/exchanges/itbit/itbit.go +++ b/exchanges/itbit/itbit.go @@ -16,14 +16,26 @@ import ( ) const ( - ITBIT_API_URL = "https://api.itbit.com/v1" - ITBIT_API_VERSION = "1" + itbitAPIURL = "https://api.itbit.com/v1" + itbitAPIVersion = "1" + itbitMarkets = "markets" + itbitOrderbook = "order_book" + itbitTicker = "ticker" + itbitWallets = "wallets" + itbitBalances = "balances" + itbitTrades = "trades" + itbitFundingHistory = "funding_history" + itbitOrders = "orders" + itbitCryptoDeposits = "cryptocurrency_deposits" + itbitWalletTransfer = "wallet_transfers" ) +// ItBit is the overarching type across the ItBit package type ItBit struct { exchange.Base } +// SetDefaults sets the defaults for the exchange func (i *ItBit) SetDefaults() { i.Name = "ITBIT" i.Enabled = false @@ -39,6 +51,7 @@ func (i *ItBit) SetDefaults() { i.AssetTypes = []string{ticker.Spot} } +// Setup sets the exchange paramaters from exchange config func (i *ItBit) Setup(exch config.ExchangeConfig) { if !exch.Enabled { i.SetEnabled(false) @@ -63,6 +76,7 @@ func (i *ItBit) Setup(exch config.ExchangeConfig) { } } +// GetFee returns the maker or taker fee func (i *ItBit) GetFee(maker bool) float64 { if maker { return i.MakerFee @@ -70,98 +84,103 @@ func (i *ItBit) GetFee(maker bool) float64 { return i.TakerFee } -func (i *ItBit) GetTicker(currency string) (Ticker, error) { - path := ITBIT_API_URL + "/markets/" + currency + "/ticker" - var itbitTicker Ticker - err := common.SendHTTPGetRequest(path, true, &itbitTicker) - if err != nil { - return Ticker{}, err - } - return itbitTicker, nil +// GetTicker returns ticker info for a specified market. +// currencyPair - example "XBTUSD" "XBTSGD" "XBTEUR" +func (i *ItBit) GetTicker(currencyPair string) (Ticker, error) { + var response Ticker + path := fmt.Sprintf("%s/%s/%s/%s", itbitAPIURL, itbitMarkets, currencyPair, itbitTicker) + + return response, + common.SendHTTPGetRequest(path, true, i.Verbose, &response) } -func (i *ItBit) GetOrderbook(currency string) (OrderbookResponse, error) { +// GetOrderbook returns full order book for the specified market. +// currencyPair - example "XBTUSD" "XBTSGD" "XBTEUR" +func (i *ItBit) GetOrderbook(currencyPair string) (OrderbookResponse, error) { response := OrderbookResponse{} - path := ITBIT_API_URL + "/markets/" + currency + "/order_book" - err := common.SendHTTPGetRequest(path, true, &response) - if err != nil { - return OrderbookResponse{}, err - } - return response, nil + path := fmt.Sprintf("%s/%s/%s/%s", itbitAPIURL, itbitMarkets, currencyPair, itbitOrderbook) + + return response, + common.SendHTTPGetRequest(path, true, i.Verbose, &response) } -func (i *ItBit) GetTradeHistory(currency, timestamp string) bool { - req := "/trades?since=" + timestamp - err := common.SendHTTPGetRequest(ITBIT_API_URL+"markets/"+currency+req, true, nil) - if err != nil { - log.Println(err) - return false - } - return true +// GetTradeHistory returns recent trades for a specified market. +// +// currencyPair - example "XBTUSD" "XBTSGD" "XBTEUR" +// timestamp - matchNumber, only executions after this will be returned +func (i *ItBit) GetTradeHistory(currencyPair, timestamp string) (Trades, error) { + response := Trades{} + req := "trades?since=" + timestamp + path := fmt.Sprintf("%s/%s/%s/%s", itbitAPIURL, itbitMarkets, currencyPair, req) + + return response, + common.SendHTTPGetRequest(path, true, i.Verbose, &response) } -func (i *ItBit) GetWallets(params url.Values) { +// GetWallets returns information about all wallets associated with the account. +// +// params -- +// page - [optional] page to return example 1. default 1 +// perPage - [optional] items per page example 50, default 50 max 50 +func (i *ItBit) GetWallets(params url.Values) ([]Wallet, error) { + resp := []Wallet{} params.Set("userId", i.ClientID) - path := "/wallets?" + params.Encode() + path := fmt.Sprintf("/%s?%s", itbitWallets, params.Encode()) - err := i.SendAuthenticatedHTTPRequest("GET", path, nil) - - if err != nil { - log.Println(err) - } + return resp, i.SendAuthenticatedHTTPRequest("GET", path, nil, &resp) } -func (i *ItBit) CreateWallet(walletName string) { - path := "/wallets" +// CreateWallet creates a new wallet with a specified name. +func (i *ItBit) CreateWallet(walletName string) (Wallet, error) { + resp := Wallet{} params := make(map[string]interface{}) params["userId"] = i.ClientID params["name"] = walletName - err := i.SendAuthenticatedHTTPRequest("POST", path, params) - - if err != nil { - log.Println(err) - } + return resp, + i.SendAuthenticatedHTTPRequest("POST", "/"+itbitWallets, params, &resp) } -func (i *ItBit) GetWallet(walletID string) { - path := "/wallets/" + walletID - err := i.SendAuthenticatedHTTPRequest("GET", path, nil) +// GetWallet returns wallet information by walletID +func (i *ItBit) GetWallet(walletID string) (Wallet, error) { + resp := Wallet{} + path := fmt.Sprintf("/%s/%s", itbitWallets, walletID) - if err != nil { - log.Println(err) - } + return resp, i.SendAuthenticatedHTTPRequest("GET", path, nil, &resp) } -func (i *ItBit) GetWalletBalance(walletID, currency string) { - path := "/wallets/ " + walletID + "/balances/" + currency - err := i.SendAuthenticatedHTTPRequest("GET", path, nil) +// GetWalletBalance returns balance information for a specific currency in a +// wallet. +func (i *ItBit) GetWalletBalance(walletID, currency string) (Balance, error) { + resp := Balance{} + path := fmt.Sprintf("/%s/%s/%s/%s", itbitWallets, walletID, itbitBalances, currency) - if err != nil { - log.Println(err) - } + return resp, i.SendAuthenticatedHTTPRequest("GET", path, nil, &resp) } -func (i *ItBit) GetWalletTrades(walletID string, params url.Values) { - path := common.EncodeURLValues("/wallets/"+walletID+"/trades", params) - err := i.SendAuthenticatedHTTPRequest("GET", path, nil) +// GetWalletTrades returns all trades for a specified wallet. +func (i *ItBit) GetWalletTrades(walletID string, params url.Values) (Records, error) { + resp := Records{} + url := fmt.Sprintf("/%s/%s/%s", itbitWallets, walletID, itbitTrades) + path := common.EncodeURLValues(url, params) - if err != nil { - log.Println(err) - } + return resp, i.SendAuthenticatedHTTPRequest("GET", path, nil, &resp) } -func (i *ItBit) GetWalletOrders(walletID string, params url.Values) { - path := common.EncodeURLValues("/wallets/"+walletID+"/orders", params) - err := i.SendAuthenticatedHTTPRequest("GET", path, nil) +// GetFundingHistory returns all funding history for a specified wallet. +func (i *ItBit) GetFundingHistory(walletID string, params url.Values) (FundingRecords, error) { + resp := FundingRecords{} + url := fmt.Sprintf("/%s/%s/%s", itbitWallets, walletID, itbitFundingHistory) + path := common.EncodeURLValues(url, params) - if err != nil { - log.Println(err) - } + return resp, i.SendAuthenticatedHTTPRequest("GET", path, nil, &resp) } -func (i *ItBit) PlaceWalletOrder(walletID, side, orderType, currency string, amount, price float64, instrument string, clientRef string) { - path := "/wallets/" + walletID + "/orders" +// PlaceOrder places a new order +func (i *ItBit) PlaceOrder(walletID, side, orderType, currency string, amount, price float64, instrument, clientRef string) (Order, error) { + resp := Order{} + path := fmt.Sprintf("/%s/%s/%s", itbitWallets, walletID, itbitOrders) + params := make(map[string]interface{}) params["side"] = side params["type"] = orderType @@ -174,85 +193,58 @@ func (i *ItBit) PlaceWalletOrder(walletID, side, orderType, currency string, amo params["clientOrderIdentifier"] = clientRef } - err := i.SendAuthenticatedHTTPRequest("POST", path, params) - - if err != nil { - log.Println(err) - } + return resp, i.SendAuthenticatedHTTPRequest("POST", path, params, &resp) } -func (i *ItBit) GetWalletOrder(walletID, orderID string) { - path := "/wallets/" + walletID + "/orders/" + orderID - err := i.SendAuthenticatedHTTPRequest("GET", path, nil) +// GetOrder returns an order by id. +func (i *ItBit) GetOrder(walletID string, params url.Values) (Order, error) { + resp := Order{} + url := fmt.Sprintf("/%s/%s/%s", itbitWallets, walletID, itbitOrders) + path := common.EncodeURLValues(url, params) - if err != nil { - log.Println(err) - } + return resp, i.SendAuthenticatedHTTPRequest("GET", path, nil, &resp) } -func (i *ItBit) CancelWalletOrder(walletID, orderID string) { - path := "/wallets/" + walletID + "/orders/" + orderID - err := i.SendAuthenticatedHTTPRequest("DELETE", path, nil) +// CancelOrder cancels and open order. *This is not a guarantee that the order +// has been cancelled!* +func (i *ItBit) CancelOrder(walletID, orderID string) error { + path := fmt.Sprintf("/%s/%s/%s/%s", itbitWallets, walletID, itbitOrders, orderID) - if err != nil { - log.Println(err) - } + return i.SendAuthenticatedHTTPRequest("DELETE", path, nil, nil) } -func (i *ItBit) PlaceWithdrawalRequest(walletID, currency, address string, amount float64) { - path := "/wallets/" + walletID + "/cryptocurrency_withdrawals" - params := make(map[string]interface{}) - params["currency"] = currency - params["amount"] = amount - params["address"] = address - - err := i.SendAuthenticatedHTTPRequest("POST", path, params) - - if err != nil { - log.Println(err) - } -} - -func (i *ItBit) GetDepositAddress(walletID, currency string) { - path := "/wallets/" + walletID + "/cryptocurrency_deposits" +// GetDepositAddress returns a deposit address to send cryptocurrency to. +func (i *ItBit) GetDepositAddress(walletID, currency string) (CryptoCurrencyDeposit, error) { + resp := CryptoCurrencyDeposit{} + path := fmt.Sprintf("/%s/%s/%s", itbitWallets, walletID, itbitCryptoDeposits) params := make(map[string]interface{}) params["currency"] = currency - err := i.SendAuthenticatedHTTPRequest("POST", path, params) - - if err != nil { - log.Println(err) - } + return resp, i.SendAuthenticatedHTTPRequest("POST", path, params, &resp) } -func (i *ItBit) WalletTransfer(walletID, sourceWallet, destWallet string, amount float64, currency string) { - path := "/wallets/" + walletID + "/wallet_transfers" +// WalletTransfer transfers funds between wallets. +func (i *ItBit) WalletTransfer(walletID, sourceWallet, destWallet string, amount float64, currency string) (WalletTransfer, error) { + resp := WalletTransfer{} + path := fmt.Sprintf("/%s/%s/%s", itbitWallets, walletID, itbitWalletTransfer) + params := make(map[string]interface{}) params["sourceWalletId"] = sourceWallet params["destinationWalletId"] = destWallet params["amount"] = strconv.FormatFloat(amount, 'f', -1, 64) params["currencyCode"] = currency - err := i.SendAuthenticatedHTTPRequest("POST", path, params) - - if err != nil { - log.Println(err) - } + return resp, i.SendAuthenticatedHTTPRequest("POST", path, params, &resp) } -func (i *ItBit) SendAuthenticatedHTTPRequest(method string, path string, params map[string]interface{}) (err error) { +// SendAuthenticatedHTTPRequest sends an authenticated request to itBit +func (i *ItBit) SendAuthenticatedHTTPRequest(method string, path string, params map[string]interface{}, result interface{}) error { if !i.AuthenticatedAPISupport { return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, i.Name) } - if i.Nonce.Get() == 0 { - i.Nonce.Set(time.Now().UnixNano()) - } else { - i.Nonce.Inc() - } - request := make(map[string]interface{}) - url := ITBIT_API_URL + path + url := itbitAPIURL + path if params != nil { for key, value := range params { @@ -261,12 +253,13 @@ func (i *ItBit) SendAuthenticatedHTTPRequest(method string, path string, params } PayloadJSON := []byte("") + var err error if params != nil { - PayloadJSON, err = common.JSONEncode(request) + PayloadJSON, err = common.JSONEncode(request) if err != nil { - return errors.New("SendAuthenticatedHTTPRequest: Unable to JSON Marshal request") + return err } if i.Verbose { @@ -274,26 +267,37 @@ func (i *ItBit) SendAuthenticatedHTTPRequest(method string, path string, params } } - message, err := common.JSONEncode([]string{method, url, string(PayloadJSON), i.Nonce.String(), i.Nonce.String()[0:13]}) + nonce := i.Nonce.GetValue(i.Name, false).String() + timestamp := strconv.FormatInt(time.Now().UnixNano()/1000000, 10) + + message, err := common.JSONEncode([]string{method, url, string(PayloadJSON), nonce, timestamp}) if err != nil { - log.Println(err) - return + return err } - hash := common.GetSHA256([]byte(i.Nonce.String() + string(message))) + hash := common.GetSHA256([]byte(nonce + string(message))) hmac := common.GetHMAC(common.HashSHA512, []byte(url+string(hash)), []byte(i.APISecret)) signature := common.Base64Encode(hmac) headers := make(map[string]string) headers["Authorization"] = i.ClientID + ":" + signature - headers["X-Auth-Timestamp"] = i.Nonce.String()[0:13] - headers["X-Auth-Nonce"] = i.Nonce.String() + headers["X-Auth-Timestamp"] = timestamp + headers["X-Auth-Nonce"] = nonce headers["Content-Type"] = "application/json" resp, err := common.SendHTTPRequest(method, url, headers, bytes.NewBuffer([]byte(PayloadJSON))) + if err != nil { + return err + } if i.Verbose { log.Printf("Received raw: \n%s\n", resp) } - return nil + + errCapture := GeneralReturn{} + if err := common.JSONDecode([]byte(resp), &errCapture); err == nil { + return errors.New(errCapture.Description) + } + + return common.JSONDecode([]byte(resp), result) } diff --git a/exchanges/itbit/itbit_test.go b/exchanges/itbit/itbit_test.go new file mode 100644 index 00000000..88c51689 --- /dev/null +++ b/exchanges/itbit/itbit_test.go @@ -0,0 +1,145 @@ +package itbit + +import ( + "net/url" + "testing" + + "github.com/thrasher-/gocryptotrader/config" +) + +var i ItBit + +// Please provide your own keys to do proper testing +const ( + apiKey = "" + apiSecret = "" + clientID = "" +) + +func TestSetDefaults(t *testing.T) { + i.SetDefaults() +} + +func TestSetup(t *testing.T) { + cfg := config.GetConfig() + cfg.LoadConfig("../../testdata/configtest.dat") + itbitConfig, err := cfg.GetExchangeConfig("ITBIT") + if err != nil { + t.Error("Test Failed - Gemini Setup() init error") + } + + itbitConfig.AuthenticatedAPISupport = true + itbitConfig.APIKey = apiKey + itbitConfig.APISecret = apiSecret + itbitConfig.ClientID = clientID + + i.Setup(itbitConfig) +} + +func TestGetFee(t *testing.T) { + t.Parallel() + if i.GetFee(true) != -0.1 || i.GetFee(false) != 0.5 { + t.Error("Test Failed - GetFee() error") + } +} + +func TestGetTicker(t *testing.T) { + t.Parallel() + _, err := i.GetTicker("XBTUSD") + if err != nil { + t.Error("Test Failed - GetTicker() error", err) + } +} + +func TestGetOrderbook(t *testing.T) { + t.Parallel() + _, err := i.GetOrderbook("XBTSGD") + if err != nil { + t.Error("Test Failed - GetOrderbook() error", err) + } +} + +func TestGetTradeHistory(t *testing.T) { + t.Parallel() + _, err := i.GetTradeHistory("XBTUSD", "0") + if err != nil { + t.Error("Test Failed - GetTradeHistory() error", err) + } +} + +func TestGetWallets(t *testing.T) { + _, err := i.GetWallets(url.Values{}) + if err == nil { + t.Error("Test Failed - GetWallets() error", err) + } +} + +func TestCreateWallet(t *testing.T) { + _, err := i.CreateWallet("test") + if err == nil { + t.Error("Test Failed - CreateWallet() error", err) + } +} + +func TestGetWallet(t *testing.T) { + _, err := i.GetWallet("1337") + if err == nil { + t.Error("Test Failed - GetWallet() error", err) + } +} + +func TestGetWalletBalance(t *testing.T) { + _, err := i.GetWalletBalance("1337", "XRT") + if err == nil { + t.Error("Test Failed - GetWalletBalance() error", err) + } +} + +func TestGetWalletTrades(t *testing.T) { + _, err := i.GetWalletTrades("1337", url.Values{}) + if err == nil { + t.Error("Test Failed - GetWalletTrades() error", err) + } +} + +func TestGetFundingHistory(t *testing.T) { + _, err := i.GetFundingHistory("1337", url.Values{}) + if err == nil { + t.Error("Test Failed - GetFundingHistory() error", err) + } +} + +func TestPlaceOrder(t *testing.T) { + _, err := i.PlaceOrder("1337", "buy", "limit", "USD", 1, 0.2, "banjo", "sauce") + if err == nil { + t.Error("Test Failed - PlaceOrder() error", err) + } +} + +func TestGetOrder(t *testing.T) { + _, err := i.GetOrder("1337", url.Values{}) + if err == nil { + t.Error("Test Failed - GetOrder() error", err) + } +} + +func TestCancelOrder(t *testing.T) { + err := i.CancelOrder("1337", "1337order") + if err == nil { + t.Error("Test Failed - CancelOrder() error", err) + } +} + +func TestGetDepositAddress(t *testing.T) { + _, err := i.GetDepositAddress("1337", "AUD") + if err == nil { + t.Error("Test Failed - GetDepositAddress() error", err) + } +} + +func TestWalletTransfer(t *testing.T) { + _, err := i.WalletTransfer("1337", "mywallet", "anotherwallet", 200, "USD") + if err == nil { + t.Error("Test Failed - WalletTransfer() error", err) + } +} diff --git a/exchanges/itbit/itbit_types.go b/exchanges/itbit/itbit_types.go index 9613404d..8adce526 100644 --- a/exchanges/itbit/itbit_types.go +++ b/exchanges/itbit/itbit_types.go @@ -1,26 +1,145 @@ package itbit -type Ticker struct { - Pair string - Bid float64 `json:",string"` - BidAmt float64 `json:",string"` - Ask float64 `json:",string"` - AskAmt float64 `json:",string"` - LastPrice float64 `json:",string"` - LastAmt float64 `json:",string"` - Volume24h float64 `json:",string"` - VolumeToday float64 `json:",string"` - High24h float64 `json:",string"` - Low24h float64 `json:",string"` - HighToday float64 `json:",string"` - LowToday float64 `json:",string"` - OpenToday float64 `json:",string"` - VwapToday float64 `json:",string"` - Vwap24h float64 `json:",string"` - ServertimeUTC string +// GeneralReturn is a generalized return type to capture any errors +type GeneralReturn struct { + Code int `json:"code"` + Description string `json:"description"` + RequestID string `json:"requestId"` } +// Ticker holds returned ticker information +type Ticker struct { + Pair string `json:"pair"` + Bid float64 `json:"bid,string"` + BidAmt float64 `json:"bidAmt,string"` + Ask float64 `json:"ask,string"` + AskAmt float64 `json:"askAmt,string"` + LastPrice float64 `json:"lastPrice,string"` + LastAmt float64 `json:"lastAmt,string"` + Volume24h float64 `json:"volume24h,string"` + VolumeToday float64 `json:"volumeToday,string"` + High24h float64 `json:"high24h,string"` + Low24h float64 `json:"low24h,string"` + HighToday float64 `json:"highToday,string"` + LowToday float64 `json:"lowToday,string"` + OpenToday float64 `json:"openToday,string"` + VwapToday float64 `json:"vwapToday,string"` + Vwap24h float64 `json:"vwap24h,string"` + ServertimeUTC string `json:"serverTimeUTC"` +} + +// OrderbookResponse contains multi-arrayed strings of bid and ask side +// information type OrderbookResponse struct { Bids [][]string `json:"bids"` Asks [][]string `json:"asks"` } + +// Trades holds recent trades with associated information +type Trades struct { + RecentTrades []struct { + Timestamp string `json:"timestamp"` + MatchNumber int64 `json:"matchNumber"` + Price float64 `json:"price,string"` + Amount float64 `json:"amount,string"` + } `json:"recentTrades"` +} + +// Wallet contains specific wallet information +type Wallet struct { + ID string `json:"id"` + UserID string `json:"userId"` + Name string `json:"name"` + Balances []Balance `json:"balances"` +} + +// Balance is a sub type holding balance information +type Balance struct { + Currency string `json:"currency"` + AvailableBalance float64 `json:"availableBalance,string"` + TotalBalance float64 `json:"totalBalance,string"` +} + +// Records embodies records of trade history information +type Records struct { + TotalNumberOfRecords int `json:"totalNumberOfRecords,string"` + CurrentPageNumber int `json:"currentPageNumber,string"` + LatestExecutedID int64 `json:"latestExecutionId,string"` + RecordsPerPage int `json:"recordsPerPage,string"` + TradingHistory []TradeHistory `json:"tradingHistory"` +} + +// TradeHistory stores historic trade values +type TradeHistory struct { + OrderID string `json:"orderId"` + Timestamp string `json:"timestamp"` + Instrument string `json:"instrument"` + Direction string `json:"direction"` + CurrencyOne string `json:"currency1"` + CurrencyOneAmount float64 `json:"currency1Amount,string"` + CurrencyTwo string `json:"currency2"` + CurrencyTwoAmount float64 `json:"currency2Amount"` + Rate float64 `json:"rate,string"` + CommissionPaid float64 `json:"commissionPaid,string"` + CommissionCurrency string `json:"commissionCurrency"` + RebatesApplied float64 `json:"rebatesApplied,string"` + RebateCurrency string `json:"rebateCurrency"` +} + +// FundingRecords embodies records of fund history information +type FundingRecords struct { + TotalNumberOfRecords int `json:"totalNumberOfRecords,string"` + CurrentPageNumber int `json:"currentPageNumber,string"` + LatestExecutedID int64 `json:"latestExecutionId,string"` + RecordsPerPage int `json:"recordsPerPage,string"` + FundingHistory []FundHistory `json:"fundingHistory"` +} + +// FundHistory stores historic funding transactions +type FundHistory struct { + BankName string `json:"bankName"` + WithdrawalID int64 `json:"withdrawalId"` + HoldingPeriodCompletionDate string `json:"holdingPeriodCompletionDate"` + DestinationAddress string `json:"destinationAddress"` + TxnHash string `json:"txnHash"` + Time string `json:"time"` + Currency string `json:"currency"` + TransactionType string `json:"transactionType"` + Amount float64 `json:"amount,string"` + WalletName string `json:"walletName"` + Status string `json:"status"` +} + +// Order holds order information +type Order struct { + ID string `json:"id"` + WalletID string `json:"walletId"` + Side string `json:"side"` + Instrument string `json:"instrument"` + Type string `json:"type"` + Currency string `json:"currency"` + Amount float64 `json:"amount,string"` + Price float64 `json:"price,string"` + AmountFilled float64 `json:"amountFilled,string"` + VolumeWeightedAveragePrice float64 `json:"volumeWeightedAveragePrice,string"` + CreatedTime string `json:"createdTime"` + Status string `json:"Status"` + Metadata interface{} `json:"metadata"` + ClientOrderIdentifier string `json:"clientOrderIdentifier"` +} + +// CryptoCurrencyDeposit holds information about a new wallet +type CryptoCurrencyDeposit struct { + ID int `json:"id"` + WalletID string `json:"walletID"` + DepositAddress string `json:"depositAddress"` + Metadata interface{} `json:"metadata"` +} + +// WalletTransfer holds wallet transfer information +type WalletTransfer struct { + SourceWalletID string `json:"sourceWalletId"` + DestinationWalletID string `json:"destinationWalletId"` + Amount float64 `json:"amount,string"` + CurrencyCode string `json:"currencyCode"` +} diff --git a/exchanges/kraken/kraken.go b/exchanges/kraken/kraken.go index cd264052..418881ef 100644 --- a/exchanges/kraken/kraken.go +++ b/exchanges/kraken/kraken.go @@ -99,7 +99,7 @@ func (k *Kraken) GetFee(cryptoTrade bool) float64 { func (k *Kraken) GetServerTime() error { var result interface{} path := fmt.Sprintf("%s/%s/public/%s", KRAKEN_API_URL, KRAKEN_API_VERSION, KRAKEN_SERVER_TIME) - err := common.SendHTTPGetRequest(path, true, &result) + err := common.SendHTTPGetRequest(path, true, k.Verbose, &result) if err != nil { return err @@ -112,7 +112,7 @@ func (k *Kraken) GetServerTime() error { func (k *Kraken) GetAssets() error { var result interface{} path := fmt.Sprintf("%s/%s/public/%s", KRAKEN_API_URL, KRAKEN_API_VERSION, KRAKEN_ASSETS) - err := common.SendHTTPGetRequest(path, true, &result) + err := common.SendHTTPGetRequest(path, true, k.Verbose, &result) if err != nil { return err @@ -130,7 +130,7 @@ func (k *Kraken) GetAssetPairs() (map[string]KrakenAssetPairs, error) { response := Response{} path := fmt.Sprintf("%s/%s/public/%s", KRAKEN_API_URL, KRAKEN_API_VERSION, KRAKEN_ASSET_PAIRS) - err := common.SendHTTPGetRequest(path, true, &response) + err := common.SendHTTPGetRequest(path, true, k.Verbose, &response) if err != nil { return nil, err @@ -150,7 +150,7 @@ func (k *Kraken) GetTicker(symbol string) error { resp := Response{} path := fmt.Sprintf("%s/%s/public/%s?%s", KRAKEN_API_URL, KRAKEN_API_VERSION, KRAKEN_TICKER, values.Encode()) - err := common.SendHTTPGetRequest(path, true, &resp) + err := common.SendHTTPGetRequest(path, true, k.Verbose, &resp) if err != nil { return err @@ -183,7 +183,7 @@ func (k *Kraken) GetOHLC(symbol string) error { var result interface{} path := fmt.Sprintf("%s/%s/public/%s?%s", KRAKEN_API_URL, KRAKEN_API_VERSION, KRAKEN_OHLC, values.Encode()) - err := common.SendHTTPGetRequest(path, true, &result) + err := common.SendHTTPGetRequest(path, true, k.Verbose, &result) if err != nil { return err @@ -201,7 +201,7 @@ func (k *Kraken) GetDepth(symbol string) (Orderbook, error) { var result interface{} var ob Orderbook path := fmt.Sprintf("%s/%s/public/%s?%s", KRAKEN_API_URL, KRAKEN_API_VERSION, KRAKEN_DEPTH, values.Encode()) - err := common.SendHTTPGetRequest(path, true, &result) + err := common.SendHTTPGetRequest(path, true, k.Verbose, &result) if err != nil { return ob, err @@ -257,7 +257,7 @@ func (k *Kraken) GetTrades(symbol string) error { var result interface{} path := fmt.Sprintf("%s/%s/public/%s?%s", KRAKEN_API_URL, KRAKEN_API_VERSION, KRAKEN_TRADES, values.Encode()) - err := common.SendHTTPGetRequest(path, true, &result) + err := common.SendHTTPGetRequest(path, true, k.Verbose, &result) if err != nil { return err @@ -273,7 +273,7 @@ func (k *Kraken) GetSpread(symbol string) { var result interface{} path := fmt.Sprintf("%s/%s/public/%s?%s", KRAKEN_API_URL, KRAKEN_API_VERSION, KRAKEN_SPREAD, values.Encode()) - err := common.SendHTTPGetRequest(path, true, &result) + err := common.SendHTTPGetRequest(path, true, k.Verbose, &result) if err != nil { log.Println(err) diff --git a/exchanges/lakebtc/lakebtc.go b/exchanges/lakebtc/lakebtc.go index 62a8f604..639619d2 100644 --- a/exchanges/lakebtc/lakebtc.go +++ b/exchanges/lakebtc/lakebtc.go @@ -85,7 +85,7 @@ func (l *LakeBTC) GetFee(maker bool) float64 { func (l *LakeBTC) GetTicker() (map[string]LakeBTCTicker, error) { response := make(map[string]LakeBTCTickerResponse) path := fmt.Sprintf("%s/%s", LAKEBTC_API_URL, LAKEBTC_TICKER) - err := common.SendHTTPGetRequest(path, true, &response) + err := common.SendHTTPGetRequest(path, true, l.Verbose, &response) if err != nil { return nil, err } @@ -126,7 +126,7 @@ func (l *LakeBTC) GetOrderBook(currency string) (LakeBTCOrderbook, error) { } path := fmt.Sprintf("%s/%s?symbol=%s", LAKEBTC_API_URL, LAKEBTC_ORDERBOOK, common.StringToLower(currency)) resp := Response{} - err := common.SendHTTPGetRequest(path, true, &resp) + err := common.SendHTTPGetRequest(path, true, l.Verbose, &resp) if err != nil { return LakeBTCOrderbook{}, err } @@ -165,7 +165,7 @@ func (l *LakeBTC) GetOrderBook(currency string) (LakeBTCOrderbook, error) { func (l *LakeBTC) GetTradeHistory(currency string) ([]LakeBTCTradeHistory, error) { path := fmt.Sprintf("%s/%s?symbol=%s", LAKEBTC_API_URL, LAKEBTC_TRADES, common.StringToLower(currency)) resp := []LakeBTCTradeHistory{} - err := common.SendHTTPGetRequest(path, true, &resp) + err := common.SendHTTPGetRequest(path, true, l.Verbose, &resp) if err != nil { return nil, err } diff --git a/exchanges/liqui/liqui.go b/exchanges/liqui/liqui.go index 4611148e..f6baf699 100644 --- a/exchanges/liqui/liqui.go +++ b/exchanges/liqui/liqui.go @@ -102,7 +102,7 @@ func (l *Liqui) GetAvailablePairs(nonHidden bool) []string { func (l *Liqui) GetInfo() (LiquiInfo, error) { req := fmt.Sprintf("%s/%s/%s/", LIQUI_API_PUBLIC_URL, LIQUI_API_PUBLIC_VERSION, LIQUI_INFO) resp := LiquiInfo{} - err := common.SendHTTPGetRequest(req, true, &resp) + err := common.SendHTTPGetRequest(req, true, l.Verbose, &resp) if err != nil { return resp, err @@ -118,7 +118,7 @@ func (l *Liqui) GetTicker(symbol string) (map[string]LiquiTicker, error) { response := Response{} req := fmt.Sprintf("%s/%s/%s/%s", LIQUI_API_PUBLIC_URL, LIQUI_API_PUBLIC_VERSION, LIQUI_TICKER, symbol) - err := common.SendHTTPGetRequest(req, true, &response.Data) + err := common.SendHTTPGetRequest(req, true, l.Verbose, &response.Data) if err != nil { return nil, err @@ -134,7 +134,7 @@ func (l *Liqui) GetDepth(symbol string) (LiquiOrderbook, error) { response := Response{} req := fmt.Sprintf("%s/%s/%s/%s", LIQUI_API_PUBLIC_URL, LIQUI_API_PUBLIC_VERSION, LIQUI_DEPTH, symbol) - err := common.SendHTTPGetRequest(req, true, &response.Data) + err := common.SendHTTPGetRequest(req, true, l.Verbose, &response.Data) if err != nil { return LiquiOrderbook{}, err } @@ -151,7 +151,7 @@ func (l *Liqui) GetTrades(symbol string) ([]LiquiTrades, error) { response := Response{} req := fmt.Sprintf("%s/%s/%s/%s", LIQUI_API_PUBLIC_URL, LIQUI_API_PUBLIC_VERSION, LIQUI_TRADES, symbol) - err := common.SendHTTPGetRequest(req, true, &response.Data) + err := common.SendHTTPGetRequest(req, true, l.Verbose, &response.Data) if err != nil { return []LiquiTrades{}, err } diff --git a/exchanges/localbitcoins/localbitcoins.go b/exchanges/localbitcoins/localbitcoins.go index 705c0aca..e07bbe1f 100644 --- a/exchanges/localbitcoins/localbitcoins.go +++ b/exchanges/localbitcoins/localbitcoins.go @@ -80,7 +80,7 @@ func (l *LocalBitcoins) GetFee(maker bool) float64 { func (l *LocalBitcoins) GetTicker() (map[string]LocalBitcoinsTicker, error) { result := make(map[string]LocalBitcoinsTicker) - err := common.SendHTTPGetRequest(LOCALBITCOINS_API_URL+LOCALBITCOINS_API_TICKER, true, &result) + err := common.SendHTTPGetRequest(LOCALBITCOINS_API_URL+LOCALBITCOINS_API_TICKER, true, l.Verbose, &result) if err != nil { return result, err @@ -92,7 +92,7 @@ func (l *LocalBitcoins) GetTicker() (map[string]LocalBitcoinsTicker, error) { func (l *LocalBitcoins) GetTrades(currency string, values url.Values) ([]LocalBitcoinsTrade, error) { path := common.EncodeURLValues(fmt.Sprintf("%s/%s/trades.json", LOCALBITCOINS_API_URL+LOCALBITCOINS_API_BITCOINCHARTS, currency), values) result := []LocalBitcoinsTrade{} - err := common.SendHTTPGetRequest(path, true, &result) + err := common.SendHTTPGetRequest(path, true, l.Verbose, &result) if err != nil { return result, err @@ -109,7 +109,7 @@ func (l *LocalBitcoins) GetOrderbook(currency string) (LocalBitcoinsOrderbook, e path := fmt.Sprintf("%s/%s/orderbook.json", LOCALBITCOINS_API_URL+LOCALBITCOINS_API_BITCOINCHARTS, currency) resp := response{} - err := common.SendHTTPGetRequest(path, true, &resp) + err := common.SendHTTPGetRequest(path, true, l.Verbose, &resp) if err != nil { return LocalBitcoinsOrderbook{}, err @@ -162,7 +162,7 @@ func (l *LocalBitcoins) GetAccountInfo(username string, self bool) (LocalBitcoin } } else { path := fmt.Sprintf("%s/api/account_info/%s/", LOCALBITCOINS_API_URL, username) - err := common.SendHTTPGetRequest(path, true, &resp) + err := common.SendHTTPGetRequest(path, true, l.Verbose, &resp) if err != nil { return resp.Data, err diff --git a/exchanges/nonce/nonce.go b/exchanges/nonce/nonce.go index e3ace537..8f87529d 100644 --- a/exchanges/nonce/nonce.go +++ b/exchanges/nonce/nonce.go @@ -8,8 +8,12 @@ import ( // Nonce struct holds the nonce value type Nonce struct { + // Standard nonce n int64 mtx sync.Mutex + // Hash table exclusive exchange specific nonce values + boundedCall map[string]int64 + boundedMtx sync.Mutex } // Inc increments the nonce value @@ -49,14 +53,32 @@ func (n *Nonce) String() string { return result } -// Evaluate returns a nonce while evaluating in a single locked call -func (n *Nonce) Evaluate() string { - n.mtx.Lock() - defer n.mtx.Unlock() - if n.n == 0 { - n.n = time.Now().Unix() - return strconv.FormatInt(n.n, 10) +// Value is a return type for GetValue +type Value int64 + +// GetValue returns a nonce value and can be set as a higher precision. Values +// stored in an exchange specific hash table using a single locked call. +func (n *Nonce) GetValue(exchName string, nanoPrecision bool) Value { + n.boundedMtx.Lock() + defer n.boundedMtx.Unlock() + + if n.boundedCall == nil { + n.boundedCall = make(map[string]int64) } - n.n = n.n + 1 - return strconv.FormatInt(n.n, 10) + + if n.boundedCall[exchName] == 0 { + if nanoPrecision { + n.boundedCall[exchName] = time.Now().UnixNano() + return Value(n.boundedCall[exchName]) + } + n.boundedCall[exchName] = time.Now().Unix() + return Value(n.boundedCall[exchName]) + } + n.boundedCall[exchName]++ + return Value(n.boundedCall[exchName]) +} + +// String is a Value method that changes format to a string +func (v Value) String() string { + return strconv.FormatInt(int64(v), 10) } diff --git a/exchanges/nonce/nonce_test.go b/exchanges/nonce/nonce_test.go index 8bf6d6da..74996d2c 100644 --- a/exchanges/nonce/nonce_test.go +++ b/exchanges/nonce/nonce_test.go @@ -1,6 +1,7 @@ package nonce import ( + "strconv" "testing" "time" ) @@ -56,6 +57,16 @@ func TestString(t *testing.T) { } } +func TestGetValue(t *testing.T) { + var nonce Nonce + timeNowNano := strconv.FormatInt(time.Now().UnixNano(), 10) + nValue := nonce.GetValue("dingdong", true).String() + + if timeNowNano == nValue { + t.Error("Test failed - GetValue() error, incorrect values") + } +} + func TestNonceConcurrency(t *testing.T) { var nonce Nonce nonce.Set(12312) diff --git a/exchanges/okcoin/okcoin.go b/exchanges/okcoin/okcoin.go index e9b8364b..12466207 100644 --- a/exchanges/okcoin/okcoin.go +++ b/exchanges/okcoin/okcoin.go @@ -152,7 +152,7 @@ func (o *OKCoin) GetTicker(symbol string) (OKCoinTicker, error) { vals := url.Values{} vals.Set("symbol", symbol) path := common.EncodeURLValues(o.APIUrl+OKCOIN_TICKER, vals) - err := common.SendHTTPGetRequest(path, true, &resp) + err := common.SendHTTPGetRequest(path, true, o.Verbose, &resp) if err != nil { return OKCoinTicker{}, err } @@ -171,7 +171,7 @@ func (o *OKCoin) GetOrderBook(symbol string, size int64, merge bool) (OKCoinOrde } path := common.EncodeURLValues(o.APIUrl+OKCOIN_DEPTH, vals) - err := common.SendHTTPGetRequest(path, true, &resp) + err := common.SendHTTPGetRequest(path, true, o.Verbose, &resp) if err != nil { return resp, err } @@ -187,7 +187,7 @@ func (o *OKCoin) GetTrades(symbol string, since int64) ([]OKCoinTrades, error) { } path := common.EncodeURLValues(o.APIUrl+OKCOIN_TRADES, vals) - err := common.SendHTTPGetRequest(path, true, &result) + err := common.SendHTTPGetRequest(path, true, o.Verbose, &result) if err != nil { return nil, err } @@ -209,7 +209,7 @@ func (o *OKCoin) GetKline(symbol, klineType string, size, since int64) ([]interf } path := common.EncodeURLValues(o.APIUrl+OKCOIN_KLINE, vals) - err := common.SendHTTPGetRequest(path, true, &resp) + err := common.SendHTTPGetRequest(path, true, o.Verbose, &resp) if err != nil { return nil, err } @@ -223,7 +223,7 @@ func (o *OKCoin) GetFuturesTicker(symbol, contractType string) (OKCoinFuturesTic vals.Set("symbol", symbol) vals.Set("contract_type", contractType) path := common.EncodeURLValues(o.APIUrl+OKCOIN_FUTURES_TICKER, vals) - err := common.SendHTTPGetRequest(path, true, &resp) + err := common.SendHTTPGetRequest(path, true, o.Verbose, &resp) if err != nil { return OKCoinFuturesTicker{}, err } @@ -244,7 +244,7 @@ func (o *OKCoin) GetFuturesDepth(symbol, contractType string, size int64, merge } path := common.EncodeURLValues(o.APIUrl+OKCOIN_FUTURES_DEPTH, vals) - err := common.SendHTTPGetRequest(path, true, &result) + err := common.SendHTTPGetRequest(path, true, o.Verbose, &result) if err != nil { return result, err } @@ -258,7 +258,7 @@ func (o *OKCoin) GetFuturesTrades(symbol, contractType string) ([]OKCoinFuturesT vals.Set("contract_type", contractType) path := common.EncodeURLValues(o.APIUrl+OKCOIN_FUTURES_TRADES, vals) - err := common.SendHTTPGetRequest(path, true, &result) + err := common.SendHTTPGetRequest(path, true, o.Verbose, &result) if err != nil { return nil, err } @@ -275,7 +275,7 @@ func (o *OKCoin) GetFuturesIndex(symbol string) (float64, error) { vals.Set("symbol", symbol) path := common.EncodeURLValues(o.APIUrl+OKCOIN_FUTURES_INDEX, vals) - err := common.SendHTTPGetRequest(path, true, &result) + err := common.SendHTTPGetRequest(path, true, o.Verbose, &result) if err != nil { return 0, err } @@ -288,7 +288,7 @@ func (o *OKCoin) GetFuturesExchangeRate() (float64, error) { } result := Response{} - err := common.SendHTTPGetRequest(o.APIUrl+OKCOIN_EXCHANGE_RATE, true, &result) + err := common.SendHTTPGetRequest(o.APIUrl+OKCOIN_EXCHANGE_RATE, true, o.Verbose, &result) if err != nil { return result.Rate, err } @@ -304,7 +304,7 @@ func (o *OKCoin) GetFuturesEstimatedPrice(symbol string) (float64, error) { vals := url.Values{} vals.Set("symbol", symbol) path := common.EncodeURLValues(o.APIUrl+OKCOIN_FUTURES_ESTIMATED_PRICE, vals) - err := common.SendHTTPGetRequest(path, true, &result) + err := common.SendHTTPGetRequest(path, true, o.Verbose, &result) if err != nil { return result.Price, err } @@ -326,7 +326,7 @@ func (o *OKCoin) GetFuturesKline(symbol, klineType, contractType string, size, s } path := common.EncodeURLValues(o.APIUrl+OKCOIN_FUTURES_KLINE, vals) - err := common.SendHTTPGetRequest(path, true, &resp) + err := common.SendHTTPGetRequest(path, true, o.Verbose, &resp) if err != nil { return nil, err @@ -341,7 +341,7 @@ func (o *OKCoin) GetFuturesHoldAmount(symbol, contractType string) ([]OKCoinFutu vals.Set("contract_type", contractType) path := common.EncodeURLValues(o.APIUrl+OKCOIN_FUTURES_HOLD_AMOUNT, vals) - err := common.SendHTTPGetRequest(path, true, &resp) + err := common.SendHTTPGetRequest(path, true, o.Verbose, &resp) if err != nil { return nil, err @@ -362,7 +362,7 @@ func (o *OKCoin) GetFuturesExplosive(symbol, contractType string, status, curren vals.Set("page_length", strconv.FormatInt(pageLength, 10)) path := common.EncodeURLValues(o.APIUrl+OKCOIN_FUTURES_EXPLOSIVE, vals) - err := common.SendHTTPGetRequest(path, true, &resp) + err := common.SendHTTPGetRequest(path, true, o.Verbose, &resp) if err != nil { return nil, err diff --git a/exchanges/poloniex/poloniex.go b/exchanges/poloniex/poloniex.go index 3c777206..d0b8d753 100644 --- a/exchanges/poloniex/poloniex.go +++ b/exchanges/poloniex/poloniex.go @@ -101,7 +101,7 @@ func (p *Poloniex) GetTicker() (map[string]PoloniexTicker, error) { resp := response{} path := fmt.Sprintf("%s/public?command=returnTicker", POLONIEX_API_URL) - err := common.SendHTTPGetRequest(path, true, &resp.Data) + err := common.SendHTTPGetRequest(path, true, p.Verbose, &resp.Data) if err != nil { return resp.Data, err @@ -112,7 +112,7 @@ func (p *Poloniex) GetTicker() (map[string]PoloniexTicker, error) { func (p *Poloniex) GetVolume() (interface{}, error) { var resp interface{} path := fmt.Sprintf("%s/public?command=return24hVolume", POLONIEX_API_URL) - err := common.SendHTTPGetRequest(path, true, &resp) + err := common.SendHTTPGetRequest(path, true, p.Verbose, &resp) if err != nil { return resp, err @@ -130,7 +130,7 @@ func (p *Poloniex) GetOrderbook(currencyPair string, depth int) (PoloniexOrderbo resp := PoloniexOrderbookResponse{} path := fmt.Sprintf("%s/public?command=returnOrderBook&%s", POLONIEX_API_URL, vals.Encode()) - err := common.SendHTTPGetRequest(path, true, &resp) + err := common.SendHTTPGetRequest(path, true, p.Verbose, &resp) if err != nil { return PoloniexOrderbook{}, err @@ -173,7 +173,7 @@ func (p *Poloniex) GetTradeHistory(currencyPair, start, end string) ([]PoloniexT resp := []PoloniexTradeHistory{} path := fmt.Sprintf("%s/public?command=returnTradeHistory&%s", POLONIEX_API_URL, vals.Encode()) - err := common.SendHTTPGetRequest(path, true, &resp) + err := common.SendHTTPGetRequest(path, true, p.Verbose, &resp) if err != nil { return nil, err @@ -199,7 +199,7 @@ func (p *Poloniex) GetChartData(currencyPair, start, end, period string) ([]Polo resp := []PoloniexChartData{} path := fmt.Sprintf("%s/public?command=returnChartData&%s", POLONIEX_API_URL, vals.Encode()) - err := common.SendHTTPGetRequest(path, true, &resp) + err := common.SendHTTPGetRequest(path, true, p.Verbose, &resp) if err != nil { return nil, err @@ -213,7 +213,7 @@ func (p *Poloniex) GetCurrencies() (map[string]PoloniexCurrencies, error) { } resp := Response{} path := fmt.Sprintf("%s/public?command=returnCurrencies", POLONIEX_API_URL) - err := common.SendHTTPGetRequest(path, true, &resp.Data) + err := common.SendHTTPGetRequest(path, true, p.Verbose, &resp.Data) if err != nil { return resp.Data, err @@ -224,7 +224,7 @@ func (p *Poloniex) GetCurrencies() (map[string]PoloniexCurrencies, error) { func (p *Poloniex) GetLoanOrders(currency string) (PoloniexLoanOrders, error) { resp := PoloniexLoanOrders{} path := fmt.Sprintf("%s/public?command=returnLoanOrders¤cy=%s", POLONIEX_API_URL, currency) - err := common.SendHTTPGetRequest(path, true, &resp) + err := common.SendHTTPGetRequest(path, true, p.Verbose, &resp) if err != nil { return resp, err diff --git a/portfolio/portfolio.go b/portfolio/portfolio.go index d3e52fb5..9a8d7632 100644 --- a/portfolio/portfolio.go +++ b/portfolio/portfolio.go @@ -80,7 +80,7 @@ func GetEthereumBalance(address []string) (EtherchainBalanceResponse, error) { "%s/%s/%s", etherchainAPIURL, etherchainAccountMultiple, addresses, ) result := EtherchainBalanceResponse{} - err := common.SendHTTPGetRequest(url, true, &result) + err := common.SendHTTPGetRequest(url, true, false, &result) if err != nil { return result, err } @@ -90,17 +90,62 @@ func GetEthereumBalance(address []string) (EtherchainBalanceResponse, error) { return result, nil } +<<<<<<< dae90a2eaa109648bdb85f8298d805e00ad4e974 // GetCryptoIDAddress queries CryptoID for an address balance for a // specified cryptocurrency func GetCryptoIDAddress(address string, coinType string) (float64, error) { ok, err := common.IsValidCryptoAddress(address, coinType) if !ok || err != nil { return 0, errors.New("invalid address") +======= +// GetBlockrBalanceSingle queries Blockr for an address balance for either a +// LTC or a BTC single address +func GetBlockrBalanceSingle(address string, coinType string) (BlockrAddressBalanceSingle, error) { + valid, _ := common.IsValidCryptoAddress(address, coinType) + if !valid { + return BlockrAddressBalanceSingle{}, fmt.Errorf( + "Not a %s address", common.StringToUpper(coinType), + ) } + url := fmt.Sprintf( + "https://%s.%s/v%s/%s/%s", common.StringToLower(coinType), blockrAPIURL, + blockrAPIVersion, blockrAddressBalance, address, + ) + result := BlockrAddressBalanceSingle{} + err := common.SendHTTPGetRequest(url, true, false, &result) + if err != nil { + return result, err + } + if result.Status != "success" { + return result, errors.New(result.Message) +>>>>>>> In the common package added JSONDecode error check. Added verbosity in SendHTTPGetRequest. Updated Nonce package function. Fixed issues in ItBit package and expanded test coverage. + } + +<<<<<<< dae90a2eaa109648bdb85f8298d805e00ad4e974 var result interface{} url := fmt.Sprintf("%s/%s/api.dws?q=getbalance&a=%s", cryptoIDAPIURL, common.StringToLower(coinType), address) err = common.SendHTTPGetRequest(url, true, &result) +======= +// GetBlockrAddressMulti queries Blockr for an address balance for either a LTC +// or a BTC multiple addresses +func GetBlockrAddressMulti(addresses []string, coinType string) (BlockrAddressBalanceMulti, error) { + for _, add := range addresses { + valid, _ := common.IsValidCryptoAddress(add, coinType) + if !valid { + return BlockrAddressBalanceMulti{}, fmt.Errorf( + "Not a %s address", common.StringToUpper(coinType), + ) + } + } + addressesStr := common.JoinStrings(addresses, ",") + url := fmt.Sprintf( + "https://%s.%s/v%s/%s/%s", common.StringToLower(coinType), blockrAPIURL, + blockrAPIVersion, blockrAddressBalance, addressesStr, + ) + result := BlockrAddressBalanceMulti{} + err := common.SendHTTPGetRequest(url, true, false, &result) +>>>>>>> In the common package added JSONDecode error check. Added verbosity in SendHTTPGetRequest. Updated Nonce package function. Fixed issues in ItBit package and expanded test coverage. if err != nil { return 0, err }