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  }