50 Commits

Author SHA1 Message Date
tecnovert
c945e267e7 Merge pull request #239 from nahuhh/pr/filters
offers: align filters
2025-01-21 19:12:20 +00:00
tecnovert
ef65420978 Merge pull request #235 from nahuhh/pr/wallet
wallet: resposive ui & cleanup
2025-01-21 19:11:56 +00:00
tecnovert
6da4bf6aaf Merge pull request #225 from nahuhh/cores
help: add --upgradecores
2025-01-21 19:11:29 +00:00
tecnovert
6e56b7f421 Merge pull request #224 from nahuhh/pr/minbid
ui: reword min bid -> min purchase
2025-01-21 19:11:13 +00:00
Gerlof van Ek
f084c6f538 JS/UI: Fix scrolling lag / tooltips + Various fixes and cleanup. (#236)
* JS/UI: Fix scrolling lag + Various fixes and cleanup.

* Fix clear button

* JS: Fix when page is hidden, reconnect and proper pause/resume logic.

* JS: Fix tooltips bugs.

* JS: Various fixes.

* JS: Fix fetch system.

* JS: Cleanup
2025-01-21 19:10:52 +00:00
nahuhh
443bd6917f prepare: fix mweb wallet generation (#238)
* prepare: fix mweb wallet generation

* Restore interface_type on LTC MWEB and send it through initialiseWallet.

---------

Co-authored-by: tecnovert <tecnovert@tecnovert.net>
2025-01-21 19:09:04 +00:00
nahuhh
b55d126a0a ui: reword min bid -> min purchase 2025-01-21 13:23:31 +00:00
nahuhh
586ff3288f offers: align filters 2025-01-21 11:53:38 +00:00
nahuhh
0398fce5a8 wallet: responsive 2025-01-20 22:50:35 +00:00
nahuhh
ef082ff7be xmr: remove inaccurate fee rate, hide sweep all checkbox 2025-01-20 22:50:35 +00:00
nahuhh
168284ce25 wallet: cleanup, deduplicate, djlints 2025-01-20 22:50:27 +00:00
Gerlof van Ek
e797e23625 Merge pull request #234 from gerlofvanek/decimals
JS: Decimals
2025-01-18 22:22:28 +01:00
gerlofvanek
d3fcdc8052 JS: Decimals 2025-01-18 22:21:24 +01:00
Gerlof van Ek
2c176a8c86 Merge pull request #232 from gerlofvanek/cleanup-4
JS: Final tweaks 429
2025-01-18 21:06:54 +01:00
gerlofvanek
e92d5560af JS: Final tweaks 429 2025-01-18 20:53:13 +01:00
Gerlof van Ek
0171ad6889 Merge pull request #231 from gerlofvanek/eslint
Fix: Eslint.
2025-01-18 20:38:02 +01:00
gerlofvanek
5d381d4b73 Fix: Eslint. 2025-01-18 20:22:51 +01:00
Gerlof van Ek
9e24d9a12a Merge pull request #230 from nahuhh/pr/overflow
ui: missing character
2025-01-18 18:58:19 +01:00
nahuhh
21ef6f3129 ui: missing character 2025-01-18 17:31:40 +00:00
Gerlof van Ek
67d808cbe4 JS: Enhanced 429 fix + better error handle. Updated refresh button. (#229)
* JS: Enhanced 429 fix + better error handle. Updated refresh button.

* Update offerstable.js
2025-01-18 18:14:49 +01:00
Gerlof van Ek
5d1bed6423 Merge pull request #228 from nahuhh/pr/overflow
ui: responsive offers page
2025-01-18 18:13:47 +01:00
nahuhh
edc11b4c96 ui: offers responsive ui
ui: offers avoid squishing tiles
2025-01-18 16:55:48 +00:00
Gerlof van Ek
5daf591985 Merge pull request #226 from gerlofvanek/cleanup-2
JS: Fix HTTP Error 429
2025-01-17 22:50:39 +01:00
Gerlof van Ek
aee66712b8 Update pricechart.js 2025-01-17 22:14:19 +01:00
Gerlof van Ek
8de365f9d3 Update offerstable.js 2025-01-17 22:13:37 +01:00
gerlofvanek
765ef9571a JS: Fix HTTP Error 429 2025-01-17 20:15:58 +01:00
nahuhh
c575625097 help: add --upgradecores 2025-01-17 14:54:56 +00:00
tecnovert
fe02441619 Merge pull request #223 from gerlofvanek/cleanup-1
JS: Cleanup + Fixes
2025-01-17 10:46:07 +00:00
gerlofvanek
c992ef571a JS: Cleanup + Fixes 2025-01-17 11:34:28 +01:00
tecnovert
5f275132de Merge pull request #218 from basicswap/dependabot/pip/dev/python-gnupg-0.5.4
build(deps): bump python-gnupg from 0.5.3 to 0.5.4
2025-01-17 07:42:13 +00:00
tecnovert
64151f4203 Merge pull request #216 from nahuhh/bch
bch: v28.0.1
2025-01-17 07:39:51 +00:00
tecnovert
734214af53 Merge pull request #222 from nahuhh/pr/overflow
offers: fix overflow bar
2025-01-17 07:39:09 +00:00
tecnovert
1cb8ffb632 Merge pull request #220 from nahuhh/pr/eslint
lint: eslinting suggestions
2025-01-17 07:34:36 +00:00
nahuhh
40d06df325 offers: fix overflow bar 2025-01-17 02:55:20 +00:00
nahuhh
62031173f5 lint: manual eslint 2025-01-16 21:36:08 +00:00
nahuhh
f473d66de5 lint: auto eslint 2025-01-16 21:26:20 +00:00
tecnovert
e548cf2b3b Merge pull request #219 from gerlofvanek/memory
Fix potential sources of mem leaks + Various related fixes.
2025-01-16 19:28:13 +00:00
gerlofvanek
d1baf4bc10 Improve identity fetching + hor/ver bar fix. 2025-01-16 19:14:58 +01:00
gerlofvanek
3b8e084b2e Simplified API requests and remove debug. 2025-01-16 16:34:36 +01:00
gerlofvanek
0a697c61e8 Fix scroll up / down memory increase bug. 2025-01-16 16:26:48 +01:00
gerlofvanek
5af59dd8da Pricechart fix potential mem leaks. 2025-01-16 14:17:46 +01:00
gerlofvanek
a75cd28995 Reduced retry delay. 2025-01-16 13:23:18 +01:00
gerlofvanek
f40d98ef23 Fix API issue with Firo and various small fixes. 2025-01-16 13:08:52 +01:00
gerlofvanek
b14fba0e1f Fix small API bug and better status feedback. 2025-01-16 12:00:50 +01:00
gerlofvanek
4d928dc98e Better error handling API / Tooltips: Rate, Market. 2025-01-16 11:38:02 +01:00
gerlofvanek
1845f802a2 Update cleanup. 2025-01-16 10:25:25 +01:00
gerlofvanek
7ec9dfa35a Fix manual refresh button. 2025-01-16 01:28:10 +01:00
gerlofvanek
b70e46ffc1 Fix potential sources of mem leaks. 2025-01-16 01:17:23 +01:00
dependabot[bot]
07de2d61af build(deps): bump python-gnupg from 0.5.3 to 0.5.4
Bumps [python-gnupg](https://github.com/vsajip/python-gnupg) from 0.5.3 to 0.5.4.
- [Release notes](https://github.com/vsajip/python-gnupg/releases)
- [Changelog](https://github.com/vsajip/python-gnupg/blob/master/release)
- [Commits](https://github.com/vsajip/python-gnupg/compare/0.5.3...0.5.4)

---
updated-dependencies:
- dependency-name: python-gnupg
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-01-15 16:59:52 +00:00
nahuhh
b87e034719 bch: v28.0.1 2025-01-14 12:49:31 +00:00
23 changed files with 2441 additions and 1438 deletions

View File

@@ -1303,16 +1303,16 @@ class BasicSwap(BaseApp):
legacy_root_hash = ci.getSeedHash(root_key, 20)
self.setStringKV(key_str, legacy_root_hash.hex(), cursor)
def initialiseWallet(self, coin_type, raise_errors: bool = False) -> None:
if coin_type == Coins.PART:
def initialiseWallet(self, interface_type, raise_errors: bool = False) -> None:
if interface_type == Coins.PART:
return
ci = self.ci(coin_type)
ci = self.ci(interface_type)
db_key_coin_name = ci.coin_name().lower()
self.log.info("Initialising {} wallet.".format(ci.coin_name()))
if coin_type in (Coins.XMR, Coins.WOW):
key_view = self.getWalletKey(coin_type, 1, for_ed25519=True)
key_spend = self.getWalletKey(coin_type, 2, for_ed25519=True)
if interface_type in (Coins.XMR, Coins.WOW):
key_view = self.getWalletKey(interface_type, 1, for_ed25519=True)
key_spend = self.getWalletKey(interface_type, 2, for_ed25519=True)
ci.initialiseWallet(key_view, key_spend)
root_address = ci.getAddressFromKeys(key_view, key_spend)
@@ -1320,7 +1320,7 @@ class BasicSwap(BaseApp):
self.setStringKV(key_str, root_address)
return
root_key = self.getWalletKey(coin_type, 1)
root_key = self.getWalletKey(interface_type, 1)
try:
ci.initialiseWallet(root_key)
except Exception as e:
@@ -1334,13 +1334,13 @@ class BasicSwap(BaseApp):
try:
cursor = self.openDB()
self.storeSeedIDForCoin(root_key, coin_type, cursor)
self.storeSeedIDForCoin(root_key, interface_type, cursor)
# Clear any saved addresses
self.clearStringKV("receive_addr_" + db_key_coin_name, cursor)
self.clearStringKV("stealth_addr_" + db_key_coin_name, cursor)
coin_id = int(coin_type)
coin_id = int(interface_type)
info_type = 1 # wallet
query_str = "DELETE FROM wallets WHERE coin_id = ? AND balance_type = ?"
cursor.execute(query_str, (coin_id, info_type))

View File

@@ -84,7 +84,7 @@ NAV_VERSION_TAG = os.getenv("NAV_VERSION_TAG", "")
DCR_VERSION = os.getenv("DCR_VERSION", "1.8.1")
DCR_VERSION_TAG = os.getenv("DCR_VERSION_TAG", "")
BITCOINCASH_VERSION = os.getenv("BITCOINCASH_VERSION", "27.1.0")
BITCOINCASH_VERSION = os.getenv("BITCOINCASH_VERSION", "28.0.1")
BITCOINCASH_VERSION_TAG = os.getenv("BITCOINCASH_VERSION_TAG", "")
DOGECOIN_VERSION = os.getenv("DOGECOIN_VERSION", "23.2.1")
@@ -268,10 +268,18 @@ TOR_PROXY_HOST = os.getenv("TOR_PROXY_HOST", "127.0.0.1")
TOR_PROXY_PORT = int(os.getenv("TOR_PROXY_PORT", 9050))
TOR_CONTROL_PORT = int(os.getenv("TOR_CONTROL_PORT", 9051))
TOR_DNS_PORT = int(os.getenv("TOR_DNS_PORT", 5353))
TOR_CONTROL_LISTEN_INTERFACE = os.getenv("TOR_CONTROL_LISTEN_INTERFACE", "127.0.0.1" if BSX_LOCAL_TOR else "0.0.0.0")
TORRC_PROXY_HOST = os.getenv("TORRC_PROXY_HOST", "127.0.0.1" if BSX_LOCAL_TOR else "0.0.0.0")
TORRC_CONTROL_HOST = os.getenv("TORRC_CONTROL_HOST", "127.0.0.1" if BSX_LOCAL_TOR else "0.0.0.0")
TORRC_DNS_HOST = os.getenv("TORRC_DNS_HOST", "127.0.0.1" if BSX_LOCAL_TOR else "0.0.0.0")
TOR_CONTROL_LISTEN_INTERFACE = os.getenv(
"TOR_CONTROL_LISTEN_INTERFACE", "127.0.0.1" if BSX_LOCAL_TOR else "0.0.0.0"
)
TORRC_PROXY_HOST = os.getenv(
"TORRC_PROXY_HOST", "127.0.0.1" if BSX_LOCAL_TOR else "0.0.0.0"
)
TORRC_CONTROL_HOST = os.getenv(
"TORRC_CONTROL_HOST", "127.0.0.1" if BSX_LOCAL_TOR else "0.0.0.0"
)
TORRC_DNS_HOST = os.getenv(
"TORRC_DNS_HOST", "127.0.0.1" if BSX_LOCAL_TOR else "0.0.0.0"
)
TEST_TOR_PROXY = toBool(
os.getenv("TEST_TOR_PROXY", "true")
@@ -1593,6 +1601,9 @@ def printHelp():
print("--withoutcoin= Do not prepare system to run daemon for coin.")
print("--addcoin= Add coin to existing setup.")
print("--disablecoin= Make coin inactive.")
print(
"--upgradecores Upgrade all coin cores present in basicswap.json. Optionally use alongside --withcoin= or --withoutcoin="
)
print("--preparebinonly Don't prepare settings or datadirs.")
print("--nocores Don't download and extract any coin clients.")
print("--usecontainers Expect each core to run in a unique container.")

View File

@@ -137,7 +137,7 @@ class LTCInterfaceMWEB(LTCInterface):
self.rpc_wallet("walletpassphrase", [password, 100000000])
if self.getWalletSeedID() == "Not found":
self._sc.initialiseWallet(self.coin_type())
self._sc.initialiseWallet(self.interface_type())
# Workaround to trigger mweb_spk_man->LoadMWEBKeychain()
self.rpc("unloadwallet", ["mweb"])

View File

@@ -0,0 +1,190 @@
(function(window) {
'use strict';
function positionElement(targetEl, triggerEl, placement = 'bottom', offsetDistance = 8) {
targetEl.style.visibility = 'hidden';
targetEl.style.display = 'block';
const triggerRect = triggerEl.getBoundingClientRect();
const targetRect = targetEl.getBoundingClientRect();
const scrollLeft = window.pageXOffset || document.documentElement.scrollLeft;
const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
let top, left;
top = triggerRect.bottom + offsetDistance;
left = triggerRect.left + (triggerRect.width - targetRect.width) / 2;
switch (placement) {
case 'bottom-start':
left = triggerRect.left;
break;
case 'bottom-end':
left = triggerRect.right - targetRect.width;
break;
}
const viewport = {
width: window.innerWidth,
height: window.innerHeight
};
if (left < 10) left = 10;
if (left + targetRect.width > viewport.width - 10) {
left = viewport.width - targetRect.width - 10;
}
targetEl.style.position = 'fixed';
targetEl.style.top = `${Math.round(top)}px`;
targetEl.style.left = `${Math.round(left)}px`;
targetEl.style.margin = '0';
targetEl.style.maxHeight = `${viewport.height - top - 10}px`;
targetEl.style.overflow = 'auto';
targetEl.style.visibility = 'visible';
}
class Dropdown {
constructor(targetEl, triggerEl, options = {}) {
this._targetEl = targetEl;
this._triggerEl = triggerEl;
this._options = {
placement: options.placement || 'bottom',
offset: options.offset || 5,
onShow: options.onShow || function() {},
onHide: options.onHide || function() {}
};
this._visible = false;
this._initialized = false;
this._handleScroll = this._handleScroll.bind(this);
this._handleResize = this._handleResize.bind(this);
this._handleOutsideClick = this._handleOutsideClick.bind(this);
this.init();
}
init() {
if (!this._initialized) {
this._targetEl.style.margin = '0';
this._targetEl.style.display = 'none';
this._targetEl.style.position = 'fixed';
this._targetEl.style.zIndex = '50';
this._setupEventListeners();
this._initialized = true;
}
}
_setupEventListeners() {
this._triggerEl.addEventListener('click', (e) => {
e.stopPropagation();
this.toggle();
});
document.addEventListener('click', this._handleOutsideClick);
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') this.hide();
});
window.addEventListener('scroll', this._handleScroll, true);
window.addEventListener('resize', this._handleResize);
}
_handleScroll() {
if (this._visible) {
requestAnimationFrame(() => {
positionElement(
this._targetEl,
this._triggerEl,
this._options.placement,
this._options.offset
);
});
}
}
_handleResize() {
if (this._visible) {
requestAnimationFrame(() => {
positionElement(
this._targetEl,
this._triggerEl,
this._options.placement,
this._options.offset
);
});
}
}
_handleOutsideClick(e) {
if (this._visible &&
!this._targetEl.contains(e.target) &&
!this._triggerEl.contains(e.target)) {
this.hide();
}
}
show() {
if (!this._visible) {
this._targetEl.style.display = 'block';
this._targetEl.style.visibility = 'hidden';
requestAnimationFrame(() => {
positionElement(
this._targetEl,
this._triggerEl,
this._options.placement,
this._options.offset
);
this._visible = true;
this._options.onShow();
});
}
}
hide() {
if (this._visible) {
this._targetEl.style.display = 'none';
this._visible = false;
this._options.onHide();
}
}
toggle() {
if (this._visible) {
this.hide();
} else {
this.show();
}
}
destroy() {
document.removeEventListener('click', this._handleOutsideClick);
window.removeEventListener('scroll', this._handleScroll, true);
window.removeEventListener('resize', this._handleResize);
this._initialized = false;
}
}
function initDropdowns() {
document.querySelectorAll('[data-dropdown-toggle]').forEach(triggerEl => {
const targetId = triggerEl.getAttribute('data-dropdown-toggle');
const targetEl = document.getElementById(targetId);
if (targetEl) {
const placement = triggerEl.getAttribute('data-dropdown-placement');
new Dropdown(targetEl, triggerEl, {
placement: placement || 'bottom-start'
});
}
});
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initDropdowns);
} else {
initDropdowns();
}
window.Dropdown = Dropdown;
window.initDropdowns = initDropdowns;
})(window);

File diff suppressed because one or more lines are too long

View File

@@ -19,8 +19,8 @@ document.addEventListener('DOMContentLoaded', function() {
const backdrop = document.querySelectorAll('.navbar-backdrop');
if (close.length) {
for (var i = 0; i < close.length; i++) {
close[i].addEventListener('click', function() {
for (var k = 0; k < close.length; k++) {
close[k].addEventListener('click', function() {
for (var j = 0; j < menu.length; j++) {
menu[j].classList.toggle('hidden');
}
@@ -29,8 +29,8 @@ document.addEventListener('DOMContentLoaded', function() {
}
if (backdrop.length) {
for (var i = 0; i < backdrop.length; i++) {
backdrop[i].addEventListener('click', function() {
for (var l = 0; l < backdrop.length; l++) {
backdrop[l].addEventListener('click', function() {
for (var j = 0; j < menu.length; j++) {
menu[j].classList.toggle('hidden');
}

View File

@@ -1,5 +1,5 @@
window.addEventListener('DOMContentLoaded', (event) => {
let err_msgs = document.querySelectorAll('p.error_msg');
window.addEventListener('DOMContentLoaded', () => {
const err_msgs = document.querySelectorAll('p.error_msg');
for (let i = 0; i < err_msgs.length; i++) {
err_msg = err_msgs[i].innerText;
if (err_msg.indexOf('coin_to') >= 0 || err_msg.indexOf('Coin To') >= 0) {
@@ -29,9 +29,9 @@ window.addEventListener('DOMContentLoaded', (event) => {
}
// remove error class on input or select focus
let inputs = document.querySelectorAll('input.error');
let selects = document.querySelectorAll('select.error');
let elements = [...inputs, ...selects];
const inputs = document.querySelectorAll('input.error');
const selects = document.querySelectorAll('select.error');
const elements = [...inputs, ...selects];
elements.forEach((element) => {
element.addEventListener('focus', (event) => {
event.target.classList.remove('error');

File diff suppressed because it is too large Load Diff

View File

@@ -28,21 +28,32 @@ const config = {
}
},
showVolume: false,
cacheTTL: 5 * 60 * 1000, // 5 minutes
cacheTTL: 10 * 60 * 1000,
specialCoins: [''],
resolutions: {
year: { days: 365, interval: 'month' },
sixMonths: { days: 180, interval: 'daily' },
day: { days: 1, interval: 'hourly' }
},
currentResolution: 'year'
currentResolution: 'year',
requestTimeout: 60000, // 60 sec
retryDelays: [5000, 15000, 30000],
rateLimits: {
coingecko: {
requestsPerMinute: 50,
minInterval: 1200 // 1.2 sec
},
cryptocompare: {
requestsPerMinute: 30,
minInterval: 2000 // 2 sec
}
}
};
// UTILS
const utils = {
formatNumber: (number, decimals = 2) =>
number.toFixed(decimals).replace(/\B(?=(\d{3})+(?!\d))/g, ','),
formatDate: (timestamp, resolution) => {
const date = new Date(timestamp);
const options = {
@@ -77,16 +88,19 @@ const logger = {
error: (message) => console.error(`[AppError] ${new Date().toISOString()}: ${message}`)
};
// API
const api = {
makePostRequest: (url, headers = {}) => {
const apiKeys = getAPIKeys();
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('POST', '/json/readurl');
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.timeout = 30000;
xhr.ontimeout = () => reject(new AppError('Request timed out'));
xhr.timeout = config.requestTimeout;
xhr.ontimeout = () => {
logger.warn(`Request timed out for ${url}`);
reject(new AppError('Request timed out'));
};
xhr.onload = () => {
logger.log(`Response for ${url}:`, xhr.responseText);
if (xhr.status === 200) {
@@ -107,7 +121,12 @@ const api = {
reject(new AppError(`HTTP Error: ${xhr.status} ${xhr.statusText}`, 'HTTPError'));
}
};
xhr.onerror = () => reject(new AppError('Network error occurred', 'NetworkError'));
xhr.onerror = () => {
logger.error(`Network error occurred for ${url}`);
reject(new AppError('Network error occurred', 'NetworkError'));
};
xhr.send(JSON.stringify({
url: url,
headers: headers
@@ -116,44 +135,81 @@ const api = {
},
fetchCryptoCompareDataXHR: (coin) => {
return rateLimiter.queueRequest('cryptocompare', async () => {
const url = `${config.apiEndpoints.cryptoCompare}?fsyms=${coin}&tsyms=USD,BTC&api_key=${config.apiKeys.cryptoCompare}`;
const headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
'Accept-Language': 'en-US,en;q=0.5',
};
return api.makePostRequest(url, headers).catch(error => ({
error: error.message
}));
try {
return await api.makePostRequest(url, headers);
} catch (error) {
logger.error(`CryptoCompare request failed for ${coin}:`, error);
const cachedData = cache.get(`coinData_${coin}`);
if (cachedData) {
logger.info(`Using cached data for ${coin}`);
return cachedData.value;
}
return { error: error.message };
}
});
},
fetchCoinGeckoDataXHR: async () => {
const cacheKey = 'coinGeckoOneLiner';
let cachedData = cache.get(cacheKey);
const cachedData = cache.get(cacheKey);
if (cachedData) {
console.log('Using cached CoinGecko data');
return cachedData.value;
}
return rateLimiter.queueRequest('coingecko', async () => {
try {
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 coinIds = config.coins
.filter(coin => coin.usesCoinGecko)
.map(coin => coin.name)
.join(',');
const url = `${config.apiEndpoints.coinGecko}/simple/price?ids=${coinIds}&vs_currencies=usd,btc&include_24hr_vol=true&include_24hr_change=true&api_key=${config.apiKeys.coinGecko}`;
//console.log(`Fetching data for multiple coins from CoinGecko: ${url}`);
const response = await api.makePostRequest(url, {
'User-Agent': 'Mozilla/5.0',
'Accept': 'application/json',
'Accept-Language': 'en-US,en;q=0.5'
});
try {
const data = await api.makePostRequest(url);
//console.log(`Raw CoinGecko data:`, data);
if (typeof response !== 'object' || response === null) {
if (fallbackData) {
console.log('Using fallback data due to invalid response');
return fallbackData;
}
throw new AppError('Invalid data structure received from CoinGecko');
}
if (typeof data !== 'object' || data === null) {
throw new AppError(`Invalid data structure received from CoinGecko`);
if (response.error || response.Error) {
if (fallbackData) {
console.log('Using fallback data due to API error');
return fallbackData;
}
throw new AppError(response.error || response.Error);
}
const transformedData = {};
Object.entries(data).forEach(([id, values]) => {
Object.entries(response).forEach(([id, values]) => {
const coinConfig = config.coins.find(coin => coin.name === id);
const symbol = coinConfig?.symbol.toLowerCase() || id;
transformedData[symbol] = {
@@ -165,13 +221,21 @@ const api = {
};
});
//console.log(`Transformed CoinGecko data:`, transformedData);
cache.set(cacheKey, transformedData);
return transformedData;
} catch (error) {
//console.error(`Error fetching CoinGecko data:`, error);
return { error: error.message };
console.error('Error fetching CoinGecko data:', error);
const cachedData = cache.get(cacheKey);
if (cachedData) {
console.log('Using expired cache data due to error');
return cachedData.value;
}
throw error;
}
});
},
fetchHistoricalDataXHR: async (coinSymbols) => {
@@ -179,32 +243,36 @@ const api = {
coinSymbols = [coinSymbols];
}
//console.log(`Fetching historical data for coins: ${coinSymbols.join(', ')}`);
const results = {};
const fetchPromises = coinSymbols.map(async coin => {
const coinConfig = config.coins.find(c => c.symbol === coin);
if (!coinConfig) {
//console.error(`Coin configuration not found for ${coin}`);
if (!coinConfig) return;
const cacheKey = `historical_${coin}_${config.currentResolution}`;
const cachedData = cache.get(cacheKey);
if (cachedData) {
results[coin] = cachedData.value;
return;
}
if (coin === 'WOW') {
return rateLimiter.queueRequest('coingecko', async () => {
const url = `${config.apiEndpoints.coinGecko}/coins/wownero/market_chart?vs_currency=usd&days=1&api_key=${config.apiKeys.coinGecko}`;
//console.log(`CoinGecko URL for WOW: ${url}`);
try {
const response = await api.makePostRequest(url);
if (response && response.prices) {
results[coin] = response.prices;
} else {
//console.error(`Unexpected data structure for WOW:`, response);
cache.set(cacheKey, response.prices);
}
} catch (error) {
//console.error(`Error fetching CoinGecko data for WOW:`, error);
console.error(`Error fetching CoinGecko data for WOW:`, error);
if (cachedData) {
results[coin] = cachedData.value;
}
}
});
} else {
return rateLimiter.queueRequest('cryptocompare', async () => {
const resolution = config.resolutions[config.currentResolution];
let url;
if (resolution.interval === 'hourly') {
@@ -213,30 +281,116 @@ const api = {
url = `${config.apiEndpoints.cryptoCompareHistorical}?fsym=${coin}&tsym=USD&limit=${resolution.days}&api_key=${config.apiKeys.cryptoCompare}`;
}
//console.log(`CryptoCompare URL for ${coin}: ${url}`);
try {
const response = await api.makePostRequest(url);
if (response.Response === "Error") {
//console.error(`API Error for ${coin}:`, response.Message);
console.error(`API Error for ${coin}:`, response.Message);
if (cachedData) {
results[coin] = cachedData.value;
}
} else if (response.Data && response.Data.Data) {
results[coin] = response.Data;
} else {
//console.error(`Unexpected data structure for ${coin}:`, response);
cache.set(cacheKey, response.Data);
}
} catch (error) {
//console.error(`Error fetching CryptoCompare data for ${coin}:`, error);
console.error(`Error fetching CryptoCompare data for ${coin}:`, error);
if (cachedData) {
results[coin] = cachedData.value;
}
}
});
}
});
await Promise.all(fetchPromises);
//console.log('Final results object:', JSON.stringify(results, null, 2));
return results;
}
};
const rateLimiter = {
lastRequestTime: {},
minRequestInterval: {
coingecko: config.rateLimits.coingecko.minInterval,
cryptocompare: config.rateLimits.cryptocompare.minInterval
},
requestQueue: {},
retryDelays: config.retryDelays,
canMakeRequest: function(apiName) {
const now = Date.now();
const lastRequest = this.lastRequestTime[apiName] || 0;
return (now - lastRequest) >= this.minRequestInterval[apiName];
},
updateLastRequestTime: function(apiName) {
this.lastRequestTime[apiName] = Date.now();
},
getWaitTime: function(apiName) {
const now = Date.now();
const lastRequest = this.lastRequestTime[apiName] || 0;
return Math.max(0, this.minRequestInterval[apiName] - (now - lastRequest));
},
queueRequest: async function(apiName, requestFn, retryCount = 0) {
if (!this.requestQueue[apiName]) {
this.requestQueue[apiName] = Promise.resolve();
}
try {
await this.requestQueue[apiName];
const executeRequest = async () => {
const waitTime = this.getWaitTime(apiName);
if (waitTime > 0) {
await new Promise(resolve => setTimeout(resolve, waitTime));
}
try {
this.updateLastRequestTime(apiName);
return await requestFn();
} catch (error) {
if (error.message.includes('429') && retryCount < this.retryDelays.length) {
const delay = this.retryDelays[retryCount];
console.log(`Rate limit hit, retrying in ${delay/1000} seconds...`);
await new Promise(resolve => setTimeout(resolve, delay));
return this.queueRequest(apiName, requestFn, retryCount + 1);
}
if ((error.message.includes('timeout') || error.name === 'NetworkError') &&
retryCount < this.retryDelays.length) {
const delay = this.retryDelays[retryCount];
logger.warn(`Request failed, retrying in ${delay/1000} seconds...`, {
apiName,
retryCount,
error: error.message
});
await new Promise(resolve => setTimeout(resolve, delay));
return this.queueRequest(apiName, requestFn, retryCount + 1);
}
throw error;
}
};
this.requestQueue[apiName] = executeRequest();
return await this.requestQueue[apiName];
} catch (error) {
if (error.message.includes('429') ||
error.message.includes('timeout') ||
error.name === 'NetworkError') {
const cachedData = cache.get(`coinData_${apiName}`);
if (cachedData) {
console.log('Using cached data due to request failure');
return cachedData.value;
}
}
throw error;
}
}
};
// CACHE
const cache = {
set: (key, value, customTtl = null) => {
@@ -266,8 +420,8 @@ const cache = {
//console.log(`Cache expired for ${key}`);
localStorage.removeItem(key);
}
} catch (e) {
//console.error('Error parsing cache item:', e);
} catch (error) {
console.error('Error parsing cache item:', error.message);
localStorage.removeItem(key);
}
return null;
@@ -288,7 +442,6 @@ const cache = {
// UI
const ui = {
displayCoinData: (coin, data) => {
const coinConfig = config.coins.find(c => c.symbol === coin);
let priceUSD, priceBTC, priceChange1d, volume24h;
const updateUI = (isError = false) => {
const priceUsdElement = document.querySelector(`#${coin.toLowerCase()}-price-usd`);
@@ -296,28 +449,23 @@ displayCoinData: (coin, data) => {
const volumeElement = document.querySelector(`#${coin.toLowerCase()}-volume-24h`);
const btcPriceDiv = document.querySelector(`#${coin.toLowerCase()}-btc-price-div`);
const priceBtcElement = document.querySelector(`#${coin.toLowerCase()}-price-btc`);
if (priceUsdElement) {
priceUsdElement.textContent = isError ? 'N/A' : `$ ${ui.formatPrice(coin, priceUSD)}`;
}
if (volumeDiv && volumeElement) {
volumeElement.textContent = isError ? 'N/A' : `${utils.formatNumber(volume24h, 0)} USD`;
volumeDiv.style.display = volumeToggle.isVisible ? 'flex' : 'none';
}
if (btcPriceDiv && priceBtcElement) {
if (coin === 'BTC') {
btcPriceDiv.style.display = 'none';
} else {
priceBtcElement.textContent = isError ? 'N/A' : `${priceBTC.toFixed(8)} BTC`;
priceBtcElement.textContent = isError ? 'N/A' : `${priceBTC.toFixed(8)}`;
btcPriceDiv.style.display = 'flex';
}
}
ui.updatePriceChangeContainer(coin, isError ? null : priceChange1d);
};
try {
if (data.error) {
throw new Error(data.error);
@@ -325,20 +473,17 @@ displayCoinData: (coin, data) => {
if (!data || !data.current_price) {
throw new Error(`Invalid CoinGecko data structure for ${coin}`);
}
priceUSD = data.current_price;
priceBTC = coin === 'BTC' ? 1 : data.price_btc || (data.current_price / app.btcPriceUSD);
priceChange1d = data.price_change_percentage_24h;
volume24h = data.total_volume;
if (isNaN(priceUSD) || isNaN(priceBTC) || isNaN(volume24h)) {
throw new Error(`Invalid numeric values in data for ${coin}`);
}
updateUI(false);
} catch (error) {
//console.error(`Error displaying data for ${coin}:`, error.message);
updateUI(true);
logger.error(`Failed to display data for ${coin}:`, error.message);
updateUI(true); // Show error state in UI
}
},
@@ -382,11 +527,9 @@ displayCoinData: (coin, data) => {
updateLoadTimeAndCache: (loadTime, cachedData) => {
const loadTimeElement = document.getElementById('load-time');
const cacheStatusElement = document.getElementById('cache-status');
if (loadTimeElement) {
loadTimeElement.textContent = `Load time: ${loadTime}ms`;
}
if (cacheStatusElement) {
if (cachedData && cachedData.remainingTime) {
const remainingMinutes = Math.ceil(cachedData.remainingTime / 60000);
@@ -399,7 +542,6 @@ displayCoinData: (coin, data) => {
cacheStatusElement.classList.remove('text-green-500');
}
}
ui.updateLastRefreshedTime();
},
@@ -446,8 +588,10 @@ displayCoinData: (coin, data) => {
if (price < 0.000001) return price.toExponential(2);
if (price < 0.001) return price.toFixed(8);
if (price < 1) return price.toFixed(4);
if (price < 10) return price.toFixed(3);
if (price < 1000) return price.toFixed(2);
return price.toFixed(1);
if (price < 100000) return price.toFixed(1);
return price.toFixed(0);
},
setActiveContainer: (containerId) => {
@@ -509,16 +653,14 @@ const chartModule = {
},
initChart: () => {
const ctx = document.getElementById('coin-chart').getContext('2d');
const ctx = document.getElementById('coin-chart')?.getContext('2d');
if (!ctx) {
logger.error('Failed to get chart context. Make sure the canvas element exists.');
return;
}
const gradient = ctx.createLinearGradient(0, 0, 0, 400);
gradient.addColorStop(0, 'rgba(77, 132, 240, 0.2)');
gradient.addColorStop(1, 'rgba(77, 132, 240, 0)');
chartModule.chart = new Chart(ctx, {
type: 'line',
data: {
@@ -565,10 +707,11 @@ const chartModule = {
family: "'Inter', sans-serif"
},
color: 'rgba(156, 163, 175, 1)',
maxRotation: 0,
minRotation: 0,
callback: function(value) {
const date = new Date(value);
if (config.currentResolution === 'day') {
// Convert to AM/PM format
return date.toLocaleTimeString('en-US', {
hour: 'numeric',
minute: '2-digit',
@@ -668,16 +811,11 @@ const chartModule = {
},
plugins: [chartModule.verticalLinePlugin]
});
//console.log('Chart initialized:', chartModule.chart);
},
prepareChartData: (coinSymbol, data) => {
if (!data) {
//console.error(`No data received for ${coinSymbol}`);
return [];
}
try {
let preparedData;
@@ -686,13 +824,10 @@ const chartModule = {
endTime.setUTCMinutes(0, 0, 0);
const endUnix = endTime.getTime();
const startUnix = endUnix - (24 * 3600000);
const hourlyPoints = [];
for (let hourUnix = startUnix; hourUnix <= endUnix; hourUnix += 3600000) {
const targetHour = new Date(hourUnix);
targetHour.setUTCMinutes(0, 0, 0);
const closestPoint = data.reduce((prev, curr) => {
const prevTime = new Date(prev[0]);
const currTime = new Date(curr[0]);
@@ -733,16 +868,14 @@ const chartModule = {
y: price
}));
} else {
//console.error(`Unexpected data structure for ${coinSymbol}:`, data);
return [];
}
return preparedData.map(point => ({
x: new Date(point.x).getTime(),
y: point.y
}));
} catch (error) {
//console.error(`Error preparing chart data for ${coinSymbol}:`, error);
console.error(`Error preparing chart data for ${coinSymbol}:`, error);
return [];
}
},
@@ -759,84 +892,77 @@ const chartModule = {
Math.abs(new Date(curr.x).getTime() - targetTime.getTime()) <
Math.abs(new Date(prev.x).getTime() - targetTime.getTime()) ? curr : prev
);
hourlyData.push({
x: targetTime.getTime(),
y: closestDataPoint.y
});
}
return hourlyData;
},
updateChart: async (coinSymbol, forceRefresh = false) => {
try {
const currentChartData = chartModule.chart?.data.datasets[0].data || [];
if (currentChartData.length === 0) {
chartModule.showChartLoader();
}
chartModule.loadStartTime = Date.now();
const cacheKey = `chartData_${coinSymbol}_${config.currentResolution}`;
let cachedData = !forceRefresh ? cache.get(cacheKey) : null;
let data;
if (cachedData && Object.keys(cachedData.value).length > 0) {
data = cachedData.value;
//console.log(`Using cached data for ${coinSymbol} (${config.currentResolution})`);
console.log(`Using cached data for ${coinSymbol}`);
} else {
//console.log(`Fetching fresh data for ${coinSymbol} (${config.currentResolution})`);
try {
const allData = await api.fetchHistoricalDataXHR([coinSymbol]);
data = allData[coinSymbol];
if (!data || Object.keys(data).length === 0) {
throw new Error(`No data returned for ${coinSymbol}`);
}
//console.log(`Caching new data for ${cacheKey}`);
cache.set(cacheKey, data, config.cacheTTL);
cachedData = null;
} catch (error) {
if (error.message.includes('429') && currentChartData.length > 0) {
console.warn(`Rate limit hit for ${coinSymbol}, maintaining current chart`);
return;
}
const expiredCache = localStorage.getItem(cacheKey);
if (expiredCache) {
try {
const parsedCache = JSON.parse(expiredCache);
data = parsedCache.value;
console.log(`Using expired cache data for ${coinSymbol}`);
} catch (cacheError) {
throw error;
}
} else {
throw error;
}
}
}
const chartData = chartModule.prepareChartData(coinSymbol, data);
//console.log(`Prepared chart data for ${coinSymbol}:`, chartData.slice(0, 5));
if (chartData.length === 0) {
throw new Error(`No valid chart data for ${coinSymbol}`);
}
if (chartModule.chart) {
if (chartData.length > 0 && chartModule.chart) {
chartModule.chart.data.datasets[0].data = chartData;
chartModule.chart.data.datasets[0].label = `${coinSymbol} Price (USD)`;
if (coinSymbol === 'WOW') {
chartModule.chart.options.scales.x.time.unit = 'hour';
chartModule.chart.options.scales.x.ticks.maxTicksLimit = 24;
} else {
const resolution = config.resolutions[config.currentResolution];
chartModule.chart.options.scales.x.time.unit = resolution.interval === 'hourly' ? 'hour' : 'day';
if (config.currentResolution === 'year' || config.currentResolution === 'sixMonths') {
chartModule.chart.options.scales.x.time.unit = 'month';
chartModule.chart.options.scales.x.time.unit =
resolution.interval === 'hourly' ? 'hour' :
config.currentResolution === 'year' ? 'month' : 'day';
}
if (config.currentResolution === 'year') {
chartModule.chart.options.scales.x.ticks.maxTicksLimit = 12;
} else if (config.currentResolution === 'sixMonths') {
chartModule.chart.options.scales.x.ticks.maxTicksLimit = 6;
} else if (config.currentResolution === 'day') {
chartModule.chart.options.scales.x.ticks.maxTicksLimit = 24;
}
}
chartModule.chart.update('active');
} else {
//console.error('Chart object not initialized');
throw new Error('Chart object not initialized');
}
chartModule.currentCoin = coinSymbol;
const loadTime = Date.now() - chartModule.loadStartTime;
ui.updateLoadTimeAndCache(loadTime, cachedData);
}
} catch (error) {
//console.error(`Error updating chart for ${coinSymbol}:`, error);
ui.displayErrorMessage(`Failed to update chart for ${coinSymbol}: ${error.message}`);
console.error(`Error updating chart for ${coinSymbol}:`, error);
if (!(chartModule.chart?.data.datasets[0].data.length > 0)) {
chartModule.chart.data.datasets[0].data = [];
chartModule.chart.update('active');
}
} finally {
chartModule.hideChartLoader();
}
@@ -845,12 +971,10 @@ const chartModule = {
showChartLoader: () => {
const loader = document.getElementById('chart-loader');
const chart = document.getElementById('coin-chart');
if (!loader || !chart) {
//console.warn('Chart loader or chart container elements not found');
return;
}
loader.classList.remove('hidden');
chart.classList.add('hidden');
},
@@ -858,12 +982,10 @@ const chartModule = {
hideChartLoader: () => {
const loader = document.getElementById('chart-loader');
const chart = document.getElementById('coin-chart');
if (!loader || !chart) {
//console.warn('Chart loader or chart container elements not found');
return;
}
loader.classList.add('hidden');
chart.classList.remove('hidden');
},
@@ -916,8 +1038,8 @@ const app = {
disabled: 'Auto-refresh: disabled',
justRefreshed: 'Just refreshed',
},
cacheTTL: 5 * 60 * 1000, // 5 minutes
minimumRefreshInterval: 60 * 1000, // 1 minute
cacheTTL: 5 * 60 * 1000, // 5 min
minimumRefreshInterval: 60 * 1000, // 1 min
init: () => {
console.log('Initializing app...');
@@ -937,8 +1059,6 @@ const app = {
if (chartContainer) {
chartModule.initChart();
chartModule.showChartLoader();
} else {
//console.warn('Chart container not found, skipping chart initialization');
}
console.log('Loading all coin data...');
@@ -948,16 +1068,19 @@ const app = {
config.currentResolution = 'day';
await chartModule.updateChart('BTC');
app.updateResolutionButtons('BTC');
const chartTitle = document.getElementById('chart-title');
if (chartTitle) {
chartTitle.textContent = 'Price Chart (BTC)';
}
}
ui.setActiveContainer('btc-container');
//console.log('Setting up event listeners and initializations...');
app.setupEventListeners();
app.initializeSelectImages();
app.initAutoRefresh();
} catch (error) {
//console.error('Error during initialization:', error);
ui.displayErrorMessage('Failed to initialize the dashboard. Please try refreshing the page.');
} finally {
ui.hideLoader();
@@ -967,7 +1090,6 @@ const app = {
console.log('App onLoad completed');
}
},
loadAllCoinData: async () => {
//console.log('Loading data for all coins...');
try {
@@ -1032,12 +1154,14 @@ const app = {
},
setupEventListeners: () => {
//console.log('Setting up event listeners...');
config.coins.forEach(coin => {
const container = document.getElementById(`${coin.symbol.toLowerCase()}-container`);
if (container) {
container.addEventListener('click', () => {
//console.log(`${coin.symbol} container clicked`);
const chartTitle = document.getElementById('chart-title');
if (chartTitle) {
chartTitle.textContent = `Price Chart (${coin.symbol})`;
}
ui.setActiveContainer(`${coin.symbol.toLowerCase()}-container`);
if (chartModule.chart) {
if (coin.symbol === 'WOW') {
@@ -1112,74 +1236,144 @@ const app = {
} else {
nextRefreshTime = now + config.cacheTTL;
}
const timeUntilRefresh = nextRefreshTime - now;
console.log(`Next refresh scheduled in ${timeUntilRefresh / 1000} seconds`);
app.nextRefreshTime = nextRefreshTime;
app.autoRefreshInterval = setTimeout(() => {
console.log('Auto-refresh triggered');
app.refreshAllData();
}, timeUntilRefresh);
localStorage.setItem('nextRefreshTime', app.nextRefreshTime.toString());
app.updateNextRefreshTime();
},
refreshAllData: async () => {
if (app.isRefreshing) {
console.log('Refresh already in progress, skipping...');
return;
}
console.log('Refreshing all data...');
const lastGeckoRequest = rateLimiter.lastRequestTime['coingecko'] || 0;
const timeSinceLastRequest = Date.now() - lastGeckoRequest;
const waitTime = Math.max(0, rateLimiter.minRequestInterval.coingecko - timeSinceLastRequest);
if (waitTime > 0) {
const seconds = Math.ceil(waitTime / 1000);
ui.displayErrorMessage(`Rate limit: Please wait ${seconds} seconds before refreshing`);
let remainingTime = seconds;
const countdownInterval = setInterval(() => {
remainingTime--;
if (remainingTime > 0) {
ui.displayErrorMessage(`Rate limit: Please wait ${remainingTime} seconds before refreshing`);
} else {
clearInterval(countdownInterval);
ui.hideErrorMessage();
}
}, 1000);
return;
}
console.log('Starting refresh of all data...');
app.isRefreshing = true;
ui.showLoader();
chartModule.showChartLoader();
try {
ui.hideErrorMessage();
cache.clear();
await app.updateBTCPrice();
const btcUpdateSuccess = await app.updateBTCPrice();
if (!btcUpdateSuccess) {
console.warn('BTC price update failed, continuing with cached or default value');
}
await new Promise(resolve => setTimeout(resolve, 1000));
const allCoinData = await api.fetchCoinGeckoDataXHR();
if (allCoinData.error) {
throw new Error(allCoinData.error);
throw new Error(`CoinGecko API Error: ${allCoinData.error}`);
}
const failedCoins = [];
for (const coin of config.coins) {
const symbol = coin.symbol.toLowerCase();
const coinData = allCoinData[symbol];
if (coinData) {
coinData.displayName = coin.displayName || coin.symbol;
try {
if (!coinData) {
throw new Error(`No data received`);
}
coinData.displayName = coin.displayName || coin.symbol;
ui.displayCoinData(coin.symbol, coinData);
const cacheKey = `coinData_${coin.symbol}`;
cache.set(cacheKey, coinData);
} else {
//console.error(`No data found for ${coin.symbol}`);
} catch (coinError) {
console.warn(`Failed to update ${coin.symbol}: ${coinError.message}`);
failedCoins.push(coin.symbol);
}
}
await new Promise(resolve => setTimeout(resolve, 1000));
if (chartModule.currentCoin) {
try {
await chartModule.updateChart(chartModule.currentCoin, true);
} catch (chartError) {
console.error('Chart update failed:', chartError);
}
}
app.lastRefreshedTime = new Date();
localStorage.setItem('lastRefreshedTime', app.lastRefreshedTime.getTime().toString());
ui.updateLastRefreshedTime();
console.log('All data refreshed successfully');
if (failedCoins.length > 0) {
const failureMessage = failedCoins.length === config.coins.length
? 'Failed to update any coin data'
: `Failed to update some coins: ${failedCoins.join(', ')}`;
let countdown = 5;
ui.displayErrorMessage(`${failureMessage} (${countdown}s)`);
const countdownInterval = setInterval(() => {
countdown--;
if (countdown > 0) {
ui.displayErrorMessage(`${failureMessage} (${countdown}s)`);
} else {
clearInterval(countdownInterval);
ui.hideErrorMessage();
}
}, 1000);
}
console.log(`Refresh completed. Failed coins: ${failedCoins.length}`);
} catch (error) {
//console.error('Error refreshing all data:', error);
ui.displayErrorMessage('Failed to refresh all data. Please try again.');
console.error('Critical error during refresh:', error);
let countdown = 10;
ui.displayErrorMessage(`Refresh failed: ${error.message}. Please try again later. (${countdown}s)`);
const countdownInterval = setInterval(() => {
countdown--;
if (countdown > 0) {
ui.displayErrorMessage(`Refresh failed: ${error.message}. Please try again later. (${countdown}s)`);
} else {
clearInterval(countdownInterval);
ui.hideErrorMessage();
}
}, 1000);
} finally {
ui.hideLoader();
chartModule.hideChartLoader();
app.isRefreshing = false;
if (app.isAutoRefreshEnabled) {
app.scheduleNextRefresh();
}
@@ -1191,7 +1385,6 @@ const app = {
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) {
@@ -1278,24 +1471,30 @@ const app = {
},
updateBTCPrice: async () => {
//console.log('Updating BTC price...');
console.log('Updating BTC price...');
try {
const priceData = await api.fetchCoinGeckoDataXHR();
if (priceData.error) {
//console.error('Error fetching BTC price:', priceData.error);
app.btcPriceUSD = 0;
} else if (priceData.btc && priceData.btc.current_price) {
if (priceData.error) {
console.warn('API error when fetching BTC price:', priceData.error);
return false;
}
if (priceData.btc && typeof priceData.btc.current_price === 'number') {
app.btcPriceUSD = priceData.btc.current_price;
} else {
//console.error('Unexpected BTC data structure:', priceData);
app.btcPriceUSD = 0;
return true;
} else if (priceData.bitcoin && typeof priceData.bitcoin.usd === 'number') {
app.btcPriceUSD = priceData.bitcoin.usd;
return true;
}
console.warn('Unexpected BTC price data structure:', priceData);
return false;
} catch (error) {
//console.error('Error fetching BTC price:', error);
app.btcPriceUSD = 0;
console.error('Error fetching BTC price:', error);
return false;
}
//console.log('Current BTC price:', app.btcPriceUSD);
},
sortTable: (columnIndex) => {
@@ -1492,10 +1691,3 @@ resolutionButtons.forEach(button => {
// LOAD
app.init();
document.addEventListener('visibilitychange', () => {
if (!document.hidden && chartModule.chart) {
console.log('Page became visible, reinitializing chart');
chartModule.updateChart(chartModule.currentCoin, true);
}
});

115
basicswap/static/js/tabs.js Normal file
View File

@@ -0,0 +1,115 @@
(function(window) {
'use strict';
class Tabs {
constructor(tabsEl, items = [], options = {}) {
this._tabsEl = tabsEl;
this._items = items;
this._activeTab = options.defaultTabId ? this.getTab(options.defaultTabId) : null;
this._options = {
defaultTabId: options.defaultTabId || null,
activeClasses: options.activeClasses || 'text-blue-600 hover:text-blue-600 dark:text-blue-500 dark:hover:text-blue-500 border-blue-600 dark:border-blue-500',
inactiveClasses: options.inactiveClasses || 'dark:border-transparent text-gray-500 hover:text-gray-600 dark:text-gray-400 border-gray-100 hover:border-gray-300 dark:border-gray-700 dark:hover:text-gray-300',
onShow: options.onShow || function() {}
};
this._initialized = false;
this.init();
}
init() {
if (this._items.length && !this._initialized) {
if (!this._activeTab) {
this.setActiveTab(this._items[0]);
}
this.show(this._activeTab.id, true);
this._items.forEach(tab => {
tab.triggerEl.addEventListener('click', () => {
this.show(tab.id);
});
});
this._initialized = true;
}
}
show(tabId, force = false) {
const tab = this.getTab(tabId);
if ((tab !== this._activeTab) || force) {
this._items.forEach(t => {
if (t !== tab) {
t.triggerEl.classList.remove(...this._options.activeClasses.split(' '));
t.triggerEl.classList.add(...this._options.inactiveClasses.split(' '));
t.targetEl.classList.add('hidden');
t.triggerEl.setAttribute('aria-selected', false);
}
});
tab.triggerEl.classList.add(...this._options.activeClasses.split(' '));
tab.triggerEl.classList.remove(...this._options.inactiveClasses.split(' '));
tab.triggerEl.setAttribute('aria-selected', true);
tab.targetEl.classList.remove('hidden');
this.setActiveTab(tab);
this._options.onShow(this, tab);
}
}
getTab(id) {
return this._items.find(t => t.id === id);
}
getActiveTab() {
return this._activeTab;
}
setActiveTab(tab) {
this._activeTab = tab;
}
}
function initTabs() {
document.querySelectorAll('[data-tabs-toggle]').forEach(tabsEl => {
const items = [];
let defaultTabId = null;
tabsEl.querySelectorAll('[role="tab"]').forEach(triggerEl => {
const isActive = triggerEl.getAttribute('aria-selected') === 'true';
const tab = {
id: triggerEl.getAttribute('data-tabs-target'),
triggerEl: triggerEl,
targetEl: document.querySelector(triggerEl.getAttribute('data-tabs-target'))
};
items.push(tab);
if (isActive) {
defaultTabId = tab.id;
}
});
new Tabs(tabsEl, items, {
defaultTabId: defaultTabId
});
});
}
const style = document.createElement('style');
style.textContent = `
[data-tabs-toggle] [role="tab"] {
cursor: pointer;
}
`;
document.head.appendChild(style);
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initTabs);
} else {
initTabs();
}
window.Tabs = Tabs;
window.initTabs = initTabs;
})(window);

View File

@@ -0,0 +1,309 @@
(function(window) {
'use strict';
const tooltipContainer = document.createElement('div');
tooltipContainer.className = 'tooltip-container';
const style = document.createElement('style');
style.textContent = `
[role="tooltip"] {
position: absolute;
z-index: 9999;
transition: opacity 0.2s ease-in-out;
pointer-events: auto;
opacity: 0;
visibility: hidden;
}
.tooltip-container {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 0;
overflow: visible;
pointer-events: none;
z-index: 9999;
}
`;
function ensureContainerExists() {
if (!document.body.contains(tooltipContainer)) {
document.body.appendChild(tooltipContainer);
}
}
function rafThrottle(callback) {
let requestId = null;
let lastArgs = null;
const later = (context) => {
requestId = null;
callback.apply(context, lastArgs);
};
return function(...args) {
lastArgs = args;
if (requestId === null) {
requestId = requestAnimationFrame(() => later(this));
}
};
}
function positionElement(targetEl, triggerEl, placement = 'top', offsetDistance = 8) {
const triggerRect = triggerEl.getBoundingClientRect();
const targetRect = targetEl.getBoundingClientRect();
let top, left;
switch (placement) {
case 'top':
top = triggerRect.top - targetRect.height - offsetDistance;
left = triggerRect.left + (triggerRect.width - targetRect.width) / 2;
break;
case 'bottom':
top = triggerRect.bottom + offsetDistance;
left = triggerRect.left + (triggerRect.width - targetRect.width) / 2;
break;
case 'left':
top = triggerRect.top + (triggerRect.height - targetRect.height) / 2;
left = triggerRect.left - targetRect.width - offsetDistance;
break;
case 'right':
top = triggerRect.top + (triggerRect.height - targetRect.height) / 2;
left = triggerRect.right + offsetDistance;
break;
}
const viewport = {
width: window.innerWidth,
height: window.innerHeight
};
if (left < 0) left = 0;
if (top < 0) top = 0;
if (left + targetRect.width > viewport.width)
left = viewport.width - targetRect.width;
if (top + targetRect.height > viewport.height)
top = viewport.height - targetRect.height;
targetEl.style.transform = `translate(${Math.round(left)}px, ${Math.round(top)}px)`;
}
const tooltips = new WeakMap();
class Tooltip {
constructor(targetEl, triggerEl, options = {}) {
ensureContainerExists();
this._targetEl = targetEl;
this._triggerEl = triggerEl;
this._options = {
placement: options.placement || 'top',
triggerType: options.triggerType || 'hover',
offset: options.offset || 8,
onShow: options.onShow || function() {},
onHide: options.onHide || function() {}
};
this._visible = false;
this._initialized = false;
this._hideTimeout = null;
this._showTimeout = null;
if (this._targetEl.parentNode !== tooltipContainer) {
tooltipContainer.appendChild(this._targetEl);
}
this._targetEl.style.visibility = 'hidden';
this._targetEl.style.opacity = '0';
this._showHandler = this.show.bind(this);
this._hideHandler = this._handleHide.bind(this);
this._updatePosition = rafThrottle(() => {
if (this._visible) {
positionElement(
this._targetEl,
this._triggerEl,
this._options.placement,
this._options.offset
);
}
});
this.init();
}
init() {
if (!this._initialized) {
this._setupEventListeners();
this._initialized = true;
positionElement(
this._targetEl,
this._triggerEl,
this._options.placement,
this._options.offset
);
}
}
_setupEventListeners() {
this._triggerEl.addEventListener('mouseenter', this._showHandler);
this._triggerEl.addEventListener('mouseleave', this._hideHandler);
this._triggerEl.addEventListener('focus', this._showHandler);
this._triggerEl.addEventListener('blur', this._hideHandler);
this._targetEl.addEventListener('mouseenter', () => {
clearTimeout(this._hideTimeout);
clearTimeout(this._showTimeout);
this._visible = true;
this._targetEl.style.visibility = 'visible';
this._targetEl.style.opacity = '1';
});
this._targetEl.addEventListener('mouseleave', this._hideHandler);
if (this._options.triggerType === 'click') {
this._triggerEl.addEventListener('click', this._showHandler);
}
window.addEventListener('scroll', this._updatePosition, { passive: true });
document.addEventListener('scroll', this._updatePosition, { passive: true, capture: true });
window.addEventListener('resize', this._updatePosition, { passive: true });
let rafId;
const smoothUpdate = () => {
if (this._visible) {
this._updatePosition();
rafId = requestAnimationFrame(smoothUpdate);
}
};
this._startSmoothUpdate = () => {
if (!rafId) rafId = requestAnimationFrame(smoothUpdate);
};
this._stopSmoothUpdate = () => {
if (rafId) {
cancelAnimationFrame(rafId);
rafId = null;
}
};
}
_handleHide() {
clearTimeout(this._hideTimeout);
clearTimeout(this._showTimeout);
this._hideTimeout = setTimeout(() => {
if (this._visible) {
this.hide();
}
}, 100);
}
show() {
clearTimeout(this._hideTimeout);
clearTimeout(this._showTimeout);
this._showTimeout = setTimeout(() => {
if (!this._visible) {
positionElement(
this._targetEl,
this._triggerEl,
this._options.placement,
this._options.offset
);
this._targetEl.style.visibility = 'visible';
this._targetEl.style.opacity = '1';
this._visible = true;
this._startSmoothUpdate();
this._options.onShow();
}
}, 20);
}
hide() {
this._targetEl.style.opacity = '0';
this._targetEl.style.visibility = 'hidden';
this._visible = false;
this._stopSmoothUpdate();
this._options.onHide();
}
destroy() {
clearTimeout(this._hideTimeout);
clearTimeout(this._showTimeout);
this._stopSmoothUpdate();
this._triggerEl.removeEventListener('mouseenter', this._showHandler);
this._triggerEl.removeEventListener('mouseleave', this._hideHandler);
this._triggerEl.removeEventListener('focus', this._showHandler);
this._triggerEl.removeEventListener('blur', this._hideHandler);
this._targetEl.removeEventListener('mouseenter', this._showHandler);
this._targetEl.removeEventListener('mouseleave', this._hideHandler);
if (this._options.triggerType === 'click') {
this._triggerEl.removeEventListener('click', this._showHandler);
}
window.removeEventListener('scroll', this._updatePosition);
document.removeEventListener('scroll', this._updatePosition, true);
window.removeEventListener('resize', this._updatePosition);
this._targetEl.style.visibility = '';
this._targetEl.style.opacity = '';
this._targetEl.style.transform = '';
if (this._targetEl.parentNode === tooltipContainer) {
document.body.appendChild(this._targetEl);
}
this._initialized = false;
}
toggle() {
if (this._visible) {
this.hide();
} else {
this.show();
}
}
}
document.head.appendChild(style);
function initTooltips() {
ensureContainerExists();
document.querySelectorAll('[data-tooltip-target]').forEach(triggerEl => {
if (tooltips.has(triggerEl)) return;
const targetId = triggerEl.getAttribute('data-tooltip-target');
const targetEl = document.getElementById(targetId);
if (targetEl) {
const placement = triggerEl.getAttribute('data-tooltip-placement');
const triggerType = triggerEl.getAttribute('data-tooltip-trigger');
const tooltip = new Tooltip(targetEl, triggerEl, {
placement: placement || 'top',
triggerType: triggerType || 'hover',
offset: 8
});
tooltips.set(triggerEl, tooltip);
}
});
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initTooltips);
} else {
initTooltips();
}
window.Tooltip = Tooltip;
window.initTooltips = initTooltips;
})(window);

View File

@@ -8,11 +8,16 @@
{% endif %}
<script src="/static/js/libs/chart.js"></script>
<script src="/static/js/libs/chartjs-adapter-date-fns.bundle.min.js"></script>
<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/style.css" rel="stylesheet">
<script src="/static/js/main.js"></script>
<script src="/static/js/libs/flowbite.js"></script>
<script src="/static/js/tabs.js"></script>
<script src="/static/js/dropdown.js"></script>
<script src="/static/js/tooltips.js"></script>
<script>
const isDarkMode =
@@ -451,7 +456,6 @@ document.addEventListener('DOMContentLoaded', function() {
<div id="tooltip-your-offers" role="tooltip" class="inline-block absolute invisible z-10 py-2 px-3 text-sm font-medium text-white bg-blue-500 rounded-lg shadow-sm opacity-0 transition-opacity duration-300 tooltip">
<p><b>Total:</b> {{ summary.num_sent_offers }}</p>
<div class="tooltip-arrow" data-popper-arrow></div>
</div>
</li>
@@ -468,7 +472,7 @@ document.addEventListener('DOMContentLoaded', function() {
<div id="tooltip-bids-received" role="tooltip" class="inline-block absolute invisible z-10 py-2 px-3 text-sm font-medium text-white bg-blue-500 rounded-lg shadow-sm opacity-0 transition-opacity duration-300 tooltip">
<p><b>Total:</b> {{ summary.num_recv_bids }}</p>
<div class="tooltip-arrow" data-popper-arrow></div>
</div>
<li>
<a data-tooltip-target="tooltip-bids-sent" class="flex mr-10 items-center text-sm text-gray-400 hover:text-gray-600 dark:text-gray-100 dark:hover:text-gray-100" href="/sentbids">
@@ -476,7 +480,7 @@ document.addEventListener('DOMContentLoaded', function() {
<span>Bids Sent</span><span class="inline-flex justify-center items-center text-xs font-semibold ml-3 mr-2 px-2.5 py-1 font-small text-white bg-blue-500 rounded-full">{{ summary.num_sent_active_bids }}</span></a>
<div id="tooltip-bids-sent" role="tooltip" class="inline-block absolute invisible z-10 py-2 px-3 text-sm font-medium text-white bg-blue-500 rounded-lg shadow-sm opacity-0 transition-opacity duration-300 tooltip">
<p><b>Total:</b> {{ summary.num_sent_bids }}</p>
<div class="tooltip-arrow" data-popper-arrow></div>
</div>
</li>

View File

@@ -137,7 +137,7 @@
<td class="py-3 px-6 bold">{{ data.amt_to }} {{ data.tla_to }}</td>
</tr>
<tr class="opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600">
<td class="py-3 px-6 bold">Minimum Bid Amount</td>
<td class="py-3 px-6 bold">Minimum Purchase</td>
<td class="py-3 px-6">{{ data.amt_bid_min }} {{ data.tla_from }}</td>
</tr>
<tr class="opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600">
@@ -747,40 +747,24 @@ function resetForm() {
const bidAmountInput = document.getElementById('bid_amount');
const bidRateInput = document.getElementById('bid_rate');
const validMinsInput = document.querySelector('input[name="validmins"]');
const addrFromSelect = document.querySelector('select[name="addr_from"]');
const amtVar = document.getElementById('amt_var')?.value === 'True';
if (bidAmountSendInput) {
const defaultSendAmount = bidAmountSendInput.getAttribute('max');
bidAmountSendInput.value = defaultSendAmount;
bidAmountSendInput.value = amtVar ? '' : bidAmountSendInput.getAttribute('max');
}
if (bidAmountInput) {
const defaultReceiveAmount = bidAmountInput.getAttribute('max');
bidAmountInput.value = defaultReceiveAmount;
bidAmountInput.value = amtVar ? '' : bidAmountInput.getAttribute('max');
}
if (bidRateInput && !bidRateInput.disabled) {
const defaultRate = document.getElementById('offer_rate')?.value || '';
bidRateInput.value = defaultRate;
}
if (validMinsInput) {
validMinsInput.value = "60";
}
if (addrFromSelect) {
if (addrFromSelect.options.length > 1) {
addrFromSelect.selectedIndex = 1;
} else {
addrFromSelect.selectedIndex = 0;
}
const selectedOption = addrFromSelect.options[addrFromSelect.selectedIndex];
saveAddress(selectedOption.value, selectedOption.text);
}
if (!amtVar) {
updateBidParams('rate');
}
updateModalValues();
const errorMessages = document.querySelectorAll('.error-message');
errorMessages.forEach(msg => msg.remove());

View File

@@ -293,7 +293,7 @@
<div class="w-full md:flex-1 p-3">
<div class="flex flex-wrap -m-3">
<div class="w-full md:w-1/2 p-3">
<p class="mb-1.5 font-medium text-base text-coolGray-800 dark:text-white">Minimum Bid Amount</p>
<p class="mb-1.5 font-medium text-base text-coolGray-800 dark:text-white">Minimum Purchase</p>
<div class="relative">
<div class="flex absolute inset-y-0 left-0 items-center pl-3 pointer-events-none">
{{ select_bid_amount_svg | safe }}

View File

@@ -274,7 +274,7 @@ if (document.readyState === 'loading') {
<div class="w-full md:flex-1 p-3">
<div class="flex flex-wrap -m-3">
<div class="w-full md:w-1/2 p-3">
<p class="mb-1.5 font-medium text-base dark:text-white text-coolGray-800">Minimum Bid Amount</p>
<p class="mb-1.5 font-medium text-base dark:text-white text-coolGray-800">Minimum Purchase</p>
<div class="relative">
<div class="flex absolute inset-y-0 left-0 items-center pl-3 pointer-events-none">
{{ select_bid_amount_svg | safe }}

View File

@@ -279,7 +279,7 @@
<div class="w-full md:flex-1 p-3">
<div class="flex flex-wrap -m-3">
<div class="w-full md:w-1/2 p-3">
<p class="mb-1.5 font-medium text-base text-coolGray-800 dark:text-white">Minimum Bid Amount</p>
<p class="mb-1.5 font-medium text-base text-coolGray-800 dark:text-white">Minimum Purchase</p>
<div class="relative">
<div class="flex absolute inset-y-0 left-0 items-center pl-3 pointer-events-none">
{{ select_bid_amount_svg | safe }}

View File

@@ -25,11 +25,11 @@ function getWebSocketConfig() {
</script>
{% if sent_offers %}
<div class="container mx-auto">
<section class="p-5 mt-5">
<div class="flex flex-wrap items-center -m-2">
<div class="w-full md:w-1/2 p-2">
<ul class="flex flex-wrap items-center gap-x-3 mb-2">
<div class="lg:container mx-auto">
<section class="p-3 mt-2">
<div class="flex items-center">
<div class="w-full">
<ul class="flex items-center gap-x-2 mb-1">
<li><a class="flex font-medium text-xs text-coolGray-500 dark:text-gray-300 hover:text-coolGray-700" href="/">Home</a></li>
<li>{{ breadcrumb_line_svg | safe }}</li>
<li>
@@ -46,21 +46,21 @@ function getWebSocketConfig() {
{% endif %}
{% if sent_offers %}
<section class="py-5">
<section class="py-3">
{% else %}
<section class="py-5 mt-5">
<section class="py-3 px-4 mt-3">
{% endif %}
<div class="container px-4 mx-auto">
<div class="relative py-11 px-16 bg-coolGray-900 dark:bg-gray-500 rounded-md overflow-hidden">
<img class="absolute z-10 left-4 top-4 right-4 bottom-4" src="/static/images/elements/dots-red.svg" alt="dots-red">
<img class="absolute h-64 left-1/2 top-1/2 transform -translate-x-1/2 -translate-y-1/2 object-cover" src="/static/images/elements/wave.svg" alt="wave">
<div class="relative z-20 flex flex-wrap items-center -m-3">
<div class="w-full md:w-1/2 p-3">
<h2 class="mb-6 text-4xl font-bold text-white tracking-tighter">{{ page_type }}</h2>
<div class="lg:container mx-auto">
<div class="relative py-6 px-8 bg-coolGray-900 dark:bg-gray-500 rounded-md overflow-hidden">
<img class="absolute h-48 left-1/2 top-1/2 transform -translate-x-1/2 -translate-y-1/2 object-cover" src="/static/images/elements/wave.svg" alt="wave">
<div class="relative z-20 flex flex-wrap items-center -m-2">
<div class="w-full md:w-1/2 p-2">
<h2 class="mb-3 text-3xl font-bold text-white tracking-tighter">{{ page_type }}</h2>
<p class="font-normal text-coolGray-200 dark:text-white">{{ page_type_description }}</p>
</div>
<div class="rounded-full{{ page_button }} w-full md:w-1/2 p-3 p-6 container flex flex-wrap items-center justify-end items-center mx-auto">
<a id="refresh" href="/newoffer" class="rounded-full flex flex-wrap justify-center px-5 py-3 bg-blue-500 hover:bg-green-600 hover:border-green-600 font-medium text-sm text-white border border-blue-500 rounded-md focus:ring-0 focus:outline-none">{{ place_new_offer_svg | safe }}<span>Place new Offer</span></a>
<div class="rounded-full{{ page_button }} w-full md:w-1/2 p-2 lg:container flex flex-wrap items-center justify-end mx-auto">
<a id="refresh" href="/newoffer" class="rounded-full flex flex-wrap justify-center px-4 py-2 bg-blue-500 hover:bg-green-600 hover:border-green-600 font-medium text-sm text-white border border-blue-500 rounded-md focus:ring-0 focus:outline-none">{{ place_new_offer_svg | safe }}<span>Place new Offer</span></a>
</div>
</div>
</div>
@@ -71,11 +71,11 @@ function getWebSocketConfig() {
{% if show_chart %}
<section class="relative hidden md:block">
<div class="pl-6 pr-6 pt-0 pb-0 mt-5 h-full overflow-hidden">
<div class="px-6 py-0 mt-5 h-full overflow-hidden">
<div class="pb-6 border-coolGray-100">
<div class="flex flex-wrap items-center justify-between -m-2">
<div class="flex flex-wrap items-center justify-between">
<div class="w-full pt-2">
<div class="container px-4 mx-auto">
<div class="lg:container mx-auto">
<div class="pt-6 pb-8 bg-coolGray-100 dark:bg-gray-500 rounded-xl container-to-blur">
<div class="flex justify-between items-center mb-4 mr-10 ml-10">
<div class="flex items-center justify-between">
@@ -90,36 +90,28 @@ function getWebSocketConfig() {
<span id="load-time hidden" class="mr-2 text-sm text-gray-600 dark:text-gray-300"></span>
<span id="last-refreshed-time hidden" class="mr-2 text-sm text-gray-600 dark:text-gray-300"></span>
<span id="cache-status hidden" class="mr-4 text-sm text-gray-600 dark:text-gray-300"></span>
<span id="tor-status" class="mr-4 text-sm hidden {% if tor_established %}text-green-500{% else %}text-red-500{% endif %}"> Tor {% if tor_established %}ON{% else %}OFF{% endif %}
<span id="tor-status" class="mr-4 text-sm hidden {% if tor_established %}text-green-500{% else %}text-red-500{% endif %}">
Tor {% if tor_established %}ON{% else %}OFF{% endif %}
<a href="https://academy.particl.io/en/latest/basicswap-guides/basicswapguides_tor.html" target="_blank" rel="noopener noreferrer" class="underline">(?)</a>
</span>
<span id="next-refresh-time" class="mr-4 text-sm text-gray-600 dark:text-gray-300">
<span id="next-refresh-label"></span>
<span id="next-refresh-value"></span>
</span>
<div id="tooltip-volume" role="tooltip" class="inline-block absolute invisible z-10 py-2 px-3 text-sm font-medium text-white bg-blue-500 rounded-lg shadow-sm opacity-0 transition-opacity duration-300 tooltip">
Toggle Coin Volume
<div class="tooltip-arrow" data-popper-arrow></div>
</div>
<div id="tooltip-volume" role="tooltip" class="inline-block absolute invisible z-10 py-2 px-3 text-sm font-medium text-white bg-blue-500 rounded-lg shadow-sm opacity-0 transition-opacity duration-300 tooltip">Toggle Coin Volume</div>
<button id="toggle-volume" data-tooltip-target="tooltip-volume" class="text-white font-bold py-2 px-4 rounded mr-2 focus:outline-none focus:ring-0 transition-colors duration-200" title="Toggle Volume">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path d="M2 11a1 1 0 011-1h2a1 1 0 011 1v5a1 1 0 01-1 1H3a1 1 0 01-1-1v-5zM8 7a1 1 0 011-1h2a1 1 0 011 1v9a1 1 0 01-1 1H9a1 1 0 01-1-1V7zM14 4a1 1 0 011-1h2a1 1 0 011 1v12a1 1 0 01-1 1h-2a1 1 0 01-1-1V4z" />
</svg>
</button>
<div id="tooltip-refresh" role="tooltip" class="inline-block absolute invisible z-10 py-2 px-3 text-sm font-medium text-white bg-blue-500 rounded-lg shadow-sm opacity-0 transition-opacity duration-300 tooltip">
Refresh Charts/Prices & Clear Cache
<div class="tooltip-arrow" data-popper-arrow></div>
</div>
<div id="tooltip-refresh" role="tooltip" class="inline-block absolute invisible z-10 py-2 px-3 text-sm font-medium text-white bg-blue-500 rounded-lg shadow-sm opacity-0 transition-opacity duration-300 tooltip">Refresh Charts/Prices & Clear Cache</div>
<button id="refresh-all" data-tooltip-target="tooltip-refresh" class="text-gray-600 dark:text-gray-400 font-bold py-2 px-4 rounded mr-2 focus:outline-none focus:ring-0 transition-colors duration-200" title="Refresh Data & Clear Cache">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
<circle cx="12" cy="12" r="3" />
</svg>
</button>
<div id="tooltip-auto" role="tooltip" class="inline-block absolute invisible z-10 py-2 px-3 text-sm font-medium text-white bg-blue-500 rounded-lg shadow-sm opacity-0 transition-opacity duration-300 tooltip">
Auto Refresh Enable/Disable
<div class="tooltip-arrow" data-popper-arrow></div>
</div>
<div id="tooltip-auto" role="tooltip" class="inline-block absolute invisible z-10 py-2 px-3 text-sm font-medium text-white bg-blue-500 rounded-lg shadow-sm opacity-0 transition-opacity duration-300 tooltip">Auto Refresh Enable/Disable</div>
<button id="toggle-auto-refresh" data-enabled="false" data-tooltip-target="tooltip-auto" class="text-white font-bold py-2 px-4 rounded mr-2 focus:outline-none focus:ring-0 transition-colors duration-200" title="Enable Auto-Refresh">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M4 2a1 1 0 011 1v2.101a7.002 7.002 0 0111.601 2.566 1 1 0 11-1.885.666A5.002 5.002 0 005.999 7H9a1 1 0 010 2H4a1 1 0 01-1-1V3a1 1 0 011-1zm.008 9.057a1 1 0 011.276.61A5.002 5.002 0 0014.001 13H11a1 1 0 110-2h5a1 1 0 011 1v5a1 1 0 11-2 0v-2.101a7.002 7.002 0 01-11.601-2.566 1 1 0 01.61-1.276z" clip-rule="evenodd" />
@@ -137,18 +129,25 @@ function getWebSocketConfig() {
</div>
</div>
</div>
<div id="error-overlay" class="error-overlay hidden absolute inset-0 bg-black bg-opacity-50 flex items-center justify-center">
<div id="error-content" class="error-content bg-white dark:bg-gray-800 rounded-lg p-6 w-full max-w-2xl mx-4 relative">
<button id="close-error" class="absolute top-3 right-3 bg-red-500 text-white rounded-full p-2 hover:bg-red-600 focus:outline-none">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<div id="error-overlay" class="notice-overlay hidden absolute inset-0 bg-opacity-30 backdrop-blur-sm flex items-center justify-center">
<div id="error-content" class="notice-content bg-white dark:bg-gray-800 rounded-xl p-6 w-full max-w-2xl mx-4 relative shadow-lg">
<div class="absolute top-0 left-0 right-0 h-1 bg-gradient-to-r from-blue-400 via-blue-500 to-blue-600"></div>
<button id="close-error" class="absolute top-3 right-3 text-gray-400 hover:text-gray-500 dark:text-gray-500 dark:hover:text-white rounded-full p-2 hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors focus:outline-none">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
<p class="text-red-600 font-semibold text-xl mb-4">Error</p>
<p id="error-message" class="text-gray-700 dark:text-gray-300 text-lg mb-6"></p>
<p class="text-sm text-gray-600 dark:text-gray-400">To review or update your Chart API Key(s), navigate to<a href="/settings" class="text-blue-500 hover:underline">Settings & Tools > Settings > General (TAB)</a>.
</p>
<div class="flex items-center gap-3 mb-4">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-blue-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9" />
</svg>
<p class="text-lg font-medium text-gray-900 dark:text-gray-100">Notice</p>
</div>
<p id="error-message" class="text-gray-600 dark:text-gray-300 text-base mb-6"></p>
<div class="text-sm text-gray-500 dark:text-gray-400">
Need to update your (API) settings?
<a href="/settings" class="text-blue-500 hover:text-blue-600 dark:hover:text-blue-400 font-medium ml-1 hover:underline">Go to Settings -> General</a>
</div>
</div>
</div>
</div>
@@ -157,9 +156,9 @@ function getWebSocketConfig() {
</div>
</section>
<section class="py-4 flex flex-wrap justify-center overflow-hidden container-to-blur">
<div class="container px-4 mx-auto">
<div class="flex flex-wrap justify-center -m-3" id="coin-container">
<section class="py-4 px-3 flex flex-wrap overflow-hidden container-to-blur">
<div class="lg:container mx-auto">
<div class="flex flex-wrap justify-center lg:justify-start xl:justify-center" id="coin-container">
{% set coin_data = {
'BTC': {'name': 'Bitcoin', 'symbol': 'BTC', 'image': 'Bitcoin.png', 'show': true},
'XMR': {'name': 'Monero', 'symbol': 'XMR', 'image': 'Monero.png', 'show': true},
@@ -192,9 +191,9 @@ function getWebSocketConfig() {
{% for coin_symbol in custom_order %}
{% if coin_symbol in display_coins and coin_data[coin_symbol]['show'] %}
<div class="w-full sm:w-1/2 lg:w-1/6 p-3" id="{{ coin_symbol.lower() }}-container">
<div class="w-full sm:w-1/2 md:w-1/4 lg:w-1/5 xl:w-1/6 p-3" id="{{ coin_symbol.lower() }}-container">
<div class="px-5 py-3 h-full bg-coolGray-100 dark:bg-gray-500 rounded-2xl dark:text-white {% if coin_symbol == 'BTC' %}active-container{% endif %}" style="min-height: 180px;">
<div class="flex items-center">
<div class="flex items-center h-10">
<img src="/static/images/coins/{{ coin_data[coin_symbol]['image'] }}" class="rounded-xl" style="width: 28px; height: 28px; object-fit: contain;" alt="{{ coin_data[coin_symbol]['name'] }}">
<p class="ml-1 text-black text-sm dark:text-white">
{{ coin_data[coin_symbol]['name'] }} {% if coin_data[coin_symbol]['symbol'] != coin_data[coin_symbol]['name'] %}({{ coin_data[coin_symbol]['symbol'] }}){% endif %}
@@ -213,12 +212,13 @@ function getWebSocketConfig() {
</div>
{% if coin_symbol != 'BTC' %}
<div id="{{ coin_symbol.lower() }}-btc-price-div" class="flex items-center text-xs text-gray-600 dark:text-gray-300 mt-2 {% if coin_symbol == 'WOW' %}hidden{% endif %}">
<span class="bold mr-2">BTC:</span> <span id="{{ coin_symbol.lower() }}-price-btc"></span>
<span class="bold mr-2 ml-1">BTC:</span>
<span id="{{ coin_symbol.lower() }}-price-btc"></span>
</div>
{% endif %}
<div id="{{ coin_symbol.lower() }}-volume-div" class="flex items-center text-xs text-gray-600 dark:text-gray-300 mt-2">
<span class="bold mr-2">VOL:</span>
<div id="{{ coin_symbol.lower() }}-volume-24h"></div>
<span class="bold mr-2 ml-1">VOL:</span>
<span id="{{ coin_symbol.lower() }}-volume-24h"></span>
</div>
</div>
</div>
@@ -233,17 +233,17 @@ function getWebSocketConfig() {
<script src="/static/js/pricechart.js"></script>
<section>
<div class="pl-6 pr-6 pt-0 pb-0 mt-5 h-full overflow-hidden">
<div class="px-6 py-0 mt-5 h-full overflow-hidden">
<div class="border-coolGray-100">
<div class="flex flex-wrap items-center justify-between -m-2">
<div class="flex flex-wrap items-center justify-between">
<div class="w-full mx-auto pt-2">
<form method="post" id="filterForm">
<div class="flex items-center justify-center pb-4 dark:text-white">
<div class="rounded-b-md">
<div class="w-full md:w-0/12">
<div class="container flex flex-wrap">
<div class="md:w-auto hover-container justify-center">
<div class="flex flex-wrap justify-center">
<div class="lg:container flex flex-wrap justify-center">
<div class="md:w-auto hover-container">
<div class="flex flex-wrap">
<div class="pt-3 px-3 md:w-auto hover-container">
<div class="flex">
<button id="coin_to_button" class="bg-gray-50 text-gray-900 appearance-none w-10 dark:bg-gray-500 dark:text-white border-l border-t border-b border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-gray-50 text-sm rounded-l-lg flex items-center" disabled></button>
@@ -302,14 +302,14 @@ function getWebSocketConfig() {
</div>
</div>
</div>
<div class="w-full md:w-auto pt-3 px-3">
<div class="w-full lg:w-auto pt-3 px-3">
<div class="relative">
<button type="button" id="clearFilters" class="transition-opacity duration-200 flex flex-wrap justify-center w-full px-4 py-2.5 font-medium text-sm hover:text-white dark:text-white dark:bg-gray-500 bg-coolGray-200 hover:bg-green-600 hover:border-green-600 rounded-lg transition duration-200 border border-coolGray-200 dark:border-gray-400 rounded-md shadow-button focus:ring-0 focus:outline-none" disabled>
<span>Clear Filters</span>
</button>
</div>
</div>
<div class="w-full md:w-auto pt-3 px-3">
<div class="w-full lg:w-auto pt-3 px-3">
<div class="relative">
<button type="button" id="refreshOffers" class="flex flex-wrap justify-center w-full px-4 py-2.5 font-medium text-sm text-white bg-blue-600 hover:bg-green-600 hover:border-green-600 rounded-lg transition duration-200 border border-blue-500 rounded-md shadow-button focus:ring-0 focus:outline-none">
<svg id="refreshIcon" class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
@@ -334,10 +334,10 @@ function getWebSocketConfig() {
<div id="jsonView" class="hidden mb-4">
<pre id="jsonContent" class="bg-gray-100 p-4 rounded overflow-auto" style="max-height: 300px;"></pre>
</div>
<div class="container mt-5 mx-auto px-4">
<div class="mt-5 lg:container mx-auto lg:px-0 px-6">
<div class="pt-0 pb-6 bg-coolGray-100 dark:bg-gray-500 rounded-xl">
<div class="px-0">
<div class="w-auto mt-6 overflow-x-auto">
<div class="w-auto mt-6 overflow-auto lg:overflow-hidden">
<table class="w-full min-w-max">
<thead class="uppercase">
<tr>

View File

@@ -1,36 +1,37 @@
{% include 'header.html' %}
{% from 'style.html' import select_box_arrow_svg, select_box_class, circular_arrows_svg, circular_error_svg, circular_info_svg, cross_close_svg, breadcrumb_line_svg, withdraw_svg, utxo_groups_svg, create_utxo_svg, red_cross_close_svg, blue_cross_close_svg, circular_update_messages_svg, circular_error_messages_svg %}
<script src="/static/js/libs//qrcode.js"></script>
<div class="container mx-auto">
<section class="p-5 mt-5">
<div class="container mx-auto">
<div class="flex flex-wrap items-center -m-2">
<div class="w-full md:w-1/2 p-2">
<ul class="flex flex-wrap items-center gap-x-3 mb-2">
<li><a class="flex font-medium text-xs text-coolGray-500 dark:text-gray-300 hover:text-coolGray-700" href="/">Home</a></li>
<li><a class="flex font-medium text-md lg:text-xs text-coolGray-500 dark:text-gray-300 hover:text-coolGray-700" href="/">Home</a></li>
<li>{{ breadcrumb_line_svg | safe }}</li>
<li><a class="flex font-medium text-xs text-coolGray-500 dark:text-gray-300 hover:text-coolGray-700" href="/wallets">Wallets</a></li>
<li><a class="flex font-medium text-md lg:text-xs text-coolGray-500 dark:text-gray-300 hover:text-coolGray-700" href="/wallets">Wallets</a></li>
<li>{{ breadcrumb_line_svg | safe }}</li>
<li><a class="flex font-medium text-xs text-coolGray-500 dark:text-gray-300 hover:text-coolGray-700" href="/wallet/{{ w.ticker }}">{{ w.ticker }}</a></li>
<li><a class="flex font-medium text-md lg:text-xs text-coolGray-500 dark:text-gray-300 hover:text-coolGray-700" href="/wallet/{{ w.ticker }}">{{ w.ticker }}</a></li>
</ul>
</div>
</div>
</div>
</section>
<section class="py-4">
<div class="container px-4 mx-auto">
<section class="py-4 px-6">
<div class="lg:container mx-auto">
<div class="relative py-11 px-16 bg-coolGray-900 dark:bg-blue-500 rounded-md overflow-hidden"> <img class="absolute z-10 left-4 top-4 right-4 bottom-4" src="/static/images/elements/dots-red.svg" alt="dots-red"> <img class="absolute h-64 left-1/2 top-1/2 transform -translate-x-1/2 -translate-y-1/2 object-cover" src="/static/images/elements/wave.svg" alt="wave">
<div class="relative z-20 flex flex-wrap items-center -m-3">
<div class="w-full md:w-1/2">
<h2 class="text-3xl font-bold text-white"> <span class="inline-block align-middle"><img class="mr-2 h-16" src="/static/images/coins/{{ w.name }}.png" alt="{{ w.name }}"></span>({{ w.ticker }}) {{ w.name }} Wallet </h2>
</div>
<div class="w-full md:w-1/2 p-3 p-6 container flex flex-wrap items-center justify-end items-center mx-auto"> <a class="rounded-full mr-5 flex flex-wrap justify-center px-5 py-3 bg-blue-500 hover:bg-blue-600 font-medium text-sm text-white border dark:bg-gray-500 dark:hover:bg-gray-700 border-blue-500 rounded-md shadow-button focus:ring-0 focus:outline-none" id="refresh" href="/wallet/{{ w.ticker }}"> {{ circular_arrows_svg | safe }}<span>Refresh</span> </a> </div>
<div class="w-full md:w-1/2 p-3 p-6 container flex flex-wrap items-center justify-end items-center mx-auto"> <a class="rounded-full mr-5 flex flex-wrap justify-center px-5 py-3 bg-blue-500 hover:bg-blue-600 font-medium text-lg lg:text-sm text-white border dark:bg-gray-500 dark:hover:bg-gray-700 border-blue-500 rounded-md shadow-button focus:ring-0 focus:outline-none" id="refresh" href="/wallet/{{ w.ticker }}"> {{ circular_arrows_svg | safe }}<span>Refresh</span> </a> </div>
</div>
</div>
</div>
</section>
{% include 'inc_messages.html' %}
{% if w.updating %}
<section class="py-4" id="messages_updating" role="alert">
<div class="container px-4 mx-auto">
<section class="py-4 px-6" id="messages_updating" role="alert">
<div class="lg:container mx-auto">
<div class="p-6 text-green-800 rounded-lg bg-blue-50 border border-blue-500 dark:bg-gray-500 dark:text-blue-400 rounded-md">
<div class="flex flex-wrap justify-between items-center -m-2">
<div class="flex-1 p-2">
@@ -39,8 +40,8 @@
{{ circular_update_messages_svg | safe }}
</div>
<ul class="ml-4 mt-1">
<li class="font-semibold text-sm text-blue-500 error_msg"><span class="bold">UPDATING:</span></li>
<li class="font-medium text-sm text-blue-500">Please wait...</li>
<li class="font-semibold text-lg lg:text-sm text-blue-500 error_msg"><span class="bold">UPDATING:</span></li>
<li class="font-medium text-lg lg:text-sm text-blue-500">Please wait...</li>
</ul>
</div>
</div>
@@ -56,8 +57,8 @@
{% endif %}
{% if w.havedata %}
{% if w.error %}
<section class="py-4" id="messages_error" role="alert">
<div class="container px-4 mx-auto">
<section class="py-4 px-6" id="messages_error" role="alert">
<div class="lg:container mx-auto">
<div class="p-6 text-green-800 rounded-lg bg-red-50 border border-red-400 dark:bg-gray-500 dark:text-red-400 rounded-md">
<div class="flex flex-wrap justify-between items-center -m-2">
<div class="flex-1 p-2">
@@ -66,8 +67,8 @@
{{ circular_error_messages_svg | safe }}
</div>
<ul class="ml-4 mt-1">
<li class="font-semibold text-sm text-red-500 error_msg"><span class="bold">ERROR:</span></li>
<li class="font-medium text-sm text-red-500 error_msg">{{ w.error }}</li>
<li class="font-semibold text-lg lg:text-sm text-red-500 error_msg"><span class="bold">ERROR:</span></li>
<li class="font-medium text-lg lg:text-sm text-red-500 error_msg">{{ w.error }}</li>
</ul>
</div>
</div>
@@ -83,8 +84,8 @@
</section>
{% else %}
{% if w.cid == '18' %} {# DOGE #}
<section class="py-4" id="messages_notice">
<div class="container px-4 mx-auto">
<section class="py-4 px-6" id="messages_notice">
<div class="lg:container mx-auto">
<div class="p-6 rounded-lg bg-coolGray-100 dark:bg-gray-500 shadow-sm">
<div class="flex items-start">
<svg class="w-6 h-6 text-blue-500 mt-1 mr-3 flex-shrink-0" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path stroke-linecap="round" stroke-linejoin="round" d="M11.25 11.25l.041-.02a.75.75 0 011.063.852l-.708 2.836a.75.75 0 001.063.853l.041-.021M21 12a9 9 0 11-18 0 9 9 0 0118 0zm-9-3.75h.008v.008H12V8.25z"></path></svg>
@@ -101,24 +102,24 @@
</div>
</section>
{% endif %}
<form method="post" autocomplete="off">
<section>
<div class="pl-6 pr-6 pt-0 pb-0 mt-5 h-full overflow-hidden">
<form method="post" autocomplete="off">
<div class="px-6 py-0 mt-5 h-full overflow-hidden">
<div class="pb-6 border-coolGray-100">
<div class="flex flex-wrap items-center justify-between -m-2">
<div class="w-full pt-2">
<div class="container mt-5 mx-auto">
<div class="lg:container mt-5 mx-auto">
<div class="pt-6 pb-8 bg-coolGray-100 dark:bg-gray-500 rounded-xl">
<div class="px-6">
<div class="w-full mt-6 pb-6 overflow-x-auto">
<table class="w-full min-w-max text-sm">
<div class="w-full pb-6 overflow-x-auto">
<table class="w-full text-lg lg:text-sm">
<thead class="uppercase">
<tr class="text-left">
<th class="p-0">
<div class="py-3 px-6 rounded-tl-xl bg-coolGray-200 dark:bg-gray-600"> <span class="text-xs text-gray-600 dark:text-gray-300 font-semibold">Wallet</span> </div>
<div class="py-3 px-6 rounded-tl-xl bg-coolGray-200 dark:bg-gray-600"> <span class="text-md lg:text-xs text-gray-600 dark:text-gray-300 font-semibold">Wallet</span> </div>
</th>
<th class="p-0">
<div class="py-3 px-6 rounded-tr-xl bg-coolGray-200 dark:bg-gray-600"> <span class="text-xs text-gray-600 dark:text-gray-300 font-semibold">Details</span> </div>
<div class="py-3 px-6 rounded-tr-xl bg-coolGray-200 dark:bg-gray-600"> <span class="text-md lg:text-xs text-gray-600 dark:text-gray-300 font-semibold">Details</span> </div>
</th>
</tr>
</thead>
@@ -129,7 +130,8 @@
<span class="inline-block py-1 px-2 rounded-full bg-green-100 text-green-500 dark:bg-gray-500 dark:text-green-500">Pending: +{{ w.pending }} {{ w.ticker }} </span>
{% endif %}
</td>
</tr> {% if w.cid == '1' %} {# PART #}
</tr>
{% if w.cid == '1' %} {# PART #}
<tr class="opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600">
<td class="py-3 px-6 bold"> <span class="inline-flex align-middle items-center justify-center w-9 h-10 bg-white-50 rounded"> <img class="h-7" src="/static/images/coins/{{ w.name }}.png" alt="{{ w.name }} Blind"> </span>Blind Balance: </td>
<td class="py-3 px-6 bold coinname-value" data-coinname="{{ w.name }}">{{ w.blind_balance }} {{ w.ticker }} (<span class="usd-value"></span>)
@@ -147,11 +149,10 @@
</td>
<td class="usd-value"></td>
</tr>
{% endif %}
{# / PART #}
{% if w.cid == '3' %} {# LTC #}
{% elif w.cid == '3' %} {# LTC #}
<tr class="opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600">
<td class="py-3 px-6 bold"> <span class="pr-3 inline-flex align-middle items-center justify-center w-9 h-10 bg-white-50 rounded"> <img class="h-7" src="/static/images/coins/{{ w.name }}.png" alt="{{ w.name }} MWEB"> </span>MWEB Balance: </td>
<td class="py-3 px-6 bold"> <span class="inline-flex align-middle items-center justify-center w-9 h-10 bg-white-50 rounded"> <img class="h-7" src="/static/images/coins/{{ w.name }}.png" alt="{{ w.name }} MWEB"> </span>MWEB Balance: </td>
<td class="py-3 px-6 bold coinname-value" data-coinname="{{ w.name }}">{{ w.mweb_balance }} {{ w.ticker }} (<span class="usd-value"></span>)
{% if w.mweb_pending %}
<span class="inline-block py-1 px-2 rounded-full bg-green-100 text-green-500 dark:bg-gray-500 dark:text-green-500">Pending: +{{ w.mweb_pending }} {{ w.ticker }} </span>
@@ -163,7 +164,7 @@
{% if w.locked_utxos %}
<tr class="opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600">
<td class="py-3 px-6 bold">Locked Outputs:</td>
<td id='locked_utxos' class="py-3 px-6">{{ w.locked_utxos }}</td>
<td id="locked_utxos" class="py-3 px-6">{{ w.locked_utxos }}</td>
</tr>
{% endif %}
{# / locked_utxos #}
@@ -176,7 +177,7 @@
<td class="py-3 px-6">{{ w.version }}</td>
</tr>
<tr class="opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600">
<td class="py-3 px-6 bold">Blocks:</td>
<td class="py-3 px-6 bold">Blockheight:</td>
<td class="py-3 px-6">{{ w.blocks }}
{% if w.known_block_count %} / {{ w.known_block_count }}
{% endif %}
@@ -206,10 +207,12 @@
</tr>
{% endif %}
{# / encrypted #}
{% if w.expected_seed != true %}
<tr class="opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600">
<td class="py-3 px-6 bold">Expected Seed:</td>
<td class="py-3 px-6">{{ w.expected_seed }}</td>
</tr>
{% endif %}
</table>
</div>
</div>
@@ -219,18 +222,19 @@
</div>
</div>
</div>
</form>
</section>
{% if block_unknown_seeds and w.expected_seed != true %} {# Only show addresses if wallet seed is correct #}
<section class="pl-6 pr-6 pt-0 pb-0 h-full overflow-hidden">
<section class="px-6 py-0 h-full overflow-hidden">
<div class="pb-6 border-coolGray-100">
<div class="flex flex-wrap items-center justify-between -m-2">
<div class="w-full pt-2">
<div class="container mt-5 mx-auto">
<div class="pt-6 pb-6 bg-coolGray-100 dark:bg-gray-500 rounded-xl">
<div class="lg:container mt-5 mx-auto">
<div class="py-6 bg-coolGray-100 dark:bg-gray-500 rounded-xl">
<div class="px-6">
{% if w.cid != '4' %} {# DCR #}
<div class="flex flex-wrap justify-end">
<div class="w-full md:w-auto p-1.5"> <input class="flex flex-wrap justify-center w-full px-4 py-2.5 font-medium text-sm text-white hover:text-red border border-red-500 hover:border-red-500 hover:bg-red-600 bg-red-500 rounded-md shadow-button focus:ring-0 focus:outline-none cursor-pointer" type="submit" name="reseed_{{ w.cid }}" value="Reseed wallet" onclick="return confirmReseed();"> </div>
<div class="w-full md:w-auto p-1.5"> <input class="flex flex-wrap justify-center w-full px-4 py-2.5 font-medium text-lg lg:text-sm text-white hover:text-red border border-red-500 hover:border-red-500 hover:bg-red-600 bg-red-500 rounded-md shadow-button focus:ring-0 focus:outline-none cursor-pointer" type="submit" name="reseed_{{ w.cid }}" value="Reseed wallet" onclick="return confirmReseed();"> </div>
</div>
{% endif %}
</div>
@@ -242,90 +246,53 @@
</section>
{% else %}
<section class="p-6">
<div class="lg:container mx-auto">
<div class="flex items-center">
<h4 class="font-semibold text-2xl text-black dark:text-white">Deposit</h4>
</div>
</div>
</section>
<form method="post" autocomplete="off">
<section>
<div class="pl-6 pr-6 pt-0 pb-0 overflow-hidden">
<div class="px-6 py-0 overflow-hidden">
<div class="pb-6 border-coolGray-100">
<div class="flex flex-wrap items-center justify-between -m-2">
<div class="w-full pt-2">
<div class="container mt-5 mx-auto">
<div class="lg:container mt-5 mx-auto">
<div class="pb-6 bg-coolGray-100 dark:bg-gray-500 rounded-xl">
<div class="px-6">
<div class="container mx-auto">
<div class="flex flex-wrap max-w-7xl mx-auto justify-center -m-3">
{% if w.cid in '6, 9' %}
{# XMR | WOW #}
<div class="w-full md:w-1/2 p-3 flex justify-center items-center">
<div class="h-full">
<div class="flex flex-wrap -m-3">
<div class="w-full p-3">
<div class="mb-2 qrcode-container flex h-60 justify-center items-center">
<div class="qrcode-border flex">
{% if w.cid in '6, 9' %}
{# XMR | WOW #}
<div id="qrcode-monero-main" class="qrcode"></div>
</div>
</div>
<div class="font-normal bold text-gray-500 text-center dark:text-white mb-5">{{ w.name }} Main Address: </div>
<div class="font-normal bold text-gray-500 text-center dark:text-white mb-5">Main Address: </div>
<div class="relative flex justify-center items-center">
<div data-tooltip-target="tooltip-copy-monero-main" class="input-like-container hover:border-blue-500 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-gray-400 text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full focus:ring-0" id="monero_main_address">{{ w.main_address }}</div>
</div>
<div id="tooltip-copy-monero-main" role="tooltip" class="inline-block absolute invisible z-10 py-2 px-3 text-sm font-medium text-white bg-blue-500 rounded-lg shadow-sm opacity-0 transition-opacity duration-300 tooltip">
<p>Copy to clipboard</p>
<div class="tooltip-arrow" data-popper-arrow></div>
</div>
</div>
</div>
</div>
</div>
<div class="w-full md:w-1/2 p-3 flex justify-center items-center">
<div class="h-full">
<div class="flex flex-wrap -m-3">
<div class="w-full p-3">
<div class="mb-2 qrcode-container flex h-60 justify-center items-center">
<div class="qrcode-border flex">
<div id="qrcode-monero-sub" class="qrcode"> </div>
</div>
</div>
<div class="font-normal bold text-gray-500 text-center dark:text-white mb-5">{{ w.name }} Sub Address: </div>
<div class="relative flex justify-center items-center">
<div data-tooltip-target="tooltip-copy-monero-sub" class="input-like-container hover:border-blue-500 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-gray-400 text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full focus:ring-0" id="monero_sub_address">{{ w.deposit_address }}</div>
</div>
<div class="opacity-100 text-gray-500 dark:text-gray-100 flex justify-center items-center">
<div class="py-3 px-6 bold mt-5">
<button type="submit" class="flex justify-center py-2 px-4 bg-blue-500 hover:bg-blue-600 font-medium text-sm text-white border border-blue-500 rounded-md shadow-button focus:ring-0 focus:outline-none" name="newaddr_{{ w.cid }}" value="New Subaddress"> {{ circular_arrows_svg }} New {{ w.name }} Deposit Address</button>
</div>
</div>
<div id="tooltip-copy-monero-sub" role="tooltip" class="inline-block absolute invisible z-10 py-2 px-3 text-sm font-medium text-white bg-blue-500 rounded-lg shadow-sm opacity-0 transition-opacity duration-300 tooltip">
<p>Copy to clipboard</p>
<div class="tooltip-arrow" data-popper-arrow></div>
</div>
</div>
</div>
</div>
<div data-tooltip-target="tooltip-copy-monero-main" class="input-like-container hover:border-blue-500 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-gray-400 text-lg lg:text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full focus:ring-0" id="monero_main_address">{{ w.main_address }}</div>
</div>
<div id="tooltip-copy-monero-main" role="tooltip" class="inline-block absolute invisible z-10 py-2 px-3 text-lg lg:text-sm font-medium text-white bg-blue-500 rounded-lg shadow-sm opacity-0 transition-opacity duration-300 tooltip">
{% else %}
<div class="w-full md:w-1/2 p-3 flex justify-center items-center">
<div class="h-full">
<div class="flex flex-wrap -m-3">
<div class="w-full p-3">
<div class="mb-2 qrcode-container flex h-60 justify-center items-center">
<div class="qrcode-border flex">
<div id="qrcode-deposit" class="qrcode"> </div>
</div>
</div>
<div class="font-normal bold text-gray-500 text-center dark:text-white mb-5">{{ w.name }} Deposit Address: </div>
<div class="font-normal bold text-gray-500 text-center dark:text-white mb-5">Deposit Address: </div>
<div class="relative flex justify-center items-center">
<div data-tooltip-target="tooltip-copy-default" class="input-like-container hover:border-blue-500 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-gray-400 text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full focus:ring-0" id="main_deposit_address">{{ w.deposit_address }}</div>
<div data-tooltip-target="tooltip-copy-default" class="input-like-container hover:border-blue-500 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-gray-400 text-lg lg:text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full focus:ring-0" id="main_deposit_address">{{ w.deposit_address }}</div>
</div>
<div class="opacity-100 text-gray-500 dark:text-gray-100 flex justify-center items-center">
<div class="py-3 px-6 bold mt-5">
<button type="submit" class="flex justify-center py-2 px-4 bg-blue-500 hover:bg-blue-600 font-medium text-sm text-white border border-blue-500 rounded-md shadow-button focus:ring-0 focus:outline-none" name="newaddr_{{ w.cid }}" value="New Deposit Address"> {{ circular_arrows_svg }} New {{ w.name }} Deposit Address </button>
</div>
</div>
<div id="tooltip-copy-default" role="tooltip" class="inline-block absolute invisible z-10 py-2 px-3 text-sm font-medium text-white bg-blue-500 rounded-lg shadow-sm opacity-0 transition-opacity duration-300 tooltip">
<div id="tooltip-copy-default" role="tooltip" class="inline-block absolute invisible z-10 py-2 px-3 text-lg lg:text-sm font-medium text-white bg-blue-500 rounded-lg shadow-sm opacity-0 transition-opacity duration-300 tooltip">
{% endif %}
<p>Copy to clipboard</p>
<div class="tooltip-arrow" data-popper-arrow></div>
</div>
@@ -333,52 +300,58 @@
</div>
</div>
</div>
{% endif %}
{% if w.cid == '1' %} {# PART #}
{% if w.cid in '1, 3, 6, 9' %}
{# PART | LTC | XMR | WOW | #}
<div class="w-full md:w-1/2 p-3 flex justify-center items-center">
<div class="h-full">
<div class="flex flex-wrap -m-3">
<div class="w-full p-3">
<div class="mb-2 qrcode-container flex h-60 justify-center items-center">
<div class="qrcode-border flex">
{% if w.cid in '6, 9' %}
{# XMR | WOW #}
<div id="qrcode-monero-sub" class="qrcode"> </div>
</div>
</div>
<div class="font-normal bold text-gray-500 text-center dark:text-white mb-5">Subaddress: </div>
<div class="relative flex justify-center items-center">
<div data-tooltip-target="tooltip-copy-monero-sub" class="input-like-container hover:border-blue-500 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-gray-400 text-lg lg:text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full focus:ring-0" id="monero_sub_address">{{ w.deposit_address }}</div>
</div>
<div class="opacity-100 text-gray-500 dark:text-gray-100 flex justify-center items-center">
<div class="py-3 px-6 bold mt-5">
<button type="submit" class="flex justify-center py-2 px-4 bg-blue-500 hover:bg-blue-600 font-medium text-sm text-white border border-blue-500 rounded-md shadow-button focus:ring-0 focus:outline-none" name="newaddr_{{ w.cid }}" value="New Subaddress"> {{ circular_arrows_svg }} New {{ w.name }} Deposit Address</button>
</div>
</div>
<div id="tooltip-copy-monero-sub" role="tooltip" class="inline-block absolute invisible z-10 py-2 px-3 text-lg lg:text-sm font-medium text-white bg-blue-500 rounded-lg shadow-sm opacity-0 transition-opacity duration-300 tooltip">
{% elif w.cid == '1' %}
{# PART #}
<div id="qrcode-stealth" class="qrcode"> </div>
</div>
</div>
<div class="font-normal bold text-gray-500 text-center dark:text-white mb-5">{{ w.name }} Stealth Address: </div>
<div class="font-normal bold text-gray-500 text-center dark:text-white mb-5">Stealth Address: </div>
<div class="relative flex justify-center items-center">
<div data-tooltip-target="tooltip-copy-particl-stealth" class="input-like-container hover:border-blue-500 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-gray-400 text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-10 focus:ring-0" id="stealth_address"> {{ w.stealth_address }}
</div>
</div>
<div id="tooltip-copy-particl-stealth" role="tooltip" class="inline-block absolute invisible z-10 py-2 px-3 text-sm font-medium text-white bg-blue-500 rounded-lg shadow-sm opacity-0 transition-opacity duration-300 tooltip">
<p>Copy to clipboard</p>
<div class="tooltip-arrow" data-popper-arrow></div>
</div>
</div>
</div>
</div>
<div data-tooltip-target="tooltip-copy-particl-stealth" class="input-like-container hover:border-blue-500 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-gray-400 text-lg lg:text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-10 focus:ring-0" id="stealth_address"> {{ w.stealth_address }}
</div>
<div id="tooltip-copy-particl-stealth" role="tooltip" class="inline-block absolute invisible z-10 py-2 px-3 text-lg lg:text-sm font-medium text-white bg-blue-500 rounded-lg shadow-sm opacity-0 transition-opacity duration-300 tooltip">
{# / PART #}
{% elif w.cid == '3' %}
{# LTC #}
<div class="w-full md:w-1/2 p-3 flex justify-center items-center">
<div class="h-full">
<div class="flex flex-wrap -m-3">
<div class="w-full p-3">
<div class="mb-2 qrcode-container flex h-60 justify-center items-center">
<div class="qrcode-border flex">
<div id="qrcode-mweb" class="qrcode"> </div>
</div>
</div>
<div class="font-normal bold text-gray-500 text-center dark:text-white mb-5">{{ w.name }} MWEB Address: </div>
<div class="font-normal bold text-gray-500 text-center dark:text-white mb-5">MWEB Address: </div>
<div class="text-center relative">
<div data-tooltip-target="tooltip-copy-litecoin-mweb" class="input-like-container hover:border-blue-500 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-gray-400 text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 focus:ring-0" id="stealth_address">{{ w.mweb_address }}</div>
<div data-tooltip-target="tooltip-copy-litecoin-mweb" class="input-like-container hover:border-blue-500 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-gray-400 text-lg lg:text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 focus:ring-0" id="stealth_address">{{ w.mweb_address }}</div>
<span class="absolute inset-y-0 right-0 flex items-center pr-3 cursor-pointer" id="copyIcon"></span>
</div>
<div class="opacity-100 text-gray-500 dark:text-gray-100 flex justify-center items-center">
<div class="py-3 px-6 bold mt-5">
<button type="submit" class="flex justify-center py-2 px-4 bg-blue-500 hover:bg-blue-600 font-medium text-sm text-white border border-blue-500 rounded-md shadow-button focus:ring-0 focus:outline-none" name="newmwebaddr_{{ w.cid }}" value="New MWEB Address"> {{ circular_arrows_svg }} New MWEB Address </button>
</div>
<div id="tooltip-copy-litecoin-mweb" role="tooltip" class="inline-block absolute invisible z-10 py-2 px-3 text-sm font-medium text-white bg-blue-500 rounded-lg shadow-sm opacity-0 transition-opacity duration-300 tooltip">
</div>
<div id="tooltip-copy-litecoin-mweb" role="tooltip" class="inline-block absolute invisible z-10 py-2 px-3 text-lg lg:text-sm font-medium text-white bg-blue-500 rounded-lg shadow-sm opacity-0 transition-opacity duration-300 tooltip">
{# / LTC #}
{% endif %}
<p>Copy to clipboard</p>
<div class="tooltip-arrow" data-popper-arrow></div>
</div>
@@ -386,9 +359,8 @@
</div>
</div>
</div>
{% endif %}
{# / LTC #}
</div>
{% endif %}
</div>
</div>
</div>
@@ -410,8 +382,7 @@
correctLevel: QRCode.CorrectLevel.L
});
</script>
{% endif %}
{% if w.cid == '3' %}
{% elif w.cid == '3' %}
{# LTC #}
<script>
// Litecoin MWEB
@@ -529,61 +500,67 @@ document.addEventListener('DOMContentLoaded', function() {
});
</script>
<section class="p-6">
<div class="lg:container mx-auto">
<div class="flex items-center">
<h4 class="font-semibold text-2xl text-black dark:text-white">Withdraw</h4>
</div>
</div>
</section>
<section>
<div class="pl-6 pr-6 pt-0 pb-0 h-full overflow-hidden">
<div class="px-6 py-0 h-full overflow-hidden">
<div class="border-coolGray-100">
<div class="flex flex-wrap items-center justify-between -m-2">
<div class="w-full pt-2">
<div class="container mt-5 mx-auto">
<div class="pt-6 pb-6 bg-coolGray-100 dark:bg-gray-500 rounded-xl">
<div class="lg:container mt-5 mx-auto">
<div class="py-6 bg-coolGray-100 dark:bg-gray-500 rounded-xl">
<div class="px-6">
<div class="w-full mt-6 pb-6 overflow-x-auto">
<table class="w-full min-w-max text-sm">
<div class="w-full pb-6 overflow-x-auto">
<table class="w-full text-lg lg:text-sm">
<thead class="uppercase">
<tr class="text-left">
<th class="p-0">
<div class="py-3 px-6 rounded-tl-xl bg-coolGray-200 dark:bg-gray-600"> <span class="text-xs text-gray-600 dark:text-gray-300 font-semibold">Options</span> </div>
<div class="py-3 px-6 rounded-tl-xl bg-coolGray-200 dark:bg-gray-600"> <span class="text-md lg:text-xs text-gray-600 dark:text-gray-300 font-semibold">Options</span> </div>
</th>
<th class="p-0">
<div class="py-3 px-6 rounded-tr-xl bg-coolGray-200 dark:bg-gray-600"> <span class="text-xs text-gray-600 dark:text-gray-300 font-semibold">Input</span> </div>
<div class="py-3 px-6 rounded-tr-xl bg-coolGray-200 dark:bg-gray-600"> <span class="text-md lg:text-xs text-gray-600 dark:text-gray-300 font-semibold">Input</span> </div>
</th>
</tr>
</thead>
<tr class="opacity-100 text-gray-500 dark:text-gray-100">
<td class="py-4 pl-6 bold"> <span class="inline-flex align-middle items-center justify-center w-9 h-10 bg-white-50 rounded"> <img class="h-7" src="/static/images/coins/{{ w.name }}.png" alt="{{ w.name }}"> </span> {{ w.name }} Balance: </td>
<td class="py-4 pl-6 bold"> <span class="inline-flex align-middle items-center justify-center w-9 h-10 bg-white-50 rounded"> <img class="h-7" src="/static/images/coins/{{ w.name }}.png" alt="{{ w.name }}"> </span>Balance: </td>
<td class="py-3 px-6" data-coinname="{{ w.name }}">{{ w.balance }} {{ w.ticker }} </td>
</tr>
{% if w.cid == '3' %}
{# LTC #}
<tr class="opacity-100 text-gray-500 dark:text-gray-100">
<td class="py-4 pl-6 bold w-1/4"> <span class="inline-flex align-middle items-center justify-center w-9 h-10 bg-white-50 rounded"> <img class="h-7" src="/static/images/coins/{{ w.name }}.png" alt="{{ w.name }}"> </span> {{ w.name }} MWEB Balance: </td>
<td class="py-4 pl-6 bold w-1/4"> <span class="inline-flex align-middle items-center justify-center w-9 h-10 bg-white-50 rounded"> <img class="h-7" src="/static/images/coins/{{ w.name }}.png" alt="{{ w.name }}"> </span>MWEB Balance: </td>
<td class="py-3 px-6" data-coinname="{{ w.name }}">{{ w.mweb_balance }} {{ w.ticker }} </td>
{% endif %}
{% if w.cid == '1' %}
</tr>
{% elif w.cid == '1' %}
{# PART #}
<tr class="opacity-100 text-gray-500 dark:text-gray-100">
<td class="py-4 pl-6 bold"> <span class="inline-flex align-middle items-center justify-center w-9 h-10 bg-white-50 rounded"> <img class="h-7" src="/static/images/coins/{{ w.name }}.png" alt="{{ w.name }}"> </span> {{ w.name }} Blind Balance: </td>
<td class="py-4 pl-6 bold"> <span class="inline-flex align-middle items-center justify-center w-9 h-10 bg-white-50 rounded"> <img class="h-7" src="/static/images/coins/{{ w.name }}.png" alt="{{ w.name }}"> </span>Blind Balance: </td>
<td class="py-3 px-6" data-coinname="{{ w.name }}">{{ w.blind_balance }} {{ w.ticker }} </td>
</tr>
<tr class="opacity-100 text-gray-500 dark:text-gray-100">
<td class="py-4 pl-6 bold"> <span class="inline-flex align-middle items-center justify-center w-9 h-10 bg-white-50 rounded"> <img class="h-7" src="/static/images/coins/{{ w.name }}.png" alt="{{ w.name }}"> </span> {{ w.name }} Anon Balance: </td>
<td class="py-3 px-6" data-coinname="{{ w.name }}">{{ w.anon_balance }} {{ w.ticker }} </td> {% endif %}
<td class="py-4 pl-6 bold"> <span class="inline-flex align-middle items-center justify-center w-9 h-10 bg-white-50 rounded"> <img class="h-7" src="/static/images/coins/{{ w.name }}.png" alt="{{ w.name }}"> </span>Anon Balance: </td>
<td class="py-3 px-6" data-coinname="{{ w.name }}">{{ w.anon_balance }} {{ w.ticker }} </td>
</tr>
{% endif %}
<tr class="opacity-100 text-gray-500 dark:text-gray-100">
<td class="py-4 pl-6 bold"> {{ w.name }} Address: </td>
<td class="py-3 px-6"> <input placeholder="{{ w.ticker }} Address" class="hover:border-blue-500 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-gray-400 text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 focus:ring-0" type="text" name="to_{{ w.cid }}" value="{{ w.wd_address }}"> </td>
<td class="py-3 px-6"> <input placeholder="{{ w.ticker }} Address" class="hover:border-blue-500 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-gray-400 text-lg lg:text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 focus:ring-0" type="text" name="to_{{ w.cid }}" value="{{ w.wd_address }}"> </td>
</tr>
<tr class="opacity-100 text-gray-500 dark:text-gray-100">
<td class="py-4 pl-6 bold"> {{ w.name }} Amount:
<td class="py-3 px-6">
<div class="flex"> <input placeholder="{{ w.ticker }} Amount" class="hover:border-blue-500 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-gray-400 text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 focus:ring-0" type="text" id="amount" name="amt_{{ w.cid }}" value="{{ w.wd_value }}">
<div class="flex"> <input placeholder="{{ w.ticker }} Amount" class="hover:border-blue-500 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-gray-400 text-lg lg:text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 focus:ring-0" type="text" id="amount" name="amt_{{ w.cid }}" value="{{ w.wd_value }}">
<div class="ml-2 flex">
{% if w.cid == '1' %}
{# PART #}
<button type="button" class="py-1 px-2 bg-blue-500 text-white text-sm rounded-md focus:outline-none" onclick="setAmount(0.25, '{{ w.balance }}', {{ w.cid }}, '{{ w.blind_balance }}', '{{ w.anon_balance }}')">25%</button>
<button type="button" class="ml-2 py-1 px-2 bg-blue-500 text-white text-sm rounded-md focus:outline-none" onclick="setAmount(0.5, '{{ w.balance }}', {{ w.cid }}, '{{ w.blind_balance }}', '{{ w.anon_balance }}')">50%</button>
<button type="button" class="ml-2 py-1 px-2 bg-blue-500 text-white text-sm rounded-md focus:outline-none" onclick="setAmount(1, '{{ w.balance }}', {{ w.cid }}, '{{ w.blind_balance }}', '{{ w.anon_balance }}')">100%</button>
<button type="button" class="hidden md:block py-1 px-2 bg-blue-500 text-white text-lg lg:text-sm rounded-md focus:outline-none" onclick="setAmount(0.25, '{{ w.balance }}', {{ w.cid }}, '{{ w.blind_balance }}', '{{ w.anon_balance }}')">25%</button>
<button type="button" class="hidden md:block ml-2 py-1 px-2 bg-blue-500 text-white text-lg lg:text-sm rounded-md focus:outline-none" onclick="setAmount(0.5, '{{ w.balance }}', {{ w.cid }}, '{{ w.blind_balance }}', '{{ w.anon_balance }}')">50%</button>
<button type="button" class="ml-2 py-1 px-2 bg-blue-500 text-white text-lg lg:text-sm rounded-md focus:outline-none" onclick="setAmount(1, '{{ w.balance }}', {{ w.cid }}, '{{ w.blind_balance }}', '{{ w.anon_balance }}')">100%</button>
<script>
function setAmount(percent, balance, cid, blindBalance, anonBalance) {
var amountInput = document.getElementById('amount');
@@ -618,9 +595,9 @@ document.addEventListener('DOMContentLoaded', function() {
{# / PART #}
{% elif w.cid == '3' %}
{# LTC #}
<button type="button" class="py-1 px-2 bg-blue-500 text-white text-sm rounded-md focus:outline-none" onclick="setAmount(0.25, '{{ w.balance }}', {{ w.cid }}, '{{ w.mweb_balance }}')">25%</button>
<button type="button" class="ml-2 py-1 px-2 bg-blue-500 text-white text-sm rounded-md focus:outline-none" onclick="setAmount(0.5, '{{ w.balance }}', {{ w.cid }}, '{{ w.mweb_balance }}')">50%</button>
<button type="button" class="ml-2 py-1 px-2 bg-blue-500 text-white text-sm rounded-md focus:outline-none" onclick="setAmount(1, '{{ w.balance }}', {{ w.cid }}, '{{ w.mweb_balance }}')">100%</button>
<button type="button" class="hidden md:block py-1 px-2 bg-blue-500 text-white text-lg lg:text-sm rounded-md focus:outline-none" onclick="setAmount(0.25, '{{ w.balance }}', {{ w.cid }}, '{{ w.mweb_balance }}')">25%</button>
<button type="button" class="hidden md:block ml-2 py-1 px-2 bg-blue-500 text-white text-lg lg:text-sm rounded-md focus:outline-none" onclick="setAmount(0.5, '{{ w.balance }}', {{ w.cid }}, '{{ w.mweb_balance }}')">50%</button>
<button type="button" class="ml-2 py-1 px-2 bg-blue-500 text-white text-lg lg:text-sm rounded-md focus:outline-none" onclick="setAmount(1, '{{ w.balance }}', {{ w.cid }}, '{{ w.mweb_balance }}')">100%</button>
<script>
function setAmount(percent, balance, cid, mwebBalance) {
var amountInput = document.getElementById('amount');
@@ -651,9 +628,9 @@ document.addEventListener('DOMContentLoaded', function() {
</script>
{# / LTC #}
{% else %}
<button type="button" class="py-1 px-2 bg-blue-500 text-white text-sm rounded-md focus:outline-none" onclick="setAmount(0.25, '{{ w.balance }}', {{ w.cid }})">25%</button>
<button type="button" class="ml-2 py-1 px-2 bg-blue-500 text-white text-sm rounded-md focus:outline-none" onclick="setAmount(0.5, '{{ w.balance }}', {{ w.cid }})">50%</button>
<button type="button" class="ml-2 py-1 px-2 bg-blue-500 text-white text-sm rounded-md focus:outline-none" onclick="setAmount(1, '{{ w.balance }}', {{ w.cid }})">100%</button>
<button type="button" class="hidden md:block py-1 px-2 bg-blue-500 text-white text-lg lg:text-sm rounded-md focus:outline-none" onclick="setAmount(0.25, '{{ w.balance }}', {{ w.cid }})">25%</button>
<button type="button" class="hidden md:block ml-2 py-1 px-2 bg-blue-500 text-white text-lg lg:text-sm rounded-md focus:outline-none" onclick="setAmount(0.5, '{{ w.balance }}', {{ w.cid }})">50%</button>
<button type="button" class="ml-2 py-1 px-2 bg-blue-500 text-white text-lg lg:text-sm rounded-md focus:outline-none" onclick="setAmount(1, '{{ w.balance }}', {{ w.cid }})">100%</button>
<script>
function setAmount(percent, balance, cid) {
var amountInput = document.getElementById('amount');
@@ -701,18 +678,20 @@ document.addEventListener('DOMContentLoaded', function() {
{% endif %}
</div>
</div>
</td>
</td>
</tr>
<tr class="opacity-100 text-gray-500 dark:text-gray-100">
{% if w.cid in '6, 9' %} {# XMR | WOW #}
<td class="py-3 px-6 bold">Sweep All:</td>
<td class="py-3 px-6">
<input class="hover:border-blue-500 w-5 h-5 form-check-input text-blue-600 bg-gray-50 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-1 dark:bg-gray-500 dark:border-gray-400" type="checkbox" id="sweepall" name="sweepall_{{ w.cid }}" {% if w.wd_sweepall==true %} checked=checked{% endif %}>
<td class="hidden py-3 px-6 bold">Sweep All:</td>
<td class="hidden py-3 px-6">
<input class="hover:border-blue-500 w-5 h-5 form-check-input text-blue-600 bg-gray-50 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-1 dark:bg-gray-500 dark:border-gray-400" type="checkbox" id="sweepall" name="sweepall_{{ w.cid }}" {% if w.wd_sweepall==true %} checked="checked"{% endif %}>
</td>
{% else %}
<td class="py-3 px-6 bold">Subtract Fee:</td>
<td class="py-3 px-6">
<input class="hover:border-blue-500 w-5 h-5 form-check-input text-blue-600 bg-gray-50 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-1 dark:bg-gray-500 dark:border-gray-400" type="checkbox" name="subfee_{{ w.cid }}" {% if w.wd_subfee==true %} checked=checked{% endif %}>
<input class="hover:border-blue-500 w-5 h-5 form-check-input text-blue-600 bg-gray-50 border-gray-300 rounded focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-1 dark:bg-gray-500 dark:border-gray-400" type="checkbox" name="subfee_{{ w.cid }}" {% if w.wd_subfee==true %} checked="checked"{% endif %}>
</td>
{% endif %}
<td>
@@ -746,9 +725,8 @@ document.addEventListener('DOMContentLoaded', function() {
</div>
</td>
</tr>
{% endif %}
{# / PART #}
{% if w.cid == '3' %} {# LTC #}
{% elif w.cid == '3' %} {# LTC #}
<tr class="opacity-100 text-gray-500 dark:text-gray-100">
<td class="py-3 px-6 bold">Type From:</td>
<td class="py-3 px-6">
@@ -762,14 +740,16 @@ document.addEventListener('DOMContentLoaded', function() {
</tr>
{% endif %}
{# / LTC #}
{% if w.cid not in '6,9' %} {# Not XMR WOW #}
<tr class="opacity-100 text-gray-500 dark:text-gray-100">
<td class="py-3 px-6 bold">Fee Rate:</td>
<td class="py-3 px-6">{{ w.fee_rate }}</td>
</tr>
<tr class="opacity-100 text-gray-500 dark:text-gray-100">
<td class="py-3 px-6 bold">Estimate Fee:</td>
<td class="py-3 px-6 bold">Fee Estimate:</td>
<td class="py-3 px-6"> {{ w.est_fee }} </td>
</tr>
{% endif %}
</table>
</div>
</div>
@@ -781,25 +761,24 @@ document.addEventListener('DOMContentLoaded', function() {
</div>
</section>
<section>
<div class="pl-6 pr-6 pt-0 pb-0 h-full overflow-hidden ">
<div class="px-6 py-0 h-full overflow-hidden">
<div class="pb-6 ">
<div class="flex flex-wrap items-center justify-between -m-2">
<div class="w-full pt-2">
<div class="container mx-auto">
<div class="pt-6 pb-6 bg-coolGray-100 border-t border-gray-100 dark:border-gray-400 dark:bg-gray-500 rounded-bl-xl rounded-br-xl">
<div class="lg:container mx-auto">
<div class="py-6 bg-coolGray-100 border-t border-gray-100 dark:border-gray-400 dark:bg-gray-500 rounded-bl-xl rounded-br-xl">
<div class="px-6">
<div class="flex flex-wrap justify-end">
{% if w.cid not in '6, 9' %}
{# !XMR | WOW #}
{% if w.show_utxo_groups %}
{% else %}
<div class="w-full md:w-auto p-1.5"> <button type="submit" class="flex flex-wrap justify-center px-4 py-2.5 bg-blue-500 hover:bg-blue-600 font-medium text-sm text-white border border-blue-500 rounded-md shadow-button focus:ring-0 focus:outline-none" id="showutxogroups" name="showutxogroups" value="Show UTXO Groups"> {{ utxo_groups_svg | safe }} Show UTXO Groups </button> </div>
{% endif %} {% endif %}
{% if w.cid in '6, 9' %}
{# XMR | WOW #}
<div class="w-full md:w-auto p-1.5 ml-2"> <button type="submit" class="flex flex-wrap justify-center w-full px-4 py-2.5 bg-blue-500 hover:bg-blue-600 font-medium text-sm text-white border border-blue-500 rounded-md shadow-button focus:ring-0 focus:outline-none" name="estfee_{{ w.cid }}" value="Estimate Fee">Estimate {{ w.ticker }} Fee </button> </div>
<div class="w-full md:w-auto p-1.5 mx-1"> <button type="submit" class="flex flex-wrap justify-center w-full px-4 py-2.5 bg-blue-500 hover:bg-blue-600 font-medium text-lg lg:text-sm text-white border border-blue-500 rounded-md shadow-button focus:ring-0 focus:outline-none" name="estfee_{{ w.cid }}" value="Estimate Fee">Estimate {{ w.ticker }} Fee </button> </div>
{# / XMR | WOW #}
{% elif w.show_utxo_groups %}
{% else %}
<div class="w-full md:w-auto p-1.5 mx-1"> <button type="submit" class="flex flex-wrap justify-center w-full px-4 py-2.5 bg-blue-500 hover:bg-blue-600 font-medium text-lg lg:text-sm text-white border border-blue-500 rounded-md shadow-button focus:ring-0 focus:outline-none" id="showutxogroups" name="showutxogroups" value="Show UTXO Groups"> {{ utxo_groups_svg | safe }} Show UTXO Groups </button> </div>
{% endif %}
{# / XMR | WOW #} <div class="w-full md:w-auto p-1.5 ml-2"> <button type="submit" class="flex flex-wrap justify-center w-full px-4 py-2.5 bg-blue-500 hover:bg-blue-600 font-medium text-sm text-white border border-blue-500 rounded-md shadow-button focus:ring-0 focus:outline-none" name="withdraw_{{ w.cid }}" value="Withdraw" onclick="return confirmWithdrawal();">{{ withdraw_svg | safe }} Withdraw {{ w.ticker }} </div>
<div class="w-full md:w-auto p-1.5 mx-1"> <button type="submit" class="flex flex-wrap justify-center w-full px-4 py-2.5 bg-blue-500 hover:bg-blue-600 font-medium text-lg lg:text-sm text-white border border-blue-500 rounded-md shadow-button focus:ring-0 focus:outline-none" name="withdraw_{{ w.cid }}" value="Withdraw" onclick="return confirmWithdrawal();">{{ withdraw_svg | safe }} Withdraw {{ w.ticker }} </button></div>
</div>
</div>
</div>
</div>
@@ -808,41 +787,41 @@ document.addEventListener('DOMContentLoaded', function() {
</div>
</div>
</section>
{% if w.cid not in '6, 9' %}
{# !XMR | WOW #}
{% if w.show_utxo_groups %}
<section class="p-6">
<div class="lg:container mx-auto">
<div class="flex items-center">
<h4 class="font-semibold text-2xl text-black dark:text-white">UTXO Groups</h4>
</div>
</div>
</section>
<section>
<div class="pl-6 pr-6 pt-0 pb-0 h-full overflow-hidden">
<div class="px-6 py-0 h-full overflow-hidden">
<div class="border-coolGray-100">
<div class="flex flex-wrap items-center justify-between -m-2">
<div class="w-full pt-2">
<div class="container mt-5 mx-auto">
<div class="lg:container mt-5 mx-auto">
<div class="pt-6 pb-8 bg-coolGray-100 dark:bg-gray-500 rounded-xl">
<div class="px-6">
<div class="w-full mt-6 pb-6 overflow-x-auto">
<table class="w-full min-w-max text-sm">
<div class="w-full pb-6 overflow-x-auto">
<table class="w-full text-lg lg:text-sm">
<thead class="uppercase">
<tr class="text-left">
<th class="p-0">
<div class="py-3 px-6 rounded-tl-xl bg-coolGray-200 dark:bg-gray-600"> <span class="text-xs text-gray-600 dark:text-gray-300 font-semibold">Options</span> </div>
<div class="py-3 px-6 rounded-tl-xl bg-coolGray-200 dark:bg-gray-600"> <span class="text-md lg:text-xs text-gray-600 dark:text-gray-300 font-semibold">Options</span> </div>
</th>
<th class="p-0">
<div class="py-3 px-6 rounded-tr-xl bg-coolGray-200 dark:bg-gray-600"> <span class="text-xs text-gray-600 dark:text-gray-300 font-semibold p-10"></span> </div>
<div class="py-3 px-6 rounded-tr-xl bg-coolGray-200 dark:bg-gray-600"> <span class="text-md lg:text-xs text-gray-600 dark:text-gray-300 font-semibold p-10"></span> </div>
</th>
</tr>
</thead>
<tr class="opacity-100 text-gray-500 dark:text-gray-100">
<td class="py-3 px-6 w-1/4 bold">UTXO Groups:</td>
<td class="py-3 px-6"> <textarea class="hover:border-blue-500 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-gray-50 text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 focus:ring-0" id="tx_view" rows="10" readonly>{{ w.utxo_groups }} </textarea> </td>
<td class="py-3 px-6"> <textarea class="hover:border-blue-500 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-gray-50 text-lg lg:text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 focus:ring-0" id="tx_view" rows="10" readonly>{{ w.utxo_groups }} </textarea> </td>
</tr>
<tr class="opacity-100 text-gray-500 dark:text-gray-100">
<td class="py-3 px-6"> <button type="submit" class="flex flex-wrap justify-center px-4 py-2 bg-blue-500 hover:bg-blue-600 font-medium text-sm text-white border border-blue-500 rounded-md shadow-button focus:ring-0 focus:outline-none" id="create_utxo" name="create_utxo" value="Create UTXO" onclick="return confirmUTXOResize();"> {{ create_utxo_svg | safe }}Create UTXO </button> </td>
<td class="py-3 px-6"> <input placeholder="Amount" class="hover:border-blue-500 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-gray-400 text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 focus:ring-0" type="text" name="utxo_value" value="{{ w.utxo_value }}"> </td>
<td class="py-3 px-6"> <button type="submit" class="flex flex-wrap justify-center px-4 py-2 bg-blue-500 hover:bg-blue-600 font-medium text-lg lg:text-sm text-white border border-blue-500 rounded-md shadow-button focus:ring-0 focus:outline-none" id="create_utxo" name="create_utxo" value="Create UTXO" onclick="return confirmUTXOResize();"> {{ create_utxo_svg | safe }}Create UTXO </button> </td>
<td class="py-3 px-6"> <input placeholder="Amount" class="hover:border-blue-500 bg-gray-50 text-gray-900 appearance-none pr-10 dark:bg-gray-500 dark:text-white border border-gray-300 dark:border-gray-400 dark:text-gray-50 dark:placeholder-gray-400 text-lg lg:text-sm rounded-lg outline-none focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 focus:ring-0" type="text" name="utxo_value" value="{{ w.utxo_value }}"> </td>
</tr>
</table>
</div>
@@ -855,14 +834,14 @@ document.addEventListener('DOMContentLoaded', function() {
</div>
</section>
<section>
<div class="pl-6 pr-6 pt-0 pb-0 h-full overflow-hidden ">
<div class="px-6 py-0 h-full overflow-hidden ">
<div class="pb-6 ">
<div class="flex flex-wrap items-center justify-between -m-2">
<div class="w-full pt-2">
<div class="container mx-auto">
<div class="pt-6 pb-6 bg-coolGray-100 border-t border-gray-100 dark:border-gray-400 dark:bg-gray-500 rounded-bl-xl rounded-br-xl">
<div class="lg:container mx-auto">
<div class="py-6 bg-coolGray-100 border-t border-gray-100 dark:border-gray-400 dark:bg-gray-500 rounded-bl-xl rounded-br-xl">
<div class="px-6">
<div class="flex flex-wrap justify-end"> <button type="submit" class="flex flex-wrap justify-center px-4 py-2.5 bg-blue-500 hover:bg-blue-600 font-medium text-sm text-white border border-blue-500 rounded-md shadow-button focus:ring-0 focus:outline-none" id="closeutxogroups" name="closeutxogroups" value="Close UTXO Groups"> {{ utxo_groups_svg | safe }} Close UTXO Groups </button> </div>
<div class="flex flex-wrap justify-end"> <button type="submit" class="flex flex-wrap justify-center px-4 py-2.5 bg-blue-500 hover:bg-blue-600 font-medium text-lg lg:text-sm text-white border border-blue-500 rounded-md shadow-button focus:ring-0 focus:outline-none" id="closeutxogroups" name="closeutxogroups" value="Close UTXO Groups"> {{ utxo_groups_svg | safe }} Close UTXO Groups </button> </div>
</div>
</div>
</div>
@@ -876,7 +855,6 @@ document.addEventListener('DOMContentLoaded', function() {
{% endif %}
{% endif %}
{% endif %}
{% endif %}
<input type="hidden" name="formid" value="{{ form_id }}">
</form>
<script>

View File

@@ -131,9 +131,9 @@ def parseOfferFormData(swap_client, form_data, page_data, options={}):
parsed_data["amt_bid_min"] < 0
or parsed_data["amt_bid_min"] > parsed_data["amt_from"]
):
errors.append("Minimum Bid Amount out of range")
errors.append("Minimum Purchase Quantity out of range")
except Exception:
errors.append("Minimum Bid Amount")
errors.append("Minimum Purchase Quantity")
if have_data_entry(form_data, "rate") and not have_data_entry(form_data, "amt_to"):
parsed_data["rate"] = ci_to.make_int(form_data["rate"], r=1)

View File

@@ -1,5 +1,5 @@
pyzmq==26.2.0
python-gnupg==0.5.3
python-gnupg==0.5.4
Jinja2==3.1.5
pycryptodome==3.21.0
PySocks==1.7.1

View File

@@ -190,9 +190,9 @@ pysocks==1.7.1 \
--hash=sha256:2725bd0a9925919b9b51739eea5f9e2bae91e83288108a9ad338b2e3a4435ee5 \
--hash=sha256:3f8804571ebe159c380ac6de37643bb4685970655d3bba243530d6558b799aa0
# via -r requirements.in
python-gnupg==0.5.3 \
--hash=sha256:290d8ddb9cd63df96cfe9284b9b265f19fd6e145e5582dc58fd7271f026d0a47 \
--hash=sha256:2f8a4c6f63766feca6cc1416408f8b84e1b914fe7b54514e570fc5cbe92e9248
python-gnupg==0.5.4 \
--hash=sha256:40ce25cde9df29af91fe931ce9df3ce544e14a37f62b13ca878c897217b2de6c \
--hash=sha256:f2fdb5fb29615c77c2743e1cb3d9314353a6e87b10c37d238d91ae1c6feae086
# via -r requirements.in
pyzmq==26.2.0 \
--hash=sha256:007137c9ac9ad5ea21e6ad97d3489af654381324d5d3ba614c323f60dab8fae6 \

View File

@@ -31,7 +31,7 @@ Create offers
# Optional
"enabled": Set to false to ignore offer template.
"swap_type": Type of swap, defaults to "adaptor_sig"
"min_swap_amount": Sets "amt_bid_min" on the offer, minimum valid bid when offer amount is variable.
"min_swap_amount": Sets "amt_bid_min" on the offer, minimum purchase quantity when offer amount is variable.
"amount_step": If set offers will be created for amount values between "amount" and "min_coin_from_amt" in decrements of "amount_step".
},
...