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.
Files changed (304) hide show
  1. package/.claude/settings.local.json +280 -0
  2. package/CLAUDE.md +46 -0
  3. package/CONNECT4_PRODUCTION_DEPLOY.md +155 -0
  4. package/CURRENT_SESSION.md +171 -0
  5. package/CURRENT_SESSION_DRAW.md +516 -0
  6. package/MARCH_MADNESS_SURVIVOR.md +254 -0
  7. package/PANDA.md +166 -0
  8. package/Procfile +4 -0
  9. package/README.md +476 -0
  10. package/controllers/livescoresController.js +376 -0
  11. package/controllers/pickemController.js +554 -0
  12. package/controllers/survivorAdminController.js +887 -0
  13. package/controllers/survivorController.js +623 -0
  14. package/cron/oracleMonitor.js +77 -0
  15. package/cron/pickemOracleMonitor.js +73 -0
  16. package/data/jackpot-history.json +952 -0
  17. package/data/ncaaTeams.js +406 -0
  18. package/documentation/API_SECURITY_GUIDE.md +327 -0
  19. package/documentation/ARCADE_API.md +593 -0
  20. package/documentation/ARCADE_IMPLEMENTATION_SUMMARY.md +399 -0
  21. package/documentation/ARCADE_QUICKSTART.md +242 -0
  22. package/documentation/AUTOMATIC_MODE_ORACLE.md +321 -0
  23. package/documentation/BUG_FIX_COHORT_DATE_DISPLAY.md +171 -0
  24. package/documentation/CLAIM_MIGRATION_INSTRUCTIONS.md +52 -0
  25. package/documentation/CLAIM_STATUS_FIX.md +67 -0
  26. package/documentation/CLI_TOOL_GUIDE.md +372 -0
  27. package/documentation/COHORT_RETENTION_ANALYSIS.md +295 -0
  28. package/documentation/COHORT_RETENTION_IMPLEMENTATION_COMPLETE.md +461 -0
  29. package/documentation/COHORT_RETENTION_SUMMARY.md +204 -0
  30. package/documentation/COMPLETE_PROJECT_SUMMARY.md +490 -0
  31. package/documentation/DATABASE_QUERIES.md +269 -0
  32. package/documentation/DATABASE_RETENTION_POLICY.md +390 -0
  33. package/documentation/DATABASE_SETUP_GUIDE.md +361 -0
  34. package/documentation/DATABASE_SETUP_SUMMARY.md +247 -0
  35. package/documentation/DEMO_API_CURL_COMMANDS.md +656 -0
  36. package/documentation/DEPLOYMENT_SUMMARY.txt +100 -0
  37. package/documentation/DUPLICATE_NOTIFICATIONS_FIXED.md +201 -0
  38. package/documentation/EXCHANGE_RATES_INTEGRATION.md +371 -0
  39. package/documentation/FINAL_API_PROTECTION_TABLE.md +175 -0
  40. package/documentation/GAME_START_NOTIFICATIONS_DEPLOYMENT.md +256 -0
  41. package/documentation/GAME_START_NOTIFICATIONS_INTEGRATION.md +275 -0
  42. package/documentation/HEROKU_DEPLOYMENT.md +134 -0
  43. package/documentation/HEROKU_SCHEDULER_SETUP.md +271 -0
  44. package/documentation/JACKPOT_API.md +521 -0
  45. package/documentation/JACKPOT_DEPLOYMENT_GUIDE.md +362 -0
  46. package/documentation/JWT_IMPLEMENTATION_SUMMARY.md +373 -0
  47. package/documentation/JWT_QUICK_SETUP.md +268 -0
  48. package/documentation/JWT_TESTING_GUIDE.md +404 -0
  49. package/documentation/KEEPER_RECOVERY_GUIDE.md +381 -0
  50. package/documentation/KEEPER_SETUP.md +206 -0
  51. package/documentation/KEEPER_STATE_MACHINE.md +423 -0
  52. package/documentation/LATEST_PRODUCTION_SETUP.md +387 -0
  53. package/documentation/LOCAL_VOTING_TEST.md +279 -0
  54. package/documentation/ORACLE_FIXES_SUMMARY.md +188 -0
  55. package/documentation/ORACLE_POSTGRESQL_UPDATE.md +202 -0
  56. package/documentation/PAYMENT_DEPLOYMENT.md +209 -0
  57. package/documentation/PNL_TRACKING_SETUP.md +189 -0
  58. package/documentation/PREVENTING_LOCKUP_ERRORS.md +472 -0
  59. package/documentation/PRODUCTION_READY_SUMMARY.md +227 -0
  60. package/documentation/PUBLIC_VS_PRIVATE_ENDPOINTS.md +278 -0
  61. package/documentation/QUICK_AUTH_SETUP.md +99 -0
  62. package/documentation/QUICK_DEPLOY.md +224 -0
  63. package/documentation/QUICK_FIX.md +114 -0
  64. package/documentation/QUICK_START.md +152 -0
  65. package/documentation/REFEREE_MODE_GUIDE.md +392 -0
  66. package/documentation/RETENTION_CORE_ACTION_UPDATE.md +313 -0
  67. package/documentation/RETENTION_UPDATE_SUMMARY.md +108 -0
  68. package/documentation/RUN_MIGRATION_NOW.md +39 -0
  69. package/documentation/SCRIPTS_UPDATE_SUMMARY.md +251 -0
  70. package/documentation/SETUP_GUIDE.md +184 -0
  71. package/documentation/STATE_MACHINE_IMPLEMENTATION.md +250 -0
  72. package/documentation/TELEGRAM_NOTIFICATIONS_DIAGNOSIS.md +361 -0
  73. package/documentation/UNIFIED_ARCHITECTURE.md +231 -0
  74. package/documentation/VOTING_DEPLOYMENT_SUMMARY.md +392 -0
  75. package/documentation/WEBSOCKET_ARCHITECTURE.md +881 -0
  76. package/documentation/WHAT_WE_BUILT_TODAY.md +369 -0
  77. package/documentation/latest/LATEST_PRODUCTION_SETUP.md +865 -0
  78. package/ecosystem.config.js +65 -0
  79. package/env.template +125 -0
  80. package/middleware/apiKeyAuth.js +136 -0
  81. package/middleware/authenticate.js +214 -0
  82. package/middleware/developerUserAuth.js +76 -0
  83. package/middleware/socketAuth.js +69 -0
  84. package/package.json +49 -0
  85. package/postman/Dubs-API-v1-With-Voting.postman_collection.json +555 -0
  86. package/postman/Dubs-API-v1.postman_collection.json +205 -0
  87. package/postman/Dubs_Developer_API.postman_collection.json +662 -0
  88. package/postman/QUICKSTART.md +118 -0
  89. package/postman/QUICK_REFERENCE.md +246 -0
  90. package/postman/README.md +71 -0
  91. package/postman/VOTING_API_GUIDE.md +426 -0
  92. package/refactor/Animations.md +148 -0
  93. package/refactor/Chat.md +252 -0
  94. package/routes/actionsRoutes.js +699 -0
  95. package/routes/adminRoutes.js +370 -0
  96. package/routes/analyticsRoutes.js +1262 -0
  97. package/routes/arcadeRoutes.js +557 -0
  98. package/routes/authRoutes.js +2310 -0
  99. package/routes/avatarRoutes.js +85 -0
  100. package/routes/botRoutes.js +211 -0
  101. package/routes/chatRoutes.js +377 -0
  102. package/routes/cryptoPriceRoutes.js +105 -0
  103. package/routes/developerRoutes.js +4201 -0
  104. package/routes/deviceRoutes.js +214 -0
  105. package/routes/dmRoutes.js +167 -0
  106. package/routes/esportsRoutes.js +806 -0
  107. package/routes/exchangeRateRoutes.js +233 -0
  108. package/routes/gamesRoutes.js +3028 -0
  109. package/routes/jackpotRoutes.js +754 -0
  110. package/routes/keeperMonitoringRoutes.js +156 -0
  111. package/routes/keeperWebhookRoutes.js +466 -0
  112. package/routes/livescoresRoutes.js +31 -0
  113. package/routes/pickemAdminRoutes.js +199 -0
  114. package/routes/pickemRoutes.js +231 -0
  115. package/routes/playerStatsRoutes.js +147 -0
  116. package/routes/portfolioRoutes.js +217 -0
  117. package/routes/promoRoutes.js +418 -0
  118. package/routes/referralEarningsRoutes.js +392 -0
  119. package/routes/socialRoutes.js +459 -0
  120. package/routes/sportsRoutes.js +1271 -0
  121. package/routes/survivorAdminRoutes.js +345 -0
  122. package/routes/survivorRoutes.js +756 -0
  123. package/routes/uploadRoutes.js +256 -0
  124. package/routes/userProfileRoutes.js +244 -0
  125. package/routes/whatsNewRoutes.js +331 -0
  126. package/scripts/.claude/settings.local.json +15 -0
  127. package/scripts/README.md +170 -0
  128. package/scripts/RESTART_EVERYTHING.sh +104 -0
  129. package/scripts/add-claim-columns.sql +48 -0
  130. package/scripts/add-crypto-prices-cache.sql +27 -0
  131. package/scripts/add-exchange-rates-cache.sql +40 -0
  132. package/scripts/add-game-invite-column.sql +23 -0
  133. package/scripts/add-game-invite-notification.sql +33 -0
  134. package/scripts/add-game-invite-telegram-pref.sql +16 -0
  135. package/scripts/add-game-joined-notification.sql +16 -0
  136. package/scripts/add-game-joined-pref.js +40 -0
  137. package/scripts/add-game-joined-preference.sql +6 -0
  138. package/scripts/add-game-start-notifications.sql +41 -0
  139. package/scripts/add-notification-flags-to-games.sql +55 -0
  140. package/scripts/add-pending-game-dismissals.sql +19 -0
  141. package/scripts/add-preferred-currency.sql +34 -0
  142. package/scripts/add-winner-columns.js +61 -0
  143. package/scripts/add_mention_system.sql +53 -0
  144. package/scripts/add_payment_system.sql +96 -0
  145. package/scripts/add_sports_event_id_column.sql +22 -0
  146. package/scripts/analyze-cohort-data-heroku.js +276 -0
  147. package/scripts/analyze-cohort-data.js +295 -0
  148. package/scripts/analyze-prod-cohorts.sh +10 -0
  149. package/scripts/backfill-matchup-images.js +245 -0
  150. package/scripts/backfill-missing-signatures.js +175 -0
  151. package/scripts/backfill-referral-earnings.js +202 -0
  152. package/scripts/check-chat-schema.js +130 -0
  153. package/scripts/check-db.sh +14 -0
  154. package/scripts/check_oracle_in_game.js +54 -0
  155. package/scripts/cleanup-database.js +193 -0
  156. package/scripts/clear-notification-cache.js +85 -0
  157. package/scripts/convert-mnemonic.js +50 -0
  158. package/scripts/create-users-table.sql +44 -0
  159. package/scripts/debug-cohort-counts.js +248 -0
  160. package/scripts/debug-winner-calc.js +84 -0
  161. package/scripts/deploy-payment-system.sh +118 -0
  162. package/scripts/deploy-to-heroku.sh +63 -0
  163. package/scripts/diagnose-locked-round.js +143 -0
  164. package/scripts/dubs-cli.js +720 -0
  165. package/scripts/dump-account.js +65 -0
  166. package/scripts/find-vrf-offset.js +48 -0
  167. package/scripts/fix-chat-notifications-constraint.sql +122 -0
  168. package/scripts/fix-claim-columns.js +124 -0
  169. package/scripts/fix-constraint-now.js +44 -0
  170. package/scripts/fix-lock-timestamps.js +96 -0
  171. package/scripts/fix-locked-round.sh +126 -0
  172. package/scripts/fix-missing-badges.sql +91 -0
  173. package/scripts/fix-payment-notifications.sql +41 -0
  174. package/scripts/force-new-round.js +55 -0
  175. package/scripts/force-resolve-and-claim.js +278 -0
  176. package/scripts/important/README.md +115 -0
  177. package/scripts/important/authority-force-lock.js +197 -0
  178. package/scripts/important/authority-resolve-game.js +267 -0
  179. package/scripts/important/check-game-status.js +373 -0
  180. package/scripts/important/list-pending-games-by-version.js +270 -0
  181. package/scripts/important/reconcile-v1-v2-payouts.js +270 -0
  182. package/scripts/initialize-jackpot.js +111 -0
  183. package/scripts/jackpot/.claude/settings.local.json +10 -0
  184. package/scripts/jackpot/force-reset.js +84 -0
  185. package/scripts/jackpot/initialize-mainnet.js +100 -0
  186. package/scripts/jackpot/keeper.js +742 -0
  187. package/scripts/jackpot/status.js +107 -0
  188. package/scripts/jackpot/update-round-duration.js +143 -0
  189. package/scripts/keeper-bot.js +112 -0
  190. package/scripts/list-pending-games.js +131 -0
  191. package/scripts/migrate-chat-v2.js +127 -0
  192. package/scripts/migrate-chat-winners.js +84 -0
  193. package/scripts/migrate-chat.sh +17 -0
  194. package/scripts/migrate-game-invite.js +83 -0
  195. package/scripts/migrate-heroku-game-notifications.sh +159 -0
  196. package/scripts/migrations/001_analytics_tables.sql +422 -0
  197. package/scripts/migrations/002_add_matchup_image_url.sql +14 -0
  198. package/scripts/migrations/003_referral_earnings.sql +208 -0
  199. package/scripts/migrations/004_add_whats_new_notification_type.sql +62 -0
  200. package/scripts/migrations/005_add_connect4_your_turn_notification.sql +61 -0
  201. package/scripts/migrations/005_push_notifications.sql +55 -0
  202. package/scripts/migrations/006_add_draw_team_players.sql +28 -0
  203. package/scripts/migrations/006_add_game_cancelled_notification.sql +62 -0
  204. package/scripts/migrations/007_add_gif_url.sql +8 -0
  205. package/scripts/migrations/008_add_connect4_columns.sql +139 -0
  206. package/scripts/migrations/008_add_pool_tracking.sql +22 -0
  207. package/scripts/migrations/009_create_survivor_pool_tables.sql +174 -0
  208. package/scripts/migrations/010_add_survivor_pool_outcome.sql +28 -0
  209. package/scripts/migrations/011_create_developer_tables.sql +67 -0
  210. package/scripts/migrations/011_fix_keeper_tables.sql +85 -0
  211. package/scripts/migrations/012_create_developer_webhooks.sql +31 -0
  212. package/scripts/migrations/013_add_network_mode.sql +18 -0
  213. package/scripts/migrations/014_create_developer_app_users.sql +19 -0
  214. package/scripts/migrations/015_add_ui_config.sql +4 -0
  215. package/scripts/migrations/016_add_resolution_secret.sql +4 -0
  216. package/scripts/migrations/017_add_external_game_id.sql +3 -0
  217. package/scripts/migrations/018_create_pickem_tables.sql +115 -0
  218. package/scripts/migrations/019_expo_push_tokens.sql +19 -0
  219. package/scripts/migrations/create_whats_new_tables.sql +88 -0
  220. package/scripts/migrations/drop_live_games_tables.sql +34 -0
  221. package/scripts/open-jackpot-round.js +85 -0
  222. package/scripts/purge-all-data.sh +329 -0
  223. package/scripts/purge-all-data.sql +142 -0
  224. package/scripts/purge-heroku-data.sh +149 -0
  225. package/scripts/purge-heroku-data.sql +62 -0
  226. package/scripts/rebuild-heroku-database.sh +113 -0
  227. package/scripts/recover-funds.js +357 -0
  228. package/scripts/regenerate-epl-images.js +278 -0
  229. package/scripts/resize-s3-matchup-images.js +374 -0
  230. package/scripts/resolve-direct.js +88 -0
  231. package/scripts/resolve-mock-game.js +124 -0
  232. package/scripts/resolve-pickem-game.js +55 -0
  233. package/scripts/resolve-round-manual.js +83 -0
  234. package/scripts/resolve-stuck-game.js +382 -0
  235. package/scripts/resolve-stuck-round.js +42 -0
  236. package/scripts/run-connect4-migration.sh +16 -0
  237. package/scripts/run-mention-migration.sh +32 -0
  238. package/scripts/run-payment-migration.sh +51 -0
  239. package/scripts/run-preferred-currency-migration.sh +31 -0
  240. package/scripts/run-referral-earnings-migration.sh +32 -0
  241. package/scripts/run-survivor-outcome-migration.sh +16 -0
  242. package/scripts/seed-test-users.js +346 -0
  243. package/scripts/setup-auth-tables.js +78 -0
  244. package/scripts/setup-complete-database.sql +992 -0
  245. package/scripts/setup-database-fresh.sh +359 -0
  246. package/scripts/setup-heroku-keeper.sh +48 -0
  247. package/scripts/setup-keeper-database.js +83 -0
  248. package/scripts/setup-keeper-state-db.sql +110 -0
  249. package/scripts/setup-oracle.sh +39 -0
  250. package/scripts/setup-pnl-tracking.js +111 -0
  251. package/scripts/start-devnet.sh +14 -0
  252. package/scripts/test-arcade-devnet.sh +160 -0
  253. package/scripts/test-arcade-match.sh +109 -0
  254. package/scripts/test-automatic-mode.sh +239 -0
  255. package/scripts/test-connect4-cancel-claim.js +370 -0
  256. package/scripts/test-connect4-e2e.js +369 -0
  257. package/scripts/test-connect4-resolve.js +369 -0
  258. package/scripts/test-game-state-endpoint.js +136 -0
  259. package/scripts/test-invite-notification.js +86 -0
  260. package/scripts/test-jackpot-api.sh +71 -0
  261. package/scripts/test-poll-confirmation.js +267 -0
  262. package/scripts/test-resolve-game.js +271 -0
  263. package/scripts/test-resolve-signature.js +223 -0
  264. package/scripts/test-signature-preservation.js +124 -0
  265. package/scripts/test-state-machine.js +291 -0
  266. package/scripts/test-webhook-receiver.js +60 -0
  267. package/scripts/update-notification-constraint.js +52 -0
  268. package/scripts/verify-account-layout.js +145 -0
  269. package/scripts/verify-winner-algorithm.js +278 -0
  270. package/server.js +5259 -0
  271. package/services/arcadeMatchService.js +763 -0
  272. package/services/automaticGameOracle.js +1596 -0
  273. package/services/chatService.js +1612 -0
  274. package/services/connect4GameService.js +1049 -0
  275. package/services/connect4NotificationService.js +374 -0
  276. package/services/cryptoPriceService.js +223 -0
  277. package/services/customGameResolver.js +260 -0
  278. package/services/db.js +79 -0
  279. package/services/directMessageService.js +389 -0
  280. package/services/discordNotifications.js +160 -0
  281. package/services/exchangeRateService.js +289 -0
  282. package/services/expoPushService.js +314 -0
  283. package/services/gamesCacheService.js +539 -0
  284. package/services/jackpotHistory.js +331 -0
  285. package/services/jackpotService.js +856 -0
  286. package/services/keeperStateService.js +355 -0
  287. package/services/matchupImageService.js +591 -0
  288. package/services/notificationCacheService.js +407 -0
  289. package/services/pickemOracle.js +440 -0
  290. package/services/playerStatsService.js +389 -0
  291. package/services/portfolioService.js +555 -0
  292. package/services/promoService.js +757 -0
  293. package/services/promoTreasuryService.js +239 -0
  294. package/services/pushNotifications.js +353 -0
  295. package/services/redisService.js +422 -0
  296. package/services/referralEarningsService.js +728 -0
  297. package/services/s3Service.js +396 -0
  298. package/services/socialService.js +1202 -0
  299. package/services/survivorOracle.js +469 -0
  300. package/services/survivorSimulator.js +475 -0
  301. package/services/telegramNotifications.js +461 -0
  302. package/services/userProfileStatsService.js +1185 -0
  303. package/services/whatsNewService.js +388 -0
  304. package/utils/urlHelper.js +95 -0
