From 3f742866ad62ec891ebf48e99075a8191b73c5d4 Mon Sep 17 00:00:00 2001 From: PFC <81114960+PFC-developer@users.noreply.github.com> Date: Tue, 6 Aug 2024 08:35:57 -0500 Subject: [PATCH 1/4] fix: helix requires uppercase, and doesn't provide volume --- oracle/provider/helix.go | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/oracle/provider/helix.go b/oracle/provider/helix.go index 4c991f3..9ffdc33 100644 --- a/oracle/provider/helix.go +++ b/oracle/provider/helix.go @@ -3,6 +3,7 @@ package provider import ( "context" "encoding/json" + "strings" "time" "price-feeder/oracle/types" @@ -86,7 +87,7 @@ func (p *HelixProvider) Poll() error { p.setTickerPrice( market.Market.Ticker, strToDec(market.MidPriceAndTob.Price), - sdk.ZeroDec(), + sdk.OneDec(), // helix doesn't appear to report volume timestamp, ) } @@ -108,7 +109,13 @@ func (p *HelixProvider) GetMarkets() ([]HelixMarket, error) { return nil, err } - return response.Markets, nil + var markets []HelixMarket + for _, market := range response.Markets { + market.Market.Ticker = strings.ToUpper(market.Market.Ticker) + markets = append(markets, market) + } + + return markets, nil } func (p *HelixProvider) GetAvailablePairs() (map[string]struct{}, error) { From 03749b68bf40d7cd9f1a7fbbd3284010964e965e Mon Sep 17 00:00:00 2001 From: PFC <81114960+PFC-developer@users.noreply.github.com> Date: Wed, 7 Aug 2024 15:13:14 -0500 Subject: [PATCH 2/4] fix: helix requires decimal sizes. --- cmd/price-feeder.go | 1 + config/config.go | 5 +++ go.mod | 1 + go.sum | 1 + oracle/oracle.go | 15 ++++++--- oracle/provider/helix.go | 70 ++++++++++++++++++++++++++++++++++++++-- 6 files changed, 86 insertions(+), 7 deletions(-) diff --git a/cmd/price-feeder.go b/cmd/price-feeder.go index 48bbf21..211724c 100644 --- a/cmd/price-feeder.go +++ b/cmd/price-feeder.go @@ -234,6 +234,7 @@ func priceFeederCmdHandler(cmd *cobra.Command, args []string) error { cfg.Decimals, cfg.Periods, volumeDatabase, + cfg.Helix, ) telemetryCfg := telemetry.Config{} diff --git a/config/config.go b/config/config.go index 8ca5639..f1fb14e 100644 --- a/config/config.go +++ b/config/config.go @@ -119,6 +119,7 @@ type ( Decimals map[string]map[string]int `toml:"decimals"` Periods map[string]map[string]int `toml:"periods"` UrlSets map[string]UrlSet `toml:"url_set"` + Helix Helix `toml:"helix"` } // Server defines the API server configuration. @@ -230,6 +231,10 @@ type ( UrlSet struct { Urls []string `toml:"urls"` } + + Helix struct { + TokenFile string `toml:"token_file"` + } ) // telemetryValidation is custom validation for the Telemetry struct. diff --git a/go.mod b/go.mod index 4b53d8b..3bab1a6 100644 --- a/go.mod +++ b/go.mod @@ -86,6 +86,7 @@ require ( github.com/go-logfmt/logfmt v0.6.0 // indirect github.com/go-playground/locales v0.14.0 // indirect github.com/go-playground/universal-translator v0.18.0 // indirect + github.com/go-stack/stack v1.8.0 // indirect github.com/go-toolsmith/astcast v1.0.0 // indirect github.com/go-toolsmith/astcopy v1.0.2 // indirect github.com/go-toolsmith/astequal v1.0.3 // indirect diff --git a/go.sum b/go.sum index e31edef..8a63eb8 100644 --- a/go.sum +++ b/go.sum @@ -310,6 +310,7 @@ github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4 github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-toolsmith/astcast v1.0.0 h1:JojxlmI6STnFVG9yOImLeGREv8W2ocNUM+iOhR6jE7g= diff --git a/oracle/oracle.go b/oracle/oracle.go index 6f8658d..6dfde93 100644 --- a/oracle/oracle.go +++ b/oracle/oracle.go @@ -60,9 +60,10 @@ type Oracle struct { periods map[string]map[string]int volumeDatabase *sql.DB - mtx sync.RWMutex - prices map[string]sdk.Dec - healthchecks map[string]http.Client + mtx sync.RWMutex + prices map[string]sdk.Dec + healthchecks map[string]http.Client + helixTokenFile string } func New( @@ -82,6 +83,7 @@ func New( decimals map[string]map[string]int, periods map[string]map[string]int, volumeDatabase *sql.DB, + helix config.Helix, ) *Oracle { providerPairs := make(map[provider.Name][]types.CurrencyPair) for _, pair := range currencyPairs { @@ -125,6 +127,7 @@ func New( decimals: decimals, periods: periods, volumeDatabase: volumeDatabase, + helixTokenFile: helix.TokenFile, } } @@ -178,7 +181,7 @@ func (o *Oracle) GetPrices() sdk.DecCoins { // SetPrices retrieves all the prices and candles from our set of providers as // determined in the config. If candles are available, uses TVWAP in order // to determine prices. If candles are not available, uses the most recent prices -// with VWAP. Warns the the user of any missing prices, and filters out any faulty +// with VWAP. Warns the user of any missing prices, and filters out any faulty // providers which do not report prices or candles within 2𝜎 of the others. func (o *Oracle) SetPrices(ctx context.Context) error { g := new(errgroup.Group) @@ -206,6 +209,7 @@ func (o *Oracle) SetPrices(ctx context.Context) error { providerName, o.logger, endpoint, + o.helixTokenFile, o.providerPairs[providerName]..., ) if err != nil { @@ -383,6 +387,7 @@ func NewProvider( providerName provider.Name, logger zerolog.Logger, endpoint provider.Endpoint, + helixTokenFile string, providerPairs ...types.CurrencyPair, ) (provider.Provider, error) { endpoint.Name = providerName @@ -431,7 +436,7 @@ func NewProvider( case provider.ProviderGate: return provider.NewGateProvider(ctx, providerLogger, endpoint, providerPairs...) case provider.ProviderHelix: - return provider.NewHelixProvider(ctx, providerLogger, endpoint, providerPairs...) + return provider.NewHelixProvider(ctx, providerLogger, endpoint, helixTokenFile, providerPairs...) case provider.ProviderHitBtc: return provider.NewHitBtcProvider(ctx, providerLogger, endpoint, providerPairs...) case provider.ProviderHuobi: diff --git a/oracle/provider/helix.go b/oracle/provider/helix.go index 9ffdc33..3e50ce6 100644 --- a/oracle/provider/helix.go +++ b/oracle/provider/helix.go @@ -3,6 +3,8 @@ package provider import ( "context" "encoding/json" + "io" + "net/http" "strings" "time" @@ -27,6 +29,7 @@ type ( HelixProvider struct { provider contracts map[string]string + token map[string]HelixToken } HelixMarketsResponse struct { @@ -35,18 +38,29 @@ type ( HelixMarket struct { Market struct { - Ticker string `json:"ticker"` + Ticker string `json:"ticker"` + BaseDenom string `json:"base_denom"` + QuoteDenom string `json:"quote_denom"` } `json:"market"` MidPriceAndTob struct { Price string `json:"mid_price"` } `json:"mid_price_and_tob"` } + + HelixToken struct { + Address string `json:"address"` + Decimals uint64 `json:"decimals"` + Symbol string `json:"symbol"` + Name string `json:"name"` + Denom string `json:"denom"` + } ) func NewHelixProvider( ctx context.Context, logger zerolog.Logger, endpoints Endpoint, + helixTokenFile string, pairs ...types.CurrencyPair, ) (*HelixProvider, error) { provider := &HelixProvider{} @@ -59,6 +73,26 @@ func NewHelixProvider( nil, ) + res, err := http.Get(helixTokenFile) + if err != nil { + return nil, err + } + body, err := io.ReadAll(res.Body) + if err != nil { + panic(err.Error()) + } + + var response []HelixToken + err = json.Unmarshal(body, &response) + if err != nil { + return nil, err + } + tokens := map[string]HelixToken{} + for _, helixToken := range response { + tokens[strings.ToUpper(helixToken.Denom)] = helixToken + } + provider.token = tokens + provider.contracts = provider.endpoints.ContractAddresses availablePairs, _ := provider.GetAvailablePairs() @@ -84,9 +118,41 @@ func (p *HelixProvider) Poll() error { continue } + pair, found := p.getPair(market.Market.Ticker) + if !found { + continue + } + + rawPrice := strToDec(market.MidPriceAndTob.Price) + if rawPrice.IsNil() { + p.logger.Info().Str("ticker", market.Market.Ticker).Msg("No Price available from Helix") + continue + } + baseToken, found := p.token[strings.ToUpper(market.Market.BaseDenom)] + if !found { + p.logger.Warn().Str("token", pair.Base).Str("denom", market.Market.BaseDenom).Msg("token not found in helix Token list") + continue + } + quoteToken, found := p.token[strings.ToUpper(market.Market.QuoteDenom)] + if !found { + p.logger.Warn().Str("token", pair.Quote).Str("denom", market.Market.QuoteDenom).Msg("token not found in helix Token list") + continue + } + var decimalDifference uint64 + if quoteToken.Decimals > baseToken.Decimals { + decimalDifference = quoteToken.Decimals - baseToken.Decimals + } else { + decimalDifference = baseToken.Decimals - quoteToken.Decimals + } + multiplier := sdk.NewDec(10).Power(decimalDifference) + p.logger.Warn().Uint64("decimalDifference", decimalDifference).Str("ticker", market.Market.Ticker). + Msg("decimalDifference") + + rawPrice = rawPrice.Mul(multiplier) + p.setTickerPrice( market.Market.Ticker, - strToDec(market.MidPriceAndTob.Price), + rawPrice, sdk.OneDec(), // helix doesn't appear to report volume timestamp, ) From 33180d6bbb745509cccf5292e066b65be87cb594 Mon Sep 17 00:00:00 2001 From: PFC <81114960+PFC-developer@users.noreply.github.com> Date: Wed, 7 Aug 2024 17:09:19 -0500 Subject: [PATCH 3/4] fix: quote > base .. * 1/10^x vs 10^x --- oracle/provider/helix.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/oracle/provider/helix.go b/oracle/provider/helix.go index 3e50ce6..6cd386d 100644 --- a/oracle/provider/helix.go +++ b/oracle/provider/helix.go @@ -139,17 +139,20 @@ func (p *HelixProvider) Poll() error { continue } var decimalDifference uint64 + var multiplier sdk.Dec if quoteToken.Decimals > baseToken.Decimals { decimalDifference = quoteToken.Decimals - baseToken.Decimals + multiplier = sdk.NewDec(10).Power(decimalDifference) + rawPrice = rawPrice.Mul(invertDec(multiplier)) } else { decimalDifference = baseToken.Decimals - quoteToken.Decimals + multiplier = sdk.NewDec(10).Power(decimalDifference) + rawPrice = rawPrice.Mul(multiplier) } - multiplier := sdk.NewDec(10).Power(decimalDifference) + p.logger.Warn().Uint64("decimalDifference", decimalDifference).Str("ticker", market.Market.Ticker). Msg("decimalDifference") - rawPrice = rawPrice.Mul(multiplier) - p.setTickerPrice( market.Market.Ticker, rawPrice, From bcea003cd305945b7656e05cc0d259f1f59a2f0f Mon Sep 17 00:00:00 2001 From: PFC <81114960+PFC-developer@users.noreply.github.com> Date: Wed, 7 Aug 2024 19:17:50 -0500 Subject: [PATCH 4/4] fix: if buy/sell spread is >10% ignore price --- oracle/provider/helix.go | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/oracle/provider/helix.go b/oracle/provider/helix.go index 6cd386d..da2dce4 100644 --- a/oracle/provider/helix.go +++ b/oracle/provider/helix.go @@ -44,6 +44,8 @@ type ( } `json:"market"` MidPriceAndTob struct { Price string `json:"mid_price"` + Buy string `json:"best_buy_price"` + Sell string `json:"best_sell_price"` } `json:"mid_price_and_tob"` } @@ -128,6 +130,24 @@ func (p *HelixProvider) Poll() error { p.logger.Info().Str("ticker", market.Market.Ticker).Msg("No Price available from Helix") continue } + buyPrice := strToDec(market.MidPriceAndTob.Buy) + if buyPrice.IsNil() { + p.logger.Info().Str("ticker", market.Market.Ticker).Msg("No Buy Price available from Helix") + continue + } + sellPrice := strToDec(market.MidPriceAndTob.Sell) + if sellPrice.IsNil() { + p.logger.Info().Str("ticker", market.Market.Ticker).Msg("No Sell Price available from Helix") + continue + } + + // |((sell-buy)/sell)*100| + ratio := sellPrice.Sub(buyPrice).Quo(sellPrice).Mul(sdk.NewDec(100)).Abs() + if ratio.GT(sdk.NewDec(10)) { + p.logger.Warn().Str("ticker", market.Market.Ticker).Msg("buy/sell spread is larger than 10%. Skipping") + continue + } + baseToken, found := p.token[strings.ToUpper(market.Market.BaseDenom)] if !found { p.logger.Warn().Str("token", pair.Base).Str("denom", market.Market.BaseDenom).Msg("token not found in helix Token list") @@ -150,9 +170,6 @@ func (p *HelixProvider) Poll() error { rawPrice = rawPrice.Mul(multiplier) } - p.logger.Warn().Uint64("decimalDifference", decimalDifference).Str("ticker", market.Market.Ticker). - Msg("decimalDifference") - p.setTickerPrice( market.Market.Ticker, rawPrice,