mirror of
https://github.com/Such-Software/wownerogue.git
synced 2026-10-20 07:38:34 +02:00
feat: network-aware currency label (sXMR), game name, and Smirk gating
- Currency display is now network-aware: XMR on a test network shows sXMR (stagenet) / tXMR (testnet) so it's clear the coins aren't mainnet; mainnet XMR shows XMR, Wownero stays WOW. A fork running mainnet automatically shows XMR. cryptoType (used for decimals/QR asset) is unchanged. - Product name (Monerogue/Wownerogue) is derived from the currency and no longer hardcoded: the chat "Connected to <name> server" message, payment descriptions, and the verify page title all follow it. - Smirk is forced off on any test network (stagenet/testnet) regardless of SMIRK_ENABLED, since Smirk only works on mainnet — so forks don't have to remember the env flag. New shared helpers (gameNameFor, currencyLabelFor, isTestNetworkFor); exposed via game_mode_info (currencyLabel, gameName). Full suite: 22 suites / 159 tests.
This commit is contained in:
@@ -515,7 +515,7 @@ const SocketHandlers = {
|
||||
if (won && data.payout && data.payout.amount) {
|
||||
var decimals = (SocketHandlers._cryptoType === 'WOW') ? 11 : 12;
|
||||
var amountFormatted = (data.payout.amount / Math.pow(10, decimals)).toFixed(4);
|
||||
var currency = SocketHandlers._cryptoType || 'WOW';
|
||||
var currency = SocketHandlers._currencyLabel || SocketHandlers._cryptoType || 'WOW';
|
||||
var multiplierText = data.payout.multiplier ? ' (' + data.payout.multiplier + 'x)' : '';
|
||||
var treasureText = data.payout.treasure ? ' + Treasure bonus!' : '';
|
||||
$('#messages').append($('<li style="color:#4ade80; font-weight:bold; padding:4px 0;">').html(
|
||||
@@ -608,6 +608,7 @@ const SocketHandlers = {
|
||||
SocketHandlers._paymentsEnabled = !!data.paymentsEnabled;
|
||||
SocketHandlers._smirkEnabled = data.smirkEnabled !== false; // Default to true if not specified
|
||||
SocketHandlers._cryptoType = data.cryptoType || 'WOW';
|
||||
SocketHandlers._currencyLabel = data.currencyLabel || data.cryptoType || 'WOW'; // sXMR on stagenet
|
||||
SocketHandlers._explorerTxUrl = data.explorerTxUrl || null;
|
||||
|
||||
// Initialize Smirk auth if enabled and not already initialized
|
||||
@@ -762,7 +763,7 @@ const SocketHandlers = {
|
||||
: '💳 <strong>Payment Required</strong>';
|
||||
parts.push(reusedCopy);
|
||||
const displayAmount = data.humanAmount || data.amountFormatted || data.amount;
|
||||
parts.push('Amount: ' + displayAmount + ' ' + (data.cryptoType || ''));
|
||||
parts.push('Amount: ' + displayAmount + ' ' + (data.currencyLabel || SocketHandlers._currencyLabel || data.cryptoType || ''));
|
||||
const shortAddr = data.address.substring(0, 10) + '…' + data.address.slice(-6);
|
||||
parts.push('Address: <span class="pay-address-full" style="cursor:pointer;" title="Click to toggle full address">' + shortAddr + '</span>' +
|
||||
' <button class="copy-pay-address" data-address="' + data.address + '" style="margin-left:4px;padding:1px 4px;font-size:11px;cursor:pointer;">Copy</button>');
|
||||
|
||||
@@ -104,7 +104,9 @@ const PaymentUI = {
|
||||
render: function() {
|
||||
if (!this.config) return;
|
||||
|
||||
const currency = this.config.cryptoType || 'WOW';
|
||||
// Display label (sXMR on stagenet) — distinct from cryptoType, which formatPrice
|
||||
// still uses for decimals.
|
||||
const currency = this.config.currencyLabel || this.config.cryptoType || 'WOW';
|
||||
const hasCredits = this.userCredits >= (this.config.creditsPerGame || 1);
|
||||
const creditPackages = this.config.creditPackages || [];
|
||||
|
||||
|
||||
@@ -9,7 +9,9 @@ const {
|
||||
inferCurrencyDecimals: inferCurrencyDecimalsForSymbol,
|
||||
formatAtomic,
|
||||
formatAtomicHuman,
|
||||
getDecimalDivisor
|
||||
getDecimalDivisor,
|
||||
gameNameFor,
|
||||
currencyLabelFor
|
||||
} = require('./helpers/gameModeUtils');
|
||||
const { ValidationError, normalizeError } = require('../utils/errors');
|
||||
const paymentConfig = require('../config/paymentConfig');
|
||||
@@ -68,6 +70,11 @@ class GameModeManager {
|
||||
return inferCurrencyDecimalsForSymbol(symbol);
|
||||
}
|
||||
|
||||
// Product name (Monerogue/Wownerogue) and network-aware currency label (sXMR on
|
||||
// stagenet, XMR on mainnet, WOW for Wownero) — derived so they always track config.
|
||||
get gameName() { return gameNameFor(this.cryptoType); }
|
||||
get currencyLabel() { return currencyLabelFor(this.cryptoType, this.network); }
|
||||
|
||||
applyLegacyEnvConfig() {
|
||||
this.setLegacyGameMode(process.env.GAME_MODE || 'FREE');
|
||||
this.cryptoType = process.env.CRYPTO_TYPE || this.cryptoType;
|
||||
@@ -904,7 +911,7 @@ class GameModeManager {
|
||||
switch (paymentType) {
|
||||
case 'single_game': {
|
||||
amount = this.singleGamePrice;
|
||||
description = `Wowngeon single game entry (${this.cryptoType})`;
|
||||
description = `${this.gameName} single game entry (${this.currencyLabel})`;
|
||||
break;
|
||||
}
|
||||
case 'credits_package': {
|
||||
@@ -927,7 +934,7 @@ class GameModeManager {
|
||||
const creditCount = selectedPackage?.credits ?? 10;
|
||||
const bonusCredits = selectedPackage?.bonus ?? 0;
|
||||
const bonusText = bonusCredits > 0 ? ` (+${bonusCredits} bonus)` : '';
|
||||
description = `Wowngeon ${creditCount}${bonusText} credits package (${this.cryptoType})`;
|
||||
description = `${this.gameName} ${creditCount}${bonusText} credits package (${this.currencyLabel})`;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@@ -1147,6 +1154,8 @@ class GameModeManager {
|
||||
return {
|
||||
mode: this.gameMode,
|
||||
cryptoType: this.cryptoType,
|
||||
currencyLabel: this.currencyLabel, // sXMR on stagenet, XMR on mainnet, WOW for Wownero
|
||||
gameName: this.gameName, // Monerogue / Wownerogue
|
||||
network: this.network,
|
||||
isTestNetwork: this.isTestNetwork,
|
||||
testnetWarning: testnetWarning,
|
||||
@@ -1176,8 +1185,9 @@ class GameModeManager {
|
||||
allowInFreeMode: this.configSnapshot?.earlyEntry?.allowInFreeMode ?? false,
|
||||
allowInCreditsMode: this.configSnapshot?.earlyEntry?.allowInCreditsMode ?? false
|
||||
},
|
||||
// Smirk wallet integration (disabled for Monero stagenet)
|
||||
smirkEnabled: process.env.SMIRK_ENABLED !== 'false',
|
||||
// Smirk wallet integration. Smirk only works on mainnet, so it is forced off on
|
||||
// any test network (stagenet/testnet) regardless of SMIRK_ENABLED.
|
||||
smirkEnabled: process.env.SMIRK_ENABLED !== 'false' && !this.isTestNetwork,
|
||||
explorerTxUrl: process.env.EXPLORER_TX_URL || null
|
||||
};
|
||||
}
|
||||
|
||||
@@ -30,6 +30,27 @@ const getDecimalDivisor = (decimals = DEFAULT_DECIMALS) => {
|
||||
return Math.pow(10, normalized);
|
||||
};
|
||||
|
||||
// The product name for a given currency (Monero -> Monerogue, otherwise Wownerogue).
|
||||
const gameNameFor = (cryptoType) => (String(cryptoType || '').toUpperCase() === 'XMR' ? 'Monerogue' : 'Wownerogue');
|
||||
|
||||
// The user-facing currency label, network-aware: Monero on a test network shows sXMR
|
||||
// (stagenet) / tXMR (testnet) so it's clear the coins aren't mainnet; mainnet shows XMR.
|
||||
// Wownero has no test networks, so it's always WOW.
|
||||
const currencyLabelFor = (cryptoType, network) => {
|
||||
const ct = String(cryptoType || '').toUpperCase();
|
||||
const net = String(network || 'mainnet').toLowerCase();
|
||||
if (ct === 'XMR' && (net === 'stagenet' || net === 'testnet')) {
|
||||
return (net === 'testnet' ? 't' : 's') + 'XMR';
|
||||
}
|
||||
return ct;
|
||||
};
|
||||
|
||||
// Smirk wallet only works on mainnet Monero/Wownero, so it must be off on test networks.
|
||||
const isTestNetworkFor = (network) => {
|
||||
const net = String(network || 'mainnet').toLowerCase();
|
||||
return net === 'stagenet' || net === 'testnet';
|
||||
};
|
||||
|
||||
const formatAtomic = ({ value, decimals, digits = 6 }) => {
|
||||
if (value === undefined || value === null) {
|
||||
return '0';
|
||||
@@ -60,5 +81,8 @@ module.exports = {
|
||||
inferCurrencyDecimals,
|
||||
getDecimalDivisor,
|
||||
formatAtomic,
|
||||
formatAtomicHuman
|
||||
formatAtomicHuman,
|
||||
gameNameFor,
|
||||
currencyLabelFor,
|
||||
isTestNetworkFor
|
||||
};
|
||||
|
||||
+5
-4
@@ -492,9 +492,10 @@ app.get('/api/user/:socketId/payouts', asyncHandler(async (req, res) => {
|
||||
// =============================================================================
|
||||
// Smirk Wallet Authentication Endpoints
|
||||
// =============================================================================
|
||||
// Smirk is a browser extension wallet for Wownero/Monero.
|
||||
// Disable for Monero stagenet deployments (Smirk doesn't support stagenet).
|
||||
const smirkEnabled = process.env.SMIRK_ENABLED !== 'false';
|
||||
// Smirk is a browser extension wallet for Wownero/Monero (mainnet only).
|
||||
// Forced off on any test network (stagenet/testnet), regardless of SMIRK_ENABLED.
|
||||
const { isTestNetworkFor } = require('./game/helpers/gameModeUtils');
|
||||
const smirkEnabled = process.env.SMIRK_ENABLED !== 'false' && !isTestNetworkFor(process.env.MONERO_NETWORK);
|
||||
if (smirkEnabled) {
|
||||
app.use(createAuthRoutes({ db: databaseManager }));
|
||||
}
|
||||
@@ -640,7 +641,7 @@ app.get('/verify/:gameId', asyncHandler(async (req, res) => {
|
||||
}
|
||||
|
||||
// Return the server-rendered HTML verification page (see src/views/verifyPage.js).
|
||||
res.send(renderVerifyPage(gameId, gameRecord));
|
||||
res.send(renderVerifyPage(gameId, gameRecord, gameModeManager.gameName));
|
||||
}));
|
||||
|
||||
// API endpoint for programmatic verification
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
const user = require('../db/user');
|
||||
const { clientIp } = require('./rateLimitContext');
|
||||
const { gameNameFor } = require('../game/helpers/gameModeUtils');
|
||||
|
||||
class ConnectionHandler {
|
||||
constructor({ io, broadcastManager, debugManager, sessionManager, rateLimiter }) {
|
||||
@@ -275,7 +276,7 @@ class ConnectionHandler {
|
||||
this.io.to(socket.id).emit('blockheight', { blockHeight: currentBlock });
|
||||
|
||||
// Send connection status
|
||||
this.broadcastManager.sendStatusUpdate(socket.id, 'connection', 'Connected to Wownerogue server');
|
||||
this.broadcastManager.sendStatusUpdate(socket.id, 'connection', `Connected to ${gameNameFor(process.env.CRYPTO_TYPE)} server`);
|
||||
}
|
||||
|
||||
_cleanupExistingMappings(socketId, clientId) {
|
||||
|
||||
@@ -14,7 +14,8 @@ function esc(value) {
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
|
||||
function renderVerifyPage(gameId, gameRecord) {
|
||||
function renderVerifyPage(gameId, gameRecord, gameName) {
|
||||
const name = gameName || 'Wownerogue';
|
||||
const recordBlock = gameRecord ? `
|
||||
<div class="box">
|
||||
<h3>Game Record</h3>
|
||||
@@ -31,7 +32,7 @@ function renderVerifyPage(gameId, gameRecord) {
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Wownerogue - Game Verification</title>
|
||||
<title>${esc(name)} - Game Verification</title>
|
||||
<style>
|
||||
body { background: #0a0a0a; color: #0f0; font-family: monospace; padding: 20px; max-width: 800px; margin: 0 auto; }
|
||||
h1 { color: #0ff; }
|
||||
@@ -48,7 +49,7 @@ function renderVerifyPage(gameId, gameRecord) {
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>🔐 Wownerogue Provably Fair Verification</h1>
|
||||
<h1>🔐 ${esc(name)} Provably Fair Verification</h1>
|
||||
|
||||
<div class="box">
|
||||
<h3>How it works:</h3>
|
||||
|
||||
Reference in New Issue
Block a user