package market import ( "encoding/json" "fmt" "net/http" "strconv" "time" "github.com/rs/zerolog/log" ) // AlphaTokenInfo represents the response from Binance Alpha token API type AlphaTokenInfo struct { TokenID string `json:"tokenId"` ChainID string `json:"chainId"` ChainIconURL string `json:"chainIconUrl"` ChainName string `json:"chainName"` ContractAddress string `json:"contractAddress"` Name string `json:"name"` Symbol string `json:"symbol"` IconURL string `json:"iconUrl"` Price string `json:"price"` PercentChange24h string `json:"percentChange24h"` Volume24h string `json:"volume24h"` MarketCap string `json:"marketCap"` FDV string `json:"fdv"` Liquidity string `json:"liquidity"` TotalSupply string `json:"totalSupply"` CirculatingSupply string `json:"circulatingSupply"` Holders string `json:"holders"` Decimals int `json:"decimals"` ListingCex bool `json:"listingCex"` HotTag bool `json:"hotTag"` CexCoinName string `json:"cexCoinName"` CanTransfer bool `json:"canTransfer"` Denomination int `json:"denomination"` Offline bool `json:"offline"` TradeDecimal int `json:"tradeDecimal"` AlphaID string `json:"alphaId"` Offsell bool `json:"offsell"` PriceHigh24h string `json:"priceHigh24h"` PriceLow24h string `json:"priceLow24h"` Count24h string `json:"count24h"` OnlineTge bool `json:"onlineTge"` OnlineAirdrop bool `json:"onlineAirdrop"` Score int `json:"score"` CexOffDisplay bool `json:"cexOffDisplay"` StockState bool `json:"stockState"` ListingTime int64 `json:"listingTime"` MulPoint int `json:"mulPoint"` BnExclusiveState bool `json:"bnExclusiveState"` } // AlphaTokenResponse represents the API response structure type AlphaTokenResponse struct { Code string `json:"code"` Message *string `json:"message"` MessageDetail *string `json:"messageDetail"` Data []AlphaTokenInfo `json:"data"` } // GetPrice returns the price as float64 func (a *AlphaTokenInfo) GetPrice() float64 { price, err := strconv.ParseFloat(a.Price, 64) if err != nil { log.Error().Err(err).Str("symbol", a.Symbol).Msg("Failed to parse Alpha token price") return 0 } return price } // GetPercentChange24h returns the 24h percentage change as float64 func (a *AlphaTokenInfo) GetPercentChange24h() float64 { change, err := strconv.ParseFloat(a.PercentChange24h, 64) if err != nil { log.Error().Err(err).Str("symbol", a.Symbol).Msg("Failed to parse Alpha token 24h change") return 0 } return change } // fetchAlphaTokens fetches Alpha tokens from Binance API func (ms *MarketData) fetchAlphaTokens() ([]AlphaTokenInfo, error) { const alphaTokenURL = "https://www.binance.com/bapi/defi/v1/public/wallet-direct/buw/wallet/cex/alpha/all/token/list" client := &http.Client{ Timeout: 10 * time.Second, } resp, err := client.Get(alphaTokenURL) if err != nil { return nil, fmt.Errorf("failed to fetch Alpha tokens: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("Alpha token API returned status: %d", resp.StatusCode) } var tokenResponse AlphaTokenResponse if err := json.NewDecoder(resp.Body).Decode(&tokenResponse); err != nil { return nil, fmt.Errorf("failed to decode Alpha token response: %w", err) } if tokenResponse.Code != "000000" { return nil, fmt.Errorf("Alpha token API returned error code: %s", tokenResponse.Code) } return tokenResponse.Data, nil } // refreshAlphaTokenCache refreshes the Alpha token cache func (ms *MarketData) refreshAlphaTokenCache() { ms.alphaCacheMutex.Lock() defer ms.alphaCacheMutex.Unlock() log.Info().Msg("Refreshing Alpha token cache") tokens, err := ms.fetchAlphaTokens() if err != nil { log.Error().Err(err).Msg("Failed to refresh Alpha token cache") return } // Clear existing cache ms.alphaTokens = make(map[string]AlphaTokenInfo) // Populate cache with new data for _, token := range tokens { symbol := token.Symbol // Already uppercase from API ms.alphaTokens[symbol] = token } // Update spot prices for Alpha tokens (separate lock to avoid holding both mutexes) ms.mu.Lock() for _, token := range tokens { symbol := token.Symbol if price := token.GetPrice(); price > 0 { ms.spotPrice[symbol] = price } } ms.mu.Unlock() ms.lastAlphaCacheUpdate = time.Now() log.Info().Int("count", len(tokens)).Msg("Alpha token cache refreshed successfully") } // GetAlphaToken returns Alpha token info by symbol func (ms *MarketData) GetAlphaToken(symbol string) (AlphaTokenInfo, bool) { ms.alphaCacheMutex.RLock() defer ms.alphaCacheMutex.RUnlock() token, exists := ms.alphaTokens[symbol] return token, exists } // IsAlphaToken checks if a symbol is an Alpha token func (ms *MarketData) IsAlphaToken(symbol string) bool { _, exists := ms.GetAlphaToken(symbol) return exists } // shouldRefreshAlphaCache checks if the Alpha cache should be refreshed func (ms *MarketData) shouldRefreshAlphaCache() bool { // Refresh if cache is empty (first time) or if it's older than 30 minutes if len(ms.alphaTokens) == 0 { return true } return time.Since(ms.lastAlphaCacheUpdate) > 30*time.Minute } // ensureAlphaCacheLoaded ensures Alpha cache is loaded, refreshes if needed func (ms *MarketData) ensureAlphaCacheLoaded() { if ms.shouldRefreshAlphaCache() { ms.refreshAlphaTokenCache() } }