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  }