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,270 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * V1 vs V2 Payout Reconciliation Script
4
+ *
5
+ * Shows what each player would receive in V1 (equal split) vs V2 (proportional),
6
+ * and calculates the difference you would owe them to make it fair.
7
+ *
8
+ * Usage:
9
+ * DATABASE_URL=$(heroku config:get DATABASE_URL -a dubs-server-prod) node scripts/reconcile-v1-v2-payouts.js <gameId>
10
+ */
11
+
12
+ const { Pool } = require('pg');
13
+
14
+ const DATABASE_URL = process.env.DATABASE_URL;
15
+
16
+ if (!DATABASE_URL) {
17
+ console.error('❌ DATABASE_URL not set. Run with:');
18
+ console.error(' DATABASE_URL=$(heroku config:get DATABASE_URL -a dubs-server-prod) node scripts/reconcile-v1-v2-payouts.js <gameId>');
19
+ process.exit(1);
20
+ }
21
+
22
+ const pool = new Pool({
23
+ connectionString: DATABASE_URL,
24
+ ssl: { rejectUnauthorized: false }
25
+ });
26
+
27
+ async function reconcilePayouts(gameId) {
28
+ try {
29
+ // Get game data
30
+ const gameResult = await pool.query(`
31
+ SELECT
32
+ g.game_id,
33
+ g.buy_in,
34
+ g.is_resolved,
35
+ g.home_team_players,
36
+ g.away_team_players,
37
+ g.draw_team_players,
38
+ g.player_amounts,
39
+ g.sports_event->>'strHomeTeam' as home_team,
40
+ g.sports_event->>'strAwayTeam' as away_team,
41
+ g.sports_event->'finalScore'->>'winner' as winner,
42
+ g.game_type
43
+ FROM games g
44
+ WHERE g.game_id = $1
45
+ `, [gameId]);
46
+
47
+ if (gameResult.rows.length === 0) {
48
+ console.error('❌ Game not found');
49
+ process.exit(1);
50
+ }
51
+
52
+ const game = gameResult.rows[0];
53
+ const homeTeam = game.home_team || 'Home';
54
+ const awayTeam = game.away_team || 'Away';
55
+
56
+ // Get participants
57
+ const participantsResult = await pool.query(`
58
+ SELECT
59
+ ugr.wallet_address,
60
+ u.username,
61
+ ugr.team_choice
62
+ FROM user_game_refs ugr
63
+ LEFT JOIN users u ON ugr.wallet_address = u.wallet_address
64
+ WHERE ugr.game_id = $1
65
+ ORDER BY ugr.team_choice, ugr.joined_at
66
+ `, [gameId]);
67
+
68
+ const playerAmounts = game.player_amounts || {};
69
+ const buyIn = parseFloat(game.buy_in);
70
+
71
+ const getPlayerAmount = (wallet) => {
72
+ if (playerAmounts[wallet] !== undefined) {
73
+ return parseFloat(playerAmounts[wallet]);
74
+ }
75
+ return buyIn;
76
+ };
77
+
78
+ // Calculate pools
79
+ const homePlayers = game.home_team_players || [];
80
+ const awayPlayers = game.away_team_players || [];
81
+ const drawPlayers = game.draw_team_players || [];
82
+
83
+ const homeTeamPool = homePlayers.reduce((sum, wallet) => sum + getPlayerAmount(wallet), 0);
84
+ const awayTeamPool = awayPlayers.reduce((sum, wallet) => sum + getPlayerAmount(wallet), 0);
85
+ const drawTeamPool = drawPlayers.reduce((sum, wallet) => sum + getPlayerAmount(wallet), 0);
86
+ const totalPot = homeTeamPool + awayTeamPool + drawTeamPool;
87
+ const platformFee = totalPot * 0.06;
88
+ const winnerPool = totalPot - platformFee;
89
+
90
+ // Check if V1 or V2
91
+ const totalPlayerCount = homePlayers.length + awayPlayers.length + drawPlayers.length;
92
+ const playerAmountsCount = Object.keys(playerAmounts).length;
93
+ const isPureV2 = totalPlayerCount > 0 && playerAmountsCount === totalPlayerCount;
94
+
95
+ // Build player list with details
96
+ const players = participantsResult.rows.map(p => ({
97
+ username: p.username || p.wallet_address.slice(0, 8) + '...',
98
+ wallet: p.wallet_address,
99
+ team: p.team_choice,
100
+ teamName: p.team_choice === 'home' ? homeTeam : p.team_choice === 'away' ? awayTeam : 'Draw',
101
+ bet: getPlayerAmount(p.wallet_address)
102
+ }));
103
+
104
+ const homePlayersList = players.filter(p => p.team === 'home');
105
+ const awayPlayersList = players.filter(p => p.team === 'away');
106
+ const drawPlayersList = players.filter(p => p.team === 'draw');
107
+
108
+ // Print header
109
+ console.log('');
110
+ console.log('╔═══════════════════════════════════════════════════════════════════════════════╗');
111
+ console.log('║ V1 vs V2 PAYOUT RECONCILIATION ║');
112
+ console.log('╚═══════════════════════════════════════════════════════════════════════════════╝');
113
+ console.log('');
114
+ console.log(`Game ID: ${gameId}`);
115
+ console.log(`Matchup: ${homeTeam} vs ${awayTeam}`);
116
+ console.log(`Type: ${game.game_type || 'sports'}`);
117
+ console.log(`Status: ${game.is_resolved ? 'Resolved' : 'Pending'}`);
118
+ if (game.winner) {
119
+ console.log(`Winner: ${game.winner === 'home' ? homeTeam : game.winner === 'away' ? awayTeam : 'Draw'}`);
120
+ }
121
+ console.log('');
122
+ console.log(`Mode: ${isPureV2 ? 'V2 (Pure Pari-Mutuel) ✅' : 'V1/Hybrid (Legacy) ⚠️'}`);
123
+ console.log('');
124
+ console.log('┌─────────────────────────────────────────────────────────────────────────────────┐');
125
+ console.log('│ POT DETAILS │');
126
+ console.log('├─────────────────────────────────────────────────────────────────────────────────┤');
127
+ console.log(`│ Total Pot: ${totalPot.toFixed(4).padStart(10)} SOL │`);
128
+ console.log(`│ Platform Fee (6%): ${platformFee.toFixed(4).padStart(9)} SOL │`);
129
+ console.log(`│ Winner Pool: ${winnerPool.toFixed(4).padStart(10)} SOL │`);
130
+ console.log('├─────────────────────────────────────────────────────────────────────────────────┤');
131
+ console.log(`│ ${homeTeam.padEnd(20)}: ${homeTeamPool.toFixed(4).padStart(8)} SOL (${homePlayers.length} players) │`);
132
+ console.log(`│ ${awayTeam.padEnd(20)}: ${awayTeamPool.toFixed(4).padStart(8)} SOL (${awayPlayers.length} players) │`);
133
+ if (drawPlayers.length > 0) {
134
+ console.log(`│ Draw : ${drawTeamPool.toFixed(4).padStart(8)} SOL (${drawPlayers.length} players) │`);
135
+ }
136
+ console.log('└─────────────────────────────────────────────────────────────────────────────────┘');
137
+ console.log('');
138
+
139
+ // Function to calculate and display scenario
140
+ function displayScenario(winningTeam, winningTeamName, winnersList, winnersPool, losersList) {
141
+ const v1PayoutPerWinner = winnersList.length > 0 ? winnerPool / winnersList.length : 0;
142
+
143
+ console.log('┌─────────────────────────────────────────────────────────────────────────────────┐');
144
+ console.log(`│ SCENARIO: ${winningTeamName.toUpperCase()} WINS`.padEnd(82) + '│');
145
+ console.log('├─────────────────────────────────────────────────────────────────────────────────┤');
146
+ console.log('│ WINNERS: │');
147
+ console.log('│ ┌────────────────┬──────────┬─────────────┬─────────────┬─────────────────────┐│');
148
+ console.log('│ │ Player │ Bet │ V1 (Equal) │ V2 (Prop) │ You Owe ││');
149
+ console.log('│ ├────────────────┼──────────┼─────────────┼─────────────┼─────────────────────┤│');
150
+
151
+ let totalOwed = 0;
152
+ const reconciliation = [];
153
+
154
+ for (const p of winnersList) {
155
+ const v2Payout = winnersPool > 0 ? (p.bet / winnersPool) * winnerPool : 0;
156
+ const difference = v2Payout - v1PayoutPerWinner;
157
+ const owed = difference > 0 ? difference : 0;
158
+ totalOwed += owed;
159
+
160
+ reconciliation.push({
161
+ username: p.username,
162
+ wallet: p.wallet,
163
+ bet: p.bet,
164
+ v1Payout: v1PayoutPerWinner,
165
+ v2Payout: v2Payout,
166
+ difference: difference,
167
+ owed: owed
168
+ });
169
+
170
+ const username = p.username.slice(0, 14).padEnd(14);
171
+ const bet = p.bet.toFixed(4).padStart(8);
172
+ const v1 = v1PayoutPerWinner.toFixed(4).padStart(9) + ' SOL';
173
+ const v2 = v2Payout.toFixed(4).padStart(9) + ' SOL';
174
+ const owedStr = owed > 0 ? `+${owed.toFixed(4)} SOL`.padStart(17) : '0 (overpaid)'.padStart(17);
175
+
176
+ console.log(`│ │ ${username} │ ${bet} │ ${v1} │ ${v2} │ ${owedStr} ││`);
177
+ }
178
+
179
+ console.log('│ └────────────────┴──────────┴─────────────┴─────────────┴─────────────────────┘│');
180
+ console.log('│ │');
181
+ console.log(`│ TOTAL YOU OWE IF ${winningTeamName.toUpperCase()} WINS: ${totalOwed.toFixed(4)} SOL`.padEnd(82) + '│');
182
+ console.log('│ │');
183
+ console.log('│ LOSERS: (get nothing in both V1 and V2) │');
184
+
185
+ if (losersList.length === 0) {
186
+ console.log('│ (none) │');
187
+ } else {
188
+ const loserNames = losersList.map(p => p.username).join(', ');
189
+ const truncatedLosers = loserNames.length > 70 ? loserNames.slice(0, 67) + '...' : loserNames;
190
+ console.log(`│ ${truncatedLosers.padEnd(75)}│`);
191
+ }
192
+
193
+ console.log('└─────────────────────────────────────────────────────────────────────────────────┘');
194
+ console.log('');
195
+
196
+ return { totalOwed, reconciliation };
197
+ }
198
+
199
+ // Scenario 1: Home wins
200
+ const homeWinsResult = displayScenario('home', homeTeam, homePlayersList, homeTeamPool, [...awayPlayersList, ...drawPlayersList]);
201
+
202
+ // Scenario 2: Away wins
203
+ const awayWinsResult = displayScenario('away', awayTeam, awayPlayersList, awayTeamPool, [...homePlayersList, ...drawPlayersList]);
204
+
205
+ // Scenario 3: Draw wins (if applicable)
206
+ let drawWinsResult = null;
207
+ if (drawPlayers.length > 0) {
208
+ drawWinsResult = displayScenario('draw', 'Draw', drawPlayersList, drawTeamPool, [...homePlayersList, ...awayPlayersList]);
209
+ }
210
+
211
+ // Summary
212
+ console.log('╔═══════════════════════════════════════════════════════════════════════════════╗');
213
+ console.log('║ SUMMARY ║');
214
+ console.log('╠═══════════════════════════════════════════════════════════════════════════════╣');
215
+ console.log(`║ If ${homeTeam.padEnd(20)} wins: Owe ${homeWinsResult.totalOwed.toFixed(4).padStart(8)} SOL ║`);
216
+ console.log(`║ If ${awayTeam.padEnd(20)} wins: Owe ${awayWinsResult.totalOwed.toFixed(4).padStart(8)} SOL ║`);
217
+ if (drawWinsResult) {
218
+ console.log(`║ If Draw wins: Owe ${drawWinsResult.totalOwed.toFixed(4).padStart(8)} SOL ║`);
219
+ }
220
+ console.log('╚═══════════════════════════════════════════════════════════════════════════════╝');
221
+ console.log('');
222
+
223
+ // If game is already resolved, show what actually needs to happen
224
+ if (game.winner) {
225
+ console.log('┌─────────────────────────────────────────────────────────────────────────────────┐');
226
+ console.log('│ ⚠️ GAME IS RESOLVED - ACTION NEEDED │');
227
+ console.log('├─────────────────────────────────────────────────────────────────────────────────┤');
228
+
229
+ const actualResult = game.winner === 'home' ? homeWinsResult :
230
+ game.winner === 'away' ? awayWinsResult : drawWinsResult;
231
+
232
+ if (actualResult) {
233
+ console.log(`│ Winner: ${game.winner === 'home' ? homeTeam : game.winner === 'away' ? awayTeam : 'Draw'}`.padEnd(82) + '│');
234
+ console.log('│ │');
235
+ console.log('│ Players to reimburse: │');
236
+
237
+ for (const p of actualResult.reconciliation) {
238
+ if (p.owed > 0) {
239
+ console.log(`│ ${p.username.padEnd(20)} → Send ${p.owed.toFixed(4)} SOL`.padEnd(82) + '│');
240
+ console.log(`│ Wallet: ${p.wallet}`.padEnd(82) + '│');
241
+ }
242
+ }
243
+
244
+ console.log('│ │');
245
+ console.log(`│ TOTAL TO SEND: ${actualResult.totalOwed.toFixed(4)} SOL`.padEnd(82) + '│');
246
+ }
247
+
248
+ console.log('└─────────────────────────────────────────────────────────────────────────────────┘');
249
+ console.log('');
250
+ }
251
+
252
+ } catch (error) {
253
+ console.error('❌ Error:', error.message);
254
+ process.exit(1);
255
+ } finally {
256
+ await pool.end();
257
+ }
258
+ }
259
+
260
+ // Main
261
+ const gameId = process.argv[2];
262
+ if (!gameId) {
263
+ console.log('Usage: node scripts/reconcile-v1-v2-payouts.js <gameId>');
264
+ console.log('');
265
+ console.log('Example:');
266
+ console.log(' DATABASE_URL=$(heroku config:get DATABASE_URL -a dubs-server-prod) node scripts/reconcile-v1-v2-payouts.js sport-1769102132440-xmirk1omx');
267
+ process.exit(1);
268
+ }
269
+
270
+ reconcilePayouts(gameId);
@@ -0,0 +1,111 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * 🎰 Initialize Dubs Jackpot Protocol
5
+ *
6
+ * One-time setup to configure the jackpot protocol
7
+ * Uses oracle wallet as admin
8
+ */
9
+
10
+ const { Connection, Keypair, PublicKey, Transaction } = require('@solana/web3.js');
11
+ const fs = require('fs');
12
+ const path = require('path');
13
+
14
+ const PROGRAM_ID = new PublicKey('BHidyz25KWkNPdTHgeANzMg25MM2KEiNnG4yE5F46XUz');
15
+ const RPC_URL = process.env.SOLANA_NETWORK || 'https://api.devnet.solana.com';
16
+
17
+ async function main() {
18
+ console.log('🎰 Initializing Dubs Jackpot Protocol\n');
19
+
20
+ // Load oracle wallet (will be the admin)
21
+ const oracleKeyPath = path.join(__dirname, '..', 'wallets', 'jackpot_oracle.json');
22
+
23
+ let oracleWallet;
24
+ if (fs.existsSync(oracleKeyPath)) {
25
+ const secretKey = JSON.parse(fs.readFileSync(oracleKeyPath, 'utf-8'));
26
+ oracleWallet = Keypair.fromSecretKey(Uint8Array.from(secretKey));
27
+ console.log('✅ Loaded oracle wallet from', oracleKeyPath);
28
+ } else {
29
+ // Use the main operator wallet as fallback
30
+ const mainWalletPath = path.join(require('os').homedir(), '.config/solana/id.json');
31
+ if (!fs.existsSync(mainWalletPath)) {
32
+ console.error('❌ No wallet found!');
33
+ console.log('Create one with: solana-keygen new');
34
+ process.exit(1);
35
+ }
36
+ const secretKey = JSON.parse(fs.readFileSync(mainWalletPath, 'utf-8'));
37
+ oracleWallet = Keypair.fromSecretKey(Uint8Array.from(secretKey));
38
+ console.log('✅ Using main wallet:', oracleWallet.publicKey.toString());
39
+ }
40
+
41
+ const connection = new Connection(RPC_URL, 'confirmed');
42
+
43
+ // Check balance
44
+ const balance = await connection.getBalance(oracleWallet.publicKey);
45
+ console.log('💰 Balance:', (balance / 1e9).toFixed(4), 'SOL');
46
+
47
+ if (balance < 0.1 * 1e9) {
48
+ console.log('⚠️ Low balance! You may need to airdrop:');
49
+ console.log(` solana airdrop 1 ${oracleWallet.publicKey.toString()} --url devnet`);
50
+ }
51
+
52
+ // Configuration
53
+ const adminAddress = oracleWallet.publicKey;
54
+ const treasuryAddress = oracleWallet.publicKey; // Use same wallet for treasury
55
+ const feeBasisPoints = 500; // 5% fee
56
+ const roundDurationSlots = 150; // 1 MINUTE rounds! (~60 seconds)
57
+
58
+ console.log('\n📝 Configuration:');
59
+ console.log(' Admin:', adminAddress.toString());
60
+ console.log(' Treasury:', treasuryAddress.toString());
61
+ console.log(' Fee:', feeBasisPoints, 'bps (5%)');
62
+ console.log(' Round Duration:', roundDurationSlots, 'slots (~1 minute)');
63
+ console.log(' Network:', RPC_URL);
64
+
65
+ // Build transaction using API
66
+ console.log('\n🔨 Building initialize transaction...');
67
+
68
+ const API_BASE = 'http://localhost:3001';
69
+ const axios = require('axios');
70
+
71
+ try {
72
+ const { data } = await axios.post(`${API_BASE}/jackpot/build/initialize`, {
73
+ adminAddress: adminAddress.toString(),
74
+ treasuryAddress: treasuryAddress.toString(),
75
+ feeBasisPoints,
76
+ roundDurationSlots,
77
+ });
78
+
79
+ console.log('✅ Transaction built');
80
+ console.log(' Config PDA:', data.configPda);
81
+
82
+ // Deserialize and get fresh blockhash
83
+ const tx = Transaction.from(Buffer.from(data.transaction, 'base64'));
84
+ tx.recentBlockhash = (await connection.getLatestBlockhash()).blockhash;
85
+ tx.feePayer = oracleWallet.publicKey;
86
+ tx.sign(oracleWallet);
87
+
88
+ console.log('\n📤 Sending transaction...');
89
+ const signature = await connection.sendRawTransaction(tx.serialize());
90
+ console.log(' Signature:', signature);
91
+
92
+ console.log('\n⏳ Confirming...');
93
+ await connection.confirmTransaction(signature);
94
+
95
+ console.log('\n🎉 PROTOCOL INITIALIZED!');
96
+ console.log('\n✅ Next steps:');
97
+ console.log(' 1. Open first round: node scripts/open-jackpot-round.js');
98
+ console.log(' 2. Refresh frontend: http://localhost:3000');
99
+ console.log(' 3. Start betting! 🎰');
100
+
101
+ } catch (error) {
102
+ console.error('\n❌ Error:', error.message);
103
+ if (error.response) {
104
+ console.error(' API Error:', error.response.data);
105
+ }
106
+ process.exit(1);
107
+ }
108
+ }
109
+
110
+ main().catch(console.error);
111
+
@@ -0,0 +1,10 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(heroku pg:psql:*)",
5
+ "Bash(heroku run:*)",
6
+ "Bash(lsof:*)",
7
+ "Bash(xargs kill:*)"
8
+ ]
9
+ }
10
+ }
@@ -0,0 +1,84 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Force reset a stuck jackpot round
5
+ *
6
+ * Usage:
7
+ * node scripts/jackpot/force-reset.js
8
+ *
9
+ * Environment:
10
+ * SOLANA_RPC_URL - RPC endpoint (default: devnet)
11
+ * API_BASE - Server URL (default: http://localhost:3001)
12
+ * KEEPER_PRIVATE_KEY - Keeper wallet (optional, falls back to wallet files)
13
+ */
14
+
15
+ const { Connection, Keypair, Transaction } = require('@solana/web3.js');
16
+ const axios = require('axios');
17
+ const fs = require('fs');
18
+ const path = require('path');
19
+
20
+ const RPC_URL = process.env.SOLANA_RPC_URL || 'https://api.devnet.solana.com';
21
+ const API_BASE = process.env.API_BASE || 'http://localhost:3001';
22
+
23
+ function loadWallet() {
24
+ if (process.env.KEEPER_PRIVATE_KEY) {
25
+ console.log('Loading wallet from KEEPER_PRIVATE_KEY env var');
26
+ return Keypair.fromSecretKey(Uint8Array.from(JSON.parse(process.env.KEEPER_PRIVATE_KEY)));
27
+ }
28
+
29
+ const oracleKeyPath = path.join(__dirname, '..', '..', 'wallets', 'jackpot_oracle.json');
30
+ if (fs.existsSync(oracleKeyPath)) {
31
+ console.log('Loading wallet from wallets/jackpot_oracle.json');
32
+ return Keypair.fromSecretKey(Uint8Array.from(JSON.parse(fs.readFileSync(oracleKeyPath, 'utf-8'))));
33
+ }
34
+
35
+ const defaultPath = path.join(require('os').homedir(), '.config/solana/id.json');
36
+ if (fs.existsSync(defaultPath)) {
37
+ console.log('Loading wallet from ~/.config/solana/id.json');
38
+ return Keypair.fromSecretKey(Uint8Array.from(JSON.parse(fs.readFileSync(defaultPath, 'utf-8'))));
39
+ }
40
+
41
+ console.error('No wallet found. Set KEEPER_PRIVATE_KEY or add a wallet file.');
42
+ process.exit(1);
43
+ }
44
+
45
+ async function main() {
46
+ const wallet = loadWallet();
47
+ const connection = new Connection(RPC_URL, 'confirmed');
48
+
49
+ console.log('\n♻️ Force resetting round...');
50
+ console.log(' Wallet:', wallet.publicKey.toString());
51
+ console.log(' API:', API_BASE);
52
+ console.log(' RPC:', RPC_URL.includes('devnet') ? 'Devnet' : RPC_URL.slice(0, 50) + '...');
53
+ console.log();
54
+
55
+ try {
56
+ const { data } = await axios.post(`${API_BASE}/jackpot/build/reset-round`, {
57
+ keeperAddress: wallet.publicKey.toString(),
58
+ });
59
+
60
+ const tx = Transaction.from(Buffer.from(data.transaction, 'base64'));
61
+ tx.recentBlockhash = (await connection.getLatestBlockhash()).blockhash;
62
+ tx.feePayer = wallet.publicKey;
63
+ tx.sign(wallet);
64
+
65
+ const signature = await connection.sendRawTransaction(tx.serialize(), { skipPreflight: false });
66
+ console.log('Signature:', signature);
67
+
68
+ // Poll for confirmation (Alchemy doesn't support signatureSubscribe)
69
+ const start = Date.now();
70
+ while (Date.now() - start < 60000) {
71
+ const statuses = await connection.getSignatureStatuses([signature]);
72
+ const status = statuses?.value?.[0];
73
+ if (status?.err) throw new Error(`Transaction failed: ${JSON.stringify(status.err)}`);
74
+ if (status?.confirmationStatus === 'confirmed' || status?.confirmationStatus === 'finalized') break;
75
+ await new Promise(resolve => setTimeout(resolve, 2000));
76
+ }
77
+
78
+ console.log(`\n✅ Round reset! New round: ${data.roundId}`);
79
+ } catch (error) {
80
+ console.error('❌ Error:', error.response?.data || error.message);
81
+ }
82
+ }
83
+
84
+ main();
@@ -0,0 +1,100 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Initialize Jackpot Protocol on Mainnet
5
+ *
6
+ * Standalone script — does NOT require the server to be running.
7
+ * Builds, signs, and sends the initialize transaction directly.
8
+ */
9
+
10
+ const { Connection, Keypair, PublicKey, SystemProgram, Transaction, TransactionInstruction } = require('@solana/web3.js');
11
+ const fs = require('fs');
12
+ const path = require('path');
13
+
14
+ // ── Configuration ──────────────────────────────────────────────
15
+ const RPC_URL = process.env.SOLANA_RPC_URL || 'https://api.mainnet-beta.solana.com';
16
+ const PROGRAM_ID = new PublicKey('BHidyz25KWkNPdTHgeANzMg25MM2KEiNnG4yE5F46XUz');
17
+ const TREASURY = new PublicKey('BVZXwZpfgyzTBdRFHohkHZppPHnAyqyctRsKy3vWfQib');
18
+ const FEE_BPS = 500; // 5%
19
+ const ROUND_DURATION_SLOTS = 1512000; // ~7 days at 400ms/slot
20
+
21
+ // Anchor discriminator for "global:initialize"
22
+ const INITIALIZE_DISC = Buffer.from([175, 175, 109, 31, 13, 152, 155, 237]);
23
+
24
+ // ── Helpers ────────────────────────────────────────────────────
25
+ function serializeU16(v) { const b = Buffer.alloc(2); b.writeUInt16LE(v); return b; }
26
+ function serializeU64(v) { const b = Buffer.alloc(8); b.writeBigUInt64LE(BigInt(v)); return b; }
27
+
28
+ // ── Main ───────────────────────────────────────────────────────
29
+ async function main() {
30
+ console.log('🎰 Initialize Jackpot Protocol — MAINNET\n');
31
+
32
+ // Load admin wallet (default Solana CLI wallet = upgrade authority)
33
+ const walletPath = path.join(require('os').homedir(), '.config/solana/id.json');
34
+ if (!fs.existsSync(walletPath)) {
35
+ console.error('❌ No wallet at', walletPath);
36
+ process.exit(1);
37
+ }
38
+ const admin = Keypair.fromSecretKey(Uint8Array.from(JSON.parse(fs.readFileSync(walletPath, 'utf-8'))));
39
+
40
+ const connection = new Connection(RPC_URL, 'confirmed');
41
+ const balance = await connection.getBalance(admin.publicKey);
42
+
43
+ // Derive config PDA
44
+ const [configPda] = PublicKey.findProgramAddressSync([Buffer.from('config')], PROGRAM_ID);
45
+
46
+ console.log(' RPC: ', RPC_URL.slice(0, 50) + '...');
47
+ console.log(' Program: ', PROGRAM_ID.toString());
48
+ console.log(' Admin: ', admin.publicKey.toString());
49
+ console.log(' Treasury: ', TREASURY.toString());
50
+ console.log(' Config PDA:', configPda.toString());
51
+ console.log(' Fee: ', FEE_BPS, 'bps (5%)');
52
+ console.log(' Duration: ', ROUND_DURATION_SLOTS.toLocaleString(), 'slots (~7 days)');
53
+ console.log(' Balance: ', (balance / 1e9).toFixed(4), 'SOL\n');
54
+
55
+ // Check if already initialized
56
+ const existing = await connection.getAccountInfo(configPda);
57
+ if (existing) {
58
+ console.log('⚠️ Config PDA already exists! Protocol may already be initialized.');
59
+ console.log(' Account size:', existing.data.length, 'bytes');
60
+ process.exit(1);
61
+ }
62
+
63
+ // Build instruction
64
+ const data = Buffer.concat([
65
+ INITIALIZE_DISC,
66
+ serializeU16(FEE_BPS),
67
+ serializeU64(ROUND_DURATION_SLOTS),
68
+ ]);
69
+
70
+ const keys = [
71
+ { pubkey: configPda, isSigner: false, isWritable: true },
72
+ { pubkey: admin.publicKey, isSigner: true, isWritable: true },
73
+ { pubkey: TREASURY, isSigner: false, isWritable: false },
74
+ { pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
75
+ ];
76
+
77
+ const ix = new TransactionInstruction({ keys, programId: PROGRAM_ID, data });
78
+ const tx = new Transaction().add(ix);
79
+ tx.feePayer = admin.publicKey;
80
+ tx.recentBlockhash = (await connection.getLatestBlockhash()).blockhash;
81
+ tx.sign(admin);
82
+
83
+ console.log('📤 Sending initialize transaction...');
84
+ const signature = await connection.sendRawTransaction(tx.serialize(), { skipPreflight: false });
85
+ console.log(' Signature:', signature);
86
+
87
+ console.log('⏳ Confirming...');
88
+ await connection.confirmTransaction(signature, 'confirmed');
89
+
90
+ console.log('\n🎉 PROTOCOL INITIALIZED ON MAINNET!');
91
+ console.log(' Config PDA:', configPda.toString());
92
+ console.log(' TX:', signature);
93
+ console.log('\n Next: deploy backend with keeper to start rounds.');
94
+ }
95
+
96
+ main().catch(err => {
97
+ console.error('❌ Error:', err.message);
98
+ if (err.logs) console.error('Logs:', err.logs);
99
+ process.exit(1);
100
+ });