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 }