mirror of
				https://github.com/Instadapp/trustwallet-assets.git
				synced 2024-07-29 22:37:31 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			302 lines
		
	
	
		
			7.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			302 lines
		
	
	
		
			7.6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package processor
 | |
| 
 | |
| import (
 | |
| 	"fmt"
 | |
| 	"reflect"
 | |
| 	"sort"
 | |
| 	"strconv"
 | |
| 	"time"
 | |
| 
 | |
| 	log "github.com/sirupsen/logrus"
 | |
| 
 | |
| 	fileLib "github.com/trustwallet/assets-go-libs/file"
 | |
| 	"github.com/trustwallet/assets-go-libs/image"
 | |
| 	"github.com/trustwallet/assets-go-libs/path"
 | |
| 	"github.com/trustwallet/assets-go-libs/validation/info"
 | |
| 	"github.com/trustwallet/assets-go-libs/validation/tokenlist"
 | |
| 	"github.com/trustwallet/assets/internal/config"
 | |
| 	"github.com/trustwallet/go-libs/blockchain/binance"
 | |
| 	"github.com/trustwallet/go-libs/blockchain/binance/explorer"
 | |
| 	assetlib "github.com/trustwallet/go-primitives/asset"
 | |
| 	"github.com/trustwallet/go-primitives/coin"
 | |
| 	"github.com/trustwallet/go-primitives/numbers"
 | |
| 	"github.com/trustwallet/go-primitives/types"
 | |
| )
 | |
| 
 | |
| const (
 | |
| 	assetsPage       = 1
 | |
| 	assetsRows       = 1000
 | |
| 	marketPairsLimit = 1000
 | |
| 	tokensListLimit  = 10000
 | |
| 
 | |
| 	activeStatus = "active"
 | |
| )
 | |
| 
 | |
| func (s *Service) UpdateBinanceTokens() error {
 | |
| 	explorerClient := explorer.InitClient(config.Default.ClientURLs.Binance.Explorer, nil)
 | |
| 
 | |
| 	bep2AssetList, err := explorerClient.FetchBep2Assets(assetsPage, assetsRows)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	dexClient := binance.InitClient(config.Default.ClientURLs.Binance.Dex, "", nil)
 | |
| 
 | |
| 	marketPairs, err := dexClient.FetchMarketPairs(marketPairsLimit)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	tokenList, err := dexClient.FetchTokens(tokensListLimit)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	chain, err := types.GetChainFromAssetType(string(types.BEP2))
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	err = fetchMissingAssets(chain, bep2AssetList.AssetInfoList)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	tokens, err := generateTokenList(marketPairs, tokenList)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	sortTokens(tokens)
 | |
| 
 | |
| 	return createTokenListJSON(chain, tokens)
 | |
| }
 | |
| 
 | |
