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:
@@ -4,11 +4,19 @@
|
||||
|
||||
#include "exchange_info.h"
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace dragonx {
|
||||
namespace data {
|
||||
|
||||
const std::vector<ExchangeInfo>& getExchangeRegistry()
|
||||
{
|
||||
// Offline seed / fallback. The live list from CoinGecko (parseCoinGeckoTickers)
|
||||
// supersedes this when available; keep the known venues here so the tab is never
|
||||
// empty without network.
|
||||
static const std::vector<ExchangeInfo> registry = {
|
||||
{
|
||||
"Nonkyc.io",
|
||||
@@ -17,9 +25,68 @@ const std::vector<ExchangeInfo>& getExchangeRegistry()
|
||||
{"DRGX", "USDT", "DRGX/USDT", "https://nonkyc.io/market/DRGX_USDT"},
|
||||
}
|
||||
},
|
||||
{
|
||||
"OurBit",
|
||||
"https://www.ourbit.com",
|
||||
{
|
||||
{"DRGX", "USDT", "DRGX/USDT", "https://www.ourbit.com/exchange/DRGX_USDT"},
|
||||
}
|
||||
},
|
||||
};
|
||||
return registry;
|
||||
}
|
||||
|
||||
namespace {
|
||||
// Extract the scheme://host origin from a URL (for ExchangeInfo.baseUrl).
|
||||
std::string originOf(const std::string& url)
|
||||
{
|
||||
const auto scheme = url.find("://");
|
||||
if (scheme == std::string::npos) return url;
|
||||
const auto host = url.find('/', scheme + 3);
|
||||
return (host == std::string::npos) ? url : url.substr(0, host);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
std::vector<ExchangeInfo> parseCoinGeckoTickers(const std::string& body)
|
||||
{
|
||||
std::vector<ExchangeInfo> exchanges;
|
||||
try {
|
||||
const nlohmann::json j = nlohmann::json::parse(body);
|
||||
if (!j.contains("tickers") || !j["tickers"].is_array()) return {};
|
||||
|
||||
std::unordered_map<std::string, size_t> byName; // exchange name -> index
|
||||
for (const auto& t : j["tickers"]) {
|
||||
if (!t.is_object()) continue;
|
||||
const std::string base = t.value("base", std::string{});
|
||||
const std::string target = t.value("target", std::string{});
|
||||
std::string market;
|
||||
if (t.contains("market") && t["market"].is_object())
|
||||
market = t["market"].value("name", std::string{});
|
||||
const std::string tradeUrl = t.value("trade_url", std::string{});
|
||||
if (base.empty() || target.empty() || market.empty()) continue;
|
||||
|
||||
auto it = byName.find(market);
|
||||
if (it == byName.end()) {
|
||||
byName[market] = exchanges.size();
|
||||
exchanges.push_back(ExchangeInfo{market, originOf(tradeUrl), {}});
|
||||
it = byName.find(market);
|
||||
}
|
||||
ExchangePair pair{base, target, base + "/" + target, tradeUrl};
|
||||
// Skip duplicate pairs on the same exchange.
|
||||
bool dup = false;
|
||||
for (const auto& p : exchanges[it->second].pairs)
|
||||
if (p.displayName == pair.displayName) { dup = true; break; }
|
||||
if (!dup) exchanges[it->second].pairs.push_back(std::move(pair));
|
||||
}
|
||||
} catch (...) {
|
||||
return {};
|
||||
}
|
||||
// Drop exchanges that ended up with no pairs (defensive).
|
||||
exchanges.erase(std::remove_if(exchanges.begin(), exchanges.end(),
|
||||
[](const ExchangeInfo& e) { return e.pairs.empty(); }),
|
||||
exchanges.end());
|
||||
return exchanges;
|
||||
}
|
||||
|
||||
} // namespace data
|
||||
} // namespace dragonx
|
||||
|
||||
Reference in New Issue
Block a user