The chart fetch and the Market tab both resolved the saved exchange and pair names into the registry, with their own near-identical scans and slightly different fallbacks. Keeping two copies risked the chart and the chips disagreeing about which pair was selected. - A single resolver maps the saved names to a registry index, matching the exchange name (any exchange when none is saved) then the pair display name, and falling back to the registry's first pair, the UI default. The chart fetch and the Market tab both call it. - The Market tab now derives its selection indices from the saved names against the current registry every frame, so the indices stay correct when the live exchange list replaces the fallback; a steady-state fast path skips the scan once the indices already name the saved selection. Tests: the resolver returns the right index for a matched exchange/pair, an empty-exchange match, and the first-pair fallback on misses. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
72 lines
2.7 KiB
C++
72 lines
2.7 KiB
C++
// DragonX Wallet - ImGui Edition
|
|
// Copyright 2024-2026 The Hush Developers
|
|
// Released under the GPLv3
|
|
|
|
#pragma once
|
|
|
|
#include <cstddef>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
namespace dragonx {
|
|
namespace data {
|
|
|
|
/**
|
|
* @brief A single trading pair on an exchange (e.g. DRGX/BTC)
|
|
*/
|
|
struct ExchangePair {
|
|
std::string base; ///< e.g. "DRGX"
|
|
std::string quote; ///< e.g. "BTC"
|
|
std::string displayName; ///< e.g. "DRGX/BTC"
|
|
std::string tradeUrl; ///< Link to the exchange pair page
|
|
std::string identifier; ///< CoinGecko exchange id (e.g. "ourbit", "nonkyc_io") — keys the candle adapter
|
|
double lastUsd = 0.0; ///< This venue's current price in USD (CoinGecko converted_last.usd); 0 if unknown
|
|
double volumeUsd = 0.0; ///< This venue's 24h volume in USD (CoinGecko converted_volume.usd); 0 if unknown
|
|
};
|
|
|
|
/**
|
|
* @brief Metadata for a supported exchange
|
|
*/
|
|
struct ExchangeInfo {
|
|
std::string name; ///< e.g. "TradeOgre"
|
|
std::string baseUrl; ///< e.g. "https://tradeogre.com"
|
|
std::vector<ExchangePair> pairs;
|
|
};
|
|
|
|
/**
|
|
* @brief Returns the static registry of supported exchanges + pairs.
|
|
* Used as the offline fallback / seed when the live CoinGecko list is unavailable.
|
|
*/
|
|
const std::vector<ExchangeInfo>& getExchangeRegistry();
|
|
|
|
/**
|
|
* @brief Index of the selected exchange/pair in `reg`, resolved by saved NAME.
|
|
* Matches the exchange name (any exchange when selEx is empty) THEN the pair display name, like the chart
|
|
* fetch always has. Not found => (0,0), found=false (the registry's first pair - the app's default).
|
|
* Single source of truth for the Market tab chips and App::refreshExchangeChart so they cannot diverge.
|
|
*/
|
|
struct ExchangeSelection { int exchangeIdx = 0; int pairIdx = 0; bool found = false; };
|
|
inline ExchangeSelection resolveExchangeSelection(const std::vector<ExchangeInfo>& reg,
|
|
const std::string& selEx, const std::string& selPair)
|
|
{
|
|
for (std::size_t ei = 0; ei < reg.size(); ++ei) {
|
|
if (!selEx.empty() && reg[ei].name != selEx) continue;
|
|
for (std::size_t pi = 0; pi < reg[ei].pairs.size(); ++pi)
|
|
if (reg[ei].pairs[pi].displayName == selPair)
|
|
return {static_cast<int>(ei), static_cast<int>(pi), true};
|
|
}
|
|
return {};
|
|
}
|
|
|
|
/**
|
|
* @brief Parse a CoinGecko /coins/{id}/tickers JSON body into the exchange registry.
|
|
*
|
|
* Groups tickers by exchange (market.name), preserving first-seen order, into
|
|
* ExchangeInfo entries (each pair carries base/target/displayName/trade_url). Returns
|
|
* an empty vector on parse failure or when no usable tickers are present. Pure (no I/O).
|
|
*/
|
|
std::vector<ExchangeInfo> parseCoinGeckoTickers(const std::string& json);
|
|
|
|
} // namespace data
|
|
} // namespace dragonx
|