Huobi: Add deposit address and withdraw quota API support (#433)

* Add Huobi deposit and withdraw quota API support
Plus perform come code deduplication

* Go mod tidy
This commit is contained in:
Adrian Gallagher
2020-02-03 12:44:46 +11:00
committed by GitHub
parent a32d16e1f5
commit b8dfa443d3
6 changed files with 210 additions and 210 deletions

View File

@@ -24,8 +24,9 @@ import (
)
const (
huobiAPIURL = "https://api.huobi.pro"
huobiAPIVersion = "1"
huobiAPIURL = "https://api.huobi.pro"
huobiAPIVersion = "1"
huobiAPIVersion2 = "2"
huobiMarketHistoryKline = "market/history/kline"
huobiMarketDetail = "market/detail"
@@ -39,6 +40,8 @@ const (
huobiTimestamp = "common/timestamp"
huobiAccounts = "account/accounts"
huobiAccountBalance = "account/accounts/%s/balance"
huobiAccountDepositAddress = "account/deposit/address"
huobiAccountWithdrawQuota = "account/withdraw/quota"
huobiAggregatedBalance = "subuser/aggregate-balance"
huobiOrderPlace = "order/orders/place"
huobiOrderCancel = "order/orders/%s/submitcancel"
@@ -60,6 +63,8 @@ const (
huobiAuthRate = 100
huobiUnauthRate = 100
huobiStatusError = "error"
)
// HUOBI is the overarching type across this package
@@ -287,61 +292,38 @@ func (h *HUOBI) GetTimestamp() (int64, error) {
// GetAccounts returns the Huobi user accounts
func (h *HUOBI) GetAccounts() ([]Account, error) {
type response struct {
Response
AccountData []Account `json:"data"`
}
var result response
err := h.SendAuthenticatedHTTPRequest(http.MethodGet, huobiAccounts, url.Values{}, nil, &result)
if result.ErrorMessage != "" {
return nil, errors.New(result.ErrorMessage)
}
return result.AccountData, err
result := struct {
Accounts []Account `json:"data"`
}{}
err := h.SendAuthenticatedHTTPRequest(http.MethodGet, huobiAccounts, url.Values{}, nil, &result, false)
return result.Accounts, err
}
// GetAccountBalance returns the users Huobi account balance
func (h *HUOBI) GetAccountBalance(accountID string) ([]AccountBalanceDetail, error) {
type response struct {
Response
result := struct {
AccountBalanceData AccountBalance `json:"data"`
}
var result response
}{}
endpoint := fmt.Sprintf(huobiAccountBalance, accountID)
v := url.Values{}
v.Set("account-id", accountID)
err := h.SendAuthenticatedHTTPRequest(http.MethodGet, endpoint, v, nil, &result)
if result.ErrorMessage != "" {
return nil, errors.New(result.ErrorMessage)
}
err := h.SendAuthenticatedHTTPRequest(http.MethodGet, endpoint, v, nil, &result, false)
return result.AccountBalanceData.AccountBalanceDetails, err
}
// GetAggregatedBalance returns the balances of all the sub-account aggregated.
func (h *HUOBI) GetAggregatedBalance() ([]AggregatedBalance, error) {
type response struct {
Response
result := struct {
AggregatedBalances []AggregatedBalance `json:"data"`
}
var result response
}{}
err := h.SendAuthenticatedHTTPRequest(
http.MethodGet,
huobiAggregatedBalance,
nil,
nil,
&result,
false,
)
if result.ErrorMessage != "" {
return nil, errors.New(result.ErrorMessage)
}
return result.AggregatedBalances, err
}
@@ -370,35 +352,28 @@ func (h *HUOBI) SpotNewOrder(arg SpotNewOrderRequestParams) (int64, error) {
data.Source = arg.Source
}
type response struct {
Response
result := struct {
OrderID int64 `json:"data,string"`
}
var result response
err := h.SendAuthenticatedHTTPRequest(http.MethodPost, huobiOrderPlace, nil, data, &result)
if result.ErrorMessage != "" {
return 0, errors.New(result.ErrorMessage)
}
}{}
err := h.SendAuthenticatedHTTPRequest(
http.MethodPost,
huobiOrderPlace,
nil,
data,
&result,
false,
)
return result.OrderID, err
}
// CancelExistingOrder cancels an order on Huobi
func (h *HUOBI) CancelExistingOrder(orderID int64) (int64, error) {
type response struct {
Response
resp := struct {
OrderID int64 `json:"data,string"`
}
var result response
}{}
endpoint := fmt.Sprintf(huobiOrderCancel, strconv.FormatInt(orderID, 10))
err := h.SendAuthenticatedHTTPRequest(http.MethodPost, endpoint, url.Values{}, nil, &result)
if result.ErrorMessage != "" {
return 0, errors.New(result.ErrorMessage)
}
return result.OrderID, err
err := h.SendAuthenticatedHTTPRequest(http.MethodPost, endpoint, url.Values{}, nil, &resp, false)
return resp.OrderID, err
}
// CancelOrderBatch cancels a batch of orders -- to-do
@@ -409,7 +384,7 @@ func (h *HUOBI) CancelOrderBatch(_ []int64) ([]CancelOrderBatch, error) {
}
var result response
err := h.SendAuthenticatedHTTPRequest(http.MethodPost, huobiOrderCancelBatch, url.Values{}, nil, &result)
err := h.SendAuthenticatedHTTPRequest(http.MethodPost, huobiOrderCancelBatch, url.Values{}, nil, &result, false)
if result.ErrorMessage != "" {
return nil, errors.New(result.ErrorMessage)
@@ -432,7 +407,7 @@ func (h *HUOBI) CancelOpenOrdersBatch(accountID, symbol string) (CancelOpenOrder
Symbol: symbol,
}
err := h.SendAuthenticatedHTTPRequest(http.MethodPost, huobiBatchCancelOpenOrders, url.Values{}, data, &result)
err := h.SendAuthenticatedHTTPRequest(http.MethodPost, huobiBatchCancelOpenOrders, url.Values{}, data, &result, false)
if result.Data.FailedCount > 0 {
return result, fmt.Errorf("there were %v failed order cancellations", result.Data.FailedCount)
}
@@ -442,45 +417,35 @@ func (h *HUOBI) CancelOpenOrdersBatch(accountID, symbol string) (CancelOpenOrder
// GetOrder returns order information for the specified order
func (h *HUOBI) GetOrder(orderID int64) (OrderInfo, error) {
type response struct {
Response
resp := struct {
Order OrderInfo `json:"data"`
}
var result response
}{}
urlVal := url.Values{}
urlVal.Set("clientOrderId", strconv.FormatInt(orderID, 10))
err := h.SendAuthenticatedHTTPRequest(http.MethodGet, huobiGetOrder, urlVal, nil, &result)
if result.ErrorMessage != "" {
return result.Order, errors.New(result.ErrorMessage)
}
return result.Order, err
err := h.SendAuthenticatedHTTPRequest(http.MethodGet,
huobiGetOrder,
urlVal,
nil,
&resp,
false)
return resp.Order, err
}
// GetOrderMatchResults returns matched order info for the specified order
func (h *HUOBI) GetOrderMatchResults(orderID int64) ([]OrderMatchInfo, error) {
type response struct {
Response
resp := struct {
Orders []OrderMatchInfo `json:"data"`
}
var result response
}{}
endpoint := fmt.Sprintf(huobiGetOrderMatch, strconv.FormatInt(orderID, 10))
err := h.SendAuthenticatedHTTPRequest(http.MethodGet, endpoint, url.Values{}, nil, &result)
if result.ErrorMessage != "" {
return nil, errors.New(result.ErrorMessage)
}
return result.Orders, err
err := h.SendAuthenticatedHTTPRequest(http.MethodGet, endpoint, url.Values{}, nil, &resp, false)
return resp.Orders, err
}
// GetOrders returns a list of orders
func (h *HUOBI) GetOrders(symbol, types, start, end, states, from, direct, size string) ([]OrderInfo, error) {
type response struct {
Response
resp := struct {
Orders []OrderInfo `json:"data"`
}
}{}
vals := url.Values{}
vals.Set("symbol", symbol)
@@ -510,21 +475,15 @@ func (h *HUOBI) GetOrders(symbol, types, start, end, states, from, direct, size
vals.Set("size", size)
}
var result response
err := h.SendAuthenticatedHTTPRequest(http.MethodGet, huobiGetOrders, vals, nil, &result)
if result.ErrorMessage != "" {
return nil, errors.New(result.ErrorMessage)
}
return result.Orders, err
err := h.SendAuthenticatedHTTPRequest(http.MethodGet, huobiGetOrders, vals, nil, &resp, false)
return resp.Orders, err
}
// GetOpenOrders returns a list of orders
func (h *HUOBI) GetOpenOrders(accountID, symbol, side string, size int64) ([]OrderInfo, error) {
type response struct {
Response
resp := struct {
Orders []OrderInfo `json:"data"`
}
}{}
vals := url.Values{}
vals.Set("symbol", symbol)
@@ -534,22 +493,15 @@ func (h *HUOBI) GetOpenOrders(accountID, symbol, side string, size int64) ([]Ord
}
vals.Set("size", strconv.FormatInt(size, 10))
var result response
err := h.SendAuthenticatedHTTPRequest(http.MethodGet, huobiGetOpenOrders, vals, nil, &result)
if result.ErrorMessage != "" {
return nil, errors.New(result.ErrorMessage)
}
return result.Orders, err
err := h.SendAuthenticatedHTTPRequest(http.MethodGet, huobiGetOpenOrders, vals, nil, &resp, false)
return resp.Orders, err
}
// GetOrdersMatch returns a list of matched orders
func (h *HUOBI) GetOrdersMatch(symbol, types, start, end, from, direct, size string) ([]OrderMatchInfo, error) {
type response struct {
Response
resp := struct {
Orders []OrderMatchInfo `json:"data"`
}
}{}
vals := url.Values{}
vals.Set("symbol", symbol)
@@ -578,13 +530,8 @@ func (h *HUOBI) GetOrdersMatch(symbol, types, start, end, from, direct, size str
vals.Set("size", size)
}
var result response
err := h.SendAuthenticatedHTTPRequest(http.MethodGet, huobiGetOrdersMatch, vals, nil, &result)
if result.ErrorMessage != "" {
return nil, errors.New(result.ErrorMessage)
}
return result.Orders, err
err := h.SendAuthenticatedHTTPRequest(http.MethodGet, huobiGetOrdersMatch, vals, nil, &resp, false)
return resp.Orders, err
}
// MarginTransfer transfers assets into or out of the margin account
@@ -604,18 +551,11 @@ func (h *HUOBI) MarginTransfer(symbol, currency string, amount float64, in bool)
path = huobiMarginTransferOut
}
type response struct {
Response
resp := struct {
TransferID int64 `json:"data"`
}
var result response
err := h.SendAuthenticatedHTTPRequest(http.MethodPost, path, nil, data, &result)
if result.ErrorMessage != "" {
return 0, errors.New(result.ErrorMessage)
}
return result.TransferID, err
}{}
err := h.SendAuthenticatedHTTPRequest(http.MethodPost, path, nil, data, &resp, false)
return resp.TransferID, err
}
// MarginOrder submits a margin order application
@@ -630,18 +570,11 @@ func (h *HUOBI) MarginOrder(symbol, currency string, amount float64) (int64, err
Amount: strconv.FormatFloat(amount, 'f', -1, 64),
}
type response struct {
Response
resp := struct {
MarginOrderID int64 `json:"data"`
}
var result response
err := h.SendAuthenticatedHTTPRequest(http.MethodPost, huobiMarginOrders, nil, data, &result)
if result.ErrorMessage != "" {
return 0, errors.New(result.ErrorMessage)
}
return result.MarginOrderID, err
}{}
err := h.SendAuthenticatedHTTPRequest(http.MethodPost, huobiMarginOrders, nil, data, &resp, false)
return resp.MarginOrderID, err
}
// MarginRepayment repays a margin amount for a margin ID
@@ -652,19 +585,13 @@ func (h *HUOBI) MarginRepayment(orderID int64, amount float64) (int64, error) {
Amount: strconv.FormatFloat(amount, 'f', -1, 64),
}
type response struct {
Response
resp := struct {
MarginOrderID int64 `json:"data"`
}
}{}
var result response
endpoint := fmt.Sprintf(huobiMarginRepay, strconv.FormatInt(orderID, 10))
err := h.SendAuthenticatedHTTPRequest(http.MethodPost, endpoint, nil, data, &result)
if result.ErrorMessage != "" {
return 0, errors.New(result.ErrorMessage)
}
return result.MarginOrderID, err
err := h.SendAuthenticatedHTTPRequest(http.MethodPost, endpoint, nil, data, &resp, false)
return resp.MarginOrderID, err
}
// GetMarginLoanOrders returns the margin loan orders
@@ -697,47 +624,31 @@ func (h *HUOBI) GetMarginLoanOrders(symbol, currency, start, end, states, from,
vals.Set("size", size)
}
type response struct {
Response
resp := struct {
MarginLoanOrders []MarginOrder `json:"data"`
}
var result response
err := h.SendAuthenticatedHTTPRequest(http.MethodGet, huobiMarginLoanOrders, vals, nil, &result)
if result.ErrorMessage != "" {
return nil, errors.New(result.ErrorMessage)
}
return result.MarginLoanOrders, err
}{}
err := h.SendAuthenticatedHTTPRequest(http.MethodGet, huobiMarginLoanOrders, vals, nil, &resp, false)
return resp.MarginLoanOrders, err
}
// GetMarginAccountBalance returns the margin account balances
func (h *HUOBI) GetMarginAccountBalance(symbol string) ([]MarginAccountBalance, error) {
type response struct {
Response
resp := struct {
Balances []MarginAccountBalance `json:"data"`
}
}{}
vals := url.Values{}
if symbol != "" {
vals.Set("symbol", symbol)
}
var result response
err := h.SendAuthenticatedHTTPRequest(http.MethodGet, huobiMarginAccountBalance, vals, nil, &result)
if result.ErrorMessage != "" {
return nil, errors.New(result.ErrorMessage)
}
return result.Balances, err
err := h.SendAuthenticatedHTTPRequest(http.MethodGet, huobiMarginAccountBalance, vals, nil, &resp, false)
return resp.Balances, err
}
// Withdraw withdraws the desired amount and currency
func (h *HUOBI) Withdraw(c currency.Code, address, addrTag string, amount, fee float64) (int64, error) {
type response struct {
Response
resp := struct {
WithdrawID int64 `json:"data"`
}
}{}
data := struct {
Address string `json:"address"`
@@ -759,33 +670,56 @@ func (h *HUOBI) Withdraw(c currency.Code, address, addrTag string, amount, fee f
data.AddrTag = addrTag
}
var result response
err := h.SendAuthenticatedHTTPRequest(http.MethodPost, huobiWithdrawCreate, nil, data, &result)
if result.ErrorMessage != "" {
return 0, errors.New(result.ErrorMessage)
}
return result.WithdrawID, err
err := h.SendAuthenticatedHTTPRequest(http.MethodPost, huobiWithdrawCreate, nil, data, &resp.WithdrawID, false)
return resp.WithdrawID, err
}
// CancelWithdraw cancels a withdraw request
func (h *HUOBI) CancelWithdraw(withdrawID int64) (int64, error) {
type response struct {
Response
resp := struct {
WithdrawID int64 `json:"data"`
}
}{}
vals := url.Values{}
vals.Set("withdraw-id", strconv.FormatInt(withdrawID, 10))
var result response
endpoint := fmt.Sprintf(huobiWithdrawCancel, strconv.FormatInt(withdrawID, 10))
err := h.SendAuthenticatedHTTPRequest(http.MethodPost, endpoint, vals, nil, &result)
err := h.SendAuthenticatedHTTPRequest(http.MethodPost, endpoint, vals, nil, &resp, false)
return resp.WithdrawID, err
}
if result.ErrorMessage != "" {
return 0, errors.New(result.ErrorMessage)
// QueryDepositAddress returns the deposit address for a specified currency
func (h *HUOBI) QueryDepositAddress(cryptocurrency string) (DepositAddress, error) {
resp := struct {
DepositAddress []DepositAddress `json:"data"`
}{}
vals := url.Values{}
vals.Set("currency", cryptocurrency)
err := h.SendAuthenticatedHTTPRequest(http.MethodGet, huobiAccountDepositAddress, vals, nil, &resp, true)
if err != nil {
return DepositAddress{}, err
}
return result.WithdrawID, err
if len(resp.DepositAddress) == 0 {
return DepositAddress{}, errors.New("deposit address data isn't populated")
}
return resp.DepositAddress[0], nil
}
// QueryWithdrawQuotas returns the users cryptocurrency withdraw quotas
func (h *HUOBI) QueryWithdrawQuotas(cryptocurrency string) (WithdrawQuota, error) {
resp := struct {
WithdrawQuota WithdrawQuota `json:"data"`
}{}
vals := url.Values{}
vals.Set("currency", cryptocurrency)
err := h.SendAuthenticatedHTTPRequest(http.MethodGet, huobiAccountWithdrawQuota, vals, nil, &resp, true)
if err != nil {
return WithdrawQuota{}, err
}
return resp.WithdrawQuota, nil
}
// SendHTTPRequest sends an unauthenticated HTTP request
@@ -803,7 +737,7 @@ 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, data, result interface{}) error {
func (h *HUOBI) SendAuthenticatedHTTPRequest(method, endpoint string, values url.Values, data, result interface{}, isVersion2API bool) error {
if !h.AllowAuthenticatedRequest() {
return fmt.Errorf(exchange.WarningAuthenticatedRequestWithoutCredentialsSet, h.Name)
}
@@ -817,7 +751,12 @@ func (h *HUOBI) SendAuthenticatedHTTPRequest(method, endpoint string, values url
values.Set("SignatureVersion", "2")
values.Set("Timestamp", time.Now().UTC().Format("2006-01-02T15:04:05"))
endpoint = fmt.Sprintf("/v%s/%s", huobiAPIVersion, endpoint)
if isVersion2API {
endpoint = fmt.Sprintf("/v%s/%s", huobiAPIVersion2, endpoint)
} else {
endpoint = fmt.Sprintf("/v%s/%s", huobiAPIVersion, endpoint)
}
payload := fmt.Sprintf("%s\napi.huobi.pro\n%s\n%s",
method, endpoint, values.Encode())
@@ -864,26 +803,45 @@ func (h *HUOBI) SendAuthenticatedHTTPRequest(method, endpoint string, values url
urlPath := h.API.Endpoints.URL + common.EncodeURLValues(endpoint, values)
var body []byte
if data != nil {
encoded, err := json.Marshal(data)
if err != nil {
return fmt.Errorf("%s unable to marshal data: %s", h.Name, err)
}
body = encoded
}
return h.SendPayload(method,
interim := json.RawMessage{}
err := h.SendPayload(method,
urlPath,
headers,
bytes.NewReader(body),
result,
bytes.NewBuffer(body),
&interim,
true,
false,
h.Verbose,
h.HTTPDebugging,
h.HTTPRecording)
if err != nil {
return err
}
if isVersion2API {
var errCap ResponseV2
if err = json.Unmarshal(interim, &errCap); err == nil {
if errCap.Code != 200 && errCap.Message != "" {
return errors.New(errCap.Message)
}
}
} else {
var errCap Response
if err = json.Unmarshal(interim, &errCap); err == nil {
if errCap.Status == huobiStatusError && errCap.ErrorMessage != "" {
return errors.New(errCap.ErrorMessage)
}
}
}
return json.Unmarshal(interim, result)
}
// GetFee returns an estimate of fee based on type of transaction

View File

@@ -634,10 +634,23 @@ func TestWithdrawInternationalBank(t *testing.T) {
}
}
func TestGetDepositAddress(t *testing.T) {
_, err := h.GetDepositAddress(currency.BTC, "")
if err == nil {
t.Error("GetDepositAddress() error cannot be nil")
func TestQueryDepositAddress(t *testing.T) {
_, err := h.QueryDepositAddress(currency.BTC.Lower().String())
if !areTestAPIKeysSet() && err == nil {
t.Error("Expecting an error when no keys are set")
}
if areTestAPIKeysSet() && err != nil {
t.Error(err)
}
}
func TestQueryWithdrawQuota(t *testing.T) {
_, err := h.QueryWithdrawQuotas(currency.BTC.Lower().String())
if !areTestAPIKeysSet() && err == nil {
t.Error("Expecting an error when no keys are set")
}
if areTestAPIKeysSet() && err != nil {
t.Error(err)
}
}

View File

@@ -9,6 +9,12 @@ type Response struct {
ErrorMessage string `json:"err-msg"`
}
// ResponseV2 stores the Huobi generic response info
type ResponseV2 struct {
Code int32 `json:"code"`
Message string `json:"message"`
}
// KlineItem stores a kline item
type KlineItem struct {
ID int64 `json:"id"`
@@ -246,6 +252,32 @@ type SpotNewOrderRequestParams struct {
Type SpotNewOrderRequestParamsType `json:"type"` // 订单类型, buy-market: 市价买, sell-market: 市价卖, buy-limit: 限价买, sell-limit: 限价卖
}
// DepositAddress stores the users deposit address info
type DepositAddress struct {
Currency string `json:"currency"`
Address string `json:"address"`
AddressTag string `json:"addressTag"`
Chain string `json:"chain"`
}
// ChainQuota stores the users currency chain quota
type ChainQuota struct {
Chain string `json:"chain"`
MaxWithdrawAmount float64 `json:"maxWithdrawAmt,string"`
WithdrawQuotaPerDay float64 `json:"withdrawQuotaPerDay,string"`
RemainingWithdrawQuotaPerDay float64 `json:"remainWithdrawQuotaPerDay,string"`
WithdrawQuotaPerYear float64 `json:"withdrawQuotaPerYear,string"`
RemainingWithdrawQuotaPerYear float64 `json:"remainWithdrawQuotaPerYear,string"`
WithdrawQuotaTotal float64 `json:"withdrawQuotaTotal,string"`
RemainingWithdrawQuotaTotal float64 `json:"remainWithdrawQuotaTotal,string"`
}
// WithdrawQuota stores the users withdraw quotas
type WithdrawQuota struct {
Currency string `json:"currency"`
Chains []ChainQuota `json:"chains"`
}
// SpotNewOrderRequestParamsType order type
type SpotNewOrderRequestParamsType string

View File

@@ -87,6 +87,7 @@ func (h *HUOBI) SetDefaults() {
CancelOrders: true,
CancelOrder: true,
SubmitOrder: true,
CryptoDeposit: true,
CryptoWithdrawal: true,
TradeFee: true,
},
@@ -652,7 +653,8 @@ func (h *HUOBI) GetOrderInfo(orderID string) (order.Detail, error) {
// GetDepositAddress returns a deposit address for a specified currency
func (h *HUOBI) GetDepositAddress(cryptocurrency currency.Code, accountID string) (string, error) {
return "", common.ErrFunctionNotSupported
resp, err := h.QueryDepositAddress(cryptocurrency.Lower().String())
return resp.Address, err
}
// WithdrawCryptocurrencyFunds returns a withdrawal ID when a withdrawal is

2
go.mod
View File

@@ -23,9 +23,7 @@ require (
github.com/urfave/cli v1.22.2
github.com/volatiletech/null v8.0.0+incompatible
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5
golang.org/x/net v0.0.0-20190606173856-1492cefac77f // indirect
golang.org/x/sys v0.0.0-20191003212358-c178f38b412c // indirect
google.golang.org/genproto v0.0.0-20191002211648-c459b9ce5143
google.golang.org/grpc v1.27.0
gopkg.in/yaml.v2 v2.2.4 // indirect
)

17
go.sum
View File

@@ -69,9 +69,11 @@ github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y
github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw=
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
@@ -85,8 +87,6 @@ github.com/grpc-ecosystem/go-grpc-middleware v1.1.0 h1:THDBEeQ9xZ8JEaCLyLQqXMMdR
github.com/grpc-ecosystem/go-grpc-middleware v1.1.0/go.mod h1:f5nM7jw/oeRSadq3xCzHAvxcr8HZnzsqU6ILg/0NiiE=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/grpc-ecosystem/grpc-gateway v1.11.3 h1:h8+NsYENhxNTuq+dobk3+ODoJtwY4Fu0WQXsxJfL8aM=
github.com/grpc-ecosystem/grpc-gateway v1.11.3/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/grpc-ecosystem/grpc-gateway v1.12.2 h1:D0EVSTwQoQOyfY35QNSuPJA4jpZRtkoGYWQMB7XNg5o=
github.com/grpc-ecosystem/grpc-gateway v1.12.2/go.mod h1:8XEsbTttt/W+VvjtQhLACqCisSPWTxCZ7sBRjU6iH9c=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
@@ -94,6 +94,7 @@ github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kat-co/vala v0.0.0-20170210184112-42e1d8b61f12 h1:DQVOxR9qdYEybJUr/c7ku34r3PfajaMYXZwgDM7KuSk=
@@ -116,8 +117,6 @@ github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mattn/go-sqlite3 v1.11.0 h1:LDdKkqtYlom37fkvqs8rMPFKAMe8+SgjbwZ6ex1/A/Q=
github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/mattn/go-sqlite3 v1.13.0 h1:LnJI81JidiW9r7pS/hXe6cFeO5EXNq7KbfvoJLRI69c=
github.com/mattn/go-sqlite3 v1.13.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
@@ -160,7 +159,9 @@ github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5I
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
@@ -187,6 +188,7 @@ github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
@@ -199,8 +201,6 @@ github.com/toorop/go-pusher v0.0.0-20180521062818-4521e2eb39fb h1:9kcmLvQdiIecpg
github.com/toorop/go-pusher v0.0.0-20180521062818-4521e2eb39fb/go.mod h1:VTLqNCX1tXrur6pdIRCl8Q90FR7nw/mEBdyMkWMcsb0=
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.22.2 h1:gsqYFH8bb9ekPA12kRo0hfjngWQjkJPlN9R0N78BoUo=
github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/volatiletech/inflect v0.0.0-20170731032912-e7201282ae8d h1:gI4/tqP6lCY5k6Sg+4k9qSoBXmPwG+xXgMpK7jivD4M=
@@ -233,8 +233,7 @@ golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73r
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190606173856-1492cefac77f h1:IWHgpgFqnL5AhBUBZSgBdjl2vkQUEzcY+JNKWfcgAU0=
golang.org/x/net v0.0.0-20190606173856-1492cefac77f/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20191002035440-2ec189313ef0 h1:2mqDk8w/o6UmeUCu5Qiq2y7iMf6anbx+YA8d1JFoFrs=
golang.org/x/net v0.0.0-20191002035440-2ec189313ef0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@@ -277,8 +276,6 @@ google.golang.org/genproto v0.0.0-20191002211648-c459b9ce5143 h1:tikhlQEJeezbnu0
google.golang.org/genproto v0.0.0-20191002211648-c459b9ce5143/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.21.1 h1:j6XxA85m/6txkUCHvzlV5f+HBNl/1r5cZ2A/3IEFOO8=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA=
google.golang.org/grpc v1.27.0 h1:rRYRFMVgRv6E0D70Skyfsr28tDXIuuPZyWGMPdMcnXg=