feat(market): auto-populate exchange pairs from CoinGecko (adds OurBit); DRGX dashboard

- Fetch /coins/dragonx-2/tickers once per session (App::refreshExchanges, reusing the
  price-fetch worker path) and build the exchange registry at runtime into
  state.market.exchanges; parseCoinGeckoTickers groups tickers by venue. The Market tab
  sources from this live list and falls back to the compiled-in registry (now including
  OurBit + Nonkyc.io) when offline. Fixes the missing OurBit pair and auto-tracks future
  listings.
- Fix stale selection defaults (TradeOgre / DRGX-BTC -> Nonkyc.io / DRGX/USDT) that never
  matched the registry, and make the attribution generic ("Price data from CoinGecko").
- Reframe the single-asset portfolio card as a DRGX holdings dashboard: relabel to
  "MY DRGX" and show the 24h change on the holdings value (colored by direction).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-01 15:25:22 -05:00
parent 57f52b7a6c
commit 1266b6e68e
8 changed files with 164 additions and 11 deletions

View File

@@ -43,13 +43,19 @@ static float s_pair_drag_start_x = 0.0f;
static float s_pair_drag_start_scroll = 0.0f;
static bool s_market_state_loaded = false;
// The effective exchange registry: the live CoinGecko list when available, else the
// compiled-in fallback.
static const std::vector<data::ExchangeInfo>& EffectiveRegistry(const MarketInfo& market)
{
return market.exchanges.empty() ? data::getExchangeRegistry() : market.exchanges;
}
// Helper: load selected exchange/pair from settings
static void LoadMarketState(config::Settings* settings)
static void LoadMarketState(config::Settings* settings, const std::vector<data::ExchangeInfo>& registry)
{
if (s_market_state_loaded || !settings) return;
s_market_state_loaded = true;
const auto& registry = data::getExchangeRegistry();
std::string savedExchange = settings->getSelectedExchange();
std::string savedPair = settings->getSelectedPair();
@@ -104,11 +110,14 @@ void RenderMarketTab(App* app)
const auto& state = app->getWalletState();
const auto& market = state.market;
// Load persisted exchange/pair on first frame
LoadMarketState(app->settings());
// Kick a once-per-session live exchange-list fetch (self-guarded), then source the
// registry from it (falling back to the compiled-in list until it arrives).
app->refreshExchanges();
const auto& registry = EffectiveRegistry(market);
// Load persisted exchange/pair on first frame
LoadMarketState(app->settings(), registry);
// Exchange registry
const auto& registry = data::getExchangeRegistry();
if (s_exchange_idx >= (int)registry.size()) s_exchange_idx = 0;
const auto& currentExchange = registry[s_exchange_idx];
if (s_pair_idx >= (int)currentExchange.pairs.size()) s_pair_idx = 0;
@@ -787,6 +796,17 @@ void RenderMarketTab(App* app)
snprintf(buf, sizeof(buf), "$%.6f USD", portfolio_usd);
DrawTextShadow(dl, sub1, sub1->LegacySize, ImVec2(cx, cy), Success(), buf);
// 24h change on the holdings (the coin's 24h % applies to the value), shown
// beside the USD figure and colored by direction.
if (market.change_24h != 0.0) {
float usdW = sub1->CalcTextSizeA(sub1->LegacySize, FLT_MAX, 0, buf).x;
char chg[48];
snprintf(chg, sizeof(chg), "%+.2f%% %s", market.change_24h, TR("market_24h_change"));
ImU32 chgCol = market.change_24h >= 0 ? Success() : Error();
dl->AddText(capFont, capFont->LegacySize,
ImVec2(cx + usdW + Layout::spacingMd(), cy + 3), chgCol, chg);
}
double portfolio_btc = total_balance * market.price_btc;
snprintf(buf, sizeof(buf), "%.10f BTC", portfolio_btc);
float valW = sub1->CalcTextSizeA(sub1->LegacySize, FLT_MAX, 0, buf).x;