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,107 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { Connection, PublicKey } = require('@solana/web3.js');
4
+ const axios = require('axios');
5
+
6
+ const PROGRAM_ID = new PublicKey('BHidyz25KWkNPdTHgeANzMg25MM2KEiNnG4yE5F46XUz');
7
+ const RPC = process.env.SOLANA_RPC_URL || 'https://api.devnet.solana.com';
8
+ const API_BASE = process.env.API_BASE || 'http://localhost:3001';
9
+
10
+ (async () => {
11
+ const conn = new Connection(RPC);
12
+
13
+ console.log('🎰 DUBS JACKPOT - COMPLETE STATUS\n');
14
+ console.log('='.repeat(70));
15
+ console.log(' RPC:', RPC.includes('devnet') ? 'Devnet' : RPC.slice(0, 50) + '...');
16
+ console.log(' API:', API_BASE);
17
+
18
+ // Config
19
+ const [configPda] = PublicKey.findProgramAddressSync([Buffer.from('config')], PROGRAM_ID);
20
+ const configInfo = await conn.getAccountInfo(configPda);
21
+ const configRoundId = configInfo.data.readBigUInt64LE(82);
22
+
23
+ console.log('\n📝 CONFIG ACCOUNT:');
24
+ console.log(' Current Round ID:', configRoundId.toString());
25
+ console.log(' Fee:', configInfo.data.readUInt16LE(72), 'bps');
26
+ const durationSlots = Number(configInfo.data.readBigUInt64LE(74));
27
+ const durationSecs = Math.floor(durationSlots * 0.4);
28
+ const durationLabel = durationSecs >= 86400
29
+ ? `${(durationSecs / 86400).toFixed(1)} days`
30
+ : durationSecs >= 3600
31
+ ? `${(durationSecs / 3600).toFixed(1)} hours`
32
+ : `${(durationSecs / 60).toFixed(1)} min`;
33
+ console.log(' Round Duration:', durationSlots.toLocaleString(), `slots (~${durationLabel})`);
34
+
35
+ // Round 1 Account (Reused)
36
+ const [round1Pda] = PublicKey.findProgramAddressSync([Buffer.from('round'), Buffer.from([1,0,0,0,0,0,0,0])], PROGRAM_ID);
37
+ const roundInfo = await conn.getAccountInfo(round1Pda);
38
+ const currentSlot = await conn.getSlot();
39
+
40
+ const roundId = roundInfo.data.readBigUInt64LE(8);
41
+ const status = roundInfo.data[16];
42
+ const startSlot = roundInfo.data.readBigUInt64LE(17);
43
+ const endSlot = roundInfo.data.readBigUInt64LE(25);
44
+ const pot = Number(roundInfo.data.readBigUInt64LE(33));
45
+
46
+ console.log('\n🎲 ROUND 1 ACCOUNT (Reused):');
47
+ console.log(' Stored Round ID:', roundId.toString());
48
+ console.log(' Status:', ['Open', 'Locked', 'Resolved'][status]);
49
+ console.log(' Pot:', (pot/1e9).toFixed(4), 'SOL');
50
+ console.log(' Start Slot:', startSlot.toString());
51
+ console.log(' End Slot:', endSlot.toString());
52
+ console.log(' Current Slot:', currentSlot);
53
+ const timeLeft = Math.max(0, Number(endSlot) - currentSlot);
54
+ const secsLeft = Math.floor(timeLeft * 0.4);
55
+ const timeLabel = secsLeft >= 86400
56
+ ? `${(secsLeft / 86400).toFixed(2)} days`
57
+ : secsLeft >= 3600
58
+ ? `${(secsLeft / 3600).toFixed(1)} hours`
59
+ : `${secsLeft}s`;
60
+ console.log(' Time Left:', timeLeft.toLocaleString(), `slots (~${timeLabel})`);
61
+
62
+ // Entries
63
+ const [entries1Pda] = PublicKey.findProgramAddressSync([Buffer.from('entries'), Buffer.from([1,0,0,0,0,0,0,0])], PROGRAM_ID);
64
+ const entriesInfo = await conn.getAccountInfo(entries1Pda);
65
+ const vecLength = entriesInfo.data.readUInt32LE(16);
66
+
67
+ console.log('\n👥 ENTRIES ACCOUNT:');
68
+ console.log(' Entry Count:', vecLength);
69
+
70
+ if (vecLength > 0 && vecLength < 100) {
71
+ console.log(' Players:');
72
+ for (let i = 0; i < vecLength; i++) {
73
+ const offset = 20 + (i * 56);
74
+ const player = new PublicKey(entriesInfo.data.slice(offset, offset + 32));
75
+ const weight = Number(entriesInfo.data.readBigUInt64LE(offset + 32));
76
+ const chance = pot > 0 ? (weight / pot * 100).toFixed(2) : '0';
77
+ console.log(` ${i+1}. ${player.toString().slice(0,6)}...${player.toString().slice(-4)} - ${(weight/1e9).toFixed(3)} SOL (${chance}%)`);
78
+ }
79
+ }
80
+
81
+ console.log('\n' + '='.repeat(70));
82
+ console.log('\n✅ SYSTEM STATUS:');
83
+ if (status === 0 && timeLeft > 0 && vecLength > 0) {
84
+ console.log(' 🎮 ACTIVE - Players betting, timer running');
85
+ } else if (status === 0 && timeLeft === 0 && vecLength > 0) {
86
+ console.log(' ⏰ EXPIRED - Ready to lock, reveal, and resolve');
87
+ } else if (status === 1) {
88
+ console.log(' 🔒 LOCKED - Waiting for oracle randomness / resolve');
89
+ } else if (status === 2) {
90
+ console.log(' ✅ RESOLVED - Ready to reset for next round');
91
+ } else if (vecLength === 0 && timeLeft > 0) {
92
+ console.log(' 💤 OPEN - No entries yet, timer running');
93
+ } else if (vecLength === 0 && timeLeft === 0) {
94
+ console.log(' 💤 NO ENTRIES + EXPIRED - Will auto-reset');
95
+ }
96
+
97
+ console.log('\n🌐 API:');
98
+ try {
99
+ const { data } = await axios.get(`${API_BASE}/jackpot/round/current`);
100
+ console.log(' API Round ID:', data.round?.roundId || 'null');
101
+ console.log(' API Status:', data.round?.status || 'null');
102
+ } catch (e) {
103
+ console.log(' API ERROR:', e.message);
104
+ }
105
+
106
+ console.log();
107
+ })();
@@ -0,0 +1,143 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Update on-chain jackpot round duration
5
+ *
6
+ * Usage:
7
+ * node scripts/jackpot/update-round-duration.js --weekly # 1,512,000 slots (~7 days)
8
+ * node scripts/jackpot/update-round-duration.js --daily # 216,000 slots (~1 day)
9
+ * node scripts/jackpot/update-round-duration.js --hourly # 9,000 slots (~1 hour)
10
+ * node scripts/jackpot/update-round-duration.js --minute # 150 slots (~1 min, for testing)
11
+ * node scripts/jackpot/update-round-duration.js --slots 1512000 # Custom slot count
12
+ *
13
+ * Environment:
14
+ * API_BASE - Server URL (default: http://localhost:3001)
15
+ */
16
+
17
+ const { Connection, Keypair, Transaction } = require('@solana/web3.js');
18
+ const axios = require('axios');
19
+ const fs = require('fs');
20
+ const path = require('path');
21
+
22
+ const RPC_URL = process.env.SOLANA_RPC_URL || 'https://api.devnet.solana.com';
23
+ const API_BASE = process.env.API_BASE || 'http://localhost:3001';
24
+
25
+ const PRESETS = {
26
+ '--weekly': { slots: 1512000, label: '~7 days' },
27
+ '--daily': { slots: 216000, label: '~1 day' },
28
+ '--hourly': { slots: 9000, label: '~1 hour' },
29
+ '--minute': { slots: 150, label: '~1 min (testing)' },
30
+ };
31
+
32
+ function parseArgs() {
33
+ const args = process.argv.slice(2);
34
+
35
+ if (args.length === 0) {
36
+ console.log('Usage:');
37
+ console.log(' node scripts/jackpot/update-round-duration.js --weekly');
38
+ console.log(' node scripts/jackpot/update-round-duration.js --daily');
39
+ console.log(' node scripts/jackpot/update-round-duration.js --hourly');
40
+ console.log(' node scripts/jackpot/update-round-duration.js --minute');
41
+ console.log(' node scripts/jackpot/update-round-duration.js --slots <number>');
42
+ process.exit(1);
43
+ }
44
+
45
+ // Check presets
46
+ for (const [flag, preset] of Object.entries(PRESETS)) {
47
+ if (args.includes(flag)) {
48
+ return { slots: preset.slots, label: preset.label };
49
+ }
50
+ }
51
+
52
+ // Custom --slots <number>
53
+ const slotsIdx = args.indexOf('--slots');
54
+ if (slotsIdx !== -1 && args[slotsIdx + 1]) {
55
+ const slots = parseInt(args[slotsIdx + 1], 10);
56
+ if (isNaN(slots) || slots <= 0) {
57
+ console.error('Error: --slots must be a positive integer');
58
+ process.exit(1);
59
+ }
60
+ const seconds = Math.floor(slots * 0.4);
61
+ const label = seconds >= 86400
62
+ ? `~${(seconds / 86400).toFixed(1)} days`
63
+ : seconds >= 3600
64
+ ? `~${(seconds / 3600).toFixed(1)} hours`
65
+ : `~${(seconds / 60).toFixed(1)} min`;
66
+ return { slots, label };
67
+ }
68
+
69
+ console.error('Error: unrecognized arguments:', args.join(' '));
70
+ process.exit(1);
71
+ }
72
+
73
+ function loadWallet() {
74
+ if (process.env.KEEPER_PRIVATE_KEY) {
75
+ console.log('Loading wallet from KEEPER_PRIVATE_KEY env var');
76
+ return Keypair.fromSecretKey(Uint8Array.from(JSON.parse(process.env.KEEPER_PRIVATE_KEY)));
77
+ }
78
+
79
+ const oracleKeyPath = path.join(__dirname, '..', '..', 'wallets', 'jackpot_oracle.json');
80
+ if (fs.existsSync(oracleKeyPath)) {
81
+ console.log('Loading wallet from wallets/jackpot_oracle.json');
82
+ return Keypair.fromSecretKey(Uint8Array.from(JSON.parse(fs.readFileSync(oracleKeyPath, 'utf-8'))));
83
+ }
84
+
85
+ const defaultPath = path.join(require('os').homedir(), '.config/solana/id.json');
86
+ if (fs.existsSync(defaultPath)) {
87
+ console.log('Loading wallet from ~/.config/solana/id.json');
88
+ return Keypair.fromSecretKey(Uint8Array.from(JSON.parse(fs.readFileSync(defaultPath, 'utf-8'))));
89
+ }
90
+
91
+ console.error('No wallet found. Set KEEPER_PRIVATE_KEY or add a wallet file.');
92
+ process.exit(1);
93
+ }
94
+
95
+ async function main() {
96
+ const { slots, label } = parseArgs();
97
+ const wallet = loadWallet();
98
+ const connection = new Connection(RPC_URL, 'confirmed');
99
+
100
+ console.log(`\nUpdating round duration to ${slots.toLocaleString()} slots (${label})`);
101
+ console.log(` Authority: ${wallet.publicKey.toString()}`);
102
+ console.log(` API: ${API_BASE}`);
103
+ console.log(` RPC: ${RPC_URL}\n`);
104
+
105
+ try {
106
+ // Fetch current config for comparison
107
+ const configRes = await axios.get(`${API_BASE}/jackpot/config`);
108
+ const currentSlots = configRes.data.config.roundDurationSlots;
109
+ console.log(` Current: ${Number(currentSlots).toLocaleString()} slots`);
110
+ console.log(` New: ${slots.toLocaleString()} slots\n`);
111
+
112
+ // Build update-config transaction
113
+ const { data } = await axios.post(`${API_BASE}/jackpot/build/update-config`, {
114
+ authorityAddress: wallet.publicKey.toString(),
115
+ roundDurationSlots: slots,
116
+ });
117
+
118
+ // Sign and send
119
+ const tx = Transaction.from(Buffer.from(data.transaction, 'base64'));
120
+ tx.recentBlockhash = (await connection.getLatestBlockhash()).blockhash;
121
+ tx.feePayer = wallet.publicKey;
122
+ tx.sign(wallet);
123
+
124
+ const signature = await connection.sendRawTransaction(tx.serialize(), { skipPreflight: false });
125
+ console.log('Signature:', signature);
126
+
127
+ // Poll for confirmation (Alchemy doesn't support signatureSubscribe)
128
+ const start = Date.now();
129
+ while (Date.now() - start < 60000) {
130
+ const statuses = await connection.getSignatureStatuses([signature]);
131
+ const status = statuses?.value?.[0];
132
+ if (status?.err) throw new Error(`Transaction failed: ${JSON.stringify(status.err)}`);
133
+ if (status?.confirmationStatus === 'confirmed' || status?.confirmationStatus === 'finalized') break;
134
+ await new Promise(resolve => setTimeout(resolve, 2000));
135
+ }
136
+ console.log(`\nRound duration updated to ${slots.toLocaleString()} slots (${label})`);
137
+ } catch (error) {
138
+ console.error('Error:', error.response?.data || error.message);
139
+ process.exit(1);
140
+ }
141
+ }
142
+
143
+ main();
@@ -0,0 +1,112 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * 🤖 Jackpot Keeper Bot
5
+ *
6
+ * Automatically manages round lifecycle:
7
+ * 1. Monitors round timers
8
+ * 2. Locks rounds when timer expires
9
+ * 3. Submits oracle randomness
10
+ * 4. Resolves rounds and pays winners
11
+ * 5. Opens new rounds immediately
12
+ *
13
+ * Keeps the jackpot running 24/7!
14
+ */
15
+
16
+ const { Connection, Keypair, Transaction } = require('@solana/web3.js');
17
+ const axios = require('axios');
18
+ const crypto = require('crypto');
19
+ const fs = require('fs');
20
+ const path = require('path');
21
+
22
+ const RPC_URL = process.env.SOLANA_NETWORK || 'https://api.devnet.solana.com';
23
+ const API_BASE = process.env.API_BASE || 'http://localhost:3001';
24
+
25
+ class JackpotKeeper {
26
+ constructor() {
27
+ this.connection = new Connection(RPC_URL, 'confirmed');
28
+
29
+ // Load keeper wallet
30
+ const mainWalletPath = path.join(require('os').homedir(), '.config/solana/id.json');
31
+ const secretKey = JSON.parse(fs.readFileSync(mainWalletPath, 'utf-8'));
32
+ this.wallet = Keypair.fromSecretKey(Uint8Array.from(secretKey));
33
+
34
+ console.log('🤖 Jackpot Keeper Bot');
35
+ console.log(' Wallet:', this.wallet.publicKey.toString());
36
+ console.log(' RPC:', RPC_URL);
37
+ console.log(' API:', API_BASE);
38
+ console.log();
39
+ }
40
+
41
+ async checkAndOpenRound() {
42
+ try {
43
+ const { data: stats } = await axios.get(`${API_BASE}/jackpot/stats`);
44
+
45
+ if (!stats.initialized) {
46
+ console.log('⏳ Protocol not initialized');
47
+ return;
48
+ }
49
+
50
+ // Check if there's an active round
51
+ try {
52
+ const { data: roundData } = await axios.get(`${API_BASE}/jackpot/round/current`);
53
+ console.log(`✅ Round ${roundData.round.roundId} active (${roundData.round.status})`);
54
+ return roundData.round;
55
+ } catch (error) {
56
+ if (error.response?.status === 404) {
57
+ console.log('📂 No active round, opening new one...');
58
+ await this.openRound();
59
+ }
60
+ }
61
+ } catch (error) {
62
+ console.error('❌ Error:', error.message);
63
+ }
64
+ }
65
+
66
+ async openRound() {
67
+ try {
68
+ const { data } = await axios.post(`${API_BASE}/jackpot/build/open-round`, {
69
+ keeperAddress: this.wallet.publicKey.toString(),
70
+ });
71
+
72
+ const tx = Transaction.from(Buffer.from(data.transaction, 'base64'));
73
+ tx.recentBlockhash = (await this.connection.getLatestBlockhash()).blockhash;
74
+ tx.feePayer = this.wallet.publicKey;
75
+ tx.sign(this.wallet);
76
+
77
+ const signature = await this.connection.sendRawTransaction(tx.serialize(), {
78
+ skipPreflight: true,
79
+ });
80
+
81
+ await this.connection.confirmTransaction(signature);
82
+
83
+ console.log(`🎉 Round ${data.roundId} opened! Signature: ${signature.slice(0, 8)}...`);
84
+ } catch (error) {
85
+ console.error('❌ Failed to open round:', error.message);
86
+ }
87
+ }
88
+
89
+ async run() {
90
+ console.log('🚀 Starting keeper bot...\n');
91
+
92
+ // Open initial round if needed
93
+ await this.checkAndOpenRound();
94
+
95
+ // Run every 10 seconds
96
+ setInterval(async () => {
97
+ await this.checkAndOpenRound();
98
+ }, 10000);
99
+
100
+ // Keep process alive
101
+ console.log('✅ Keeper bot running. Press Ctrl+C to stop.\n');
102
+ }
103
+ }
104
+
105
+ const keeper = new JackpotKeeper();
106
+ keeper.run().catch(console.error);
107
+
108
+
109
+
110
+
111
+
112
+
@@ -0,0 +1,131 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * List Pending Games
4
+ * Quick script to see what games are waiting to be resolved
5
+ *
6
+ * Usage: node list-pending-games.js
7
+ */
8
+
9
+ require('dotenv').config();
10
+ const axios = require('axios');
11
+ const fs = require('fs');
12
+ const path = require('path');
13
+
14
+ async function listPendingGames() {
15
+ try {
16
+ const apiUrl = process.env.DUBS_GAMES_API_URL || 'https://dubs-games-api-dev-76c556653fa3.herokuapp.com';
17
+
18
+ console.log('🔍 Fetching all automatic games...\n');
19
+
20
+ // Fetch games from Firebase - get ALL games and filter
21
+ let games = [];
22
+
23
+ try {
24
+ // Try the pending endpoint first
25
+ const pendingResponse = await axios.get(`${apiUrl}/api/auth/games/automatic/pending`);
26
+ games = pendingResponse.data.games || [];
27
+ } catch (err) {
28
+ console.log('⚠️ Pending endpoint failed, trying direct Firebase query...\n');
29
+ }
30
+
31
+ // If we got no games, try fetching all games directly from Firebase
32
+ if (games.length === 0) {
33
+ try {
34
+ // Direct Firebase query for automatic games
35
+ const admin = require('firebase-admin');
36
+
37
+ // Try to load service account from dubs-games-api directory
38
+ const firebasePath = path.join(__dirname, '../../dubs-games-api/firebase/dubs-a835d-firebase-adminsdk-fbsvc-e84002f7e9.json');
39
+
40
+ if (!fs.existsSync(firebasePath)) {
41
+ throw new Error('Firebase credentials not found. Using API endpoint only.');
42
+ }
43
+
44
+ const serviceAccount = require(firebasePath);
45
+
46
+ if (!admin.apps.length) {
47
+ admin.initializeApp({
48
+ credential: admin.credential.cert(serviceAccount)
49
+ });
50
+ }
51
+
52
+ const snapshot = await admin.firestore()
53
+ .collection('games')
54
+ .where('gameMode', '==', 4)
55
+ .where('isResolved', '==', false)
56
+ .get();
57
+
58
+ games = snapshot.docs.map(doc => ({
59
+ gameId: doc.id,
60
+ ...doc.data()
61
+ }));
62
+
63
+ } catch (fbErr) {
64
+ console.error('❌ Direct Firebase query failed:', fbErr.message);
65
+ console.log('Using API endpoint data only\n');
66
+ }
67
+ }
68
+
69
+ if (games.length === 0) {
70
+ console.log('❌ No automatic games found\n');
71
+ return;
72
+ }
73
+
74
+ const readyCount = games.filter(g => {
75
+ if (!g.lockTime) return false;
76
+ const lockDate = g.lockTime._seconds ? new Date(g.lockTime._seconds * 1000) : new Date(g.lockTime);
77
+ return lockDate < new Date();
78
+ }).length;
79
+
80
+ console.log(`Found ${games.length} automatic game(s) (${readyCount} ready to resolve):\n`);
81
+ console.log('='.repeat(80));
82
+
83
+ games.forEach((game, index) => {
84
+ console.log(`\n${index + 1}. ${game.title || 'Untitled'}`);
85
+ console.log(` Game ID: ${game.gameId}`);
86
+ console.log(` Event: ${game.sportsEvent?.strEvent || 'N/A'}`);
87
+ console.log(` Status: ${game.automaticStatus || game.status}`);
88
+ console.log(` Players: ${game.participants?.length || 0}`);
89
+ console.log(` Chat ID: ${game.telegramChatId || '❌ NOT SET'}`);
90
+ console.log(` Created: ${game.createdAt ? new Date(game.createdAt._seconds * 1000 || game.createdAt).toLocaleString() : 'N/A'}`);
91
+
92
+ if (game.lockTime) {
93
+ // Handle both Timestamp and ISO string formats
94
+ let lockDate;
95
+ if (game.lockTime._seconds) {
96
+ lockDate = new Date(game.lockTime._seconds * 1000);
97
+ } else if (typeof game.lockTime === 'string') {
98
+ lockDate = new Date(game.lockTime);
99
+ } else {
100
+ lockDate = new Date(game.lockTime);
101
+ }
102
+
103
+ const isPast = lockDate < new Date();
104
+ const status = isPast ? '✅ READY TO RESOLVE' : '⏳ NOT READY YET';
105
+ console.log(` Lock Time: ${lockDate.toLocaleString()} ${status}`);
106
+
107
+ if (!isPast) {
108
+ const minutesUntil = Math.ceil((lockDate - new Date()) / 60000);
109
+ console.log(` ⏰ Wait ${minutesUntil} minute(s) or use --winner to force`);
110
+ }
111
+ }
112
+
113
+ // Show command to resolve
114
+ console.log(`\n Resolve: node test-resolve-game.js ${game.gameId}`);
115
+ console.log(` Force: node test-resolve-game.js ${game.gameId} --winner=home`);
116
+ });
117
+
118
+ console.log('\n' + '='.repeat(80));
119
+ console.log(`\nTo resolve all: node test-resolve-game.js --all\n`);
120
+
121
+ } catch (error) {
122
+ console.error('❌ Error:', error.message);
123
+ if (error.response?.data) {
124
+ console.error('Response:', error.response.data);
125
+ }
126
+ process.exit(1);
127
+ }
128
+ }
129
+
130
+ listPendingGames();
131
+
@@ -0,0 +1,127 @@
1
+ /**
2
+ * 🔄 Chat Tables Migration to V2
3
+ *
4
+ * Drops old chat tables and creates new v2 schema
5
+ * Run this once to upgrade to the new chat system
6
+ */
7
+
8
+ const { Pool } = require('pg');
9
+ require('dotenv').config();
10
+
11
+ const pool = new Pool({
12
+ connectionString: process.env.DATABASE_URL,
13
+ ssl: process.env.DATABASE_URL && (process.env.DATABASE_URL.includes('amazonaws') || process.env.DATABASE_URL.includes('heroku'))
14
+ ? { rejectUnauthorized: false }
15
+ : false,
16
+ });
17
+
18
+ async function migrateChatTables() {
19
+ console.log('🔄 Starting chat tables migration to v2...');
20
+
21
+ try {
22
+ // Drop old tables (cascade to remove dependencies)
23
+ console.log('📦 Dropping old chat tables...');
24
+ await pool.query(`
25
+ DROP TABLE IF EXISTS chat_reactions CASCADE;
26
+ DROP TABLE IF EXISTS chat_notifications CASCADE;
27
+ DROP TABLE IF EXISTS user_relationships CASCADE;
28
+ DROP TABLE IF EXISTS chat_messages CASCADE;
29
+ `);
30
+ console.log('✅ Old tables dropped');
31
+
32
+ // Create new v2 tables
33
+ console.log('🆕 Creating new v2 chat tables...');
34
+ await pool.query(`
35
+ -- Main chat messages table (enhanced)
36
+ CREATE TABLE chat_messages (
37
+ id SERIAL PRIMARY KEY,
38
+ user_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
39
+ wallet_address VARCHAR(100) NOT NULL,
40
+ username VARCHAR(50) NOT NULL,
41
+ avatar TEXT,
42
+ message TEXT NOT NULL,
43
+ reply_to_id INTEGER REFERENCES chat_messages(id) ON DELETE SET NULL,
44
+ is_winner_announcement BOOLEAN DEFAULT FALSE,
45
+ win_amount NUMERIC(20, 9),
46
+ round_id INTEGER,
47
+ edited BOOLEAN DEFAULT FALSE,
48
+ edited_at TIMESTAMP,
49
+ deleted BOOLEAN DEFAULT FALSE,
50
+ deleted_at TIMESTAMP,
51
+ timestamp TIMESTAMP DEFAULT NOW(),
52
+ created_at TIMESTAMP DEFAULT NOW()
53
+ );
54
+
55
+ -- Message reactions (for future)
56
+ CREATE TABLE chat_reactions (
57
+ id SERIAL PRIMARY KEY,
58
+ message_id INTEGER REFERENCES chat_messages(id) ON DELETE CASCADE,
59
+ user_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
60
+ wallet_address VARCHAR(100) NOT NULL,
61
+ reaction VARCHAR(20) NOT NULL,
62
+ created_at TIMESTAMP DEFAULT NOW(),
63
+ UNIQUE(message_id, user_id, reaction)
64
+ );
65
+
66
+ -- User relationships (friends/blocks)
67
+ CREATE TABLE user_relationships (
68
+ id SERIAL PRIMARY KEY,
69
+ user_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
70
+ target_user_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
71
+ relationship_type VARCHAR(20) NOT NULL CHECK (relationship_type IN ('friend', 'block')),
72
+ created_at TIMESTAMP DEFAULT NOW(),
73
+ UNIQUE(user_id, target_user_id)
74
+ );
75
+
76
+ -- Chat notifications
77
+ CREATE TABLE chat_notifications (
78
+ id SERIAL PRIMARY KEY,
79
+ user_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
80
+ message_id INTEGER REFERENCES chat_messages(id) ON DELETE CASCADE,
81
+ notification_type VARCHAR(20) NOT NULL CHECK (notification_type IN ('reply', 'mention', 'friend_message')),
82
+ read BOOLEAN DEFAULT FALSE,
83
+ created_at TIMESTAMP DEFAULT NOW()
84
+ );
85
+
86
+ -- Indexes for performance
87
+ CREATE INDEX idx_chat_timestamp ON chat_messages(timestamp DESC);
88
+ CREATE INDEX idx_chat_wallet ON chat_messages(wallet_address);
89
+ CREATE INDEX idx_chat_user_id ON chat_messages(user_id);
90
+ CREATE INDEX idx_chat_reply_to ON chat_messages(reply_to_id);
91
+ CREATE INDEX idx_chat_deleted ON chat_messages(deleted) WHERE deleted = false;
92
+ CREATE INDEX idx_reactions_message ON chat_reactions(message_id);
93
+ CREATE INDEX idx_relationships_user ON user_relationships(user_id);
94
+ CREATE INDEX idx_relationships_type ON user_relationships(relationship_type);
95
+ CREATE INDEX idx_notifications_user ON chat_notifications(user_id);
96
+ CREATE INDEX idx_notifications_read ON chat_notifications(read) WHERE read = false;
97
+ `);
98
+ console.log('✅ New v2 tables created');
99
+
100
+ console.log('🎉 Migration complete!');
101
+ console.log('');
102
+ console.log('New tables created:');
103
+ console.log(' - chat_messages (with user_id, replies, editing, soft delete)');
104
+ console.log(' - chat_reactions (for future emoji reactions)');
105
+ console.log(' - user_relationships (friends/blocks)');
106
+ console.log(' - chat_notifications (reply notifications)');
107
+
108
+ } catch (error) {
109
+ console.error('❌ Migration failed:', error);
110
+ throw error;
111
+ } finally {
112
+ await pool.end();
113
+ }
114
+ }
115
+
116
+ // Run migration
117
+ migrateChatTables()
118
+ .then(() => {
119
+ console.log('✅ Done! Restart your server now.');
120
+ process.exit(0);
121
+ })
122
+ .catch((err) => {
123
+ console.error('❌ Migration failed:', err);
124
+ process.exit(1);
125
+ });
126
+
127
+