github.com/InjectiveLabs/sdk-go@v1.53.0/client/chain/markets_assistant.go (about) 1 package chain 2 3 import ( 4 "context" 5 "fmt" 6 "path" 7 "runtime" 8 "strings" 9 "sync" 10 11 "github.com/InjectiveLabs/sdk-go/client/core" 12 "github.com/InjectiveLabs/sdk-go/client/exchange" 13 derivativeExchangePB "github.com/InjectiveLabs/sdk-go/exchange/derivative_exchange_rpc/pb" 14 spotExchangePB "github.com/InjectiveLabs/sdk-go/exchange/spot_exchange_rpc/pb" 15 "github.com/cosmos/cosmos-sdk/types/query" 16 banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" 17 "github.com/shopspring/decimal" 18 "gopkg.in/ini.v1" 19 ) 20 21 var legacyMarketAssistantLazyInitialization sync.Once 22 var legacyMarketAssistant MarketsAssistant 23 24 type TokenMetadata interface { 25 GetName() string 26 GetAddress() string 27 GetSymbol() string 28 GetLogo() string 29 GetDecimals() int32 30 GetUpdatedAt() int64 31 } 32 33 type MarketsAssistant struct { 34 tokensBySymbol map[string]core.Token 35 tokensByDenom map[string]core.Token 36 spotMarkets map[string]core.SpotMarket 37 derivativeMarkets map[string]core.DerivativeMarket 38 } 39 40 func newMarketsAssistant() MarketsAssistant { 41 return MarketsAssistant{ 42 tokensBySymbol: make(map[string]core.Token), 43 tokensByDenom: make(map[string]core.Token), 44 spotMarkets: make(map[string]core.SpotMarket), 45 derivativeMarkets: make(map[string]core.DerivativeMarket), 46 } 47 } 48 49 // Deprecated: use NewMarketsAssistantInitializedFromChain instead 50 func NewMarketsAssistant(networkName string) (MarketsAssistant, error) { 51 52 legacyMarketAssistantLazyInitialization.Do(func() { 53 assistant := newMarketsAssistant() 54 fileName := getFileAbsPath(fmt.Sprintf("../metadata/assets/%s.ini", networkName)) 55 metadataFile, err := ini.Load(fileName) 56 57 if err == nil { 58 for _, section := range metadataFile.Sections() { 59 sectionName := section.Name() 60 if strings.HasPrefix(sectionName, "0x") { 61 description := section.Key("description").Value() 62 63 decimals, _ := section.Key("quote").Int() 64 quoteToken := core.Token{ 65 Name: "", 66 Symbol: "", 67 Denom: "", 68 Address: "", 69 Decimals: int32(decimals), 70 Logo: "", 71 Updated: -1, 72 } 73 74 minPriceTickSize := decimal.RequireFromString(section.Key("min_price_tick_size").String()) 75 minQuantityTickSize := decimal.RequireFromString(section.Key("min_quantity_tick_size").String()) 76 minNotional := decimal.Zero 77 if section.HasKey("min_notional") { 78 minNotional = decimal.RequireFromString(section.Key("min_notional").String()) 79 } 80 81 if strings.Contains(description, "Spot") { 82 baseDecimals, _ := section.Key("quote").Int() 83 baseToken := core.Token{ 84 Name: "", 85 Symbol: "", 86 Denom: "", 87 Address: "", 88 Decimals: int32(baseDecimals), 89 Logo: "", 90 Updated: -1, 91 } 92 93 market := core.SpotMarket{ 94 Id: sectionName, 95 Status: "", 96 Ticker: description, 97 BaseToken: baseToken, 98 QuoteToken: quoteToken, 99 MakerFeeRate: decimal.NewFromInt32(0), 100 TakerFeeRate: decimal.NewFromInt32(0), 101 ServiceProviderFee: decimal.NewFromInt32(0), 102 MinPriceTickSize: minPriceTickSize, 103 MinQuantityTickSize: minQuantityTickSize, 104 MinNotional: minNotional, 105 } 106 107 assistant.spotMarkets[market.Id] = market 108 } else { 109 market := core.DerivativeMarket{ 110 Id: sectionName, 111 Status: "", 112 Ticker: description, 113 OracleBase: "", 114 OracleQuote: "", 115 OracleType: "", 116 OracleScaleFactor: 1, 117 InitialMarginRatio: decimal.NewFromInt32(0), 118 MaintenanceMarginRatio: decimal.NewFromInt32(0), 119 QuoteToken: quoteToken, 120 MakerFeeRate: decimal.NewFromInt32(0), 121 TakerFeeRate: decimal.NewFromInt32(0), 122 ServiceProviderFee: decimal.NewFromInt32(0), 123 MinPriceTickSize: minPriceTickSize, 124 MinQuantityTickSize: minQuantityTickSize, 125 MinNotional: minNotional, 126 } 127 128 assistant.derivativeMarkets[market.Id] = market 129 } 130 } else if sectionName != "DEFAULT" { 131 tokenDecimals, _ := section.Key("decimals").Int() 132 newToken := core.Token{ 133 Name: sectionName, 134 Symbol: sectionName, 135 Denom: section.Key("peggy_denom").String(), 136 Address: "", 137 Decimals: int32(tokenDecimals), 138 Logo: "", 139 Updated: -1, 140 } 141 142 assistant.tokensByDenom[newToken.Denom] = newToken 143 assistant.tokensBySymbol[newToken.Symbol] = newToken 144 } 145 } 146 } 147 148 legacyMarketAssistant = assistant 149 }) 150 151 return legacyMarketAssistant, nil 152 } 153 154 func NewMarketsAssistantInitializedFromChain(ctx context.Context, exchangeClient exchange.ExchangeClient) (MarketsAssistant, error) { 155 assistant := newMarketsAssistant() 156 157 officialTokens, err := core.LoadTokens(exchangeClient.GetNetwork().OfficialTokensListURL) 158 if err == nil { 159 for i := range officialTokens { 160 tokenMetadata := officialTokens[i] 161 if tokenMetadata.Denom != "" { 162 // add tokens to the assistant ensuring all of them get assigned a unique symbol 163 tokenRepresentation(tokenMetadata.GetSymbol(), tokenMetadata, tokenMetadata.Denom, &assistant) 164 } 165 } 166 } 167 168 spotMarketsRequest := spotExchangePB.MarketsRequest{ 169 MarketStatus: "active", 170 } 171 spotMarkets, err := exchangeClient.GetSpotMarkets(ctx, &spotMarketsRequest) 172 173 if err != nil { 174 return assistant, err 175 } 176 177 for _, marketInfo := range spotMarkets.GetMarkets() { 178 if marketInfo.GetBaseTokenMeta().GetSymbol() == "" || marketInfo.GetQuoteTokenMeta().GetSymbol() == "" { 179 continue 180 } 181 182 var baseTokenSymbol, quoteTokenSymbol string 183 if strings.Contains(marketInfo.GetTicker(), "/") { 184 baseAndQuote := strings.Split(marketInfo.GetTicker(), "/") 185 baseTokenSymbol, quoteTokenSymbol = baseAndQuote[0], baseAndQuote[1] 186 } else { 187 baseTokenSymbol = marketInfo.GetBaseTokenMeta().GetSymbol() 188 quoteTokenSymbol = marketInfo.GetQuoteTokenMeta().GetSymbol() 189 } 190 191 baseToken := tokenRepresentation(baseTokenSymbol, marketInfo.GetBaseTokenMeta(), marketInfo.GetBaseDenom(), &assistant) 192 quoteToken := tokenRepresentation(quoteTokenSymbol, marketInfo.GetQuoteTokenMeta(), marketInfo.GetQuoteDenom(), &assistant) 193 194 makerFeeRate := decimal.RequireFromString(marketInfo.GetMakerFeeRate()) 195 takerFeeRate := decimal.RequireFromString(marketInfo.GetTakerFeeRate()) 196 serviceProviderFee := decimal.RequireFromString(marketInfo.GetServiceProviderFee()) 197 minPriceTickSize := decimal.RequireFromString(marketInfo.GetMinPriceTickSize()) 198 minQuantityTickSize := decimal.RequireFromString(marketInfo.GetMinQuantityTickSize()) 199 minNotional := decimal.RequireFromString(marketInfo.GetMinNotional()) 200 201 market := core.SpotMarket{ 202 Id: marketInfo.GetMarketId(), 203 Status: marketInfo.GetMarketStatus(), 204 Ticker: marketInfo.GetTicker(), 205 BaseToken: baseToken, 206 QuoteToken: quoteToken, 207 MakerFeeRate: makerFeeRate, 208 TakerFeeRate: takerFeeRate, 209 ServiceProviderFee: serviceProviderFee, 210 MinPriceTickSize: minPriceTickSize, 211 MinQuantityTickSize: minQuantityTickSize, 212 MinNotional: minNotional, 213 } 214 215 assistant.spotMarkets[market.Id] = market 216 } 217 218 derivativeMarketsRequest := derivativeExchangePB.MarketsRequest{ 219 MarketStatus: "active", 220 } 221 derivativeMarkets, err := exchangeClient.GetDerivativeMarkets(ctx, &derivativeMarketsRequest) 222 223 if err != nil { 224 return assistant, err 225 } 226 227 for _, marketInfo := range derivativeMarkets.GetMarkets() { 228 if marketInfo.GetQuoteTokenMeta().GetSymbol() == "" { 229 continue 230 } 231 232 quoteTokenSymbol := marketInfo.GetQuoteTokenMeta().GetSymbol() 233 234 quoteToken := tokenRepresentation(quoteTokenSymbol, marketInfo.GetQuoteTokenMeta(), marketInfo.GetQuoteDenom(), &assistant) 235 236 initialMarginRatio := decimal.RequireFromString(marketInfo.GetInitialMarginRatio()) 237 maintenanceMarginRatio := decimal.RequireFromString(marketInfo.GetMaintenanceMarginRatio()) 238 makerFeeRate := decimal.RequireFromString(marketInfo.GetMakerFeeRate()) 239 takerFeeRate := decimal.RequireFromString(marketInfo.GetTakerFeeRate()) 240 serviceProviderFee := decimal.RequireFromString(marketInfo.GetServiceProviderFee()) 241 minPriceTickSize := decimal.RequireFromString(marketInfo.GetMinPriceTickSize()) 242 minQuantityTickSize := decimal.RequireFromString(marketInfo.GetMinQuantityTickSize()) 243 minNotional := decimal.RequireFromString(marketInfo.GetMinNotional()) 244 245 market := core.DerivativeMarket{ 246 Id: marketInfo.GetMarketId(), 247 Status: marketInfo.GetMarketStatus(), 248 Ticker: marketInfo.GetTicker(), 249 OracleBase: marketInfo.GetOracleBase(), 250 OracleQuote: marketInfo.GetOracleQuote(), 251 OracleType: marketInfo.GetOracleType(), 252 OracleScaleFactor: marketInfo.GetOracleScaleFactor(), 253 InitialMarginRatio: initialMarginRatio, 254 MaintenanceMarginRatio: maintenanceMarginRatio, 255 QuoteToken: quoteToken, 256 MakerFeeRate: makerFeeRate, 257 TakerFeeRate: takerFeeRate, 258 ServiceProviderFee: serviceProviderFee, 259 MinPriceTickSize: minPriceTickSize, 260 MinQuantityTickSize: minQuantityTickSize, 261 MinNotional: minNotional, 262 } 263 264 assistant.derivativeMarkets[market.Id] = market 265 } 266 267 return assistant, nil 268 } 269 270 func NewMarketsAssistantWithAllTokens(ctx context.Context, exchangeClient exchange.ExchangeClient, chainClient ChainClient) (MarketsAssistant, error) { 271 assistant, err := NewMarketsAssistantInitializedFromChain(ctx, exchangeClient) 272 if err != nil { 273 return assistant, err 274 } 275 276 assistant.initializeTokensFromChainDenoms(ctx, chainClient) 277 278 return assistant, nil 279 } 280 281 func uniqueSymbol(symbol, denom, tokenMetaSymbol, tokenMetaName string, assistant MarketsAssistant) string { 282 uniqueSymbol := denom 283 _, isSymbolPresent := assistant.tokensBySymbol[symbol] 284 if isSymbolPresent { 285 _, isSymbolPresent = assistant.tokensBySymbol[tokenMetaSymbol] 286 if isSymbolPresent { 287 _, isSymbolPresent = assistant.tokensBySymbol[tokenMetaName] 288 if !isSymbolPresent { 289 uniqueSymbol = tokenMetaName 290 } 291 } else { 292 uniqueSymbol = tokenMetaSymbol 293 } 294 } else { 295 uniqueSymbol = symbol 296 } 297 298 return uniqueSymbol 299 } 300 301 func tokenRepresentation(symbol string, tokenMeta TokenMetadata, denom string, assistant *MarketsAssistant) core.Token { 302 _, isPresent := assistant.tokensByDenom[denom] 303 304 if !isPresent { 305 uniqueSymbol := uniqueSymbol(symbol, denom, tokenMeta.GetSymbol(), tokenMeta.GetName(), *assistant) 306 307 newToken := core.Token{ 308 Name: tokenMeta.GetName(), 309 Symbol: symbol, 310 Denom: denom, 311 Address: tokenMeta.GetAddress(), 312 Decimals: tokenMeta.GetDecimals(), 313 Logo: tokenMeta.GetLogo(), 314 Updated: tokenMeta.GetUpdatedAt(), 315 } 316 317 assistant.tokensByDenom[denom] = newToken 318 assistant.tokensBySymbol[uniqueSymbol] = newToken 319 } 320 321 return assistant.tokensByDenom[denom] 322 } 323 324 func getFileAbsPath(relativePath string) string { 325 _, filename, _, _ := runtime.Caller(0) 326 return path.Join(path.Dir(filename), relativePath) 327 } 328 329 func (assistant MarketsAssistant) AllTokens() map[string]core.Token { 330 return assistant.tokensBySymbol 331 } 332 333 func (assistant MarketsAssistant) AllSpotMarkets() map[string]core.SpotMarket { 334 return assistant.spotMarkets 335 } 336 337 func (assistant MarketsAssistant) AllDerivativeMarkets() map[string]core.DerivativeMarket { 338 return assistant.derivativeMarkets 339 } 340 341 func (assistant MarketsAssistant) initializeTokensFromChainDenoms(ctx context.Context, chainClient ChainClient) { 342 var denomsMetadata []banktypes.Metadata 343 var nextKey []byte 344 345 for readNextPage := true; readNextPage; readNextPage = len(nextKey) > 0 { 346 pagination := query.PageRequest{Key: nextKey} 347 result, err := chainClient.GetDenomsMetadata(ctx, &pagination) 348 349 if err != nil { 350 panic(err) 351 } 352 353 denomsMetadata = append(denomsMetadata, result.GetMetadatas()...) 354 } 355 356 for i := range denomsMetadata { 357 denomMetadata := denomsMetadata[i] 358 symbol := denomMetadata.GetSymbol() 359 denom := denomMetadata.GetBase() 360 361 _, isDenomPresent := assistant.tokensByDenom[denom] 362 363 if symbol != "" && denom != "" && !isDenomPresent { 364 name := denomMetadata.GetName() 365 if name == "" { 366 name = symbol 367 } 368 369 var decimals int32 = -1 370 for _, denomUnit := range denomMetadata.GetDenomUnits() { 371 exponent := int32(denomUnit.GetExponent()) 372 if exponent > decimals { 373 decimals = exponent 374 } 375 } 376 377 uniqueSymbol := uniqueSymbol(symbol, denom, symbol, name, assistant) 378 379 newToken := core.Token{ 380 Name: name, 381 Symbol: symbol, 382 Denom: denom, 383 Address: "", 384 Decimals: decimals, 385 Logo: denomMetadata.GetURI(), 386 Updated: -1, 387 } 388 389 assistant.tokensByDenom[denom] = newToken 390 assistant.tokensBySymbol[uniqueSymbol] = newToken 391 } 392 } 393 }