github.com/diadata-org/diadata@v1.4.593/pkg/dia/service/assetservice/source/orca.go (about)

     1  package source
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"strings"
     7  	"time"
     8  
     9  	"github.com/diadata-org/diadata/pkg/dia"
    10  	scrapers "github.com/diadata-org/diadata/pkg/dia/scraper/exchange-scrapers"
    11  	orcaWhirlpoolIdlBind "github.com/diadata-org/diadata/pkg/dia/scraper/exchange-scrapers/orca/whirlpool"
    12  	"github.com/diadata-org/diadata/pkg/utils"
    13  	bin "github.com/gagliardetto/binary"
    14  	tokenmetadata "github.com/gagliardetto/metaplex-go/clients/token-metadata"
    15  	"github.com/gagliardetto/solana-go"
    16  	"github.com/gagliardetto/solana-go/programs/token"
    17  	"github.com/gagliardetto/solana-go/programs/tokenregistry"
    18  	"github.com/gagliardetto/solana-go/rpc"
    19  )
    20  
    21  const (
    22  	orcaSolanaHttpEndpoint = "https://rpc.ankr.com/solana"
    23  )
    24  
    25  // The scraper object for Orca.
    26  type OrcaAssetSource struct {
    27  	RestClient   *rpc.Client
    28  	assetChannel chan dia.Asset
    29  	doneChannel  chan bool
    30  	blockchain   string
    31  }
    32  
    33  // Returns a new Orca asset scraper.
    34  func NewOrcaScraper(exchange dia.Exchange) *OrcaAssetSource {
    35  
    36  	var (
    37  		assetChannel = make(chan dia.Asset)
    38  		doneChannel  = make(chan bool)
    39  		pas          *OrcaAssetSource
    40  	)
    41  
    42  	log.Infof("init rest and ws client for %s", exchange.BlockChain.Name)
    43  	restClient := rpc.New(utils.Getenv(strings.ToUpper(exchange.BlockChain.Name)+"_URI_REST", orcaSolanaHttpEndpoint))
    44  
    45  	pas = &OrcaAssetSource{
    46  		assetChannel: assetChannel,
    47  		doneChannel:  doneChannel,
    48  		blockchain:   exchange.BlockChain.Name,
    49  		RestClient:   restClient,
    50  	}
    51  	go func() {
    52  		pas.loadMarketsMetadata()
    53  	}()
    54  	return pas
    55  }
    56  
    57  // Load markets and tokens metadata
    58  func (pas *OrcaAssetSource) loadMarketsMetadata() {
    59  	log.Infof("loading initial data from pools ...")
    60  	start := time.Now()
    61  	err := pas.loadMarketPools()
    62  	if err != nil {
    63  		log.Error(err)
    64  	}
    65  	log.Infof("loaded legacy-pools data in %.1fs", time.Since(start).Seconds())
    66  	start = time.Now()
    67  	err = pas.loadMarketWhirlpools()
    68  	if err != nil {
    69  		log.Error(err)
    70  	}
    71  	log.Infof("loaded whirlpools data in %.1fs", time.Since(start).Seconds())
    72  	pas.doneChannel <- true
    73  }
    74  
    75  // Get Orca market whirlpools
    76  func (pas *OrcaAssetSource) loadMarketWhirlpools() (err error) {
    77  	resp, err := pas.RestClient.GetProgramAccountsWithOpts(
    78  		context.TODO(),
    79  		solana.MustPublicKeyFromBase58(scrapers.OrcaProgWhirlpoolAddr),
    80  		&rpc.GetProgramAccountsOpts{
    81  			Filters: []rpc.RPCFilter{
    82  				{
    83  					DataSize: scrapers.OrcaProgWhirlpoolAccountDataSize,
    84  				},
    85  			},
    86  		},
    87  	)
    88  	if err != nil {
    89  		return
    90  	}
    91  	if resp == nil {
    92  		return fmt.Errorf("program account not found")
    93  	}
    94  	log.Infof("discovered %d accounts in whirlpool program, retrieving metadata ...", len(resp))
    95  	cachedAssets := make(map[string]bool)
    96  	for _, progAcc := range resp {
    97  		acct := progAcc.Account
    98  		pubKey := progAcc.Pubkey.String()
    99  		if acct.Owner.String() == scrapers.OrcaProgWhirlpoolAddr {
   100  			d := bin.NewBorshDecoder(acct.Data.GetBinary())
   101  			var w orcaWhirlpoolIdlBind.Whirlpool
   102  			err = d.Decode(&w)
   103  			if err != nil {
   104  				return err
   105  			}
   106  			// Blacklist XXX/USDC, ATLAS/USDC, SHIB/USDC
   107  			if pubKey == "FfBeru58Q7hjqHq9T2Trw1BeyjE1YwHsx9MivKUwoTLQ" || pubKey == "9vqFu6v9CcVDaSx2oRD3jo8H5gqkE2urYQgpT16V1BTa" || pubKey == "DahhciLA89UkZoqrqVWL2nojwPLmSVkXQGTiEhAtkaFa" {
   108  				continue
   109  			}
   110  			if w.WhirlpoolsConfig.String() == scrapers.OrcaProgWhirlpoolConfigAddr {
   111  				// Get token A mint data and metadata
   112  				if _, ok := cachedAssets[w.TokenMintA.String()]; !ok {
   113  					token, err := pas.getAssetData(w.TokenMintA.String())
   114  					if err != nil {
   115  						return err
   116  					}
   117  
   118  					pas.Asset() <- token
   119  					cachedAssets[token.Address] = true
   120  				}
   121  
   122  				// Get token B mint data and metadata
   123  				if _, ok := cachedAssets[w.TokenMintB.String()]; !ok {
   124  					token, err := pas.getAssetData(w.TokenMintB.String())
   125  					if err != nil {
   126  						return err
   127  					}
   128  
   129  					pas.Asset() <- token
   130  					cachedAssets[token.Address] = true
   131  				}
   132  			}
   133  		}
   134  	}
   135  	return
   136  }
   137  
   138  func (pas *OrcaAssetSource) getAssetData(mintAddress string) (asset dia.Asset, err error) {
   139  	if mintData, err := pas.getTokenMintData(mintAddress); err == nil {
   140  		asset.Decimals = mintData.Decimals
   141  	} else {
   142  		return asset, err
   143  	}
   144  	if metadata, err := pas.getTokenMetadata(mintAddress); err != nil {
   145  		hardcodedTokenMeta := scrapers.GetOrcaTokensMetadata()
   146  		if v, ok := hardcodedTokenMeta[mintAddress]; ok {
   147  			asset.Symbol = v.(scrapers.OrcaTokenMetadata).GetSymbol()
   148  			asset.Name = v.(scrapers.OrcaTokenMetadata).GetName()
   149  		} else {
   150  			log.Warnf("cannot found token metadata for %s: %s", mintAddress, err)
   151  			return asset, nil
   152  		}
   153  	} else {
   154  		asset.Symbol = strings.TrimRight(metadata.Data.Symbol, "\x00")
   155  		asset.Name = strings.TrimRight(metadata.Data.Name, "\x00")
   156  	}
   157  	asset.Address = mintAddress
   158  	asset.Blockchain = "Solana"
   159  	return asset, nil
   160  }
   161  
   162  // Get Solana token mint data
   163  func (pas *OrcaAssetSource) getTokenMintData(account string) (mint token.Mint, err error) {
   164  	resp, err := pas.RestClient.GetAccountInfoWithOpts(
   165  		context.TODO(),
   166  		solana.MustPublicKeyFromBase58(account),
   167  		&rpc.GetAccountInfoOpts{},
   168  	)
   169  	if err != nil {
   170  		return
   171  	}
   172  	d := bin.NewBorshDecoder(resp.Value.Data.GetBinary())
   173  	err = d.Decode(&mint)
   174  	if err != nil {
   175  		return
   176  	}
   177  	return
   178  }
   179  
   180  // Get Solana token metadata
   181  func (pas *OrcaAssetSource) getTokenMetadata(account string) (metadata tokenmetadata.Metadata, err error) {
   182  	accMint := solana.MustPublicKeyFromBase58(account)
   183  	tMeta, err := tokenregistry.GetTokenRegistryEntry(context.TODO(), pas.RestClient, accMint)
   184  	if err != nil {
   185  		metaAddress, _, err := solana.FindTokenMetadataAddress(accMint)
   186  		if err != nil {
   187  			return metadata, err
   188  		}
   189  		resp, err := pas.RestClient.GetAccountInfo(
   190  			context.TODO(),
   191  			metaAddress,
   192  		)
   193  		if err != nil {
   194  			return metadata, err
   195  		}
   196  		d := bin.NewBorshDecoder(resp.Value.Data.GetBinary())
   197  		err = d.Decode(&metadata)
   198  		if err != nil {
   199  			return metadata, err
   200  		}
   201  		return metadata, nil
   202  	}
   203  	return tokenmetadata.Metadata{Data: tokenmetadata.Data{Symbol: tMeta.Symbol.String()}}, nil
   204  }
   205  
   206  // Get Orca market legacy pools
   207  func (pas *OrcaAssetSource) loadMarketPools() (err error) {
   208  	return
   209  }
   210  
   211  func (pas *OrcaAssetSource) Asset() chan dia.Asset {
   212  	return pas.assetChannel
   213  }
   214  
   215  func (pas *OrcaAssetSource) Done() chan bool {
   216  	return pas.doneChannel
   217  }