github.com/diadata-org/diadata@v1.4.593/pkg/dia/scraper/liquidity-scrapers/OrcaScraper.go (about)

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