Extra refactor + Various bug/fixes. (#293)

* Refactor + Various Fixes.

* WS / LINT

* Show also failed status.

* Fix sorting market +/-

* Simplified swaps in progress

* Black

* Update basicswap/static/js/modules/coin-manager.js

Co-authored-by: nahuhh <50635951+nahuhh@users.noreply.github.com>

* Update basicswap/static/js/modules/coin-manager.js

Co-authored-by: nahuhh <50635951+nahuhh@users.noreply.github.com>

* Fixes + GUI: v3.2.1

* Fixes + AutoRefreshEnabled true as default.

* Fix small memory issue since new features added,

---------

Co-authored-by: nahuhh <50635951+nahuhh@users.noreply.github.com>
This commit is contained in:
Gerlof van Ek
2025-04-10 21:18:03 +02:00
committed by GitHub
parent f15f073b12
commit 748dd388cb
15 changed files with 1611 additions and 1401 deletions
+14 -3
View File
@@ -201,12 +201,23 @@ const ApiManager = (function() {
},
fetchCoinPrices: async function(coins, source = "coingecko.com", ttl = 300) {
if (!Array.isArray(coins)) {
coins = [coins];
if (!coins) {
throw new Error('No coins specified for price lookup');
}
let coinsParam;
if (Array.isArray(coins)) {
coinsParam = coins.filter(c => c && c.trim() !== '').join(',');
} else if (typeof coins === 'object' && coins.coins) {
coinsParam = coins.coins;
} else {
coinsParam = coins;
}
if (!coinsParam || coinsParam.trim() === '') {
throw new Error('No valid coins to fetch prices for');
}
return this.makeRequest('/json/coinprices', 'POST', {}, {
coins: Array.isArray(coins) ? coins.join(',') : coins,
coins: coinsParam,
source: source,
ttl: ttl
});
+230
View File
@@ -0,0 +1,230 @@
const CoinManager = (function() {
const coinRegistry = [
{
symbol: 'BTC',
name: 'bitcoin',
displayName: 'Bitcoin',
aliases: ['btc', 'bitcoin'],
coingeckoId: 'bitcoin',
cryptocompareId: 'BTC',
usesCryptoCompare: false,
usesCoinGecko: true,
historicalDays: 30,
icon: 'Bitcoin.png'
},
{
symbol: 'XMR',
name: 'monero',
displayName: 'Monero',
aliases: ['xmr', 'monero'],
coingeckoId: 'monero',
cryptocompareId: 'XMR',
usesCryptoCompare: true,
usesCoinGecko: true,
historicalDays: 30,
icon: 'Monero.png'
},
{
symbol: 'PART',
name: 'particl',
displayName: 'Particl',
aliases: ['part', 'particl', 'particl anon', 'particl blind'],
variants: ['Particl', 'Particl Blind', 'Particl Anon'],
coingeckoId: 'particl',
cryptocompareId: 'PART',
usesCryptoCompare: true,
usesCoinGecko: true,
historicalDays: 30,
icon: 'Particl.png'
},
{
symbol: 'BCH',
name: 'bitcoin-cash',
displayName: 'Bitcoin Cash',
aliases: ['bch', 'bitcoincash', 'bitcoin cash'],
coingeckoId: 'bitcoin-cash',
cryptocompareId: 'BCH',
usesCryptoCompare: true,
usesCoinGecko: true,
historicalDays: 30,
icon: 'Bitcoin-Cash.png'
},
{
symbol: 'PIVX',
name: 'pivx',
displayName: 'PIVX',
aliases: ['pivx'],
coingeckoId: 'pivx',
cryptocompareId: 'PIVX',
usesCryptoCompare: true,
usesCoinGecko: true,
historicalDays: 30,
icon: 'PIVX.png'
},
{
symbol: 'FIRO',
name: 'firo',
displayName: 'Firo',
aliases: ['firo', 'zcoin'],
coingeckoId: 'firo',
cryptocompareId: 'FIRO',
usesCryptoCompare: true,
usesCoinGecko: true,
historicalDays: 30,
icon: 'Firo.png'
},
{
symbol: 'DASH',
name: 'dash',
displayName: 'Dash',
aliases: ['dash'],
coingeckoId: 'dash',
cryptocompareId: 'DASH',
usesCryptoCompare: true,
usesCoinGecko: true,
historicalDays: 30,
icon: 'Dash.png'
},
{
symbol: 'LTC',
name: 'litecoin',
displayName: 'Litecoin',
aliases: ['ltc', 'litecoin'],
variants: ['Litecoin', 'Litecoin MWEB'],
coingeckoId: 'litecoin',
cryptocompareId: 'LTC',
usesCryptoCompare: true,
usesCoinGecko: true,
historicalDays: 30,
icon: 'Litecoin.png'
},
{
symbol: 'DOGE',
name: 'dogecoin',
displayName: 'Dogecoin',
aliases: ['doge', 'dogecoin'],
coingeckoId: 'dogecoin',
cryptocompareId: 'DOGE',
usesCryptoCompare: true,
usesCoinGecko: true,
historicalDays: 30,
icon: 'Dogecoin.png'
},
{
symbol: 'DCR',
name: 'decred',
displayName: 'Decred',
aliases: ['dcr', 'decred'],
coingeckoId: 'decred',
cryptocompareId: 'DCR',
usesCryptoCompare: true,
usesCoinGecko: true,
historicalDays: 30,
icon: 'Decred.png'
},
{
symbol: 'NMC',
name: 'namecoin',
displayName: 'Namecoin',
aliases: ['nmc', 'namecoin'],
coingeckoId: 'namecoin',
cryptocompareId: 'NMC',
usesCryptoCompare: true,
usesCoinGecko: true,
historicalDays: 30,
icon: 'Namecoin.png'
},
{
symbol: 'WOW',
name: 'wownero',
displayName: 'Wownero',
aliases: ['wow', 'wownero'],
coingeckoId: 'wownero',
cryptocompareId: 'WOW',
usesCryptoCompare: false,
usesCoinGecko: true,
historicalDays: 30,
icon: 'Wownero.png'
}
];
const symbolToInfo = {};
const nameToInfo = {};
const displayNameToInfo = {};
const coinAliasesMap = {};
function buildLookupMaps() {
coinRegistry.forEach(coin => {
symbolToInfo[coin.symbol.toLowerCase()] = coin;
nameToInfo[coin.name.toLowerCase()] = coin;
displayNameToInfo[coin.displayName.toLowerCase()] = coin;
if (coin.aliases && Array.isArray(coin.aliases)) {
coin.aliases.forEach(alias => {
coinAliasesMap[alias.toLowerCase()] = coin;
});
}
coinAliasesMap[coin.symbol.toLowerCase()] = coin;
coinAliasesMap[coin.name.toLowerCase()] = coin;
coinAliasesMap[coin.displayName.toLowerCase()] = coin;
if (coin.variants && Array.isArray(coin.variants)) {
coin.variants.forEach(variant => {
coinAliasesMap[variant.toLowerCase()] = coin;
});
}
});
}
buildLookupMaps();
function getCoinByAnyIdentifier(identifier) {
if (!identifier) return null;
const normalizedId = identifier.toString().toLowerCase().trim();
const coin = coinAliasesMap[normalizedId];
if (coin) return coin;
if (normalizedId.includes('bitcoin') && normalizedId.includes('cash') ||
normalizedId === 'bch') {
return symbolToInfo['bch'];
}
if (normalizedId === 'zcoin' || normalizedId.includes('firo')) {
return symbolToInfo['firo'];
}
if (normalizedId.includes('particl')) {
return symbolToInfo['part'];
}
return null;
}
return {
getAllCoins: function() {
return [...coinRegistry];
},
getCoinByAnyIdentifier: getCoinByAnyIdentifier,
getSymbol: function(identifier) {
const coin = getCoinByAnyIdentifier(identifier);
return coin ? coin.symbol : null;
},
getDisplayName: function(identifier) {
const coin = getCoinByAnyIdentifier(identifier);
return coin ? coin.displayName : null;
},
getCoingeckoId: function(identifier) {
const coin = getCoinByAnyIdentifier(identifier);
return coin ? coin.coingeckoId : null;
},
coinMatches: function(coinId1, coinId2) {
if (!coinId1 || !coinId2) return false;
const coin1 = getCoinByAnyIdentifier(coinId1);
const coin2 = getCoinByAnyIdentifier(coinId2);
if (!coin1 || !coin2) return false;
return coin1.symbol === coin2.symbol;
},
getPriceKey: function(coinIdentifier) {
if (!coinIdentifier) return null;
const coin = getCoinByAnyIdentifier(coinIdentifier);
if (!coin) return coinIdentifier.toLowerCase();
return coin.coingeckoId;
}
};
})();
window.CoinManager = CoinManager;
console.log('CoinManager initialized');
+25 -123
View File
@@ -17,10 +17,8 @@ const ConfigManager = (function() {
cacheDuration: 10 * 60 * 1000,
requestTimeout: 60000,
wsPort: selectedWsPort,
cacheConfig: {
defaultTTL: 10 * 60 * 1000,
ttlSettings: {
prices: 5 * 60 * 1000,
chart: 5 * 60 * 1000,
@@ -29,17 +27,13 @@ const ConfigManager = (function() {
offers: 2 * 60 * 1000,
identity: 15 * 60 * 1000
},
storage: {
maxSizeBytes: 10 * 1024 * 1024,
maxItems: 200
},
fallbackTTL: 24 * 60 * 60 * 1000
},
itemsPerPage: 50,
apiEndpoints: {
cryptoCompare: 'https://min-api.cryptocompare.com/data/pricemultifull',
coinGecko: 'https://api.coingecko.com/api/v3',
@@ -47,7 +41,6 @@ const ConfigManager = (function() {
cryptoCompareHourly: 'https://min-api.cryptocompare.com/data/v2/histohour',
volumeEndpoint: 'https://api.coingecko.com/api/v3/simple/price'
},
rateLimits: {
coingecko: {
requestsPerMinute: 50,
@@ -58,86 +51,23 @@ const ConfigManager = (function() {
minInterval: 2000
}
},
retryDelays: [5000, 15000, 30000],
coins: [
{ symbol: 'BTC', name: 'bitcoin', usesCryptoCompare: false, usesCoinGecko: true, historicalDays: 30 },
{ symbol: 'XMR', name: 'monero', usesCryptoCompare: true, usesCoinGecko: true, historicalDays: 30 },
{ symbol: 'PART', name: 'particl', usesCryptoCompare: true, usesCoinGecko: true, historicalDays: 30 },
{ symbol: 'BCH', name: 'bitcoincash', usesCryptoCompare: true, usesCoinGecko: true, historicalDays: 30 },
{ symbol: 'PIVX', name: 'pivx', usesCryptoCompare: true, usesCoinGecko: true, historicalDays: 30 },
{ symbol: 'FIRO', name: 'firo', displayName: 'Firo', usesCryptoCompare: true, usesCoinGecko: true, historicalDays: 30 },
{ symbol: 'DASH', name: 'dash', usesCryptoCompare: true, usesCoinGecko: true, historicalDays: 30 },
{ symbol: 'LTC', name: 'litecoin', usesCryptoCompare: true, usesCoinGecko: true, historicalDays: 30 },
{ symbol: 'DOGE', name: 'dogecoin', usesCryptoCompare: true, usesCoinGecko: true, historicalDays: 30 },
{ symbol: 'DCR', name: 'decred', usesCryptoCompare: true, usesCoinGecko: true, historicalDays: 30 },
{ symbol: 'NMC', name: 'namecoin', usesCryptoCompare: true, usesCoinGecko: true, historicalDays: 30 },
{ symbol: 'WOW', name: 'wownero', usesCryptoCompare: false, usesCoinGecko: true, historicalDays: 30 }
],
coinMappings: {
nameToSymbol: {
'Bitcoin': 'BTC',
'Litecoin': 'LTC',
'Monero': 'XMR',
'Particl': 'PART',
'Particl Blind': 'PART',
'Particl Anon': 'PART',
'PIVX': 'PIVX',
'Firo': 'FIRO',
'Zcoin': 'FIRO',
'Dash': 'DASH',
'Decred': 'DCR',
'Namecoin': 'NMC',
'Wownero': 'WOW',
'Bitcoin Cash': 'BCH',
'Dogecoin': 'DOGE'
},
nameToDisplayName: {
'Bitcoin': 'Bitcoin',
'Litecoin': 'Litecoin',
'Monero': 'Monero',
'Particl': 'Particl',
'Particl Blind': 'Particl Blind',
'Particl Anon': 'Particl Anon',
'PIVX': 'PIVX',
'Firo': 'Firo',
'Zcoin': 'Firo',
'Dash': 'Dash',
'Decred': 'Decred',
'Namecoin': 'Namecoin',
'Wownero': 'Wownero',
'Bitcoin Cash': 'Bitcoin Cash',
'Dogecoin': 'Dogecoin'
},
idToName: {
1: 'particl', 2: 'bitcoin', 3: 'litecoin', 4: 'decred', 5: 'namecoin',
6: 'monero', 7: 'particl blind', 8: 'particl anon',
9: 'wownero', 11: 'pivx', 13: 'firo', 17: 'bitcoincash',
18: 'dogecoin'
},
nameToCoinGecko: {
'bitcoin': 'bitcoin',
'monero': 'monero',
'particl': 'particl',
'bitcoin cash': 'bitcoin-cash',
'bitcoincash': 'bitcoin-cash',
'pivx': 'pivx',
'firo': 'firo',
'zcoin': 'firo',
'dash': 'dash',
'litecoin': 'litecoin',
'dogecoin': 'dogecoin',
'decred': 'decred',
'namecoin': 'namecoin',
'wownero': 'wownero'
}
get coins() {
return window.CoinManager ? window.CoinManager.getAllCoins() : [
{ symbol: 'BTC', name: 'bitcoin', usesCryptoCompare: false, usesCoinGecko: true, historicalDays: 30 },
{ symbol: 'XMR', name: 'monero', usesCryptoCompare: true, usesCoinGecko: true, historicalDays: 30 },
{ symbol: 'PART', name: 'particl', usesCryptoCompare: true, usesCoinGecko: true, historicalDays: 30 },
{ symbol: 'BCH', name: 'bitcoincash', usesCryptoCompare: true, usesCoinGecko: true, historicalDays: 30 },
{ symbol: 'PIVX', name: 'pivx', usesCryptoCompare: true, usesCoinGecko: true, historicalDays: 30 },
{ symbol: 'FIRO', name: 'firo', displayName: 'Firo', usesCryptoCompare: true, usesCoinGecko: true, historicalDays: 30 },
{ symbol: 'DASH', name: 'dash', usesCryptoCompare: true, usesCoinGecko: true, historicalDays: 30 },
{ symbol: 'LTC', name: 'litecoin', usesCryptoCompare: true, usesCoinGecko: true, historicalDays: 30 },
{ symbol: 'DOGE', name: 'dogecoin', usesCryptoCompare: true, usesCoinGecko: true, historicalDays: 30 },
{ symbol: 'DCR', name: 'decred', usesCryptoCompare: true, usesCoinGecko: true, historicalDays: 30 },
{ symbol: 'NMC', name: 'namecoin', usesCryptoCompare: true, usesCoinGecko: true, historicalDays: 30 },
{ symbol: 'WOW', name: 'wownero', usesCryptoCompare: false, usesCoinGecko: true, historicalDays: 30 }
];
},
chartConfig: {
colors: {
default: {
@@ -158,28 +88,22 @@ const ConfigManager = (function() {
const publicAPI = {
...defaultConfig,
initialize: function(options = {}) {
if (state.isInitialized) {
console.warn('[ConfigManager] Already initialized');
return this;
}
if (options) {
Object.assign(this, options);
}
if (window.CleanupManager) {
window.CleanupManager.registerResource('configManager', this, (mgr) => mgr.dispose());
}
this.utils = utils;
state.isInitialized = true;
console.log('ConfigManager initialized');
return this;
},
getAPIKeys: function() {
if (typeof window.getAPIKeys === 'function') {
const apiKeys = window.getAPIKeys();
@@ -188,16 +112,16 @@ const ConfigManager = (function() {
coinGecko: apiKeys.coinGecko || ''
};
}
return {
cryptoCompare: '',
coinGecko: ''
};
},
getCoinBackendId: function(coinName) {
if (!coinName) return null;
if (window.CoinManager) {
return window.CoinManager.getPriceKey(coinName);
}
const nameMap = {
'bitcoin-cash': 'bitcoincash',
'bitcoin cash': 'bitcoincash',
@@ -205,70 +129,57 @@ const ConfigManager = (function() {
'zcoin': 'firo',
'bitcoincash': 'bitcoin-cash'
};
const lowerCoinName = typeof coinName === 'string' ? coinName.toLowerCase() : '';
return nameMap[lowerCoinName] || lowerCoinName;
},
coinMatches: function(offerCoin, filterCoin) {
if (!offerCoin || !filterCoin) return false;
if (window.CoinManager) {
return window.CoinManager.coinMatches(offerCoin, filterCoin);
}
offerCoin = offerCoin.toLowerCase();
filterCoin = filterCoin.toLowerCase();
if (offerCoin === filterCoin) return true;
if ((offerCoin === 'firo' || offerCoin === 'zcoin') &&
(filterCoin === 'firo' || filterCoin === 'zcoin')) {
return true;
}
if ((offerCoin === 'bitcoincash' && filterCoin === 'bitcoin cash') ||
(offerCoin === 'bitcoin cash' && filterCoin === 'bitcoincash')) {
return true;
}
const particlVariants = ['particl', 'particl anon', 'particl blind'];
if (filterCoin === 'particl' && particlVariants.includes(offerCoin)) {
return true;
}
if (particlVariants.includes(filterCoin)) {
return offerCoin === filterCoin;
}
return false;
},
update: function(path, value) {
const parts = path.split('.');
let current = this;
for (let i = 0; i < parts.length - 1; i++) {
if (!current[parts[i]]) {
current[parts[i]] = {};
}
current = current[parts[i]];
}
current[parts[parts.length - 1]] = value;
return this;
},
get: function(path, defaultValue = null) {
const parts = path.split('.');
let current = this;
for (let i = 0; i < parts.length; i++) {
if (current === undefined || current === null) {
return defaultValue;
}
current = current[parts[i]];
}
return current !== undefined ? current : defaultValue;
},
dispose: function() {
state.isInitialized = false;
console.log('ConfigManager disposed');
@@ -290,7 +201,6 @@ const ConfigManager = (function() {
return '0';
}
},
formatDate: function(timestamp, resolution) {
const date = new Date(timestamp);
const options = {
@@ -300,7 +210,6 @@ const ConfigManager = (function() {
};
return date.toLocaleString('en-US', { ...options[resolution], timeZone: 'UTC' });
},
debounce: function(func, delay) {
let timeoutId;
return function(...args) {
@@ -308,17 +217,14 @@ const ConfigManager = (function() {
timeoutId = setTimeout(() => func(...args), delay);
};
},
formatTimeLeft: function(timestamp) {
const now = Math.floor(Date.now() / 1000);
if (timestamp <= now) return "Expired";
return this.formatTime(timestamp);
},
formatTime: function(timestamp, addAgoSuffix = false) {
const now = Math.floor(Date.now() / 1000);
const diff = Math.abs(now - timestamp);
let timeString;
if (diff < 60) {
timeString = `${diff} seconds`;
@@ -333,10 +239,8 @@ const ConfigManager = (function() {
} else {
timeString = `${Math.floor(diff / 31536000)} years`;
}
return addAgoSuffix ? `${timeString} ago` : timeString;
},
escapeHtml: function(unsafe) {
if (typeof unsafe !== 'string') {
console.warn('escapeHtml received a non-string value:', unsafe);
@@ -349,7 +253,6 @@ const ConfigManager = (function() {
.replace(/"/g, "&quot;")
.replace(/'/g, "&#039;");
},
formatPrice: function(coin, price) {
if (typeof price !== 'number' || isNaN(price)) {
console.warn(`Invalid price for ${coin}:`, price);
@@ -363,7 +266,6 @@ const ConfigManager = (function() {
if (price < 100000) return price.toFixed(1);
return price.toFixed(0);
},
getEmptyPriceData: function() {
return {
'bitcoin': { usd: null, btc: null },
@@ -381,12 +283,13 @@ const ConfigManager = (function() {
'firo': { usd: null, btc: null }
};
},
getCoinSymbol: function(fullName) {
return publicAPI.coinMappings?.nameToSymbol[fullName] || fullName;
if (window.CoinManager) {
return window.CoinManager.getSymbol(fullName) || fullName;
}
return fullName;
}
};
return publicAPI;
})();
@@ -415,5 +318,4 @@ if (typeof module !== 'undefined') {
module.exports = ConfigManager;
}
//console.log('ConfigManager initialized with properties:', Object.keys(ConfigManager));
console.log('ConfigManager initialized');
@@ -0,0 +1,233 @@
const PriceManager = (function() {
const PRICES_CACHE_KEY = 'prices_unified';
let fetchPromise = null;
let lastFetchTime = 0;
const MIN_FETCH_INTERVAL = 60000;
let isInitialized = false;
const eventListeners = {
'priceUpdate': [],
'error': []
};
return {
addEventListener: function(event, callback) {
if (eventListeners[event]) {
eventListeners[event].push(callback);
}
},
removeEventListener: function(event, callback) {
if (eventListeners[event]) {
eventListeners[event] = eventListeners[event].filter(cb => cb !== callback);
}
},
triggerEvent: function(event, data) {
if (eventListeners[event]) {
eventListeners[event].forEach(callback => callback(data));
}
},
initialize: function() {
if (isInitialized) {
console.warn('PriceManager: Already initialized');
return this;
}
if (window.CleanupManager) {
window.CleanupManager.registerResource('priceManager', this, (mgr) => {
Object.keys(eventListeners).forEach(event => {
eventListeners[event] = [];
});
});
}
setTimeout(() => this.getPrices(), 1500);
isInitialized = true;
return this;
},
getPrices: async function(forceRefresh = false) {
if (!forceRefresh) {
const cachedData = CacheManager.get(PRICES_CACHE_KEY);
if (cachedData) {
return cachedData.value;
}
}
if (fetchPromise && Date.now() - lastFetchTime < MIN_FETCH_INTERVAL) {
return fetchPromise;
}
console.log('PriceManager: Fetching latest prices.');
lastFetchTime = Date.now();
fetchPromise = this.fetchPrices()
.then(prices => {
this.triggerEvent('priceUpdate', prices);
return prices;
})
.catch(error => {
this.triggerEvent('error', error);
throw error;
})
.finally(() => {
fetchPromise = null;
});
return fetchPromise;
},
fetchPrices: async function() {
try {
if (!NetworkManager.isOnline()) {
throw new Error('Network is offline');
}
const coinSymbols = window.CoinManager
? window.CoinManager.getAllCoins().map(c => c.symbol).filter(symbol => symbol && symbol.trim() !== '')
: (window.config.coins
? window.config.coins.map(c => c.symbol).filter(symbol => symbol && symbol.trim() !== '')
: ['BTC', 'XMR', 'PART', 'BCH', 'PIVX', 'FIRO', 'DASH', 'LTC', 'DOGE', 'DCR', 'NMC', 'WOW']);
console.log('PriceManager: lookupFiatRates ' + coinSymbols.join(', '));
if (!coinSymbols.length) {
throw new Error('No valid coins configured');
}
let apiResponse;
try {
apiResponse = await Api.fetchCoinPrices(
coinSymbols,
"coingecko.com",
300
);
if (!apiResponse) {
throw new Error('Empty response received from API');
}
if (apiResponse.error) {
throw new Error(`API error: ${apiResponse.error}`);
}
if (!apiResponse.rates) {
throw new Error('No rates found in API response');
}
if (typeof apiResponse.rates !== 'object' || Object.keys(apiResponse.rates).length === 0) {
throw new Error('Empty rates object in API response');
}
} catch (apiError) {
console.error('API call error:', apiError);
throw new Error(`API error: ${apiError.message}`);
}
const processedData = {};
Object.entries(apiResponse.rates).forEach(([coinId, price]) => {
let normalizedCoinId;
if (window.CoinManager) {
const coin = window.CoinManager.getCoinByAnyIdentifier(coinId);
if (coin) {
normalizedCoinId = window.CoinManager.getPriceKey(coin.name);
} else {
normalizedCoinId = coinId === 'bitcoincash' ? 'bitcoin-cash' : coinId.toLowerCase();
}
} else {
normalizedCoinId = coinId === 'bitcoincash' ? 'bitcoin-cash' : coinId.toLowerCase();
}
if (coinId.toLowerCase() === 'zcoin') {
normalizedCoinId = 'firo';
}
processedData[normalizedCoinId] = {
usd: price,
btc: normalizedCoinId === 'bitcoin' ? 1 : price / (apiResponse.rates.bitcoin || 1)
};
});
CacheManager.set(PRICES_CACHE_KEY, processedData, 'prices');
Object.entries(processedData).forEach(([coin, prices]) => {
if (prices.usd) {
if (window.tableRateModule) {
window.tableRateModule.setFallbackValue(coin, prices.usd);
}
}
});
return processedData;
} catch (error) {
console.error('Error fetching prices:', error);
NetworkManager.handleNetworkError(error);
const cachedData = CacheManager.get(PRICES_CACHE_KEY);
if (cachedData) {
console.log('Using cached price data');
return cachedData.value;
}
try {
const existingCache = localStorage.getItem(PRICES_CACHE_KEY);
if (existingCache) {
console.log('Using localStorage cached price data');
return JSON.parse(existingCache).value;
}
} catch (e) {
console.warn('Failed to parse existing cache:', e);
}
const emptyData = {};
const coinNames = window.CoinManager
? window.CoinManager.getAllCoins().map(c => c.name.toLowerCase())
: ['bitcoin', 'bitcoin-cash', 'dash', 'dogecoin', 'decred', 'namecoin', 'litecoin', 'particl', 'pivx', 'monero', 'wownero', 'firo'];
coinNames.forEach(coin => {
emptyData[coin] = { usd: null, btc: null };
});
return emptyData;
}
},
getCoinPrice: function(coinSymbol) {
if (!coinSymbol) return null;
const prices = this.getPrices();
if (!prices) return null;
let normalizedSymbol;
if (window.CoinManager) {
normalizedSymbol = window.CoinManager.getPriceKey(coinSymbol);
} else {
normalizedSymbol = coinSymbol.toLowerCase();
}
return prices[normalizedSymbol] || null;
},
formatPrice: function(coin, price) {
if (window.config && window.config.utils && window.config.utils.formatPrice) {
return window.config.utils.formatPrice(coin, price);
}
if (typeof price !== 'number' || isNaN(price)) return 'N/A';
if (price < 0.01) return price.toFixed(8);
if (price < 1) return price.toFixed(4);
if (price < 1000) return price.toFixed(2);
return price.toFixed(0);
}
};
})();
window.PriceManager = PriceManager;
document.addEventListener('DOMContentLoaded', function() {
if (!window.priceManagerInitialized) {
window.PriceManager = PriceManager.initialize();
window.priceManagerInitialized = true;
}
});
console.log('PriceManager initialized');
+59 -94
View File
@@ -23,54 +23,6 @@ const WalletManager = (function() {
balancesVisible: 'balancesVisible'
};
const coinData = {
symbols: {
'Bitcoin': 'BTC',
'Particl': 'PART',
'Monero': 'XMR',
'Wownero': 'WOW',
'Litecoin': 'LTC',
'Dogecoin': 'DOGE',
'Firo': 'FIRO',
'Dash': 'DASH',
'PIVX': 'PIVX',
'Decred': 'DCR',
'Namecoin': 'NMC',
'Bitcoin Cash': 'BCH'
},
coingeckoIds: {
'BTC': 'btc',
'PART': 'part',
'XMR': 'xmr',
'WOW': 'wownero',
'LTC': 'ltc',
'DOGE': 'doge',
'FIRO': 'firo',
'DASH': 'dash',
'PIVX': 'pivx',
'DCR': 'dcr',
'NMC': 'nmc',
'BCH': 'bch'
},
shortNames: {
'Bitcoin': 'BTC',
'Particl': 'PART',
'Monero': 'XMR',
'Wownero': 'WOW',
'Litecoin': 'LTC',
'Litecoin MWEB': 'LTC MWEB',
'Firo': 'FIRO',
'Dash': 'DASH',
'PIVX': 'PIVX',
'Decred': 'DCR',
'Namecoin': 'NMC',
'Bitcoin Cash': 'BCH',
'Dogecoin': 'DOGE'
}
};
const state = {
lastFetchTime: 0,
toggleInProgress: false,
@@ -83,7 +35,23 @@ const WalletManager = (function() {
};
function getShortName(fullName) {
return coinData.shortNames[fullName] || fullName;
return window.CoinManager.getSymbol(fullName) || fullName;
}
function getCoingeckoId(coinName) {
if (!window.CoinManager) {
console.warn('[WalletManager] CoinManager not available');
return coinName;
}
const coin = window.CoinManager.getCoinByAnyIdentifier(coinName);
if (!coin) {
console.warn(`[WalletManager] No coin found for: ${coinName}`);
return coinName;
}
return coin.symbol;
}
async function fetchPrices(forceUpdate = false) {
@@ -105,16 +73,33 @@ const WalletManager = (function() {
const shouldIncludeWow = currentSource === 'coingecko.com';
const coinsToFetch = Object.values(coinData.symbols)
.filter(symbol => shouldIncludeWow || symbol !== 'WOW')
.map(symbol => coinData.coingeckoIds[symbol] || symbol.toLowerCase())
.join(',');
const coinsToFetch = [];
const processedCoins = new Set();
document.querySelectorAll('.coinname-value').forEach(el => {
const coinName = el.getAttribute('data-coinname');
if (!coinName || processedCoins.has(coinName)) return;
const adjustedName = coinName === 'Zcoin' ? 'Firo' :
coinName.includes('Particl') ? 'Particl' :
coinName;
const coinId = getCoingeckoId(adjustedName);
if (coinId && (shouldIncludeWow || coinId !== 'WOW')) {
coinsToFetch.push(coinId);
processedCoins.add(coinName);
}
});
const fetchCoinsString = coinsToFetch.join(',');
const mainResponse = await fetch("/json/coinprices", {
method: "POST",
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
coins: coinsToFetch,
coins: fetchCoinsString,
source: currentSource,
ttl: config.defaultTTL
})
@@ -127,46 +112,27 @@ const WalletManager = (function() {
const mainData = await mainResponse.json();
if (mainData && mainData.rates) {
Object.entries(mainData.rates).forEach(([coinId, price]) => {
const symbol = Object.entries(coinData.coingeckoIds).find(([sym, id]) => id.toLowerCase() === coinId.toLowerCase())?.[0];
if (symbol) {
const coinKey = Object.keys(coinData.symbols).find(key => coinData.symbols[key] === symbol);
if (coinKey) {
processedData[coinKey.toLowerCase().replace(' ', '-')] = {
usd: price,
btc: symbol === 'BTC' ? 1 : price / (mainData.rates.btc || 1)
};
}
document.querySelectorAll('.coinname-value').forEach(el => {
const coinName = el.getAttribute('data-coinname');
if (!coinName) return;
const adjustedName = coinName === 'Zcoin' ? 'Firo' :
coinName.includes('Particl') ? 'Particl' :
coinName;
const coinId = getCoingeckoId(adjustedName);
const price = mainData.rates[coinId];
if (price) {
const coinKey = coinName.toLowerCase().replace(' ', '-');
processedData[coinKey] = {
usd: price,
btc: coinId === 'BTC' ? 1 : price / (mainData.rates.BTC || 1)
};
}
});
}
if (!shouldIncludeWow && !processedData['wownero']) {
try {
const wowResponse = await fetch("/json/coinprices", {
method: "POST",
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
coins: "wownero",
source: "coingecko.com",
ttl: config.defaultTTL
})
});
if (wowResponse.ok) {
const wowData = await wowResponse.json();
if (wowData && wowData.rates && wowData.rates.wownero) {
processedData['wownero'] = {
usd: wowData.rates.wownero,
btc: processedData.bitcoin ? wowData.rates.wownero / processedData.bitcoin.usd : 0
};
}
}
} catch (wowError) {
console.error('Error fetching WOW price:', wowError);
}
}
CacheManager.set(state.cacheKey, processedData, config.cacheExpiration);
state.lastFetchTime = now;
return processedData;
@@ -202,7 +168,6 @@ const WalletManager = (function() {
throw lastError || new Error('Failed to fetch prices');
}
// UI Management functions
function storeOriginalValues() {
document.querySelectorAll('.coinname-value').forEach(el => {
const coinName = el.getAttribute('data-coinname');
@@ -210,10 +175,10 @@ const WalletManager = (function() {
if (coinName) {
const amount = value ? parseFloat(value.replace(/[^0-9.-]+/g, '')) : 0;
const coinId = coinData.symbols[coinName];
const coinSymbol = window.CoinManager.getSymbol(coinName);
const shortName = getShortName(coinName);
if (coinId) {
if (coinSymbol) {
if (coinName === 'Particl') {
const isBlind = el.closest('.flex')?.querySelector('h4')?.textContent?.includes('Blind');
const isAnon = el.closest('.flex')?.querySelector('h4')?.textContent?.includes('Anon');
@@ -224,7 +189,7 @@ const WalletManager = (function() {
const balanceType = isMWEB ? 'mweb' : 'public';
localStorage.setItem(`litecoin-${balanceType}-amount`, amount.toString());
} else {
localStorage.setItem(`${coinId.toLowerCase()}-amount`, amount.toString());
localStorage.setItem(`${coinSymbol.toLowerCase()}-amount`, amount.toString());
}
el.setAttribute('data-original-value', `${amount} ${shortName}`);
@@ -168,6 +168,8 @@ const WebSocketManager = (function() {
}
state.isConnecting = false;
state.messageHandlers = {};
if (ws) {
ws.onopen = null;