github.com/diadata-org/diadata@v1.4.593/pkg/dia/scraper/exchange-scrapers/MultichainScraper.go (about) 1 package scrapers 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "strings" 8 "time" 9 10 "github.com/diadata-org/diadata/pkg/dia" 11 models "github.com/diadata-org/diadata/pkg/model" 12 "github.com/ethereum/go-ethereum" 13 14 "github.com/ethereum/go-ethereum/accounts/abi" 15 "github.com/ethereum/go-ethereum/accounts/abi/bind" 16 "github.com/ethereum/go-ethereum/common" 17 "github.com/ethereum/go-ethereum/core/types" 18 "github.com/ethereum/go-ethereum/crypto" 19 "github.com/ethereum/go-ethereum/ethclient" 20 21 "github.com/diadata-org/diadata/pkg/dia/scraper/exchange-scrapers/anyerc20" 22 "github.com/diadata-org/diadata/pkg/dia/scraper/exchange-scrapers/uniswap" 23 24 "math/big" 25 26 "sync" 27 ) 28 29 type BridgeSwapToken struct { 30 Address common.Address 31 Symbol string 32 Decimals uint8 33 Name string 34 } 35 36 type BridgeSwapPair struct { 37 Token0 BridgeSwapToken 38 Token1 BridgeSwapToken 39 ForeignName string 40 Address common.Address 41 } 42 43 type BridgeSwapSwap struct { 44 TransactionHash string 45 TokenAddress common.Address 46 ToAddress common.Address 47 Amount *big.Int 48 FromChainId *big.Int 49 ToChainId *big.Int 50 } 51 52 type BridgeSwapScraper struct { 53 WsClient *ethclient.Client 54 RestClient *ethclient.Client 55 56 // signaling channels for session initialization and finishing 57 //initDone chan nothing 58 //run bool 59 shutdown chan nothing 60 shutdownDone chan nothing 61 // error handling; to read error or closed, first acquire read lock 62 // only cleanup method should hold write lock 63 errorLock sync.RWMutex 64 error error 65 closed bool 66 // used to keep track of trading pairs that we subscribed to 67 pairScrapers map[string]*BridgeSwapPairScraper 68 //exchangeName string 69 chanTrades chan *dia.Trade 70 // waitTime int 71 // If true, only pairs given in config file are scraped. Default is false. 72 //listenByAddress bool 73 relDB *models.RelDB 74 } 75 76 type MultiChainConfig struct { 77 restURL string 78 wsURL string 79 contractAddress string 80 contratDeployedAtBlock int64 81 } 82 83 var ( 84 restClients map[string]*ethclient.Client 85 wsClients map[string]*ethclient.Client 86 multichainconfigs map[string]MultiChainConfig 87 ) 88 89 const ( 90 ftmHTTP = "https://rpc.ftm.tools/" 91 ethHTTP = "https://eth-mainnet.alchemyapi.io/v2/UpWALFqrTh5m8bojhDcgtBIif-Ug5UUE" 92 abiString = `[{ 93 "anonymous": false, 94 "inputs": [{ 95 "indexed": true, 96 "internalType": "bytes32", 97 "name": "txhash", 98 "type": "bytes32" 99 }, { 100 "indexed": true, 101 "internalType": "address", 102 "name": "token", 103 "type": "address" 104 }, { 105 "indexed": true, 106 "internalType": "address", 107 "name": "to", 108 "type": "address" 109 }, { 110 "indexed": false, 111 "internalType": "uint256", 112 "name": "amount", 113 "type": "uint256" 114 }, { 115 "indexed": false, 116 "internalType": "uint256", 117 "name": "fromChainID", 118 "type": "uint256" 119 }, { 120 "indexed": false, 121 "internalType": "uint256", 122 "name": "toChainID", 123 "type": "uint256" 124 }], 125 "name": "LogAnySwapIn", 126 "type": "event" 127 }, { 128 "anonymous": false, 129 "inputs": [{ 130 "indexed": true, 131 "internalType": "address", 132 "name": "token", 133 "type": "address" 134 }, { 135 "indexed": true, 136 "internalType": "address", 137 "name": "from", 138 "type": "address" 139 }, { 140 "indexed": true, 141 "internalType": "address", 142 "name": "to", 143 "type": "address" 144 }, { 145 "indexed": false, 146 "internalType": "uint256", 147 "name": "amount", 148 "type": "uint256" 149 }, { 150 "indexed": false, 151 "internalType": "uint256", 152 "name": "fromChainID", 153 "type": "uint256" 154 }, { 155 "indexed": false, 156 "internalType": "uint256", 157 "name": "toChainID", 158 "type": "uint256" 159 }], 160 "name": "LogAnySwapOut", 161 "type": "event" 162 }]` 163 ) 164 165 // NewBridgeSwapScraper returns a new BridgeSwapScraper for the given pair 166 func NewBridgeSwapScraper(exchange dia.Exchange, scrape bool, relDB *models.RelDB) *BridgeSwapScraper { 167 var s *BridgeSwapScraper 168 // var waitgroup sync.WaitGroup 169 multichainconfigs = make(map[string]MultiChainConfig) 170 171 multichainconfigs["1"] = MultiChainConfig{restURL: chainConfigs["1"].RestURL, wsURL: chainConfigs["1"].WSURL, contratDeployedAtBlock: 12242619, contractAddress: "0x765277eebeca2e31912c9946eae1021199b39c61"} 172 multichainconfigs["56"] = MultiChainConfig{restURL: chainConfigs["56"].RestURL, wsURL: chainConfigs["56"].WSURL, contratDeployedAtBlock: 7910338, contractAddress: "0xd1c5966f9f5ee6881ff6b261bbeda45972b1b5f3"} 173 multichainconfigs["137"] = MultiChainConfig{restURL: chainConfigs["137"].RestURL, wsURL: chainConfigs["137"].WSURL, contratDeployedAtBlock: 17355461, contractAddress: "0x6ff0609046a38d76bd40c5863b4d1a2dce687f73"} 174 multichainconfigs["250"] = MultiChainConfig{restURL: chainConfigs["250"].RestURL, wsURL: chainConfigs["250"].WSURL, contratDeployedAtBlock: 8475644, contractAddress: "0x1ccca1ce62c62f7be95d4a67722a8fdbed6eecb4"} 175 multichainconfigs["42161"] = MultiChainConfig{restURL: chainConfigs["42161"].RestURL, wsURL: chainConfigs["42161"].WSURL, contratDeployedAtBlock: 15315466, contractAddress: "0x650af55d5877f289837c30b94af91538a7504b76"} 176 multichainconfigs["43114"] = MultiChainConfig{restURL: chainConfigs["43114"].RestURL, wsURL: chainConfigs["43114"].WSURL, contratDeployedAtBlock: 3397229, contractAddress: "0xB0731d50C681C45856BFc3f7539D5f61d4bE81D8"} 177 178 log.Info("NewBridgeSwapScraper: ", exchange.Name) 179 log.Infof("Init rest and ws client for %s.", exchange.BlockChain.Name) 180 181 s = &BridgeSwapScraper{ 182 chanTrades: make(chan *dia.Trade), 183 shutdown: make(chan nothing), 184 shutdownDone: make(chan nothing), 185 pairScrapers: make(map[string]*BridgeSwapPairScraper), 186 relDB: relDB, 187 } 188 189 if scrape { 190 s.loop() 191 } 192 193 return s 194 } 195 196 func (s *BridgeSwapScraper) loop() { 197 198 InitialiseRestClientsMap() 199 InitialiseWsClientsMap() 200 201 events := getFilteredEvents() 202 s.checkTransactionOnChain(events) 203 204 } 205 206 func (s *BridgeSwapScraper) checkTransactionOnChain(events chan types.Log) { 207 log.Infoln("Listening swaps") 208 209 //get hex value for swap function name for the transaction 210 LogAnySwapInbyteHex := crypto.Keccak256Hash([]byte("LogAnySwapIn(bytes32,address,address,uint256,uint256,uint256)")) 211 212 for { 213 msg := <-events 214 switch msg.Topics[0].Hex() { 215 216 case LogAnySwapInbyteHex.Hex(): 217 log.Infoln("msg TxHash", msg.TxHash) 218 219 event, contractAbi := getEventDetailsAbi("LogAnySwapIn", msg) 220 221 outAmount, _ := event[0].(*big.Int) 222 fromChainIdValue, _ := event[1].(*big.Int) 223 toChainIdValue, _ := event[2].(*big.Int) 224 225 tokenAddress := getMultichainUnderlyingToken(common.HexToAddress(msg.Topics[2].Hex()), toChainIdValue.String()) 226 227 if tokenAddress == common.HexToAddress("0x0000000000000000000000000000000000000000") { 228 tokenAddress = common.HexToAddress(msg.Topics[2].Hex()) 229 } 230 bs := BridgeSwapSwap{ 231 TransactionHash: msg.Topics[1].Hex(), 232 TokenAddress: tokenAddress, 233 ToAddress: common.HexToAddress(msg.Topics[3].Hex()), 234 Amount: outAmount, 235 FromChainId: fromChainIdValue, 236 ToChainId: toChainIdValue, 237 } 238 log.Debugln("BridgeSwap", bs) 239 tokenbridged, inAmount, err := getDetailsFromTransactionHash(msg, fromChainIdValue, contractAbi) 240 if err != nil { 241 continue 242 } 243 244 quoteTokenName, err := GetName(tokenAddress, toChainIdValue.String()) 245 if err != nil { 246 log.Warnf("Error getting GetName token %s of chain id %s", tokenAddress, toChainIdValue.String()) 247 } 248 quoteTokenSymbol, err := GetSymbol(tokenAddress, toChainIdValue.String()) 249 if err != nil { 250 log.Warnf("Error getting GetSymbol token %s of chain id %s", tokenAddress, toChainIdValue.String()) 251 } 252 quoteTokenDecimal, err := GetDecimals(tokenAddress, toChainIdValue.String()) 253 if err != nil { 254 log.Warnf("Error getting GetDecimals token %s of chain id %s", tokenAddress, toChainIdValue.String()) 255 continue 256 } 257 258 baseBlockchain, isAvailable := evmID[fromChainIdValue.String()] 259 if !isAvailable { 260 log.Warn("Blockchain configs not available for chain with ID ", fromChainIdValue) 261 continue 262 263 } 264 265 quoteBlockchain, isAvailable := evmID[toChainIdValue.String()] 266 if !isAvailable { 267 log.Warn("Blockchain configs not available for chain with ID ", toChainIdValue) 268 continue 269 270 } 271 272 quoteToken := dia.Asset{ 273 Address: tokenAddress.Hex(), 274 Symbol: quoteTokenSymbol, 275 Name: quoteTokenName, 276 Decimals: quoteTokenDecimal, 277 Blockchain: Blockchains[quoteBlockchain].Name, 278 } 279 280 baseTokenName, err := GetName(tokenbridged, fromChainIdValue.String()) 281 if err != nil { 282 log.Warnf("Error getting GetName token %s of chain id %s", tokenbridged, fromChainIdValue.String()) 283 } 284 baseTokenSymbol, err := GetSymbol(tokenbridged, fromChainIdValue.String()) 285 if err != nil { 286 log.Warnf("Error getting GetSymbol token %s of chain id %s", tokenbridged, fromChainIdValue.String()) 287 } 288 baseTokenDecimal, err := GetDecimals(tokenbridged, fromChainIdValue.String()) 289 if err != nil { 290 log.Warnf("Error getting GetDecimals token %s of chain id %s", tokenbridged, fromChainIdValue.String()) 291 continue 292 } 293 294 baseToken := dia.Asset{ 295 Address: tokenbridged.Hex(), 296 Symbol: baseTokenName, 297 Name: baseTokenSymbol, 298 Decimals: baseTokenDecimal, 299 Blockchain: Blockchains[baseBlockchain].Name, 300 } 301 302 inAmountt := inAmount.Quo(inAmount, inAmount.Exp(big.NewInt(10), big.NewInt(int64(baseTokenDecimal)), nil)) 303 304 outAmountt := outAmount.Div(outAmount, big.NewInt(int64(quoteTokenDecimal))) 305 306 priceamt := inAmountt.Div(inAmountt, outAmountt) 307 308 priceamtfloat, _ := new(big.Float).SetInt(priceamt).Float64() 309 inAmountfloat, _ := new(big.Float).SetInt(inAmountt).Float64() 310 311 t := &dia.Trade{ 312 Symbol: baseTokenName + "" + quoteTokenName, 313 Pair: baseTokenName + "" + quoteTokenName, 314 Price: priceamtfloat, 315 Volume: inAmountfloat, 316 BaseToken: baseToken, 317 QuoteToken: quoteToken, 318 Time: time.Now(), 319 ForeignTradeID: msg.TxHash.Hex(), 320 // Source: s.exchangeName, 321 VerifiedPair: true, 322 } 323 log.Println("trade", t) 324 s.mapasset(*t) 325 // s.chanTrades <- t 326 327 } 328 } 329 330 } 331 332 func (s *BridgeSwapScraper) mapasset(t dia.Trade) { 333 //check if quote token exists 334 335 quoteToken_id, err := s.relDB.GetAssetID(t.QuoteToken) 336 if err != nil { 337 log.Errorln("Error getting quotetoken asset id", err) 338 } 339 baseToken_id, err := s.relDB.GetAssetID(t.BaseToken) 340 if err != nil { 341 log.Errorln("Error getting basetoken asset id", err) 342 } 343 344 quote_group_id, err := s.relDB.GetAssetMap(quoteToken_id) 345 if err != nil { 346 log.Errorln("quotetoken not exists", quoteToken_id) 347 } else if quote_group_id != "" { 348 log.Errorln("InsertAssetMap1 ", quote_group_id, baseToken_id) 349 errInsertAssetMap := s.relDB.InsertAssetMap(quote_group_id, baseToken_id) 350 log.Errorln("err InsertAssetMap1", errInsertAssetMap) 351 return 352 } 353 354 base_group_id, err := s.relDB.GetAssetMap(baseToken_id) 355 if err != nil { 356 log.Errorln("base does not exists ") 357 } else if quote_group_id != "" { 358 log.Errorln("InsertAssetMap2 ", quote_group_id, baseToken_id) 359 errInsertAssetMap := s.relDB.InsertAssetMap(base_group_id, quoteToken_id) 360 log.Errorln("err InsertAssetMap2", errInsertAssetMap) 361 362 return 363 } 364 log.Errorln("InsertAssetMap3 ", quoteToken_id) 365 366 err = s.relDB.InsertNewAssetMap(quoteToken_id) 367 log.Errorln("err InsertAssetMap3", err) 368 369 gpid, err := s.relDB.GetAssetMap(quoteToken_id) 370 if err != nil { 371 log.Errorln("gpid generated err ", err) 372 } 373 s.relDB.InsertAssetMap(gpid, baseToken_id) 374 log.Infoln("quote_group_id, base_group_id", baseToken_id, gpid) 375 } 376 377 func GetDecimals(tokenAddress common.Address, chainid string) (decimals uint8, err error) { 378 379 var contract *uniswap.IERC20Caller 380 contract, err = uniswap.NewIERC20Caller(tokenAddress, restClients[chainid]) 381 if err != nil { 382 log.Error(err) 383 return 384 } 385 decimals, err = contract.Decimals(&bind.CallOpts{}) 386 387 return 388 } 389 390 func GetName(tokenAddress common.Address, chainid string) (name string, err error) { 391 392 var contract *uniswap.IERC20Caller 393 contract, err = uniswap.NewIERC20Caller(tokenAddress, restClients[chainid]) 394 if err != nil { 395 log.Error(err) 396 return 397 } 398 name, err = contract.Name(&bind.CallOpts{}) 399 400 return 401 } 402 403 func GetSymbol(tokenAddress common.Address, chainid string) (name string, err error) { 404 405 var contract *uniswap.IERC20Caller 406 contract, err = uniswap.NewIERC20Caller(tokenAddress, restClients[chainid]) 407 if err != nil { 408 log.Error(err) 409 return 410 } 411 name, err = contract.Symbol(&bind.CallOpts{}) 412 413 return 414 } 415 416 func getEventDetailsAbi(funcName string, msg types.Log) ([]interface{}, abi.ABI) { 417 var event []interface{} 418 419 contractAbi, err := abi.JSON(strings.NewReader(abiString)) 420 if err != nil { 421 log.Fatal("read contract abi: ", err) 422 } 423 event, err = contractAbi.Unpack(funcName, msg.Data) 424 if err != nil { 425 log.Fatal("unpack event: ", err) 426 } 427 return event, contractAbi 428 } 429 430 func getDetailsFromTransactionHash(msg types.Log, fromChainIdValue *big.Int, contractAbi abi.ABI) (tokenmoved common.Address, inAmount *big.Int, err error) { 431 //check if chain address value is present in map 432 restClient, ok := restClients[fromChainIdValue.String()] 433 //LogAnySwapOut (index_topic_1 address token, index_topic_2 address from, index_topic_3 address to, uint256 amount, uint256 fromChainID, uint256 toChainID) 434 LogAnySwapOutbyteHex := crypto.Keccak256Hash([]byte("LogAnySwapOut(address,address,address,uint256,uint256,uint256)")) 435 if ok { 436 //get transaction receipt from transaction hash 437 var receipt *types.Receipt 438 receipt, err = restClient.TransactionReceipt(context.Background(), common.HexToHash(msg.Topics[1].Hex())) 439 if err != nil { 440 log.Errorf("fetch transaction receipt of tx %s on chain with ID %v: %v", msg.Topics[1].Hex(), fromChainIdValue.Int64(), err) 441 return 442 } 443 444 for _, txlog := range receipt.Logs { 445 switch txlog.Topics[0].Hex() { 446 447 case LogAnySwapOutbyteHex.Hex(): 448 fmt.Println("token swapped between chains ", common.HexToAddress(txlog.Topics[1].Hex())) 449 tokenmoved = getMultichainUnderlyingToken(common.HexToAddress(txlog.Topics[1].Hex()), fromChainIdValue.String()) 450 // fmt.Println("underlyingtoken", underlyingtoken) 451 event, errUnpack := contractAbi.Unpack("LogAnySwapOut", txlog.Data) 452 if errUnpack != nil { 453 log.Fatal("unpack event LogAnySwapOut: ", errUnpack) 454 } 455 fmt.Println("------", event[0].(*big.Int)) 456 457 inAmount = event[0].(*big.Int) 458 } 459 } 460 //putDetailsInChanel() 461 } else { 462 fmt.Println("-------Client not available for this chain---------", fromChainIdValue) 463 err = errors.New("client not available for chain" + fromChainIdValue.String()) 464 return 465 } 466 467 return 468 } 469 470 func getMultichainUnderlyingToken(multichainTokenAddress common.Address, chainid string) (tokenAddress common.Address) { 471 472 anyerc20caller, err := anyerc20.NewAnyerc20Caller(multichainTokenAddress, restClients[chainid]) 473 if err != nil { 474 log.Errorln(err) 475 return 476 } 477 478 tokenAddress, _ = anyerc20caller.Underlying(&bind.CallOpts{}) 479 480 return 481 482 } 483 484 func InitialiseRestClientsMap() { 485 var err error 486 restClients = make(map[string]*ethclient.Client) 487 488 restClients["250"], err = ethclient.Dial(ftmHTTP) 489 if err != nil { 490 log.Fatal("init Fantom rest client: ", err) 491 } 492 493 restClients["1"], err = ethclient.Dial(ethHTTP) 494 if err != nil { 495 log.Fatal("init Ethereum rest client: ", err) 496 } 497 498 restClients["137"], err = ethclient.Dial(multichainconfigs["137"].restURL) 499 if err != nil { 500 log.Fatal("init Polygon rest client: ", err) 501 } 502 restClients["56"], err = ethclient.Dial(multichainconfigs["56"].restURL) 503 if err != nil { 504 log.Fatal("init Binance Smart Chain rest client: ", err) 505 } 506 507 restClients["25"], err = ethclient.Dial("https://cronosrpc-1.xstaking.sg") 508 if err != nil { 509 log.Fatal("init Cronos rest client: ", err) 510 } 511 512 restClients["43114"], err = ethclient.Dial("https://rpc.ankr.com/avalanche") 513 if err != nil { 514 log.Fatal("init Avalanche rest client: ", err) 515 } 516 517 restClients["10"], err = ethclient.Dial("https://mainnet.optimism.io") 518 if err != nil { 519 log.Fatal("init Optimism rest client: ", err) 520 } 521 522 restClients["1285"], err = ethclient.Dial("https://rpc.api.moonriver.moonbeam.network") 523 if err != nil { 524 log.Fatal("init Moonbeam rest client: ", err) 525 } 526 527 restClients["66"], err = ethclient.Dial("https://exchainrpc.okex.org") 528 if err != nil { 529 log.Fatal("init OKXChain rest client: ", err) 530 } 531 532 restClients["42161"], err = ethclient.Dial("https://arb1.arbitrum.io/rpc") 533 if err != nil { 534 log.Fatal("init Arbitrum rest client: ", err) 535 } 536 537 } 538 539 func InitialiseWsClientsMap() { 540 var err error 541 542 wsClients = make(map[string]*ethclient.Client) 543 544 for chainID, chainconfig := range multichainconfigs { 545 wsClients[chainID], err = ethclient.Dial(chainconfig.wsURL) 546 if err != nil { 547 log.Errorf("init ws client on chain with id %s: %v", chainID, err) 548 } 549 550 } 551 552 } 553 554 func getFilteredEvents() chan types.Log { 555 556 var channels []chan types.Log 557 out := make(chan types.Log) 558 559 for chainID, config := range multichainconfigs { 560 561 //filter query by block number 562 query := ethereum.FilterQuery{ 563 FromBlock: big.NewInt(config.contratDeployedAtBlock), 564 Addresses: []common.Address{ 565 common.HexToAddress(config.contractAddress), 566 }, 567 } 568 569 events := make(chan types.Log) 570 _, err := wsClients[chainID].SubscribeFilterLogs(context.Background(), query, events) 571 if err != nil { 572 log.Fatal("error connecting to wsclient", err) 573 } 574 575 channels = append(channels, events) 576 577 } 578 579 // merge all events in single channel 580 581 for _, c := range channels { 582 go func(c <-chan types.Log) { 583 for v := range c { 584 out <- v 585 } 586 }(c) 587 } 588 589 return out 590 } 591 592 // BridgeSwapPairScraper implements PairScraper for Uniswap 593 type BridgeSwapPairScraper struct { 594 parent *BridgeSwapScraper 595 pair dia.ExchangePair 596 closed bool 597 } 598 599 func (s *BridgeSwapScraper) ScrapePair(pair dia.ExchangePair) (PairScraper, error) { 600 601 s.errorLock.RLock() 602 defer s.errorLock.RUnlock() 603 if s.error != nil { 604 return nil, s.error 605 } 606 if s.closed { 607 return nil, errors.New("UniswapScraper: Call ScrapePair on closed scraper") 608 } 609 ps := &BridgeSwapPairScraper{ 610 parent: s, 611 pair: pair, 612 } 613 s.pairScrapers[pair.ForeignName] = ps 614 return ps, nil 615 616 } 617 618 func (b *BridgeSwapScraper) FetchAvailablePairs() (pairs []dia.ExchangePair, err error) { 619 620 return []dia.ExchangePair{}, nil 621 622 } 623 624 func (ps *BridgeSwapScraper) Channel() chan *dia.Trade { 625 return ps.chanTrades 626 627 } 628 629 func (s *BridgeSwapScraper) FillSymbolData(symbol string) (dia.Asset, error) { 630 return dia.Asset{Symbol: symbol}, nil 631 } 632 633 func (up *BridgeSwapScraper) NormalizePair(pair dia.ExchangePair) (dia.ExchangePair, error) { 634 return pair, nil 635 } 636 637 // Close closes any existing API connections, as well as channels of 638 // PairScrapers from calls to ScrapePair 639 func (s *BridgeSwapScraper) Close() error { 640 if s.closed { 641 return errors.New("BridgeSwapScraper: Already closed") 642 } 643 s.WsClient.Close() 644 s.RestClient.Close() 645 close(s.shutdown) 646 <-s.shutdownDone 647 s.errorLock.RLock() 648 defer s.errorLock.RUnlock() 649 return s.error 650 } 651 652 // Close stops listening for trades of the pair associated with s 653 func (ps *BridgeSwapPairScraper) Close() error { 654 ps.closed = true 655 return nil 656 } 657 658 // Error returns an error when the channel Channel() is closed 659 // and nil otherwise 660 func (ps *BridgeSwapPairScraper) Error() error { 661 s := ps.parent 662 s.errorLock.RLock() 663 defer s.errorLock.RUnlock() 664 return s.error 665 } 666 667 // Pair returns the pair this scraper is subscribed to 668 func (ps *BridgeSwapPairScraper) Pair() dia.ExchangePair { 669 return ps.pair 670 }