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 }