Skip to content

Commit

Permalink
Merge pull request #25 from ingenuity-build/ajansari95/supply
Browse files Browse the repository at this point in the history
feat: add `circulating_supply` route
  • Loading branch information
ajansari95 authored Apr 26, 2023
2 parents 4772e8e + 88e89af commit bd95695
Show file tree
Hide file tree
Showing 6 changed files with 1,469 additions and 18 deletions.
6 changes: 5 additions & 1 deletion conf.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,8 @@ chains:
- "cosmoshub"
- "stargaze"
- "osmosis"
- "regen"
- "regen"
aprurl: "https://chains.cosmos.directory"
aprcachetime: 15 #minutes
lcdendpoint: "https://lcd.quicksilver.zone"
supplycachetime: 3 #hours
19 changes: 11 additions & 8 deletions errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@ package main
import "errors"

var (
ErrRPCClientConnection = errors.New("unable to connect to RPC client")
ErrABCIQuery = errors.New("unable to execute ABCI query")
ErrUnmarshalResponse = errors.New("unable to unmarshal ABCI query response")
ErrMarshalResponse = errors.New("unable to marshal JSON response")
ErrReadConfigFile = errors.New("unable to read config file")
ErrParseConfigFile = errors.New("unable to parse config file")
ErrEchoFatal = errors.New("shutting down server")
ErrUnableToGetAPR = errors.New("unable to get apr response")
ErrRPCClientConnection = errors.New("unable to connect to RPC client")
ErrABCIQuery = errors.New("unable to execute ABCI query")
ErrUnmarshalResponse = errors.New("unable to unmarshal ABCI query response")
ErrMarshalResponse = errors.New("unable to marshal JSON response")
ErrReadConfigFile = errors.New("unable to read config file")
ErrParseConfigFile = errors.New("unable to parse config file")
ErrEchoFatal = errors.New("shutting down server")
ErrUnableToGetAPR = errors.New("unable to get apr response")
ErrUnableToGetLockedTokens = errors.New("unable to get locked tokens response")
ErrUnableToGetTotalSupply = errors.New("unable to get total supply response")
ErrUnableToGetCommunityPool = errors.New("unable to get CommunityPool response")
)
94 changes: 89 additions & 5 deletions handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ import (
"net/http"
"time"

echov4 "github.com/labstack/echo/v4"

sdkmath "cosmossdk.io/math"
"github.com/cosmos/cosmos-sdk/codec"
cdctypes "github.com/cosmos/cosmos-sdk/codec/types"
"github.com/cosmos/cosmos-sdk/types/query"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
icstypes "github.com/ingenuity-build/quicksilver/x/interchainstaking/types"
echov4 "github.com/labstack/echo/v4"
rpcclient "github.com/tendermint/tendermint/rpc/client"
)

Expand Down Expand Up @@ -70,6 +70,26 @@ func (s *Service) ConfigureRoutes() {
}
return ctx.JSONBlob(http.StatusOK, data.([]byte))
})

s.Echo.GET("/total_supply", func(ctx echov4.Context) error {
key := "total_supply"

data, found := s.Cache.Get(key)
if !found {
return s.getTotalSupply(ctx, key)
}
return ctx.JSONBlob(http.StatusOK, data.([]byte))
})

s.Echo.GET("/circulating_supply", func(ctx echov4.Context) error {
key := "circulating_supply"

data, found := s.Cache.Get(key)
if !found {
return s.getCirculatingSupply(ctx, key)
}
return ctx.JSONBlob(http.StatusOK, data.([]byte))
})
}

