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

View File

@@ -984,76 +984,53 @@ def js_readurl(self, url_split, post_string, is_json) -> bytes:
def js_active(self, url_split, post_string, is_json) -> bytes:
swap_client = self.server.swap_client
swap_client.checkSystemStatus()
filters = {
"sort_by": "created_at",
"sort_dir": "desc",
"with_available_or_active": True,
"with_extra_info": True,
}
EXCLUDED_STATES = [
"Failed, refunded",
"Failed, swiped",
"Failed",
"Error",
"Expired",
"Timed-out",
"Abandoned",
"Completed",
]
all_bids = []
processed_bid_ids = set()
try:
received_bids = swap_client.listBids(filters=filters)
sent_bids = swap_client.listBids(sent=True, filters=filters)
for bid in received_bids + sent_bids:
for bid_id, (bid, offer) in list(swap_client.swaps_in_progress.items()):
try:
bid_id_hex = bid[2].hex()
if bid_id_hex in processed_bid_ids:
continue
offer = swap_client.getOffer(bid[3])
if not offer:
continue
bid_state = strBidState(bid[5])
if bid_state in EXCLUDED_STATES:
continue
tx_state_a = strTxState(bid[7])
tx_state_b = strTxState(bid[8])
swap_data = {
"bid_id": bid_id_hex,
"offer_id": bid[3].hex(),
"created_at": bid[0],
"bid_state": bid_state,
"tx_state_a": tx_state_a if tx_state_a else "None",
"tx_state_b": tx_state_b if tx_state_b else "None",
"coin_from": swap_client.ci(bid[9]).coin_name(),
"bid_id": bid_id.hex(),
"offer_id": offer.offer_id.hex(),
"created_at": bid.created_at,
"expire_at": bid.expire_at,
"bid_state": strBidState(bid.state),
"tx_state_a": None,
"tx_state_b": None,
"coin_from": swap_client.ci(offer.coin_from).coin_name(),
"coin_to": swap_client.ci(offer.coin_to).coin_name(),
"amount_from": swap_client.ci(bid[9]).format_amount(bid[4]),
"amount_to": swap_client.ci(offer.coin_to).format_amount(
(bid[4] * bid[10]) // swap_client.ci(bid[9]).COIN()
"amount_from": swap_client.ci(offer.coin_from).format_amount(
bid.amount
),
"addr_from": bid[11],
"status": {
"main": bid_state,
"initial_tx": tx_state_a if tx_state_a else "None",
"payment_tx": tx_state_b if tx_state_b else "None",
},
"amount_to": swap_client.ci(offer.coin_to).format_amount(
bid.amount_to
),
"addr_from": bid.bid_addr if bid.was_received else offer.addr_from,
}
if offer.swap_type == SwapTypes.XMR_SWAP:
swap_data["tx_state_a"] = (
strTxState(bid.xmr_a_lock_tx.state)
if bid.xmr_a_lock_tx
else None
)
swap_data["tx_state_b"] = (
strTxState(bid.xmr_b_lock_tx.state)
if bid.xmr_b_lock_tx
else None
)
else:
swap_data["tx_state_a"] = bid.getITxState()
swap_data["tx_state_b"] = bid.getPTxState()
if hasattr(bid, "rate"):
swap_data["rate"] = bid.rate
all_bids.append(swap_data)
processed_bid_ids.add(bid_id_hex)
except Exception:
continue
pass
except Exception:
return bytes(json.dumps([]), "UTF-8")
return bytes(json.dumps(all_bids), "UTF-8")

View File

@@ -1,20 +1,4 @@
const PAGE_SIZE = 50;
const COIN_NAME_TO_SYMBOL = {
'Bitcoin': 'BTC',
'Litecoin': 'LTC',
'Monero': 'XMR',
'Particl': 'PART',
'Particl Blind': 'PART',
'Particl Anon': 'PART',
'PIVX': 'PIVX',
'Firo': 'FIRO',
'Dash': 'DASH',
'Decred': 'DCR',
'Namecoin': 'NMC',
'Wownero': 'WOW',
'Bitcoin Cash': 'BCH',
'Dogecoin': 'DOGE'
};
const state = {
dentities: new Map(),
@@ -288,8 +272,8 @@ const createBidTableRow = async (bid) => {
const rate = toAmount > 0 ? toAmount / fromAmount : 0;
const inverseRate = fromAmount > 0 ? fromAmount / toAmount : 0;
const fromSymbol = COIN_NAME_TO_SYMBOL[bid.coin_from] || bid.coin_from;
const toSymbol = COIN_NAME_TO_SYMBOL[bid.coin_to] || bid.coin_to;
const fromSymbol = window.CoinManager.getSymbol(bid.coin_from) || bid.coin_from;
const toSymbol = window.CoinManager.getSymbol(bid.coin_to) || bid.coin_to;
const timeColor = getTimeStrokeColor(bid.expire_at);
const uniqueId = `${bid.bid_id}_${bid.created_at}`;

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
});

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');

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,10 +51,9 @@ const ConfigManager = (function() {
minInterval: 2000
}
},
retryDelays: [5000, 15000, 30000],
coins: [
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 },
@@ -74,70 +66,8 @@ const ConfigManager = (function() {
{ 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'
}
},
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');

View File

@@ -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');

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(' ', '-')] = {
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: symbol === 'BTC' ? 1 : price / (mainData.rates.btc || 1)
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}`);

View File

@@ -168,6 +168,8 @@ const WebSocketManager = (function() {
}
state.isConnecting = false;
state.messageHandlers = {};
if (ws) {
ws.onopen = null;

File diff suppressed because it is too large Load Diff

View File

@@ -118,122 +118,28 @@ const api = {
},
fetchCoinGeckoDataXHR: async () => {
const cacheKey = 'coinGeckoOneLiner';
const cachedData = CacheManager.get(cacheKey);
if (cachedData) {
return cachedData.value;
}
try {
if (!NetworkManager.isOnline()) {
throw new Error('Network is offline');
}
const existingCache = localStorage.getItem(cacheKey);
let fallbackData = null;
if (existingCache) {
try {
const parsed = JSON.parse(existingCache);
fallbackData = parsed.value;
} catch (e) {
console.warn('Failed to parse existing cache:', e);
}
}
const apiResponse = await Api.fetchCoinGeckoData({
coinGecko: window.config.getAPIKeys().coinGecko
});
if (!apiResponse || !apiResponse.rates) {
if (fallbackData) {
return fallbackData;
}
throw new Error('Invalid data structure received from API');
}
const priceData = await window.PriceManager.getPrices();
const transformedData = {};
window.config.coins.forEach(coin => {
const coinName = coin.name;
const coinRate = apiResponse.rates[coinName];
if (coinRate) {
const symbol = coin.symbol.toLowerCase();
const coinData = priceData[symbol] || priceData[coin.name.toLowerCase()];
if (coinData && coinData.usd) {
transformedData[symbol] = {
current_price: coinRate,
price_btc: coinName === 'bitcoin' ? 1 : coinRate / (apiResponse.rates.bitcoin || 1),
total_volume: fallbackData && fallbackData[symbol] ? fallbackData[symbol].total_volume : null,
price_change_percentage_24h: fallbackData && fallbackData[symbol] ? fallbackData[symbol].price_change_percentage_24h : null,
displayName: coin.displayName || coin.symbol || coinName
current_price: coinData.usd,
price_btc: coinData.btc || (priceData.bitcoin ? coinData.usd / priceData.bitcoin.usd : 0),
displayName: coin.displayName || coin.symbol
};
}
});
try {
if (!transformedData['wow'] && config.coins.some(c => c.symbol === 'WOW')) {
const wowResponse = await Api.fetchCoinPrices("wownero", {
coinGecko: window.config.getAPIKeys().coinGecko
});
if (wowResponse && wowResponse.rates && wowResponse.rates.wownero) {
transformedData['wow'] = {
current_price: wowResponse.rates.wownero,
price_btc: transformedData.btc ? wowResponse.rates.wownero / transformedData.btc.current_price : 0,
total_volume: fallbackData && fallbackData['wow'] ? fallbackData['wow'].total_volume : null,
price_change_percentage_24h: fallbackData && fallbackData['wow'] ? fallbackData['wow'].price_change_percentage_24h : null,
displayName: 'Wownero'
};
}
}
} catch (wowError) {
console.error('Error fetching WOW price:', wowError);
}
const missingCoins = window.config.coins.filter(coin =>
!transformedData[coin.symbol.toLowerCase()] &&
fallbackData &&
fallbackData[coin.symbol.toLowerCase()]
);
missingCoins.forEach(coin => {
const symbol = coin.symbol.toLowerCase();
if (fallbackData && fallbackData[symbol]) {
transformedData[symbol] = fallbackData[symbol];
}
});
CacheManager.set(cacheKey, transformedData, 'prices');
if (NetworkManager.getReconnectAttempts() > 0) {
NetworkManager.resetReconnectAttempts();
}
return transformedData;
} catch (error) {
console.error('Error fetching coin data:', error);
NetworkManager.handleNetworkError(error);
const cachedData = CacheManager.get(cacheKey);
if (cachedData) {
console.log('Using cached data due to error');
return cachedData.value;
}
try {
const existingCache = localStorage.getItem(cacheKey);
if (existingCache) {
const parsed = JSON.parse(existingCache);
if (parsed.value) {
console.log('Using expired cache as last resort');
return parsed.value;
}
}
} catch (e) {
console.warn('Failed to parse expired cache:', e);
}
throw error;
console.error('Error in fetchCoinGeckoDataXHR:', error);
return {};
}
},
@@ -617,6 +523,7 @@ const chartModule = {
currentCoin: 'BTC',
loadStartTime: 0,
chartRefs: new WeakMap(),
pendingAnimationFrame: null,
verticalLinePlugin: {
id: 'verticalLine',
@@ -650,29 +557,20 @@ const chartModule = {
destroyChart: function() {
if (chartModule.chart) {
try {
const chartInstance = chartModule.chart;
const canvas = document.getElementById('coin-chart');
if (canvas) {
const events = ['click', 'mousemove', 'mouseout', 'mouseover', 'mousedown', 'mouseup'];
events.forEach(eventType => {
canvas.removeEventListener(eventType, null);
});
}
chartModule.chart.destroy();
chartModule.chart = null;
if (chartInstance && chartInstance.destroy && typeof chartInstance.destroy === 'function') {
chartInstance.destroy();
}
if (canvas) {
chartModule.chartRefs.delete(canvas);
}
} catch (e) {
try {
if (chartModule.chart) {
if (chartModule.chart.destroy && typeof chartModule.chart.destroy === 'function') {
chartModule.chart.destroy();
}
chartModule.chart = null;
}
} catch (finalError) {}
console.error('Error destroying chart:', e);
}
}
},
@@ -1088,11 +986,18 @@ const chartModule = {
},
cleanup: function() {
this.destroyChart();
if (this.pendingAnimationFrame) {
cancelAnimationFrame(this.pendingAnimationFrame);
this.pendingAnimationFrame = null;
}
if (!document.hidden) {
this.currentCoin = null;
}
this.loadStartTime = 0;
this.chartRefs = new WeakMap();
}
}
};
Chart.register(chartModule.verticalLinePlugin);
@@ -1152,14 +1057,16 @@ const app = {
nextRefreshTime: null,
lastRefreshedTime: null,
isRefreshing: false,
isAutoRefreshEnabled: localStorage.getItem('autoRefreshEnabled') !== 'false',
isAutoRefreshEnabled: localStorage.getItem('autoRefreshEnabled') !== 'true',
updateNextRefreshTimeRAF: null,
refreshTexts: {
label: 'Auto-refresh in',
disabled: 'Auto-refresh: disabled',
justRefreshed: 'Just refreshed',
},
cacheTTL: window.config.cacheConfig.ttlSettings.prices,
minimumRefreshInterval: 60 * 1000,
minimumRefreshInterval: 300 * 1000,
init: function() {
window.addEventListener('load', app.onLoad);
@@ -1329,7 +1236,6 @@ const app = {
const headers = document.querySelectorAll('th');
headers.forEach((header, index) => {
CleanupManager.addListener(header, 'click', () => app.sortTable(index, header.classList.contains('disabled')));
});
const closeErrorButton = document.getElementById('close-error');
@@ -1355,6 +1261,52 @@ const app = {
}
},
updateNextRefreshTime: function() {
const nextRefreshSpan = document.getElementById('next-refresh-time');
const labelElement = document.getElementById('next-refresh-label');
const valueElement = document.getElementById('next-refresh-value');
if (nextRefreshSpan && labelElement && valueElement) {
if (app.isRefreshing) {
labelElement.textContent = '';
valueElement.textContent = 'Refreshing...';
valueElement.classList.add('text-blue-500');
return;
} else {
valueElement.classList.remove('text-blue-500');
}
if (app.nextRefreshTime) {
if (app.updateNextRefreshTimeRAF) {
cancelAnimationFrame(app.updateNextRefreshTimeRAF);
app.updateNextRefreshTimeRAF = null;
}
const updateDisplay = () => {
const timeUntilRefresh = Math.max(0, Math.ceil((app.nextRefreshTime - Date.now()) / 1000));
if (timeUntilRefresh === 0) {
labelElement.textContent = '';
valueElement.textContent = app.refreshTexts.justRefreshed;
} else {
const minutes = Math.floor(timeUntilRefresh / 60);
const seconds = timeUntilRefresh % 60;
labelElement.textContent = `${app.refreshTexts.label}: `;
valueElement.textContent = `${minutes}:${seconds.toString().padStart(2, '0')}`;
}
if (timeUntilRefresh > 0) {
app.updateNextRefreshTimeRAF = requestAnimationFrame(updateDisplay);
}
};
updateDisplay();
} else {
labelElement.textContent = '';
valueElement.textContent = app.refreshTexts.disabled;
}
}
},
scheduleNextRefresh: function() {
if (app.autoRefreshInterval) {
clearTimeout(app.autoRefreshInterval);
@@ -1376,7 +1328,7 @@ const app = {
}
});
let nextRefreshTime;
let nextRefreshTime = now + app.minimumRefreshInterval;
if (earliestExpiration !== Infinity) {
nextRefreshTime = Math.max(earliestExpiration, now + app.minimumRefreshInterval);
} else {
@@ -1395,7 +1347,9 @@ const app = {
app.updateNextRefreshTime();
},
refreshAllData: async function() {
refreshAllData: async function() {
console.log('Price refresh started at', new Date().toLocaleTimeString());
if (app.isRefreshing) {
console.log('Refresh already in progress, skipping...');
return;
@@ -1430,6 +1384,7 @@ const app = {
console.log('Starting refresh of all data...');
app.isRefreshing = true;
app.updateNextRefreshTime();
ui.showLoader();
chartModule.showChartLoader();
@@ -1494,6 +1449,8 @@ const app = {
const cacheKey = `coinData_${coin.symbol}`;
CacheManager.set(cacheKey, coinData, 'prices');
console.log(`Updated price for ${coin.symbol}: $${coinData.current_price}`);
} catch (coinError) {
console.warn(`Failed to update ${coin.symbol}: ${coinError.message}`);
failedCoins.push(coin.symbol);
@@ -1532,8 +1489,7 @@ const app = {
}
}, 1000);
}
console.log(`Refresh completed. Failed coins: ${failedCoins.length}`);
console.log(`Price refresh completed at ${new Date().toLocaleTimeString()}. Updated ${window.config.coins.length - failedCoins.length}/${window.config.coins.length} coins.`);
} catch (error) {
console.error('Critical error during refresh:', error);
@@ -1552,51 +1508,21 @@ const app = {
}
}, 1000);
console.error(`Price refresh failed at ${new Date().toLocaleTimeString()}: ${error.message}`);
} finally {
ui.hideLoader();
chartModule.hideChartLoader();
app.isRefreshing = false;
app.updateNextRefreshTime();
if (app.isAutoRefreshEnabled) {
app.scheduleNextRefresh();
}
}
},
updateNextRefreshTime: function() {
const nextRefreshSpan = document.getElementById('next-refresh-time');
const labelElement = document.getElementById('next-refresh-label');
const valueElement = document.getElementById('next-refresh-value');
if (nextRefreshSpan && labelElement && valueElement) {
if (app.nextRefreshTime) {
if (app.updateNextRefreshTimeRAF) {
cancelAnimationFrame(app.updateNextRefreshTimeRAF);
console.log(`Refresh process finished at ${new Date().toLocaleTimeString()}, next refresh scheduled: ${app.isAutoRefreshEnabled ? 'yes' : 'no'}`);
}
const updateDisplay = () => {
const timeUntilRefresh = Math.max(0, Math.ceil((app.nextRefreshTime - Date.now()) / 1000));
if (timeUntilRefresh === 0) {
labelElement.textContent = '';
valueElement.textContent = app.refreshTexts.justRefreshed;
} else {
const minutes = Math.floor(timeUntilRefresh / 60);
const seconds = timeUntilRefresh % 60;
labelElement.textContent = `${app.refreshTexts.label}: `;
valueElement.textContent = `${minutes}:${seconds.toString().padStart(2, '0')}`;
}
if (timeUntilRefresh > 0) {
app.updateNextRefreshTimeRAF = requestAnimationFrame(updateDisplay);
}
};
updateDisplay();
} else {
labelElement.textContent = '';
valueElement.textContent = app.refreshTexts.disabled;
}
}
},
},
updateAutoRefreshButton: function() {
const button = document.getElementById('toggle-auto-refresh');
@@ -1649,23 +1575,33 @@ const app = {
updateBTCPrice: async function() {
try {
if (!NetworkManager.isOnline()) {
throw new Error('Network is offline');
const priceData = await window.PriceManager.getPrices();
if (priceData) {
if (priceData.bitcoin && priceData.bitcoin.usd) {
app.btcPriceUSD = priceData.bitcoin.usd;
return true;
} else if (priceData.btc && priceData.btc.usd) {
app.btcPriceUSD = priceData.btc.usd;
return true;
}
}
const response = await Api.fetchCoinPrices("bitcoin");
if (response && response.rates && response.rates.bitcoin) {
app.btcPriceUSD = response.rates.bitcoin;
if (app.btcPriceUSD > 0) {
console.log('Using previously cached BTC price:', app.btcPriceUSD);
return true;
}
console.warn('Unexpected BTC price data structure:', response);
console.warn('Could not find BTC price in current data');
return false;
} catch (error) {
console.error('Error fetching BTC price:', error);
NetworkManager.handleNetworkError(error);
if (app.btcPriceUSD > 0) {
console.log('Using previously cached BTC price after error:', app.btcPriceUSD);
return true;
}
return false;
}
},
@@ -1815,13 +1751,13 @@ document.addEventListener('DOMContentLoaded', () => {
CleanupManager.setInterval(() => {
CacheManager.cleanup();
}, 300000); // Every 5 minutes
}, 300000);
CleanupManager.setInterval(() => {
if (chartModule && chartModule.currentCoin && NetworkManager.isOnline()) {
chartModule.updateChart(chartModule.currentCoin);
}
}, 900000); // Every 15 minutes
}, 900000);
CleanupManager.addListener(document, 'visibilitychange', () => {
if (!document.hidden) {

View File

@@ -1,20 +1,4 @@
const PAGE_SIZE = 50;
const COIN_NAME_TO_SYMBOL = {
'Bitcoin': 'BTC',
'Litecoin': 'LTC',
'Monero': 'XMR',
'Particl': 'PART',
'Particl Blind': 'PART',
'Particl Anon': 'PART',
'PIVX': 'PIVX',
'Firo': 'FIRO',
'Dash': 'DASH',
'Decred': 'DCR',
'Namecoin': 'NMC',
'Wownero': 'WOW',
'Bitcoin Cash': 'BCH',
'Dogecoin': 'DOGE'
};
const state = {
identities: new Map(),
@@ -309,8 +293,8 @@ const createSwapTableRow = async (swap) => {
const identity = await IdentityManager.getIdentityData(swap.addr_from);
const uniqueId = `${swap.bid_id}_${swap.created_at}`;
const fromSymbol = COIN_NAME_TO_SYMBOL[swap.coin_from] || swap.coin_from;
const toSymbol = COIN_NAME_TO_SYMBOL[swap.coin_to] || swap.coin_to;
const fromSymbol = window.CoinManager.getSymbol(swap.coin_from) || swap.coin_from;
const toSymbol = window.CoinManager.getSymbol(swap.coin_to) || swap.coin_to;
const timeColor = getTimeStrokeColor(swap.expire_at);
const fromAmount = parseFloat(swap.amount_from) || 0;
const toAmount = parseFloat(swap.amount_to) || 0;
@@ -630,31 +614,7 @@ async function updateSwapsTable(options = {}) {
}
function isActiveSwap(swap) {
const activeStates = [
'InProgress',
'Accepted',
'Delaying',
'Auto accept delay',
'Request accepted',
//'Received',
'Script coin locked',
'Scriptless coin locked',
'Script coin lock released',
'SendingInitialTx',
'SendingPaymentTx',
'Exchanged script lock tx sigs msg',
'Exchanged script lock spend tx msg',
'Script tx redeemed',
'Scriptless tx redeemed',
'Scriptless tx recovered'
];
return activeStates.includes(swap.bid_state);
return true;
}
const setupEventListeners = () => {

View File

@@ -25,7 +25,7 @@
<div class="flex items-center">
<p class="text-sm text-gray-90 dark:text-white font-medium">© 2025~ (BSX) BasicSwap</p> <span class="w-1 h-1 mx-1.5 bg-gray-500 dark:bg-white rounded-full"></span>
<p class="text-sm text-coolGray-400 font-medium">BSX: v{{ version }}</p> <span class="w-1 h-1 mx-1.5 bg-gray-500 dark:bg-white rounded-full"></span>
<p class="text-sm text-coolGray-400 font-medium">GUI: v3.2.0</p> <span class="w-1 h-1 mx-1.5 bg-gray-500 dark:bg-white rounded-full"></span>
<p class="text-sm text-coolGray-400 font-medium">GUI: v3.2.1</p> <span class="w-1 h-1 mx-1.5 bg-gray-500 dark:bg-white rounded-full"></span>
<p class="mr-2 text-sm font-bold dark:text-white text-gray-90 ">Made with </p>
{{ love_svg | safe }}
</div>

View File

@@ -12,23 +12,18 @@
<!DOCTYPE html>
<html lang="en">
<head>
<!-- Meta Tags -->
<meta charset="UTF-8">
{% if refresh %}
<meta http-equiv="refresh" content="{{ refresh }}">
{% endif %}
<title>(BSX) BasicSwap - v{{ version }}</title>
<!-- Favicon -->
<link rel="icon" sizes="32x32" type="image/png" href="/static/images/favicon/favicon-32.png">
<!-- Stylesheets -->
<!-- CSS Stylesheets -->>
<link type="text/css" media="all" href="/static/css/libs/flowbite.min.css" rel="stylesheet">
<link type="text/css" media="all" href="/static/css/libs/tailwind.min.css" rel="stylesheet">
<!-- Custom styles -->
<link type="text/css" media="all" href="/static/css/style.css" rel="stylesheet">
<script>
// API Keys Configuration
function getAPIKeys() {
return {
cryptoCompare: "{{ chart_api_key|safe }}",
@@ -36,7 +31,6 @@
};
}
// WebSocket Configuration
(function() {
Object.defineProperty(window, 'ws_port', {
value: "{{ ws_port|safe }}",
@@ -44,16 +38,14 @@
configurable: false,
enumerable: true
});
window.getWebSocketConfig = window.getWebSocketConfig || function() {
return {
port: window.ws_port || '11701',
port: window.ws_port || '11700',
fallbackPort: '11700'
};
};
})();
// Dark Mode Initialization
(function() {
const isDarkMode = localStorage.getItem('color-theme') === 'dark' ||
(!localStorage.getItem('color-theme') && window.matchMedia('(prefers-color-scheme: dark)').matches);
@@ -64,28 +56,23 @@
document.documentElement.classList.toggle('dark', isDarkMode);
})();
</script>
<!-- Third-party Libraries -->
<script src="/static/js/libs/chart.js"></script>
<script src="/static/js/libs/chartjs-adapter-date-fns.bundle.min.js"></script>
<script src="/static/js/libs/popper.js"></script>
<script src="/static/js/libs/tippy.js"></script>
<!-- UI Components -->
<script src="/static/js/ui/tabs.js"></script>
<script src="/static/js/ui/dropdown.js"></script>
<!-- Core Application Modules -->
<!-- Core functionality -->
<script src="/static/js/modules/coin-manager.js"></script>
<script src="/static/js/modules/config-manager.js"></script>
<script src="/static/js/modules/cache-manager.js"></script>
<script src="/static/js/modules/cleanup-manager.js"></script>
<!-- Connection & Communication Modules -->
<script src="/static/js/modules/websocket-manager.js"></script>
<script src="/static/js/modules/network-manager.js"></script>
<script src="/static/js/modules/api-manager.js"></script>
<!-- UI & Interaction Modules -->
<script src="/static/js/modules/price-manager.js"></script>
<script src="/static/js/modules/tooltips-manager.js"></script>
<script src="/static/js/modules/notification-manager.js"></script>
<script src="/static/js/modules/identity-manager.js"></script>
@@ -93,9 +80,9 @@
{% if current_page == 'wallets' or current_page == 'wallet' %}
<script src="/static/js/modules/wallet-manager.js"></script>
{% endif %}
<!-- Memory management -->
<script src="/static/js/modules/memory-manager.js"></script>
<!-- Global Script -->
<!-- Main application script -->
<script src="/static/js/global.js"></script>
</head>
<body class="dark:bg-gray-700">

View File

@@ -343,16 +343,16 @@
{% endif %}
</div>
</th>
<th class="p-0" data-sortable="true" data-column-index="5">
<th class="p-0" data-sortable="true" data-column-index="6">
<div class="py-3 px-4 bg-coolGray-200 dark:bg-gray-600 text-right flex items-center justify-end">
<span class="text-sm text-gray-600 dark:text-gray-300 font-semibold">Rate</span>
<span class="sort-icon ml-1 text-gray-600 dark:text-gray-400" id="sort-icon-5"></span>
<span class="sort-icon ml-1 text-gray-600 dark:text-gray-400" id="sort-icon-6"></span>
</div>
</th>
<th class="p-0" data-sortable="true" data-column-index="6">
<th class="p-0" data-sortable="true" data-column-index="7">
<div class="py-3 bg-coolGray-200 dark:bg-gray-600 text-center flex items-center justify-center">
<span class="text-sm text-gray-600 dark:text-gray-300 font-semibold">Market +/-</span>
<span class="sort-icon ml-1 text-gray-600 dark:text-gray-400" id="sort-icon-6"></span>
<span class="sort-icon ml-1 text-gray-600 dark:text-gray-400" id="sort-icon-7"></span>
</div>
</th>
<th class="p-0">

View File

@@ -1,36 +1,68 @@
{% from 'style.html' import circular_info_messages_svg, green_cross_close_svg, red_cross_close_svg, circular_error_messages_svg %}
<!DOCTYPE html>
<html lang="en">
<head>
<head>
<meta charset="UTF-8">
{% if refresh %}
<meta http-equiv="refresh" content="{{ refresh }}">
{% endif %}
<link type="text/css" media="all" href="/static/css/libs/flowbite.min.css" rel="stylesheet" />
<title>(BSX) BasicSwap - v{{ version }}</title>
<link rel="icon" sizes="32x32" type="image/png" href="/static/images/favicon/favicon-32.png">
<!-- CSS Stylesheets -->>
<link type="text/css" media="all" href="/static/css/libs/flowbite.min.css" rel="stylesheet">
<link type="text/css" media="all" href="/static/css/libs/tailwind.min.css" rel="stylesheet">
<!-- Custom styles -->
<link type="text/css" media="all" href="/static/css/style.css" rel="stylesheet">
<script>
function getAPIKeys() {
return {
cryptoCompare: "{{ chart_api_key|safe }}",
coinGecko: "{{ coingecko_api_key|safe }}"
};
}
(function() {
Object.defineProperty(window, 'ws_port', {
value: "{{ ws_port|safe }}",
writable: false,
configurable: false,
enumerable: true
});
window.getWebSocketConfig = window.getWebSocketConfig || function() {
return {
port: window.ws_port || '11700',
fallbackPort: '11700'
};
};
})();
(function() {
const isDarkMode = localStorage.getItem('color-theme') === 'dark' ||
(!localStorage.getItem('color-theme') && window.matchMedia('(prefers-color-scheme: dark)').matches);
if (!localStorage.getItem('color-theme')) {
localStorage.setItem('color-theme', 'dark');
}
document.documentElement.classList.toggle('dark', isDarkMode);
})();
</script>
<!-- Third-party Libraries -->
<script src="/static/js/libs/chart.js"></script>
<script src="/static/js/libs/chartjs-adapter-date-fns.bundle.min.js"></script>
<script src="/static/js/libs/popper.js"></script>
<script src="/static/js/libs/tippy.js"></script>
<!-- UI Components -->
<script src="/static/js/ui/tabs.js"></script>
<script src="/static/js/ui/dropdown.js"></script>
<!-- Core Application Modules -->
<!-- Core functionality -->
<script src="/static/js/modules/coin-manager.js"></script>
<script src="/static/js/modules/config-manager.js"></script>
<script src="/static/js/modules/cache-manager.js"></script>
<script src="/static/js/modules/cleanup-manager.js"></script>
<!-- Connection & Communication Modules -->
<script src="/static/js/modules/websocket-manager.js"></script>
<script src="/static/js/modules/network-manager.js"></script>
<script src="/static/js/modules/api-manager.js"></script>
<!-- UI & Interaction Modules -->
<script src="/static/js/modules/price-manager.js"></script>
<script src="/static/js/modules/tooltips-manager.js"></script>
<script src="/static/js/modules/notification-manager.js"></script>
<script src="/static/js/modules/identity-manager.js"></script>
@@ -38,8 +70,11 @@
{% if current_page == 'wallets' or current_page == 'wallet' %}
<script src="/static/js/modules/wallet-manager.js"></script>
{% endif %}
<!-- Memory management -->
<script src="/static/js/modules/memory-manager.js"></script>
<!-- Main application script -->
<script src="/static/js/global.js"></script>
</head>
<script>
(function() {
const isDarkMode = localStorage.getItem('color-theme') === 'dark' ||