| func fetchMissingAssets(chain coin.Coin, assets []explorer.Bep2Asset) error {
 | |
| 	for _, a := range assets {
 | |
| 		if a.AssetImg == "" || a.Decimals == 0 {
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		assetLogoPath := path.GetAssetLogoPath(chain.Handle, a.Asset)
 | |
| 		if fileLib.Exists(assetLogoPath) {
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		if err := createLogo(assetLogoPath, a); err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 
 | |
| 		if err := createInfoJSON(chain, a); err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func createLogo(assetLogoPath string, a explorer.Bep2Asset) error {
 | |
| 	err := fileLib.CreateDirPath(assetLogoPath)
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	return image.CreatePNGFromURL(a.AssetImg, assetLogoPath)
 | |
| }
 | |
| 
 | |
| func createInfoJSON(chain coin.Coin, a explorer.Bep2Asset) error {
 | |
| 	explorerURL, err := coin.GetCoinExploreURL(chain, a.Asset, "")
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	assetType := string(types.BEP2)
 | |
| 	website := ""
 | |
| 	description := "-"
 | |
| 	status := activeStatus
 | |
| 
 | |
| 	assetInfo := info.AssetModel{
 | |
| 		Name:        &a.Name,
 | |
| 		Type:        &assetType,
 | |
| 		Symbol:      &a.MappedAsset,
 | |
| 		Decimals:    &a.Decimals,
 | |
| 		Website:     &website,
 | |
| 		Description: &description,
 | |
| 		Explorer:    &explorerURL,
 | |
| 		Status:      &status,
 | |
| 		ID:          &a.Asset,
 | |
| 	}
 | |
| 
 | |
| 	assetInfoPath := path.GetAssetInfoPath(chain.Handle, a.Asset)
 | |
| 
 | |
| 	return fileLib.CreateJSONFile(assetInfoPath, &assetInfo)
 | |
| }
 | |
| 
 | |
| func createTokenListJSON(chain coin.Coin, tokens []tokenlist.Token) error {
 | |
| 	tokenListPath := path.GetTokenListPath(chain.Handle, path.TokenlistDefault)
 | |
| 
 | |
| 	var oldTokenList tokenlist.Model
 | |
| 	err := fileLib.ReadJSONFile(tokenListPath, &oldTokenList)
 | |
| 	if err != nil {
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	if reflect.DeepEqual(oldTokenList.Tokens, tokens) {
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	if len(tokens) == 0 {
 | |
| 		return nil
 | |
| 	}
 | |
| 
 | |
| 	log.Debugf("Tokenlist: list with %d tokens and %d pairs written to %s.",
 | |
| 		len(tokens), countTotalPairs(tokens), tokenListPath)
 | |
| 
 | |
| 	return fileLib.CreateJSONFile(tokenListPath, &tokenlist.Model{
 | |
| 		Name:      fmt.Sprintf("Trust Wallet: %s", coin.Coins[chain.ID].Name),
 | |
| 		LogoURI:   config.Default.URLs.Logo,
 | |
| 		Timestamp: time.Now().Format(config.Default.TimeFormat),
 | |
| 		Tokens:    tokens,
 | |
| 		Version:   tokenlist.Version{Major: oldTokenList.Version.Major + 1},
 | |
| 	})
 | |
| }
 | |
| 
 | |
| func countTotalPairs(tokens []tokenlist.Token) int {
 | |
| 	var counter int
 | |
| 	for _, token := range tokens {
 | |
| 		counter += len(token.Pairs)
 | |
| 	}
 | |
| 
 | |
| 	return counter
 | |
| }
 | |
| 
 | |
| func sortTokens(tokens []tokenlist.Token) {
 | |
| 	sort.Slice(tokens, func(i, j int) bool {
 | |
| 		if len(tokens[i].Pairs) != len(tokens[j].Pairs) {
 | |
| 			return len(tokens[i].Pairs) > len(tokens[j].Pairs)
 | |
| 		}
 | |
| 
 | |
| 		return tokens[i].Address < tokens[j].Address
 | |
| 	})
 | |
| 
 | |
| 	for _, token := range tokens {
 | |
| 		sort.Slice(token.Pairs, func(i, j int) bool {
 | |
| 			return token.Pairs[i].Base < token.Pairs[j].Base
 | |
| 		})
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func generateTokenList(marketPairs []binance.MarketPair, tokenList binance.Tokens) ([]tokenlist.Token, error) {
 | |
| 	if len(marketPairs) < 5 {
 | |
| 		return nil, fmt.Errorf("no markets info is returned from Binance DEX: %d", len(marketPairs))
 | |
| 	}
 | |
| 
 | |
| 	if len(tokenList) < 5 {
 | |
| 		return nil, fmt.Errorf("no tokens info is returned from Binance DEX: %d", len(tokenList))
 | |
| 	}
 | |
| 
 | |
| 	pairsMap := make(map[string][]tokenlist.Pair)
 | |
| 	pairsList := make(map[string]struct{})
 | |
| 	tokensMap := make(map[string]binance.Token)
 | |
| 
 | |
| 	for _, token := range tokenList {
 | |
| 		tokensMap[token.Symbol] = token
 | |
| 	}
 | |
| 
 | |
| 	for _, marketPair := range marketPairs {
 | |
| 		if !isTokenExistOrActive(marketPair.BaseAssetSymbol) || !isTokenExistOrActive(marketPair.QuoteAssetSymbol) {
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		tokenSymbol := marketPair.QuoteAssetSymbol
 | |
| 
 | |
| 		if val, exists := pairsMap[tokenSymbol]; exists {
 | |
| 			val = append(val, getPair(marketPair))
 | |
| 			pairsMap[tokenSymbol] = val
 | |
| 		} else {
 | |
| 			pairsMap[tokenSymbol] = []tokenlist.Pair{getPair(marketPair)}
 | |
| 		}
 | |
| 
 | |
| 		pairsList[marketPair.BaseAssetSymbol] = struct{}{}
 | |
| 		pairsList[marketPair.QuoteAssetSymbol] = struct{}{}
 | |
| 	}
 | |
| 
 | |
| 	tokenItems := make([]tokenlist.Token, 0, len(pairsList))
 | |
| 
 | |
| 	for pair := range pairsList {
 | |
| 		token := tokensMap[pair]
 | |
| 
 | |
| 		var pairs []tokenlist.Pair
 | |
| 		pairs, exists := pairsMap[token.Symbol]
 | |
| 		if !exists {
 | |
| 			pairs = make([]tokenlist.Pair, 0)
 | |
| 		}
 | |
| 
 | |
| 		tokenItems = append(tokenItems, tokenlist.Token{
 | |
| 			Asset:    getAssetIDSymbol(token.Symbol, coin.Coins[coin.BINANCE].Symbol, coin.BINANCE),
 | |
| 			Type:     getTokenType(token.Symbol, coin.Coins[coin.BINANCE].Symbol, types.BEP2),
 | |
| 			Address:  token.Symbol,
 | |
| 			Name:     token.Name,
 | |
| 			Symbol:   token.OriginalSymbol,
 | |
| 			Decimals: coin.Coins[coin.BINANCE].Decimals,
 | |
| 			LogoURI:  getLogoURI(token.Symbol, coin.Coins[coin.BINANCE].Handle, coin.Coins[coin.BINANCE].Symbol),
 | |
| 			Pairs:    pairs,
 | |
| 		})
 | |
| 	}
 | |
| 
 | |
| 	return tokenItems, nil
 | |
| }
 | |
| 
 | |
| func isTokenExistOrActive(symbol string) bool {
 | |
| 	if symbol == coin.Coins[coin.BINANCE].Symbol {
 | |
| 		return true
 | |
| 	}
 | |
| 
 | |
| 	assetPath := path.GetAssetInfoPath(coin.Coins[coin.BINANCE].Handle, symbol)
 | |
| 
 | |
| 	var infoAsset info.AssetModel
 | |
| 	if err := fileLib.ReadJSONFile(assetPath, infoAsset); err != nil {
 | |
| 		log.Debug(err)
 | |
| 		return false
 | |
| 	}
 | |
| 
 | |
| 	if infoAsset.GetStatus() != activeStatus {
 | |
| 		log.Debugf("asset status [%s] is not active", symbol)
 | |
| 		return false
 | |
| 	}
 | |
| 
 | |
| 	return true
 | |
| }
 | |
| 
 | |
| func getPair(marketPair binance.MarketPair) tokenlist.Pair {
 | |
| 	return tokenlist.Pair{
 | |
| 		Base:     getAssetIDSymbol(marketPair.BaseAssetSymbol, coin.Coins[coin.BINANCE].Symbol, coin.BINANCE),
 | |
| 		LotSize:  strconv.FormatInt(numbers.ToSatoshi(marketPair.LotSize), 10),
 | |
| 		TickSize: strconv.FormatInt(numbers.ToSatoshi(marketPair.TickSize), 10),
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func getAssetIDSymbol(tokenID string, nativeCoinID string, coinType uint) string {
 | |
| 	if tokenID == nativeCoinID {
 | |
| 		return assetlib.BuildID(coinType, "")
 | |
| 	}
 | |
| 
 | |
| 	return assetlib.BuildID(coinType, tokenID)
 | |
| }
 | |
| 
 | |
| func getTokenType(symbol string, nativeCoinSymbol string, tokenType types.TokenType) types.TokenType {
 | |
| 	if symbol == nativeCoinSymbol {
 | |
| 		return types.Coin
 | |
| 	}
 | |
| 
 | |
| 	return tokenType
 | |
| }
 | |
| 
 | |
| func getLogoURI(id, githubChainFolder, nativeCoinSymbol string) string {
 | |
| 	if id == nativeCoinSymbol {
 | |
| 		return path.GetChainLogoURL(config.Default.URLs.AssetsApp, githubChainFolder)
 | |
| 	}
 | |
| 
 | |
| 	return path.GetAssetLogoURL(config.Default.URLs.AssetsApp, githubChainFolder, id)
 | |
| }
 | 
