github.com/diadata-org/diadata@v1.4.593/pkg/dia/service/assetservice/source/osmosis.go (about) 1 package source 2 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "io/ioutil" 8 "net/http" 9 "net/url" 10 "strings" 11 12 sdk "github.com/cosmos/cosmos-sdk/types" 13 query "github.com/cosmos/cosmos-sdk/types/query" 14 banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" 15 ibctransfertypes "github.com/cosmos/ibc-go/v3/modules/apps/transfer/types" 16 "github.com/diadata-org/diadata/pkg/dia" 17 scrapers "github.com/diadata-org/diadata/pkg/dia/scraper/exchange-scrapers" 18 "github.com/diadata-org/diadata/pkg/utils" 19 "google.golang.org/grpc" 20 "google.golang.org/grpc/credentials" 21 "google.golang.org/grpc/metadata" 22 ) 23 24 type GRPCClient struct { 25 ctx context.Context 26 grpcConn *grpc.ClientConn 27 bank banktypes.QueryClient 28 ibc ibctransfertypes.QueryClient 29 } 30 31 // The scraper object for Osmosis. 32 type OsmosisAssetSource struct { 33 GRPCClient *GRPCClient 34 assetChannel chan dia.Asset 35 doneChannel chan bool 36 blockchain string 37 assetsMap map[string]*OsmosisAsset 38 } 39 40 type OsmosisAssetResponse struct { 41 Assets *[]OsmosisAsset `json:"Assets"` 42 } 43 44 type OsmosisAsset struct { 45 Base string `json:"Base"` // base_denom 46 Name string `json:"Name"` // name of denom 47 Display string `json:"Display"` // human name 48 Symbol string `json:"Symbol"` // symbol of the denom 49 Decimals uint8 `json:"Decimals"` // to be loaded from Denom_units 50 Denom_units *[]banktypes.DenomUnit `json:"Denom_units"` 51 } 52 53 // Returns a new Osmosis asset scraper. 54 func NewOsmosisScraper(exchange dia.Exchange) *OsmosisAssetSource { 55 assetChannel := make(chan dia.Asset) 56 doneChannel := make(chan bool) 57 58 assetsMap, err := GetAssetsJson() 59 if err != nil { 60 log.Fatal("failed to download assets json file: ", err) 61 } 62 63 cfg := &scrapers.OsmosisConfig{ 64 Bech32AddrPrefix: "osmo", 65 Bech32PkPrefix: "osmopub", 66 Bech32ValPrefix: "osmovaloper", 67 Bech32PkValPrefix: "osmovalpub", 68 Encoding: nil, 69 RpcURL: utils.Getenv("OSMOSIS_RPC_URL", ""), 70 } 71 grpcClient, err := NewGRPCClient(cfg) 72 if err != nil { 73 log.Fatal("failed to create GRPC client: ", err) 74 } 75 oas := &OsmosisAssetSource{ 76 assetChannel: assetChannel, 77 doneChannel: doneChannel, 78 blockchain: exchange.BlockChain.Name, 79 GRPCClient: grpcClient, 80 assetsMap: assetsMap, 81 } 82 83 go oas.loadMarketsMetadata() 84 return oas 85 } 86 87 func (oas *OsmosisAssetSource) loadMarketsMetadata() { 88 oas.getAssetsTotalSupply(nil) 89 } 90 91 func (oas *OsmosisAssetSource) getAssetsTotalSupply(page *[]byte) { 92 reqPage := &banktypes.QueryTotalSupplyRequest{Pagination: &query.PageRequest{}} 93 if page != nil { 94 reqPage.Pagination.Key = *page 95 } 96 res, err := oas.GRPCClient.bank.TotalSupply(oas.GRPCClient.ctx, reqPage) 97 if err != nil { 98 log.Warn("failed to get data ", err) 99 return 100 } 101 102 go func() { 103 if res.Pagination.NextKey != nil { 104 oas.getAssetsTotalSupply(&res.Pagination.NextKey) 105 } 106 }() 107 108 for _, coin := range res.Supply { 109 oas.getAssetInfo(coin) 110 } 111 112 if res.Pagination.NextKey == nil { 113 defer func() { 114 oas.GRPCClient.grpcConn.Close() 115 oas.Done() <- true 116 }() 117 } 118 } 119 120 // Get osmosis/ibc asset metadata 121 func (oas *OsmosisAssetSource) getAssetInfo(coin sdk.Coin) { 122 a := &dia.Asset{ 123 Blockchain: "Osmosis", 124 } 125 // ibc denoms start with `ibc/` 126 splittedDenom := strings.Split(coin.Denom, "/") 127 switch splittedDenom[0] { 128 case "ibc": 129 { 130 // TODO: once https://github.com/cosmos/ibc-go/pull/3104 gets merged 131 // we can use the following codes to get the metadata: 132 // 133 // res, err := oas.GRPCClient.ibc.DenomTrace( 134 // oas.GRPCClient.ctx, 135 // &ibctransfertypes.QueryDenomTraceRequest{Hash: splittedDenom[1]}, 136 // ) 137 // if err != nil { 138 // log.Warn("can't get denom metadata for ", coin.Denom) 139 // return 140 // } 141 142 osmosisAsset := oas.assetsMap[coin.Denom] 143 if osmosisAsset == nil { 144 log.Warn("Couldn't find any info about ", coin.Denom) 145 return 146 } 147 a.Name = osmosisAsset.Display 148 a.Symbol = osmosisAsset.Symbol 149 a.Address = coin.Denom // keeping base denom as address 150 a.Decimals = osmosisAsset.Decimals 151 oas.Asset() <- *a 152 return 153 } 154 case "factory": 155 case "gamm": 156 { 157 // we don't want factory/gamm assets in the db as they're not swappable. 158 // https://docs.osmosis.zone/osmosis-core/modules/tokenfactory/ 159 // https://docs.osmosis.zone/osmosis-core/modules/gamm/ 160 return 161 } 162 default: 163 { 164 res, err := oas.GRPCClient.bank.DenomMetadata( 165 oas.GRPCClient.ctx, 166 &banktypes.QueryDenomMetadataRequest{Denom: coin.Denom}, 167 ) 168 if err != nil { 169 log.Warn("can't get denom metadata for ", coin.Denom, err) 170 return 171 } 172 a.Name = res.Metadata.Display 173 a.Symbol = res.Metadata.Symbol 174 // fallback to Display if Metadata.Symbol is empty 175 if a.Symbol == "" { 176 a.Symbol = res.Metadata.Display 177 } 178 denomUnitsMap := make(map[string]uint8) 179 for _, du := range res.Metadata.DenomUnits { 180 denomUnitsMap[du.Denom] = uint8(du.Exponent) 181 } 182 a.Decimals = denomUnitsMap[res.Metadata.Display] 183 a.Address = res.Metadata.Base 184 oas.Asset() <- *a 185 } 186 } 187 } 188 189 func (oas *OsmosisAssetSource) Asset() chan dia.Asset { 190 return oas.assetChannel 191 } 192 193 func (oas *OsmosisAssetSource) Done() chan bool { 194 return oas.doneChannel 195 } 196 197 func NewGRPCClient(conf *scrapers.OsmosisConfig) (*GRPCClient, error) { 198 sdk.GetConfig().SetBech32PrefixForAccount(conf.Bech32AddrPrefix, conf.Bech32PkPrefix) 199 md := metadata.Pairs() 200 ctx := metadata.NewOutgoingContext(context.Background(), md) 201 grpcURL, err := url.Parse(conf.RpcURL) 202 if err != nil { 203 return nil, fmt.Errorf("failed to parse GRPCURL: %s", conf.RpcURL) 204 } 205 grpcConn, err := grpc.DialContext( 206 context.Background(), 207 grpcURL.String(), 208 grpc.WithTransportCredentials(credentials.NewTLS(nil)), 209 ) 210 if err != nil { 211 if grpcConn != nil { 212 grpcConn.Close() 213 } 214 return nil, fmt.Errorf("unable to connect to grpcConn: %w", err) 215 } 216 bank := banktypes.NewQueryClient(grpcConn) 217 ibc := ibctransfertypes.NewQueryClient(grpcConn) 218 219 c := &GRPCClient{ 220 ctx: ctx, 221 grpcConn: grpcConn, 222 bank: bank, 223 ibc: ibc, 224 } 225 226 return c, nil 227 } 228 229 func GetAssetsJson() (map[string]*OsmosisAsset, error) { 230 url := "https://raw.githubusercontent.com/osmosis-labs/assetlists/main/osmosis-1/osmosis-1.assetlist.json" 231 232 client := &http.Client{} 233 req, err := http.NewRequestWithContext(context.Background(), "GET", url, nil) 234 if err != nil { 235 return nil, err 236 } 237 res, err := client.Do(req) 238 if err != nil { 239 return nil, err 240 } 241 defer res.Body.Close() 242 data, err := ioutil.ReadAll(res.Body) 243 if err != nil { 244 return nil, err 245 } 246 var assetsResponse OsmosisAssetResponse 247 err = json.Unmarshal([]byte(data), &assetsResponse) 248 if err != nil { 249 return nil, err 250 } 251 assetsMap := make(map[string]*OsmosisAsset) 252 for _, asset := range *assetsResponse.Assets { 253 denomUnitsMap := make(map[string]uint32) 254 for _, unit := range *asset.Denom_units { 255 denomUnitsMap[unit.Denom] = unit.Exponent 256 } 257 assetCopy := asset 258 assetCopy.Decimals = uint8(denomUnitsMap[asset.Display]) 259 assetsMap[asset.Base] = &assetCopy 260 } 261 return assetsMap, nil 262 }