diff --git a/cmd/version.go b/cmd/version.go index 579a416a..05157e1d 100644 --- a/cmd/version.go +++ b/cmd/version.go @@ -37,7 +37,7 @@ func getVersionCmd() *cobra.Command { versionCmd := &cobra.Command{ Use: "version", Short: "Print binary version information", - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(_ *cobra.Command, _ []string) error { verInfo := versionInfo{ Version: Version, Commit: Commit, diff --git a/config/config_test.go b/config/config_test.go index 363bb5a9..cb66e6ca 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -627,7 +627,7 @@ global-labels = [["chain-id", "ojo-local-testnet"]] require.Equal(t, provider.ProviderBinance, cfg.CurrencyPairs[0].Providers[1]) } -func TestCheckProviderMins_Valid(t *testing.T) { +func TestCheckProviderMins_Invalid(t *testing.T) { tmpFile, err := ioutil.TempFile("", "price-feeder*.toml") require.NoError(t, err) defer os.Remove(tmpFile.Name()) @@ -647,7 +647,6 @@ quote = "USDT" providers = [ "kraken", "binance", - "huobi" ] [[currency_pairs]] @@ -694,10 +693,10 @@ enabled = false logger := zerolog.New(zerolog.ConsoleWriter{Out: os.Stderr}).Level(zerolog.InfoLevel).With().Timestamp().Logger() err = config.CheckProviderMins(context.TODO(), logger, cfg) - require.NoError(t, err) + require.EqualError(t, err, "must have at least 3 providers for ATOM") } -func TestCheckProviderMins_Invalid(t *testing.T) { +func TestCheckProviderMins_Valid(t *testing.T) { tmpFile, err := ioutil.TempFile("", "price-feeder*.toml") require.NoError(t, err) defer os.Remove(tmpFile.Name()) @@ -717,6 +716,7 @@ quote = "USDT" providers = [ "kraken", "binance", + "huobi" ] [[currency_pairs]] @@ -763,7 +763,7 @@ enabled = false logger := zerolog.New(zerolog.ConsoleWriter{Out: os.Stderr}).Level(zerolog.InfoLevel).With().Timestamp().Logger() err = config.CheckProviderMins(context.TODO(), logger, cfg) - require.EqualError(t, err, "must have at least 3 providers for ATOM") + require.NoError(t, err) } func TestProviderWithAPIKey_Valid(t *testing.T) { diff --git a/config/load.go b/config/load.go index b95e614d..a1a1c0de 100644 --- a/config/load.go +++ b/config/load.go @@ -32,7 +32,7 @@ func LoadConfigFromFlags(nodeConfigPath, dirPrefix string) (Config, error) { // filesInFolder returns a slice of all file paths in a given folder. func filesInFolder(folder string) ([]string, error) { var files []string - err := filepath.Walk(folder, func(path string, info os.FileInfo, err error) error { + err := filepath.Walk(folder, func(path string, info os.FileInfo, _ error) error { if info != nil && !info.IsDir() { files = append(files, path) } diff --git a/oracle/provider/astroport_test.go b/oracle/provider/astroport_test.go new file mode 100644 index 00000000..f99b7fdf --- /dev/null +++ b/oracle/provider/astroport_test.go @@ -0,0 +1,41 @@ +package provider + +// import ( +// "context" +// "os" +// "testing" +// "time" + +// "github.com/ojo-network/price-feeder/oracle/types" +// "github.com/rs/zerolog" +// "github.com/stretchr/testify/require" +// ) + +// // TestAstroportProvider_GetTickers tests the polling process. +// // TODO: Make this more comprehensive. +// // +// // Ref: https://github.com/ojo-network/price-feeder/issues/317 +// func TestAstroportProvider_GetTickers(t *testing.T) { +// ctx := context.Background() +// pairs := []types.CurrencyPair{{ +// Base: "STINJ", +// Quote: "INJ", +// }} +// p, err := NewAstroportProvider( +// ctx, +// zerolog.New(os.Stdout).With().Timestamp().Logger(), +// Endpoint{}, +// pairs..., +// ) +// require.NoError(t, err) +// availPairs, err := p.GetAvailablePairs() +// require.NoError(t, err) +// require.NotEmpty(t, availPairs) + +// p.StartConnections() +// time.Sleep(10 * time.Second) + +// res, err := p.GetTickerPrices(pairs...) +// require.NoError(t, err) +// require.NotEmpty(t, res) +// } diff --git a/oracle/provider/mexc.go b/oracle/provider/mexc.go index 9d1add28..abb0a8ae 100644 --- a/oracle/provider/mexc.go +++ b/oracle/provider/mexc.go @@ -3,23 +3,25 @@ package provider import ( "context" "encoding/json" + "fmt" + "math/big" "net/http" "net/url" "strings" "sync" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/gorilla/websocket" "github.com/ojo-network/price-feeder/oracle/types" "github.com/rs/zerolog" - - "github.com/ojo-network/ojo/util/decmath" ) const ( mexcWSHost = "wbs.mexc.com" - mexcWSPath = "/raw/ws" - mexcRestHost = "https://www.mexc.com" - mexcRestPath = "/open/api/v2/market/ticker" + mexcWSPath = "/ws" + mexcRestHost = "https://api.mexc.com/" + mexcRestPath = "/api/v3/ticker/price" ) var _ Provider = (*MexcProvider)(nil) @@ -28,9 +30,7 @@ type ( // MexcProvider defines an Oracle provider implemented by the Mexc public // API. // - // REF: https://mxcdevelop.github.io/apidocs/spot_v2_en/#ticker-information - // REF: https://mxcdevelop.github.io/apidocs/spot_v2_en/#k-line - // REF: https://mxcdevelop.github.io/apidocs/spot_v2_en/#overview + // REF: https://mexcdevelop.github.io/apidocs/spot_v3_en/ MexcProvider struct { wsc *WebsocketController logger zerolog.Logger @@ -42,41 +42,43 @@ type ( // MexcTickerResponse is the ticker price response object. MexcTickerResponse struct { - Symbol map[string]MexcTicker `json:"data"` // e.x. ATOM_USDT + Symbol string `json:"s"` // e.x. ATOMUSDT + Metadata MexcTicker `json:"d"` // Metadata for ticker } MexcTicker struct { - LastPrice float64 `json:"p"` // Last price ex.: 0.0025 - Volume float64 `json:"v"` // Total traded base asset volume ex.: 1000 + LastPrice string `json:"b"` // Best bid price ex.: 0.0025 + Volume string `json:"B"` // Best bid qty ex.: 1000 } // MexcCandle is the candle websocket response object. MexcCandleResponse struct { - Symbol string `json:"symbol"` // Symbol ex.: ATOM_USDT - Metadata MexcCandle `json:"data"` // Metadata for candle + Symbol string `json:"s"` // Symbol ex.: ATOMUSDT + Metadata MexcCandle `json:"d"` // Metadata for candle } MexcCandle struct { - Close float64 `json:"c"` // Price at close - TimeStamp int64 `json:"t"` // Close time in unix epoch ex.: 1645756200000 - Volume float64 `json:"v"` // Volume during period + Data MexcCandleData `json:"k"` + } + MexcCandleData struct { + Close *big.Float `json:"c"` // Price at close + TimeStamp int64 `json:"T"` // Close time in unix epoch ex.: 1645756200 + Volume *big.Float `json:"v"` // Volume during period } // MexcCandleSubscription Msg to subscribe all the candle channels. MexcCandleSubscription struct { - OP string `json:"op"` // kline - Symbol string `json:"symbol"` // streams to subscribe ex.: atom_usdt - Interval string `json:"interval"` // Min1、Min5、Min15、Min30 + Method string `json:"method"` // ex.: SUBSCRIPTION + Params []string `json:"params"` // ex.: [spot@public.kline.v3.api@@] } // MexcTickerSubscription Msg to subscribe all the ticker channels. MexcTickerSubscription struct { - OP string `json:"op"` // kline + Method string `json:"method"` // ex.: SUBSCRIPTION + Params []string `json:"params"` // ex.: [spot@public.bookTicker.v3.api@] } // MexcPairSummary defines the response structure for a Mexc pair // summary. - MexcPairSummary struct { - Data []MexcPairData `json:"data"` - } + MexcPairSummary []MexcPairData // MexcPairData defines the data response structure for an Mexc pair. MexcPairData struct { @@ -144,12 +146,13 @@ func (p *MexcProvider) StartConnections() { } func (p *MexcProvider) getSubscriptionMsgs(cps ...types.CurrencyPair) []interface{} { - subscriptionMsgs := make([]interface{}, 0, len(cps)+1) + subscriptionMsgs := make([]interface{}, 0, len(cps)*2) + mexcPairs := make([]string, 0, len(cps)) for _, cp := range cps { - mexcPair := currencyPairToMexcPair(cp) - subscriptionMsgs = append(subscriptionMsgs, newMexcCandleSubscriptionMsg(mexcPair)) + mexcPairs = append(mexcPairs, currencyPairToMexcPair(cp)) } - subscriptionMsgs = append(subscriptionMsgs, newMexcTickerSubscriptionMsg()) + subscriptionMsgs = append(subscriptionMsgs, newMexcCandleSubscriptionMsg(mexcPairs)) + subscriptionMsgs = append(subscriptionMsgs, newMexcTickerSubscriptionMsg(mexcPairs)) return subscriptionMsgs } @@ -195,20 +198,14 @@ func (p *MexcProvider) messageReceived(_ int, _ *WebsocketConnection, bz []byte) ) tickerErr = json.Unmarshal(bz, &tickerResp) - for _, cp := range p.subscribedPairs { - mexcPair := currencyPairToMexcPair(cp) - if tickerResp.Symbol[mexcPair].LastPrice != 0 { - p.setTickerPair( - tickerResp.Symbol[mexcPair], - mexcPair, - ) - telemetryWebsocketMessage(ProviderMexc, MessageTypeTicker) - return - } + if tickerResp.Metadata.LastPrice != "" { + p.setTickerPair(tickerResp.Metadata, tickerResp.Symbol) + telemetryWebsocketMessage(ProviderMexc, MessageTypeTicker) + return } candleErr = json.Unmarshal(bz, &candleResp) - if candleResp.Metadata.Close != 0 { + if candleResp.Metadata.Data.Close != nil { p.setCandlePair(candleResp.Metadata, candleResp.Symbol) telemetryWebsocketMessage(ProviderMexc, MessageTypeCandle) return @@ -224,11 +221,11 @@ func (p *MexcProvider) messageReceived(_ int, _ *WebsocketConnection, bz []byte) } func (mt MexcTicker) toTickerPrice() (types.TickerPrice, error) { - price, err := decmath.NewDecFromFloat(mt.LastPrice) + price, err := sdk.NewDecFromStr(mt.LastPrice) if err != nil { return types.TickerPrice{}, err } - volume, err := decmath.NewDecFromFloat(mt.Volume) + volume, err := sdk.NewDecFromStr(mt.Volume) if err != nil { return types.TickerPrice{}, err } @@ -241,19 +238,20 @@ func (mt MexcTicker) toTickerPrice() (types.TickerPrice, error) { } func (mc MexcCandle) toCandlePrice() (types.CandlePrice, error) { - close, err := decmath.NewDecFromFloat(mc.Close) + close, err := sdk.NewDecFromStr(mc.Data.Close.String()) if err != nil { return types.CandlePrice{}, err } - volume, err := decmath.NewDecFromFloat(mc.Volume) + volume, err := sdk.NewDecFromStr(mc.Data.Volume.String()) if err != nil { return types.CandlePrice{}, err } + candle := types.CandlePrice{ Price: close, Volume: volume, // convert seconds -> milli - TimeStamp: SecondsToMilli(mc.TimeStamp), + TimeStamp: SecondsToMilli(mc.Data.TimeStamp), } return candle, nil } @@ -279,9 +277,9 @@ func (p *MexcProvider) GetAvailablePairs() (map[string]struct{}, error) { return nil, err } - availablePairs := make(map[string]struct{}, len(pairsSummary.Data)) - for _, pairName := range pairsSummary.Data { - availablePairs[strings.ToUpper(strings.ReplaceAll(pairName.Symbol, "_", ""))] = struct{}{} + availablePairs := make(map[string]struct{}, len(pairsSummary)) + for _, pairName := range pairsSummary { + availablePairs[strings.ToUpper(pairName.Symbol)] = struct{}{} } return availablePairs, nil @@ -290,21 +288,29 @@ func (p *MexcProvider) GetAvailablePairs() (map[string]struct{}, error) { // currencyPairToMexcPair receives a currency pair and return mexc // ticker symbol atomusdt@ticker. func currencyPairToMexcPair(cp types.CurrencyPair) string { - return strings.ToUpper(cp.Base + "_" + cp.Quote) + return strings.ToUpper(cp.Base + cp.Quote) } // newMexcCandleSubscriptionMsg returns a new candle subscription Msg. -func newMexcCandleSubscriptionMsg(param string) MexcCandleSubscription { +func newMexcCandleSubscriptionMsg(symbols []string) MexcCandleSubscription { + params := make([]string, len(symbols)) + for i, symbol := range symbols { + params[i] = fmt.Sprintf("spot@public.kline.v3.api@%s@Min1", symbol) + } return MexcCandleSubscription{ - OP: "sub.kline", - Symbol: param, - Interval: "Min1", + Method: "SUBSCRIPTION", + Params: params, } } // newMexcTickerSubscriptionMsg returns a new ticker subscription Msg. -func newMexcTickerSubscriptionMsg() MexcTickerSubscription { +func newMexcTickerSubscriptionMsg(symbols []string) MexcTickerSubscription { + params := make([]string, len(symbols)) + for i, symbol := range symbols { + params[i] = fmt.Sprintf("spot@public.bookTicker.v3.api@%s", symbol) + } return MexcTickerSubscription{ - OP: "sub.overview", + Method: "SUBSCRIPTION", + Params: params, } } diff --git a/oracle/provider/mexc_test.go b/oracle/provider/mexc_test.go index 90ea04c1..f8a1392b 100644 --- a/oracle/provider/mexc_test.go +++ b/oracle/provider/mexc_test.go @@ -25,7 +25,7 @@ func TestMexcProvider_GetTickerPrices(t *testing.T) { volume := sdk.MustNewDecFromStr("2396974.02000000") tickerMap := map[string]types.TickerPrice{} - tickerMap["ATOM_USDT"] = types.TickerPrice{ + tickerMap["ATOMUSDT"] = types.TickerPrice{ Price: lastPrice, Volume: volume, } @@ -45,12 +45,12 @@ func TestMexcProvider_GetTickerPrices(t *testing.T) { volume := sdk.MustNewDecFromStr("2396974.02000000") tickerMap := map[string]types.TickerPrice{} - tickerMap["ATOM_USDT"] = types.TickerPrice{ + tickerMap["ATOMUSDT"] = types.TickerPrice{ Price: lastPriceAtom, Volume: volume, } - tickerMap["LUNA_USDT"] = types.TickerPrice{ + tickerMap["LUNAUSDT"] = types.TickerPrice{ Price: lastPriceLuna, Volume: volume, } @@ -77,7 +77,7 @@ func TestMexcProvider_GetTickerPrices(t *testing.T) { func TestMexcCurrencyPairToMexcPair(t *testing.T) { cp := types.CurrencyPair{Base: "ATOM", Quote: "USDT"} MexcSymbol := currencyPairToMexcPair(cp) - require.Equal(t, MexcSymbol, "ATOM_USDT") + require.Equal(t, MexcSymbol, "ATOMUSDT") } func TestMexcProvider_getSubscriptionMsgs(t *testing.T) { @@ -88,8 +88,8 @@ func TestMexcProvider_getSubscriptionMsgs(t *testing.T) { subMsgs := provider.getSubscriptionMsgs(cps...) msg, _ := json.Marshal(subMsgs[0]) - require.Equal(t, "{\"op\":\"sub.kline\",\"symbol\":\"ATOM_USDT\",\"interval\":\"Min1\"}", string(msg)) + require.Equal(t, "{\"method\":\"SUBSCRIPTION\",\"params\":[\"spot@public.kline.v3.api@ATOMUSDT@Min1\"]}", string(msg)) msg, _ = json.Marshal(subMsgs[1]) - require.Equal(t, "{\"op\":\"sub.overview\"}", string(msg)) + require.Equal(t, "{\"method\":\"SUBSCRIPTION\",\"params\":[\"spot@public.bookTicker.v3.api@ATOMUSDT\"]}", string(msg)) } diff --git a/oracle/types/currency.go b/oracle/types/currency.go index e52f7d23..d741d512 100644 --- a/oracle/types/currency.go +++ b/oracle/types/currency.go @@ -30,7 +30,7 @@ func MapPairsToSlice(mapPairs map[string]CurrencyPair) []CurrencyPair { return currencyPairs } -func (cp CurrencyPair) MarshalText() (text []byte, err error) { +func (cp CurrencyPair) MarshalText() ([]byte, error) { type noMethod CurrencyPair return json.Marshal(noMethod(cp)) } diff --git a/router/v1/router.go b/router/v1/router.go index deeb8852..84ecbb91 100644 --- a/router/v1/router.go +++ b/router/v1/router.go @@ -89,7 +89,7 @@ func (r *Router) RegisterRoutes(rtr *mux.Router, prefix string) { } func (r *Router) healthzHandler() http.HandlerFunc { - return func(w http.ResponseWriter, req *http.Request) { + return func(w http.ResponseWriter, _ *http.Request) { resp := HealthZResponse{ Status: StatusAvailable, } @@ -101,7 +101,7 @@ func (r *Router) healthzHandler() http.HandlerFunc { } func (r *Router) pricesHandler() http.HandlerFunc { - return func(w http.ResponseWriter, req *http.Request) { + return func(w http.ResponseWriter, _ *http.Request) { resp := PricesResponse{ Prices: r.oracle.GetPrices(), } @@ -111,7 +111,7 @@ func (r *Router) pricesHandler() http.HandlerFunc { } func (r *Router) candlePricesHandler() http.HandlerFunc { - return func(w http.ResponseWriter, req *http.Request) { + return func(w http.ResponseWriter, _ *http.Request) { resp := PricesPerProviderResponse{ Prices: r.oracle.GetTvwapPrices(), } @@ -121,7 +121,7 @@ func (r *Router) candlePricesHandler() http.HandlerFunc { } func (r *Router) tickerPricesHandler() http.HandlerFunc { - return func(w http.ResponseWriter, req *http.Request) { + return func(w http.ResponseWriter, _ *http.Request) { resp := PricesPerProviderResponse{ Prices: r.oracle.GetVwapPrices(), }