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 }