mirror of
https://github.com/basicswap/basicswap.git
synced 2025-11-05 18:38:09 +01:00
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:
@@ -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:
|
def js_active(self, url_split, post_string, is_json) -> bytes:
|
||||||
swap_client = self.server.swap_client
|
swap_client = self.server.swap_client
|
||||||
swap_client.checkSystemStatus()
|
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 = []
|
all_bids = []
|
||||||
processed_bid_ids = set()
|
|
||||||
try:
|
try:
|
||||||
received_bids = swap_client.listBids(filters=filters)
|
for bid_id, (bid, offer) in list(swap_client.swaps_in_progress.items()):
|
||||||
sent_bids = swap_client.listBids(sent=True, filters=filters)
|
|
||||||
|
|
||||||
for bid in received_bids + sent_bids:
|
|
||||||
try:
|
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 = {
|
swap_data = {
|
||||||
"bid_id": bid_id_hex,
|
"bid_id": bid_id.hex(),
|
||||||
"offer_id": bid[3].hex(),
|
"offer_id": offer.offer_id.hex(),
|
||||||
"created_at": bid[0],
|
"created_at": bid.created_at,
|
||||||
"bid_state": bid_state,
|
"expire_at": bid.expire_at,
|
||||||
"tx_state_a": tx_state_a if tx_state_a else "None",
|
"bid_state": strBidState(bid.state),
|
||||||
"tx_state_b": tx_state_b if tx_state_b else "None",
|
"tx_state_a": None,
|
||||||
"coin_from": swap_client.ci(bid[9]).coin_name(),
|
"tx_state_b": None,
|
||||||
|
"coin_from": swap_client.ci(offer.coin_from).coin_name(),
|
||||||
"coin_to": swap_client.ci(offer.coin_to).coin_name(),
|
"coin_to": swap_client.ci(offer.coin_to).coin_name(),
|
||||||
"amount_from": swap_client.ci(bid[9]).format_amount(bid[4]),
|
"amount_from": swap_client.ci(offer.coin_from).format_amount(
|
||||||
"amount_to": swap_client.ci(offer.coin_to).format_amount(
|
bid.amount
|
||||||
(bid[4] * bid[10]) // swap_client.ci(bid[9]).COIN()
|
|
||||||
),
|
),
|
||||||
"addr_from": bid[11],
|
"amount_to": swap_client.ci(offer.coin_to).format_amount(
|
||||||
"status": {
|
bid.amount_to
|
||||||
"main": bid_state,
|
),
|
||||||
"initial_tx": tx_state_a if tx_state_a else "None",
|
"addr_from": bid.bid_addr if bid.was_received else offer.addr_from,
|
||||||
"payment_tx": tx_state_b if tx_state_b else "None",
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
all_bids.append(swap_data)
|
||||||
processed_bid_ids.add(bid_id_hex)
|
|
||||||
except Exception:
|
except Exception:
|
||||||
continue
|
pass
|
||||||
except Exception:
|
except Exception:
|
||||||
return bytes(json.dumps([]), "UTF-8")
|
return bytes(json.dumps([]), "UTF-8")
|
||||||
|
|
||||||
return bytes(json.dumps(all_bids), "UTF-8")
|
return bytes(json.dumps(all_bids), "UTF-8")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,20 +1,4 @@
|
|||||||
const PAGE_SIZE = 50;
|
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 = {
|
const state = {
|
||||||
dentities: new Map(),
|
dentities: new Map(),
|
||||||
@@ -288,8 +272,8 @@ const createBidTableRow = async (bid) => {
|
|||||||
const rate = toAmount > 0 ? toAmount / fromAmount : 0;
|
const rate = toAmount > 0 ? toAmount / fromAmount : 0;
|
||||||
const inverseRate = fromAmount > 0 ? fromAmount / toAmount : 0;
|
const inverseRate = fromAmount > 0 ? fromAmount / toAmount : 0;
|
||||||
|
|
||||||
const fromSymbol = COIN_NAME_TO_SYMBOL[bid.coin_from] || bid.coin_from;
|
const fromSymbol = window.CoinManager.getSymbol(bid.coin_from) || bid.coin_from;
|
||||||
const toSymbol = COIN_NAME_TO_SYMBOL[bid.coin_to] || bid.coin_to;
|
const toSymbol = window.CoinManager.getSymbol(bid.coin_to) || bid.coin_to;
|
||||||
|
|
||||||
const timeColor = getTimeStrokeColor(bid.expire_at);
|
const timeColor = getTimeStrokeColor(bid.expire_at);
|
||||||
const uniqueId = `${bid.bid_id}_${bid.created_at}`;
|
const uniqueId = `${bid.bid_id}_${bid.created_at}`;
|
||||||
|
|||||||
@@ -201,12 +201,23 @@ const ApiManager = (function() {
|
|||||||
},
|
},
|
||||||
|
|
||||||
fetchCoinPrices: async function(coins, source = "coingecko.com", ttl = 300) {
|
fetchCoinPrices: async function(coins, source = "coingecko.com", ttl = 300) {
|
||||||
if (!Array.isArray(coins)) {
|
if (!coins) {
|
||||||
coins = [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', {}, {
|
return this.makeRequest('/json/coinprices', 'POST', {}, {
|
||||||
coins: Array.isArray(coins) ? coins.join(',') : coins,
|
coins: coinsParam,
|
||||||
source: source,
|
source: source,
|
||||||
ttl: ttl
|
ttl: ttl
|
||||||
});
|
});
|
||||||
|
|||||||
230
basicswap/static/js/modules/coin-manager.js
Normal file
230
basicswap/static/js/modules/coin-manager.js
Normal 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');
|
||||||
@@ -17,10 +17,8 @@ const ConfigManager = (function() {
|
|||||||
cacheDuration: 10 * 60 * 1000,
|
cacheDuration: 10 * 60 * 1000,
|
||||||
requestTimeout: 60000,
|
requestTimeout: 60000,
|
||||||
wsPort: selectedWsPort,
|
wsPort: selectedWsPort,
|
||||||
|
|
||||||
cacheConfig: {
|
cacheConfig: {
|
||||||
defaultTTL: 10 * 60 * 1000,
|
defaultTTL: 10 * 60 * 1000,
|
||||||
|
|
||||||
ttlSettings: {
|
ttlSettings: {
|
||||||
prices: 5 * 60 * 1000,
|
prices: 5 * 60 * 1000,
|
||||||
chart: 5 * 60 * 1000,
|
chart: 5 * 60 * 1000,
|
||||||
@@ -29,17 +27,13 @@ const ConfigManager = (function() {
|
|||||||
offers: 2 * 60 * 1000,
|
offers: 2 * 60 * 1000,
|
||||||
identity: 15 * 60 * 1000
|
identity: 15 * 60 * 1000
|
||||||
},
|
},
|
||||||
|
|
||||||
storage: {
|
storage: {
|
||||||
maxSizeBytes: 10 * 1024 * 1024,
|
maxSizeBytes: 10 * 1024 * 1024,
|
||||||
maxItems: 200
|
maxItems: 200
|
||||||
},
|
},
|
||||||
|
|
||||||
fallbackTTL: 24 * 60 * 60 * 1000
|
fallbackTTL: 24 * 60 * 60 * 1000
|
||||||
},
|
},
|
||||||
|
|
||||||
itemsPerPage: 50,
|
itemsPerPage: 50,
|
||||||
|
|
||||||
apiEndpoints: {
|
apiEndpoints: {
|
||||||
cryptoCompare: 'https://min-api.cryptocompare.com/data/pricemultifull',
|
cryptoCompare: 'https://min-api.cryptocompare.com/data/pricemultifull',
|
||||||
coinGecko: 'https://api.coingecko.com/api/v3',
|
coinGecko: 'https://api.coingecko.com/api/v3',
|
||||||
@@ -47,7 +41,6 @@ const ConfigManager = (function() {
|
|||||||
cryptoCompareHourly: 'https://min-api.cryptocompare.com/data/v2/histohour',
|
cryptoCompareHourly: 'https://min-api.cryptocompare.com/data/v2/histohour',
|
||||||
volumeEndpoint: 'https://api.coingecko.com/api/v3/simple/price'
|
volumeEndpoint: 'https://api.coingecko.com/api/v3/simple/price'
|
||||||
},
|
},
|
||||||
|
|
||||||
rateLimits: {
|
rateLimits: {
|
||||||
coingecko: {
|
coingecko: {
|
||||||
requestsPerMinute: 50,
|
requestsPerMinute: 50,
|
||||||
@@ -58,10 +51,9 @@ const ConfigManager = (function() {
|
|||||||
minInterval: 2000
|
minInterval: 2000
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
retryDelays: [5000, 15000, 30000],
|
retryDelays: [5000, 15000, 30000],
|
||||||
|
get coins() {
|
||||||
coins: [
|
return window.CoinManager ? window.CoinManager.getAllCoins() : [
|
||||||
{ symbol: 'BTC', name: 'bitcoin', usesCryptoCompare: false, usesCoinGecko: true, historicalDays: 30 },
|
{ symbol: 'BTC', name: 'bitcoin', usesCryptoCompare: false, usesCoinGecko: true, historicalDays: 30 },
|
||||||
{ symbol: 'XMR', name: 'monero', usesCryptoCompare: true, 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: '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: 'DCR', name: 'decred', usesCryptoCompare: true, usesCoinGecko: true, historicalDays: 30 },
|
||||||
{ symbol: 'NMC', name: 'namecoin', 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 }
|
{ 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: {
|
chartConfig: {
|
||||||
colors: {
|
colors: {
|
||||||
default: {
|
default: {
|
||||||
@@ -158,28 +88,22 @@ const ConfigManager = (function() {
|
|||||||
|
|
||||||
const publicAPI = {
|
const publicAPI = {
|
||||||
...defaultConfig,
|
...defaultConfig,
|
||||||
|
|
||||||
initialize: function(options = {}) {
|
initialize: function(options = {}) {
|
||||||
if (state.isInitialized) {
|
if (state.isInitialized) {
|
||||||
console.warn('[ConfigManager] Already initialized');
|
console.warn('[ConfigManager] Already initialized');
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options) {
|
if (options) {
|
||||||
Object.assign(this, options);
|
Object.assign(this, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (window.CleanupManager) {
|
if (window.CleanupManager) {
|
||||||
window.CleanupManager.registerResource('configManager', this, (mgr) => mgr.dispose());
|
window.CleanupManager.registerResource('configManager', this, (mgr) => mgr.dispose());
|
||||||
}
|
}
|
||||||
|
|
||||||
this.utils = utils;
|
this.utils = utils;
|
||||||
|
|
||||||
state.isInitialized = true;
|
state.isInitialized = true;
|
||||||
console.log('ConfigManager initialized');
|
console.log('ConfigManager initialized');
|
||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
|
|
||||||
getAPIKeys: function() {
|
getAPIKeys: function() {
|
||||||
if (typeof window.getAPIKeys === 'function') {
|
if (typeof window.getAPIKeys === 'function') {
|
||||||
const apiKeys = window.getAPIKeys();
|
const apiKeys = window.getAPIKeys();
|
||||||
@@ -188,16 +112,16 @@ const ConfigManager = (function() {
|
|||||||
coinGecko: apiKeys.coinGecko || ''
|
coinGecko: apiKeys.coinGecko || ''
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
cryptoCompare: '',
|
cryptoCompare: '',
|
||||||
coinGecko: ''
|
coinGecko: ''
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
getCoinBackendId: function(coinName) {
|
getCoinBackendId: function(coinName) {
|
||||||
if (!coinName) return null;
|
if (!coinName) return null;
|
||||||
|
if (window.CoinManager) {
|
||||||
|
return window.CoinManager.getPriceKey(coinName);
|
||||||
|
}
|
||||||
const nameMap = {
|
const nameMap = {
|
||||||
'bitcoin-cash': 'bitcoincash',
|
'bitcoin-cash': 'bitcoincash',
|
||||||
'bitcoin cash': 'bitcoincash',
|
'bitcoin cash': 'bitcoincash',
|
||||||
@@ -205,70 +129,57 @@ const ConfigManager = (function() {
|
|||||||
'zcoin': 'firo',
|
'zcoin': 'firo',
|
||||||
'bitcoincash': 'bitcoin-cash'
|
'bitcoincash': 'bitcoin-cash'
|
||||||
};
|
};
|
||||||
|
|
||||||
const lowerCoinName = typeof coinName === 'string' ? coinName.toLowerCase() : '';
|
const lowerCoinName = typeof coinName === 'string' ? coinName.toLowerCase() : '';
|
||||||
return nameMap[lowerCoinName] || lowerCoinName;
|
return nameMap[lowerCoinName] || lowerCoinName;
|
||||||
},
|
},
|
||||||
|
|
||||||
coinMatches: function(offerCoin, filterCoin) {
|
coinMatches: function(offerCoin, filterCoin) {
|
||||||
if (!offerCoin || !filterCoin) return false;
|
if (!offerCoin || !filterCoin) return false;
|
||||||
|
if (window.CoinManager) {
|
||||||
|
return window.CoinManager.coinMatches(offerCoin, filterCoin);
|
||||||
|
}
|
||||||
offerCoin = offerCoin.toLowerCase();
|
offerCoin = offerCoin.toLowerCase();
|
||||||
filterCoin = filterCoin.toLowerCase();
|
filterCoin = filterCoin.toLowerCase();
|
||||||
|
|
||||||
if (offerCoin === filterCoin) return true;
|
if (offerCoin === filterCoin) return true;
|
||||||
|
|
||||||
if ((offerCoin === 'firo' || offerCoin === 'zcoin') &&
|
if ((offerCoin === 'firo' || offerCoin === 'zcoin') &&
|
||||||
(filterCoin === 'firo' || filterCoin === 'zcoin')) {
|
(filterCoin === 'firo' || filterCoin === 'zcoin')) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((offerCoin === 'bitcoincash' && filterCoin === 'bitcoin cash') ||
|
if ((offerCoin === 'bitcoincash' && filterCoin === 'bitcoin cash') ||
|
||||||
(offerCoin === 'bitcoin cash' && filterCoin === 'bitcoincash')) {
|
(offerCoin === 'bitcoin cash' && filterCoin === 'bitcoincash')) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const particlVariants = ['particl', 'particl anon', 'particl blind'];
|
const particlVariants = ['particl', 'particl anon', 'particl blind'];
|
||||||
if (filterCoin === 'particl' && particlVariants.includes(offerCoin)) {
|
if (filterCoin === 'particl' && particlVariants.includes(offerCoin)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (particlVariants.includes(filterCoin)) {
|
if (particlVariants.includes(filterCoin)) {
|
||||||
return offerCoin === filterCoin;
|
return offerCoin === filterCoin;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
|
|
||||||
update: function(path, value) {
|
update: function(path, value) {
|
||||||
const parts = path.split('.');
|
const parts = path.split('.');
|
||||||
let current = this;
|
let current = this;
|
||||||
|
|
||||||
for (let i = 0; i < parts.length - 1; i++) {
|
for (let i = 0; i < parts.length - 1; i++) {
|
||||||
if (!current[parts[i]]) {
|
if (!current[parts[i]]) {
|
||||||
current[parts[i]] = {};
|
current[parts[i]] = {};
|
||||||
}
|
}
|
||||||
current = current[parts[i]];
|
current = current[parts[i]];
|
||||||
}
|
}
|
||||||
|
|
||||||
current[parts[parts.length - 1]] = value;
|
current[parts[parts.length - 1]] = value;
|
||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
|
|
||||||
get: function(path, defaultValue = null) {
|
get: function(path, defaultValue = null) {
|
||||||
const parts = path.split('.');
|
const parts = path.split('.');
|
||||||
let current = this;
|
let current = this;
|
||||||
|
|
||||||
for (let i = 0; i < parts.length; i++) {
|
for (let i = 0; i < parts.length; i++) {
|
||||||
if (current === undefined || current === null) {
|
if (current === undefined || current === null) {
|
||||||
return defaultValue;
|
return defaultValue;
|
||||||
}
|
}
|
||||||
current = current[parts[i]];
|
current = current[parts[i]];
|
||||||
}
|
}
|
||||||
|
|
||||||
return current !== undefined ? current : defaultValue;
|
return current !== undefined ? current : defaultValue;
|
||||||
},
|
},
|
||||||
|
|
||||||
dispose: function() {
|
dispose: function() {
|
||||||
state.isInitialized = false;
|
state.isInitialized = false;
|
||||||
console.log('ConfigManager disposed');
|
console.log('ConfigManager disposed');
|
||||||
@@ -290,7 +201,6 @@ const ConfigManager = (function() {
|
|||||||
return '0';
|
return '0';
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
formatDate: function(timestamp, resolution) {
|
formatDate: function(timestamp, resolution) {
|
||||||
const date = new Date(timestamp);
|
const date = new Date(timestamp);
|
||||||
const options = {
|
const options = {
|
||||||
@@ -300,7 +210,6 @@ const ConfigManager = (function() {
|
|||||||
};
|
};
|
||||||
return date.toLocaleString('en-US', { ...options[resolution], timeZone: 'UTC' });
|
return date.toLocaleString('en-US', { ...options[resolution], timeZone: 'UTC' });
|
||||||
},
|
},
|
||||||
|
|
||||||
debounce: function(func, delay) {
|
debounce: function(func, delay) {
|
||||||
let timeoutId;
|
let timeoutId;
|
||||||
return function(...args) {
|
return function(...args) {
|
||||||
@@ -308,17 +217,14 @@ const ConfigManager = (function() {
|
|||||||
timeoutId = setTimeout(() => func(...args), delay);
|
timeoutId = setTimeout(() => func(...args), delay);
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
formatTimeLeft: function(timestamp) {
|
formatTimeLeft: function(timestamp) {
|
||||||
const now = Math.floor(Date.now() / 1000);
|
const now = Math.floor(Date.now() / 1000);
|
||||||
if (timestamp <= now) return "Expired";
|
if (timestamp <= now) return "Expired";
|
||||||
return this.formatTime(timestamp);
|
return this.formatTime(timestamp);
|
||||||
},
|
},
|
||||||
|
|
||||||
formatTime: function(timestamp, addAgoSuffix = false) {
|
formatTime: function(timestamp, addAgoSuffix = false) {
|
||||||
const now = Math.floor(Date.now() / 1000);
|
const now = Math.floor(Date.now() / 1000);
|
||||||
const diff = Math.abs(now - timestamp);
|
const diff = Math.abs(now - timestamp);
|
||||||
|
|
||||||
let timeString;
|
let timeString;
|
||||||
if (diff < 60) {
|
if (diff < 60) {
|
||||||
timeString = `${diff} seconds`;
|
timeString = `${diff} seconds`;
|
||||||
@@ -333,10 +239,8 @@ const ConfigManager = (function() {
|
|||||||
} else {
|
} else {
|
||||||
timeString = `${Math.floor(diff / 31536000)} years`;
|
timeString = `${Math.floor(diff / 31536000)} years`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return addAgoSuffix ? `${timeString} ago` : timeString;
|
return addAgoSuffix ? `${timeString} ago` : timeString;
|
||||||
},
|
},
|
||||||
|
|
||||||
escapeHtml: function(unsafe) {
|
escapeHtml: function(unsafe) {
|
||||||
if (typeof unsafe !== 'string') {
|
if (typeof unsafe !== 'string') {
|
||||||
console.warn('escapeHtml received a non-string value:', unsafe);
|
console.warn('escapeHtml received a non-string value:', unsafe);
|
||||||
@@ -349,7 +253,6 @@ const ConfigManager = (function() {
|
|||||||
.replace(/"/g, """)
|
.replace(/"/g, """)
|
||||||
.replace(/'/g, "'");
|
.replace(/'/g, "'");
|
||||||
},
|
},
|
||||||
|
|
||||||
formatPrice: function(coin, price) {
|
formatPrice: function(coin, price) {
|
||||||
if (typeof price !== 'number' || isNaN(price)) {
|
if (typeof price !== 'number' || isNaN(price)) {
|
||||||
console.warn(`Invalid price for ${coin}:`, price);
|
console.warn(`Invalid price for ${coin}:`, price);
|
||||||
@@ -363,7 +266,6 @@ const ConfigManager = (function() {
|
|||||||
if (price < 100000) return price.toFixed(1);
|
if (price < 100000) return price.toFixed(1);
|
||||||
return price.toFixed(0);
|
return price.toFixed(0);
|
||||||
},
|
},
|
||||||
|
|
||||||
getEmptyPriceData: function() {
|
getEmptyPriceData: function() {
|
||||||
return {
|
return {
|
||||||
'bitcoin': { usd: null, btc: null },
|
'bitcoin': { usd: null, btc: null },
|
||||||
@@ -381,12 +283,13 @@ const ConfigManager = (function() {
|
|||||||
'firo': { usd: null, btc: null }
|
'firo': { usd: null, btc: null }
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
getCoinSymbol: function(fullName) {
|
getCoinSymbol: function(fullName) {
|
||||||
return publicAPI.coinMappings?.nameToSymbol[fullName] || fullName;
|
if (window.CoinManager) {
|
||||||
|
return window.CoinManager.getSymbol(fullName) || fullName;
|
||||||
|
}
|
||||||
|
return fullName;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return publicAPI;
|
return publicAPI;
|
||||||
})();
|
})();
|
||||||
|
|
||||||
@@ -415,5 +318,4 @@ if (typeof module !== 'undefined') {
|
|||||||
module.exports = ConfigManager;
|
module.exports = ConfigManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
//console.log('ConfigManager initialized with properties:', Object.keys(ConfigManager));
|
|
||||||
console.log('ConfigManager initialized');
|
console.log('ConfigManager initialized');
|
||||||
|
|||||||
233
basicswap/static/js/modules/price-manager.js
Normal file
233
basicswap/static/js/modules/price-manager.js
Normal 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');
|
||||||
@@ -23,54 +23,6 @@ const WalletManager = (function() {
|
|||||||
balancesVisible: 'balancesVisible'
|
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 = {
|
const state = {
|
||||||
lastFetchTime: 0,
|
lastFetchTime: 0,
|
||||||
toggleInProgress: false,
|
toggleInProgress: false,
|
||||||
@@ -83,7 +35,23 @@ const WalletManager = (function() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function getShortName(fullName) {
|
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) {
|
async function fetchPrices(forceUpdate = false) {
|
||||||
@@ -105,16 +73,33 @@ const WalletManager = (function() {
|
|||||||
|
|
||||||
const shouldIncludeWow = currentSource === 'coingecko.com';
|
const shouldIncludeWow = currentSource === 'coingecko.com';
|
||||||
|
|
||||||
const coinsToFetch = Object.values(coinData.symbols)
|
const coinsToFetch = [];
|
||||||
.filter(symbol => shouldIncludeWow || symbol !== 'WOW')
|
const processedCoins = new Set();
|
||||||
.map(symbol => coinData.coingeckoIds[symbol] || symbol.toLowerCase())
|
|
||||||
.join(',');
|
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", {
|
const mainResponse = await fetch("/json/coinprices", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {'Content-Type': 'application/json'},
|
headers: {'Content-Type': 'application/json'},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
coins: coinsToFetch,
|
coins: fetchCoinsString,
|
||||||
source: currentSource,
|
source: currentSource,
|
||||||
ttl: config.defaultTTL
|
ttl: config.defaultTTL
|
||||||
})
|
})
|
||||||
@@ -127,46 +112,27 @@ const WalletManager = (function() {
|
|||||||
const mainData = await mainResponse.json();
|
const mainData = await mainResponse.json();
|
||||||
|
|
||||||
if (mainData && mainData.rates) {
|
if (mainData && mainData.rates) {
|
||||||
Object.entries(mainData.rates).forEach(([coinId, price]) => {
|
document.querySelectorAll('.coinname-value').forEach(el => {
|
||||||
const symbol = Object.entries(coinData.coingeckoIds).find(([sym, id]) => id.toLowerCase() === coinId.toLowerCase())?.[0];
|
const coinName = el.getAttribute('data-coinname');
|
||||||
if (symbol) {
|
if (!coinName) return;
|
||||||
const coinKey = Object.keys(coinData.symbols).find(key => coinData.symbols[key] === symbol);
|
|
||||||
if (coinKey) {
|
const adjustedName = coinName === 'Zcoin' ? 'Firo' :
|
||||||
processedData[coinKey.toLowerCase().replace(' ', '-')] = {
|
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,
|
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);
|
CacheManager.set(state.cacheKey, processedData, config.cacheExpiration);
|
||||||
state.lastFetchTime = now;
|
state.lastFetchTime = now;
|
||||||
return processedData;
|
return processedData;
|
||||||
@@ -202,7 +168,6 @@ const WalletManager = (function() {
|
|||||||
throw lastError || new Error('Failed to fetch prices');
|
throw lastError || new Error('Failed to fetch prices');
|
||||||
}
|
}
|
||||||
|
|
||||||
// UI Management functions
|
|
||||||
function storeOriginalValues() {
|
function storeOriginalValues() {
|
||||||
document.querySelectorAll('.coinname-value').forEach(el => {
|
document.querySelectorAll('.coinname-value').forEach(el => {
|
||||||
const coinName = el.getAttribute('data-coinname');
|
const coinName = el.getAttribute('data-coinname');
|
||||||
@@ -210,10 +175,10 @@ const WalletManager = (function() {
|
|||||||
|
|
||||||
if (coinName) {
|
if (coinName) {
|
||||||
const amount = value ? parseFloat(value.replace(/[^0-9.-]+/g, '')) : 0;
|
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);
|
const shortName = getShortName(coinName);
|
||||||
|
|
||||||
if (coinId) {
|
if (coinSymbol) {
|
||||||
if (coinName === 'Particl') {
|
if (coinName === 'Particl') {
|
||||||
const isBlind = el.closest('.flex')?.querySelector('h4')?.textContent?.includes('Blind');
|
const isBlind = el.closest('.flex')?.querySelector('h4')?.textContent?.includes('Blind');
|
||||||
const isAnon = el.closest('.flex')?.querySelector('h4')?.textContent?.includes('Anon');
|
const isAnon = el.closest('.flex')?.querySelector('h4')?.textContent?.includes('Anon');
|
||||||
@@ -224,7 +189,7 @@ const WalletManager = (function() {
|
|||||||
const balanceType = isMWEB ? 'mweb' : 'public';
|
const balanceType = isMWEB ? 'mweb' : 'public';
|
||||||
localStorage.setItem(`litecoin-${balanceType}-amount`, amount.toString());
|
localStorage.setItem(`litecoin-${balanceType}-amount`, amount.toString());
|
||||||
} else {
|
} else {
|
||||||
localStorage.setItem(`${coinId.toLowerCase()}-amount`, amount.toString());
|
localStorage.setItem(`${coinSymbol.toLowerCase()}-amount`, amount.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
el.setAttribute('data-original-value', `${amount} ${shortName}`);
|
el.setAttribute('data-original-value', `${amount} ${shortName}`);
|
||||||
|
|||||||
@@ -168,6 +168,8 @@ const WebSocketManager = (function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
state.isConnecting = false;
|
state.isConnecting = false;
|
||||||
|
state.messageHandlers = {};
|
||||||
|
|
||||||
|
|
||||||
if (ws) {
|
if (ws) {
|
||||||
ws.onopen = null;
|
ws.onopen = null;
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -118,122 +118,28 @@ const api = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
fetchCoinGeckoDataXHR: async () => {
|
fetchCoinGeckoDataXHR: async () => {
|
||||||
const cacheKey = 'coinGeckoOneLiner';
|
|
||||||
const cachedData = CacheManager.get(cacheKey);
|
|
||||||
|
|
||||||
if (cachedData) {
|
|
||||||
return cachedData.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (!NetworkManager.isOnline()) {
|
const priceData = await window.PriceManager.getPrices();
|
||||||
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 transformedData = {};
|
const transformedData = {};
|
||||||
|
|
||||||
window.config.coins.forEach(coin => {
|
window.config.coins.forEach(coin => {
|
||||||
const coinName = coin.name;
|
|
||||||
const coinRate = apiResponse.rates[coinName];
|
|
||||||
if (coinRate) {
|
|
||||||
const symbol = coin.symbol.toLowerCase();
|
const symbol = coin.symbol.toLowerCase();
|
||||||
|
const coinData = priceData[symbol] || priceData[coin.name.toLowerCase()];
|
||||||
|
|
||||||
|
if (coinData && coinData.usd) {
|
||||||
transformedData[symbol] = {
|
transformedData[symbol] = {
|
||||||
current_price: coinRate,
|
current_price: coinData.usd,
|
||||||
price_btc: coinName === 'bitcoin' ? 1 : coinRate / (apiResponse.rates.bitcoin || 1),
|
price_btc: coinData.btc || (priceData.bitcoin ? coinData.usd / priceData.bitcoin.usd : 0),
|
||||||
total_volume: fallbackData && fallbackData[symbol] ? fallbackData[symbol].total_volume : null,
|
displayName: coin.displayName || coin.symbol
|
||||||
price_change_percentage_24h: fallbackData && fallbackData[symbol] ? fallbackData[symbol].price_change_percentage_24h : null,
|
|
||||||
displayName: coin.displayName || coin.symbol || coinName
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
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;
|
return transformedData;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching coin data:', error);
|
console.error('Error in fetchCoinGeckoDataXHR:', error);
|
||||||
|
return {};
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -617,6 +523,7 @@ const chartModule = {
|
|||||||
currentCoin: 'BTC',
|
currentCoin: 'BTC',
|
||||||
loadStartTime: 0,
|
loadStartTime: 0,
|
||||||
chartRefs: new WeakMap(),
|
chartRefs: new WeakMap(),
|
||||||
|
pendingAnimationFrame: null,
|
||||||
|
|
||||||
verticalLinePlugin: {
|
verticalLinePlugin: {
|
||||||
id: 'verticalLine',
|
id: 'verticalLine',
|
||||||
@@ -650,29 +557,20 @@ const chartModule = {
|
|||||||
destroyChart: function() {
|
destroyChart: function() {
|
||||||
if (chartModule.chart) {
|
if (chartModule.chart) {
|
||||||
try {
|
try {
|
||||||
|
const chartInstance = chartModule.chart;
|
||||||
const canvas = document.getElementById('coin-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;
|
chartModule.chart = null;
|
||||||
|
|
||||||
|
if (chartInstance && chartInstance.destroy && typeof chartInstance.destroy === 'function') {
|
||||||
|
chartInstance.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
if (canvas) {
|
if (canvas) {
|
||||||
chartModule.chartRefs.delete(canvas);
|
chartModule.chartRefs.delete(canvas);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
try {
|
console.error('Error destroying chart:', e);
|
||||||
if (chartModule.chart) {
|
|
||||||
if (chartModule.chart.destroy && typeof chartModule.chart.destroy === 'function') {
|
|
||||||
chartModule.chart.destroy();
|
|
||||||
}
|
|
||||||
chartModule.chart = null;
|
|
||||||
}
|
|
||||||
} catch (finalError) {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -1088,8 +986,15 @@ const chartModule = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
cleanup: function() {
|
cleanup: function() {
|
||||||
this.destroyChart();
|
if (this.pendingAnimationFrame) {
|
||||||
|
cancelAnimationFrame(this.pendingAnimationFrame);
|
||||||
|
this.pendingAnimationFrame = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!document.hidden) {
|
||||||
this.currentCoin = null;
|
this.currentCoin = null;
|
||||||
|
}
|
||||||
|
|
||||||
this.loadStartTime = 0;
|
this.loadStartTime = 0;
|
||||||
this.chartRefs = new WeakMap();
|
this.chartRefs = new WeakMap();
|
||||||
}
|
}
|
||||||
@@ -1152,14 +1057,16 @@ const app = {
|
|||||||
nextRefreshTime: null,
|
nextRefreshTime: null,
|
||||||
lastRefreshedTime: null,
|
lastRefreshedTime: null,
|
||||||
isRefreshing: false,
|
isRefreshing: false,
|
||||||
isAutoRefreshEnabled: localStorage.getItem('autoRefreshEnabled') !== 'false',
|
isAutoRefreshEnabled: localStorage.getItem('autoRefreshEnabled') !== 'true',
|
||||||
|
updateNextRefreshTimeRAF: null,
|
||||||
|
|
||||||
refreshTexts: {
|
refreshTexts: {
|
||||||
label: 'Auto-refresh in',
|
label: 'Auto-refresh in',
|
||||||
disabled: 'Auto-refresh: disabled',
|
disabled: 'Auto-refresh: disabled',
|
||||||
justRefreshed: 'Just refreshed',
|
justRefreshed: 'Just refreshed',
|
||||||
},
|
},
|
||||||
cacheTTL: window.config.cacheConfig.ttlSettings.prices,
|
cacheTTL: window.config.cacheConfig.ttlSettings.prices,
|
||||||
minimumRefreshInterval: 60 * 1000,
|
minimumRefreshInterval: 300 * 1000,
|
||||||
|
|
||||||
init: function() {
|
init: function() {
|
||||||
window.addEventListener('load', app.onLoad);
|
window.addEventListener('load', app.onLoad);
|
||||||
@@ -1329,7 +1236,6 @@ const app = {
|
|||||||
|
|
||||||
const headers = document.querySelectorAll('th');
|
const headers = document.querySelectorAll('th');
|
||||||
headers.forEach((header, index) => {
|
headers.forEach((header, index) => {
|
||||||
CleanupManager.addListener(header, 'click', () => app.sortTable(index, header.classList.contains('disabled')));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const closeErrorButton = document.getElementById('close-error');
|
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() {
|
scheduleNextRefresh: function() {
|
||||||
if (app.autoRefreshInterval) {
|
if (app.autoRefreshInterval) {
|
||||||
clearTimeout(app.autoRefreshInterval);
|
clearTimeout(app.autoRefreshInterval);
|
||||||
@@ -1376,7 +1328,7 @@ const app = {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let nextRefreshTime;
|
let nextRefreshTime = now + app.minimumRefreshInterval;
|
||||||
if (earliestExpiration !== Infinity) {
|
if (earliestExpiration !== Infinity) {
|
||||||
nextRefreshTime = Math.max(earliestExpiration, now + app.minimumRefreshInterval);
|
nextRefreshTime = Math.max(earliestExpiration, now + app.minimumRefreshInterval);
|
||||||
} else {
|
} else {
|
||||||
@@ -1396,6 +1348,8 @@ const app = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
refreshAllData: async function() {
|
refreshAllData: async function() {
|
||||||
|
console.log('Price refresh started at', new Date().toLocaleTimeString());
|
||||||
|
|
||||||
if (app.isRefreshing) {
|
if (app.isRefreshing) {
|
||||||
console.log('Refresh already in progress, skipping...');
|
console.log('Refresh already in progress, skipping...');
|
||||||
return;
|
return;
|
||||||
@@ -1430,6 +1384,7 @@ const app = {
|
|||||||
|
|
||||||
console.log('Starting refresh of all data...');
|
console.log('Starting refresh of all data...');
|
||||||
app.isRefreshing = true;
|
app.isRefreshing = true;
|
||||||
|
app.updateNextRefreshTime();
|
||||||
ui.showLoader();
|
ui.showLoader();
|
||||||
chartModule.showChartLoader();
|
chartModule.showChartLoader();
|
||||||
|
|
||||||
@@ -1494,6 +1449,8 @@ const app = {
|
|||||||
const cacheKey = `coinData_${coin.symbol}`;
|
const cacheKey = `coinData_${coin.symbol}`;
|
||||||
CacheManager.set(cacheKey, coinData, 'prices');
|
CacheManager.set(cacheKey, coinData, 'prices');
|
||||||
|
|
||||||
|
console.log(`Updated price for ${coin.symbol}: $${coinData.current_price}`);
|
||||||
|
|
||||||
} catch (coinError) {
|
} catch (coinError) {
|
||||||
console.warn(`Failed to update ${coin.symbol}: ${coinError.message}`);
|
console.warn(`Failed to update ${coin.symbol}: ${coinError.message}`);
|
||||||
failedCoins.push(coin.symbol);
|
failedCoins.push(coin.symbol);
|
||||||
@@ -1532,8 +1489,7 @@ const app = {
|
|||||||
}
|
}
|
||||||
}, 1000);
|
}, 1000);
|
||||||
}
|
}
|
||||||
|
console.log(`Price refresh completed at ${new Date().toLocaleTimeString()}. Updated ${window.config.coins.length - failedCoins.length}/${window.config.coins.length} coins.`);
|
||||||
console.log(`Refresh completed. Failed coins: ${failedCoins.length}`);
|
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Critical error during refresh:', error);
|
console.error('Critical error during refresh:', error);
|
||||||
@@ -1552,49 +1508,19 @@ const app = {
|
|||||||
}
|
}
|
||||||
}, 1000);
|
}, 1000);
|
||||||
|
|
||||||
|
console.error(`Price refresh failed at ${new Date().toLocaleTimeString()}: ${error.message}`);
|
||||||
|
|
||||||
} finally {
|
} finally {
|
||||||
ui.hideLoader();
|
ui.hideLoader();
|
||||||
chartModule.hideChartLoader();
|
chartModule.hideChartLoader();
|
||||||
app.isRefreshing = false;
|
app.isRefreshing = false;
|
||||||
|
app.updateNextRefreshTime();
|
||||||
|
|
||||||
if (app.isAutoRefreshEnabled) {
|
if (app.isAutoRefreshEnabled) {
|
||||||
app.scheduleNextRefresh();
|
app.scheduleNextRefresh();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
updateNextRefreshTime: function() {
|
console.log(`Refresh process finished at ${new Date().toLocaleTimeString()}, next refresh scheduled: ${app.isAutoRefreshEnabled ? 'yes' : 'no'}`);
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -1649,23 +1575,33 @@ const app = {
|
|||||||
|
|
||||||
updateBTCPrice: async function() {
|
updateBTCPrice: async function() {
|
||||||
try {
|
try {
|
||||||
if (!NetworkManager.isOnline()) {
|
const priceData = await window.PriceManager.getPrices();
|
||||||
throw new Error('Network is offline');
|
|
||||||
|
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 (app.btcPriceUSD > 0) {
|
||||||
|
console.log('Using previously cached BTC price:', app.btcPriceUSD);
|
||||||
if (response && response.rates && response.rates.bitcoin) {
|
|
||||||
app.btcPriceUSD = response.rates.bitcoin;
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.warn('Unexpected BTC price data structure:', response);
|
console.warn('Could not find BTC price in current data');
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching BTC price:', 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;
|
return false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -1815,13 +1751,13 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||||||
|
|
||||||
CleanupManager.setInterval(() => {
|
CleanupManager.setInterval(() => {
|
||||||
CacheManager.cleanup();
|
CacheManager.cleanup();
|
||||||
}, 300000); // Every 5 minutes
|
}, 300000);
|
||||||
|
|
||||||
CleanupManager.setInterval(() => {
|
CleanupManager.setInterval(() => {
|
||||||
if (chartModule && chartModule.currentCoin && NetworkManager.isOnline()) {
|
if (chartModule && chartModule.currentCoin && NetworkManager.isOnline()) {
|
||||||
chartModule.updateChart(chartModule.currentCoin);
|
chartModule.updateChart(chartModule.currentCoin);
|
||||||
}
|
}
|
||||||
}, 900000); // Every 15 minutes
|
}, 900000);
|
||||||
|
|
||||||
CleanupManager.addListener(document, 'visibilitychange', () => {
|
CleanupManager.addListener(document, 'visibilitychange', () => {
|
||||||
if (!document.hidden) {
|
if (!document.hidden) {
|
||||||
|
|||||||
@@ -1,20 +1,4 @@
|
|||||||
const PAGE_SIZE = 50;
|
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 = {
|
const state = {
|
||||||
identities: new Map(),
|
identities: new Map(),
|
||||||
@@ -309,8 +293,8 @@ const createSwapTableRow = async (swap) => {
|
|||||||
|
|
||||||
const identity = await IdentityManager.getIdentityData(swap.addr_from);
|
const identity = await IdentityManager.getIdentityData(swap.addr_from);
|
||||||
const uniqueId = `${swap.bid_id}_${swap.created_at}`;
|
const uniqueId = `${swap.bid_id}_${swap.created_at}`;
|
||||||
const fromSymbol = COIN_NAME_TO_SYMBOL[swap.coin_from] || swap.coin_from;
|
const fromSymbol = window.CoinManager.getSymbol(swap.coin_from) || swap.coin_from;
|
||||||
const toSymbol = COIN_NAME_TO_SYMBOL[swap.coin_to] || swap.coin_to;
|
const toSymbol = window.CoinManager.getSymbol(swap.coin_to) || swap.coin_to;
|
||||||
const timeColor = getTimeStrokeColor(swap.expire_at);
|
const timeColor = getTimeStrokeColor(swap.expire_at);
|
||||||
const fromAmount = parseFloat(swap.amount_from) || 0;
|
const fromAmount = parseFloat(swap.amount_from) || 0;
|
||||||
const toAmount = parseFloat(swap.amount_to) || 0;
|
const toAmount = parseFloat(swap.amount_to) || 0;
|
||||||
@@ -630,31 +614,7 @@ async function updateSwapsTable(options = {}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function isActiveSwap(swap) {
|
function isActiveSwap(swap) {
|
||||||
const activeStates = [
|
return true;
|
||||||
|
|
||||||
'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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const setupEventListeners = () => {
|
const setupEventListeners = () => {
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
<div class="flex items-center">
|
<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-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">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>
|
<p class="mr-2 text-sm font-bold dark:text-white text-gray-90 ">Made with </p>
|
||||||
{{ love_svg | safe }}
|
{{ love_svg | safe }}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -12,23 +12,18 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<!-- Meta Tags -->
|
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
{% if refresh %}
|
{% if refresh %}
|
||||||
<meta http-equiv="refresh" content="{{ refresh }}">
|
<meta http-equiv="refresh" content="{{ refresh }}">
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<title>(BSX) BasicSwap - v{{ version }}</title>
|
<title>(BSX) BasicSwap - v{{ version }}</title>
|
||||||
|
|
||||||
<!-- Favicon -->
|
|
||||||
<link rel="icon" sizes="32x32" type="image/png" href="/static/images/favicon/favicon-32.png">
|
<link rel="icon" sizes="32x32" type="image/png" href="/static/images/favicon/favicon-32.png">
|
||||||
|
<!-- CSS Stylesheets -->>
|
||||||
<!-- 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/flowbite.min.css" rel="stylesheet">
|
||||||
<link type="text/css" media="all" href="/static/css/libs/tailwind.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">
|
<link type="text/css" media="all" href="/static/css/style.css" rel="stylesheet">
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// API Keys Configuration
|
|
||||||
function getAPIKeys() {
|
function getAPIKeys() {
|
||||||
return {
|
return {
|
||||||
cryptoCompare: "{{ chart_api_key|safe }}",
|
cryptoCompare: "{{ chart_api_key|safe }}",
|
||||||
@@ -36,7 +31,6 @@
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// WebSocket Configuration
|
|
||||||
(function() {
|
(function() {
|
||||||
Object.defineProperty(window, 'ws_port', {
|
Object.defineProperty(window, 'ws_port', {
|
||||||
value: "{{ ws_port|safe }}",
|
value: "{{ ws_port|safe }}",
|
||||||
@@ -44,16 +38,14 @@
|
|||||||
configurable: false,
|
configurable: false,
|
||||||
enumerable: true
|
enumerable: true
|
||||||
});
|
});
|
||||||
|
|
||||||
window.getWebSocketConfig = window.getWebSocketConfig || function() {
|
window.getWebSocketConfig = window.getWebSocketConfig || function() {
|
||||||
return {
|
return {
|
||||||
port: window.ws_port || '11701',
|
port: window.ws_port || '11700',
|
||||||
fallbackPort: '11700'
|
fallbackPort: '11700'
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|
||||||
// Dark Mode Initialization
|
|
||||||
(function() {
|
(function() {
|
||||||
const isDarkMode = localStorage.getItem('color-theme') === 'dark' ||
|
const isDarkMode = localStorage.getItem('color-theme') === 'dark' ||
|
||||||
(!localStorage.getItem('color-theme') && window.matchMedia('(prefers-color-scheme: dark)').matches);
|
(!localStorage.getItem('color-theme') && window.matchMedia('(prefers-color-scheme: dark)').matches);
|
||||||
@@ -64,28 +56,23 @@
|
|||||||
document.documentElement.classList.toggle('dark', isDarkMode);
|
document.documentElement.classList.toggle('dark', isDarkMode);
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- Third-party Libraries -->
|
<!-- Third-party Libraries -->
|
||||||
<script src="/static/js/libs/chart.js"></script>
|
<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/chartjs-adapter-date-fns.bundle.min.js"></script>
|
||||||
<script src="/static/js/libs/popper.js"></script>
|
<script src="/static/js/libs/popper.js"></script>
|
||||||
<script src="/static/js/libs/tippy.js"></script>
|
<script src="/static/js/libs/tippy.js"></script>
|
||||||
|
|
||||||
<!-- UI Components -->
|
<!-- UI Components -->
|
||||||
<script src="/static/js/ui/tabs.js"></script>
|
<script src="/static/js/ui/tabs.js"></script>
|
||||||
<script src="/static/js/ui/dropdown.js"></script>
|
<script src="/static/js/ui/dropdown.js"></script>
|
||||||
|
<!-- Core functionality -->
|
||||||
<!-- Core Application Modules -->
|
<script src="/static/js/modules/coin-manager.js"></script>
|
||||||
<script src="/static/js/modules/config-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/cache-manager.js"></script>
|
||||||
<script src="/static/js/modules/cleanup-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/websocket-manager.js"></script>
|
||||||
<script src="/static/js/modules/network-manager.js"></script>
|
<script src="/static/js/modules/network-manager.js"></script>
|
||||||
<script src="/static/js/modules/api-manager.js"></script>
|
<script src="/static/js/modules/api-manager.js"></script>
|
||||||
|
<script src="/static/js/modules/price-manager.js"></script>
|
||||||
<!-- UI & Interaction Modules -->
|
|
||||||
<script src="/static/js/modules/tooltips-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/notification-manager.js"></script>
|
||||||
<script src="/static/js/modules/identity-manager.js"></script>
|
<script src="/static/js/modules/identity-manager.js"></script>
|
||||||
@@ -93,9 +80,9 @@
|
|||||||
{% if current_page == 'wallets' or current_page == 'wallet' %}
|
{% if current_page == 'wallets' or current_page == 'wallet' %}
|
||||||
<script src="/static/js/modules/wallet-manager.js"></script>
|
<script src="/static/js/modules/wallet-manager.js"></script>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
<!-- Memory management -->
|
||||||
<script src="/static/js/modules/memory-manager.js"></script>
|
<script src="/static/js/modules/memory-manager.js"></script>
|
||||||
|
<!-- Main application script -->
|
||||||
<!-- Global Script -->
|
|
||||||
<script src="/static/js/global.js"></script>
|
<script src="/static/js/global.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body class="dark:bg-gray-700">
|
<body class="dark:bg-gray-700">
|
||||||
|
|||||||
@@ -343,16 +343,16 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</th>
|
</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">
|
<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="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>
|
</div>
|
||||||
</th>
|
</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">
|
<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="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>
|
</div>
|
||||||
</th>
|
</th>
|
||||||
<th class="p-0">
|
<th class="p-0">
|
||||||
|
|||||||
@@ -6,31 +6,63 @@
|
|||||||
{% if refresh %}
|
{% if refresh %}
|
||||||
<meta http-equiv="refresh" content="{{ refresh }}">
|
<meta http-equiv="refresh" content="{{ refresh }}">
|
||||||
{% endif %}
|
{% 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">
|
<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">
|
<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 -->
|
<!-- Third-party Libraries -->
|
||||||
<script src="/static/js/libs/chart.js"></script>
|
<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/chartjs-adapter-date-fns.bundle.min.js"></script>
|
||||||
<script src="/static/js/libs/popper.js"></script>
|
<script src="/static/js/libs/popper.js"></script>
|
||||||
<script src="/static/js/libs/tippy.js"></script>
|
<script src="/static/js/libs/tippy.js"></script>
|
||||||
|
|
||||||
<!-- UI Components -->
|
<!-- UI Components -->
|
||||||
<script src="/static/js/ui/tabs.js"></script>
|
<script src="/static/js/ui/tabs.js"></script>
|
||||||
<script src="/static/js/ui/dropdown.js"></script>
|
<script src="/static/js/ui/dropdown.js"></script>
|
||||||
|
<!-- Core functionality -->
|
||||||
<!-- Core Application Modules -->
|
<script src="/static/js/modules/coin-manager.js"></script>
|
||||||
<script src="/static/js/modules/config-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/cache-manager.js"></script>
|
||||||
<script src="/static/js/modules/cleanup-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/websocket-manager.js"></script>
|
||||||
<script src="/static/js/modules/network-manager.js"></script>
|
<script src="/static/js/modules/network-manager.js"></script>
|
||||||
<script src="/static/js/modules/api-manager.js"></script>
|
<script src="/static/js/modules/api-manager.js"></script>
|
||||||
|
<script src="/static/js/modules/price-manager.js"></script>
|
||||||
<!-- UI & Interaction Modules -->
|
|
||||||
<script src="/static/js/modules/tooltips-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/notification-manager.js"></script>
|
||||||
<script src="/static/js/modules/identity-manager.js"></script>
|
<script src="/static/js/modules/identity-manager.js"></script>
|
||||||
@@ -38,8 +70,11 @@
|
|||||||
{% if current_page == 'wallets' or current_page == 'wallet' %}
|
{% if current_page == 'wallets' or current_page == 'wallet' %}
|
||||||
<script src="/static/js/modules/wallet-manager.js"></script>
|
<script src="/static/js/modules/wallet-manager.js"></script>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
<!-- Memory management -->
|
||||||
<script src="/static/js/modules/memory-manager.js"></script>
|
<script src="/static/js/modules/memory-manager.js"></script>
|
||||||
|
<!-- Main application script -->
|
||||||
|
<script src="/static/js/global.js"></script>
|
||||||
|
</head>
|
||||||
<script>
|
<script>
|
||||||
(function() {
|
(function() {
|
||||||
const isDarkMode = localStorage.getItem('color-theme') === 'dark' ||
|
const isDarkMode = localStorage.getItem('color-theme') === 'dark' ||
|
||||||
|
|||||||
Reference in New Issue
Block a user