github.com/diadata-org/diadata@v1.4.593/pkg/dia/service/assetservice/source/uniswapv4.go (about)

     1  package source
     2  
     3  import (
     4  	"context"
     5  	"strconv"
     6  	"strings"
     7  	"time"
     8  
     9  	uniswapcontract "github.com/diadata-org/diadata/pkg/dia/scraper/exchange-scrapers/uniswap"
    10  	uniswapcontractv4 "github.com/diadata-org/diadata/pkg/dia/scraper/exchange-scrapers/uniswapv4"
    11  	models "github.com/diadata-org/diadata/pkg/model"
    12  
    13  	"github.com/diadata-org/diadata/pkg/utils"
    14  
    15  	"github.com/diadata-org/diadata/pkg/dia"
    16  	"github.com/ethereum/go-ethereum/accounts/abi/bind"
    17  	"github.com/ethereum/go-ethereum/common"
    18  	"github.com/ethereum/go-ethereum/ethclient"
    19  )
    20  
    21  var numBlocksQueryUniswapV4 = uint64(1000)
    22  
    23  type UniswapV4AssetSource struct {
    24  	RestClient *ethclient.Client
    25  	WsClient   *ethclient.Client
    26  	relDB      *models.RelDB
    27  	// signaling channels for session initialization and finishing
    28  	assetChannel    chan dia.Asset
    29  	doneChannel     chan bool
    30  	exchange        dia.Exchange
    31  	startBlock      uint64
    32  	factoryContract string
    33  	waitTime        int
    34  }
    35  
    36  // NewUniswapV4AssetSource returns a new UniswapV4AssetSource
    37  func NewUniswapV4AssetSource(exchange dia.Exchange, relDB *models.RelDB) *UniswapV4AssetSource {
    38  	log.Info("NewUniswapV4Scraper ", exchange.Name)
    39  	log.Info("NewUniswapV4Scraper Address ", exchange.Contract)
    40  
    41  	var uas *UniswapV4AssetSource
    42  
    43  	switch exchange.Name {
    44  	case dia.UniswapExchangeV4:
    45  		uas = makeUniswapV4AssetSource(exchange, "", "", relDB, "200", uint64(21688329))
    46  
    47  	}
    48  
    49  	go func() {
    50  		uas.fetchAssets()
    51  	}()
    52  	return uas
    53  
    54  }
    55  
    56  // makeUniswapV4AssetSource returns a uniswap asset source.
    57  func makeUniswapV4AssetSource(exchange dia.Exchange, restDial string, wsDial string, relDB *models.RelDB, waitMilliseconds string, startBlock uint64) *UniswapV4AssetSource {
    58  	var (
    59  		restClient   *ethclient.Client
    60  		wsClient     *ethclient.Client
    61  		err          error
    62  		uas          *UniswapV4AssetSource
    63  		assetChannel = make(chan dia.Asset)
    64  		doneChannel  = make(chan bool)
    65  	)
    66  
    67  	log.Infof("Init rest and ws client for %s.", exchange.BlockChain.Name)
    68  	restClient, err = ethclient.Dial(utils.Getenv(strings.ToUpper(exchange.BlockChain.Name)+"_URI_REST", restDial))
    69  	if err != nil {
    70  		log.Fatal("init rest client: ", err)
    71  	}
    72  	wsClient, err = ethclient.Dial(utils.Getenv(strings.ToUpper(exchange.BlockChain.Name)+"_URI_WS", wsDial))
    73  	if err != nil {
    74  		log.Fatal("init rest client: ", err)
    75  	}
    76  
    77  	var waitTime int
    78  	waitTimeString := utils.Getenv(strings.ToUpper(exchange.BlockChain.Name)+"_WAIT_TIME", waitMilliseconds)
    79  	waitTime, err = strconv.Atoi(waitTimeString)
    80  	if err != nil {
    81  		log.Error("could not parse wait time: ", err)
    82  		waitTime = 500
    83  	}
    84  
    85  	uas = &UniswapV4AssetSource{
    86  		RestClient:      restClient,
    87  		WsClient:        wsClient,
    88  		relDB:           relDB,
    89  		assetChannel:    assetChannel,
    90  		doneChannel:     doneChannel,
    91  		exchange:        exchange,
    92  		startBlock:      startBlock,
    93  		factoryContract: exchange.Contract,
    94  		waitTime:        waitTime,
    95  	}
    96  	return uas
    97  }
    98  
    99  // getNumPairs returns the number of available pairs on Uniswap
   100  func (uas *UniswapV4AssetSource) fetchAssets() {
   101  
   102  	// filter from contract created https://etherscan.io/tx/0x1e20cd6d47d7021ae7e437792823517eeadd835df09dde17ab45afd7a5df4603
   103  
   104  	log.Info("get pool creations from address: ", uas.factoryContract)
   105  	poolsCount := 0
   106  	var blocknumber int64
   107  	checkMap := make(map[string]struct{})
   108  	_, startblock, err := uas.relDB.GetScraperIndex(uas.exchange.Name, dia.SCRAPER_TYPE_ASSETCOLLECTOR)
   109  	if err != nil {
   110  		log.Error("GetScraperIndex: ", err)
   111  	} else {
   112  		uas.startBlock = uint64(startblock)
   113  	}
   114  
   115  	contract, err := uniswapcontractv4.NewPoolmanagerFilterer(common.HexToAddress(uas.factoryContract), uas.WsClient)
   116  	if err != nil {
   117  		log.Error(err)
   118  	}
   119  
   120  	currentBlockNumber, err := uas.RestClient.BlockNumber(context.Background())
   121  	if err != nil {
   122  		log.Error("GetBlockNumber: ", err)
   123  	}
   124  
   125  	endblock := utils.Min(uint64(uas.startBlock)+numBlocksQueryUniswapV4, currentBlockNumber)
   126  	log.Infof("startblock -- endblock: %v -- %v", uas.startBlock, endblock)
   127  
   128  	for uas.startBlock <= currentBlockNumber {
   129  		poolCreated, err := contract.FilterInitialize(
   130  			&bind.FilterOpts{
   131  				Start: uas.startBlock,
   132  				End:   &endblock,
   133  			},
   134  			[][32]byte{},
   135  			[]common.Address{},
   136  			[]common.Address{},
   137  		)
   138  		if err != nil {
   139  			log.Error("filter pool created: ", err)
   140  		}
   141  		for poolCreated.Next() {
   142  			time.Sleep(time.Duration(uas.waitTime) * time.Millisecond)
   143  			poolsCount++
   144  			log.Info("pools count: ", poolsCount)
   145  
   146  			blocknumber = int64(poolCreated.Event.Raw.BlockNumber)
   147  			address0 := poolCreated.Event.Currency0
   148  			address1 := poolCreated.Event.Currency0
   149  			// Don't repeat sending already sent assets.
   150  			// Take into account that UniswapV4 allows for trading unwrapped ETH.
   151  			if (address0 != common.Address{}) {
   152  				if _, ok := checkMap[address0.Hex()]; !ok {
   153  					checkMap[address0.Hex()] = struct{}{}
   154  					asset, err := uas.GetAssetFromAddress(address0)
   155  					if err != nil {
   156  						log.Warnf("cannot fetch asset from address %s: %v", address0.Hex(), err)
   157  					}
   158  					uas.assetChannel <- asset
   159  				}
   160  			}
   161  			if (address1 != common.Address{}) {
   162  				if _, ok := checkMap[address1.Hex()]; !ok {
   163  					checkMap[address1.Hex()] = struct{}{}
   164  					asset, err := uas.GetAssetFromAddress(address1)
   165  					if err != nil {
   166  						log.Warnf("cannot fetch asset from address %s: %v", address1.Hex(), err)
   167  					}
   168  					uas.assetChannel <- asset
   169  				}
   170  			}
   171  		}
   172  		err = uas.relDB.SetScraperIndex(uas.exchange.Name, dia.SCRAPER_TYPE_ASSETCOLLECTOR, dia.INDEX_TYPE_BLOCKNUMBER, blocknumber)
   173  		if err != nil {
   174  			log.Error("SetScraperIndex: ", err)
   175  		}
   176  		endblock += numBlocksQueryUniswapV4
   177  		uas.startBlock += numBlocksQueryUniswapV4
   178  	}
   179  
   180  	uas.doneChannel <- true
   181  }
   182  
   183  func (uas *UniswapV4AssetSource) GetAssetFromAddress(address common.Address) (asset dia.Asset, err error) {
   184  	connection := uas.RestClient
   185  
   186  	var tokenContract *uniswapcontract.IERC20Caller
   187  
   188  	tokenContract, err = uniswapcontract.NewIERC20Caller(address, connection)
   189  	if err != nil {
   190  		log.Error(err)
   191  	}
   192  
   193  	symbol, err := tokenContract.Symbol(&bind.CallOpts{})
   194  	if err != nil {
   195  		log.Error(err)
   196  	}
   197  	name, err := tokenContract.Name(&bind.CallOpts{})
   198  	if err != nil {
   199  		log.Error(err)
   200  	}
   201  	decimals, err := tokenContract.Decimals(&bind.CallOpts{})
   202  	if err != nil {
   203  		log.Error(err)
   204  	}
   205  
   206  	asset = dia.Asset{
   207  		Symbol:     symbol,
   208  		Name:       name,
   209  		Address:    address.Hex(),
   210  		Blockchain: uas.exchange.BlockChain.Name,
   211  		Decimals:   decimals,
   212  	}
   213  
   214  	return
   215  }
   216  
   217  func (uas *UniswapV4AssetSource) Asset() chan dia.Asset {
   218  	return uas.assetChannel
   219  }
   220  
   221  func (uas *UniswapV4AssetSource) Done() chan bool {
   222  	return uas.doneChannel
   223  }