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:
|
||||
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")
|
||||
|
||||
|
||||
|
||||
@@ -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}`;
|
||||
|
||||
@@ -201,12 +201,23 @@ const ApiManager = (function() {
|
||||
},
|
||||
|
||||
fetchCoinPrices: async function(coins, source = "coingecko.com", ttl = 300) {
|
||||
if (!Array.isArray(coins)) {
|
||||
coins = [coins];
|
||||
if (!coins) {
|
||||
throw new Error('No coins specified for price lookup');
|
||||
}
|
||||
let coinsParam;
|
||||
if (Array.isArray(coins)) {
|
||||
coinsParam = coins.filter(c => c && c.trim() !== '').join(',');
|
||||
} else if (typeof coins === 'object' && coins.coins) {
|
||||
coinsParam = coins.coins;
|
||||
} else {
|
||||
coinsParam = coins;
|
||||
}
|
||||
if (!coinsParam || coinsParam.trim() === '') {
|
||||
throw new Error('No valid coins to fetch prices for');
|
||||
}
|
||||
|
||||
return this.makeRequest('/json/coinprices', 'POST', {}, {
|
||||
coins: Array.isArray(coins) ? coins.join(',') : coins,
|
||||
coins: coinsParam,
|
||||
source: source,
|
||||
ttl: ttl
|
||||
});
|
||||
|
||||
230
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,
|
||||
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, """)
|
||||
.replace(/'/g, "'");
|
||||
},
|
||||
|
||||
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');
|
||||
|
||||
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'
|
||||
};
|
||||
|
||||
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}`);
|
||||
|
||||
@@ -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
@@ -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,8 +986,15 @@ 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();
|
||||
}
|
||||
@@ -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 {
|
||||
@@ -1396,6 +1348,8 @@ const app = {
|
||||
},
|
||||
|
||||
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,49 +1508,19 @@ 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);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
console.log(`Refresh process finished at ${new Date().toLocaleTimeString()}, next refresh scheduled: ${app.isAutoRefreshEnabled ? 'yes' : 'no'}`);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 = () => {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -6,31 +6,63 @@
|
||||
{% 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' ||
|
||||
|
||||
Reference in New Issue
Block a user