From 604443fbbbcb6af018f68fb80cb29a374bd798fe Mon Sep 17 00:00:00 2001 From: herenow Date: Tue, 2 Oct 2018 21:58:32 -0300 Subject: [PATCH] Fix Huobi's POST requests (#186) * Fix Huobi's POST request content-type If we try to send a POST request to Huobi w/ a content-type of "application/x-www-form-urlencoded" we will receive an error: Content type 'application/x-www-form-urlencoded;charset=UTF-8' not supported Their english documentation is incorrect, it does not specify this atm, translating their chinese documentation, it states: "Content-Type: application/json must be declared in the POST request header; Content-Type: application/x-www-form-urlencoded must be declared in the GET request header. (Chinese users recommend setting Accept-Language: zh-cn)" * Fix Huobi's place new order request (send as json) We should not send the order details through url parameters, this needs to be sent as a json payload via the request body. Documentation: https://github.com/huobiapi/API_Docs_en/wiki/REST_Reference#post-v1orderordersplace--make-an-order-in-huobipro * Fix Huobi's margin transfer/margin order/withdraw requests This requests data need to be sent as json, not as a query string. Docs: https://github.com/huobiapi/API_Docs_en/wiki/REST_Reference#post-v1dwtransfer-inmargin--transfer-asset-from-spot-account-to-margin-account https://github.com/huobiapi/API_Docs_en/wiki/REST_Reference#post-v1marginorders--margin-application https://github.com/huobiapi/API_Docs_en/wiki/REST_Reference#post-v1dwwithdrawapicreate---create-a-withdraw-application * Fix Huobi's margin repayment request This request data needs to be sent a json, not via the query string. Also note, that the "order-id" parameter is already sent through the url path, it does not need to be included in the request body. --- exchanges/huobi/huobi.go | 136 ++++++++++++++++++++++++++------------- 1 file changed, 92 insertions(+), 44 deletions(-) diff --git a/exchanges/huobi/huobi.go b/exchanges/huobi/huobi.go index cf96520d..a33cc11c 100644 --- a/exchanges/huobi/huobi.go +++ b/exchanges/huobi/huobi.go @@ -5,11 +5,13 @@ import ( "crypto/ecdsa" "crypto/rand" "crypto/x509" + "encoding/json" "encoding/pem" "errors" "fmt" "io/ioutil" "log" + "net/http" "net/url" "strconv" "strings" @@ -338,7 +340,7 @@ func (h *HUOBI) GetAccounts() ([]Account, error) { } var result response - err := h.SendAuthenticatedHTTPRequest("GET", huobiAccounts, url.Values{}, &result) + err := h.SendAuthenticatedHTTPRequest("GET", huobiAccounts, url.Values{}, nil, &result) if result.ErrorMessage != "" { return nil, errors.New(result.ErrorMessage) @@ -355,7 +357,7 @@ func (h *HUOBI) GetAccountBalance(accountID string) ([]AccountBalanceDetail, err var result response endpoint := fmt.Sprintf(huobiAccountBalance, accountID) - err := h.SendAuthenticatedHTTPRequest("GET", endpoint, url.Values{}, &result) + err := h.SendAuthenticatedHTTPRequest("GET", endpoint, url.Values{}, nil, &result) if result.ErrorMessage != "" { return nil, errors.New(result.ErrorMessage) @@ -365,29 +367,36 @@ func (h *HUOBI) GetAccountBalance(accountID string) ([]AccountBalanceDetail, err // SpotNewOrder submits an order to Huobi func (h *HUOBI) SpotNewOrder(arg SpotNewOrderRequestParams) (int64, error) { - vals := url.Values{} - vals.Set("account-id", fmt.Sprintf("%d", arg.AccountID)) - vals.Set("amount", strconv.FormatFloat(arg.Amount, 'f', -1, 64)) + data := struct { + AccountID int `json:"account-id,string"` + Amount string `json:"amount"` + Price string `json:"price"` + Source string `json:"source"` + Symbol string `json:"symbol"` + Type string `json:"type"` + }{ + AccountID: arg.AccountID, + Amount: strconv.FormatFloat(arg.Amount, 'f', -1, 64), + Symbol: arg.Symbol, + Type: string(arg.Type), + } // Only set price if order type is not equal to buy-market or sell-market if arg.Type != SpotNewOrderRequestTypeBuyMarket && arg.Type != SpotNewOrderRequestTypeSellMarket { - vals.Set("price", strconv.FormatFloat(arg.Price, 'f', -1, 64)) + data.Price = strconv.FormatFloat(arg.Price, 'f', -1, 64) } if arg.Source != "" { - vals.Set("source", arg.Source) + data.Source = arg.Source } - vals.Set("symbol", arg.Symbol) - vals.Set("type", string(arg.Type)) - type response struct { Response OrderID int64 `json:"data,string"` } var result response - err := h.SendAuthenticatedHTTPRequest("POST", huobiOrderPlace, vals, &result) + err := h.SendAuthenticatedHTTPRequest("POST", huobiOrderPlace, nil, data, &result) if result.ErrorMessage != "" { return 0, errors.New(result.ErrorMessage) @@ -404,7 +413,7 @@ func (h *HUOBI) CancelOrder(orderID int64) (int64, error) { var result response endpoint := fmt.Sprintf(huobiOrderCancel, strconv.FormatInt(orderID, 10)) - err := h.SendAuthenticatedHTTPRequest("POST", endpoint, url.Values{}, &result) + err := h.SendAuthenticatedHTTPRequest("POST", endpoint, url.Values{}, nil, &result) if result.ErrorMessage != "" { return 0, errors.New(result.ErrorMessage) @@ -420,7 +429,7 @@ func (h *HUOBI) CancelOrderBatch(orderIDs []int64) ([]CancelOrderBatch, error) { } var result response - err := h.SendAuthenticatedHTTPRequest("POST", huobiOrderCancelBatch, url.Values{}, &result) + err := h.SendAuthenticatedHTTPRequest("POST", huobiOrderCancelBatch, url.Values{}, nil, &result) if result.ErrorMessage != "" { return nil, errors.New(result.ErrorMessage) @@ -437,7 +446,7 @@ func (h *HUOBI) GetOrder(orderID int64) (OrderInfo, error) { var result response endpoint := fmt.Sprintf(huobiGetOrder, strconv.FormatInt(orderID, 10)) - err := h.SendAuthenticatedHTTPRequest("GET", endpoint, url.Values{}, &result) + err := h.SendAuthenticatedHTTPRequest("GET", endpoint, url.Values{}, nil, &result) if result.ErrorMessage != "" { return result.Order, errors.New(result.ErrorMessage) @@ -454,7 +463,7 @@ func (h *HUOBI) GetOrderMatchResults(orderID int64) ([]OrderMatchInfo, error) { var result response endpoint := fmt.Sprintf(huobiGetOrderMatch, strconv.FormatInt(orderID, 10)) - err := h.SendAuthenticatedHTTPRequest("GET", endpoint, url.Values{}, &result) + err := h.SendAuthenticatedHTTPRequest("GET", endpoint, url.Values{}, nil, &result) if result.ErrorMessage != "" { return nil, errors.New(result.ErrorMessage) @@ -498,7 +507,7 @@ func (h *HUOBI) GetOrders(symbol, types, start, end, states, from, direct, size } var result response - err := h.SendAuthenticatedHTTPRequest("GET", huobiGetOrders, vals, &result) + err := h.SendAuthenticatedHTTPRequest("GET", huobiGetOrders, vals, nil, &result) if result.ErrorMessage != "" { return nil, errors.New(result.ErrorMessage) @@ -541,7 +550,7 @@ func (h *HUOBI) GetOrdersMatch(symbol, types, start, end, from, direct, size str } var result response - err := h.SendAuthenticatedHTTPRequest("GET", huobiGetOrdersMatch, vals, &result) + err := h.SendAuthenticatedHTTPRequest("GET", huobiGetOrdersMatch, vals, nil, &result) if result.ErrorMessage != "" { return nil, errors.New(result.ErrorMessage) @@ -551,10 +560,15 @@ func (h *HUOBI) GetOrdersMatch(symbol, types, start, end, from, direct, size str // MarginTransfer transfers assets into or out of the margin account func (h *HUOBI) MarginTransfer(symbol, currency string, amount float64, in bool) (int64, error) { - vals := url.Values{} - vals.Set("symbol", symbol) - vals.Set("currency", currency) - vals.Set("amount", strconv.FormatFloat(amount, 'f', -1, 64)) + data := struct { + Symbol string `json:"symbol"` + Currency string `json:"currency"` + Amount string `json:"amount"` + }{ + Symbol: symbol, + Currency: currency, + Amount: strconv.FormatFloat(amount, 'f', -1, 64), + } path := huobiMarginTransferIn if !in { @@ -567,7 +581,7 @@ func (h *HUOBI) MarginTransfer(symbol, currency string, amount float64, in bool) } var result response - err := h.SendAuthenticatedHTTPRequest("POST", path, vals, &result) + err := h.SendAuthenticatedHTTPRequest("POST", path, nil, data, &result) if result.ErrorMessage != "" { return 0, errors.New(result.ErrorMessage) @@ -577,10 +591,15 @@ func (h *HUOBI) MarginTransfer(symbol, currency string, amount float64, in bool) // MarginOrder submits a margin order application func (h *HUOBI) MarginOrder(symbol, currency string, amount float64) (int64, error) { - vals := url.Values{} - vals.Set("symbol", symbol) - vals.Set("currency", currency) - vals.Set("amount", strconv.FormatFloat(amount, 'f', -1, 64)) + data := struct { + Symbol string `json:"symbol"` + Currency string `json:"currency"` + Amount string `json:"amount"` + }{ + Symbol: symbol, + Currency: currency, + Amount: strconv.FormatFloat(amount, 'f', -1, 64), + } type response struct { Response @@ -588,7 +607,7 @@ func (h *HUOBI) MarginOrder(symbol, currency string, amount float64) (int64, err } var result response - err := h.SendAuthenticatedHTTPRequest("POST", huobiMarginOrders, vals, &result) + err := h.SendAuthenticatedHTTPRequest("POST", huobiMarginOrders, nil, data, &result) if result.ErrorMessage != "" { return 0, errors.New(result.ErrorMessage) @@ -598,9 +617,11 @@ func (h *HUOBI) MarginOrder(symbol, currency string, amount float64) (int64, err // MarginRepayment repays a margin amount for a margin ID func (h *HUOBI) MarginRepayment(orderID int64, amount float64) (int64, error) { - vals := url.Values{} - vals.Set("order-id", strconv.FormatInt(orderID, 10)) - vals.Set("amount", strconv.FormatFloat(amount, 'f', -1, 64)) + data := struct { + Amount string `json:"amount"` + }{ + Amount: strconv.FormatFloat(amount, 'f', -1, 64), + } type response struct { Response @@ -609,7 +630,7 @@ func (h *HUOBI) MarginRepayment(orderID int64, amount float64) (int64, error) { var result response endpoint := fmt.Sprintf(huobiMarginRepay, strconv.FormatInt(orderID, 10)) - err := h.SendAuthenticatedHTTPRequest("POST", endpoint, vals, &result) + err := h.SendAuthenticatedHTTPRequest("POST", endpoint, nil, data, &result) if result.ErrorMessage != "" { return 0, errors.New(result.ErrorMessage) @@ -653,7 +674,7 @@ func (h *HUOBI) GetMarginLoanOrders(symbol, currency, start, end, states, from, } var result response - err := h.SendAuthenticatedHTTPRequest("GET", huobiMarginLoanOrders, vals, &result) + err := h.SendAuthenticatedHTTPRequest("GET", huobiMarginLoanOrders, vals, nil, &result) if result.ErrorMessage != "" { return nil, errors.New(result.ErrorMessage) @@ -674,7 +695,7 @@ func (h *HUOBI) GetMarginAccountBalance(symbol string) ([]MarginAccountBalance, } var result response - err := h.SendAuthenticatedHTTPRequest("GET", huobiMarginAccountBalance, vals, &result) + err := h.SendAuthenticatedHTTPRequest("GET", huobiMarginAccountBalance, vals, nil, &result) if result.ErrorMessage != "" { return nil, errors.New(result.ErrorMessage) @@ -689,21 +710,28 @@ func (h *HUOBI) Withdraw(address, currency, addrTag string, amount, fee float64) WithdrawID int64 `json:"data"` } - vals := url.Values{} - vals.Set("address", address) - vals.Set("currency", currency) - vals.Set("amount", strconv.FormatFloat(amount, 'f', -1, 64)) + data := struct { + Address string `json:"address"` + Amount string `json:"amount"` + Currency string `json:"currency"` + Fee string `json:"fee"` + AddrTag string `json:"addr-tag"` + }{ + Address: address, + Currency: currency, + Amount: strconv.FormatFloat(amount, 'f', -1, 64), + } if fee != 0 { - vals.Set("fee", strconv.FormatFloat(fee, 'f', -1, 64)) + data.Fee = strconv.FormatFloat(fee, 'f', -1, 64) } if currency == "XRP" { - vals.Set("addr-tag", addrTag) + data.AddrTag = addrTag } var result response - err := h.SendAuthenticatedHTTPRequest("POST", huobiWithdrawCreate, vals, &result) + err := h.SendAuthenticatedHTTPRequest("POST", huobiWithdrawCreate, nil, data, &result) if result.ErrorMessage != "" { return 0, errors.New(result.ErrorMessage) @@ -723,7 +751,7 @@ func (h *HUOBI) CancelWithdraw(withdrawID int64) (int64, error) { var result response endpoint := fmt.Sprintf(huobiWithdrawCancel, strconv.FormatInt(withdrawID, 10)) - err := h.SendAuthenticatedHTTPRequest("POST", endpoint, vals, &result) + err := h.SendAuthenticatedHTTPRequest("POST", endpoint, vals, nil, &result) if result.ErrorMessage != "" { return 0, errors.New(result.ErrorMessage) @@ -737,11 +765,15 @@ func (h *HUOBI) SendHTTPRequest(path string, result interface{}) error { } // SendAuthenticatedHTTPRequest sends authenticated requests to the HUOBI API -func (h *HUOBI) SendAuthenticatedHTTPRequest(method, endpoint string, values url.Values, result interface{}) error { +func (h *HUOBI) SendAuthenticatedHTTPRequest(method, endpoint string, values url.Values, data interface{}, result interface{}) error { if !h.AuthenticatedAPISupport { return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, h.Name) } + if values == nil { + values = url.Values{} + } + values.Set("AccessKeyId", h.APIKey) values.Set("SignatureMethod", "HmacSHA256") values.Set("SignatureVersion", "2") @@ -752,7 +784,12 @@ func (h *HUOBI) SendAuthenticatedHTTPRequest(method, endpoint string, values url method, endpoint, values.Encode()) headers := make(map[string]string) - headers["Content-Type"] = "application/x-www-form-urlencoded" + + if method == http.MethodGet { + headers["Content-Type"] = "application/x-www-form-urlencoded" + } else { + headers["Content-Type"] = "application/json" + } hmac := common.GetHMAC(common.HashSHA256, []byte(payload), []byte(h.APISecret)) signature := common.Base64Encode(hmac) @@ -789,5 +826,16 @@ func (h *HUOBI) SendAuthenticatedHTTPRequest(method, endpoint string, values url url := fmt.Sprintf("%s%s", h.APIUrl, endpoint) url = common.EncodeURLValues(url, values) - return h.SendPayload(method, url, headers, bytes.NewBufferString(""), result, true, h.Verbose) + var body []byte + + if data != nil { + encoded, err := json.Marshal(data) + if err != nil { + return fmt.Errorf("Huobi unable to marshal data: %s", err) + } + + body = encoded + } + + return h.SendPayload(method, url, headers, bytes.NewReader(body), result, true, h.Verbose) }