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

@@ -1631,6 +1631,51 @@ void App::refreshPrice()
void App::refreshMarketData()
{
refreshPrice();
refreshExchanges();
}
void App::refreshExchanges()
{
if (!settings_ || !settings_->getFetchPrices()) return;
if (!worker_) return;
if (exchanges_fetch_started_) return; // once per session (venues are near-static)
exchanges_fetch_started_ = true;
worker_->post([this]() -> rpc::RPCWorker::MainCb {
std::vector<data::ExchangeInfo> exchanges;
try {
CURL* curl = curl_easy_init();
if (curl) {
std::string body;
const char* url = "https://api.coingecko.com/api/v3/coins/dragonx-2/tickers";
auto write_callback = [](void* contents, size_t size, size_t nmemb, std::string* userp) -> size_t {
size_t total = size * nmemb;
userp->append((char*)contents, total);
return total;
};
curl_easy_setopt(curl, CURLOPT_URL, url);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, +write_callback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &body);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 12L);
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 5L);
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
curl_easy_setopt(curl, CURLOPT_USERAGENT, "DragonX-Wallet/1.0");
CURLcode res = curl_easy_perform(curl);
long http_code = 0;
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
curl_easy_cleanup(curl);
if (res == CURLE_OK && http_code >= 200 && http_code < 300)
exchanges = data::parseCoinGeckoTickers(body);
}
} catch (...) {}
return [this, exchanges = std::move(exchanges)]() mutable {
if (!exchanges.empty())
state_.market.exchanges = std::move(exchanges);
else
exchanges_fetch_started_ = false; // allow a retry if it failed/empty
};
});
}
// ============================================================================