dubs-server 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +280 -0
- package/CLAUDE.md +46 -0
- package/CONNECT4_PRODUCTION_DEPLOY.md +155 -0
- package/CURRENT_SESSION.md +171 -0
- package/CURRENT_SESSION_DRAW.md +516 -0
- package/MARCH_MADNESS_SURVIVOR.md +254 -0
- package/PANDA.md +166 -0
- package/Procfile +4 -0
- package/README.md +476 -0
- package/controllers/livescoresController.js +376 -0
- package/controllers/pickemController.js +554 -0
- package/controllers/survivorAdminController.js +887 -0
- package/controllers/survivorController.js +623 -0
- package/cron/oracleMonitor.js +77 -0
- package/cron/pickemOracleMonitor.js +73 -0
- package/data/jackpot-history.json +952 -0
- package/data/ncaaTeams.js +406 -0
- package/documentation/API_SECURITY_GUIDE.md +327 -0
- package/documentation/ARCADE_API.md +593 -0
- package/documentation/ARCADE_IMPLEMENTATION_SUMMARY.md +399 -0
- package/documentation/ARCADE_QUICKSTART.md +242 -0
- package/documentation/AUTOMATIC_MODE_ORACLE.md +321 -0
- package/documentation/BUG_FIX_COHORT_DATE_DISPLAY.md +171 -0
- package/documentation/CLAIM_MIGRATION_INSTRUCTIONS.md +52 -0
- package/documentation/CLAIM_STATUS_FIX.md +67 -0
- package/documentation/CLI_TOOL_GUIDE.md +372 -0
- package/documentation/COHORT_RETENTION_ANALYSIS.md +295 -0
- package/documentation/COHORT_RETENTION_IMPLEMENTATION_COMPLETE.md +461 -0
- package/documentation/COHORT_RETENTION_SUMMARY.md +204 -0
- package/documentation/COMPLETE_PROJECT_SUMMARY.md +490 -0
- package/documentation/DATABASE_QUERIES.md +269 -0
- package/documentation/DATABASE_RETENTION_POLICY.md +390 -0
- package/documentation/DATABASE_SETUP_GUIDE.md +361 -0
- package/documentation/DATABASE_SETUP_SUMMARY.md +247 -0
- package/documentation/DEMO_API_CURL_COMMANDS.md +656 -0
- package/documentation/DEPLOYMENT_SUMMARY.txt +100 -0
- package/documentation/DUPLICATE_NOTIFICATIONS_FIXED.md +201 -0
- package/documentation/EXCHANGE_RATES_INTEGRATION.md +371 -0
- package/documentation/FINAL_API_PROTECTION_TABLE.md +175 -0
- package/documentation/GAME_START_NOTIFICATIONS_DEPLOYMENT.md +256 -0
- package/documentation/GAME_START_NOTIFICATIONS_INTEGRATION.md +275 -0
- package/documentation/HEROKU_DEPLOYMENT.md +134 -0
- package/documentation/HEROKU_SCHEDULER_SETUP.md +271 -0
- package/documentation/JACKPOT_API.md +521 -0
- package/documentation/JACKPOT_DEPLOYMENT_GUIDE.md +362 -0
- package/documentation/JWT_IMPLEMENTATION_SUMMARY.md +373 -0
- package/documentation/JWT_QUICK_SETUP.md +268 -0
- package/documentation/JWT_TESTING_GUIDE.md +404 -0
- package/documentation/KEEPER_RECOVERY_GUIDE.md +381 -0
- package/documentation/KEEPER_SETUP.md +206 -0
- package/documentation/KEEPER_STATE_MACHINE.md +423 -0
- package/documentation/LATEST_PRODUCTION_SETUP.md +387 -0
- package/documentation/LOCAL_VOTING_TEST.md +279 -0
- package/documentation/ORACLE_FIXES_SUMMARY.md +188 -0
- package/documentation/ORACLE_POSTGRESQL_UPDATE.md +202 -0
- package/documentation/PAYMENT_DEPLOYMENT.md +209 -0
- package/documentation/PNL_TRACKING_SETUP.md +189 -0
- package/documentation/PREVENTING_LOCKUP_ERRORS.md +472 -0
- package/documentation/PRODUCTION_READY_SUMMARY.md +227 -0
- package/documentation/PUBLIC_VS_PRIVATE_ENDPOINTS.md +278 -0
- package/documentation/QUICK_AUTH_SETUP.md +99 -0
- package/documentation/QUICK_DEPLOY.md +224 -0
- package/documentation/QUICK_FIX.md +114 -0
- package/documentation/QUICK_START.md +152 -0
- package/documentation/REFEREE_MODE_GUIDE.md +392 -0
- package/documentation/RETENTION_CORE_ACTION_UPDATE.md +313 -0
- package/documentation/RETENTION_UPDATE_SUMMARY.md +108 -0
- package/documentation/RUN_MIGRATION_NOW.md +39 -0
- package/documentation/SCRIPTS_UPDATE_SUMMARY.md +251 -0
- package/documentation/SETUP_GUIDE.md +184 -0
- package/documentation/STATE_MACHINE_IMPLEMENTATION.md +250 -0
- package/documentation/TELEGRAM_NOTIFICATIONS_DIAGNOSIS.md +361 -0
- package/documentation/UNIFIED_ARCHITECTURE.md +231 -0
- package/documentation/VOTING_DEPLOYMENT_SUMMARY.md +392 -0
- package/documentation/WEBSOCKET_ARCHITECTURE.md +881 -0
- package/documentation/WHAT_WE_BUILT_TODAY.md +369 -0
- package/documentation/latest/LATEST_PRODUCTION_SETUP.md +865 -0
- package/ecosystem.config.js +65 -0
- package/env.template +125 -0
- package/middleware/apiKeyAuth.js +136 -0
- package/middleware/authenticate.js +214 -0
- package/middleware/developerUserAuth.js +76 -0
- package/middleware/socketAuth.js +69 -0
- package/package.json +49 -0
- package/postman/Dubs-API-v1-With-Voting.postman_collection.json +555 -0
- package/postman/Dubs-API-v1.postman_collection.json +205 -0
- package/postman/Dubs_Developer_API.postman_collection.json +662 -0
- package/postman/QUICKSTART.md +118 -0
- package/postman/QUICK_REFERENCE.md +246 -0
- package/postman/README.md +71 -0
- package/postman/VOTING_API_GUIDE.md +426 -0
- package/refactor/Animations.md +148 -0
- package/refactor/Chat.md +252 -0
- package/routes/actionsRoutes.js +699 -0
- package/routes/adminRoutes.js +370 -0
- package/routes/analyticsRoutes.js +1262 -0
- package/routes/arcadeRoutes.js +557 -0
- package/routes/authRoutes.js +2310 -0
- package/routes/avatarRoutes.js +85 -0
- package/routes/botRoutes.js +211 -0
- package/routes/chatRoutes.js +377 -0
- package/routes/cryptoPriceRoutes.js +105 -0
- package/routes/developerRoutes.js +4201 -0
- package/routes/deviceRoutes.js +214 -0
- package/routes/dmRoutes.js +167 -0
- package/routes/esportsRoutes.js +806 -0
- package/routes/exchangeRateRoutes.js +233 -0
- package/routes/gamesRoutes.js +3028 -0
- package/routes/jackpotRoutes.js +754 -0
- package/routes/keeperMonitoringRoutes.js +156 -0
- package/routes/keeperWebhookRoutes.js +466 -0
- package/routes/livescoresRoutes.js +31 -0
- package/routes/pickemAdminRoutes.js +199 -0
- package/routes/pickemRoutes.js +231 -0
- package/routes/playerStatsRoutes.js +147 -0
- package/routes/portfolioRoutes.js +217 -0
- package/routes/promoRoutes.js +418 -0
- package/routes/referralEarningsRoutes.js +392 -0
- package/routes/socialRoutes.js +459 -0
- package/routes/sportsRoutes.js +1271 -0
- package/routes/survivorAdminRoutes.js +345 -0
- package/routes/survivorRoutes.js +756 -0
- package/routes/uploadRoutes.js +256 -0
- package/routes/userProfileRoutes.js +244 -0
- package/routes/whatsNewRoutes.js +331 -0
- package/scripts/.claude/settings.local.json +15 -0
- package/scripts/README.md +170 -0
- package/scripts/RESTART_EVERYTHING.sh +104 -0
- package/scripts/add-claim-columns.sql +48 -0
- package/scripts/add-crypto-prices-cache.sql +27 -0
- package/scripts/add-exchange-rates-cache.sql +40 -0
- package/scripts/add-game-invite-column.sql +23 -0
- package/scripts/add-game-invite-notification.sql +33 -0
- package/scripts/add-game-invite-telegram-pref.sql +16 -0
- package/scripts/add-game-joined-notification.sql +16 -0
- package/scripts/add-game-joined-pref.js +40 -0
- package/scripts/add-game-joined-preference.sql +6 -0
- package/scripts/add-game-start-notifications.sql +41 -0
- package/scripts/add-notification-flags-to-games.sql +55 -0
- package/scripts/add-pending-game-dismissals.sql +19 -0
- package/scripts/add-preferred-currency.sql +34 -0
- package/scripts/add-winner-columns.js +61 -0
- package/scripts/add_mention_system.sql +53 -0
- package/scripts/add_payment_system.sql +96 -0
- package/scripts/add_sports_event_id_column.sql +22 -0
- package/scripts/analyze-cohort-data-heroku.js +276 -0
- package/scripts/analyze-cohort-data.js +295 -0
- package/scripts/analyze-prod-cohorts.sh +10 -0
- package/scripts/backfill-matchup-images.js +245 -0
- package/scripts/backfill-missing-signatures.js +175 -0
- package/scripts/backfill-referral-earnings.js +202 -0
- package/scripts/check-chat-schema.js +130 -0
- package/scripts/check-db.sh +14 -0
- package/scripts/check_oracle_in_game.js +54 -0
- package/scripts/cleanup-database.js +193 -0
- package/scripts/clear-notification-cache.js +85 -0
- package/scripts/convert-mnemonic.js +50 -0
- package/scripts/create-users-table.sql +44 -0
- package/scripts/debug-cohort-counts.js +248 -0
- package/scripts/debug-winner-calc.js +84 -0
- package/scripts/deploy-payment-system.sh +118 -0
- package/scripts/deploy-to-heroku.sh +63 -0
- package/scripts/diagnose-locked-round.js +143 -0
- package/scripts/dubs-cli.js +720 -0
- package/scripts/dump-account.js +65 -0
- package/scripts/find-vrf-offset.js +48 -0
- package/scripts/fix-chat-notifications-constraint.sql +122 -0
- package/scripts/fix-claim-columns.js +124 -0
- package/scripts/fix-constraint-now.js +44 -0
- package/scripts/fix-lock-timestamps.js +96 -0
- package/scripts/fix-locked-round.sh +126 -0
- package/scripts/fix-missing-badges.sql +91 -0
- package/scripts/fix-payment-notifications.sql +41 -0
- package/scripts/force-new-round.js +55 -0
- package/scripts/force-resolve-and-claim.js +278 -0
- package/scripts/important/README.md +115 -0
- package/scripts/important/authority-force-lock.js +197 -0
- package/scripts/important/authority-resolve-game.js +267 -0
- package/scripts/important/check-game-status.js +373 -0
- package/scripts/important/list-pending-games-by-version.js +270 -0
- package/scripts/important/reconcile-v1-v2-payouts.js +270 -0
- package/scripts/initialize-jackpot.js +111 -0
- package/scripts/jackpot/.claude/settings.local.json +10 -0
- package/scripts/jackpot/force-reset.js +84 -0
- package/scripts/jackpot/initialize-mainnet.js +100 -0
- package/scripts/jackpot/keeper.js +742 -0
- package/scripts/jackpot/status.js +107 -0
- package/scripts/jackpot/update-round-duration.js +143 -0
- package/scripts/keeper-bot.js +112 -0
- package/scripts/list-pending-games.js +131 -0
- package/scripts/migrate-chat-v2.js +127 -0
- package/scripts/migrate-chat-winners.js +84 -0
- package/scripts/migrate-chat.sh +17 -0
- package/scripts/migrate-game-invite.js +83 -0
- package/scripts/migrate-heroku-game-notifications.sh +159 -0
- package/scripts/migrations/001_analytics_tables.sql +422 -0
- package/scripts/migrations/002_add_matchup_image_url.sql +14 -0
- package/scripts/migrations/003_referral_earnings.sql +208 -0
- package/scripts/migrations/004_add_whats_new_notification_type.sql +62 -0
- package/scripts/migrations/005_add_connect4_your_turn_notification.sql +61 -0
- package/scripts/migrations/005_push_notifications.sql +55 -0
- package/scripts/migrations/006_add_draw_team_players.sql +28 -0
- package/scripts/migrations/006_add_game_cancelled_notification.sql +62 -0
- package/scripts/migrations/007_add_gif_url.sql +8 -0
- package/scripts/migrations/008_add_connect4_columns.sql +139 -0
- package/scripts/migrations/008_add_pool_tracking.sql +22 -0
- package/scripts/migrations/009_create_survivor_pool_tables.sql +174 -0
- package/scripts/migrations/010_add_survivor_pool_outcome.sql +28 -0
- package/scripts/migrations/011_create_developer_tables.sql +67 -0
- package/scripts/migrations/011_fix_keeper_tables.sql +85 -0
- package/scripts/migrations/012_create_developer_webhooks.sql +31 -0
- package/scripts/migrations/013_add_network_mode.sql +18 -0
- package/scripts/migrations/014_create_developer_app_users.sql +19 -0
- package/scripts/migrations/015_add_ui_config.sql +4 -0
- package/scripts/migrations/016_add_resolution_secret.sql +4 -0
- package/scripts/migrations/017_add_external_game_id.sql +3 -0
- package/scripts/migrations/018_create_pickem_tables.sql +115 -0
- package/scripts/migrations/019_expo_push_tokens.sql +19 -0
- package/scripts/migrations/create_whats_new_tables.sql +88 -0
- package/scripts/migrations/drop_live_games_tables.sql +34 -0
- package/scripts/open-jackpot-round.js +85 -0
- package/scripts/purge-all-data.sh +329 -0
- package/scripts/purge-all-data.sql +142 -0
- package/scripts/purge-heroku-data.sh +149 -0
- package/scripts/purge-heroku-data.sql +62 -0
- package/scripts/rebuild-heroku-database.sh +113 -0
- package/scripts/recover-funds.js +357 -0
- package/scripts/regenerate-epl-images.js +278 -0
- package/scripts/resize-s3-matchup-images.js +374 -0
- package/scripts/resolve-direct.js +88 -0
- package/scripts/resolve-mock-game.js +124 -0
- package/scripts/resolve-pickem-game.js +55 -0
- package/scripts/resolve-round-manual.js +83 -0
- package/scripts/resolve-stuck-game.js +382 -0
- package/scripts/resolve-stuck-round.js +42 -0
- package/scripts/run-connect4-migration.sh +16 -0
- package/scripts/run-mention-migration.sh +32 -0
- package/scripts/run-payment-migration.sh +51 -0
- package/scripts/run-preferred-currency-migration.sh +31 -0
- package/scripts/run-referral-earnings-migration.sh +32 -0
- package/scripts/run-survivor-outcome-migration.sh +16 -0
- package/scripts/seed-test-users.js +346 -0
- package/scripts/setup-auth-tables.js +78 -0
- package/scripts/setup-complete-database.sql +992 -0
- package/scripts/setup-database-fresh.sh +359 -0
- package/scripts/setup-heroku-keeper.sh +48 -0
- package/scripts/setup-keeper-database.js +83 -0
- package/scripts/setup-keeper-state-db.sql +110 -0
- package/scripts/setup-oracle.sh +39 -0
- package/scripts/setup-pnl-tracking.js +111 -0
- package/scripts/start-devnet.sh +14 -0
- package/scripts/test-arcade-devnet.sh +160 -0
- package/scripts/test-arcade-match.sh +109 -0
- package/scripts/test-automatic-mode.sh +239 -0
- package/scripts/test-connect4-cancel-claim.js +370 -0
- package/scripts/test-connect4-e2e.js +369 -0
- package/scripts/test-connect4-resolve.js +369 -0
- package/scripts/test-game-state-endpoint.js +136 -0
- package/scripts/test-invite-notification.js +86 -0
- package/scripts/test-jackpot-api.sh +71 -0
- package/scripts/test-poll-confirmation.js +267 -0
- package/scripts/test-resolve-game.js +271 -0
- package/scripts/test-resolve-signature.js +223 -0
- package/scripts/test-signature-preservation.js +124 -0
- package/scripts/test-state-machine.js +291 -0
- package/scripts/test-webhook-receiver.js +60 -0
- package/scripts/update-notification-constraint.js +52 -0
- package/scripts/verify-account-layout.js +145 -0
- package/scripts/verify-winner-algorithm.js +278 -0
- package/server.js +5259 -0
- package/services/arcadeMatchService.js +763 -0
- package/services/automaticGameOracle.js +1596 -0
- package/services/chatService.js +1612 -0
- package/services/connect4GameService.js +1049 -0
- package/services/connect4NotificationService.js +374 -0
- package/services/cryptoPriceService.js +223 -0
- package/services/customGameResolver.js +260 -0
- package/services/db.js +79 -0
- package/services/directMessageService.js +389 -0
- package/services/discordNotifications.js +160 -0
- package/services/exchangeRateService.js +289 -0
- package/services/expoPushService.js +314 -0
- package/services/gamesCacheService.js +539 -0
- package/services/jackpotHistory.js +331 -0
- package/services/jackpotService.js +856 -0
- package/services/keeperStateService.js +355 -0
- package/services/matchupImageService.js +591 -0
- package/services/notificationCacheService.js +407 -0
- package/services/pickemOracle.js +440 -0
- package/services/playerStatsService.js +389 -0
- package/services/portfolioService.js +555 -0
- package/services/promoService.js +757 -0
- package/services/promoTreasuryService.js +239 -0
- package/services/pushNotifications.js +353 -0
- package/services/redisService.js +422 -0
- package/services/referralEarningsService.js +728 -0
- package/services/s3Service.js +396 -0
- package/services/socialService.js +1202 -0
- package/services/survivorOracle.js +469 -0
- package/services/survivorSimulator.js +475 -0
- package/services/telegramNotifications.js +461 -0
- package/services/userProfileStatsService.js +1185 -0
- package/services/whatsNewService.js +388 -0
- package/utils/urlHelper.js +95 -0
|
@@ -0,0 +1,754 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 🎰 Jackpot API Routes
|
|
3
|
+
*
|
|
4
|
+
* RESTful API for Solpot-style continuous jackpot
|
|
5
|
+
* Isolated from sports betting and arcade systems
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const express = require('express');
|
|
9
|
+
const router = express.Router();
|
|
10
|
+
const crypto = require('crypto');
|
|
11
|
+
const { instance: history } = require('../services/jackpotHistory');
|
|
12
|
+
const promoService = require('../services/promoService');
|
|
13
|
+
const promoTreasuryService = require('../services/promoTreasuryService');
|
|
14
|
+
|
|
15
|
+
module.exports = (jackpotService) => {
|
|
16
|
+
|
|
17
|
+
// ============================================
|
|
18
|
+
// 🏗️ BUILD TRANSACTION ENDPOINTS
|
|
19
|
+
// Returns unsigned transactions for frontend to sign
|
|
20
|
+
// ============================================
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* POST /jackpot/build/initialize
|
|
24
|
+
* Build UNSIGNED transaction for initializing the protocol
|
|
25
|
+
*
|
|
26
|
+
* Body:
|
|
27
|
+
* {
|
|
28
|
+
* adminAddress: string (Solana pubkey),
|
|
29
|
+
* treasuryAddress: string (Solana pubkey),
|
|
30
|
+
* feeBasisPoints: number (e.g., 500 = 5%),
|
|
31
|
+
* roundDurationSlots: number (e.g., 1800 = ~12 min)
|
|
32
|
+
* }
|
|
33
|
+
*/
|
|
34
|
+
router.post('/build/initialize', async (req, res) => {
|
|
35
|
+
try {
|
|
36
|
+
const {
|
|
37
|
+
adminAddress,
|
|
38
|
+
treasuryAddress,
|
|
39
|
+
feeBasisPoints = 500,
|
|
40
|
+
roundDurationSlots = 1800,
|
|
41
|
+
} = req.body;
|
|
42
|
+
|
|
43
|
+
if (!adminAddress || !treasuryAddress) {
|
|
44
|
+
return res.status(400).json({
|
|
45
|
+
error: 'Missing required fields: adminAddress, treasuryAddress'
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (feeBasisPoints > 1000) {
|
|
50
|
+
return res.status(400).json({
|
|
51
|
+
error: 'Fee cannot exceed 10% (1000 basis points)'
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const result = await jackpotService.buildInitializeTransaction({
|
|
56
|
+
adminAddress,
|
|
57
|
+
treasuryAddress,
|
|
58
|
+
feeBasisPoints,
|
|
59
|
+
roundDurationSlots,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
res.json(result);
|
|
63
|
+
} catch (error) {
|
|
64
|
+
console.error('Build initialize error:', error);
|
|
65
|
+
res.status(500).json({
|
|
66
|
+
error: error.message || 'Failed to build transaction',
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* POST /jackpot/build/update-config
|
|
73
|
+
* Build UNSIGNED transaction for updating protocol config (authority only)
|
|
74
|
+
*
|
|
75
|
+
* Body:
|
|
76
|
+
* {
|
|
77
|
+
* authorityAddress: string (Solana pubkey),
|
|
78
|
+
* feeBasisPoints?: number (e.g., 500 = 5%),
|
|
79
|
+
* roundDurationSlots?: number (e.g., 1512000 = ~1 week),
|
|
80
|
+
* newAuthority?: string (Solana pubkey)
|
|
81
|
+
* }
|
|
82
|
+
*/
|
|
83
|
+
router.post('/build/update-config', async (req, res) => {
|
|
84
|
+
try {
|
|
85
|
+
const { authorityAddress, feeBasisPoints, roundDurationSlots, newAuthority } = req.body;
|
|
86
|
+
|
|
87
|
+
if (!authorityAddress) {
|
|
88
|
+
return res.status(400).json({
|
|
89
|
+
error: 'Missing required field: authorityAddress'
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (feeBasisPoints != null && feeBasisPoints > 1000) {
|
|
94
|
+
return res.status(400).json({
|
|
95
|
+
error: 'Fee cannot exceed 10% (1000 basis points)'
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const result = await jackpotService.buildUpdateConfigTransaction({
|
|
100
|
+
authorityAddress,
|
|
101
|
+
feeBasisPoints,
|
|
102
|
+
roundDurationSlots,
|
|
103
|
+
newAuthority,
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
res.json(result);
|
|
107
|
+
} catch (error) {
|
|
108
|
+
console.error('Build update-config error:', error);
|
|
109
|
+
res.status(500).json({
|
|
110
|
+
error: error.message || 'Failed to build transaction',
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* POST /jackpot/build/open-round
|
|
117
|
+
* Build UNSIGNED transaction for opening a new round
|
|
118
|
+
*
|
|
119
|
+
* Body:
|
|
120
|
+
* {
|
|
121
|
+
* keeperAddress: string (Solana pubkey)
|
|
122
|
+
* }
|
|
123
|
+
*/
|
|
124
|
+
router.post('/build/open-round', async (req, res) => {
|
|
125
|
+
try {
|
|
126
|
+
const { keeperAddress } = req.body;
|
|
127
|
+
|
|
128
|
+
if (!keeperAddress) {
|
|
129
|
+
return res.status(400).json({
|
|
130
|
+
error: 'Missing required field: keeperAddress'
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const result = await jackpotService.buildOpenRoundTransaction({
|
|
135
|
+
keeperAddress,
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
res.json(result);
|
|
139
|
+
} catch (error) {
|
|
140
|
+
console.error('Build open round error:', error);
|
|
141
|
+
res.status(500).json({
|
|
142
|
+
error: error.message || 'Failed to build transaction',
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* POST /jackpot/build/enter
|
|
149
|
+
* Build UNSIGNED transaction for entering the current round
|
|
150
|
+
*
|
|
151
|
+
* Body:
|
|
152
|
+
* {
|
|
153
|
+
* playerAddress: string (Solana pubkey),
|
|
154
|
+
* amount: number (lamports),
|
|
155
|
+
* roundId?: string (optional, uses current round if not provided)
|
|
156
|
+
* }
|
|
157
|
+
*/
|
|
158
|
+
router.post('/build/enter', async (req, res) => {
|
|
159
|
+
try {
|
|
160
|
+
const { playerAddress, amount, roundId } = req.body;
|
|
161
|
+
|
|
162
|
+
if (!playerAddress || !amount) {
|
|
163
|
+
return res.status(400).json({
|
|
164
|
+
error: 'Missing required fields: playerAddress, amount'
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (amount < 10000) {
|
|
169
|
+
return res.status(400).json({
|
|
170
|
+
error: 'Minimum entry amount is 10,000 lamports (0.00001 SOL)'
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const result = await jackpotService.buildEnterRoundTransaction({
|
|
175
|
+
playerAddress,
|
|
176
|
+
amount,
|
|
177
|
+
roundId,
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
res.json(result);
|
|
181
|
+
} catch (error) {
|
|
182
|
+
console.error('Build enter round error:', error);
|
|
183
|
+
res.status(500).json({
|
|
184
|
+
error: error.message || 'Failed to build transaction',
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* POST /jackpot/build/enter-sponsored
|
|
191
|
+
* Build UNSIGNED transaction for entering via promo code (treasury pays)
|
|
192
|
+
* Compound tx: SystemProgram.transfer(treasury → player) + enter_round(player, amount)
|
|
193
|
+
*
|
|
194
|
+
* Body:
|
|
195
|
+
* {
|
|
196
|
+
* playerAddress: string (Solana pubkey),
|
|
197
|
+
* promoCode: string (e.g. "DUBS-XXXX-XXXX")
|
|
198
|
+
* }
|
|
199
|
+
*/
|
|
200
|
+
router.post('/build/enter-sponsored', async (req, res) => {
|
|
201
|
+
try {
|
|
202
|
+
const { playerAddress, promoCode } = req.body;
|
|
203
|
+
|
|
204
|
+
if (!playerAddress || !promoCode) {
|
|
205
|
+
return res.status(400).json({
|
|
206
|
+
error: 'Missing required fields: playerAddress, promoCode'
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Verify treasury is ready
|
|
211
|
+
if (!promoTreasuryService.isReady()) {
|
|
212
|
+
return res.status(503).json({ error: 'Promo system is currently unavailable' });
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Get user's active promo (must be reserved first)
|
|
216
|
+
const activePromo = await promoService.getActivePromoForUser(playerAddress);
|
|
217
|
+
if (!activePromo) {
|
|
218
|
+
return res.status(400).json({ error: 'No active promo code found. Please reserve a code first.' });
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Verify the promo code matches
|
|
222
|
+
if (activePromo.code !== promoCode.trim().toUpperCase()) {
|
|
223
|
+
return res.status(400).json({ error: 'Promo code does not match your reserved code' });
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Check treasury balance
|
|
227
|
+
const balanceCheck = await promoTreasuryService.hasEnoughBalance(activePromo.amountLamports);
|
|
228
|
+
if (!balanceCheck.enough) {
|
|
229
|
+
return res.status(503).json({ error: 'Promo system temporarily unavailable' });
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const treasuryKeypair = promoTreasuryService.getKeypair();
|
|
233
|
+
|
|
234
|
+
const result = await jackpotService.buildEnterRoundSponsoredTransaction({
|
|
235
|
+
playerAddress,
|
|
236
|
+
amount: activePromo.amountLamports,
|
|
237
|
+
treasuryKeypair,
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
// NOTE: Do NOT mark promo as used here!
|
|
241
|
+
// Frontend calls /api/promo/confirm-usage AFTER transaction is confirmed on-chain
|
|
242
|
+
|
|
243
|
+
res.json({
|
|
244
|
+
...result,
|
|
245
|
+
promoCode: activePromo.code,
|
|
246
|
+
sponsorWallet: treasuryKeypair.publicKey.toString(),
|
|
247
|
+
});
|
|
248
|
+
} catch (error) {
|
|
249
|
+
console.error('Build enter-sponsored error:', error);
|
|
250
|
+
res.status(500).json({
|
|
251
|
+
error: error.message || 'Failed to build sponsored transaction',
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* POST /jackpot/player-joined
|
|
258
|
+
* Notify server that a player's transaction confirmed (triggers WebSocket broadcast)
|
|
259
|
+
*
|
|
260
|
+
* Body:
|
|
261
|
+
* {
|
|
262
|
+
* playerAddress: string,
|
|
263
|
+
* roundId: string,
|
|
264
|
+
* amount: number (lamports),
|
|
265
|
+
* signature: string
|
|
266
|
+
* }
|
|
267
|
+
*/
|
|
268
|
+
router.post('/player-joined', async (req, res) => {
|
|
269
|
+
try {
|
|
270
|
+
const { playerAddress, roundId, amount, signature } = req.body;
|
|
271
|
+
|
|
272
|
+
console.log(`👤 Player joined notification: ${playerAddress.slice(0,8)}... for round ${roundId}`);
|
|
273
|
+
|
|
274
|
+
// Broadcast to all clients (globally + room) so every viewer gets the update
|
|
275
|
+
if (global.io) {
|
|
276
|
+
const event = {
|
|
277
|
+
roundId,
|
|
278
|
+
player: playerAddress,
|
|
279
|
+
weight: amount,
|
|
280
|
+
timestamp: Date.now(),
|
|
281
|
+
signature
|
|
282
|
+
};
|
|
283
|
+
global.io.to(`round_${roundId}`).emit('player_joined', event);
|
|
284
|
+
global.io.emit('player_joined', event);
|
|
285
|
+
console.log(`⚡ Broadcasted: player_joined globally to ALL clients`);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Persist entry for transaction history linking
|
|
289
|
+
if (signature && playerAddress && roundId) {
|
|
290
|
+
history.addEntry({
|
|
291
|
+
walletAddress: playerAddress,
|
|
292
|
+
roundId: roundId.toString(),
|
|
293
|
+
amount: amount || 0,
|
|
294
|
+
signature,
|
|
295
|
+
}).catch(err => console.error('Failed to persist jackpot entry:', err));
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
res.json({ success: true, broadcasted: true });
|
|
299
|
+
} catch (error) {
|
|
300
|
+
console.error('Player joined notification error:', error);
|
|
301
|
+
res.status(500).json({ error: error.message });
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* POST /jackpot/build/lock-round
|
|
307
|
+
* Build UNSIGNED transaction for locking a round (commit phase)
|
|
308
|
+
*
|
|
309
|
+
* Body:
|
|
310
|
+
* {
|
|
311
|
+
* keeperAddress: string (Solana pubkey),
|
|
312
|
+
* serverSeed?: string (optional, auto-generated if not provided),
|
|
313
|
+
* roundId?: string (optional, uses current round if not provided)
|
|
314
|
+
* }
|
|
315
|
+
*/
|
|
316
|
+
router.post('/build/lock-round', async (req, res) => {
|
|
317
|
+
try {
|
|
318
|
+
const { keeperAddress, roundId } = req.body;
|
|
319
|
+
|
|
320
|
+
if (!keeperAddress) {
|
|
321
|
+
return res.status(400).json({
|
|
322
|
+
error: 'Missing required field: keeperAddress'
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const result = await jackpotService.buildLockRoundTransaction({
|
|
327
|
+
keeperAddress,
|
|
328
|
+
roundId,
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
res.json(result);
|
|
332
|
+
} catch (error) {
|
|
333
|
+
console.error('Build lock round error:', error);
|
|
334
|
+
res.status(500).json({
|
|
335
|
+
error: error.message || 'Failed to build transaction',
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* POST /jackpot/build/resolve
|
|
342
|
+
* Build UNSIGNED transaction for resolving a round
|
|
343
|
+
*
|
|
344
|
+
* Body:
|
|
345
|
+
* {
|
|
346
|
+
* keeperAddress: string (Solana pubkey),
|
|
347
|
+
* roundId?: string (optional, uses current round if not provided)
|
|
348
|
+
* }
|
|
349
|
+
*/
|
|
350
|
+
router.post('/build/resolve', async (req, res) => {
|
|
351
|
+
try {
|
|
352
|
+
const { keeperAddress, roundId } = req.body;
|
|
353
|
+
|
|
354
|
+
if (!keeperAddress) {
|
|
355
|
+
return res.status(400).json({
|
|
356
|
+
error: 'Missing required field: keeperAddress'
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const result = await jackpotService.buildResolveRoundTransaction({
|
|
361
|
+
keeperAddress,
|
|
362
|
+
roundId,
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
res.json(result);
|
|
366
|
+
} catch (error) {
|
|
367
|
+
console.error('Build resolve error:', error);
|
|
368
|
+
res.status(500).json({
|
|
369
|
+
error: error.message || 'Failed to build transaction',
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* POST /jackpot/build/reset-round
|
|
376
|
+
* Build UNSIGNED transaction for resetting round (account reuse - Solpot style!)
|
|
377
|
+
* Reuses Round #1 accounts forever instead of creating new ones
|
|
378
|
+
*
|
|
379
|
+
* Body:
|
|
380
|
+
* {
|
|
381
|
+
* keeperAddress: string (Solana pubkey)
|
|
382
|
+
* }
|
|
383
|
+
*/
|
|
384
|
+
router.post('/build/reset-round', async (req, res) => {
|
|
385
|
+
try {
|
|
386
|
+
const { keeperAddress } = req.body;
|
|
387
|
+
|
|
388
|
+
if (!keeperAddress) {
|
|
389
|
+
return res.status(400).json({
|
|
390
|
+
error: 'Missing required field: keeperAddress'
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
const result = await jackpotService.buildResetRoundTransaction({
|
|
395
|
+
keeperAddress,
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
res.json(result);
|
|
399
|
+
} catch (error) {
|
|
400
|
+
console.error('Build reset round error:', error);
|
|
401
|
+
res.status(500).json({
|
|
402
|
+
error: error.message || 'Failed to build transaction',
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
// ============================================
|
|
408
|
+
// 🤖 ORACLE ENDPOINTS (Server-side only)
|
|
409
|
+
// ============================================
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* POST /jackpot/oracle/reveal
|
|
413
|
+
* Oracle submits random seed (reveal phase)
|
|
414
|
+
*
|
|
415
|
+
* Body:
|
|
416
|
+
* {
|
|
417
|
+
* roundId: string,
|
|
418
|
+
* oracleSeed?: string (hex, optional - auto-generated from Random.org style if not provided)
|
|
419
|
+
* }
|
|
420
|
+
*/
|
|
421
|
+
router.post('/oracle/reveal', async (req, res) => {
|
|
422
|
+
try {
|
|
423
|
+
const { roundId } = req.body;
|
|
424
|
+
let { oracleSeed } = req.body;
|
|
425
|
+
|
|
426
|
+
if (!roundId) {
|
|
427
|
+
return res.status(400).json({
|
|
428
|
+
error: 'Missing required field: roundId'
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// Generate oracle seed if not provided (from Random.org in production)
|
|
433
|
+
if (!oracleSeed) {
|
|
434
|
+
// In production, fetch from Random.org API
|
|
435
|
+
oracleSeed = crypto.randomBytes(32);
|
|
436
|
+
} else {
|
|
437
|
+
oracleSeed = Buffer.from(oracleSeed, 'hex');
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
const result = await jackpotService.consumeRandomness({
|
|
441
|
+
roundId,
|
|
442
|
+
oracleSeed,
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
res.json({
|
|
446
|
+
success: true,
|
|
447
|
+
...result,
|
|
448
|
+
});
|
|
449
|
+
} catch (error) {
|
|
450
|
+
console.error('Oracle reveal error:', error);
|
|
451
|
+
res.status(500).json({
|
|
452
|
+
error: error.message || 'Failed to reveal randomness',
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
// ============================================
|
|
458
|
+
// 📊 QUERY ENDPOINTS
|
|
459
|
+
// ============================================
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* GET /jackpot/config
|
|
463
|
+
* Get protocol configuration
|
|
464
|
+
*/
|
|
465
|
+
router.get('/config', async (req, res) => {
|
|
466
|
+
try {
|
|
467
|
+
const config = await jackpotService.getConfig();
|
|
468
|
+
|
|
469
|
+
if (!config) {
|
|
470
|
+
return res.status(404).json({
|
|
471
|
+
error: 'Protocol not initialized'
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
res.json({
|
|
476
|
+
success: true,
|
|
477
|
+
config,
|
|
478
|
+
});
|
|
479
|
+
} catch (error) {
|
|
480
|
+
console.error('Get config error:', error);
|
|
481
|
+
res.status(500).json({
|
|
482
|
+
error: error.message || 'Failed to get config',
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* GET /jackpot/round/current
|
|
489
|
+
* Get current round information
|
|
490
|
+
*/
|
|
491
|
+
router.get('/round/current', async (req, res) => {
|
|
492
|
+
try {
|
|
493
|
+
const round = await jackpotService.getCurrentRound();
|
|
494
|
+
|
|
495
|
+
if (!round) {
|
|
496
|
+
return res.status(404).json({
|
|
497
|
+
error: 'No active round'
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
res.json({
|
|
502
|
+
success: true,
|
|
503
|
+
round,
|
|
504
|
+
});
|
|
505
|
+
} catch (error) {
|
|
506
|
+
console.error('Get current round error:', error);
|
|
507
|
+
res.status(500).json({
|
|
508
|
+
error: error.message || 'Failed to get current round',
|
|
509
|
+
});
|
|
510
|
+
}
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
/**
|
|
514
|
+
* GET /jackpot/round/:roundId
|
|
515
|
+
* Get specific round information
|
|
516
|
+
*/
|
|
517
|
+
router.get('/round/:roundId', async (req, res) => {
|
|
518
|
+
try {
|
|
519
|
+
const { roundId } = req.params;
|
|
520
|
+
|
|
521
|
+
const round = await jackpotService.getRoundInfo(roundId);
|
|
522
|
+
|
|
523
|
+
if (!round) {
|
|
524
|
+
return res.status(404).json({
|
|
525
|
+
error: 'Round not found'
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
res.json({
|
|
530
|
+
success: true,
|
|
531
|
+
round,
|
|
532
|
+
});
|
|
533
|
+
} catch (error) {
|
|
534
|
+
console.error('Get round error:', error);
|
|
535
|
+
res.status(500).json({
|
|
536
|
+
error: error.message || 'Failed to get round',
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
/**
|
|
542
|
+
* GET /jackpot/round/:roundId/verify
|
|
543
|
+
* Get verification data for a round (for provably fair verification)
|
|
544
|
+
* Returns: round data, entries, server seed, oracle seed, signatures
|
|
545
|
+
*/
|
|
546
|
+
router.get('/round/:roundId/verify', async (req, res) => {
|
|
547
|
+
try {
|
|
548
|
+
const { roundId } = req.params;
|
|
549
|
+
|
|
550
|
+
// Get round from history database (has verification seeds)
|
|
551
|
+
const historyData = await history.getHistory();
|
|
552
|
+
const historicalRound = historyData.rounds.find(r => r.roundId === roundId);
|
|
553
|
+
|
|
554
|
+
if (!historicalRound) {
|
|
555
|
+
return res.status(404).json({
|
|
556
|
+
error: 'Round not found in history. Only completed rounds can be verified.'
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
// Get entries for weight verification (current round entries)
|
|
561
|
+
let entries = [];
|
|
562
|
+
try {
|
|
563
|
+
const entriesRes = await jackpotService.getRoundEntries(1); // Always round 1 PDA
|
|
564
|
+
entries = entriesRes?.entries || [];
|
|
565
|
+
} catch (e) {
|
|
566
|
+
console.log('Could not fetch entries (expected for old rounds)');
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
res.json({
|
|
570
|
+
success: true,
|
|
571
|
+
verification: {
|
|
572
|
+
roundId: historicalRound.roundId,
|
|
573
|
+
winner: historicalRound.winner,
|
|
574
|
+
winAmount: historicalRound.winAmount,
|
|
575
|
+
totalPot: historicalRound.totalPot,
|
|
576
|
+
entryCount: historicalRound.entryCount,
|
|
577
|
+
timestamp: historicalRound.timestamp,
|
|
578
|
+
signature: historicalRound.signature,
|
|
579
|
+
|
|
580
|
+
// Provably fair verification data
|
|
581
|
+
serverSeed: historicalRound.serverSeed,
|
|
582
|
+
serverSeedHash: historicalRound.serverSeedHash,
|
|
583
|
+
oracleSeed: historicalRound.oracleSeed,
|
|
584
|
+
|
|
585
|
+
// Entries for weight verification
|
|
586
|
+
entries,
|
|
587
|
+
}
|
|
588
|
+
});
|
|
589
|
+
} catch (error) {
|
|
590
|
+
console.error('Get verification data error:', error);
|
|
591
|
+
res.status(500).json({
|
|
592
|
+
error: error.message || 'Failed to get verification data',
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
});
|
|
596
|
+
|
|
597
|
+
/**
|
|
598
|
+
* GET /jackpot/round/:roundId/entries
|
|
599
|
+
* Get all entries for a round
|
|
600
|
+
* NOTE: With account reuse, we always query Round 1 PDAs!
|
|
601
|
+
*/
|
|
602
|
+
router.get('/round/:roundId/entries', async (req, res) => {
|
|
603
|
+
try {
|
|
604
|
+
const { roundId } = req.params;
|
|
605
|
+
|
|
606
|
+
// Always use Round 1 PDA for entries (account reuse!)
|
|
607
|
+
const entries = await jackpotService.getRoundEntries(1);
|
|
608
|
+
const round = await jackpotService.getRoundInfo(1);
|
|
609
|
+
|
|
610
|
+
// Calculate odds for each entry
|
|
611
|
+
const totalWeight = round ? BigInt(round.totalWeight) : 0n;
|
|
612
|
+
|
|
613
|
+
const entriesWithOdds = entries.map(entry => ({
|
|
614
|
+
...entry,
|
|
615
|
+
oddsPercent: totalWeight > 0n
|
|
616
|
+
? (Number(BigInt(entry.weight) * 10000n / totalWeight) / 100).toFixed(2)
|
|
617
|
+
: '0.00',
|
|
618
|
+
}));
|
|
619
|
+
|
|
620
|
+
res.json({
|
|
621
|
+
success: true,
|
|
622
|
+
roundId: round ? round.roundId : roundId.toString(), // Return actual round ID from account
|
|
623
|
+
entries: entriesWithOdds,
|
|
624
|
+
totalEntries: entries.length,
|
|
625
|
+
});
|
|
626
|
+
} catch (error) {
|
|
627
|
+
console.error('Get entries error:', error);
|
|
628
|
+
res.status(500).json({
|
|
629
|
+
error: error.message || 'Failed to get entries',
|
|
630
|
+
});
|
|
631
|
+
}
|
|
632
|
+
});
|
|
633
|
+
|
|
634
|
+
/**
|
|
635
|
+
* GET /jackpot/stats
|
|
636
|
+
* Get overall jackpot statistics
|
|
637
|
+
*/
|
|
638
|
+
router.get('/stats', async (req, res) => {
|
|
639
|
+
try {
|
|
640
|
+
const stats = await jackpotService.getStats();
|
|
641
|
+
|
|
642
|
+
res.json({
|
|
643
|
+
success: true,
|
|
644
|
+
...stats,
|
|
645
|
+
});
|
|
646
|
+
} catch (error) {
|
|
647
|
+
console.error('Get stats error:', error);
|
|
648
|
+
res.status(500).json({
|
|
649
|
+
error: error.message || 'Failed to get stats',
|
|
650
|
+
});
|
|
651
|
+
}
|
|
652
|
+
});
|
|
653
|
+
|
|
654
|
+
/**
|
|
655
|
+
* GET /jackpot/winner/last
|
|
656
|
+
* Get last winner information
|
|
657
|
+
*/
|
|
658
|
+
router.get('/winner/last', async (req, res) => {
|
|
659
|
+
try {
|
|
660
|
+
const lastWinner = await history.getLastWinner();
|
|
661
|
+
|
|
662
|
+
if (!lastWinner) {
|
|
663
|
+
return res.json({
|
|
664
|
+
success: true,
|
|
665
|
+
winner: null,
|
|
666
|
+
message: 'No winners yet'
|
|
667
|
+
});
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
res.json({
|
|
671
|
+
success: true,
|
|
672
|
+
winner: lastWinner,
|
|
673
|
+
});
|
|
674
|
+
} catch (error) {
|
|
675
|
+
console.error('Get last winner error:', error);
|
|
676
|
+
res.status(500).json({
|
|
677
|
+
error: error.message || 'Failed to get last winner',
|
|
678
|
+
});
|
|
679
|
+
}
|
|
680
|
+
});
|
|
681
|
+
|
|
682
|
+
/**
|
|
683
|
+
* GET /jackpot/history
|
|
684
|
+
* Get round history
|
|
685
|
+
*/
|
|
686
|
+
router.get('/history', async (req, res) => {
|
|
687
|
+
try {
|
|
688
|
+
const limit = parseInt(req.query.limit) || 10;
|
|
689
|
+
const historyData = await history.getHistory();
|
|
690
|
+
|
|
691
|
+
res.json({
|
|
692
|
+
success: true,
|
|
693
|
+
rounds: historyData.rounds.slice(0, limit),
|
|
694
|
+
total: historyData.rounds.length,
|
|
695
|
+
});
|
|
696
|
+
} catch (error) {
|
|
697
|
+
console.error('Get history error:', error);
|
|
698
|
+
res.status(500).json({
|
|
699
|
+
error: error.message || 'Failed to get history',
|
|
700
|
+
});
|
|
701
|
+
}
|
|
702
|
+
});
|
|
703
|
+
|
|
704
|
+
/**
|
|
705
|
+
* GET /jackpot/history/wallet/:walletAddress
|
|
706
|
+
* Get jackpot entries and wins for a specific wallet (for transaction history linking)
|
|
707
|
+
*/
|
|
708
|
+
router.get('/history/wallet/:walletAddress', async (req, res) => {
|
|
709
|
+
try {
|
|
710
|
+
const { walletAddress } = req.params;
|
|
711
|
+
|
|
712
|
+
if (!walletAddress || walletAddress.length < 32) {
|
|
713
|
+
return res.status(400).json({ error: 'Invalid wallet address' });
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
const [entries, wins] = await Promise.all([
|
|
717
|
+
history.getEntriesByWallet(walletAddress),
|
|
718
|
+
history.getWinsByWallet(walletAddress),
|
|
719
|
+
]);
|
|
720
|
+
|
|
721
|
+
res.json({
|
|
722
|
+
success: true,
|
|
723
|
+
entries,
|
|
724
|
+
wins,
|
|
725
|
+
});
|
|
726
|
+
} catch (error) {
|
|
727
|
+
console.error('Get wallet jackpot history error:', error);
|
|
728
|
+
res.status(500).json({
|
|
729
|
+
error: error.message || 'Failed to get wallet jackpot history',
|
|
730
|
+
});
|
|
731
|
+
}
|
|
732
|
+
});
|
|
733
|
+
|
|
734
|
+
// ============================================
|
|
735
|
+
// 🏥 HEALTH ENDPOINT
|
|
736
|
+
// ============================================
|
|
737
|
+
|
|
738
|
+
/**
|
|
739
|
+
* GET /jackpot/health
|
|
740
|
+
* Health check for jackpot service
|
|
741
|
+
*/
|
|
742
|
+
router.get('/health', (req, res) => {
|
|
743
|
+
res.json({
|
|
744
|
+
status: 'healthy',
|
|
745
|
+
service: 'jackpot',
|
|
746
|
+
programId: jackpotService.programId.toString(),
|
|
747
|
+
oracleWallet: jackpotService.oracleWallet.toString(),
|
|
748
|
+
timestamp: new Date().toISOString(),
|
|
749
|
+
});
|
|
750
|
+
});
|
|
751
|
+
|
|
752
|
+
return router;
|
|
753
|
+
};
|
|
754
|
+
|