diff --git a/pkg/chain/chain.go b/pkg/chain/chain.go index ba4d58cb2..1a3ac6835 100644 --- a/pkg/chain/chain.go +++ b/pkg/chain/chain.go @@ -316,6 +316,27 @@ func (ch *Chain) Balances(address string, tokens []model.Token) (map[string]floa return balances, nil } +func (ch *Chain) TokenTotalSupply(address string, decimal int) (float64, error) { + v, err := ch.scan.TokenTotalSupply(address) + if err != nil { + return 0, err + } + + if v == nil { + return 0, nil + } + + str := v.Int().String() + f, ok := new(big.Float).SetString(str) + f.Quo(f, big.NewFloat(math.Pow10(decimal))) + if !ok { + return 0, nil + } + + supply, _ := f.Float64() + return supply, nil +} + // func (ch *Chain) RawBalances(address string, tokens []model.Token) (map[string]*big.Int, error) { // balances := make(map[string]*big.Int, 0) // for _, token := range tokens { diff --git a/pkg/entities/defi.go b/pkg/entities/defi.go index 842ee244b..c0d02cc68 100644 --- a/pkg/entities/defi.go +++ b/pkg/entities/defi.go @@ -20,6 +20,7 @@ import ( "github.com/defipod/mochi/pkg/logger" "github.com/defipod/mochi/pkg/model" baseerrs "github.com/defipod/mochi/pkg/model/errors" + "github.com/defipod/mochi/pkg/repo/chain" coingeckosupportedtokens "github.com/defipod/mochi/pkg/repo/coingecko_supported_tokens" "github.com/defipod/mochi/pkg/repo/token" usertokenpricealert "github.com/defipod/mochi/pkg/repo/user_token_price_alert" @@ -227,22 +228,80 @@ func (e *Entity) GetCoinData(coinID string, isDominanceChart bool) (*response.Ge // if no market cap data, get token supply to calculate it // support solana only for now - if data.AssetPlatformID == "solana" && data.Platforms != nil { - currency := "usd" - if _, ok := data.Platforms[data.AssetPlatformID]; ok && data.MarketData.MarketCap[currency] == 0 { - supply, err := util.GetSplTokenSupply(data.Platforms[data.AssetPlatformID]) - if err != nil { - e.log.Fields(logger.Fields{"id": data.AssetPlatformID}).Error(err, "[entity.GetCoinData] util.GetSplTokenSupply() failed") - return data, nil, http.StatusOK - } - price := data.MarketData.CurrentPrice[currency] - data.MarketData.MarketCap[currency] = price * supply - } + // if data.AssetPlatformID == "solana" && data.Platforms != nil { + // currency := "usd" + // if _, ok := data.Platforms[data.AssetPlatformID]; ok && data.MarketData.MarketCap[currency] == 0 { + // supply, err := util.GetSplTokenSupply(data.Platforms[data.AssetPlatformID]) + // if err != nil { + // e.log.Fields(logger.Fields{"id": data.AssetPlatformID}).Error(err, "[entity.GetCoinData] util.GetSplTokenSupply() failed") + // return data, nil, http.StatusOK + // } + // price := data.MarketData.CurrentPrice[currency] + // data.MarketData.MarketCap[currency] = price * supply + // } + // } + + // if no market cap data, try to find from network api + currency := "usd" + var supply float64 + if data.DetailPlatforms != nil && data.Platforms != nil && data.MarketData.MarketCap[currency] == 0 { + platformId := data.AssetPlatformID + detail := data.DetailPlatforms[platformId] + contractAddr := data.Platforms[platformId] + supply = e.getTokenTotalSupply(contractAddr, platformId, detail.DecimalPlace) + price := data.MarketData.CurrentPrice[currency] + data.MarketData.MarketCap[currency] = price * supply } return data, nil, http.StatusOK } +func (e *Entity) getTokenTotalSupply(address, assetPlatformId string, decimal int) float64 { + // no address data -> returns 0 + if address == "" || assetPlatformId == "" || decimal == 0 { + return 0 + } + + // solana + if assetPlatformId == "solana" { + supply, err := util.GetSplTokenSupply(address) + if err != nil { + e.log.Fields(logger.Fields{"address": address, "platform_id": assetPlatformId}).Error(err, "[entity.getTokenTotalSupply] util.GetSplTokenSupply() failed") + } + return supply + } + + // evm-based chains + chain, err := e.repo.Chain.GetOne(chain.GetOneQuery{CoingeckoId: assetPlatformId, Type: "evm"}) + if err != nil { + e.log.Fields(logger.Fields{"address": address, "platform_id": assetPlatformId}).Error(err, "[entity.getTokenTotalSupply] repo.Chain.GetOne() failed") + return 0 + } + + apiKey := chain.APIKey + apiBaseUrl := chain.APIBaseURL + if apiKey == "" || apiBaseUrl == "" { + return 0 + } + + if strings.HasSuffix(apiBaseUrl, "api") { + apiBaseUrl += "?" + } + + w := e.dcwallet.Chain(chain.ID) + if w == nil { + e.log.Info("[entity.getTokenTotalSupply] chain not supported") + return 0 + } + + supply, err := w.TokenTotalSupply(address, decimal) + if err != nil { + e.log.Fields(logger.Fields{"address": address, "platform_id": assetPlatformId}).Error(err, "[entity.getTokenTotalSupply] chain.TokenTotalSupply() failed") + } + + return supply +} + func (e *Entity) GetTokenInfo(tokenId string) (*response.TokenInfoResponse, error) { // get coin data coinData, err, status := e.GetCoinData(tokenId, false) diff --git a/pkg/repo/chain/pg.go b/pkg/repo/chain/pg.go index 7d0a20a31..66800651c 100644 --- a/pkg/repo/chain/pg.go +++ b/pkg/repo/chain/pg.go @@ -31,3 +31,14 @@ func (pg *pg) GetByShortName(shortName string) (*model.Chain, error) { } return chain, nil } + +func (pg *pg) GetOne(q GetOneQuery) (chain *model.Chain, err error) { + db := pg.db + if q.CoingeckoId != "" { + db = db.Where("coin_gecko_id = ?", q.CoingeckoId) + } + if q.Type != "" { + db = db.Where("\"type\" = ?", q.Type) + } + return chain, db.First(&chain).Error +} diff --git a/pkg/repo/chain/query.go b/pkg/repo/chain/query.go new file mode 100644 index 000000000..724bee304 --- /dev/null +++ b/pkg/repo/chain/query.go @@ -0,0 +1,6 @@ +package chain + +type GetOneQuery struct { + CoingeckoId string + Type string +} diff --git a/pkg/repo/chain/store.go b/pkg/repo/chain/store.go index b616b8165..dc4688ca4 100644 --- a/pkg/repo/chain/store.go +++ b/pkg/repo/chain/store.go @@ -6,4 +6,5 @@ type Store interface { GetAll() ([]model.Chain, error) GetByID(id int) (model.Chain, error) GetByShortName(shortName string) (*model.Chain, error) + GetOne(GetOneQuery) (*model.Chain, error) }