func (s *Service) getValidatorList(ctx echov4.Context, key string, chainId string) error {
Expand Down Expand Up @@ -239,12 +259,11 @@ func (s *Service) getZones(ctx echov4.Context, key string) error {

func (s *Service) getAPR(ctx echov4.Context, key string) error {
s.Echo.Logger.Infof("getAPR")
baseurl := "https://chains.cosmos.directory/"

chains := s.Config.Chains
aprResp := APRResponse{}
for _, chain := range chains {
chainAPR, err := getAPRquery(baseurl, chain)
chainAPR, err := getAPRquery(s.Config.APRURL+"/", chain)
if err != nil {
s.Echo.Logger.Errorf("getAPR: %v - %v", ErrUnableToGetAPR, err)
return ErrUnableToGetAPR
Expand All @@ -259,7 +278,72 @@ func (s *Service) getAPR(ctx echov4.Context, key string) error {
return ErrMarshalResponse
}

s.Cache.SetWithTTL(key, respdata, 1, 15*time.Minute)
s.Cache.SetWithTTL(key, respdata, 1, time.Duration(s.Config.APRCacheTime)*time.Minute)

return ctx.JSONBlob(http.StatusOK, respdata)
}

func (s *Service) getTotalSupply(ctx echov4.Context, key string) error {
s.Echo.Logger.Infof("getTotalSupply")

totalSupply, err := getTotalSupply(s.Config.LCDEndpoint + "/cosmos/bank/v1beta1/supply")
if err != nil {
s.Echo.Logger.Errorf("getTotalSupply: %v - %v", ErrUnableToGetTotalSupply, err)
return ErrUnableToGetTotalSupply
}
s.Echo.Logger.Info("totalSupply", " -> ", totalSupply)
respData, err := json.Marshal(float64(totalSupply.Int64()) / 1_000_000)
if err != nil {
s.Echo.Logger.Errorf("getTotalSupply: %v - %v", ErrMarshalResponse, err)
return ErrMarshalResponse
}
s.Cache.SetWithTTL(key, respData, 1, time.Duration(s.Config.SupplyCacheTime)*time.Hour)

return ctx.JSONBlob(http.StatusOK, respData)
}

func (s *Service) getCirculatingSupply(ctx echov4.Context, key string) error {
s.Echo.Logger.Infof("getCirculatingSupply")

var CirculatingSupplyResponse int64

totalLockedTokens := sdkmath.ZeroInt()

for _, address := range VESTING_ACCOUNTS {
lockedTokensForAddress, err := getVestingAccountLocked(s.Config.LCDEndpoint+"/cosmos/auth/v1beta1/accounts/", address)
if err != nil {
s.Echo.Logger.Errorf("getCirculatingSupply: %v - %v", ErrUnableToGetLockedTokens, err)
return ErrUnableToGetLockedTokens
}
totalLockedTokens = totalLockedTokens.Add(lockedTokensForAddress)
s.Echo.Logger.Info("lockedTokensFor", address, " -> ", lockedTokensForAddress)
}

totalSupply, err := getTotalSupply(s.Config.LCDEndpoint + "/cosmos/bank/v1beta1/supply")
if err != nil {
s.Echo.Logger.Errorf("getCirculatingSupply: %v - %v", ErrUnableToGetTotalSupply, err)
return ErrUnableToGetTotalSupply
}
s.Echo.Logger.Info("totalSupply", " -> ", totalSupply)

communityPoolBalance, err := getCommunityPool(s.Config.LCDEndpoint + "/cosmos/distribution/v1beta1/community_pool")
if err != nil {
s.Echo.Logger.Errorf("getCirculatingSupply: %v - %v", ErrUnableToGetCommunityPool, err)
return ErrUnableToGetCommunityPool
}

s.Echo.Logger.Info("communityPoolBalance", " -> ", communityPoolBalance)

totalCirculatingSupply := totalSupply.Sub(totalLockedTokens).Sub(communityPoolBalance).Sub(sdkmath.NewInt(500_000_000_000)) // unknown account
CirculatingSupplyResponse = totalCirculatingSupply.Int64()

respData, err := json.Marshal(float64(CirculatingSupplyResponse) / 1_000_000)
if err != nil {
s.Echo.Logger.Errorf("getCirculatingSupply: %v - %v", ErrMarshalResponse, err)
return ErrMarshalResponse
}
s.Cache.SetWithTTL(key, respData, 1, time.Duration(s.Config.SupplyCacheTime)*time.Hour)

return ctx.JSONBlob(http.StatusOK, respData)

}
11 changes: 7 additions & 4 deletions service.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (

"github.com/dgraph-io/ristretto"
echov4 "github.com/labstack/echo/v4"

tmhttp "github.com/tendermint/tendermint/rpc/client/http"
libclient "github.com/tendermint/tendermint/rpc/jsonrpc/client"
)
Expand Down Expand Up @@ -52,9 +51,13 @@ type Service struct {
}

type Config struct {
QuickHost string
ChainHost string
Chains []string
QuickHost string
ChainHost string
Chains []string
APRURL string
APRCacheTime int
LCDEndpoint string
SupplyCacheTime int
}

func NewCacheService(e *echov4.Echo, cache *ristretto.Cache, cfg Config) *Service {
Expand Down
134 changes: 134 additions & 0 deletions supply.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
package main

import (
"encoding/json"
"net/http"
"strconv"
"time"

sdkmath "cosmossdk.io/math"
sdktypes "github.com/cosmos/cosmos-sdk/types"
)

// get supply
// bondDenom uqck
/* delemap*/

type Supply struct {
Supply sdktypes.Coins `json:"supply"`
}

type CommunityPool struct {
Pool sdktypes.DecCoins `json:"pool"`
}

type Account struct {
Type string `json:"@type"`
BaseVestingAccount BaseVestingAccount `json:"base_vesting_account"`
StartTime string `json:"start_time"`
VestingPeriods []VestingPeriods `json:"vesting_periods"`
}

type BaseVestingAccount struct {
BaseAccount BaseAccount `protobuf:"bytes,1,opt,name=base_account,json=baseAccount,proto3,embedded=base_account" json:"base_account,omitempty"`
OriginalVesting sdktypes.Coins `protobuf:"bytes,2,rep,name=original_vesting,json=originalVesting,proto3,castrepeated=github.com/cosmos/cosmos-sdk/types.Coins" json:"original_vesting" yaml:"original_vesting"`
DelegatedFree sdktypes.Coins `protobuf:"bytes,3,rep,name=delegated_free,json=delegatedFree,proto3,castrepeated=github.com/cosmos/cosmos-sdk/types.Coins" json:"delegated_free" yaml:"delegated_free"`
DelegatedVesting sdktypes.Coins `protobuf:"bytes,4,rep,name=delegated_vesting,json=delegatedVesting,proto3,castrepeated=github.com/cosmos/cosmos-sdk/types.Coins" json:"delegated_vesting" yaml:"delegated_vesting"`
EndTime string `protobuf:"varint,5,opt,name=end_time,json=endTime,proto3" json:"end_time,omitempty" yaml:"end_time"`
}

type BaseAccount struct {
Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"`
PubKey string `protobuf:"bytes,2,opt,name=pub_key,json=pubKey,proto3" json:"public_key,omitempty" yaml:"public_key"`
AccountNumber string `protobuf:"varint,3,opt,name=account_number,json=accountNumber,proto3" json:"account_number,omitempty" yaml:"account_number"`
Sequence string `protobuf:"varint,4,opt,name=sequence,proto3" json:"sequence,omitempty"`
}

type VestingPeriods struct {
Length string `json:"length"`
Amount sdktypes.Coins `json:"amount"`
}

func getVestingAccountLocked(baseurl, address string) (sdkmath.Int, error) {
url := baseurl + address
resp, err := http.Get(url)
if err != nil {
return sdkmath.Int{}, err
}

var result map[string]json.RawMessage
err = json.NewDecoder(resp.Body).Decode(&result)
if err != nil {
return sdkmath.Int{}, err
}
var account Account
err = json.Unmarshal(result["account"], &account)
if err != nil {
return sdkmath.Int{}, err
}
if account.Type == "/cosmos.vesting.v1beta1.PeriodicVestingAccount" {
lockedTokens := account.BaseVestingAccount.OriginalVesting.AmountOf("uqck")
startTime, err := strconv.ParseInt(account.StartTime, 10, 64)
if err != nil {
return sdkmath.Int{}, err
}
for _, vestPeriod := range account.VestingPeriods {
period, err := strconv.ParseInt(vestPeriod.Length, 10, 64)
if err != nil {
return sdkmath.Int{}, err
}
if (startTime + period) < time.Now().Unix() {
lockedTokens = lockedTokens.Sub(vestPeriod.Amount.AmountOf("uqck"))
}
startTime = startTime + period

}

return lockedTokens, nil
}
return sdkmath.ZeroInt(), nil
}

func getTotalSupply(url string) (sdkmath.Int, error) {
resp, err := http.Get(url)
if err != nil {
return sdkmath.Int{}, err
}

var result json.RawMessage
err = json.NewDecoder(resp.Body).Decode(&result)
if err != nil {
return sdkmath.Int{}, err
}

var supply Supply
err = json.Unmarshal(result, &supply)
if err != nil {
return sdkmath.Int{}, err
}

return supply.Supply.AmountOf("uqck"), nil

}

func getCommunityPool(url string) (sdkmath.Int, error) {
resp, err := http.Get(url)
if err != nil {
return sdkmath.Int{}, err
}

var result json.RawMessage
err = json.NewDecoder(resp.Body).Decode(&result)
if err != nil {
return sdkmath.Int{}, err
}

var comPool CommunityPool
err = json.Unmarshal(result, &comPool)
if err != nil {
return sdkmath.Int{}, err
}

return comPool.Pool.AmountOf("uqck").RoundInt(), nil

}
Loading

0 comments on commit bd95695

Please sign in to comment.