Fix bid tooltips (Adaptor / Secret hash) and progress bar.

This commit is contained in:
gerlofvanek
2026-01-28 23:19:36 +01:00
parent 3fcd70098a
commit 39e134c46c
3 changed files with 142 additions and 16 deletions

View File

@@ -6,9 +6,15 @@ const BidPage = {
elapsedTimeInterval: null, elapsedTimeInterval: null,
AUTO_REFRESH_SECONDS: 60, AUTO_REFRESH_SECONDS: 60,
refreshPaused: false, refreshPaused: false,
swapType: null,
coinFrom: null,
coinTo: null,
previousStateInd: null,
INACTIVE_STATES: [8, 17, 18, 19, 21, 22, 23, 25, 31], // Completed, Failed variants, Timed-out, Abandoned, Error, Rejected, Expired INACTIVE_STATES: [8, 17, 18, 19, 21, 22, 23, 25, 31], // Completed, Failed variants, Timed-out, Abandoned, Error, Rejected, Expired
DELAYING_STATE: 20,
STATE_TOOLTIPS: { STATE_TOOLTIPS: {
'Bid Sent': 'Your bid has been broadcast to the network', 'Bid Sent': 'Your bid has been broadcast to the network',
'Bid Receiving': 'Receiving partial bid message from the network', 'Bid Receiving': 'Receiving partial bid message from the network',
@@ -18,14 +24,14 @@ const BidPage = {
'Bid Initiated': 'Swap initiated. First lock transaction is being created', 'Bid Initiated': 'Swap initiated. First lock transaction is being created',
'Bid Participating': 'Participating in the swap. Second lock transaction is being created', 'Bid Participating': 'Participating in the swap. Second lock transaction is being created',
'Bid Completed': 'Swap completed successfully! Both parties received their coins', 'Bid Completed': 'Swap completed successfully! Both parties received their coins',
'Bid Script coin locked': 'Your coins are locked in the atomic swap contract on the script chain (e.g., BTC/LTC)', 'Bid Script coin locked': null,
'Bid Script coin spend tx valid': 'The spend transaction for the script coin has been validated and is ready', 'Bid Script coin spend tx valid': null,
'Bid Scriptless coin locked': 'The other party\'s coins are locked using adaptor signatures (e.g., XMR)', 'Bid Scriptless coin locked': null,
'Bid Script coin lock released': 'Secret key revealed. The script coin can now be claimed', 'Bid Script coin lock released': 'Adaptor signature revealed. The script coin can now be claimed',
'Bid Script tx redeemed': 'Script coin has been successfully claimed', 'Bid Script tx redeemed': null,
'Bid Script pre-refund tx in chain': 'Pre-refund transaction detected. Swap may be failing', 'Bid Script pre-refund tx in chain': 'Pre-refund transaction detected. Swap may be failing',
'Bid Scriptless tx redeemed': 'Scriptless coin (e.g., XMR) has been successfully claimed', 'Bid Scriptless tx redeemed': null,
'Bid Scriptless tx recovered': 'Scriptless coin recovered after swap failure', 'Bid Scriptless tx recovered': null,
'Bid Failed, refunded': 'Swap failed but your coins have been refunded', 'Bid Failed, refunded': 'Swap failed but your coins have been refunded',
'Bid Failed, swiped': 'Swap failed due to an unexpected issue. Please check the event log for details', 'Bid Failed, swiped': 'Swap failed due to an unexpected issue. Please check the event log for details',
'Bid Failed': 'Swap failed. Check events for details', 'Bid Failed': 'Swap failed. Check events for details',
@@ -35,7 +41,7 @@ const BidPage = {
'Bid Error': 'An error occurred. Check events for details', 'Bid Error': 'An error occurred. Check events for details',
'Bid Rejected': 'Bid was rejected by the offer owner', 'Bid Rejected': 'Bid was rejected by the offer owner',
'Bid Stalled (debug)': 'Debug mode: swap intentionally stalled for testing', 'Bid Stalled (debug)': 'Debug mode: swap intentionally stalled for testing',
'Bid Exchanged script lock tx sigs msg': 'Exchanging cryptographic signatures needed for lock transactions', 'Bid Exchanged script lock tx sigs msg': 'Exchanging adaptor signatures needed for lock transactions',
'Bid Exchanged script lock spend tx msg': 'Exchanging signed spend transaction for locked coins', 'Bid Exchanged script lock spend tx msg': 'Exchanging signed spend transaction for locked coins',
'Bid Request sent': 'Connection request sent to the other party', 'Bid Request sent': 'Connection request sent to the other party',
'Bid Request accepted': 'Connection request accepted', 'Bid Request accepted': 'Connection request accepted',
@@ -59,6 +65,38 @@ const BidPage = {
'PTX In Chain': 'Participate transaction is included in a block' 'PTX In Chain': 'Participate transaction is included in a block'
}, },
getStateTooltip: function(stateText) {
const staticTooltip = this.STATE_TOOLTIPS[stateText];
if (staticTooltip !== null && staticTooltip !== undefined) {
return staticTooltip;
}
const scriptlessCoins = ['XMR', 'WOW'];
let scriptCoin, scriptlessCoin;
if (scriptlessCoins.includes(this.coinFrom)) {
scriptlessCoin = this.coinFrom;
scriptCoin = this.coinTo;
} else if (scriptlessCoins.includes(this.coinTo)) {
scriptlessCoin = this.coinTo;
scriptCoin = this.coinFrom;
} else {
scriptCoin = this.coinFrom;
scriptlessCoin = this.coinTo;
}
const dynamicTooltips = {
'Bid Script coin locked': `${scriptCoin} is locked in the atomic swap contract`,
'Bid Script coin spend tx valid': `The ${scriptCoin} spend transaction has been validated and is ready`,
'Bid Scriptless coin locked': `${scriptlessCoin} is locked using adaptor signatures`,
'Bid Script tx redeemed': `${scriptCoin} has been successfully claimed`,
'Bid Scriptless tx redeemed': `${scriptlessCoin} has been successfully claimed`,
'Bid Scriptless tx recovered': `${scriptlessCoin} recovered after swap failure`,
};
return dynamicTooltips[stateText] || null;
},
EVENT_TOOLTIPS: { EVENT_TOOLTIPS: {
'Lock tx A published': 'First lock transaction broadcast to the blockchain network', 'Lock tx A published': 'First lock transaction broadcast to the blockchain network',
'Lock tx A seen in mempool': 'First lock transaction detected in mempool (unconfirmed)', 'Lock tx A seen in mempool': 'First lock transaction detected in mempool (unconfirmed)',
@@ -131,13 +169,22 @@ const BidPage = {
34: { phase: 'negotiation', order: 0.4, label: 'Negotiation' } // CONNECT_REQ_SENT 34: { phase: 'negotiation', order: 0.4, label: 'Negotiation' } // CONNECT_REQ_SENT
}, },
init: function(bidId, bidStateInd, createdAtTimestamp, stateTimeTimestamp) { init: function(bidId, bidStateInd, createdAtTimestamp, stateTimeTimestamp, options) {
this.bidId = bidId; this.bidId = bidId;
this.bidStateInd = bidStateInd; this.bidStateInd = bidStateInd;
this.createdAtTimestamp = createdAtTimestamp; this.createdAtTimestamp = createdAtTimestamp;
this.stateTimeTimestamp = stateTimeTimestamp; this.stateTimeTimestamp = stateTimeTimestamp;
this.tooltipCounter = 0; this.tooltipCounter = 0;
options = options || {};
this.swapType = options.swapType || 'secret-hash';
this.coinFrom = options.coinFrom || '';
this.coinTo = options.coinTo || '';
if (this.bidStateInd === this.DELAYING_STATE) {
this.previousStateInd = this.findPreviousState();
}
this.applyStateTooltips(); this.applyStateTooltips();
this.applyEventTooltips(); this.applyEventTooltips();
this.createProgressBar(); this.createProgressBar();
@@ -145,6 +192,59 @@ const BidPage = {
this.setupAutoRefresh(); this.setupAutoRefresh();
}, },
findPreviousState: function() {
const sections = document.querySelectorAll('section');
let oldStatesSection = null;
sections.forEach(section => {
const h4 = section.querySelector('h4');
if (h4 && h4.textContent.includes('Old states')) {
oldStatesSection = section.nextElementSibling;
}
});
if (oldStatesSection) {
const table = oldStatesSection.querySelector('table');
if (table) {
const rows = table.querySelectorAll('tr');
for (let i = rows.length - 1; i >= 0; i--) {
const cells = rows[i].querySelectorAll('td');
if (cells.length >= 2) {
const stateText = cells[cells.length - 1].textContent.trim();
if (!stateText.includes('Delaying')) {
return this.stateTextToIndex(stateText);
}
}
}
}
}
return null;
},
stateTextToIndex: function(stateText) {
const stateMap = {
'Sent': 1, 'Receiving': 2, 'Received': 3, 'Receiving accept': 4,
'Accepted': 5, 'Initiated': 6, 'Participating': 7, 'Completed': 8,
'Script coin locked': 9, 'Script coin spend tx valid': 10,
'Scriptless coin locked': 11, 'Script coin lock released': 12,
'Script tx redeemed': 13, 'Script pre-refund tx in chain': 14,
'Scriptless tx redeemed': 15, 'Scriptless tx recovered': 16,
'Failed, refunded': 17, 'Failed, swiped': 18, 'Failed': 19,
'Delaying': 20, 'Timed-out': 21, 'Abandoned': 22, 'Error': 23,
'Rejected': 25, 'Exchanged script lock tx sigs msg': 27,
'Exchanged script lock spend tx msg': 28, 'Request sent': 29,
'Request accepted': 30, 'Expired': 31
};
for (const [key, value] of Object.entries(stateMap)) {
if (stateText.includes(key)) {
return value;
}
}
return null;
},
isActiveState: function() { isActiveState: function() {
return !this.INACTIVE_STATES.includes(this.bidStateInd); return !this.INACTIVE_STATES.includes(this.bidStateInd);
}, },
@@ -243,7 +343,7 @@ const BidPage = {
if (cells.length >= 2) { if (cells.length >= 2) {
const stateCell = cells[cells.length - 1]; const stateCell = cells[cells.length - 1];
const stateText = stateCell.textContent.trim(); const stateText = stateCell.textContent.trim();
const tooltip = this.STATE_TOOLTIPS[stateText]; const tooltip = this.getStateTooltip(stateText) || this.getStateTooltip('Bid ' + stateText);
if (tooltip) { if (tooltip) {
this.addHelpIcon(stateCell, tooltip); this.addHelpIcon(stateCell, tooltip);
} }
@@ -261,7 +361,7 @@ const BidPage = {
const valueCell = row.querySelectorAll('td')[1]; const valueCell = row.querySelectorAll('td')[1];
if (valueCell) { if (valueCell) {
const stateText = valueCell.textContent.trim(); const stateText = valueCell.textContent.trim();
const tooltip = this.STATE_TOOLTIPS[stateText] || this.STATE_TOOLTIPS['Bid ' + stateText]; const tooltip = this.getStateTooltip(stateText) || this.getStateTooltip('Bid ' + stateText);
if (tooltip) { if (tooltip) {
this.addHelpIcon(valueCell, tooltip); this.addHelpIcon(valueCell, tooltip);
} }
@@ -340,7 +440,15 @@ const BidPage = {
}, },
createProgressBar: function() { createProgressBar: function() {
const phaseInfo = this.STATE_PHASES[this.bidStateInd]; let stateForProgress = this.bidStateInd;
let isDelaying = false;
if (this.bidStateInd === this.DELAYING_STATE && this.previousStateInd) {
stateForProgress = this.previousStateInd;
isDelaying = true;
}
const phaseInfo = this.STATE_PHASES[stateForProgress];
if (!phaseInfo) return; if (!phaseInfo) return;
let progressPercent = 0; let progressPercent = 0;
@@ -373,7 +481,17 @@ const BidPage = {
const isError = ['failed', 'error'].includes(phase); const isError = ['failed', 'error'].includes(phase);
const isComplete = phase === 'complete'; const isComplete = phase === 'complete';
const barColor = isError ? 'bg-red-500' : (isComplete ? 'bg-green-500' : 'bg-blue-500'); const barColor = isError ? 'bg-red-500' : (isComplete ? 'bg-green-500' : 'bg-blue-500');
const phaseLabel = isError ? phaseInfo.label : (isComplete ? 'Complete' : `${phaseInfo.label} (${progressPercent}%)`);
let phaseLabel;
if (isError) {
phaseLabel = phaseInfo.label;
} else if (isComplete) {
phaseLabel = 'Complete';
} else if (isDelaying) {
phaseLabel = `${phaseInfo.label} (${progressPercent}%) - Delaying`;
} else {
phaseLabel = `${phaseInfo.label} (${progressPercent}%)`;
}
progressRow.innerHTML = ` progressRow.innerHTML = `
<td class="py-3 px-6 bold">Swap Progress</td> <td class="py-3 px-6 bold">Swap Progress</td>

View File

@@ -692,7 +692,11 @@ document.addEventListener('DOMContentLoaded', function() {
<script src="/static/js/pages/bid-page.js"></script> <script src="/static/js/pages/bid-page.js"></script>
<script> <script>
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
BidPage.init('{{ bid_id }}', {{ data.bid_state_ind }}, {{ data.created_at_timestamp }}, {{ data.state_time_timestamp or 'null' }}); BidPage.init('{{ bid_id }}', {{ data.bid_state_ind }}, {{ data.created_at_timestamp }}, {{ data.state_time_timestamp or 'null' }}, {
swapType: 'secret-hash',
coinFrom: '{{ data.ticker_from }}',
coinTo: '{{ data.ticker_to }}'
});
}); });
</script> </script>
</div> </div>

View File

@@ -968,7 +968,11 @@ document.addEventListener('DOMContentLoaded', function() {
<script src="/static/js/pages/bid-page.js"></script> <script src="/static/js/pages/bid-page.js"></script>
<script> <script>
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
BidPage.init('{{ bid_id }}', {{ data.bid_state_ind }}, {{ data.created_at_timestamp }}, {{ data.state_time_timestamp or 'null' }}); BidPage.init('{{ bid_id }}', {{ data.bid_state_ind }}, {{ data.created_at_timestamp }}, {{ data.state_time_timestamp or 'null' }}, {
swapType: 'adaptor-sig',
coinFrom: '{{ data.ticker_from }}',
coinTo: '{{ data.ticker_to }}'
});
}); });
</script> </script>
</div> </div>