131 lines
3.7 KiB
Go
131 lines
3.7 KiB
Go
package prices
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"time"
|
|
|
|
"github.com/shopspring/decimal"
|
|
wormscanNotionalCache "github.com/wormhole-foundation/wormhole-explorer/common/client/cache/notional"
|
|
"github.com/wormhole-foundation/wormhole-explorer/common/domain"
|
|
sdk "github.com/wormhole-foundation/wormhole/sdk/vaa"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
var (
|
|
ErrTokenNotFound = errors.New("token not found")
|
|
)
|
|
|
|
type Price struct {
|
|
CoingeckoID string `json:"coingeckoId"`
|
|
Symbol string `json:"symbol"`
|
|
Price string `json:"price"`
|
|
Datetime time.Time `json:"dateTime"`
|
|
}
|
|
|
|
// PriceService provides an interface to interact with prices.
|
|
type PriceService struct {
|
|
priceRepository *PriceRepository
|
|
tokenProvider *domain.TokenProvider
|
|
notionalCache wormscanNotionalCache.NotionalLocalCacheReadable
|
|
logger *zap.Logger
|
|
}
|
|
|
|
// NewPriceService creates a new price service.
|
|
func NewPriceService(priceRepository *PriceRepository,
|
|
tokenProvider *domain.TokenProvider,
|
|
notionalCache wormscanNotionalCache.NotionalLocalCacheReadable,
|
|
logger *zap.Logger) *PriceService {
|
|
return &PriceService{
|
|
priceRepository: priceRepository,
|
|
tokenProvider: tokenProvider,
|
|
notionalCache: notionalCache,
|
|
logger: logger.With(zap.String("module", "priceService")),
|
|
}
|
|
}
|
|
|
|
// GetPrice returns the price of a token at a given datetime.
|
|
func (s *PriceService) GetPrice(ctx context.Context, tokenChainID sdk.ChainID, tokenAddress string, datetime time.Time) (*Price, error) {
|
|
log := s.logger.With(zap.Uint16("chainID", uint16(tokenChainID)), zap.String("tokenAddress", tokenAddress))
|
|
token, found := s.tokenProvider.GetTokenByAddress(tokenChainID, tokenAddress)
|
|
if !found {
|
|
log.Warn("Token not found")
|
|
return nil, ErrTokenNotFound
|
|
}
|
|
|
|
v, err := s.GetPriceBySymbol(ctx, token, datetime, log)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &Price{
|
|
CoingeckoID: token.CoingeckoID,
|
|
Symbol: token.Symbol.String(),
|
|
Price: v.price.String(),
|
|
Datetime: v.datetime,
|
|
}, nil
|
|
}
|
|
|
|
func (s *PriceService) GetPriceByCoingeckoID(ctx context.Context, coingeckoID string, datetime time.Time) (*Price, error) {
|
|
log := s.logger.With(zap.String("coingeckoId", coingeckoID))
|
|
token, found := s.tokenProvider.GetTokenByCoingeckoID(coingeckoID)
|
|
if !found {
|
|
log.Warn("Token not found")
|
|
return nil, ErrTokenNotFound
|
|
}
|
|
v, err := s.GetPriceBySymbol(ctx, token, datetime, log)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &Price{
|
|
CoingeckoID: coingeckoID,
|
|
Symbol: token.Symbol.String(),
|
|
Price: v.price.String(),
|
|
Datetime: v.datetime,
|
|
}, nil
|
|
}
|
|
|
|
type priceSymbol struct {
|
|
price decimal.Decimal
|
|
datetime time.Time
|
|
}
|
|
|
|
func (s *PriceService) GetPriceBySymbol(ctx context.Context, token *domain.TokenMetadata, datetime time.Time, log *zap.Logger) (*priceSymbol, error) {
|
|
if token.CoingeckoID == "" {
|
|
log.Warn("CoingeckoID not found")
|
|
return nil, ErrTokenNotFound
|
|
}
|
|
|
|
dayDatetime := datetime.Truncate(24 * time.Hour)
|
|
cachePrice, err := s.notionalCache.Get(token.GetTokenID())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
diffCachePrice := cachePrice.UpdatedAt.Sub(datetime).Abs()
|
|
diffDayPrice := dayDatetime.Sub(datetime).Abs()
|
|
var price decimal.Decimal
|
|
var priceDatetime time.Time
|
|
if diffCachePrice > diffDayPrice {
|
|
p, err := s.priceRepository.Find(ctx, token.CoingeckoID, dayDatetime)
|
|
if err != nil {
|
|
if err == ErrPriceNotFound {
|
|
return nil, ErrTokenNotFound
|
|
}
|
|
log.Error("Failed to find price", zap.Error(err))
|
|
return nil, err
|
|
}
|
|
price, err = decimal.NewFromString(p.Price)
|
|
if err != nil {
|
|
log.Error("Failed to parse price", zap.Error(err))
|
|
return nil, err
|
|
}
|
|
priceDatetime = p.Datetime
|
|
} else {
|
|
price = cachePrice.NotionalUsd
|
|
priceDatetime = cachePrice.UpdatedAt
|
|
}
|
|
return &priceSymbol{price: price, datetime: priceDatetime}, nil
|
|
}
|