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,248 @@
1
+ /**
2
+ * Debug Cohort Count Discrepancy
3
+ *
4
+ * This script investigates why cohort counts don't match total user counts
5
+ */
6
+
7
+ require('dotenv').config();
8
+ const { Pool } = require('pg');
9
+
10
+ async function debugCohorts() {
11
+ const DATABASE_URL = process.env.DATABASE_URL;
12
+
13
+ if (!DATABASE_URL) {
14
+ console.error('āŒ DATABASE_URL not set');
15
+ process.exit(1);
16
+ }
17
+
18
+ const isProduction = DATABASE_URL.includes('amazonaws') || DATABASE_URL.includes('heroku');
19
+ const pool = new Pool({
20
+ connectionString: DATABASE_URL,
21
+ ssl: isProduction ? { rejectUnauthorized: false } : false,
22
+ max: 2,
23
+ });
24
+
25
+ console.log('\nšŸ” DEBUGGING COHORT COUNT DISCREPANCY\n');
26
+ console.log('=' .repeat(80));
27
+
28
+ try {
29
+ // 1. Total unique users with registration_completed
30
+ console.log('\n1ļøāƒ£ TOTAL UNIQUE USERS');
31
+ console.log('-'.repeat(80));
32
+
33
+ const totalUsers = await pool.query(`
34
+ SELECT
35
+ COUNT(DISTINCT user_id) as unique_users,
36
+ COUNT(*) as total_events
37
+ FROM audit_logs
38
+ WHERE log_type = 'registration_completed'
39
+ AND user_id IS NOT NULL
40
+ `);
41
+
42
+ console.log(`Total Unique Users: ${totalUsers.rows[0].unique_users}`);
43
+ console.log(`Total registration_completed Events: ${totalUsers.rows[0].total_events}`);
44
+ console.log(`Average Events per User: ${(totalUsers.rows[0].total_events / totalUsers.rows[0].unique_users).toFixed(2)}`);
45
+
46
+ // 2. Users with multiple registration events
47
+ console.log('\n\n2ļøāƒ£ USERS WITH MULTIPLE REGISTRATIONS');
48
+ console.log('-'.repeat(80));
49
+
50
+ const multipleRegs = await pool.query(`
51
+ SELECT
52
+ user_id,
53
+ COUNT(*) as registration_count,
54
+ MIN(created_at) as first_registration,
55
+ MAX(created_at) as last_registration
56
+ FROM audit_logs
57
+ WHERE log_type = 'registration_completed'
58
+ AND user_id IS NOT NULL
59
+ GROUP BY user_id
60
+ HAVING COUNT(*) > 1
61
+ ORDER BY registration_count DESC
62
+ `);
63
+
64
+ if (multipleRegs.rows.length > 0) {
65
+ console.log(`\nāš ļø Found ${multipleRegs.rows.length} users with multiple registration events:\n`);
66
+ multipleRegs.rows.forEach(row => {
67
+ console.log(`User: ${row.user_id.substring(0, 12)}...`);
68
+ console.log(` Events: ${row.registration_count}`);
69
+ console.log(` First: ${row.first_registration.toISOString()}`);
70
+ console.log(` Last: ${row.last_registration.toISOString()}`);
71
+ console.log('');
72
+ });
73
+ } else {
74
+ console.log('āœ… All users have exactly 1 registration event');
75
+ }
76
+
77
+ // 3. Distribution by month
78
+ console.log('\n3ļøāƒ£ MONTHLY DISTRIBUTION (Using MIN per user)');
79
+ console.log('-'.repeat(80));
80
+
81
+ const monthlyDist = await pool.query(`
82
+ WITH first_registration AS (
83
+ SELECT
84
+ user_id,
85
+ MIN(DATE(created_at)) as signup_date
86
+ FROM audit_logs
87
+ WHERE log_type = 'registration_completed'
88
+ AND user_id IS NOT NULL
89
+ GROUP BY user_id
90
+ )
91
+ SELECT
92
+ DATE_TRUNC('month', signup_date) as cohort_month,
93
+ COUNT(*) as unique_users,
94
+ MIN(signup_date) as first_signup,
95
+ MAX(signup_date) as last_signup
96
+ FROM first_registration
97
+ GROUP BY DATE_TRUNC('month', signup_date)
98
+ ORDER BY cohort_month DESC
99
+ `);
100
+
101
+ console.log('\nMonth | Users | First Signup | Last Signup');
102
+ console.log('-'.repeat(60));
103
+ monthlyDist.rows.forEach(row => {
104
+ const month = new Date(row.cohort_month).toLocaleDateString('en-US', { month: 'long', year: 'numeric' });
105
+ const first = new Date(row.first_signup).toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
106
+ const last = new Date(row.last_signup).toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
107
+ console.log(`${month.padEnd(15)} | ${row.unique_users.toString().padStart(5)} | ${first.padEnd(13)} | ${last}`);
108
+ });
109
+
110
+ // 4. What the API returns
111
+ console.log('\n\n4ļøāƒ£ API QUERY RESULT (Monthly Cohorts)');
112
+ console.log('-'.repeat(80));
113
+
114
+ const apiResult = await pool.query(`
115
+ WITH signups AS (
116
+ SELECT
117
+ user_id,
118
+ MIN(DATE(created_at)) as signup_date,
119
+ MIN(metadata->>'referralCode') as referral_code
120
+ FROM audit_logs
121
+ WHERE log_type = 'registration_completed'
122
+ AND user_id IS NOT NULL
123
+ GROUP BY user_id
124
+ ),
125
+ cohorts AS (
126
+ SELECT
127
+ DATE_TRUNC('month', signup_date) as cohort_period,
128
+ user_id,
129
+ signup_date,
130
+ CASE
131
+ WHEN referral_code IS NOT NULL AND referral_code != '' THEN 'referral'
132
+ ELSE 'organic'
133
+ END as user_source
134
+ FROM signups
135
+ ),
136
+ retention_calc AS (
137
+ SELECT
138
+ cohort_period,
139
+ COUNT(DISTINCT user_id) as total_users
140
+ FROM cohorts
141
+ WHERE 'all' = 'all' OR user_source = 'all'
142
+ GROUP BY cohort_period
143
+ )
144
+ SELECT
145
+ cohort_period,
146
+ total_users
147
+ FROM retention_calc
148
+ WHERE total_users > 0
149
+ ORDER BY cohort_period DESC
150
+ LIMIT 12
151
+ `);
152
+
153
+ console.log('\nCohort Month | Users (What API Returns)');
154
+ console.log('-'.repeat(50));
155
+ apiResult.rows.forEach(row => {
156
+ const month = new Date(row.cohort_period).toLocaleDateString('en-US', { month: 'long', year: 'numeric' });
157
+ console.log(`${month.padEnd(15)} | ${row.total_users}`);
158
+ });
159
+
160
+ // 5. Date formatting test
161
+ console.log('\n\n5ļøāƒ£ DATE FORMATTING TEST');
162
+ console.log('-'.repeat(80));
163
+
164
+ const dateTest = await pool.query(`
165
+ WITH test_dates AS (
166
+ SELECT DATE_TRUNC('month', DATE '2025-12-15') as cohort_period
167
+ )
168
+ SELECT
169
+ cohort_period,
170
+ cohort_period + INTERVAL '1 month' - INTERVAL '1 day' as month_end,
171
+ TO_CHAR(cohort_period, 'Month YYYY') as formatted_month
172
+ FROM test_dates
173
+ `);
174
+
175
+ console.log(`\nFor December 15, 2025 signup:`);
176
+ console.log(` DATE_TRUNC result: ${dateTest.rows[0].cohort_period.toISOString()}`);
177
+ console.log(` Month end: ${dateTest.rows[0].month_end.toISOString()}`);
178
+ console.log(` Formatted: ${dateTest.rows[0].formatted_month}`);
179
+
180
+ // 6. Check for the "Nov 30, 2025" issue
181
+ console.log('\n\n6ļøāƒ£ INVESTIGATING "Nov 30, 2025" LABEL');
182
+ console.log('-'.repeat(80));
183
+
184
+ const novCheck = await pool.query(`
185
+ SELECT
186
+ DATE(created_at) as signup_date,
187
+ COUNT(*) as count
188
+ FROM audit_logs
189
+ WHERE log_type = 'registration_completed'
190
+ AND user_id IS NOT NULL
191
+ AND DATE(created_at) >= '2025-11-01'
192
+ AND DATE(created_at) < '2025-12-01'
193
+ GROUP BY DATE(created_at)
194
+ ORDER BY signup_date
195
+ `);
196
+
197
+ if (novCheck.rows.length > 0) {
198
+ console.log('\nāš ļø Found signups in November 2025:');
199
+ novCheck.rows.forEach(row => {
200
+ console.log(` ${row.signup_date.toISOString().split('T')[0]}: ${row.count} signups`);
201
+ });
202
+ } else {
203
+ console.log('āœ… No signups found in November 2025');
204
+ console.log(' The "Nov 30, 2025" label is likely a date formatting bug!');
205
+ }
206
+
207
+ // 7. Summary
208
+ console.log('\n\nšŸ“‹ SUMMARY');
209
+ console.log('=' .repeat(80));
210
+
211
+ const summary = await pool.query(`
212
+ WITH first_registration AS (
213
+ SELECT
214
+ user_id,
215
+ MIN(DATE(created_at)) as signup_date
216
+ FROM audit_logs
217
+ WHERE log_type = 'registration_completed'
218
+ AND user_id IS NOT NULL
219
+ GROUP BY user_id
220
+ )
221
+ SELECT
222
+ COUNT(*) as total_unique_users,
223
+ MIN(signup_date) as earliest_signup,
224
+ MAX(signup_date) as latest_signup
225
+ FROM first_registration
226
+ `);
227
+
228
+ console.log(`\nāœ… Total Unique Users: ${summary.rows[0].total_unique_users}`);
229
+ console.log(`šŸ“… Date Range: ${summary.rows[0].earliest_signup.toISOString().split('T')[0]} to ${summary.rows[0].latest_signup.toISOString().split('T')[0]}`);
230
+ console.log(`\nšŸ’” EXPLANATION:`);
231
+ console.log(` - ${summary.rows[0].total_unique_users} unique users are spread across multiple monthly cohorts`);
232
+ console.log(` - Each cohort only shows users who signed up in that specific month`);
233
+ console.log(` - "December 2025" cohort = users who signed up between Dec 1-31, 2025`);
234
+ console.log(` - If you see 7 in December, the other ${summary.rows[0].total_unique_users - 7} are in other months`);
235
+ console.log(`\nšŸ› BUG IDENTIFIED:`);
236
+ console.log(` - "Nov 30, 2025" label is incorrect for December cohort`);
237
+ console.log(` - Should display "December 2025" with date range`);
238
+
239
+ } catch (error) {
240
+ console.error('\nāŒ Error:', error.message);
241
+ console.error(error);
242
+ } finally {
243
+ await pool.end();
244
+ }
245
+ }
246
+
247
+ debugCohorts().catch(console.error);
248
+
@@ -0,0 +1,84 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * šŸ” Debug Winner Calculation
5
+ * Shows VRF result and winner calculation step-by-step
6
+ */
7
+
8
+ const { Connection, PublicKey } = require('@solana/web3.js');
9
+ const axios = require('axios');
10
+
11
+ const RPC_URL = 'https://api.devnet.solana.com';
12
+ const API_BASE = 'http://localhost:3001';
13
+ const PROGRAM_ID = new PublicKey('BHidyz25KWkNPdTHgeANzMg25MM2KEiNnG4yE5F46XUz');
14
+
15
+ async function debugWinner() {
16
+ console.log('šŸ” Debug Winner Calculation\n');
17
+
18
+ const connection = new Connection(RPC_URL, 'confirmed');
19
+
20
+ // Get entries
21
+ const { data: entriesData } = await axios.get(`${API_BASE}/jackpot/round/50/entries`);
22
+ const entries = entriesData.entries;
23
+
24
+ console.log('šŸ“Š Entries:');
25
+ entries.forEach((entry, i) => {
26
+ console.log(` [${i}] ${entry.player.slice(0, 8)}... weight: ${entry.weight}, cumulative: ${entry.cumulativeTo}`);
27
+ });
28
+ console.log();
29
+
30
+ // Get round account
31
+ const [roundPda] = PublicKey.findProgramAddressSync(
32
+ [Buffer.from('round'), Buffer.from([1,0,0,0,0,0,0,0])],
33
+ PROGRAM_ID
34
+ );
35
+
36
+ console.log('Round PDA:', roundPda.toString());
37
+
38
+ const roundAccount = await connection.getAccountInfo(roundPda);
39
+ if (!roundAccount) {
40
+ console.log('āŒ Round account not found!');
41
+ return;
42
+ }
43
+
44
+ // Parse VRF result (at offset 51)
45
+ const hasVrfResult = roundAccount.data[51];
46
+ console.log('\nVRF Result exists:', hasVrfResult === 1);
47
+
48
+ if (hasVrfResult !== 1) {
49
+ console.log('āŒ No VRF result available!');
50
+ return;
51
+ }
52
+
53
+ // Read u128 VRF result (16 bytes starting at offset 52)
54
+ const vrfBytes = roundAccount.data.slice(52, 68);
55
+ console.log('VRF bytes (hex):', vrfBytes.toString('hex'));
56
+
57
+ const vrfResult = BigInt('0x' + Buffer.from(vrfBytes).reverse().toString('hex'));
58
+ console.log('VRF result (as bigint):', vrfResult.toString());
59
+ console.log();
60
+
61
+ // Calculate winner
62
+ const totalWeight = BigInt(entries[entries.length - 1].cumulativeTo);
63
+ console.log('Total weight:', totalWeight.toString());
64
+
65
+ const winnerPoint = vrfResult % totalWeight;
66
+ console.log('Winner point:', winnerPoint.toString());
67
+ console.log();
68
+
69
+ // Find winner
70
+ console.log('Finding winner:');
71
+ for (const entry of entries) {
72
+ const cumulative = BigInt(entry.cumulativeTo);
73
+ const matches = cumulative > winnerPoint;
74
+ console.log(` ${entry.player.slice(0, 8)}... cumulative: ${cumulative}, matches: ${matches ? 'āœ… WINNER' : 'āŒ'}`);
75
+ if (matches) {
76
+ console.log();
77
+ console.log('šŸ† Winner:', entry.player);
78
+ break;
79
+ }
80
+ }
81
+ }
82
+
83
+ debugWinner().catch(console.error);
84
+
@@ -0,0 +1,118 @@
1
+ #!/bin/bash
2
+
3
+ # Dubs Payment System Deployment Script
4
+ # Deploys server with payment functionality to Heroku
5
+
6
+ set -e # Exit on error
7
+
8
+ echo "šŸš€ Starting Dubs Payment System Deployment..."
9
+ echo ""
10
+
11
+ # Colors for output
12
+ GREEN='\033[0;32m'
13
+ YELLOW='\033[1;33m'
14
+ RED='\033[0;31m'
15
+ NC='\033[0m' # No Color
16
+
17
+ # Check if we're in the right directory
18
+ if [ ! -f "server.js" ]; then
19
+ echo -e "${RED}āŒ Error: Must run from dubs-server directory${NC}"
20
+ exit 1
21
+ fi
22
+
23
+ # Check if heroku CLI is installed
24
+ if ! command -v heroku &> /dev/null; then
25
+ echo -e "${RED}āŒ Error: Heroku CLI not installed${NC}"
26
+ echo "Install from: https://devcenter.heroku.com/articles/heroku-cli"
27
+ exit 1
28
+ fi
29
+
30
+ # Get Heroku app name
31
+ echo -e "${YELLOW}šŸ“ Enter your Heroku app name:${NC}"
32
+ read -p "App name: " APP_NAME
33
+
34
+ if [ -z "$APP_NAME" ]; then
35
+ echo -e "${RED}āŒ Error: App name is required${NC}"
36
+ exit 1
37
+ fi
38
+
39
+ echo ""
40
+ echo -e "${GREEN}āœ… Deploying to: $APP_NAME${NC}"
41
+ echo ""
42
+
43
+ # Check git status
44
+ echo "šŸ“‹ Checking git status..."
45
+ if [[ -n $(git status -s) ]]; then
46
+ echo -e "${YELLOW}āš ļø You have uncommitted changes${NC}"
47
+ echo ""
48
+ git status -s
49
+ echo ""
50
+ read -p "Commit these changes? (y/n): " -n 1 -r
51
+ echo
52
+ if [[ $REPLY =~ ^[Yy]$ ]]; then
53
+ git add .
54
+ read -p "Commit message: " COMMIT_MSG
55
+ git commit -m "$COMMIT_MSG"
56
+ echo -e "${GREEN}āœ… Changes committed${NC}"
57
+ else
58
+ echo -e "${RED}āŒ Deployment cancelled${NC}"
59
+ exit 1
60
+ fi
61
+ fi
62
+
63
+ # Show what will be deployed
64
+ echo ""
65
+ echo "šŸ“¦ Deployment summary:"
66
+ echo " - Payment system with SOL transfers"
67
+ echo " - Payment notifications (payment_received, payment_sent)"
68
+ echo " - Database constraint update (auto-runs on startup)"
69
+ echo " - Real-time WebSocket payment alerts"
70
+ echo ""
71
+
72
+ read -p "Continue with deployment? (y/n): " -n 1 -r
73
+ echo
74
+ if [[ ! $REPLY =~ ^[Yy]$ ]]; then
75
+ echo -e "${RED}āŒ Deployment cancelled${NC}"
76
+ exit 1
77
+ fi
78
+
79
+ # Deploy to Heroku
80
+ echo ""
81
+ echo "šŸš€ Deploying to Heroku..."
82
+ git push heroku main
83
+
84
+ # Wait a moment for deployment to settle
85
+ echo ""
86
+ echo "ā³ Waiting for deployment to complete..."
87
+ sleep 5
88
+
89
+ # Check if migration ran
90
+ echo ""
91
+ echo "šŸ” Checking database migration..."
92
+ heroku logs --tail --app "$APP_NAME" | grep -i "notification types updated" | head -1 || echo -e "${YELLOW}āš ļø Migration log not found yet (might still be running)${NC}"
93
+
94
+ # Show recent logs
95
+ echo ""
96
+ echo "šŸ“Š Recent logs:"
97
+ heroku logs --tail --num 50 --app "$APP_NAME"
98
+
99
+ echo ""
100
+ echo -e "${GREEN}āœ… Deployment complete!${NC}"
101
+ echo ""
102
+ echo "🧪 Next steps:"
103
+ echo " 1. Test payment: @username \$0.01"
104
+ echo " 2. Check logs: heroku logs --tail --app $APP_NAME"
105
+ echo " 3. Monitor database: heroku pg:psql --app $APP_NAME"
106
+ echo ""
107
+ echo "šŸ“š See PAYMENT_DEPLOYMENT.md for full testing guide"
108
+ echo ""
109
+
110
+
111
+
112
+
113
+
114
+
115
+
116
+
117
+
118
+
@@ -0,0 +1,63 @@
1
+ #!/bin/bash
2
+
3
+ # šŸš€ Quick Heroku Deployment Script
4
+
5
+ set -e
6
+
7
+ echo "šŸš€ Deploying Dubs Server to Heroku"
8
+ echo "==================================="
9
+ echo ""
10
+
11
+ # Check if we're in the right directory
12
+ if [ ! -f "server.js" ]; then
13
+ echo "āŒ Error: Must run from dubs-server directory"
14
+ exit 1
15
+ fi
16
+
17
+ # Check for uncommitted changes
18
+ if ! git diff-index --quiet HEAD --; then
19
+ echo "āš ļø You have uncommitted changes"
20
+ echo ""
21
+ read -p "Commit them now? (y/n) " -n 1 -r
22
+ echo ""
23
+ if [[ $REPLY =~ ^[Yy]$ ]]; then
24
+ git add .
25
+ read -p "Commit message: " commit_msg
26
+ git commit -m "$commit_msg"
27
+ fi
28
+ fi
29
+
30
+ # Deploy to Heroku
31
+ echo "šŸ“¤ Pushing to Heroku..."
32
+ git push heroku-dev main
33
+
34
+ echo ""
35
+ echo "āš™ļø Setting environment variables..."
36
+ heroku config:set SOLANA_NETWORK=https://api.devnet.solana.com -a dubs-server-dev
37
+ heroku config:set JACKPOT_PROGRAM_ID=bqoSjTSPLweMuqNG6jy39jmzGyZvZdWjsr4csGfD8F6 -a dubs-server-dev
38
+
39
+ echo ""
40
+ echo "šŸ”„ Scaling processes..."
41
+ heroku ps:scale web=1 jackpot-keeper=1 oracle=1 -a dubs-server-dev
42
+
43
+ echo ""
44
+ echo "ā³ Waiting for services to start..."
45
+ sleep 10
46
+
47
+ echo ""
48
+ echo "šŸ„ Health check..."
49
+ curl -s https://dubs-server-dev.herokuapp.com/jackpot/health | python3 -m json.tool
50
+
51
+ echo ""
52
+ echo "šŸ“Š Process status:"
53
+ heroku ps -a dubs-server-dev
54
+
55
+ echo ""
56
+ echo "āœ… Deployment complete!"
57
+ echo ""
58
+ echo "šŸ“‹ Next steps:"
59
+ echo " - View logs: heroku logs --tail -a dubs-server-dev"
60
+ echo " - Check round: curl https://dubs-server-dev.herokuapp.com/jackpot/round/current | jq ."
61
+ echo " - Monitor keeper: heroku logs --tail --ps jackpot-keeper -a dubs-server-dev"
62
+ echo ""
63
+
@@ -0,0 +1,143 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * šŸ” Diagnose Locked Round
5
+ *
6
+ * Checks why a jackpot round is stuck in "Locked" state
7
+ */
8
+
9
+ const { Connection, PublicKey } = require('@solana/web3.js');
10
+ const axios = require('axios');
11
+
12
+ const RPC_URL = 'https://api.devnet.solana.com';
13
+ const API_BASE = 'http://localhost:3001';
14
+ const PROGRAM_ID = new PublicKey('BHidyz25KWkNPdTHgeANzMg25MM2KEiNnG4yE5F46XUz');
15
+
16
+ async function diagnoseRound() {
17
+ console.log('šŸ” Diagnosing Jackpot Round State\n');
18
+
19
+ const connection = new Connection(RPC_URL, 'confirmed');
20
+
21
+ try {
22
+ // 1. Get current round from API
23
+ console.log('šŸ“” Fetching current round from API...');
24
+ const { data } = await axios.get(`${API_BASE}/jackpot/round/current`);
25
+
26
+ if (!data.round) {
27
+ console.log('āŒ No active round found!');
28
+ console.log('šŸ’” Solution: Run the keeper bot to open a new round');
29
+ return;
30
+ }
31
+
32
+ const round = data.round;
33
+ console.log('āœ… Round found:', round.roundId);
34
+ console.log(' Status:', round.status);
35
+ console.log(' Pot:', Number(round.totalPotLamports) / 1e9, 'SOL');
36
+ console.log(' Entry Count:', round.entryCount || 0);
37
+ console.log();
38
+
39
+ // 2. Check if round is locked
40
+ if (round.status !== 'Locked') {
41
+ console.log(`āœ… Round is not locked (status: ${round.status})`);
42
+ if (round.status === 'Open') {
43
+ const slotsRemaining = round.timeRemainingSlots;
44
+ const secsRemaining = Math.floor(slotsRemaining * 0.4);
45
+ console.log(`ā³ Round ends in ~${secsRemaining} seconds (${slotsRemaining} slots)`);
46
+ }
47
+ return;
48
+ }
49
+
50
+ console.log('šŸ”’ Round IS LOCKED - investigating...\n');
51
+
52
+ // 3. Fetch raw account data to check VRF result
53
+ console.log('šŸ“Š Checking on-chain state...');
54
+ const [roundPda] = PublicKey.findProgramAddressSync(
55
+ [Buffer.from('round'), Buffer.from([1, 0, 0, 0, 0, 0, 0, 0])],
56
+ PROGRAM_ID
57
+ );
58
+
59
+ const accountInfo = await connection.getAccountInfo(roundPda);
60
+ if (!accountInfo) {
61
+ console.log('āŒ Round account not found on-chain!');
62
+ return;
63
+ }
64
+
65
+ const accountData = accountInfo.data;
66
+
67
+ // Parse critical fields
68
+ const status = accountData[16]; // 0=Open, 1=Locked, 2=Resolved
69
+ const entryCount = accountData.readUInt32LE(68);
70
+ const serverSeedHash = accountData.slice(108, 140); // 32 bytes
71
+
72
+ // Check VRF result (Option<u128>)
73
+ const hasVrfResult = accountData[49]; // Option discriminant (0=None, 1=Some)
74
+
75
+ console.log(' On-chain status:', ['Open', 'Locked', 'Resolved'][status]);
76
+ console.log(' Entry count:', entryCount);
77
+ console.log(' Server seed hash:', serverSeedHash.toString('hex').slice(0, 16) + '...');
78
+ console.log(' Has VRF result:', hasVrfResult === 1 ? 'YES āœ…' : 'NO āŒ');
79
+ console.log();
80
+
81
+ // 4. Diagnose the issue
82
+ if (hasVrfResult === 0) {
83
+ console.log('šŸŽÆ DIAGNOSIS: Oracle randomness not consumed yet\n');
84
+ console.log('Why this happens:');
85
+ console.log(' • The keeper bot locked the round (commit phase)');
86
+ console.log(' • But oracle randomness was never revealed (reveal phase)');
87
+ console.log(' • Without VRF result, the round cannot be resolved\n');
88
+
89
+ console.log('šŸ’” SOLUTIONS:\n');
90
+ console.log('1. Make sure the keeper bot is running:');
91
+ console.log(' cd /Users/adamdahan/Developer/iheartsolana/solana-programs/dubs-server');
92
+ console.log(' node scripts/jackpot-keeper.js\n');
93
+
94
+ console.log('2. Or manually reveal randomness:');
95
+ console.log(' curl -X POST http://localhost:3001/jackpot/oracle/reveal \\');
96
+ console.log(` -H "Content-Type: application/json" \\`);
97
+ console.log(` -d '{"roundId": "${round.roundId}", "oracleSeed": "0000000000000000000000000000000000000000000000000000000000000001"}'\n`);
98
+
99
+ console.log('3. Check oracle wallet exists:');
100
+ console.log(' ls -la /Users/adamdahan/Developer/iheartsolana/solana-programs/dubs-server/wallets/jackpot_oracle.json\n');
101
+
102
+ } else {
103
+ console.log('šŸŽÆ DIAGNOSIS: VRF result exists but round not resolved yet\n');
104
+ console.log('Why this happens:');
105
+ console.log(' • Oracle randomness was consumed successfully');
106
+ console.log(' • But resolve_round was never called');
107
+ console.log(' • The keeper bot may have crashed or stopped\n');
108
+
109
+ console.log('šŸ’” SOLUTIONS:\n');
110
+ console.log('1. Restart the keeper bot:');
111
+ console.log(' cd /Users/adamdahan/Developer/iheartsolana/solana-programs/dubs-server');
112
+ console.log(' node scripts/jackpot-keeper.js\n');
113
+
114
+ console.log('2. Or manually resolve:');
115
+ console.log(' # First, calculate winner off-chain, then:');
116
+ console.log(' # This requires knowing the winner address beforehand\n');
117
+ }
118
+
119
+ // 5. Check keeper bot process
120
+ console.log('šŸ¤– Checking keeper bot status...');
121
+ const { exec } = require('child_process');
122
+ exec('ps aux | grep jackpot-keeper', (error, stdout) => {
123
+ if (stdout.includes('node') && stdout.includes('jackpot-keeper')) {
124
+ console.log('āœ… Keeper bot appears to be running');
125
+ console.log('šŸ’” Check its logs for errors');
126
+ } else {
127
+ console.log('āŒ Keeper bot is NOT running!');
128
+ console.log('šŸ’” This is likely why the round is stuck');
129
+ }
130
+ console.log();
131
+ });
132
+
133
+ } catch (error) {
134
+ console.error('āŒ Error:', error.message);
135
+ console.log('\nšŸ’” Make sure:');
136
+ console.log(' 1. The server is running (node server.js)');
137
+ console.log(' 2. You\'re connected to devnet');
138
+ console.log(' 3. The program is deployed');
139
+ }
140
+ }
141
+
142
+ diagnoseRound().catch(console.error);
143
+