github.com/diadata-org/diadata@v1.4.593/pkg/dia/scraper/exchange-scrapers/TraderJoeScraper.go (about)

     1  package scrapers
     2  
     3  import (
     4  	"encoding/json"
     5  	"errors"
     6  	"io"
     7  	"math"
     8  	"math/big"
     9  	"os"
    10  	"strconv"
    11  	"strings"
    12  	"sync"
    13  	"time"
    14  
    15  	"github.com/diadata-org/diadata/pkg/dia"
    16  	"github.com/diadata-org/diadata/pkg/dia/helpers"
    17  	"github.com/diadata-org/diadata/pkg/dia/helpers/configCollectors"
    18  	"github.com/diadata-org/diadata/pkg/dia/scraper/exchange-scrapers/traderjoe2.1/traderjoeILBPair"
    19  	models "github.com/diadata-org/diadata/pkg/model"
    20  	"github.com/diadata-org/diadata/pkg/utils"
    21  	"github.com/ethereum/go-ethereum/accounts/abi/bind"
    22  	"github.com/ethereum/go-ethereum/common"
    23  	"github.com/ethereum/go-ethereum/ethclient"
    24  )
    25  
    26  var (
    27  	TraderJoeExchangeFactoryContractAddress = "0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f"
    28  
    29  	MapOfPools = make(map[string]TraderJoePair)
    30  )
    31  
    32  type TraderJoeTokens struct {
    33  	Address  common.Address
    34  	Symbol   string
    35  	Decimals uint8
    36  	Name     string
    37  }
    38  
    39  type TraderJoePair struct {
    40  	Token0      TraderJoeTokens
    41  	Token1      TraderJoeTokens
    42  	ForeignName string
    43  	Address     common.Address
    44  }
    45  
    46  type TraderJoeSwap struct {
    47  	ID        string
    48  	Timestamp int64
    49  	Pair      TraderJoePair
    50  	Amount0   float64
    51  	Amount1   float64
    52  }
    53  
    54  type TraderJoeScraper struct {
    55  	// Ethereum WebSocket client for real-time data.
    56  	WsClient *ethclient.Client
    57  	// Ethereum REST client for querying historical data.
    58  	RestClient *ethclient.Client
    59  	// Relational database connection.
    60  	relDB *models.RelDB
    61  	// Signaling channels for managing session start and shutdown.
    62  	run          bool
    63  	shutdown     chan nothing
    64  	shutdownDone chan nothing
    65  	// Error handling; read lock for error or closed status.
    66  	errorLock sync.RWMutex
    67  	error     error
    68  	closed    bool
    69  	// Map of active TraderJoeTradeScraper instances for trading pairs.
    70  	pairScrapers map[string]*TraderJoeTradeScraper
    71  	// Channel to receive new trading pairs for scraping.
    72  	pairReceived chan *TraderJoePair
    73  	// Name of the exchange.
    74  	exchangeName string
    75  	// Time interval for waiting between actions.
    76  	waitTime int
    77  	// Option to listen for trading pairs by address.
    78  	listenByAddress bool
    79  	// Channel for receiving trade data.
    80  	chanTrades chan *dia.Trade
    81  	// Address of the factory contract for the exchange.
    82  	factoryContractAddress common.Address
    83  }
    84  
    85  // NewTraderJoeScraper initializes a Trader Joe scraper instance with the provided exchange information,
    86  // scraping flag, and relational database connection. It configures parameters, sets up pool maps,
    87  // and starts the scraping process if requested.
    88  func NewTraderJoeScraper(exchange dia.Exchange, scrape bool, relDB *models.RelDB) *TraderJoeScraper {
    89  	log.Info("NewTraderJoeScraper ", exchange.Name)
    90  	log.Info("NewTraderJoeScraper Address ", exchange.Contract)
    91  
    92  	var (
    93  		tjs             *TraderJoeScraper
    94  		listenByAddress bool
    95  		err             error
    96  	)
    97  
    98  	listenByAddress, err = strconv.ParseBool(utils.Getenv("LISTEN_BY_ADDRESS", ""))
    99  	if err != nil {
   100  		log.Fatal("parse LISTEN_BY_ADDRESS: ", err)
   101  	}
   102  
   103  	switch exchange.Name {
   104  	case dia.TraderJoeExchangeV2_1:
   105  		tjs = makeTraderJoeScraper(exchange, listenByAddress, "", "", "200")
   106  	case dia.TraderJoeExchangeV2_1Arbitrum:
   107  		tjs = makeTraderJoeScraper(exchange, listenByAddress, "", "", "200")
   108  	case dia.TraderJoeExchangeV2_1Avalanche:
   109  		tjs = makeTraderJoeScraper(exchange, listenByAddress, "", "", "200")
   110  	case dia.TraderJoeExchangeV2_1BNB:
   111  		tjs = makeTraderJoeScraper(exchange, listenByAddress, "", "", "200")
   112  	case dia.TraderJoeExchangeV2_2Avalanche:
   113  		tjs = makeTraderJoeScraper(exchange, listenByAddress, "", "", "200")
   114  
   115  	}
   116  
   117  	tjs.relDB = relDB
   118  
   119  	// Only include pools with (minimum) liquidity bigger than given env var.
   120  	liquidityThreshold, err := strconv.ParseFloat(utils.Getenv("LIQUIDITY_THRESHOLD", "0"), 64)
   121  	if err != nil {
   122  		liquidityThreshold = float64(0)
   123  		log.Warnf("parse liquidity threshold:  %v. Set to default %v", err, liquidityThreshold)
   124  	}
   125  	liquidityThresholdUSD, err := strconv.ParseFloat(utils.Getenv("LIQUIDITY_THRESHOLD_USD", "0"), 64)
   126  	if err != nil {
   127  		liquidityThresholdUSD = float64(0)
   128  		log.Warnf("parse liquidity threshold:  %v. Set to default %v", err, liquidityThresholdUSD)
   129  	}
   130  
   131  	MapOfPools, err = tjs.makeTraderJoePoolMap(liquidityThreshold, liquidityThresholdUSD)
   132  	if err != nil {
   133  		log.Fatal("build poolMap: ", err)
   134  	}
   135  
   136  	if scrape {
   137  		go tjs.mainLoop()
   138  	}
   139  	return tjs
   140  }
   141  
   142  // makeTraderJoeScraper creates and initializes a Trader Joe scraper instance with the given exchange information,
   143  // connection details, and configuration parameters. It establishes REST and WebSocket clients for the blockchain,
   144  // determines wait time, and sets up various channels and data structures for scraping tasks.
   145  func makeTraderJoeScraper(exchange dia.Exchange, listenByAddress bool, restDial string, wsDial string, waitMilliseconds string) *TraderJoeScraper {
   146  	var (
   147  		restClient, wsClient *ethclient.Client
   148  		s                    *TraderJoeScraper
   149  		err                  error
   150  	)
   151  
   152  	log.Infof("Initialize rest and ws client for %s.", exchange.BlockChain.Name)
   153  	restClient, err = ethclient.Dial(utils.Getenv(strings.ToUpper(exchange.BlockChain.Name)+"_URI_REST", restDial))
   154  	if err != nil {
   155  		log.Fatal("init rest client: ", err)
   156  	}
   157  	wsClient, err = ethclient.Dial(utils.Getenv(strings.ToUpper(exchange.BlockChain.Name)+"_URI_WS", wsDial))
   158  	if err != nil {
   159  		log.Fatal("init ws client: ", err)
   160  	}
   161  
   162  	var waitTime int
   163  	waitTimeString := utils.Getenv(strings.ToUpper(exchange.BlockChain.Name)+"_WAIT_TIME", waitMilliseconds)
   164  	waitTime, err = strconv.Atoi(waitTimeString)
   165  	if err != nil {
   166  		log.Error("could not parse wait time: ", err)
   167  		waitTime = 500
   168  	}
   169  
   170  	s = &TraderJoeScraper{
   171  		WsClient:               wsClient,
   172  		RestClient:             restClient,
   173  		shutdown:               make(chan nothing),
   174  		shutdownDone:           make(chan nothing),
   175  		pairScrapers:           make(map[string]*TraderJoeTradeScraper),
   176  		exchangeName:           exchange.Name,
   177  		pairReceived:           make(chan *TraderJoePair),
   178  		error:                  nil,
   179  		chanTrades:             make(chan *dia.Trade),
   180  		waitTime:               waitTime,
   181  		listenByAddress:        listenByAddress,
   182  		factoryContractAddress: common.HexToAddress(exchange.Contract),
   183  	}
   184  
   185  	return s
   186  }
   187  
   188  // GetSwapsChannel returns a channel for swaps of the pair with pair address.
   189  func (tjs *TraderJoeScraper) GetSwapsChannel(pairAddress common.Address) (chan *traderjoeILBPair.ILBPairSwap, error) {
   190  	sink := make(chan *traderjoeILBPair.ILBPairSwap)
   191  
   192  	pairFiltererContract, err := traderjoeILBPair.NewILBPairFilterer(pairAddress, tjs.WsClient)
   193  	if err != nil {
   194  		log.Fatal(err)
   195  	}
   196  	_, err = pairFiltererContract.WatchSwap(&bind.WatchOpts{}, sink, []common.Address{}, []common.Address{})
   197  	if err != nil {
   198  		log.Error("error in get swaps channel: ", err)
   199  	}
   200  
   201  	return sink, nil
   202  }
   203  
   204  func (tjs *TraderJoeScraper) normalizeTraderJoeSwap(swap traderjoeILBPair.ILBPairSwap) (normalizedSwap TraderJoeSwap) {
   205  
   206  	pair := MapOfPools[swap.Raw.Address.Hex()]
   207  	decimals0 := int(pair.Token0.Decimals)
   208  	decimals1 := int(pair.Token1.Decimals)
   209  
   210  	amount1In := new(big.Int).SetBytes(swap.AmountsIn[:16])
   211  	amount0In := new(big.Int).SetBytes(swap.AmountsIn[16:])
   212  	amount1Out := new(big.Int).SetBytes(swap.AmountsOut[:16])
   213  	amount0Out := new(big.Int).SetBytes(swap.AmountsOut[16:])
   214  	var amount0, amount1 float64
   215  
   216  	if amount0In.Cmp(big.NewInt(0)) == 1 {
   217  		amount0, _ = new(big.Float).Quo(big.NewFloat(0).SetInt(amount0In), new(big.Float).SetFloat64(math.Pow10(decimals0))).Float64()
   218  	} else {
   219  		amount0, _ = new(big.Float).Quo(big.NewFloat(0).SetInt(amount0Out), new(big.Float).SetFloat64(math.Pow10(decimals0))).Float64()
   220  	}
   221  	if amount1In.Cmp(big.NewInt(0)) == 1 {
   222  		amount1, _ = new(big.Float).Quo(big.NewFloat(0).SetInt(amount1In), new(big.Float).SetFloat64(math.Pow10(decimals1))).Float64()
   223  	} else {
   224  		amount1, _ = new(big.Float).Quo(big.NewFloat(0).SetInt(amount1Out), new(big.Float).SetFloat64(math.Pow10(decimals1))).Float64()
   225  	}
   226  
   227  	normalizedSwap = TraderJoeSwap{
   228  		ID:        swap.Raw.TxHash.Hex(),
   229  		Timestamp: time.Now().UnixNano(),
   230  		Pair:      pair,
   231  		Amount0:   amount0,
   232  		Amount1:   amount1,
   233  	}
   234  	return
   235  }
   236  
   237  // mainLoop is the central loop of the Trader Joe scraper that manages the subscription and scraping of pairs.
   238  // It initializes the process by retrieving reverse base tokens and quote tokens from configuration. After a brief
   239  // initial delay, it sets the `run` flag to true and kicks off a goroutine to feed pools to subscriptions.
   240  // The function then listens for incoming pairs from the `pairReceived` channel and subscribes to and scrapes data
   241  // for each pair. It performs various checks to skip pairs that don't meet certain criteria, such as blacklisted
   242  // tokens or pools. It also logs relevant information about the progress of the loop.
   243  func (tjs *TraderJoeScraper) mainLoop() {
   244  	var err error
   245  	reverseBasetokens, err = getReverseTokensFromConfig("traderjoe/reverse_tokens/" + tjs.exchangeName + "Basetoken")
   246  	if err != nil {
   247  		log.Error("error getting base tokens for which pairs should be reversed: ", err)
   248  	}
   249  	log.Infof("reverse the following basetokens on %s: %v", tjs.exchangeName, reverseBasetokens)
   250  	reverseQuotetokens, err = getReverseTokensFromConfig("traderjoe/reverse_tokens/" + tjs.exchangeName + "Quotetoken")
   251  	if err != nil {
   252  		log.Error("error getting quote tokens for which pairs should be reversed: ", err)
   253  	}
   254  	log.Infof("reverse the following quotetokens on %s: %v", tjs.exchangeName, reverseQuotetokens)
   255  
   256  	time.Sleep(4 * time.Second)
   257  	tjs.run = true
   258  
   259  	go func() {
   260  		pools := tjs.feedPoolsToSubscriptions()
   261  		log.Info("Found ", len(pools), " pairs")
   262  		log.Info("Found ", len(tjs.pairScrapers), " pairScrapers")
   263  	}()
   264  
   265  	if len(tjs.pairScrapers) == 0 {
   266  		tjs.error = errors.New("traderjoe scraper: No pairs to scrape provided")
   267  		log.Error(tjs.error.Error())
   268  	}
   269  
   270  	count := 0
   271  	for {
   272  		pool := <-tjs.pairReceived
   273  		log.Infoln("Subscribing for pair: ", pool)
   274  
   275  		if len(pool.Token0.Symbol) < 2 || len(pool.Token1.Symbol) < 2 {
   276  			log.Info("skip pair: ", pool.ForeignName)
   277  			continue
   278  		}
   279  		if helpers.AddressIsBlacklisted(pool.Token0.Address) || helpers.AddressIsBlacklisted(pool.Token1.Address) {
   280  			log.Info("skip pair ", pool.ForeignName, ", address is blacklisted")
   281  			continue
   282  		}
   283  		if helpers.PoolIsBlacklisted(pool.Address) {
   284  			log.Info("skip blacklisted pool ", pool.Address)
   285  			continue
   286  		}
   287  		log.Infof("%v found pair scraper for: %s with address %s", count, pool.ForeignName, pool.Address.Hex())
   288  		count++
   289  
   290  		sink, err := tjs.GetSwapsChannel(pool.Address)
   291  		if err != nil {
   292  			log.Error("error fetching swaps channel: ", err)
   293  		}
   294  		go func() {
   295  			for {
   296  				rawSwap, ok := <-sink
   297  				if ok {
   298  					swap := tjs.normalizeTraderJoeSwap(*rawSwap)
   299  					tjs.sendTrade(swap, pool)
   300  				}
   301  			}
   302  		}()
   303  
   304  	}
   305  }
   306  
   307  // makeTraderJoePoolMap generates a map of Trader Joe pool pairs based on the provided liquidity thresholds and configuration.
   308  // It retrieves pool information either by specific addresses from a JSON file or by querying the database for all pools above
   309  // the liquidity threshold. The resulting pool map includes pairs with sufficient liquidity and handles lower-bound checks.
   310  // It returns the generated pool map and any error encountered during the process.
   311  func (tjs *TraderJoeScraper) makeTraderJoePoolMap(liquidityThreshold, liquidityThresholdUSD float64) (map[string]TraderJoePair, error) {
   312  	poolMap := make(map[string]TraderJoePair)
   313  	var (
   314  		pools []dia.Pool
   315  		err   error
   316  	)
   317  
   318  	if tjs.listenByAddress {
   319  		// Only load pool info for addresses from json file.
   320  		poolAddresses, errAddr := getTradeAddressesFromConfig("traderjoe/subscribe_pools/" + tjs.exchangeName)
   321  		if errAddr != nil {
   322  			log.Error("fetch pool addresses from config file: ", errAddr)
   323  		}
   324  		for _, address := range poolAddresses {
   325  			pool, errPool := tjs.relDB.GetPoolByAddress(Exchanges[tjs.exchangeName].BlockChain.Name, address.Hex())
   326  			if errPool != nil {
   327  				log.Fatalf("Get pool with address %s: %v", address.Hex(), errPool)
   328  			}
   329  			pools = append(pools, pool)
   330  		}
   331  	} else {
   332  		// Load all pools above liquidity threshold.
   333  		pools, err = tjs.relDB.GetAllPoolsExchange(tjs.exchangeName, liquidityThreshold)
   334  		if err != nil {
   335  			return poolMap, err
   336  		}
   337  	}
   338  
   339  	log.Info("Found ", len(pools), " pools.")
   340  	log.Info("make pool map...")
   341  	lowerBoundCount := 0
   342  	for _, pool := range pools {
   343  		if len(pool.Assetvolumes) != 2 {
   344  			continue
   345  		}
   346  		liquidity, lowerBound := pool.GetPoolLiquidityUSD()
   347  		// Discard pool if complete USD liquidity is below threshold.
   348  		if !lowerBound && liquidity < liquidityThresholdUSD {
   349  			continue
   350  		}
   351  		if lowerBound {
   352  			lowerBoundCount++
   353  		}
   354  
   355  		up := TraderJoePair{
   356  			Address: common.HexToAddress(pool.Address),
   357  		}
   358  		if pool.Assetvolumes[0].Index == 0 {
   359  			up.Token0 = asset2TraderJoeAsset(pool.Assetvolumes[0].Asset)
   360  			up.Token1 = asset2TraderJoeAsset(pool.Assetvolumes[1].Asset)
   361  		} else {
   362  			up.Token0 = asset2TraderJoeAsset(pool.Assetvolumes[1].Asset)
   363  			up.Token1 = asset2TraderJoeAsset(pool.Assetvolumes[0].Asset)
   364  		}
   365  		up.ForeignName = up.Token0.Symbol + "-" + up.Token1.Symbol
   366  		poolMap[pool.Address] = up
   367  	}
   368  
   369  	log.Infof("found %v subscribable pools.", len(poolMap))
   370  	log.Infof("%v pools with lowerBound=true.", lowerBoundCount)
   371  
   372  	return poolMap, err
   373  }
   374  
   375  // sendTrade receives Trader Joe trade data and transforms it into a standardized dia.Trade
   376  // structure for further analysis and publication.
   377  func (tjs *TraderJoeScraper) sendTrade(traderjoeswap TraderJoeSwap, pool *TraderJoePair) {
   378  	price, volume := tjs.getTradeData(traderjoeswap)
   379  	token0 := dia.Asset{
   380  		Address:    pool.Token0.Address.Hex(),
   381  		Symbol:     pool.Token0.Symbol,
   382  		Name:       pool.Token0.Name,
   383  		Decimals:   pool.Token0.Decimals,
   384  		Blockchain: Exchanges[tjs.exchangeName].BlockChain.Name,
   385  	}
   386  	token1 := dia.Asset{
   387  		Address:    pool.Token1.Address.Hex(),
   388  		Symbol:     pool.Token1.Symbol,
   389  		Name:       pool.Token1.Name,
   390  		Decimals:   pool.Token1.Decimals,
   391  		Blockchain: Exchanges[tjs.exchangeName].BlockChain.Name,
   392  	}
   393  
   394  	t := &dia.Trade{
   395  		Symbol:         pool.Token0.Symbol,
   396  		Pair:           pool.ForeignName,
   397  		QuoteToken:     token0,
   398  		BaseToken:      token1,
   399  		Price:          price,
   400  		Volume:         volume,
   401  		Time:           time.Unix(0, traderjoeswap.Timestamp),
   402  		PoolAddress:    pool.Address.Hex(),
   403  		ForeignTradeID: traderjoeswap.ID,
   404  		//EstimatedUSDPrice: 0,
   405  		Source:       tjs.exchangeName,
   406  		VerifiedPair: true,
   407  	}
   408  
   409  	switch {
   410  	case utils.Contains(reverseBasetokens, pool.Token1.Address.Hex()):
   411  		// If we need quotation of a base token, reverse pair
   412  		tSwapped, err := dia.SwapTrade(*t)
   413  		if err == nil {
   414  			t = &tSwapped
   415  		}
   416  	case utils.Contains(reverseQuotetokens, pool.Token0.Address.Hex()):
   417  		// If we need quotation of a base token, reverse pair
   418  		tSwapped, err := dia.SwapTrade(*t)
   419  		if err == nil {
   420  			t = &tSwapped
   421  		}
   422  	}
   423  	if price > 0 {
   424  		log.Infof("Got trade on pool %s: %v", pool.Address.Hex(), t)
   425  		tjs.chanTrades <- t
   426  	}
   427  }
   428  
   429  // TraderJoeTradeScraper represents a scraper for collecting trade data associated with a specific dia.ExchangePair
   430  // within the Trader Joe exchange.
   431  type TraderJoeTradeScraper struct {
   432  	parent *TraderJoeScraper
   433  	pair   dia.ExchangePair
   434  }
   435  
   436  func (tjs *TraderJoeScraper) FetchAvailablePairs() ([]dia.ExchangePair, error) {
   437  	return []dia.ExchangePair{}, nil
   438  }
   439  
   440  func (tjs *TraderJoeScraper) FillSymbolData(symbol string) (dia.Asset, error) {
   441  	return dia.Asset{Symbol: symbol}, nil
   442  }
   443  
   444  func (tjs *TraderJoeScraper) NormalizePair(pair dia.ExchangePair) (dia.ExchangePair, error) {
   445  	return pair, nil
   446  }
   447  
   448  // Close closes any existing API connections, as well as channels of PairScrapers from calls to ScrapePair
   449  func (tjs *TraderJoeScraper) Close() error {
   450  	if tjs.closed {
   451  		return errors.New("TraderJoeScraper: Already closed")
   452  	}
   453  	tjs.WsClient.Close()
   454  	tjs.RestClient.Close()
   455  	close(tjs.shutdown)
   456  	<-tjs.shutdownDone
   457  	tjs.errorLock.RLock()
   458  	defer tjs.errorLock.RUnlock()
   459  	return tjs.error
   460  }
   461  
   462  // getTradeData extracts price and volume data from TraderJoe trade information.
   463  func (tjs *TraderJoeScraper) getTradeData(swap TraderJoeSwap) (price, volume float64) {
   464  	volume = swap.Amount0
   465  	price = math.Abs(swap.Amount1 / swap.Amount0)
   466  	return
   467  }
   468  
   469  // ScrapePair initiates a new scraping process for the specified dia.ExchangePair within the Trader Joe scraper.
   470  // It checks for any previously encountered errors using a read lock on the error lock. If an error is present,
   471  // it returns that error. Additionally, if the Trader Joe scraper has been closed, it returns an error indicating
   472  // that ScrapePair cannot be called on a closed pair. Otherwise, it creates a new TraderJoeTradeScraper instance
   473  // associated with the provided ExchangePair, adds it to the list of active pair scrapers, and returns it along
   474  // with a nil error to indicate successful initiation.
   475  func (tjs *TraderJoeScraper) ScrapePair(pair dia.ExchangePair) (PairScraper, error) {
   476  	tjs.errorLock.RLock()
   477  	defer tjs.errorLock.RUnlock()
   478  	if tjs.error != nil {
   479  		return nil, tjs.error
   480  	}
   481  
   482  	if tjs.closed {
   483  		return nil, errors.New("TraderJoeScraper: Call Scrape Pair on closed pair")
   484  	}
   485  
   486  	pairScraper := &TraderJoeTradeScraper{
   487  		parent: tjs,
   488  		pair:   pair,
   489  	}
   490  
   491  	tjs.pairScrapers[pair.ForeignName] = pairScraper
   492  
   493  	return pairScraper, nil
   494  }
   495  
   496  // Close closes the TraderJoeTradeScraper instance.
   497  func (ps TraderJoeTradeScraper) Close() error {
   498  	return nil
   499  }
   500  
   501  // Error returns the error associated with the parent Trader Joe scraper. It retrieves the error from the parent scraper's state
   502  // using a read lock on the error lock. This function is useful for obtaining any error that occurred during scraping tasks.
   503  func (ps TraderJoeTradeScraper) Error() error {
   504  	tjs := ps.parent
   505  	tjs.errorLock.RLock()
   506  	defer tjs.errorLock.RUnlock()
   507  	return tjs.error
   508  }
   509  
   510  // Pair returns the dia.ExchangePair associated with the current Trader Joe trade scraper.
   511  // It simply retrieves and returns the ExchangePair stored within the scraper's state.
   512  func (ps TraderJoeTradeScraper) Pair() dia.ExchangePair {
   513  	return ps.pair
   514  }
   515  
   516  // Channel returns a channel that can be used to receive trades
   517  func (tjs *TraderJoeScraper) Channel() chan *dia.Trade {
   518  	return tjs.chanTrades
   519  }
   520  
   521  // asset2TraderJoeAsset converts a dia.Asset into a TraderJoeTokens structure.
   522  // It takes the provided asset's address, decimals, symbol, and name,
   523  // and returns a TraderJoeTokens representation containing the same information.
   524  func asset2TraderJoeAsset(asset dia.Asset) TraderJoeTokens {
   525  	return TraderJoeTokens{
   526  		Address:  common.HexToAddress(asset.Address),
   527  		Decimals: asset.Decimals,
   528  		Symbol:   asset.Symbol,
   529  		Name:     asset.Name,
   530  	}
   531  }
   532  
   533  // getTradeAddressesFromConfig reads a JSON configuration file specified by the provided filename and retrieves
   534  // trading pair addresses. The function opens and reads the file, unmarshals the data to extract pairs' addresses
   535  // and foreign names, and returns a slice of common.Address containing the extracted addresses. In case of any
   536  func getTradeAddressesFromConfig(filename string) (pairAddresses []common.Address, err error) {
   537  
   538  	// Load file and read data
   539  	fileHandle := configCollectors.ConfigFileConnectors(filename, ".json")
   540  	jsonFile, err := os.Open(fileHandle)
   541  	if err != nil {
   542  		return
   543  	}
   544  	defer func() {
   545  		err = jsonFile.Close()
   546  		if err != nil {
   547  			log.Error(err)
   548  		}
   549  	}()
   550  
   551  	byteData, err := io.ReadAll(jsonFile)
   552  	if err != nil {
   553  		return
   554  	}
   555  
   556  	// Unmarshal read data
   557  	type scrapedPair struct {
   558  		Address     string `json:"Address"`
   559  		ForeignName string `json:"ForeignName"`
   560  	}
   561  	type scrapedPairList struct {
   562  		AllPairs []scrapedPair `json:"Pools"`
   563  	}
   564  	var allPairs scrapedPairList
   565  	err = json.Unmarshal(byteData, &allPairs)
   566  	if err != nil {
   567  		return
   568  	}
   569  
   570  	// Extract addresses
   571  	for _, token := range allPairs.AllPairs {
   572  		pairAddresses = append(pairAddresses, common.HexToAddress(token.Address))
   573  	}
   574  
   575  	return
   576  }
   577  
   578  // feedPoolsToSubscriptions sends a list of TraderJoePairs to subscription channels.
   579  func (tjs *TraderJoeScraper) feedPoolsToSubscriptions() (pairs []TraderJoePair) {
   580  	for i := range MapOfPools {
   581  		up := MapOfPools[i]
   582  		pairs = append(pairs, up)
   583  		tjs.pairReceived <- &up
   584  	}
   585  	return
   586  }