@@ -0,0 +1,331 @@
1
+ /**
2
+ * 🎰 Jackpot History Service (Postgres)
3
+ *
4
+ * Tracks round results in Heroku Postgres database
5
+ * NOTE: Gracefully degrades when DATABASE_URL is not set (local dev)
6
+ */
7
+
8
+ const { pool } = require('./db'); // Shared database pool
9
+
10
+ class JackpotHistory {
11
+ constructor() {
12
+ // Check if database is configured
13
+ this.enabled = !!process.env.DATABASE_URL;
14
+
15
+ // In-memory storage for local dev (fallback when no DATABASE_URL)
16
+ this.memoryRounds = [];
17
+ this.lastWinnerMemory = null;
18
+
19
+ if (this.enabled) {
20
+ // Use shared pool from services/db.js
21
+ this.pool = pool;
22
+
23
+ // Initialize table on startup
24
+ this.initializeTable();
25
+ } else {
26
+ console.log('⚠️ Jackpot history: No DATABASE_URL set - using in-memory storage (local dev mode)');
27
+ this.pool = null;
28
+ }
29
+ }
30
+
31
+ // Helper to check if DB operations should run
32
+ _shouldRun() {
33
+ return this.enabled && this.pool;
34
+ }
35
+
36
+ async initializeTable() {
37
+ if (!this._shouldRun()) return;
38
+
39
+ try {
40
+ await this.pool.query(`
41
+ CREATE TABLE IF NOT EXISTS jackpot_rounds (
42
+ id SERIAL PRIMARY KEY,
43
+ round_id VARCHAR(50) UNIQUE NOT NULL,
44
+ winner VARCHAR(100) NOT NULL,
45
+ win_amount VARCHAR(50) NOT NULL,
46
+ total_pot VARCHAR(50) NOT NULL,
47
+ entry_count INTEGER NOT NULL,
48
+ timestamp TIMESTAMP DEFAULT NOW(),
49
+ signature VARCHAR(200),
50
+ server_seed TEXT,
51
+ server_seed_hash TEXT,
52
+ oracle_seed TEXT,
53
+ created_at TIMESTAMP DEFAULT NOW()
54
+ );
55
+
56
+ CREATE INDEX IF NOT EXISTS idx_round_id ON jackpot_rounds(round_id);
57
+ CREATE INDEX IF NOT EXISTS idx_timestamp ON jackpot_rounds(timestamp DESC);
58
+
59
+ CREATE TABLE IF NOT EXISTS jackpot_entries (
60
+ id SERIAL PRIMARY KEY,
61
+ wallet_address VARCHAR(64) NOT NULL,
62
+ round_id VARCHAR(32) NOT NULL,
63
+ amount BIGINT NOT NULL,
64
+ transaction_signature VARCHAR(128) NOT NULL,
65
+ created_at TIMESTAMP DEFAULT NOW()
66
+ );
67
+
68
+ CREATE INDEX IF NOT EXISTS idx_jackpot_entries_wallet ON jackpot_entries(wallet_address);
69
+ CREATE INDEX IF NOT EXISTS idx_jackpot_entries_sig ON jackpot_entries(transaction_signature);
70
+ `);
71
+
72
+ // Migrate: add VRF columns if table existed before they were added
73
+ const cols = ['timestamp', 'signature', 'server_seed', 'server_seed_hash', 'oracle_seed'];
74
+ for (const col of cols) {
75
+ try {
76
+ const type = col === 'timestamp' ? 'TIMESTAMP DEFAULT NOW()'
77
+ : col === 'signature' ? 'VARCHAR(200)'
78
+ : 'TEXT';
79
+ await this.pool.query(`ALTER TABLE jackpot_rounds ADD COLUMN IF NOT EXISTS ${col} ${type}`);
80
+ } catch (e) {
81
+ // Column already exists — safe to ignore
82
+ }
83
+ }
84
+
85
+ console.log('✅ Jackpot history table initialized');
86
+ } catch (error) {
87
+ console.error('❌ Failed to initialize table:', error.message);
88
+ }
89
+ }
90
+
91
+ async getHistory() {
92
+ // Fallback to in-memory storage for local dev
93
+ if (!this._shouldRun()) {
94
+ return { rounds: this.memoryRounds.slice().reverse().slice(0, 100) };
95
+ }
96
+
97
+ try {
98
+ const result = await this.pool.query(
99
+ 'SELECT * FROM jackpot_rounds ORDER BY timestamp DESC LIMIT 100'
100
+ );
101
+
102
+ return {
103
+ rounds: result.rows.map(row => ({
104
+ roundId: row.round_id,
105
+ winner: row.winner,
106
+ winAmount: row.win_amount,
107
+ totalPot: row.total_pot,
108
+ entryCount: row.entry_count,
109
+ timestamp: row.timestamp,
110
+ signature: row.signature,
111
+ serverSeed: row.server_seed,
112
+ serverSeedHash: row.server_seed_hash,
113
+ oracleSeed: row.oracle_seed,
114
+ }))
115
+ };
116
+ } catch (error) {
117
+ console.error('Error getting history:', error);
118
+ return { rounds: [] };
119
+ }
120
+ }
121
+
122
+ async addRound(roundData) {
123
+ const normalizedData = {
124
+ roundId: roundData.roundId,
125
+ winner: roundData.winner,
126
+ winAmount: roundData.winAmount,
127
+ totalPot: roundData.totalPot,
128
+ entryCount: roundData.entryCount,
129
+ timestamp: roundData.timestamp || new Date().toISOString(),
130
+ signature: roundData.signature || null,
131
+ serverSeed: roundData.serverSeed || null,
132
+ serverSeedHash: roundData.serverSeedHash || null,
133
+ oracleSeed: roundData.oracleSeed || null,
134
+ };
135
+
136
+ // Always update in-memory storage (for local dev fallback)
137
+ this.memoryRounds.push(normalizedData);
138
+ this.lastWinnerMemory = normalizedData;
139
+ console.log(`📝 [Memory] Saved round ${roundData.roundId} - Winner: ${roundData.winner.slice(0,8)}...`);
140
+
141
+ if (!this._shouldRun()) return;
142
+
143
+ try {
144
+ await this.pool.query(
145
+ `INSERT INTO jackpot_rounds
146
+ (round_id, winner, win_amount, total_pot, entry_count, timestamp, signature, server_seed, server_seed_hash, oracle_seed)
147
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
148
+ ON CONFLICT (round_id) DO UPDATE SET
149
+ winner = EXCLUDED.winner,
150
+ win_amount = EXCLUDED.win_amount,
151
+ total_pot = EXCLUDED.total_pot,
152
+ entry_count = EXCLUDED.entry_count,
153
+ timestamp = EXCLUDED.timestamp,
154
+ signature = EXCLUDED.signature,
155
+ server_seed = EXCLUDED.server_seed,
156
+ server_seed_hash = EXCLUDED.server_seed_hash,
157
+ oracle_seed = EXCLUDED.oracle_seed`,
158
+ [
159
+ normalizedData.roundId,
160
+ normalizedData.winner,
161
+ normalizedData.winAmount,
162
+ normalizedData.totalPot,
163
+ normalizedData.entryCount,
164
+ normalizedData.timestamp,
165
+ normalizedData.signature,
166
+ normalizedData.serverSeed,
167
+ normalizedData.serverSeedHash,
168
+ normalizedData.oracleSeed,
169
+ ]
170
+ );
171
+ console.log(`📝 [DB] Saved round ${roundData.roundId} to history`);
172
+ } catch (error) {
173
+ console.error('Error adding round to DB:', error);
174
+ }
175
+ }
176
+
177
+ async getLastWinner() {
178
+ // Fallback to in-memory storage for local dev
179
+ if (!this._shouldRun()) {
180
+ return this.lastWinnerMemory;
181
+ }
182
+
183
+ try {
184
+ const result = await this.pool.query(
185
+ 'SELECT * FROM jackpot_rounds ORDER BY timestamp DESC LIMIT 1'
186
+ );
187
+
188
+ if (result.rows.length === 0) return null;
189
+
190
+ const row = result.rows[0];
191
+ return {
192
+ roundId: row.round_id,
193
+ winner: row.winner,
194
+ winAmount: row.win_amount,
195
+ totalPot: row.total_pot,
196
+ entryCount: row.entry_count,
197
+ timestamp: row.timestamp,
198
+ signature: row.signature,
199
+ serverSeed: row.server_seed,
200
+ serverSeedHash: row.server_seed_hash,
201
+ oracleSeed: row.oracle_seed,
202
+ };
203
+ } catch (error) {
204
+ console.error('Error getting last winner:', error);
205
+ return null;
206
+ }
207
+ }
208
+
209
+ async getRound(roundId) {
210
+ if (!this._shouldRun()) return null;
211
+
212
+ try {
213
+ const result = await this.pool.query(
214
+ 'SELECT * FROM jackpot_rounds WHERE round_id = $1',
215
+ [roundId.toString()]
216
+ );
217
+
218
+ if (result.rows.length === 0) return null;
219
+
220
+ const row = result.rows[0];
221
+ return {
222
+ roundId: row.round_id,
223
+ winner: row.winner,
224
+ winAmount: row.win_amount,
225
+ totalPot: row.total_pot,
226
+ entryCount: row.entry_count,
227
+ timestamp: row.timestamp,
228
+ signature: row.signature,
229
+ serverSeed: row.server_seed,
230
+ serverSeedHash: row.server_seed_hash,
231
+ oracleSeed: row.oracle_seed,
232
+ };
233
+ } catch (error) {
234
+ console.error('Error getting round:', error);
235
+ return null;
236
+ }
237
+ }
238
+
239
+ async addEntry({ walletAddress, roundId, amount, signature }) {
240
+ if (!this._shouldRun()) return;
241
+
242
+ try {
243
+ await this.pool.query(
244
+ `INSERT INTO jackpot_entries (wallet_address, round_id, amount, transaction_signature)
245
+ VALUES ($1, $2, $3, $4)
246
+ ON CONFLICT DO NOTHING`,
247
+ [walletAddress, roundId.toString(), amount, signature]
248
+ );
249
+ console.log(`📝 [DB] Saved jackpot entry for ${walletAddress.slice(0, 8)}... in round ${roundId}`);
250
+ } catch (error) {
251
+ console.error('Error saving jackpot entry:', error);
252
+ }
253
+ }
254
+
255
+ async getEntriesByWallet(walletAddress) {
256
+ if (!this._shouldRun()) return [];
257
+
258
+ try {
259
+ const result = await this.pool.query(
260
+ `SELECT round_id, amount, transaction_signature, created_at
261
+ FROM jackpot_entries
262
+ WHERE wallet_address = $1
263
+ ORDER BY created_at DESC
264
+ LIMIT 200`,
265
+ [walletAddress]
266
+ );
267
+
268
+ return result.rows.map(row => ({
269
+ roundId: row.round_id,
270
+ amount: row.amount.toString(),
271
+ signature: row.transaction_signature,
272
+ createdAt: row.created_at,
273
+ }));
274
+ } catch (error) {
275
+ console.error('Error getting jackpot entries by wallet:', error);
276
+ return [];
277
+ }
278
+ }
279
+
280
+ async getWinsByWallet(walletAddress) {
281
+ if (!this._shouldRun()) return [];
282
+
283
+ try {
284
+ const result = await this.pool.query(
285
+ `SELECT round_id, win_amount, total_pot, entry_count, signature, timestamp
286
+ FROM jackpot_rounds
287
+ WHERE winner = $1
288
+ ORDER BY timestamp DESC
289
+ LIMIT 200`,
290
+ [walletAddress]
291
+ );
292
+
293
+ return result.rows.map(row => ({
294
+ roundId: row.round_id,
295
+ winAmount: row.win_amount,
296
+ totalPot: row.total_pot,
297
+ entryCount: row.entry_count,
298
+ signature: row.signature,
299
+ timestamp: row.timestamp,
300
+ }));
301
+ } catch (error) {
302
+ console.error('Error getting jackpot wins by wallet:', error);
303
+ return [];
304
+ }
305
+ }
306
+
307
+ async getStats() {
308
+ if (!this._shouldRun()) return null;
309
+
310
+ try {
311
+ const result = await this.pool.query(`
312
+ SELECT
313
+ COUNT(*) as total_rounds,
314
+ SUM(CAST(total_pot AS BIGINT)) as total_volume,
315
+ SUM(CAST(win_amount AS BIGINT)) as total_winnings,
316
+ SUM(CAST(total_pot AS BIGINT) - CAST(win_amount AS BIGINT)) as total_fees
317
+ FROM jackpot_rounds
318
+ `);
319
+
320
+ return result.rows[0];
321
+ } catch (error) {
322
+ console.error('Error getting stats:', error);
323
+ return null;
324
+ }
325
+ }
326
+ }
327
+
328
+ // Export both class and singleton instance for shared state
329
+ const jackpotHistoryInstance = new JackpotHistory();
330
+ module.exports = JackpotHistory;
331
+ module.exports.instance = jackpotHistoryInstance;