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,763 @@
1
+ /**
2
+ * 🎮 Arcade Match Service
3
+ *
4
+ * Handles head-to-head arcade game matches with the following features:
5
+ * - Create matches with stakes
6
+ * - Join existing matches
7
+ * - Submit scores
8
+ * - Resolve matches with operator fees
9
+ * - Cancel unmatched games
10
+ */
11
+
12
+ const { Connection, Keypair, PublicKey, SystemProgram, Transaction, TransactionInstruction } = require('@solana/web3.js');
13
+ const crypto = require('crypto');
14
+ const { v4: uuidv4 } = require('uuid');
15
+ const fs = require('fs');
16
+ const path = require('path');
17
+
18
+ // 🎮 Arcade Program Configuration
19
+ const ARCADE_PROGRAM_ID = new PublicKey('EiNzW28xL1a92m1i9T3MyRZqEEszMcVBtrYB3NkphwRq');
20
+
21
+ // 🔐 HARDCODED OPERATOR WALLET - Must match the contract!
22
+ const OPERATOR_WALLET = new PublicKey('BVZXwZpfgyzTBdRFHohkHZppPHnAyqyctRsKy3vWfQib');
23
+
24
+ // Instruction discriminators (first 8 bytes of SHA256 hash of "global:instruction_name")
25
+ const DISCRIMINATORS = {
26
+ CREATE_MATCH: Buffer.from([107, 2, 184, 145, 70, 142, 17, 165]), // create_match
27
+ JOIN_MATCH: Buffer.from([244, 8, 47, 130, 192, 59, 179, 44]), // join_match
28
+ SUBMIT_SCORE: Buffer.from([130, 100, 1, 137, 70, 252, 96, 3]), // submit_match_score
29
+ RESOLVE_MATCH: Buffer.from([73, 0, 15, 197, 178, 47, 21, 193]), // resolve_match
30
+ CANCEL_MATCH: Buffer.from([142, 136, 247, 45, 92, 112, 180, 83]), // cancel_match
31
+ };
32
+
33
+ // Game types
34
+ const GAME_TYPES = {
35
+ ASTEROIDS: { asteroids: {} },
36
+ SNAKE: { snake: {} },
37
+ };
38
+
39
+ // Match types
40
+ const MATCH_TYPES = {
41
+ BEST_OF_1: { bestOf1: {} },
42
+ BEST_OF_3: { bestOf3: {} },
43
+ };
44
+
45
+ // Match status
46
+ const MATCH_STATUS = {
47
+ WAITING_FOR_OPPONENT: 0,
48
+ ACTIVE: 1,
49
+ RESOLVED: 2,
50
+ CANCELLED: 3,
51
+ };
52
+
53
+ class ArcadeMatchService {
54
+ constructor(config) {
55
+ this.connection = new Connection(config.rpcUrl || 'http://127.0.0.1:8899', 'confirmed');
56
+ this.programId = ARCADE_PROGRAM_ID;
57
+ this.operatorWallet = OPERATOR_WALLET;
58
+ this.walletsDir = config.walletsDir || path.join(__dirname, '..', 'wallets');
59
+
60
+ // Ensure wallets directory exists
61
+ if (!fs.existsSync(this.walletsDir)) {
62
+ fs.mkdirSync(this.walletsDir, { recursive: true });
63
+ }
64
+
65
+ console.log('🎮 Arcade Match Service initialized');
66
+ console.log(' Program ID:', this.programId.toString());
67
+ console.log(' Operator:', this.operatorWallet.toString());
68
+ }
69
+
70
+ /**
71
+ * Get match PDA from match ID
72
+ */
73
+ getMatchPDA(matchId) {
74
+ const matchIdNum = typeof matchId === 'string' ? this.uuidToU64(matchId) : BigInt(matchId);
75
+ const matchIdBuf = Buffer.alloc(8);
76
+ matchIdBuf.writeBigUInt64LE(matchIdNum);
77
+ return PublicKey.findProgramAddressSync(
78
+ [Buffer.from('match'), matchIdBuf],
79
+ this.programId
80
+ );
81
+ }
82
+
83
+ /**
84
+ * Convert UUID to u64 for use as match ID
85
+ */
86
+ uuidToU64(uuid) {
87
+ const hash = crypto.createHash('sha256').update(uuid).digest();
88
+ return hash.readBigUInt64LE(0);
89
+ }
90
+
91
+ /**
92
+ * Get or create a wallet for a player
93
+ */
94
+ getWallet(playerName) {
95
+ const walletPath = path.join(this.walletsDir, `arcade_${playerName}.json`);
96
+
97
+ if (fs.existsSync(walletPath)) {
98
+ const secretKey = JSON.parse(fs.readFileSync(walletPath, 'utf-8'));
99
+ return Keypair.fromSecretKey(Uint8Array.from(secretKey));
100
+ }
101
+
102
+ const newWallet = Keypair.generate();
103
+ fs.writeFileSync(walletPath, JSON.stringify(Array.from(newWallet.secretKey)));
104
+ console.log(`✅ Created arcade wallet for ${playerName}`);
105
+ return newWallet;
106
+ }
107
+
108
+ /**
109
+ * Fetch match data from chain
110
+ */
111
+ async getMatchData(matchId) {
112
+ try {
113
+ const [matchPDA] = this.getMatchPDA(matchId);
114
+ const accountInfo = await this.connection.getAccountInfo(matchPDA);
115
+
116
+ if (!accountInfo) {
117
+ return null;
118
+ }
119
+
120
+ // Parse match account data (simplified - you may want to use Anchor's IDL parser)
121
+ const data = accountInfo.data;
122
+
123
+ // Basic structure (you'll need to adjust based on actual layout)
124
+ return {
125
+ matchId: matchId,
126
+ pda: matchPDA.toString(),
127
+ exists: true,
128
+ lamports: accountInfo.lamports,
129
+ // Add more parsing as needed
130
+ };
131
+ } catch (error) {
132
+ console.error('Error fetching match data:', error);
133
+ return null;
134
+ }
135
+ }
136
+
137
+ /**
138
+ * Build UNSIGNED transaction for creating a match
139
+ * Returns base64 encoded transaction for user to sign
140
+ */
141
+ async buildCreateMatchTransaction(params) {
142
+ const {
143
+ creatorAddress,
144
+ stake,
145
+ gameType = 'asteroids',
146
+ matchType = 'bestOf1',
147
+ deadlineMinutes = 60,
148
+ } = params;
149
+
150
+ try {
151
+ // Generate UUID for Firebase and human-readable sharing
152
+ const matchUuid = uuidv4();
153
+ // Convert to u64 for on-chain storage
154
+ const matchId = this.uuidToU64(matchUuid);
155
+ const [matchPDA] = this.getMatchPDA(matchId);
156
+
157
+ // Calculate deadline
158
+ const currentTime = Math.floor(Date.now() / 1000);
159
+ const deadline = currentTime + (deadlineMinutes * 60);
160
+
161
+ // Build instruction data
162
+ const matchIdBuf = Buffer.alloc(8);
163
+ matchIdBuf.writeBigUInt64LE(matchId);
164
+
165
+ const stakeBuf = Buffer.alloc(8);
166
+ stakeBuf.writeBigUInt64LE(BigInt(stake));
167
+
168
+ const gameTypeBuf = Buffer.from([gameType === 'asteroids' ? 0 : 1]);
169
+ const matchTypeBuf = Buffer.from([matchType === 'bestOf1' ? 0 : 1]);
170
+
171
+ const deadlineBuf = Buffer.alloc(8);
172
+ deadlineBuf.writeBigInt64LE(BigInt(deadline));
173
+
174
+ const instructionData = Buffer.concat([
175
+ DISCRIMINATORS.CREATE_MATCH,
176
+ matchIdBuf,
177
+ stakeBuf,
178
+ gameTypeBuf,
179
+ matchTypeBuf,
180
+ deadlineBuf,
181
+ ]);
182
+
183
+ const creatorPubkey = new PublicKey(creatorAddress);
184
+
185
+ const instruction = new TransactionInstruction({
186
+ programId: this.programId,
187
+ keys: [
188
+ { pubkey: matchPDA, isSigner: false, isWritable: true },
189
+ { pubkey: creatorPubkey, isSigner: true, isWritable: true },
190
+ { pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
191
+ ],
192
+ data: instructionData,
193
+ });
194
+
195
+ // Build unsigned transaction
196
+ const transaction = new Transaction().add(instruction);
197
+
198
+ // Get recent blockhash
199
+ const { blockhash } = await this.connection.getLatestBlockhash('confirmed');
200
+ transaction.recentBlockhash = blockhash;
201
+ transaction.feePayer = creatorPubkey;
202
+
203
+ // Serialize to base64
204
+ const serialized = transaction.serialize({
205
+ requireAllSignatures: false,
206
+ verifySignatures: false,
207
+ });
208
+
209
+ console.log(`✅ Built create match transaction`);
210
+ console.log(` Match UUID: ${matchUuid}`);
211
+ console.log(` Match PDA: ${matchPDA.toString()}`);
212
+
213
+ return {
214
+ success: true,
215
+ matchId: matchId.toString(), // u64 for on-chain
216
+ matchUuid: matchUuid, // UUID for Firebase/sharing
217
+ matchPDA: matchPDA.toString(),
218
+ transaction: serialized.toString('base64'),
219
+ stake,
220
+ gameType,
221
+ matchType,
222
+ };
223
+ } catch (error) {
224
+ console.error('Error building create match transaction:', error);
225
+ throw error;
226
+ }
227
+ }
228
+
229
+ /**
230
+ * Create a new arcade match (LEGACY - for testing with test wallets)
231
+ */
232
+ async createMatch(params) {
233
+ const {
234
+ matchId,
235
+ creatorName,
236
+ stake,
237
+ gameType = 'asteroids',
238
+ matchType = 'bestOf1',
239
+ deadlineMinutes = 60,
240
+ } = params;
241
+
242
+ try {
243
+ const creator = this.getWallet(creatorName);
244
+ const [matchPDA] = this.getMatchPDA(matchId);
245
+
246
+ // Calculate deadline
247
+ const currentTime = Math.floor(Date.now() / 1000);
248
+ const deadline = currentTime + (deadlineMinutes * 60);
249
+
250
+ // Build instruction data with proper Borsh encoding
251
+ const matchIdNum = typeof matchId === 'string' ? this.uuidToU64(matchId) : BigInt(matchId);
252
+ const matchIdBuf = Buffer.alloc(8);
253
+ matchIdBuf.writeBigUInt64LE(matchIdNum);
254
+
255
+ const stakeBuf = Buffer.alloc(8);
256
+ stakeBuf.writeBigUInt64LE(BigInt(stake));
257
+
258
+ // Borsh enum encoding: variant index (u8) only (no data for these simple enums)
259
+ const gameTypeBuf = Buffer.from([gameType === 'asteroids' ? 0 : 1]);
260
+ const matchTypeBuf = Buffer.from([matchType === 'bestOf1' ? 0 : 1]);
261
+
262
+ const deadlineBuf = Buffer.alloc(8);
263
+ deadlineBuf.writeBigInt64LE(BigInt(deadline));
264
+
265
+ const instructionData = Buffer.concat([
266
+ DISCRIMINATORS.CREATE_MATCH,
267
+ matchIdBuf,
268
+ stakeBuf,
269
+ gameTypeBuf,
270
+ matchTypeBuf,
271
+ deadlineBuf,
272
+ ]);
273
+
274
+ const instruction = new TransactionInstruction({
275
+ programId: this.programId,
276
+ keys: [
277
+ { pubkey: matchPDA, isSigner: false, isWritable: true },
278
+ { pubkey: creator.publicKey, isSigner: true, isWritable: true },
279
+ { pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
280
+ ],
281
+ data: instructionData,
282
+ });
283
+
284
+ const transaction = new Transaction().add(instruction);
285
+ const signature = await this.connection.sendTransaction(transaction, [creator]);
286
+ await this.connection.confirmTransaction(signature);
287
+
288
+ console.log(`✅ Match created: ${matchId}`);
289
+ console.log(` Signature: ${signature}`);
290
+
291
+ return {
292
+ success: true,
293
+ matchId,
294
+ signature,
295
+ matchPDA: matchPDA.toString(),
296
+ creator: creator.publicKey.toString(),
297
+ stake,
298
+ gameType,
299
+ matchType,
300
+ deadline: new Date(deadline * 1000).toISOString(),
301
+ };
302
+ } catch (error) {
303
+ console.error('Error creating match:', error);
304
+ throw error;
305
+ }
306
+ }
307
+
308
+ /**
309
+ * Build UNSIGNED transaction for joining a match
310
+ */
311
+ async buildJoinMatchTransaction(params) {
312
+ const { matchId, opponentAddress } = params;
313
+
314
+ try {
315
+ const matchIdNum = typeof matchId === 'string' ? BigInt(matchId) : BigInt(matchId);
316
+ const [matchPDA] = this.getMatchPDA(matchIdNum);
317
+
318
+ // Build instruction data
319
+ const matchIdBuf = Buffer.alloc(8);
320
+ matchIdBuf.writeBigUInt64LE(matchIdNum);
321
+
322
+ const instructionData = Buffer.concat([
323
+ DISCRIMINATORS.JOIN_MATCH,
324
+ matchIdBuf,
325
+ ]);
326
+
327
+ const opponentPubkey = new PublicKey(opponentAddress);
328
+
329
+ const instruction = new TransactionInstruction({
330
+ programId: this.programId,
331
+ keys: [
332
+ { pubkey: matchPDA, isSigner: false, isWritable: true },
333
+ { pubkey: opponentPubkey, isSigner: true, isWritable: true },
334
+ { pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
335
+ ],
336
+ data: instructionData,
337
+ });
338
+
339
+ // Build unsigned transaction
340
+ const transaction = new Transaction().add(instruction);
341
+
342
+ // Get recent blockhash
343
+ const { blockhash } = await this.connection.getLatestBlockhash('confirmed');
344
+ transaction.recentBlockhash = blockhash;
345
+ transaction.feePayer = opponentPubkey;
346
+
347
+ // Serialize to base64
348
+ const serialized = transaction.serialize({
349
+ requireAllSignatures: false,
350
+ verifySignatures: false,
351
+ });
352
+
353
+ console.log(`✅ Built join match transaction`);
354
+ console.log(` Match PDA: ${matchPDA.toString()}`);
355
+
356
+ return {
357
+ success: true,
358
+ matchId: matchId.toString(),
359
+ transaction: serialized.toString('base64'),
360
+ };
361
+ } catch (error) {
362
+ console.error('Error building join match transaction:', error);
363
+ throw error;
364
+ }
365
+ }
366
+
367
+ /**
368
+ * Build UNSIGNED transaction for submitting a score
369
+ */
370
+ async buildSubmitScoreTransaction(params) {
371
+ const { matchId, playerAddress, score, gameRound = 1 } = params;
372
+
373
+ console.log('🎮 [Backend] Building submit score transaction:');
374
+ console.log(' matchId:', matchId);
375
+ console.log(' playerAddress:', playerAddress);
376
+ console.log(' score:', score);
377
+ console.log(' gameRound:', gameRound, 'Type:', typeof gameRound);
378
+
379
+ try {
380
+ const matchIdNum = typeof matchId === 'string' ? BigInt(matchId) : BigInt(matchId);
381
+ const [matchPDA] = this.getMatchPDA(matchIdNum);
382
+
383
+ // Build instruction data
384
+ const matchIdBuf = Buffer.alloc(8);
385
+ matchIdBuf.writeBigUInt64LE(matchIdNum);
386
+
387
+ const scoreBuf = Buffer.alloc(8);
388
+ scoreBuf.writeBigUInt64LE(BigInt(score));
389
+
390
+ const roundBuf = Buffer.from([gameRound]);
391
+
392
+ const instructionData = Buffer.concat([
393
+ DISCRIMINATORS.SUBMIT_SCORE,
394
+ matchIdBuf,
395
+ scoreBuf,
396
+ roundBuf,
397
+ ]);
398
+
399
+ const playerPubkey = new PublicKey(playerAddress);
400
+
401
+ const instruction = new TransactionInstruction({
402
+ programId: this.programId,
403
+ keys: [
404
+ { pubkey: matchPDA, isSigner: false, isWritable: true },
405
+ { pubkey: playerPubkey, isSigner: true, isWritable: false },
406
+ ],
407
+ data: instructionData,
408
+ });
409
+
410
+ // Build unsigned transaction
411
+ const transaction = new Transaction().add(instruction);
412
+
413
+ // Get recent blockhash
414
+ const { blockhash } = await this.connection.getLatestBlockhash('confirmed');
415
+ transaction.recentBlockhash = blockhash;
416
+ transaction.feePayer = playerPubkey;
417
+
418
+ // Serialize to base64
419
+ const serialized = transaction.serialize({
420
+ requireAllSignatures: false,
421
+ verifySignatures: false,
422
+ });
423
+
424
+ console.log(`✅ Built submit score transaction`);
425
+ console.log(` Match PDA: ${matchPDA.toString()}`);
426
+ console.log(` Score: ${score}, Round: ${gameRound}`);
427
+
428
+ return {
429
+ success: true,
430
+ matchId: matchId.toString(),
431
+ transaction: serialized.toString('base64'),
432
+ };
433
+ } catch (error) {
434
+ console.error('Error building submit score transaction:', error);
435
+ throw error;
436
+ }
437
+ }
438
+
439
+ /**
440
+ * Join an existing match (LEGACY - for testing with test wallets)
441
+ */
442
+ async joinMatch(params) {
443
+ const { matchId, opponentName } = params;
444
+
445
+ try {
446
+ const opponent = this.getWallet(opponentName);
447
+ const [matchPDA] = this.getMatchPDA(matchId);
448
+
449
+ // Build instruction data
450
+ const matchIdNum = typeof matchId === 'string' ? this.uuidToU64(matchId) : BigInt(matchId);
451
+ const matchIdBuf = Buffer.alloc(8);
452
+ matchIdBuf.writeBigUInt64LE(matchIdNum);
453
+
454
+ const instructionData = Buffer.concat([
455
+ DISCRIMINATORS.JOIN_MATCH,
456
+ matchIdBuf,
457
+ ]);
458
+
459
+ const instruction = new TransactionInstruction({
460
+ programId: this.programId,
461
+ keys: [
462
+ { pubkey: matchPDA, isSigner: false, isWritable: true },
463
+ { pubkey: opponent.publicKey, isSigner: true, isWritable: true },
464
+ { pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
465
+ ],
466
+ data: instructionData,
467
+ });
468
+
469
+ const transaction = new Transaction().add(instruction);
470
+ const signature = await this.connection.sendTransaction(transaction, [opponent]);
471
+ await this.connection.confirmTransaction(signature);
472
+
473
+ console.log(`✅ Player joined match: ${matchId}`);
474
+ console.log(` Signature: ${signature}`);
475
+
476
+ return {
477
+ success: true,
478
+ matchId,
479
+ signature,
480
+ opponent: opponent.publicKey.toString(),
481
+ };
482
+ } catch (error) {
483
+ console.error('Error joining match:', error);
484
+ throw error;
485
+ }
486
+ }
487
+
488
+ /**
489
+ * Submit a score for a match
490
+ */
491
+ async submitScore(params) {
492
+ const { matchId, playerName, score, gameRound = 1 } = params;
493
+
494
+ try {
495
+ const player = this.getWallet(playerName);
496
+ const [matchPDA] = this.getMatchPDA(matchId);
497
+
498
+ // Build instruction data
499
+ const matchIdNum = typeof matchId === 'string' ? this.uuidToU64(matchId) : BigInt(matchId);
500
+ const matchIdBuf = Buffer.alloc(8);
501
+ matchIdBuf.writeBigUInt64LE(matchIdNum);
502
+
503
+ const scoreBuf = Buffer.alloc(8);
504
+ scoreBuf.writeBigUInt64LE(BigInt(score));
505
+
506
+ const roundBuf = Buffer.alloc(1);
507
+ roundBuf.writeUInt8(gameRound);
508
+
509
+ const instructionData = Buffer.concat([
510
+ DISCRIMINATORS.SUBMIT_SCORE,
511
+ matchIdBuf,
512
+ scoreBuf,
513
+ roundBuf,
514
+ ]);
515
+
516
+ const instruction = new TransactionInstruction({
517
+ programId: this.programId,
518
+ keys: [
519
+ { pubkey: matchPDA, isSigner: false, isWritable: true },
520
+ { pubkey: player.publicKey, isSigner: true, isWritable: false },
521
+ ],
522
+ data: instructionData,
523
+ });
524
+
525
+ const transaction = new Transaction().add(instruction);
526
+ const signature = await this.connection.sendTransaction(transaction, [player]);
527
+ await this.connection.confirmTransaction(signature);
528
+
529
+ console.log(`✅ Score submitted: ${playerName} scored ${score} in round ${gameRound}`);
530
+ console.log(` Signature: ${signature}`);
531
+
532
+ return {
533
+ success: true,
534
+ matchId,
535
+ signature,
536
+ player: player.publicKey.toString(),
537
+ score,
538
+ gameRound,
539
+ };
540
+ } catch (error) {
541
+ console.error('Error submitting score:', error);
542
+ throw error;
543
+ }
544
+ }
545
+
546
+ /**
547
+ * Build UNSIGNED transaction for resolving a match
548
+ */
549
+ async buildResolveMatchTransaction(params) {
550
+ const { matchId, callerAddress, creatorAddress, opponentAddress } = params;
551
+
552
+ try {
553
+ const matchIdNum = typeof matchId === 'string' ? BigInt(matchId) : BigInt(matchId);
554
+ const [matchPDA] = this.getMatchPDA(matchIdNum);
555
+
556
+ // Build instruction data
557
+ const matchIdBuf = Buffer.alloc(8);
558
+ matchIdBuf.writeBigUInt64LE(matchIdNum);
559
+
560
+ const instructionData = Buffer.concat([
561
+ DISCRIMINATORS.RESOLVE_MATCH,
562
+ matchIdBuf,
563
+ ]);
564
+
565
+ const callerPubkey = new PublicKey(callerAddress);
566
+ const creatorPubkey = new PublicKey(creatorAddress);
567
+ const opponentPubkey = new PublicKey(opponentAddress);
568
+
569
+ const instruction = new TransactionInstruction({
570
+ programId: this.programId,
571
+ keys: [
572
+ { pubkey: matchPDA, isSigner: false, isWritable: true },
573
+ { pubkey: callerPubkey, isSigner: true, isWritable: false }, // Caller (anyone - pays gas)
574
+ // Remaining accounts for payouts: [0]=Operator, [1]=Creator, [2]=Opponent
575
+ { pubkey: this.operatorWallet, isSigner: false, isWritable: true },
576
+ { pubkey: creatorPubkey, isSigner: false, isWritable: true },
577
+ { pubkey: opponentPubkey, isSigner: false, isWritable: true },
578
+ ],
579
+ data: instructionData,
580
+ });
581
+
582
+ // Build unsigned transaction
583
+ const transaction = new Transaction().add(instruction);
584
+
585
+ // Get recent blockhash
586
+ const { blockhash } = await this.connection.getLatestBlockhash('confirmed');
587
+ transaction.recentBlockhash = blockhash;
588
+ transaction.feePayer = callerPubkey; // Caller pays gas
589
+
590
+ // Serialize to base64
591
+ const serialized = transaction.serialize({
592
+ requireAllSignatures: false,
593
+ verifySignatures: false,
594
+ });
595
+
596
+ console.log(`✅ Built resolve match transaction`);
597
+ console.log(` Match PDA: ${matchPDA.toString()}`);
598
+
599
+ return {
600
+ success: true,
601
+ matchId: matchId.toString(),
602
+ transaction: serialized.toString('base64'),
603
+ };
604
+ } catch (error) {
605
+ console.error('Error building resolve match transaction:', error);
606
+ throw error;
607
+ }
608
+ }
609
+
610
+ /**
611
+ * Resolve a match and distribute winnings (LEGACY)
612
+ */
613
+ async resolveMatch(params) {
614
+ const { matchId, callerName } = params;
615
+
616
+ try {
617
+ const caller = this.getWallet(callerName);
618
+ const [matchPDA] = this.getMatchPDA(matchId);
619
+
620
+ // Fetch match data to determine winner and opponent
621
+ const matchData = await this.getMatchData(matchId);
622
+ if (!matchData) {
623
+ throw new Error('Match not found');
624
+ }
625
+
626
+ // Build instruction data
627
+ const matchIdNum = typeof matchId === 'string' ? this.uuidToU64(matchId) : BigInt(matchId);
628
+ const matchIdBuf = Buffer.alloc(8);
629
+ matchIdBuf.writeBigUInt64LE(matchIdNum);
630
+
631
+ const instructionData = Buffer.concat([
632
+ DISCRIMINATORS.RESOLVE_MATCH,
633
+ matchIdBuf,
634
+ ]);
635
+
636
+ // Build accounts - need to include operator and opponent in remaining_accounts
637
+ const instruction = new TransactionInstruction({
638
+ programId: this.programId,
639
+ keys: [
640
+ { pubkey: matchPDA, isSigner: false, isWritable: true },
641
+ { pubkey: caller.publicKey, isSigner: true, isWritable: true },
642
+ // Remaining accounts (operator + opponent)
643
+ { pubkey: this.operatorWallet, isSigner: false, isWritable: true },
644
+ // Note: You'll need to fetch opponent pubkey from match data
645
+ // { pubkey: opponentPubkey, isSigner: false, isWritable: true },
646
+ ],
647
+ data: instructionData,
648
+ });
649
+
650
+ const transaction = new Transaction().add(instruction);
651
+ const signature = await this.connection.sendTransaction(transaction, [caller]);
652
+ await this.connection.confirmTransaction(signature);
653
+
654
+ console.log(`✅ Match resolved: ${matchId}`);
655
+ console.log(` Signature: ${signature}`);
656
+
657
+ return {
658
+ success: true,
659
+ matchId,
660
+ signature,
661
+ };
662
+ } catch (error) {
663
+ console.error('Error resolving match:', error);
664
+ throw error;
665
+ }
666
+ }
667
+
668
+ /**
669
+ * Cancel a match (if no opponent joined)
670
+ */
671
+ async cancelMatch(params) {
672
+ const { matchId, creatorName } = params;
673
+
674
+ try {
675
+ const creator = this.getWallet(creatorName);
676
+ const [matchPDA] = this.getMatchPDA(matchId);
677
+
678
+ // Build instruction data
679
+ const matchIdNum = typeof matchId === 'string' ? this.uuidToU64(matchId) : BigInt(matchId);
680
+ const matchIdBuf = Buffer.alloc(8);
681
+ matchIdBuf.writeBigUInt64LE(matchIdNum);
682
+
683
+ const instructionData = Buffer.concat([
684
+ DISCRIMINATORS.CANCEL_MATCH,
685
+ matchIdBuf,
686
+ ]);
687
+
688
+ const instruction = new TransactionInstruction({
689
+ programId: this.programId,
690
+ keys: [
691
+ { pubkey: matchPDA, isSigner: false, isWritable: true },
692
+ { pubkey: creator.publicKey, isSigner: true, isWritable: true },
693
+ ],
694
+ data: instructionData,
695
+ });
696
+
697
+ const transaction = new Transaction().add(instruction);
698
+ const signature = await this.connection.sendTransaction(transaction, [creator]);
699
+ await this.connection.confirmTransaction(signature);
700
+
701
+ console.log(`✅ Match cancelled: ${matchId}`);
702
+ console.log(` Signature: ${signature}`);
703
+
704
+ return {
705
+ success: true,
706
+ matchId,
707
+ signature,
708
+ };
709
+ } catch (error) {
710
+ console.error('Error cancelling match:', error);
711
+ throw error;
712
+ }
713
+ }
714
+
715
+ /**
716
+ * Airdrop SOL to a wallet (for testing)
717
+ */
718
+ async airdrop(playerName, amount = 1) {
719
+ try {
720
+ const wallet = this.getWallet(playerName);
721
+ const signature = await this.connection.requestAirdrop(
722
+ wallet.publicKey,
723
+ amount * 1_000_000_000
724
+ );
725
+ await this.connection.confirmTransaction(signature);
726
+
727
+ console.log(`✅ Airdropped ${amount} SOL to ${playerName}`);
728
+ return {
729
+ success: true,
730
+ player: playerName,
731
+ pubkey: wallet.publicKey.toString(),
732
+ amount,
733
+ signature,
734
+ };
735
+ } catch (error) {
736
+ console.error('Error airdropping:', error);
737
+ throw error;
738
+ }
739
+ }
740
+
741
+ /**
742
+ * Get wallet balance
743
+ */
744
+ async getBalance(playerName) {
745
+ try {
746
+ const wallet = this.getWallet(playerName);
747
+ const balance = await this.connection.getBalance(wallet.publicKey);
748
+
749
+ return {
750
+ player: playerName,
751
+ pubkey: wallet.publicKey.toString(),
752
+ balance: balance / 1_000_000_000,
753
+ lamports: balance,
754
+ };
755
+ } catch (error) {
756
+ console.error('Error getting balance:', error);
757
+ throw error;
758
+ }
759
+ }
760
+ }
761
+
762
+ module.exports = ArcadeMatchService;
763
+