github.com/diadata-org/diadata@v1.4.593/pkg/dia/service/assetservice/source/serum.go (about) 1 package source 2 3 import ( 4 "fmt" 5 6 "github.com/diadata-org/diadata/pkg/dia" 7 "github.com/diadata-org/diadata/pkg/utils" 8 "github.com/mr-tron/base58" 9 "github.com/streamingfast/solana-go" 10 "github.com/streamingfast/solana-go/programs/serum" 11 "github.com/streamingfast/solana-go/rpc" 12 ) 13 14 type SerumPair struct { 15 Token0 dia.Asset 16 Token1 dia.Asset 17 ForeignName string 18 Address string 19 } 20 21 const ( 22 // Public Solana clients. 23 rpcEndpointSolana = "" // refer - https://try.blockdaemon.com/rpc/solana/ 24 dexProgramAddress = "9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin" // refer - https://github.com/project-serum/serum-dex 25 nameServiceProgramAddress = "namesLPneVptA9Z5rqUDD9tMTWEJwofgaYwp8cawRkX" 26 dotTokenTLD = "6NSu2tci4apRKQtt257bAVcvqYjB3zV2H1dWo56vgpa6" 27 marketDataSize = 388 28 ) 29 30 type SerumAssetSource struct { 31 solanaRpcClient *rpc.Client 32 tokenNameRegistry map[string]tokenMeta 33 assetChannel chan dia.Asset 34 doneChannel chan bool 35 blockchain string 36 } 37 38 func NewSerumAssetSource(exchange dia.Exchange) *SerumAssetSource { 39 40 var assetChannel = make(chan dia.Asset) 41 var doneChannel = make(chan bool) 42 var sas *SerumAssetSource 43 44 exchangeFactoryContractAddress = "" 45 46 sas = &SerumAssetSource{ 47 solanaRpcClient: rpc.NewClient(utils.Getenv("SOLANA_URI_REST", rpcEndpointSolana)), 48 assetChannel: assetChannel, 49 doneChannel: doneChannel, 50 blockchain: dia.SOLANA, 51 } 52 53 go func() { 54 sas.fetchAssets() 55 }() 56 return sas 57 58 } 59 60 func (sas *SerumAssetSource) Asset() chan dia.Asset { 61 return sas.assetChannel 62 } 63 64 func (sas *SerumAssetSource) Done() chan bool { 65 return sas.doneChannel 66 } 67 68 func (sas *SerumAssetSource) fetchAssets() { 69 pairs, err := sas.getPairs() 70 if err != nil { 71 log.Error(err) 72 return 73 } 74 log.Info("Found ", len(pairs), " pairs") 75 checkMap := make(map[string]struct{}) 76 sas.tokenNameRegistry, err = sas.getTokenNames() 77 if err != nil { 78 log.Error(err) 79 return 80 } 81 for _, pair := range pairs { 82 if tokenInfo, ok := sas.tokenNameRegistry[pair.BaseMint.String()]; ok { 83 if _, ok := checkMap[tokenInfo.symbol]; !ok { 84 checkMap[tokenInfo.symbol] = struct{}{} 85 sas.assetChannel <- dia.Asset{ 86 Symbol: tokenInfo.symbol, 87 Name: tokenInfo.name, 88 Address: tokenInfo.mint, 89 Decimals: tokenInfo.decimals, 90 Blockchain: sas.blockchain, 91 } 92 } 93 } 94 if tokenInfo, ok := sas.tokenNameRegistry[pair.QuoteMint.String()]; ok { 95 if _, ok := checkMap[tokenInfo.symbol]; !ok { 96 checkMap[tokenInfo.symbol] = struct{}{} 97 sas.assetChannel <- dia.Asset{ 98 Symbol: tokenInfo.symbol, 99 Name: tokenInfo.name, 100 Address: tokenInfo.mint, 101 Decimals: tokenInfo.decimals, 102 Blockchain: sas.blockchain, 103 } 104 } 105 } 106 } 107 sas.doneChannel <- true 108 } 109 110 func (sas *SerumAssetSource) getPairs() ([]*serum.MarketV2, error) { 111 resp, err := sas.solanaRpcClient.GetProgramAccounts( 112 solana.MustPublicKeyFromBase58(dexProgramAddress), 113 &rpc.GetProgramAccountsOpts{ 114 Filters: []rpc.RPCFilter{ 115 { 116 DataSize: marketDataSize, 117 }, 118 }, 119 }, 120 ) 121 if err != nil { 122 return nil, err 123 } 124 out := make([]*serum.MarketV2, 0) 125 for _, keyedAcct := range resp { 126 acct := keyedAcct.Account 127 marketV2 := &serum.MarketV2{} 128 if err := marketV2.Decode(acct.Data); err != nil { 129 return nil, fmt.Errorf("decoding market v2: %w", err) 130 } 131 out = append(out, marketV2) 132 } 133 return out, nil 134 } 135 136 func (sas *SerumAssetSource) getTokenNames() (map[string]tokenMeta, error) { 137 names := make(map[string]tokenMeta) 138 tldPublicKey := solana.MustPublicKeyFromBase58(dotTokenTLD) 139 resp, err := sas.solanaRpcClient.GetProgramAccounts( 140 solana.MustPublicKeyFromBase58(nameServiceProgramAddress), 141 &rpc.GetProgramAccountsOpts{ 142 Filters: []rpc.RPCFilter{ 143 { 144 Memcmp: &rpc.RPCFilterMemcmp{ 145 Bytes: tldPublicKey[:], 146 }, 147 }, 148 }, 149 }, 150 ) 151 if err != nil { 152 return nil, err 153 } 154 for _, keyedAcct := range resp { 155 if t, ok := extractTokenMetaFromData(keyedAcct.Account.Data[96:]); ok { 156 names[t.mint] = t 157 } 158 } 159 return names, nil 160 } 161 162 type tokenMeta struct { 163 name string 164 symbol string 165 mint string 166 decimals uint8 167 } 168 169 func extractTokenMetaFromData(data []byte) (tokenMeta, bool) { 170 var t tokenMeta 171 if len(data) > 0 { 172 nameSize := int(data[0]) 173 nameStart := 4 174 nameEnd := nameStart + nameSize 175 if len(data) > nameEnd { 176 t.name = string(data[nameStart:nameEnd]) 177 symbolSize := int(data[nameEnd]) 178 symbolStart := 4 + nameEnd 179 symbolEnd := symbolStart + symbolSize 180 if len(data) > symbolEnd { 181 t.symbol = string(data[symbolStart:symbolEnd]) 182 mintSize := 32 183 mintStart := symbolEnd 184 mintEnd := mintStart + mintSize 185 if len(data) > mintEnd { 186 t.mint = base58.Encode(data[mintStart:mintEnd]) 187 t.decimals = data[mintEnd] 188 return t, true 189 } 190 } 191 } 192 } 193 return tokenMeta{}, false 194 }