package bithumb import ( "bytes" "errors" "fmt" "log" "net/url" "strconv" "time" "github.com/thrasher-/gocryptotrader/common" "github.com/thrasher-/gocryptotrader/config" exchange "github.com/thrasher-/gocryptotrader/exchanges" "github.com/thrasher-/gocryptotrader/exchanges/request" "github.com/thrasher-/gocryptotrader/exchanges/ticker" ) const ( apiURL = "https://api.bithumb.com" noError = "0000" // Public API requestsPerSecondPublicAPI = 20 publicTicker = "/public/ticker/" publicOrderBook = "/public/orderbook/" publicTransactionHistory = "/public/transaction_history/" // Private API requestsPerSecondPrivateAPI = 10 privateAccInfo = "/info/account" privateAccBalance = "/info/balance" privateWalletAdd = "/info/wallet_address" privateTicker = "/info/ticker" privateOrders = "/info/orders" privateUserTrans = "/info/user_transactions" privatePlaceTrade = "/trade/place" privateOrderDetail = "/info/order_detail" privateCancelTrade = "/trade/cancel" privateBTCWithdraw = "/trade/btc_withdrawal" privateKRWDeposit = "/trade/krw_deposit" privateKRWWithdraw = "/trade/krw_withdrawal" privateMarketBuy = "/trade/market_buy" privateMarketSell = "/trade/market_sell" bithumbAuthRate = 10 bithumbUnauthRate = 20 ) // Bithumb is the overarching type across the Bithumb package type Bithumb struct { exchange.Base } // SetDefaults sets the basic defaults for Bithumb func (b *Bithumb) SetDefaults() { b.Name = "Bithumb" b.Enabled = false b.Verbose = false b.Websocket = false b.RESTPollingDelay = 10 b.RequestCurrencyPairFormat.Delimiter = "" b.RequestCurrencyPairFormat.Uppercase = true b.ConfigCurrencyPairFormat.Delimiter = "" b.ConfigCurrencyPairFormat.Uppercase = true b.ConfigCurrencyPairFormat.Index = "KRW" b.AssetTypes = []string{ticker.Spot} b.SupportsAutoPairUpdating = true b.SupportsRESTTickerBatching = true b.Requester = request.New(b.Name, request.NewRateLimit(time.Second, bithumbAuthRate), request.NewRateLimit(time.Second, bithumbUnauthRate), common.NewHTTPClientWithTimeout(exchange.DefaultHTTPTimeout)) b.APIUrlDefault = apiURL b.APIUrl = b.APIUrlDefault } // Setup takes in the supplied exchange configuration details and sets params func (b *Bithumb) Setup(exch config.ExchangeConfig) { if !exch.Enabled { b.SetEnabled(false) } else { b.Enabled = true b.AuthenticatedAPISupport = exch.AuthenticatedAPISupport b.SetAPIKeys(exch.APIKey, exch.APISecret, "", false) b.SetHTTPClientTimeout(exch.HTTPTimeout) b.SetHTTPClientUserAgent(exch.HTTPUserAgent) b.RESTPollingDelay = exch.RESTPollingDelay b.Verbose = exch.Verbose b.Websocket = exch.Websocket b.BaseCurrencies = common.SplitStrings(exch.BaseCurrencies, ",") b.AvailablePairs = common.SplitStrings(exch.AvailablePairs, ",") b.EnabledPairs = common.SplitStrings(exch.EnabledPairs, ",") err := b.SetCurrencyPairFormat() if err != nil { log.Fatal(err) } err = b.SetAssetTypes() if err != nil { log.Fatal(err) } err = b.SetAutoPairDefaults() if err != nil { log.Fatal(err) } err = b.SetAPIURL(exch) if err != nil { log.Fatal(err) } } } // GetTradablePairs returns a list of tradable currencies func (b *Bithumb) GetTradablePairs() ([]string, error) { result, err := b.GetAllTickers() if err != nil { return nil, err } var currencies []string for x := range result { currencies = append(currencies, x) } return currencies, nil } // GetTicker returns ticker information // // symbol e.g. "btc" func (b *Bithumb) GetTicker(symbol string) (Ticker, error) { response := Ticker{} path := fmt.Sprintf("%s%s%s", b.APIUrl, publicTicker, common.StringToUpper(symbol)) err := b.SendHTTPRequest(path, &response) if err != nil { return response, err } if response.Status != noError { return response, errors.New(response.Message) } return response, nil } // GetAllTickers returns all ticker information func (b *Bithumb) GetAllTickers() (map[string]Ticker, error) { type Response struct { ActionStatus Data map[string]interface{} } response := Response{} path := fmt.Sprintf("%s%s%s", b.APIUrl, publicTicker, "all") err := b.SendHTTPRequest(path, &response) if err != nil { return nil, err } if response.Status != noError { return nil, errors.New(response.Message) } result := make(map[string]Ticker) for k, v := range response.Data { if k == "date" { continue } data := v.(map[string]interface{}) var t Ticker t.AveragePrice, _ = strconv.ParseFloat(data["average_price"].(string), 64) t.BuyPrice, _ = strconv.ParseFloat(data["buy_price"].(string), 64) t.ClosingPrice, _ = strconv.ParseFloat(data["closing_price"].(string), 64) t.MaxPrice, _ = strconv.ParseFloat(data["max_price"].(string), 64) t.MinPrice, _ = strconv.ParseFloat(data["min_price"].(string), 64) t.OpeningPrice, _ = strconv.ParseFloat(data["opening_price"].(string), 64) t.SellPrice, _ = strconv.ParseFloat(data["sell_price"].(string), 64) t.UnitsTraded, _ = strconv.ParseFloat(data["units_traded"].(string), 64) t.Volume1Day, _ = strconv.ParseFloat(data["volume_1day"].(string), 64) t.Volume7Day, _ = strconv.ParseFloat(data["volume_7day"].(string), 64) result[k] = t } return result, nil } // GetOrderBook returns current orderbook // // symbol e.g. "btc" func (b *Bithumb) GetOrderBook(symbol string) (Orderbook, error) { response := Orderbook{} path := fmt.Sprintf("%s%s%s", b.APIUrl, publicOrderBook, common.StringToUpper(symbol)) err := b.SendHTTPRequest(path, &response) if err != nil { return response, err } if response.Status != noError { return response, errors.New(response.Message) } return response, nil } // GetTransactionHistory returns recent transactions // // symbol e.g. "btc" func (b *Bithumb) GetTransactionHistory(symbol string) (TransactionHistory, error) { response := TransactionHistory{} path := fmt.Sprintf("%s%s%s", b.APIUrl, publicTransactionHistory, common.StringToUpper(symbol)) err := b.SendHTTPRequest(path, &response) if err != nil { return response, err } if response.Status != noError { return response, errors.New(response.Message) } return response, nil } // GetAccountInfo returns account information func (b *Bithumb) GetAccountInfo() (Account, error) { response := Account{} err := b.SendAuthenticatedHTTPRequest(privateAccInfo, nil, &response) if err != nil { return response, err } if response.Status != noError { return response, errors.New(response.Message) } return response, nil } // GetAccountBalance returns customer wallet information func (b *Bithumb) GetAccountBalance() (Balance, error) { response := Balance{} err := b.SendAuthenticatedHTTPRequest(privateAccBalance, nil, &response) if err != nil { return response, err } if response.Status != noError { return response, errors.New(response.Message) } return response, nil } // GetWalletAddress returns customer wallet address // // currency e.g. btc, ltc or "", will default to btc without currency specified func (b *Bithumb) GetWalletAddress(currency string) (WalletAddressRes, error) { response := WalletAddressRes{} params := url.Values{} params.Set("currency", common.StringToUpper(currency)) err := b.SendAuthenticatedHTTPRequest(privateWalletAdd, params, &response) if err != nil { return response, err } if response.Status != noError { return response, errors.New(response.Message) } return response, nil } // GetLastTransaction returns customer last transaction func (b *Bithumb) GetLastTransaction() (LastTransactionTicker, error) { response := LastTransactionTicker{} err := b.SendAuthenticatedHTTPRequest(privateTicker, nil, &response) if err != nil { return response, err } if response.Status != noError { return response, errors.New(response.Message) } return response, nil } // GetOrders returns order list // // orderID: order number registered for purchase/sales // transactionType: transaction type(bid : purchase, ask : sell) // count: Value : 1 ~1000 (default : 100) // after: YYYY-MM-DD hh:mm:ss's UNIX Timestamp // (2014-11-28 16:40:01 = 1417160401000) func (b *Bithumb) GetOrders(orderID, transactionType, count, after, currency string) (Orders, error) { response := Orders{} params := url.Values{} params.Set("order_id", orderID) params.Set("type", transactionType) params.Set("count", count) params.Set("after", after) params.Set("currency", common.StringToUpper(currency)) err := b.SendAuthenticatedHTTPRequest(privateOrders, params, &response) if err != nil { return response, err } if response.Status != noError { return response, errors.New(response.Message) } return response, nil } // GetUserTransactions returns customer transactions func (b *Bithumb) GetUserTransactions() (UserTransactions, error) { response := UserTransactions{} err := b.SendAuthenticatedHTTPRequest(privateUserTrans, nil, &response) if err != nil { return response, err } if response.Status != noError { return response, errors.New(response.Message) } return response, nil } // PlaceTrade executes a trade order // // orderCurrency: BTC, ETH, DASH, LTC, ETC, XRP, BCH, XMR, ZEC, QTUM, BTG, EOS // (default value: BTC) // transactionType: Transaction type(bid : purchase, ask : sales) // units: Order quantity // price: Transaction amount per currency func (b *Bithumb) PlaceTrade(orderCurrency, transactionType string, units float64, price int64) (OrderPlace, error) { response := OrderPlace{} params := url.Values{} params.Set("order_currency", common.StringToUpper(orderCurrency)) params.Set("Payment_currency", "KRW") params.Set("type", common.StringToUpper(transactionType)) params.Set("units", strconv.FormatFloat(units, 'f', -1, 64)) params.Set("price", strconv.FormatInt(price, 10)) err := b.SendAuthenticatedHTTPRequest(privatePlaceTrade, params, &response) if err != nil { return response, err } if response.Status != noError { return response, errors.New(response.Message) } return response, nil } // GetOrderDetails returns specific order details // // orderID: Order number registered for purchase/sales // transactionType: Transaction type(bid : purchase, ask : sales) // currency: BTC, ETH, DASH, LTC, ETC, XRP, BCH, XMR, ZEC, QTUM, BTG, EOS // (default value: BTC) func (b *Bithumb) GetOrderDetails(orderID, transactionType, currency string) (OrderDetails, error) { response := OrderDetails{} params := url.Values{} params.Set("order_id", common.StringToUpper(orderID)) params.Set("type", common.StringToUpper(transactionType)) params.Set("currency", common.StringToUpper(currency)) err := b.SendAuthenticatedHTTPRequest(privateOrderDetail, params, &response) if err != nil { return response, err } if response.Status != noError { return response, errors.New(response.Message) } return response, nil } // CancelTrade cancels a customer purchase/sales transaction // transactionType: Transaction type(bid : purchase, ask : sales) // orderID: Order number registered for purchase/sales // currency: BTC, ETH, DASH, LTC, ETC, XRP, BCH, XMR, ZEC, QTUM, BTG, EOS // (default value: BTC) func (b *Bithumb) CancelTrade(transactionType, orderID, currency string) (ActionStatus, error) { response := ActionStatus{} params := url.Values{} params.Set("order_id", common.StringToUpper(orderID)) params.Set("type", common.StringToUpper(transactionType)) params.Set("currency", common.StringToUpper(currency)) err := b.SendAuthenticatedHTTPRequest(privateCancelTrade, nil, &response) if err != nil { return response, err } if response.Status != noError { return response, errors.New(response.Message) } return response, nil } // WithdrawCrypto withdraws a customer currency to an address // // address: Currency withdrawing address // destination: Currency withdrawal Destination Tag (when withdraw XRP) OR // Currency withdrawal Payment Id (when withdraw XMR) // currency: BTC, ETH, DASH, LTC, ETC, XRP, BCH, XMR, ZEC, QTUM // (default value: BTC) // units: Quantity to withdraw currency func (b *Bithumb) WithdrawCrypto(address, destination, currency string, units float64) (ActionStatus, error) { response := ActionStatus{} params := url.Values{} params.Set("address", address) params.Set("destination", destination) params.Set("currency", common.StringToUpper(currency)) params.Set("units", strconv.FormatFloat(units, 'f', -1, 64)) err := b.SendAuthenticatedHTTPRequest(privateBTCWithdraw, params, &response) if err != nil { return response, err } if response.Status != noError { return response, errors.New(response.Message) } return response, nil } // RequestKRWDepositDetails returns Bithumb banking details for deposit // information func (b *Bithumb) RequestKRWDepositDetails() (KRWDeposit, error) { response := KRWDeposit{} err := b.SendAuthenticatedHTTPRequest(privateKRWDeposit, nil, &response) if err != nil { return response, err } if response.Status != noError { return response, errors.New(response.Message) } return response, nil } // RequestKRWWithdraw allows a customer KRW withdrawal request // // bank: Bankcode with bank name e.g. (bankcode)_(bankname) // account: Withdrawing bank account number // price: Withdrawing amount func (b *Bithumb) RequestKRWWithdraw(bank, account string, price int64) (ActionStatus, error) { response := ActionStatus{} params := url.Values{} params.Set("bank", bank) params.Set("account", account) params.Set("price", strconv.FormatInt(price, 10)) err := b.SendAuthenticatedHTTPRequest(privateKRWWithdraw, params, &response) if err != nil { return response, err } if response.Status != noError { return response, errors.New(response.Message) } return response, nil } // MarketBuyOrder initiates a buy order through available order books // // currency: BTC, ETH, DASH, LTC, ETC, XRP, BCH, XMR, ZEC, QTUM, BTG, EOS // (default value: BTC) // units: Order quantity func (b *Bithumb) MarketBuyOrder(currency string, units float64) (MarketBuy, error) { response := MarketBuy{} params := url.Values{} params.Set("currency", common.StringToUpper(currency)) params.Set("units", strconv.FormatFloat(units, 'f', -1, 64)) err := b.SendAuthenticatedHTTPRequest(privateMarketBuy, params, &response) if err != nil { return response, err } if response.Status != noError { return response, errors.New(response.Message) } return response, nil } // MarketSellOrder initiates a sell order through available order books // // currency: BTC, ETH, DASH, LTC, ETC, XRP, BCH, XMR, ZEC, QTUM, BTG, EOS // (default value: BTC) // units: Order quantity func (b *Bithumb) MarketSellOrder(currency string, units float64) (MarketSell, error) { response := MarketSell{} params := url.Values{} params.Set("currency", common.StringToUpper(currency)) params.Set("units", strconv.FormatFloat(units, 'f', -1, 64)) err := b.SendAuthenticatedHTTPRequest(privateMarketSell, params, &response) if err != nil { return response, err } if response.Status != noError { return response, errors.New(response.Message) } return response, nil } // SendHTTPRequest sends an unauthenticated HTTP request func (b *Bithumb) SendHTTPRequest(path string, result interface{}) error { return b.SendPayload("GET", path, nil, nil, result, false, b.Verbose) } // SendAuthenticatedHTTPRequest sends an authenticated HTTP request to bithumb func (b *Bithumb) SendAuthenticatedHTTPRequest(path string, params url.Values, result interface{}) error { if !b.AuthenticatedAPISupport { return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, b.Name) } if params == nil { params = url.Values{} } if b.Nonce.Get() == 0 { b.Nonce.Set(time.Now().UnixNano() / int64(time.Millisecond)) } else { b.Nonce.Inc() } params.Set("endpoint", path) payload := params.Encode() hmacPayload := path + string(0) + payload + string(0) + b.Nonce.String() hmac := common.GetHMAC(common.HashSHA512, []byte(hmacPayload), []byte(b.APISecret)) hmacStr := common.HexEncodeToString(hmac) headers := make(map[string]string) headers["Api-Key"] = b.APIKey headers["Api-Sign"] = common.Base64Encode([]byte(hmacStr)) headers["Api-Nonce"] = b.Nonce.String() headers["Content-Type"] = "application/x-www-form-urlencoded" return b.SendPayload("POST", b.APIUrl+path, headers, bytes.NewBufferString(payload), result, true, b.Verbose) }