mirror of
https://github.com/Such-Software/wownerogue.git
synced 2026-10-21 08:08:32 +02:00
Get back into it
This commit is contained in:
+5
-4
@@ -4,7 +4,7 @@
|
||||
# PAID_SINGLE: Users pay per game, get 2x for escape, 3x with treasure
|
||||
# PAID_CREDITS: Users buy 10 credits, play 10 times, no payouts
|
||||
# FREE: Free play mode, no payments required
|
||||
GAME_MODE=FREE
|
||||
GAME_MODE=PAID_SINGLE
|
||||
|
||||
# Cryptocurrency Configuration
|
||||
CRYPTO_TYPE=XMR
|
||||
@@ -14,9 +14,10 @@ CRYPTO_TYPE=XMR
|
||||
SINGLE_GAME_PRICE=5000000000
|
||||
CREDITS_PACKAGE_PRICE=50000000000
|
||||
|
||||
# MoneroPay Configuration
|
||||
MONEROPAY_ENDPOINT=http://jw-nodes:5000
|
||||
MONEROPAY_CALLBACK_BASE_URL=http://localhost:3000/api/payments/callback
|
||||
# Wallet RPC Configuration (replaces MoneroPay)
|
||||
PRIMARY_WALLET_ENDPOINT=http://127.0.0.1:34570
|
||||
# WALLET_RPC_USER=user
|
||||
# WALLET_RPC_PASSWORD=password
|
||||
|
||||
# Blockchain RPC Configuration
|
||||
PRIMARY_RPC_ENDPOINT=http://jw-nodes:18081
|
||||
|
||||
+31
-12
@@ -103,36 +103,55 @@ class DebugManager {
|
||||
console.log("🚀 PRODUCTION MODE ENABLED - Using real blockchain RPC calls");
|
||||
}
|
||||
|
||||
// Real blockchain monitoring
|
||||
this.debugInterval = setInterval(() => {
|
||||
rpc.daemonCall("get_block_count", "", (result) => {
|
||||
if (!result || !result.result || !result.result.count) {
|
||||
// Initialize RPC service if available
|
||||
const RpcService = require('../rpc/rpcService');
|
||||
this.rpcService = new RpcService();
|
||||
|
||||
// Real blockchain monitoring with new RPC service
|
||||
this.debugInterval = setInterval(async () => {
|
||||
try {
|
||||
const currentHeight = await this.rpcService.getBlockHeight();
|
||||
|
||||
if (!currentHeight) {
|
||||
if (this.CONSOLE_LOGGING) {
|
||||
console.log("❌ Failed to get block count from daemon");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const currentHeight = result.result.count;
|
||||
rpc.lastBlock.setHeight(currentHeight);
|
||||
// Always broadcast current height to keep clients updated
|
||||
this.broadcastManager.broadcastBlockHeight(currentHeight);
|
||||
|
||||
// If new block found
|
||||
if (currentHeight > this.lastProductionBlockHeight) {
|
||||
if (this.CONSOLE_LOGGING) {
|
||||
console.log(`⛏️ New block found: ${currentHeight}`);
|
||||
}
|
||||
this.broadcastManager.broadcastBlockHeight(currentHeight);
|
||||
|
||||
// Notify listeners about new block
|
||||
this.onNewBlock(currentHeight);
|
||||
|
||||
this.lastProductionBlockHeight = currentHeight;
|
||||
} else {
|
||||
// Still broadcast current height for status updates
|
||||
this.broadcastManager.broadcastBlockHeight(currentHeight);
|
||||
}
|
||||
});
|
||||
}, 5000); // Every 5 seconds
|
||||
} catch (error) {
|
||||
if (this.CONSOLE_LOGGING) {
|
||||
console.error("❌ RPC Error:", error.message);
|
||||
}
|
||||
|
||||
// Fallback to legacy RPC if new service fails
|
||||
rpc.daemonCall("get_block_count", "", (result) => {
|
||||
if (result && result.result && result.result.count) {
|
||||
const currentHeight = result.result.count;
|
||||
this.broadcastManager.broadcastBlockHeight(currentHeight);
|
||||
|
||||
if (currentHeight > this.lastProductionBlockHeight) {
|
||||
this.onNewBlock(currentHeight);
|
||||
this.lastProductionBlockHeight = currentHeight;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}, 2000); // Every 2 seconds as specified in .env
|
||||
}
|
||||
|
||||
// ====== BLOCK HEIGHT MANAGEMENT ======
|
||||
|
||||
+101
-10
@@ -4,9 +4,10 @@
|
||||
*/
|
||||
|
||||
class GameModeManager {
|
||||
constructor(databaseManager, moneroPayService) {
|
||||
constructor(databaseManager, walletRPCService, debugManager) {
|
||||
this.db = databaseManager;
|
||||
this.moneroPayService = moneroPayService;
|
||||
this.walletService = walletRPCService; // Changed from moneroPayService
|
||||
this.debugManager = debugManager;
|
||||
this.gameMode = process.env.GAME_MODE || 'FREE';
|
||||
this.cryptoType = process.env.CRYPTO_TYPE || 'XMR';
|
||||
this.singleGamePrice = parseInt(process.env.SINGLE_GAME_PRICE) || 5000000000; // 0.005 XMR
|
||||
@@ -184,7 +185,7 @@ class GameModeManager {
|
||||
}
|
||||
|
||||
// Create payout record
|
||||
await this.moneroPayService.createPayout(
|
||||
await this.walletService.processPayout(
|
||||
game.user_id,
|
||||
gameId,
|
||||
game.payout_address,
|
||||
@@ -229,25 +230,44 @@ class GameModeManager {
|
||||
switch (paymentType) {
|
||||
case 'single_game':
|
||||
amount = this.singleGamePrice;
|
||||
description = `Wowgue single game entry (${this.cryptoType})`;
|
||||
description = `Wowngeon single game entry (${this.cryptoType})`;
|
||||
break;
|
||||
|
||||
case 'credits_package':
|
||||
amount = this.creditsPackagePrice;
|
||||
description = `Wowgue 10 credits package (${this.cryptoType})`;
|
||||
description = `Wowngeon 10 credits package (${this.cryptoType})`;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Error('Invalid payment type');
|
||||
}
|
||||
|
||||
return await this.moneroPayService.createPaymentRequest(
|
||||
user.id,
|
||||
socketId,
|
||||
paymentType,
|
||||
// Create payment request using wallet RPC with correct parameters
|
||||
const paymentResult = await this.walletService.createPaymentRequest(
|
||||
amount,
|
||||
description
|
||||
description,
|
||||
user.id,
|
||||
socketId
|
||||
);
|
||||
|
||||
if (!paymentResult.success) {
|
||||
throw new Error(paymentResult.error || 'Failed to create payment address');
|
||||
}
|
||||
|
||||
// Store payment info in database
|
||||
await this.db.query(`
|
||||
INSERT INTO payments (user_id, subaddress, expected_amount, payment_type, status, created_at)
|
||||
VALUES ($1, $2, $3, $4, 'pending', NOW())
|
||||
`, [user.id, paymentResult.address, amount, paymentType]);
|
||||
|
||||
return {
|
||||
id: paymentResult.id,
|
||||
address: paymentResult.address,
|
||||
amount: amount,
|
||||
amountFormatted: (amount / 1e12).toFixed(4), // Convert from atomic units
|
||||
currency: this.cryptoType,
|
||||
expiresAt: paymentResult.expiresAt
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Error creating payment request:', error.message);
|
||||
@@ -360,6 +380,77 @@ class GameModeManager {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete a game (called from socket handlers when game ends)
|
||||
* @param {string} socketId - Player's socket ID
|
||||
* @param {string} gameId - Game UUID
|
||||
* @param {boolean} won - Whether the player won (escaped)
|
||||
* @param {boolean} treasureFound - Whether treasure was found
|
||||
* @returns {object} payout / completion info
|
||||
*/
|
||||
async completeGame(socketId, gameId, won, treasureFound) {
|
||||
// For FREE mode we just return basic info
|
||||
if (this.gameMode === 'FREE') {
|
||||
return { success: true, mode: 'FREE', payout: null };
|
||||
}
|
||||
|
||||
try {
|
||||
// Update/record game completion in DB (simplified; reuse processGameCompletion if schema matches)
|
||||
// If a games table exists with at least id & user reference, mark completion.
|
||||
try {
|
||||
await this.db.query(`
|
||||
UPDATE games SET status = $1, treasure_found = $2, completed_at = NOW()
|
||||
WHERE id = $3
|
||||
`, [won ? 'won' : 'lost', treasureFound, gameId]);
|
||||
} catch (e) {
|
||||
// Non-fatal if games table differs during early dev.
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.warn('Game completion update warning:', e.message);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle payouts only in PAID_SINGLE mode and only on win (escaped)
|
||||
if (this.gameMode === 'PAID_SINGLE' && won) {
|
||||
// Derive payout multiplier
|
||||
const multiplier = treasureFound ? 3 : 2;
|
||||
const payoutAmount = this.singleGamePrice * multiplier;
|
||||
|
||||
// Look up user record for payout address
|
||||
const userResult = await this.db.query(`SELECT * FROM users WHERE socket_id = $1 LIMIT 1`, [socketId]);
|
||||
const userRow = userResult.rows[0];
|
||||
if (userRow && userRow.payout_address) {
|
||||
try {
|
||||
await this.walletService.processPayout(
|
||||
userRow.id,
|
||||
gameId,
|
||||
userRow.payout_address,
|
||||
payoutAmount
|
||||
);
|
||||
return {
|
||||
success: true,
|
||||
mode: this.gameMode,
|
||||
payout: {
|
||||
amount: payoutAmount,
|
||||
multiplier,
|
||||
treasure: treasureFound
|
||||
}
|
||||
};
|
||||
} catch (payoutErr) {
|
||||
console.error('❌ Error creating payout:', payoutErr.message);
|
||||
return { success: true, mode: this.gameMode, payout: null, payoutError: payoutErr.message };
|
||||
}
|
||||
}
|
||||
return { success: true, mode: this.gameMode, payout: null, reason: 'No payout address' };
|
||||
}
|
||||
|
||||
// Credits mode: decrement nothing here (already handled start). Optionally could award stats.
|
||||
return { success: true, mode: this.gameMode, payout: null };
|
||||
} catch (err) {
|
||||
console.error('❌ completeGame error:', err.message);
|
||||
return { success: false, error: err.message };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = GameModeManager;
|
||||
|
||||
+21
-18
@@ -10,7 +10,7 @@ require('dotenv').config();
|
||||
|
||||
// Import payment system components
|
||||
const DatabaseManager = require('./db/databaseManager');
|
||||
const MoneroPayService = require('./payments/moneroPayService');
|
||||
const WalletRPCService = require('./payments/walletRPCService');
|
||||
const GameModeManager = require('./game/gameModeManager');
|
||||
const RpcService = require('./rpc/rpcService');
|
||||
|
||||
@@ -19,17 +19,19 @@ const BroadcastManager = require('./network/broadcastManager');
|
||||
const DebugManager = require('./debug/debugManager');
|
||||
const SocketHandlers = require('./network/socketHandlers');
|
||||
|
||||
// Initialize payment system components
|
||||
const databaseManager = new DatabaseManager();
|
||||
const rpcService = new RpcService();
|
||||
const moneroPayService = new MoneroPayService(databaseManager, rpcService);
|
||||
const gameModeManager = new GameModeManager(databaseManager, moneroPayService);
|
||||
|
||||
// Initialize modular components
|
||||
// Initialize modular components first
|
||||
const broadcastManager = new BroadcastManager(io);
|
||||
const debugManager = new DebugManager(broadcastManager);
|
||||
|
||||
// Initialize payment system components (debugManager is now available)
|
||||
const databaseManager = new DatabaseManager();
|
||||
const rpcService = new RpcService();
|
||||
const walletRPCService = new WalletRPCService(debugManager);
|
||||
const gameModeManager = new GameModeManager(databaseManager, walletRPCService, debugManager);
|
||||
|
||||
// Initialize remaining components
|
||||
const activeGames = new Map(); // Maps socketId to Game objects
|
||||
const socketHandlers = new SocketHandlers(io, activeGames, broadcastManager, debugManager, gameModeManager);
|
||||
const socketHandlers = new SocketHandlers(io, activeGames, broadcastManager, debugManager, gameModeManager, walletRPCService);
|
||||
// Configure static file serving
|
||||
const htmlPath = path.join(__dirname, '../html');
|
||||
app.use(express.static(htmlPath));
|
||||
@@ -56,7 +58,7 @@ app.post('/api/payment/create', async (req, res) => {
|
||||
return res.status(400).json({ error: 'Missing userId or gameMode' });
|
||||
}
|
||||
|
||||
const payment = await moneroPayService.createPaymentRequest(userId, gameMode);
|
||||
const payment = await walletRPCService.createPaymentRequest(userId, gameMode);
|
||||
res.json(payment);
|
||||
} catch (error) {
|
||||
console.error('Error creating payment:', error);
|
||||
@@ -66,7 +68,7 @@ app.post('/api/payment/create', async (req, res) => {
|
||||
|
||||
app.post('/api/payment/callback', async (req, res) => {
|
||||
try {
|
||||
const result = await moneroPayService.processCallback(req.body);
|
||||
const result = await walletRPCService.processCallback(req.body);
|
||||
res.json({ status: 'success', result });
|
||||
} catch (error) {
|
||||
console.error('Error processing callback:', error);
|
||||
@@ -76,7 +78,7 @@ app.post('/api/payment/callback', async (req, res) => {
|
||||
|
||||
app.get('/api/payment/status/:paymentId', async (req, res) => {
|
||||
try {
|
||||
const status = await moneroPayService.checkPaymentStatus(req.params.paymentId);
|
||||
const status = await walletRPCService.checkPaymentStatus(req.params.paymentId);
|
||||
res.json(status);
|
||||
} catch (error) {
|
||||
console.error('Error checking payment status:', error);
|
||||
@@ -122,7 +124,12 @@ debugManager.onNewBlockCallback((blockHeight) => {
|
||||
// Initialize payment system
|
||||
async function initializePaymentSystem() {
|
||||
try {
|
||||
console.log('🔄 Initializing payment system...');
|
||||
console.log('🔧 Initializing payment system...');
|
||||
const walletInitialized = await walletRPCService.initialize();
|
||||
if (!walletInitialized) {
|
||||
console.log('⚠️ Wallet RPC not available - falling back to FREE mode');
|
||||
process.env.GAME_MODE = 'FREE';
|
||||
}
|
||||
|
||||
// Initialize database connection and run migrations
|
||||
await databaseManager.initialize();
|
||||
@@ -132,10 +139,6 @@ async function initializePaymentSystem() {
|
||||
const rpcHealth = await rpcService.healthCheck();
|
||||
console.log(`✅ RPC Service: ${rpcHealth.healthy ? 'Connected' : 'Warning - using fallback'}`);
|
||||
|
||||
// Initialize MoneroPay service
|
||||
await moneroPayService.initialize();
|
||||
console.log('✅ MoneroPay service initialized');
|
||||
|
||||
console.log('🚀 Payment system ready!');
|
||||
return true;
|
||||
} catch (error) {
|
||||
@@ -187,7 +190,7 @@ async function startServer() {
|
||||
if (paymentSystemReady) {
|
||||
setInterval(async () => {
|
||||
try {
|
||||
await moneroPayService.processBatchPayouts();
|
||||
await walletRPCService.processBatchPayouts();
|
||||
} catch (error) {
|
||||
console.error('Error in batch payout processing:', error);
|
||||
}
|
||||
|
||||
+448
-98
@@ -7,12 +7,13 @@ const user = require('../db/user');
|
||||
const Game = require('../game/game');
|
||||
|
||||
class SocketHandlers {
|
||||
constructor(io, activeGames, broadcastManager, debugManager, gameModeManager = null) {
|
||||
constructor(io, activeGames, broadcastManager, debugManager, gameModeManager = null, walletService = null) {
|
||||
this.io = io;
|
||||
this.activeGames = activeGames;
|
||||
this.broadcastManager = broadcastManager;
|
||||
this.debugManager = debugManager;
|
||||
this.gameModeManager = gameModeManager;
|
||||
this.walletService = walletService;
|
||||
this.playerMoveTimestamps = new Map();
|
||||
this.clientSocketMap = new Map();
|
||||
this.MOVE_COOLDOWN = 100; // Minimum 100ms between moves
|
||||
@@ -115,6 +116,11 @@ class SocketHandlers {
|
||||
'Please paste your XMR/WOW address directly in chat. The system will automatically detect and confirm it.');
|
||||
return;
|
||||
|
||||
case 'payment':
|
||||
case 'pay':
|
||||
this.handlePaymentCommand(socket);
|
||||
return;
|
||||
|
||||
default:
|
||||
// Broadcast as chat message to all clients
|
||||
this.handleChatBroadcast(socket, msg);
|
||||
@@ -168,9 +174,9 @@ class SocketHandlers {
|
||||
/**
|
||||
* Handle game queue request (typing "enter")
|
||||
*/
|
||||
handleGameQueue(socket) {
|
||||
async handleGameQueue(socket) {
|
||||
if (this.debugManager.CONSOLE_LOGGING) {
|
||||
console.log(`Player ${socket.id} requested to enter the dungeon - ADDING TO QUEUE`);
|
||||
console.log(`Player ${socket.id} requested to enter the dungeon`);
|
||||
}
|
||||
|
||||
const currentUser = this.getUserBySocket(socket.id);
|
||||
@@ -192,11 +198,47 @@ class SocketHandlers {
|
||||
return;
|
||||
}
|
||||
|
||||
// Add to waiting queue
|
||||
// Check payment eligibility if payment system is available
|
||||
if (this.gameModeManager) {
|
||||
try {
|
||||
const eligibility = await this.gameModeManager.canUserStartGame(socket.id);
|
||||
|
||||
if (!eligibility.allowed) {
|
||||
if (this.debugManager.CONSOLE_LOGGING) {
|
||||
console.log(`❌ Payment required for ${socket.id}: ${eligibility.reason}`);
|
||||
}
|
||||
|
||||
// Create payment request immediately instead of asking user to type 'payment'
|
||||
await this.createAndShowPaymentRequest(socket);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.debugManager.CONSOLE_LOGGING) {
|
||||
console.log(`✅ Payment validated for ${socket.id}: ${eligibility.reason}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error checking payment eligibility:', error);
|
||||
|
||||
// In paid mode, don't add to queue if payment check fails
|
||||
if (this.gameModeManager.gameMode !== 'FREE') {
|
||||
this.broadcastManager.sendStatusUpdate(socket.id, 'error',
|
||||
'Payment system error. Please try again or contact support.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Only in FREE mode, continue to queue on payment system errors
|
||||
this.broadcastManager.sendStatusUpdate(socket.id, 'warning', 'Payment system unavailable. Playing in FREE mode.');
|
||||
}
|
||||
}
|
||||
|
||||
// Add to waiting queue (in free mode or already authorized paid mode)
|
||||
this.WAITING_PLAYERS.push({
|
||||
serverId: socket.id,
|
||||
clientId: currentUser.clientId,
|
||||
entryTime: Date.now()
|
||||
entryTime: Date.now(),
|
||||
// For paid modes, this path only executes if payment already confirmed (canUserStartGame returned allowed)
|
||||
requiresConfirmation: false,
|
||||
confirmed: true
|
||||
});
|
||||
|
||||
const currentBlock = this.debugManager.getCurrentBlockHeight();
|
||||
@@ -218,91 +260,15 @@ class SocketHandlers {
|
||||
console.log(`Player ${socket.id} requested auto-start via start button`);
|
||||
}
|
||||
|
||||
const currentUser = this.getUserBySocket(socket.id);
|
||||
if (!currentUser) {
|
||||
this.broadcastManager.sendStatusUpdate(socket.id, 'error', 'Error: Could not start game. Please try again.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if already in a game
|
||||
if (this.activeGames.has(socket.id)) {
|
||||
this.broadcastManager.sendStatusUpdate(socket.id, 'error', 'You are already in a game!');
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if payment system is available and validate eligibility
|
||||
if (this.gameModeManager) {
|
||||
try {
|
||||
const eligibility = await this.gameModeManager.canUserStartGame(socket.id);
|
||||
|
||||
if (!eligibility.allowed) {
|
||||
// Send payment requirement to client
|
||||
this.io.to(socket.id).emit('payment_required', {
|
||||
reason: eligibility.reason,
|
||||
action: eligibility.action,
|
||||
gameMode: this.gameModeManager.gameMode
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.debugManager.CONSOLE_LOGGING) {
|
||||
console.log(`✅ Payment validated for ${socket.id}: ${eligibility.reason}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error checking payment eligibility:', error);
|
||||
this.broadcastManager.sendStatusUpdate(socket.id, 'error', 'Payment system error. Playing in FREE mode.');
|
||||
}
|
||||
}
|
||||
|
||||
// Remove from waiting queue if present
|
||||
const waitingIndex = this.WAITING_PLAYERS.findIndex(p => p.serverId === socket.id);
|
||||
if (waitingIndex !== -1) {
|
||||
this.WAITING_PLAYERS.splice(waitingIndex, 1);
|
||||
if (this.debugManager.CONSOLE_LOGGING) {
|
||||
console.log(`🚀 AUTO-START: Removed ${socket.id} from waiting queue for immediate start`);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.debugManager.CONSOLE_LOGGING) {
|
||||
console.log(`Found user for socket ${socket.id}, starting game immediately...`);
|
||||
}
|
||||
|
||||
try {
|
||||
// For auto-entry, player enters on current block
|
||||
const currentBlock = this.debugManager.getCurrentBlockHeight();
|
||||
currentUser.blockRec = currentBlock;
|
||||
if (this.debugManager.CONSOLE_LOGGING) {
|
||||
console.log(`🕒 AUTO-START: Player enters on block ${currentUser.blockRec}, will die when block ${currentUser.blockRec + 1} starts`);
|
||||
}
|
||||
|
||||
const game = this.createGameForUser(currentUser, 'standard');
|
||||
|
||||
const gameState = game.getState();
|
||||
gameState.blockHeight = currentBlock;
|
||||
|
||||
if (this.debugManager.CONSOLE_LOGGING) {
|
||||
console.log(`🎮 SENDING IMMEDIATE GAME_START to ${socket.id} (AUTO-START)`);
|
||||
}
|
||||
this.io.to(socket.id).emit('game_start', gameState);
|
||||
|
||||
// Register game with payment system if available
|
||||
if (this.gameModeManager) {
|
||||
try {
|
||||
await this.gameModeManager.startGame(socket.id, game.id);
|
||||
} catch (error) {
|
||||
console.error('Error registering game with payment system:', error);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.debugManager.CONSOLE_LOGGING) {
|
||||
console.log(`Game started immediately for player ${socket.id} via auto-start`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error creating game:`, error);
|
||||
this.broadcastManager.sendStatusUpdate(socket.id, 'error', 'Error starting game: ' + error.message);
|
||||
}
|
||||
// Use the same logic as handleGameQueue for consistency
|
||||
// This ensures both START GAME button and typing 'enter' behave the same way
|
||||
await this.handleGameQueue(socket);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle entry cancellation
|
||||
*/
|
||||
|
||||
/**
|
||||
* Handle cancel entry request
|
||||
*/
|
||||
@@ -380,6 +346,23 @@ class SocketHandlers {
|
||||
return;
|
||||
}
|
||||
|
||||
// Persist move count if DB available
|
||||
if (this.gameModeManager && this.gameModeManager.db) {
|
||||
const db = this.gameModeManager.db;
|
||||
db.query(`
|
||||
UPDATE games SET moves_made = moves_made + 1
|
||||
WHERE socket_id = $1 AND dungeon_seed = $2
|
||||
`, [socket.id, game.id]).catch(err => {
|
||||
if (this.debugManager.CONSOLE_LOGGING) console.warn('moves_made update failed:', err.message);
|
||||
});
|
||||
}
|
||||
|
||||
// Check collision if player moved onto monster BEFORE monster reacts
|
||||
if (this.checkMonsterKill(game.player, game.monster)) {
|
||||
this.handleGameOver(socket, game, 'lost', 'monster', 'The monster caught you!');
|
||||
return;
|
||||
}
|
||||
|
||||
// Move monster after player moves
|
||||
game.moveMonster();
|
||||
|
||||
@@ -443,8 +426,20 @@ class SocketHandlers {
|
||||
reason: reason,
|
||||
message: message,
|
||||
score: score,
|
||||
payout: payoutInfo
|
||||
payout: payoutInfo,
|
||||
treasure: game.player.hasTreasure || false
|
||||
});
|
||||
|
||||
// Persist completion details if DB available
|
||||
if (this.gameModeManager && this.gameModeManager.db) {
|
||||
const db = this.gameModeManager.db;
|
||||
const outcome = reason === 'escaped' ? 'escaped' : (reason === 'monster' ? 'caught_by_monster' : reason);
|
||||
db.query(`
|
||||
UPDATE games SET status = $1, outcome = $2, treasure_found = $3, completed_at = NOW()
|
||||
WHERE dungeon_seed = $4 AND socket_id = $5
|
||||
`, [status, outcome, game.player.hasTreasure, game.id, socket.id])
|
||||
.catch(err => console.error('Game completion update failed:', err.message));
|
||||
}
|
||||
|
||||
this.activeGames.delete(socket.id);
|
||||
}
|
||||
@@ -457,6 +452,9 @@ class SocketHandlers {
|
||||
console.log('User disconnected', socket.client.id);
|
||||
}
|
||||
|
||||
// Clean up payment monitoring
|
||||
this.stopPaymentMonitoring(socket.id);
|
||||
|
||||
// Clean up movement timestamps
|
||||
this.playerMoveTimestamps.delete(socket.id);
|
||||
|
||||
@@ -545,6 +543,19 @@ class SocketHandlers {
|
||||
|
||||
user.joinGame(game);
|
||||
this.activeGames.set(user.id, game);
|
||||
|
||||
// Insert a DB record for this game (best effort)
|
||||
if (this.gameModeManager && this.gameModeManager.db) {
|
||||
const db = this.gameModeManager.db;
|
||||
const gameMode = this.gameModeManager.gameMode || 'FREE';
|
||||
const blockHeight = this.debugManager.getCurrentBlockHeight ? this.debugManager.getCurrentBlockHeight() : null;
|
||||
const socketId = user.id; // user.id is the socket id string
|
||||
db.query(`
|
||||
INSERT INTO games (user_id, socket_id, game_mode, status, start_block_height, dungeon_seed, created_at)
|
||||
VALUES ((SELECT id FROM users WHERE socket_id = $1), $1, $2, 'active', $3, $4, NOW())
|
||||
`, [socketId, gameMode, blockHeight, game.id])
|
||||
.catch(err => console.error('Game insert failed:', err.message));
|
||||
}
|
||||
|
||||
if (this.debugManager.CONSOLE_LOGGING) {
|
||||
console.log(`[createGameForUser] Created ${gameType} game ${game.id} for user ${user.id}`);
|
||||
@@ -591,6 +602,8 @@ class SocketHandlers {
|
||||
console.log(`Starting games for ${this.WAITING_PLAYERS.length} waiting players at block height ${blockHeight}`);
|
||||
}
|
||||
|
||||
// We may skip some entries if they are not yet confirmed (paid mode, mempool only)
|
||||
const remainingQueue = [];
|
||||
while (this.WAITING_PLAYERS.length > 0) {
|
||||
const playerEntry = this.WAITING_PLAYERS.shift();
|
||||
const serverId = playerEntry.serverId;
|
||||
@@ -602,6 +615,14 @@ class SocketHandlers {
|
||||
const currentUser = this.getUserBySocket(serverId);
|
||||
|
||||
if (currentUser) {
|
||||
// If this entry requires confirmation and is not confirmed yet, keep it in queue
|
||||
if (playerEntry.requiresConfirmation && !playerEntry.confirmed) {
|
||||
remainingQueue.push(playerEntry); // re-queue for next block
|
||||
if (this.debugManager.CONSOLE_LOGGING) {
|
||||
console.log(`⏳ Skipping unconfirmed paid entry for ${serverId}, still waiting for confirmation.`);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
currentUser.blockRec = blockHeight;
|
||||
if (this.debugManager.CONSOLE_LOGGING) {
|
||||
console.log(`🕒 QUEUE ENTRY: Player enters on block ${currentUser.blockRec}, will die when block ${currentUser.blockRec + 1} starts`);
|
||||
@@ -629,6 +650,10 @@ class SocketHandlers {
|
||||
this.io.to(serverId).emit('message', 'Error: User not found');
|
||||
}
|
||||
}
|
||||
// Put back any remaining (unconfirmed) entries
|
||||
if (remainingQueue.length > 0) {
|
||||
this.WAITING_PLAYERS = remainingQueue.concat(this.WAITING_PLAYERS);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -664,18 +689,18 @@ class SocketHandlers {
|
||||
|
||||
try {
|
||||
const { gameMode } = data;
|
||||
const payment = await this.gameModeManager.moneroPayService.createPaymentRequest(socket.id, gameMode);
|
||||
// Use gameModeManager to create payment request with proper amount
|
||||
const paymentRequest = await this.gameModeManager.createPaymentRequest(socket.id, gameMode);
|
||||
|
||||
this.io.to(socket.id).emit('payment_created', {
|
||||
paymentId: payment.id,
|
||||
address: payment.address,
|
||||
amount: payment.amount,
|
||||
confirmations: payment.confirmations,
|
||||
paymentId: paymentRequest.id,
|
||||
address: paymentRequest.address,
|
||||
amount: paymentRequest.amount,
|
||||
gameMode: gameMode
|
||||
});
|
||||
|
||||
if (this.debugManager.CONSOLE_LOGGING) {
|
||||
console.log(`💳 Payment request created for ${socket.id}: ${payment.amount} XMR`);
|
||||
console.log(`💳 Payment request created for ${socket.id}: ${paymentRequest.amount} atomic units`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error creating payment request:', error);
|
||||
@@ -697,13 +722,13 @@ class SocketHandlers {
|
||||
}
|
||||
|
||||
try {
|
||||
const { paymentId } = data;
|
||||
const status = await this.gameModeManager.moneroPayService.checkPaymentStatus(paymentId);
|
||||
const { address } = data;
|
||||
const status = await this.walletService.checkPaymentStatus(address);
|
||||
|
||||
this.io.to(socket.id).emit('payment_status', status);
|
||||
|
||||
if (this.debugManager.CONSOLE_LOGGING) {
|
||||
console.log(`💳 Payment status checked for ${socket.id}: ${status.status}`);
|
||||
console.log(`💳 Payment status checked for ${socket.id}: ${status.complete ? 'complete' : 'pending'}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error checking payment status:', error);
|
||||
@@ -846,6 +871,331 @@ class SocketHandlers {
|
||||
`Address noted: ${pendingAddress.address} (Payment system not active)`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create payment request immediately when user types 'enter' in paid mode
|
||||
* User stays on welcome screen until payment is detected
|
||||
*/
|
||||
async createAndShowPaymentRequest(socket) {
|
||||
try {
|
||||
const currentUser = this.getUserBySocket(socket.id);
|
||||
if (!currentUser) {
|
||||
this.broadcastManager.sendStatusUpdate(socket.id, 'error',
|
||||
'Error: Could not process payment request. Please try again.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Get game mode information
|
||||
const gameMode = this.gameModeManager.gameMode;
|
||||
const cryptoType = this.gameModeManager.cryptoType;
|
||||
|
||||
// Determine payment type and amount based on game mode
|
||||
let paymentType, amount, description;
|
||||
|
||||
if (gameMode === 'PAID_SINGLE') {
|
||||
paymentType = 'single_game';
|
||||
amount = this.gameModeManager.singleGamePrice;
|
||||
description = 'Single game entry';
|
||||
} else if (gameMode === 'PAID_CREDITS') {
|
||||
paymentType = 'credits_package';
|
||||
amount = this.gameModeManager.creditsPackagePrice;
|
||||
description = '10 game credits package';
|
||||
} else {
|
||||
this.broadcastManager.sendStatusUpdate(socket.id, 'error',
|
||||
'Invalid game mode configuration.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Create payment request
|
||||
const paymentRequest = await this.gameModeManager.createPaymentRequest(socket.id, paymentType);
|
||||
|
||||
// Convert atomic units to human readable amount
|
||||
const humanAmount = (amount / 1000000000000).toFixed(3);
|
||||
|
||||
// Send payment information to client (for UI)
|
||||
this.io.to(socket.id).emit('payment_created', {
|
||||
paymentId: paymentRequest.id,
|
||||
address: paymentRequest.address,
|
||||
amount: paymentRequest.amount,
|
||||
paymentType: paymentType,
|
||||
gameMode: gameMode,
|
||||
cryptoType: cryptoType,
|
||||
humanAmount: humanAmount,
|
||||
description: description,
|
||||
expiresAt: paymentRequest.expiresAt
|
||||
});
|
||||
|
||||
// Send status message to chat with payment details
|
||||
this.broadcastManager.sendStatusUpdate(socket.id, 'payment',
|
||||
`💳 PAYMENT REQUIRED (${description})\n\n` +
|
||||
`Amount: ${humanAmount} ${cryptoType}\n` +
|
||||
`Address: ${paymentRequest.address}\n\n` +
|
||||
`⚠️ Send EXACTLY ${humanAmount} ${cryptoType} to the address above.\n` +
|
||||
`🔄 You will be added to the game queue once payment is detected in mempool.\n` +
|
||||
`⏰ Payment expires in 30 minutes.`);
|
||||
|
||||
// Start monitoring the payment address
|
||||
this.walletService.startPaymentMonitoring(
|
||||
paymentRequest.address,
|
||||
async (status) => {
|
||||
if (status.in_mempool && !status.confirmed) {
|
||||
// Payment detected in mempool - add to queue
|
||||
socket.emit('payment_detected', {
|
||||
message: 'Payment detected! Adding you to the game queue...',
|
||||
amount: status.amount,
|
||||
confirmations: 0
|
||||
});
|
||||
|
||||
// Add user to waiting queue (requires confirmation before starting)
|
||||
this.WAITING_PLAYERS.push({
|
||||
serverId: socket.id,
|
||||
clientId: currentUser.clientId,
|
||||
entryTime: Date.now(),
|
||||
paymentId: paymentRequest.id,
|
||||
requiresConfirmation: true,
|
||||
confirmed: false
|
||||
});
|
||||
|
||||
// Show waiting screen
|
||||
socket.emit('queue_joined', {
|
||||
position: this.WAITING_PLAYERS.length,
|
||||
message: 'Payment received! Waiting for next block to start game...'
|
||||
});
|
||||
|
||||
} else if (status.confirmed) {
|
||||
// Payment confirmed - game can start
|
||||
socket.emit('payment_confirmed', {
|
||||
message: 'Payment confirmed!',
|
||||
confirmations: status.confirmations
|
||||
});
|
||||
// Mark any existing waiting queue entry as confirmed
|
||||
const entry = this.WAITING_PLAYERS.find(p => p.serverId === socket.id);
|
||||
if (entry) {
|
||||
entry.confirmed = true;
|
||||
}
|
||||
}
|
||||
},
|
||||
2000 // Check every 2 seconds
|
||||
);
|
||||
|
||||
if (this.debugManager.CONSOLE_LOGGING) {
|
||||
console.log(`💳 Payment request created for ${socket.id}: ${humanAmount} ${cryptoType} (${paymentType})`);
|
||||
console.log(`👀 Started monitoring address: ${paymentRequest.address}`);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error creating payment request:', error);
|
||||
this.broadcastManager.sendStatusUpdate(socket.id, 'error',
|
||||
'Failed to create payment request. Please try again or contact support.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start monitoring a payment address for incoming payments
|
||||
*/
|
||||
startPaymentMonitoring(socketId, paymentRequest) {
|
||||
if (!this.paymentMonitors) {
|
||||
this.paymentMonitors = new Map();
|
||||
}
|
||||
|
||||
// Clear any existing monitor for this user
|
||||
this.stopPaymentMonitoring(socketId);
|
||||
|
||||
const monitor = {
|
||||
paymentId: paymentRequest.id,
|
||||
address: paymentRequest.address,
|
||||
socketId: socketId,
|
||||
startTime: Date.now(),
|
||||
interval: setInterval(async () => {
|
||||
try {
|
||||
const status = await this.walletService.checkPaymentStatus(paymentRequest.address);
|
||||
|
||||
if (status.confirmed || status.in_mempool) {
|
||||
// Payment detected!
|
||||
this.handlePaymentDetected(socketId, paymentRequest, status);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error checking payment status for ${socketId}:`, error);
|
||||
}
|
||||
}, 5000) // Check every 5 seconds
|
||||
};
|
||||
|
||||
this.paymentMonitors.set(socketId, monitor);
|
||||
|
||||
// Auto-expire after 30 minutes
|
||||
setTimeout(() => {
|
||||
this.stopPaymentMonitoring(socketId);
|
||||
this.broadcastManager.sendStatusUpdate(socketId, 'warning',
|
||||
'Payment request expired. Type \'enter\' again to create a new payment request.');
|
||||
}, 30 * 60 * 1000); // 30 minutes
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop monitoring a payment address
|
||||
*/
|
||||
stopPaymentMonitoring(socketId) {
|
||||
if (this.paymentMonitors && this.paymentMonitors.has(socketId)) {
|
||||
const monitor = this.paymentMonitors.get(socketId);
|
||||
clearInterval(monitor.interval);
|
||||
this.paymentMonitors.delete(socketId);
|
||||
|
||||
if (this.debugManager.CONSOLE_LOGGING) {
|
||||
console.log(`🛑 Stopped payment monitoring for ${socketId}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle payment detection - add user to queue
|
||||
*/
|
||||
async handlePaymentDetected(socketId, paymentRequest, paymentStatus) {
|
||||
this.stopPaymentMonitoring(socketId);
|
||||
|
||||
const currentUser = this.getUserBySocket(socketId);
|
||||
if (!currentUser) {
|
||||
console.error(`Payment detected but user not found: ${socketId}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if user is already in queue or game
|
||||
const existingIndex = this.WAITING_PLAYERS.findIndex(p => p.serverId === socketId);
|
||||
if (existingIndex !== -1) {
|
||||
this.broadcastManager.sendStatusUpdate(socketId, 'info', 'Payment confirmed, you are already in the queue!');
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.activeGames.has(socketId)) {
|
||||
this.broadcastManager.sendStatusUpdate(socketId, 'info', 'Payment confirmed, but you are already in a game!');
|
||||
return;
|
||||
}
|
||||
|
||||
// Add to waiting queue. If only mempool detected, require confirmation.
|
||||
this.WAITING_PLAYERS.push({
|
||||
serverId: socketId,
|
||||
clientId: currentUser.clientId,
|
||||
entryTime: Date.now(),
|
||||
paymentId: paymentRequest.id,
|
||||
requiresConfirmation: paymentStatus.in_mempool && !paymentStatus.confirmed,
|
||||
confirmed: paymentStatus.confirmed
|
||||
});
|
||||
|
||||
const currentBlock = this.debugManager.getCurrentBlockHeight();
|
||||
const nextBlock = currentBlock + 1;
|
||||
|
||||
if (paymentStatus.in_mempool && !paymentStatus.confirmed) {
|
||||
// Payment in mempool but not confirmed yet
|
||||
this.broadcastManager.sendStatusUpdate(socketId, 'success',
|
||||
`💰 PAYMENT DETECTED IN MEMPOOL!\n\n` +
|
||||
`✅ You have been added to the game queue.\n` +
|
||||
`🕒 Your game will start when block ${nextBlock} is found.\n` +
|
||||
`📦 Current block: ${currentBlock}`);
|
||||
this.broadcastManager.sendStatusUpdate(socketId, 'info', 'Waiting for block confirmation before starting...');
|
||||
} else if (paymentStatus.confirmed) {
|
||||
// Payment already confirmed
|
||||
this.broadcastManager.sendStatusUpdate(socketId, 'success',
|
||||
`💰 PAYMENT CONFIRMED!\n\n` +
|
||||
`✅ You have been added to the game queue.\n` +
|
||||
`🕒 Your game will start when block ${nextBlock} is found.\n` +
|
||||
`📦 Current block: ${currentBlock}`);
|
||||
}
|
||||
|
||||
// Send user to waiting screen
|
||||
this.io.to(socketId).emit('payment_confirmed', {
|
||||
paymentId: paymentRequest.id,
|
||||
status: paymentStatus,
|
||||
nextBlock: nextBlock,
|
||||
currentBlock: currentBlock
|
||||
});
|
||||
|
||||
if (this.debugManager.CONSOLE_LOGGING) {
|
||||
console.log(`💰 Payment detected for ${socketId}: Added to queue for block ${nextBlock}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle payment command - create payment request for user
|
||||
*/
|
||||
async handlePaymentCommand(socket) {
|
||||
if (!this.gameModeManager) {
|
||||
this.broadcastManager.sendStatusUpdate(socket.id, 'error',
|
||||
'Payment system is not available. Server is running in FREE mode.');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const currentUser = this.getUserBySocket(socket.id);
|
||||
if (!currentUser) {
|
||||
this.broadcastManager.sendStatusUpdate(socket.id, 'error',
|
||||
'Error: Could not process payment request. Please try again.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Get game mode information
|
||||
const gameMode = this.gameModeManager.gameMode;
|
||||
const cryptoType = this.gameModeManager.cryptoType;
|
||||
|
||||
if (gameMode === 'FREE') {
|
||||
this.broadcastManager.sendStatusUpdate(socket.id, 'info',
|
||||
'Server is in FREE mode - no payment required to play!');
|
||||
return;
|
||||
}
|
||||
|
||||
// Determine payment type and amount based on game mode
|
||||
let paymentType, amount, description;
|
||||
|
||||
if (gameMode === 'PAID_SINGLE') {
|
||||
paymentType = 'single_game';
|
||||
amount = this.gameModeManager.singleGamePrice;
|
||||
description = `Single game entry (${cryptoType})`;
|
||||
} else if (gameMode === 'PAID_CREDITS') {
|
||||
paymentType = 'credits_package';
|
||||
amount = this.gameModeManager.creditsPackagePrice;
|
||||
description = `10 game credits package (${cryptoType})`;
|
||||
} else {
|
||||
this.broadcastManager.sendStatusUpdate(socket.id, 'error',
|
||||
'Invalid game mode configuration.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Create payment request
|
||||
const paymentRequest = await this.gameModeManager.createPaymentRequest(socket.id, paymentType);
|
||||
|
||||
// Convert atomic units to human readable amount
|
||||
const humanAmount = (amount / 1000000000000).toFixed(3);
|
||||
|
||||
// Send payment information to client
|
||||
this.io.to(socket.id).emit('payment_created', {
|
||||
paymentId: paymentRequest.id,
|
||||
address: paymentRequest.address,
|
||||
amount: paymentRequest.amount,
|
||||
paymentType: paymentType,
|
||||
gameMode: gameMode,
|
||||
cryptoType: cryptoType,
|
||||
humanAmount: humanAmount,
|
||||
description: description,
|
||||
expiresAt: paymentRequest.expiresAt
|
||||
});
|
||||
|
||||
// Send status message to chat
|
||||
this.broadcastManager.sendStatusUpdate(socket.id, 'payment',
|
||||
`💳 Payment Request Created!\n\n` +
|
||||
`Amount: ${humanAmount} ${cryptoType}\n` +
|
||||
`Type: ${description}\n` +
|
||||
`Address: ${paymentRequest.address}\n\n` +
|
||||
`⚠️ Send the EXACT amount to the address above.\n` +
|
||||
`⏰ Payment will expire in 30 minutes.\n` +
|
||||
`🔄 Your game will start automatically after payment confirmation.`);
|
||||
|
||||
if (this.debugManager.CONSOLE_LOGGING) {
|
||||
console.log(`💳 Payment request created for ${socket.id}: ${humanAmount} ${cryptoType} (${paymentType})`);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error creating payment request:', error);
|
||||
this.broadcastManager.sendStatusUpdate(socket.id, 'error',
|
||||
'Failed to create payment request. Please try again or contact support.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = SocketHandlers;
|
||||
|
||||
+1
-1
@@ -18,7 +18,7 @@
|
||||
},
|
||||
"production": {
|
||||
"enabled": true,
|
||||
"console_logging": false,
|
||||
"console_logging": true,
|
||||
"debug_hotkeys": false,
|
||||
"simulated_blocks": false
|
||||
}
|
||||
|
||||
@@ -0,0 +1,352 @@
|
||||
const axios = require('axios');
|
||||
const { v4: uuidv4 } = require('uuid');
|
||||
|
||||
class WalletRPCService {
|
||||
constructor(debugManager) {
|
||||
this.debugManager = debugManager;
|
||||
this.walletEndpoint = process.env.PRIMARY_WALLET_ENDPOINT || 'http://127.0.0.1:34570';
|
||||
this.accountIndex = 0;
|
||||
this.paymentWatchers = new Map();
|
||||
this.addressToUser = new Map();
|
||||
this.addressToSocket = new Map();
|
||||
this.isHealthy = false;
|
||||
}
|
||||
|
||||
async initialize() {
|
||||
try {
|
||||
// Test wallet connection
|
||||
const response = await this.rpcCall('get_version');
|
||||
this.isHealthy = true;
|
||||
|
||||
if (this.debugManager?.CONSOLE_LOGGING) {
|
||||
console.log('✅ Wallet RPC service initialized:', {
|
||||
version: response.result?.version || 'unknown',
|
||||
endpoint: this.walletEndpoint
|
||||
});
|
||||
}
|
||||
|
||||
// Get wallet height to ensure it's synced
|
||||
const heightResponse = await this.rpcCall('get_height');
|
||||
if (this.debugManager?.CONSOLE_LOGGING) {
|
||||
console.log('📊 Wallet height:', heightResponse.result?.height || 0);
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
this.isHealthy = false;
|
||||
console.error('❌ Wallet RPC connection failed:', error.message);
|
||||
console.error(' Make sure wownero-wallet-rpc is running on', this.walletEndpoint);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async rpcCall(method, params = {}) {
|
||||
try {
|
||||
const response = await axios.post(`${this.walletEndpoint}/json_rpc`, {
|
||||
jsonrpc: '2.0',
|
||||
id: '0',
|
||||
method: method,
|
||||
params: params
|
||||
}, {
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
timeout: 10000
|
||||
});
|
||||
|
||||
if (response.data.error) {
|
||||
throw new Error(response.data.error.message);
|
||||
}
|
||||
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
if (this.debugManager?.CONSOLE_LOGGING) {
|
||||
console.error(`❌ Wallet RPC call failed (${method}):`, error.message);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async createPaymentRequest(amount, description, userId, socketId) {
|
||||
try {
|
||||
// Create a new subaddress for this payment
|
||||
const response = await this.rpcCall('create_address', {
|
||||
account_index: this.accountIndex,
|
||||
label: `game_${userId}_${Date.now()}`
|
||||
});
|
||||
|
||||
const address = response.result.address;
|
||||
const addressIndex = response.result.address_index;
|
||||
|
||||
// Store mapping for payment monitoring
|
||||
const paymentInfo = {
|
||||
userId: userId,
|
||||
socketId: socketId,
|
||||
amount: amount,
|
||||
description: description,
|
||||
addressIndex: addressIndex,
|
||||
accountIndex: this.accountIndex,
|
||||
createdAt: new Date(),
|
||||
status: 'pending',
|
||||
detected: false,
|
||||
confirmed: false
|
||||
};
|
||||
|
||||
this.addressToUser.set(address, paymentInfo);
|
||||
this.addressToSocket.set(address, socketId);
|
||||
|
||||
if (this.debugManager?.CONSOLE_LOGGING) {
|
||||
console.log(`💳 Payment address created:`, {
|
||||
user: userId,
|
||||
address: address.substring(0, 10) + '...',
|
||||
amount: amount,
|
||||
index: addressIndex
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
id: uuidv4(), // Generate payment ID
|
||||
address: address,
|
||||
amount: amount,
|
||||
description: description,
|
||||
addressIndex: addressIndex,
|
||||
expiresAt: new Date(Date.now() + 30 * 60 * 1000) // 30 minutes
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('❌ Failed to create payment address:', error);
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
async checkPaymentStatus(address) {
|
||||
try {
|
||||
const userInfo = this.addressToUser.get(address);
|
||||
if (!userInfo) {
|
||||
return {
|
||||
complete: false,
|
||||
in_mempool: false,
|
||||
confirmed: false,
|
||||
amount: 0,
|
||||
confirmations: 0
|
||||
};
|
||||
}
|
||||
|
||||
// Get incoming transfers for this specific subaddress
|
||||
const response = await this.rpcCall('get_transfers', {
|
||||
in: true,
|
||||
pending: true,
|
||||
failed: false,
|
||||
pool: true,
|
||||
account_index: userInfo.accountIndex,
|
||||
subaddr_indices: [userInfo.addressIndex]
|
||||
});
|
||||
|
||||
let totalReceived = 0;
|
||||
let inMempool = false;
|
||||
let confirmed = false;
|
||||
let confirmations = 0;
|
||||
let txHash = null;
|
||||
|
||||
// Check pending (mempool) transactions
|
||||
if (response.result?.pool && response.result.pool.length > 0) {
|
||||
for (const tx of response.result.pool) {
|
||||
totalReceived += tx.amount;
|
||||
inMempool = true;
|
||||
txHash = tx.txid;
|
||||
|
||||
// Mark as detected if this is first time seeing it
|
||||
if (!userInfo.detected) {
|
||||
userInfo.detected = true;
|
||||
if (this.debugManager?.CONSOLE_LOGGING) {
|
||||
console.log(`🎯 Payment detected in mempool:`, {
|
||||
address: address.substring(0, 10) + '...',
|
||||
amount: tx.amount,
|
||||
txid: txHash
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check confirmed transactions
|
||||
if (response.result?.in && response.result.in.length > 0) {
|
||||
for (const tx of response.result.in) {
|
||||
totalReceived += tx.amount;
|
||||
confirmed = true;
|
||||
txHash = tx.txid;
|
||||
|
||||
// Calculate confirmations
|
||||
if (tx.height > 0) {
|
||||
const heightResp = await this.rpcCall('get_height');
|
||||
confirmations = Math.max(confirmations, heightResp.result.height - tx.height);
|
||||
}
|
||||
|
||||
if (!userInfo.confirmed) {
|
||||
userInfo.confirmed = true;
|
||||
if (this.debugManager?.CONSOLE_LOGGING) {
|
||||
console.log(`✅ Payment confirmed:`, {
|
||||
address: address.substring(0, 10) + '...',
|
||||
confirmations: confirmations,
|
||||
txid: txHash
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const paymentComplete = totalReceived >= userInfo.amount;
|
||||
|
||||
return {
|
||||
complete: paymentComplete,
|
||||
in_mempool: inMempool,
|
||||
confirmed: confirmed,
|
||||
amount: totalReceived,
|
||||
confirmations: confirmations,
|
||||
required: userInfo.amount,
|
||||
txHash: txHash
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('❌ Failed to check payment status:', error);
|
||||
return {
|
||||
complete: false,
|
||||
in_mempool: false,
|
||||
confirmed: false,
|
||||
amount: 0,
|
||||
confirmations: 0
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
startPaymentMonitoring(address, callback, interval = 2000) {
|
||||
if (this.paymentWatchers.has(address)) {
|
||||
return; // Already monitoring
|
||||
}
|
||||
|
||||
const watcher = setInterval(async () => {
|
||||
const status = await this.checkPaymentStatus(address);
|
||||
|
||||
if (status.in_mempool || status.confirmed) {
|
||||
callback(status);
|
||||
|
||||
// Stop monitoring if payment is confirmed
|
||||
if (status.confirmed && status.complete) {
|
||||
this.stopPaymentMonitoring(address);
|
||||
}
|
||||
}
|
||||
}, interval);
|
||||
|
||||
this.paymentWatchers.set(address, watcher);
|
||||
|
||||
if (this.debugManager?.CONSOLE_LOGGING) {
|
||||
console.log(`👁️ Started monitoring payments for:`, address.substring(0, 10) + '...');
|
||||
}
|
||||
}
|
||||
|
||||
stopPaymentMonitoring(address) {
|
||||
const watcher = this.paymentWatchers.get(address);
|
||||
if (watcher) {
|
||||
clearInterval(watcher);
|
||||
this.paymentWatchers.delete(address);
|
||||
|
||||
if (this.debugManager?.CONSOLE_LOGGING) {
|
||||
console.log(`🛑 Stopped monitoring:`, address.substring(0, 10) + '...');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async processPayout(recipientAddress, amount, description) {
|
||||
try {
|
||||
// Send payment to user
|
||||
const response = await this.rpcCall('transfer', {
|
||||
destinations: [{
|
||||
amount: amount,
|
||||
address: recipientAddress
|
||||
}],
|
||||
account_index: this.accountIndex,
|
||||
priority: 1, // Normal priority
|
||||
get_tx_key: true
|
||||
});
|
||||
|
||||
if (this.debugManager?.CONSOLE_LOGGING) {
|
||||
console.log(`💸 Payout sent:`, {
|
||||
to: recipientAddress.substring(0, 10) + '...',
|
||||
amount: amount,
|
||||
txHash: response.result.tx_hash
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
txHash: response.result.tx_hash,
|
||||
txKey: response.result.tx_key,
|
||||
fee: response.result.fee
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('❌ Failed to process payout:', error);
|
||||
return {
|
||||
success: false,
|
||||
error: error.message
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
startPaymentMonitoring(address, callback, interval = 2000) {
|
||||
if (this.paymentWatchers.has(address)) {
|
||||
return; // Already monitoring
|
||||
}
|
||||
|
||||
const watcher = setInterval(async () => {
|
||||
try {
|
||||
const status = await this.checkPaymentStatus(address);
|
||||
|
||||
if (status.in_mempool || status.confirmed) {
|
||||
callback(status);
|
||||
|
||||
// Stop monitoring if payment is confirmed
|
||||
if (status.confirmed && status.complete) {
|
||||
this.stopPaymentMonitoring(address);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`❌ Error monitoring payment ${address.substring(0, 10)}...:`, error.message);
|
||||
}
|
||||
}, interval);
|
||||
|
||||
this.paymentWatchers.set(address, watcher);
|
||||
|
||||
if (this.debugManager?.CONSOLE_LOGGING) {
|
||||
console.log(`👁️ Started monitoring payments for:`, address.substring(0, 10) + '...');
|
||||
}
|
||||
}
|
||||
|
||||
stopPaymentMonitoring(address) {
|
||||
const watcher = this.paymentWatchers.get(address);
|
||||
if (watcher) {
|
||||
clearInterval(watcher);
|
||||
this.paymentWatchers.delete(address);
|
||||
|
||||
if (this.debugManager?.CONSOLE_LOGGING) {
|
||||
console.log(`🛑 Stopped monitoring:`, address.substring(0, 10) + '...');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cleanupUserPayments(userId) {
|
||||
for (const [address, userInfo] of this.addressToUser.entries()) {
|
||||
if (userInfo.userId === userId || userInfo.socketId === userId) {
|
||||
this.stopPaymentMonitoring(address);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get service health status
|
||||
getHealthStatus() {
|
||||
return {
|
||||
healthy: this.isHealthy,
|
||||
endpoint: this.walletEndpoint,
|
||||
activeMonitors: this.paymentWatchers.size,
|
||||
pendingPayments: this.addressToUser.size
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = WalletRPCService;
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
const axios = require('axios');
|
||||
|
||||
const CONSOLE_LOGGING = process.env.NODE_ENV === 'debug' || process.env.NODE_ENV === 'development';
|
||||
const CONSOLE_LOGGING = true; // Temporarily enable for debugging
|
||||
|
||||
class RPCService {
|
||||
constructor() {
|
||||
@@ -127,7 +127,7 @@ class RPCService {
|
||||
*/
|
||||
async getBlockHeight() {
|
||||
try {
|
||||
const result = await this.makeRPCCall('get_block_count');
|
||||
const result = await this.makeRPCCall('getblockcount');
|
||||
const blockHeight = result.count;
|
||||
|
||||
if (blockHeight !== this.lastBlockHeight) {
|
||||
@@ -166,7 +166,7 @@ class RPCService {
|
||||
async getNetworkInfo() {
|
||||
try {
|
||||
const [blockCount, networkInfo] = await Promise.all([
|
||||
this.makeRPCCall('get_block_count'),
|
||||
this.makeRPCCall('getblockcount'),
|
||||
this.makeRPCCall('get_info')
|
||||
]);
|
||||
|
||||
@@ -203,7 +203,7 @@ class RPCService {
|
||||
await axios.post(`${this.primaryEndpoint}/json_rpc`, {
|
||||
jsonrpc: '2.0',
|
||||
id: '0',
|
||||
method: 'get_block_count'
|
||||
method: 'getblockcount'
|
||||
}, { timeout: 5000 });
|
||||
checks.primary.status = 'healthy';
|
||||
checks.primary.responseTime = Date.now() - start;
|
||||
@@ -219,7 +219,7 @@ class RPCService {
|
||||
await axios.post(`${this.fallbackEndpoint}/json_rpc`, {
|
||||
jsonrpc: '2.0',
|
||||
id: '0',
|
||||
method: 'get_block_count'
|
||||
method: 'getblockcount'
|
||||
}, { timeout: 5000 });
|
||||
checks.fallback.status = 'healthy';
|
||||
checks.fallback.responseTime = Date.now() - start;
|
||||
|
||||
+9
-1
@@ -24,7 +24,15 @@ var lastBlock = {
|
||||
hash: 0,
|
||||
setHeight: function(newHeight) {this.height = newHeight}
|
||||
}
|
||||
var daemonURL = 'http://localhost:34568/json_rpc';
|
||||
|
||||
// Use environment variable for daemon URL, fallback to localhost for backward compatibility
|
||||
var daemonURL = process.env.PRIMARY_RPC_ENDPOINT ?
|
||||
process.env.PRIMARY_RPC_ENDPOINT + '/json_rpc' :
|
||||
'http://localhost:34568/json_rpc';
|
||||
|
||||
if (CONSOLE_LOGGING) {
|
||||
console.log(`🔗 Legacy RPC service using URL: ${daemonURL}`);
|
||||
}
|
||||
|
||||
function daemonCall(method, params, callback, io) {
|
||||
// Real blockchain call (now used for all cases)
|
||||
|
||||
Reference in New Issue
Block a user