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,197 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Authority Force Lock Script
4
+ * Uses program authority to force-lock games so oracle can resolve them
5
+ *
6
+ * Usage:
7
+ * SOLANA_RPC_URL=<mainnet-rpc> node scripts/authority-force-lock.js <gameId>
8
+ *
9
+ * Examples:
10
+ * SOLANA_RPC_URL="https://..." node scripts/authority-force-lock.js sport-1769102132440-xmirk1omx
11
+ */
12
+
13
+ require('dotenv').config();
14
+ const {
15
+ Connection,
16
+ PublicKey,
17
+ Keypair,
18
+ Transaction,
19
+ TransactionInstruction,
20
+ } = require('@solana/web3.js');
21
+ const fs = require('fs');
22
+ const path = require('path');
23
+ const crypto = require('crypto');
24
+
25
+ // Program ID (mainnet)
26
+ const PROGRAM_ID = new PublicKey('85wJGp9uc8w2FeKX9CEHsudTo1UVCrmuRFy37oCcaoG1');
27
+
28
+ // authority_force_lock discriminator
29
+ // sha256("global:authority_force_lock")[0..8]
30
+ const AUTHORITY_FORCE_LOCK_DISCRIMINATOR = Buffer.from([
31
+ 186, 206, 107, 11, 185, 154, 217, 166
32
+ ]);
33
+
34
+ // RPC endpoint - use mainnet
35
+ const RPC_URL = process.env.SOLANA_RPC_URL || 'https://solana-mainnet.g.alchemy.com/v2/M7pyy3QL4xYOndpcYukNhf-yq2IG6eyl';
36
+
37
+ console.log('🔐 Authority Force Lock Tool');
38
+ console.log('============================');
39
+ console.log(`RPC: ${RPC_URL.slice(0, 50)}...`);
40
+ console.log(`Program: ${PROGRAM_ID.toString()}`);
41
+ console.log('');
42
+
43
+ const connection = new Connection(RPC_URL, 'confirmed');
44
+
45
+ /**
46
+ * Load authority wallet (program upgrade authority)
47
+ */
48
+ function loadAuthorityWallet() {
49
+ const walletPath = process.env.AUTHORITY_WALLET_PATH ||
50
+ path.join(process.env.HOME, '.config/solana/id.json');
51
+
52
+ if (!fs.existsSync(walletPath)) {
53
+ console.error('❌ Authority wallet not found at:', walletPath);
54
+ process.exit(1);
55
+ }
56
+
57
+ console.log('🔑 Loading authority wallet from:', walletPath);
58
+ const secretKey = JSON.parse(fs.readFileSync(walletPath, 'utf-8'));
59
+ return Keypair.fromSecretKey(Uint8Array.from(secretKey));
60
+ }
61
+
62
+ /**
63
+ * Get game PDA and u64 ID from string game ID
64
+ */
65
+ function getGamePDA(gameIdString) {
66
+ const hash = crypto.createHash('sha256').update(gameIdString).digest();
67
+ const gameIdNum = hash.readBigUInt64LE(0);
68
+ const gameIdBuf = Buffer.alloc(8);
69
+ gameIdBuf.writeBigUInt64LE(gameIdNum);
70
+
71
+ const [gamePDA] = PublicKey.findProgramAddressSync(
72
+ [Buffer.from('game'), gameIdBuf],
73
+ PROGRAM_ID
74
+ );
75
+
76
+ return { gamePDA, gameIdNum, gameIdBuf };
77
+ }
78
+
79
+ /**
80
+ * Build authority_force_lock instruction
81
+ */
82
+ function buildAuthorityForceLockInstruction(gamePDA, gameIdBuf, authorityWallet) {
83
+ const data = Buffer.concat([
84
+ AUTHORITY_FORCE_LOCK_DISCRIMINATOR,
85
+ gameIdBuf,
86
+ ]);
87
+
88
+ return new TransactionInstruction({
89
+ keys: [
90
+ { pubkey: gamePDA, isSigner: false, isWritable: true },
91
+ { pubkey: authorityWallet.publicKey, isSigner: true, isWritable: true },
92
+ ],
93
+ programId: PROGRAM_ID,
94
+ data,
95
+ });
96
+ }
97
+
98
+ /**
99
+ * Poll for transaction confirmation
100
+ */
101
+ async function confirmTransaction(signature, timeout = 60000) {
102
+ const start = Date.now();
103
+
104
+ while (Date.now() - start < timeout) {
105
+ const statuses = await connection.getSignatureStatuses([signature]);
106
+ const status = statuses?.value?.[0];
107
+
108
+ if (status?.err) {
109
+ throw new Error(`Transaction failed: ${JSON.stringify(status.err)}`);
110
+ }
111
+
112
+ if (status?.confirmationStatus === 'confirmed' || status?.confirmationStatus === 'finalized') {
113
+ return status;
114
+ }
115
+
116
+ await new Promise(resolve => setTimeout(resolve, 1000));
117
+ }
118
+
119
+ throw new Error('Transaction confirmation timeout');
120
+ }
121
+
122
+ async function forceLockGame(gameIdString) {
123
+ console.log(`📍 Game ID: ${gameIdString}`);
124
+ console.log('');
125
+
126
+ // Load authority wallet
127
+ const authorityWallet = loadAuthorityWallet();
128
+ console.log(` Authority: ${authorityWallet.publicKey.toString()}`);
129
+
130
+ // Get game PDA
131
+ const { gamePDA, gameIdNum, gameIdBuf } = getGamePDA(gameIdString);
132
+ console.log(` Game PDA: ${gamePDA.toString()}`);
133
+ console.log(` Game ID (u64): ${gameIdNum}`);
134
+
135
+ // Check if game exists
136
+ const gameAccount = await connection.getAccountInfo(gamePDA);
137
+ if (!gameAccount) {
138
+ console.error('❌ Game account not found on-chain!');
139
+ process.exit(1);
140
+ }
141
+ console.log(` Game balance: ${gameAccount.lamports / 1e9} SOL`);
142
+ console.log('');
143
+
144
+ // Build instruction
145
+ const instruction = buildAuthorityForceLockInstruction(gamePDA, gameIdBuf, authorityWallet);
146
+
147
+ // Build transaction
148
+ const latestBlockhash = await connection.getLatestBlockhash('confirmed');
149
+ const transaction = new Transaction();
150
+ transaction.recentBlockhash = latestBlockhash.blockhash;
151
+ transaction.lastValidBlockHeight = latestBlockhash.lastValidBlockHeight;
152
+ transaction.feePayer = authorityWallet.publicKey;
153
+ transaction.add(instruction);
154
+
155
+ // Sign and send
156
+ transaction.sign(authorityWallet);
157
+
158
+ console.log('📡 Sending transaction...');
159
+ try {
160
+ const signature = await connection.sendRawTransaction(transaction.serialize(), {
161
+ skipPreflight: false,
162
+ preflightCommitment: 'confirmed',
163
+ });
164
+
165
+ console.log(` Signature: ${signature}`);
166
+ console.log(' Waiting for confirmation...');
167
+
168
+ await confirmTransaction(signature);
169
+
170
+ console.log('');
171
+ console.log('✅ Game force-locked successfully!');
172
+ console.log(` Explorer: https://explorer.solana.com/tx/${signature}`);
173
+ console.log('');
174
+ console.log('🔄 The oracle should now be able to resolve this game.');
175
+
176
+ } catch (error) {
177
+ console.error('');
178
+ console.error('❌ Transaction failed:', error.message);
179
+ if (error.logs) {
180
+ console.log('📋 Logs:');
181
+ error.logs.forEach(log => console.log(' ', log));
182
+ }
183
+ process.exit(1);
184
+ }
185
+ }
186
+
187
+ // Main
188
+ const gameId = process.argv[2];
189
+ if (!gameId) {
190
+ console.log('Usage: node scripts/authority-force-lock.js <gameId>');
191
+ console.log('');
192
+ console.log('Example:');
193
+ console.log(' SOLANA_RPC_URL="https://..." node scripts/authority-force-lock.js sport-1769102132440-xmirk1omx');
194
+ process.exit(1);
195
+ }
196
+
197
+ forceLockGame(gameId);
@@ -0,0 +1,267 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Authority Emergency Resolve Script
4
+ * Uses program authority to resolve postponed/stuck games
5
+ *
6
+ * Usage:
7
+ * node authority-resolve-game.js <gameId> [winner]
8
+ *
9
+ * Examples:
10
+ * node authority-resolve-game.js sport-1769366095299-m1h01fvt9 # Refund all (no winner)
11
+ * node authority-resolve-game.js sport-1769366095299-m1h01fvt9 home # Home wins
12
+ */
13
+
14
+ require('dotenv').config();
15
+ const {
16
+ Connection,
17
+ PublicKey,
18
+ Keypair,
19
+ Transaction,
20
+ TransactionInstruction,
21
+ LAMPORTS_PER_SOL
22
+ } = require('@solana/web3.js');
23
+ const fs = require('fs');
24
+ const path = require('path');
25
+ const crypto = require('crypto');
26
+
27
+ // Program ID (mainnet)
28
+ const PROGRAM_ID = new PublicKey('85wJGp9uc8w2FeKX9CEHsudTo1UVCrmuRFy37oCcaoG1');
29
+
30
+ // Operator wallet (receives fees)
31
+ const OPERATOR_WALLET = new PublicKey('BVZXwZpfgyzTBdRFHohkHZppPHnAyqyctRsKy3vWfQib');
32
+
33
+ // authority_force_refund discriminator (no lock check!)
34
+ const AUTHORITY_FORCE_REFUND_DISCRIMINATOR = Buffer.from([49, 187, 216, 119, 254, 50, 118, 225]);
35
+
36
+ // authority_emergency_resolve discriminator (requires lock)
37
+ const AUTHORITY_EMERGENCY_RESOLVE_DISCRIMINATOR = Buffer.from([31, 157, 128, 122, 207, 17, 218, 155]);
38
+
39
+ // RPC endpoint - use mainnet
40
+ const RPC_URL = process.env.SOLANA_RPC_URL || 'https://solana-mainnet.g.alchemy.com/v2/M7pyy3QL4xYOndpcYukNhf-yq2IG6eyl';
41
+
42
+ console.log('🔧 Authority Emergency Resolve Tool');
43
+ console.log('====================================');
44
+ console.log(`RPC: ${RPC_URL.slice(0, 50)}...`);
45
+ console.log(`Program: ${PROGRAM_ID.toString()}`);
46
+ console.log('');
47
+
48
+ const connection = new Connection(RPC_URL, 'confirmed');
49
+
50
+ /**
51
+ * Load authority wallet (program upgrade authority)
52
+ */
53
+ function loadAuthorityWallet() {
54
+ const walletPath = process.env.AUTHORITY_WALLET_PATH ||
55
+ path.join(process.env.HOME, '.config/solana/id.json');
56
+
57
+ if (!fs.existsSync(walletPath)) {
58
+ console.error('❌ Authority wallet not found at:', walletPath);
59
+ process.exit(1);
60
+ }
61
+
62
+ console.log('🔑 Loading authority wallet from:', walletPath);
63
+ const secretKey = JSON.parse(fs.readFileSync(walletPath, 'utf-8'));
64
+ return Keypair.fromSecretKey(Uint8Array.from(secretKey));
65
+ }
66
+
67
+ /**
68
+ * Get game PDA and u64 ID from string game ID
69
+ */
70
+ function getGamePDA(gameIdString) {
71
+ const hash = crypto.createHash('sha256').update(gameIdString).digest();
72
+ const gameIdNum = hash.readBigUInt64LE(0);
73
+ const gameIdBuf = Buffer.alloc(8);
74
+ gameIdBuf.writeBigUInt64LE(gameIdNum);
75
+
76
+ const [gamePDA] = PublicKey.findProgramAddressSync(
77
+ [Buffer.from('game'), gameIdBuf],
78
+ PROGRAM_ID
79
+ );
80
+
81
+ return { gamePDA, gameIdNum, gameIdBuf };
82
+ }
83
+
84
+ /**
85
+ * Build authority_force_refund instruction (no lock check!)
86
+ */
87
+ function buildAuthorityForceRefundInstruction(gamePDA, gameIdBuf, authorityWallet) {
88
+ const data = Buffer.concat([
89
+ AUTHORITY_FORCE_REFUND_DISCRIMINATOR,
90
+ gameIdBuf,
91
+ ]);
92
+
93
+ return new TransactionInstruction({
94
+ keys: [
95
+ { pubkey: gamePDA, isSigner: false, isWritable: true },
96
+ { pubkey: authorityWallet.publicKey, isSigner: true, isWritable: true },
97
+ ],
98
+ programId: PROGRAM_ID,
99
+ data,
100
+ });
101
+ }
102
+
103
+ /**
104
+ * Build authority_emergency_resolve instruction (requires lock)
105
+ */
106
+ function buildAuthorityResolveInstruction(gamePDA, gameIdBuf, authorityWallet, winningTeam) {
107
+ let winningTeamBuf;
108
+ if (winningTeam === 'home') {
109
+ winningTeamBuf = Buffer.from([1, 0]); // Some(Home)
110
+ } else if (winningTeam === 'away') {
111
+ winningTeamBuf = Buffer.from([1, 1]); // Some(Away)
112
+ } else if (winningTeam === 'draw') {
113
+ winningTeamBuf = Buffer.from([1, 2]); // Some(Draw)
114
+ } else {
115
+ winningTeamBuf = Buffer.from([0]); // None - refund all
116
+ }
117
+
118
+ const data = Buffer.concat([
119
+ AUTHORITY_EMERGENCY_RESOLVE_DISCRIMINATOR,
120
+ gameIdBuf,
121
+ winningTeamBuf,
122
+ ]);
123
+
124
+ return new TransactionInstruction({
125
+ keys: [
126
+ { pubkey: gamePDA, isSigner: false, isWritable: true },
127
+ { pubkey: authorityWallet.publicKey, isSigner: true, isWritable: true },
128
+ ],
129
+ programId: PROGRAM_ID,
130
+ data,
131
+ });
132
+ }
133
+
134
+ /**
135
+ * Poll for transaction confirmation
136
+ */
137
+ async function confirmTransaction(signature, timeout = 60000) {
138
+ const start = Date.now();
139
+
140
+ while (Date.now() - start < timeout) {
141
+ const statuses = await connection.getSignatureStatuses([signature]);
142
+ const status = statuses?.value?.[0];
143
+
144
+ if (status?.err) {
145
+ throw new Error(`Transaction failed: ${JSON.stringify(status.err)}`);
146
+ }
147
+
148
+ if (status?.confirmationStatus === 'confirmed' || status?.confirmationStatus === 'finalized') {
149
+ return status;
150
+ }
151
+
152
+ await new Promise(resolve => setTimeout(resolve, 1000));
153
+ }
154
+
155
+ throw new Error('Transaction confirmation timeout');
156
+ }
157
+
158
+ /**
159
+ * Main execution
160
+ */
161
+ async function main() {
162
+ const args = process.argv.slice(2);
163
+
164
+ if (args.length < 1) {
165
+ console.log('Usage: node authority-resolve-game.js <gameId> [winner]');
166
+ console.log('');
167
+ console.log('Arguments:');
168
+ console.log(' gameId - The game ID string (e.g., sport-1769366095299-m1h01fvt9)');
169
+ console.log(' winner - Optional: home, away, draw (default: refund all)');
170
+ console.log('');
171
+ console.log('Examples:');
172
+ console.log(' node authority-resolve-game.js sport-1769366095299-m1h01fvt9');
173
+ console.log(' node authority-resolve-game.js sport-1769366095299-m1h01fvt9 home');
174
+ process.exit(1);
175
+ }
176
+
177
+ const gameIdString = args[0];
178
+ const winningTeam = args[1] || null;
179
+
180
+ console.log(`📍 Game ID: ${gameIdString}`);
181
+ console.log(`🏆 Winner: ${winningTeam || 'None (refund all)'}`);
182
+ console.log('');
183
+
184
+ // Load authority wallet
185
+ const authorityWallet = loadAuthorityWallet();
186
+ console.log(` Authority: ${authorityWallet.publicKey.toString()}`);
187
+
188
+ // Verify it matches program authority
189
+ const expectedAuthority = '57voP1Y8U4ztX2YAcHveK3JFvVRn1n6T6iUnHdAW6xr9';
190
+ if (authorityWallet.publicKey.toString() !== expectedAuthority) {
191
+ console.error(`❌ Wallet doesn't match program authority!`);
192
+ console.error(` Expected: ${expectedAuthority}`);
193
+ console.error(` Got: ${authorityWallet.publicKey.toString()}`);
194
+ process.exit(1);
195
+ }
196
+ console.log(' ✅ Authority verified');
197
+
198
+ // Get game PDA
199
+ const { gamePDA, gameIdNum, gameIdBuf } = getGamePDA(gameIdString);
200
+ console.log(` Game PDA: ${gamePDA.toString()}`);
201
+ console.log(` Game ID (u64): ${gameIdNum.toString()}`);
202
+
203
+ // Check if game exists on-chain
204
+ const gameAccount = await connection.getAccountInfo(gamePDA);
205
+ if (!gameAccount) {
206
+ console.error('❌ Game account not found on-chain!');
207
+ process.exit(1);
208
+ }
209
+ console.log(` Game balance: ${gameAccount.lamports / LAMPORTS_PER_SOL} SOL`);
210
+
211
+ // Build instruction based on whether we're refunding or declaring winner
212
+ let ix;
213
+ if (!winningTeam) {
214
+ // Use authority_force_refund (no lock check needed!)
215
+ console.log('');
216
+ console.log('📋 Using authority_force_refund (bypasses lock check)');
217
+ ix = buildAuthorityForceRefundInstruction(gamePDA, gameIdBuf, authorityWallet);
218
+ } else {
219
+ // Use authority_emergency_resolve (requires lock)
220
+ console.log('');
221
+ console.log('📋 Using authority_emergency_resolve (requires game to be locked)');
222
+ ix = buildAuthorityResolveInstruction(gamePDA, gameIdBuf, authorityWallet, winningTeam);
223
+ // Add operator wallet to remaining accounts for fee transfer
224
+ ix.keys.push({ pubkey: OPERATOR_WALLET, isSigner: false, isWritable: true });
225
+ }
226
+
227
+ // Build transaction
228
+ const tx = new Transaction().add(ix);
229
+ const { blockhash } = await connection.getLatestBlockhash();
230
+ tx.recentBlockhash = blockhash;
231
+ tx.feePayer = authorityWallet.publicKey;
232
+ tx.sign(authorityWallet);
233
+
234
+ console.log('');
235
+ console.log('📡 Sending transaction...');
236
+
237
+ try {
238
+ const signature = await connection.sendRawTransaction(tx.serialize(), {
239
+ skipPreflight: false,
240
+ preflightCommitment: 'confirmed',
241
+ });
242
+
243
+ console.log(` Signature: ${signature}`);
244
+ console.log(' ⏳ Confirming...');
245
+
246
+ await confirmTransaction(signature);
247
+
248
+ console.log('');
249
+ console.log('✅ Game resolved successfully!');
250
+ console.log(` Signature: ${signature}`);
251
+ console.log('');
252
+ console.log('💡 Players can now claim their refunds using the Claim button in the app.');
253
+
254
+ } catch (error) {
255
+ console.error('');
256
+ console.error('❌ Transaction failed:', error.message);
257
+
258
+ if (error.logs) {
259
+ console.error('📋 Logs:');
260
+ error.logs.forEach(log => console.error(' ', log));
261
+ }
262
+
263
+ process.exit(1);
264
+ }
265
+ }
266
+
267
+ main().catch(console.error);