').css({
+ position: 'fixed', top: 0, left: 0, width: '100%', height: '100%',
+ background: 'rgba(0,0,0,0.7)', zIndex: 3000,
+ display: 'flex', alignItems: 'center', justifyContent: 'center'
+ });
+
+ var $modal = $('
').css({
+ background: '#0a1a0a', border: '2px solid #0f0', borderRadius: '8px',
+ padding: '24px', maxWidth: '420px', width: '90%', color: '#0f0',
+ fontFamily: 'monospace', textAlign: 'center'
+ });
+
+ $modal.html(
+ '
How do you want to enter?
' +
+ '
Cost: ' + cost + ' credit per game
' +
+ '
' +
+ '
' +
+ '
'
+ );
+
+ $overlay.append($modal);
+ $('body').append($overlay);
+
+ // Handlers
+ $('#entryChoiceNow').on('click', function() {
+ $('#entryChoiceOverlay').remove();
+ socket.emit('auto_start');
+ $('#messages').append($('
').text('⚡ Starting game immediately...'));
+ UI.scrollChat();
+ if (typeof ScreenManager !== 'undefined' && ScreenManager.drawWaitingScreen) {
+ ScreenManager.drawWaitingScreen();
+ }
+ });
+
+ $('#entryChoiceQueue').on('click', function() {
+ $('#entryChoiceOverlay').remove();
+ socket.emit('join_queue');
+ $('#messages').append($('').text('🛡️ Joining queue — waiting for next block...'));
+ UI.scrollChat();
+ if (typeof ScreenManager !== 'undefined' && ScreenManager.drawWaitingScreen) {
+ ScreenManager.drawWaitingScreen();
+ }
+ });
+
+ $('#entryChoiceCancel').on('click', function() {
+ $('#entryChoiceOverlay').remove();
+ });
+
+ // Close on overlay click (outside modal)
+ $overlay.on('click', function(e) {
+ if (e.target === this) {
+ $('#entryChoiceOverlay').remove();
+ }
+ });
}
};
diff --git a/html/js/ui/transactionHistory.js b/html/js/ui/transactionHistory.js
index 78e8918..cfcc151 100644
--- a/html/js/ui/transactionHistory.js
+++ b/html/js/ui/transactionHistory.js
@@ -143,7 +143,11 @@ const TransactionHistory = {
${payout.reason || 'Game reward'} • ${date}
- ${payout.txHash ? `TX: ${payout.txHash.substring(0, 16)}...
` : ''}
+ ${payout.txHash ? `` : ''}
`);
diff --git a/src/.env.example b/src/.env.example
index f23bd34..e4505d4 100644
--- a/src/.env.example
+++ b/src/.env.example
@@ -391,6 +391,17 @@ ALERT_COOLDOWN_MS=3600000
# Set to false for Monero stagenet deployments
SMIRK_ENABLED=true
+# =============================================================================
+# SECTION 14b: BLOCK EXPLORER
+# =============================================================================
+# URL prefix for linking transaction hashes to a block explorer.
+# Include the trailing slash. TX hash will be appended directly.
+# Leave unset to disable clickable TX links in transaction history.
+#
+# Wownero: https://explore.wowne.ro/tx/
+# Monero: https://xmrchain.net/tx/
+EXPLORER_TX_URL=https://explore.wowne.ro/tx/
+
# =============================================================================
# SECTION 15: DEVELOPMENT/DEBUG OPTIONS
# =============================================================================
diff --git a/src/game/gameModeManager.js b/src/game/gameModeManager.js
index d795093..3142a76 100644
--- a/src/game/gameModeManager.js
+++ b/src/game/gameModeManager.js
@@ -1071,7 +1071,8 @@ class GameModeManager {
allowInCreditsMode: this.configSnapshot?.earlyEntry?.allowInCreditsMode ?? false
},
// Smirk wallet integration (disabled for Monero stagenet)
- smirkEnabled: process.env.SMIRK_ENABLED !== 'false'
+ smirkEnabled: process.env.SMIRK_ENABLED !== 'false',
+ explorerTxUrl: process.env.EXPLORER_TX_URL || null
};
}
diff --git a/src/index.js b/src/index.js
index eab09b6..b9d82ce 100644
--- a/src/index.js
+++ b/src/index.js
@@ -424,7 +424,7 @@ app.get('/api/user/:socketId/payouts', asyncHandler(async (req, res) => {
// Calculate total received (confirmed payouts)
const totalResult = await db.query(
- `SELECT COALESCE(SUM(amount), 0) as total_received FROM payouts WHERE user_id = $1 AND status = 'confirmed'`,
+ `SELECT COALESCE(SUM(amount), 0) as total_received FROM payouts WHERE user_id = $1 AND status = 'completed'`,
[user.id]
);
diff --git a/src/network/socketHandlers.js b/src/network/socketHandlers.js
index a1e8164..aa4ff00 100644
--- a/src/network/socketHandlers.js
+++ b/src/network/socketHandlers.js
@@ -463,6 +463,7 @@ class SocketHandlers {
}
});
socket.on('auto_start', () => this.handleAutoStart(socket)); // New handler for start button
+ socket.on('join_queue', () => this.handleJoinQueue(socket)); // Queue instead of auto-start
socket.on('early_entry', () => this.handleEarlyEntry(socket)); // Early entry without waiting for block
socket.on('address:prompt', () => this.handleAddressPrompt(socket));
@@ -581,6 +582,32 @@ class SocketHandlers {
}
}
+ /**
+ * Handle join_queue request — adds player to block queue instead of starting immediately
+ */
+ async handleJoinQueue(socket) {
+ try {
+ await this.queueHandler.handleGameQueue(
+ socket,
+ this.connectionHandler.getUserBySocket.bind(this.connectionHandler)
+ );
+ // queueHandler already emits queue status and adds to queue
+ // Client receives queue_joined event from broadcastManager
+ const pos = this.queueManager.getPlayerIndex(socket.id);
+ if (pos !== -1) {
+ const currentBlock = this.debugManager.getCurrentBlockHeight ? this.debugManager.getCurrentBlockHeight() : 0;
+ socket.emit('queue_joined', {
+ position: pos + 1,
+ currentBlock: currentBlock,
+ nextBlock: currentBlock + 1
+ });
+ }
+ } catch (err) {
+ console.error('handleJoinQueue error:', err);
+ this.broadcastManager.sendStatusUpdate(socket.id, 'error', 'Failed to join queue.');
+ }
+ }
+
/**
* Handle early entry request - start game immediately without waiting for block
* Only available for free mode and credits mode (not direct payment mode)