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,557 @@
1
+ /**
2
+ * 🎮 Arcade Match API Routes
3
+ *
4
+ * RESTful API for managing arcade matches
5
+ * Isolated from the main sports betting system
6
+ */
7
+
8
+ const express = require('express');
9
+ const router = express.Router();
10
+ const { v4: uuidv4 } = require('uuid');
11
+
12
+ module.exports = (arcadeService) => {
13
+
14
+ // ============================================
15
+ // 🎮 BUILD TRANSACTION ENDPOINTS (MUST BE FIRST!)
16
+ // These must come before parameterized routes to avoid matching issues
17
+ // ============================================
18
+
19
+ /**
20
+ * POST /arcade/build/match/create
21
+ * Build UNSIGNED transaction for creating arcade match
22
+ * Returns base64 transaction for user to sign with their wallet
23
+ *
24
+ * Body:
25
+ * {
26
+ * creatorAddress: string (Solana pubkey),
27
+ * stake: number (in lamports),
28
+ * gameType?: 'asteroids' | 'snake',
29
+ * matchType?: 'bestOf1' | 'bestOf3',
30
+ * deadlineMinutes?: number
31
+ * }
32
+ */
33
+ router.post('/build/match/create', async (req, res) => {
34
+ try {
35
+ const {
36
+ creatorAddress,
37
+ stake,
38
+ gameType = 'asteroids',
39
+ matchType = 'bestOf1',
40
+ deadlineMinutes = 60,
41
+ } = req.body;
42
+
43
+ // Validate inputs
44
+ if (!creatorAddress || !stake) {
45
+ return res.status(400).json({
46
+ error: 'Missing required fields: creatorAddress, stake'
47
+ });
48
+ }
49
+
50
+ if (stake <= 0) {
51
+ return res.status(400).json({
52
+ error: 'Stake must be greater than 0'
53
+ });
54
+ }
55
+
56
+ // Build UNSIGNED transaction
57
+ const result = await arcadeService.buildCreateMatchTransaction({
58
+ creatorAddress,
59
+ stake,
60
+ gameType,
61
+ matchType,
62
+ deadlineMinutes,
63
+ });
64
+
65
+ res.json(result);
66
+ } catch (error) {
67
+ console.error('Build create match error:', error);
68
+ res.status(500).json({
69
+ error: error.message || 'Failed to build transaction',
70
+ });
71
+ }
72
+ });
73
+
74
+ /**
75
+ * POST /arcade/build/match/join
76
+ * Build UNSIGNED transaction for joining arcade match
77
+ *
78
+ * Body:
79
+ * {
80
+ * matchId: string (u64 as string),
81
+ * opponentAddress: string (Solana pubkey)
82
+ * }
83
+ */
84
+ router.post('/build/match/join', async (req, res) => {
85
+ console.log('🎮 [BUILD JOIN] Route hit!');
86
+ console.log('🎮 [BUILD JOIN] Body:', req.body);
87
+
88
+ try {
89
+ const { matchId, opponentAddress } = req.body;
90
+
91
+ if (!matchId || !opponentAddress) {
92
+ return res.status(400).json({
93
+ error: 'Missing required fields: matchId, opponentAddress'
94
+ });
95
+ }
96
+
97
+ // Build UNSIGNED transaction
98
+ const result = await arcadeService.buildJoinMatchTransaction({
99
+ matchId,
100
+ opponentAddress,
101
+ });
102
+
103
+ res.json(result);
104
+ } catch (error) {
105
+ console.error('Build join match error:', error);
106
+ res.status(500).json({
107
+ error: error.message || 'Failed to build transaction',
108
+ });
109
+ }
110
+ });
111
+
112
+ /**
113
+ * POST /arcade/build/match/score
114
+ * Build UNSIGNED transaction for submitting a score
115
+ *
116
+ * Body:
117
+ * {
118
+ * matchId: string (u64 as string),
119
+ * playerAddress: string (Solana pubkey),
120
+ * score: number,
121
+ * gameRound?: number (defaults to 1)
122
+ * }
123
+ */
124
+ router.post('/build/match/score', async (req, res) => {
125
+ console.log('🎮 [BUILD SUBMIT SCORE] ✅ CORRECT ROUTE HIT!');
126
+ console.log('🎮 [BUILD SUBMIT SCORE] Body:', req.body);
127
+
128
+ try {
129
+ const { matchId, playerAddress, score, gameRound = 1 } = req.body;
130
+
131
+ if (!matchId || !playerAddress || score === undefined) {
132
+ return res.status(400).json({
133
+ error: 'Missing required fields: matchId, playerAddress, score'
134
+ });
135
+ }
136
+
137
+ if (score < 0) {
138
+ return res.status(400).json({
139
+ error: 'Score must be non-negative'
140
+ });
141
+ }
142
+
143
+ // Build UNSIGNED transaction
144
+ const result = await arcadeService.buildSubmitScoreTransaction({
145
+ matchId,
146
+ playerAddress,
147
+ score,
148
+ gameRound,
149
+ });
150
+
151
+ res.json(result);
152
+ } catch (error) {
153
+ console.error('Build submit score error:', error);
154
+ res.status(500).json({
155
+ error: error.message || 'Failed to build transaction',
156
+ });
157
+ }
158
+ });
159
+
160
+ /**
161
+ * POST /arcade/build/match/resolve
162
+ * Build UNSIGNED transaction for resolving a match
163
+ *
164
+ * Body:
165
+ * {
166
+ * matchId: string (u64 as string),
167
+ * callerAddress: string (who's calling - pays gas),
168
+ * creatorAddress: string,
169
+ * opponentAddress: string
170
+ * }
171
+ */
172
+ router.post('/build/match/resolve', async (req, res) => {
173
+ console.log('🎮 [BUILD RESOLVE] Route hit!');
174
+ console.log('🎮 [BUILD RESOLVE] Body:', req.body);
175
+
176
+ try {
177
+ const { matchId, callerAddress, creatorAddress, opponentAddress } = req.body;
178
+
179
+ if (!matchId || !callerAddress || !creatorAddress || !opponentAddress) {
180
+ return res.status(400).json({
181
+ error: 'Missing required fields: matchId, callerAddress, creatorAddress, opponentAddress'
182
+ });
183
+ }
184
+
185
+ // Build UNSIGNED transaction
186
+ const result = await arcadeService.buildResolveMatchTransaction({
187
+ matchId,
188
+ callerAddress,
189
+ creatorAddress,
190
+ opponentAddress,
191
+ });
192
+
193
+ res.json(result);
194
+ } catch (error) {
195
+ console.error('Build resolve match error:', error);
196
+ res.status(500).json({
197
+ error: error.message || 'Failed to build transaction',
198
+ });
199
+ }
200
+ });
201
+
202
+ // ============================================
203
+ // 🏥 HEALTH & INFO ENDPOINTS
204
+ // ============================================
205
+
206
+ /**
207
+ * GET /arcade/health
208
+ * Health check for arcade service
209
+ */
210
+ router.get('/health', (req, res) => {
211
+ res.json({
212
+ status: 'healthy',
213
+ service: 'arcade-matches',
214
+ programId: arcadeService.programId.toString(),
215
+ operatorWallet: arcadeService.operatorWallet.toString(),
216
+ timestamp: new Date().toISOString(),
217
+ });
218
+ });
219
+
220
+ // ============================================
221
+ // 🎮 LEGACY ENDPOINTS (For testing with test wallets)
222
+ // ============================================
223
+
224
+ /**
225
+ * POST /arcade/matches/create
226
+ * Create a new arcade match (LEGACY - for testing with test wallets)
227
+ *
228
+ * Body:
229
+ * {
230
+ * creatorName: string,
231
+ * stake: number (in lamports),
232
+ * gameType?: 'asteroids' | 'snake',
233
+ * matchType?: 'bestOf1' | 'bestOf3',
234
+ * deadlineMinutes?: number
235
+ * }
236
+ */
237
+ router.post('/matches/create', async (req, res) => {
238
+ try {
239
+ const {
240
+ creatorName,
241
+ stake,
242
+ gameType = 'asteroids',
243
+ matchType = 'bestOf1',
244
+ deadlineMinutes = 60,
245
+ } = req.body;
246
+
247
+ // Validate inputs
248
+ if (!creatorName || !stake) {
249
+ return res.status(400).json({
250
+ error: 'Missing required fields: creatorName, stake'
251
+ });
252
+ }
253
+
254
+ if (stake <= 0) {
255
+ return res.status(400).json({
256
+ error: 'Stake must be greater than 0'
257
+ });
258
+ }
259
+
260
+ if (!['asteroids', 'snake'].includes(gameType)) {
261
+ return res.status(400).json({
262
+ error: 'gameType must be "asteroids" or "snake"'
263
+ });
264
+ }
265
+
266
+ if (!['bestOf1', 'bestOf3'].includes(matchType)) {
267
+ return res.status(400).json({
268
+ error: 'matchType must be "bestOf1" or "bestOf3"'
269
+ });
270
+ }
271
+
272
+ // Generate unique match ID
273
+ const matchId = uuidv4();
274
+
275
+ // Create match
276
+ const result = await arcadeService.createMatch({
277
+ matchId,
278
+ creatorName,
279
+ stake,
280
+ gameType,
281
+ matchType,
282
+ deadlineMinutes,
283
+ });
284
+
285
+ res.json({
286
+ success: true,
287
+ ...result,
288
+ });
289
+ } catch (error) {
290
+ console.error('Create match error:', error);
291
+ res.status(500).json({
292
+ error: error.message || 'Failed to create match',
293
+ });
294
+ }
295
+ });
296
+
297
+ /**
298
+ * POST /arcade/matches/:matchId/join
299
+ * Join an existing match
300
+ *
301
+ * Body:
302
+ * {
303
+ * opponentName: string
304
+ * }
305
+ */
306
+ router.post('/matches/:matchId/join', async (req, res) => {
307
+ console.log('🎮 [LEGACY JOIN] Route hit! matchId:', req.params.matchId);
308
+ console.log('🎮 [LEGACY JOIN] Body:', req.body);
309
+
310
+ try {
311
+ const { matchId } = req.params;
312
+ const { opponentName } = req.body;
313
+
314
+ if (!opponentName) {
315
+ return res.status(400).json({
316
+ error: 'Missing required field: opponentName'
317
+ });
318
+ }
319
+
320
+ const result = await arcadeService.joinMatch({
321
+ matchId,
322
+ opponentName,
323
+ });
324
+
325
+ res.json({
326
+ success: true,
327
+ ...result,
328
+ });
329
+ } catch (error) {
330
+ console.error('Join match error:', error);
331
+ res.status(500).json({
332
+ error: error.message || 'Failed to join match',
333
+ });
334
+ }
335
+ });
336
+
337
+ /**
338
+ * POST /arcade/matches/:matchId/score
339
+ * Submit a score for a match
340
+ *
341
+ * Body:
342
+ * {
343
+ * playerName: string,
344
+ * score: number,
345
+ * gameRound?: number (1-3, defaults to 1)
346
+ * }
347
+ */
348
+ router.post('/matches/:matchId/score', async (req, res) => {
349
+ console.log('🎮 [LEGACY SUBMIT SCORE] ❌ WRONG ROUTE! matchId:', req.params.matchId);
350
+ console.log('🎮 [LEGACY SUBMIT SCORE] Body:', req.body);
351
+
352
+ try {
353
+ const { matchId } = req.params;
354
+ const { playerName, score, gameRound = 1 } = req.body;
355
+
356
+ if (!playerName || score === undefined) {
357
+ return res.status(400).json({
358
+ error: 'Missing required fields: playerName, score'
359
+ });
360
+ }
361
+
362
+ if (score < 0) {
363
+ return res.status(400).json({
364
+ error: 'Score must be non-negative'
365
+ });
366
+ }
367
+
368
+ if (gameRound < 1 || gameRound > 3) {
369
+ return res.status(400).json({
370
+ error: 'gameRound must be between 1 and 3'
371
+ });
372
+ }
373
+
374
+ const result = await arcadeService.submitScore({
375
+ matchId,
376
+ playerName,
377
+ score,
378
+ gameRound,
379
+ });
380
+
381
+ res.json({
382
+ success: true,
383
+ ...result,
384
+ });
385
+ } catch (error) {
386
+ console.error('Submit score error:', error);
387
+ res.status(500).json({
388
+ error: error.message || 'Failed to submit score',
389
+ });
390
+ }
391
+ });
392
+
393
+ /**
394
+ * POST /arcade/matches/:matchId/resolve
395
+ * Resolve a match and distribute winnings
396
+ *
397
+ * Body:
398
+ * {
399
+ * callerName: string (usually the creator)
400
+ * }
401
+ */
402
+ router.post('/matches/:matchId/resolve', async (req, res) => {
403
+ try {
404
+ const { matchId } = req.params;
405
+ const { callerName } = req.body;
406
+
407
+ if (!callerName) {
408
+ return res.status(400).json({
409
+ error: 'Missing required field: callerName'
410
+ });
411
+ }
412
+
413
+ const result = await arcadeService.resolveMatch({
414
+ matchId,
415
+ callerName,
416
+ });
417
+
418
+ res.json({
419
+ success: true,
420
+ ...result,
421
+ });
422
+ } catch (error) {
423
+ console.error('Resolve match error:', error);
424
+ res.status(500).json({
425
+ error: error.message || 'Failed to resolve match',
426
+ });
427
+ }
428
+ });
429
+
430
+ /**
431
+ * POST /arcade/matches/:matchId/cancel
432
+ * Cancel a match (if no opponent joined)
433
+ *
434
+ * Body:
435
+ * {
436
+ * creatorName: string
437
+ * }
438
+ */
439
+ router.post('/matches/:matchId/cancel', async (req, res) => {
440
+ try {
441
+ const { matchId } = req.params;
442
+ const { creatorName } = req.body;
443
+
444
+ if (!creatorName) {
445
+ return res.status(400).json({
446
+ error: 'Missing required field: creatorName'
447
+ });
448
+ }
449
+
450
+ const result = await arcadeService.cancelMatch({
451
+ matchId,
452
+ creatorName,
453
+ });
454
+
455
+ res.json({
456
+ success: true,
457
+ ...result,
458
+ });
459
+ } catch (error) {
460
+ console.error('Cancel match error:', error);
461
+ res.status(500).json({
462
+ error: error.message || 'Failed to cancel match',
463
+ });
464
+ }
465
+ });
466
+
467
+ /**
468
+ * GET /arcade/matches/:matchId
469
+ * Get match information
470
+ */
471
+ router.get('/matches/:matchId', async (req, res) => {
472
+ try {
473
+ const { matchId } = req.params;
474
+
475
+ const matchData = await arcadeService.getMatchData(matchId);
476
+
477
+ if (!matchData) {
478
+ return res.status(404).json({
479
+ error: 'Match not found'
480
+ });
481
+ }
482
+
483
+ res.json({
484
+ success: true,
485
+ match: matchData,
486
+ });
487
+ } catch (error) {
488
+ console.error('Get match error:', error);
489
+ res.status(500).json({
490
+ error: error.message || 'Failed to get match data',
491
+ });
492
+ }
493
+ });
494
+
495
+ // ============================================
496
+ // 💰 WALLET & TESTING ENDPOINTS
497
+ // ============================================
498
+
499
+ /**
500
+ * POST /arcade/wallet/airdrop
501
+ * Airdrop SOL to a wallet (for testing on localnet/devnet)
502
+ *
503
+ * Body:
504
+ * {
505
+ * playerName: string,
506
+ * amount?: number (in SOL, defaults to 1)
507
+ * }
508
+ */
509
+ router.post('/wallet/airdrop', async (req, res) => {
510
+ try {
511
+ const { playerName, amount = 1 } = req.body;
512
+
513
+ if (!playerName) {
514
+ return res.status(400).json({
515
+ error: 'Missing required field: playerName'
516
+ });
517
+ }
518
+
519
+ const result = await arcadeService.airdrop(playerName, amount);
520
+
521
+ res.json({
522
+ success: true,
523
+ ...result,
524
+ });
525
+ } catch (error) {
526
+ console.error('Airdrop error:', error);
527
+ res.status(500).json({
528
+ error: error.message || 'Failed to airdrop'
529
+ });
530
+ }
531
+ });
532
+
533
+ /**
534
+ * GET /arcade/wallet/:playerName/balance
535
+ * Get wallet balance
536
+ */
537
+ router.get('/wallet/:playerName/balance', async (req, res) => {
538
+ try {
539
+ const { playerName } = req.params;
540
+
541
+ const result = await arcadeService.getBalance(playerName);
542
+
543
+ res.json({
544
+ success: true,
545
+ ...result,
546
+ });
547
+ } catch (error) {
548
+ console.error('Get balance error:', error);
549
+ res.status(500).json({
550
+ error: error.message || 'Failed to get balance'
551
+ });
552
+ }
553
+ });
554
+
555
+ return router;
556
+ };
557
+