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,267 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Test Script: Poll Transaction Confirmation
4
+ *
5
+ * Tests the Alchemy-compatible polling confirmation approach
6
+ * that uses getSignatureStatuses instead of WebSocket subscriptions.
7
+ *
8
+ * Usage:
9
+ * node test-poll-confirmation.js # Test with a known confirmed tx
10
+ * node test-poll-confirmation.js <signature> # Test with specific signature
11
+ */
12
+
13
+ require('dotenv').config();
14
+ const { Connection } = require('@solana/web3.js');
15
+
16
+ // Configuration
17
+ const RPC_URL = process.env.SOLANA_RPC_URL || process.env.SOLANA_NETWORK || 'https://api.devnet.solana.com';
18
+
19
+ console.log('🧪 Poll Transaction Confirmation Test');
20
+ console.log('=====================================');
21
+ console.log(`RPC URL: ${RPC_URL}`);
22
+ console.log('');
23
+
24
+ const connection = new Connection(RPC_URL, 'confirmed');
25
+
26
+ /**
27
+ * Poll for transaction confirmation using getSignatureStatuses
28
+ * This is the Alchemy-compatible approach (no WebSocket subscriptions)
29
+ */
30
+ async function pollTransactionConfirmation(signature, lastValidBlockHeight = null, timeout = 30000) {
31
+ const start = Date.now();
32
+ console.log(`🔄 Polling confirmation for: ${signature}`);
33
+ if (lastValidBlockHeight) {
34
+ console.log(` lastValidBlockHeight: ${lastValidBlockHeight}`);
35
+ }
36
+
37
+ let pollCount = 0;
38
+ while (Date.now() - start < timeout) {
39
+ pollCount++;
40
+ try {
41
+ // Check if blockhash has expired (if we have lastValidBlockHeight)
42
+ if (lastValidBlockHeight) {
43
+ const currentBlockHeight = await connection.getBlockHeight('confirmed');
44
+ console.log(` [Poll ${pollCount}] Current block height: ${currentBlockHeight}`);
45
+ if (currentBlockHeight > lastValidBlockHeight) {
46
+ throw new Error(`Transaction expired: blockhash no longer valid (current: ${currentBlockHeight}, lastValid: ${lastValidBlockHeight})`);
47
+ }
48
+ }
49
+
50
+ // Poll signature status
51
+ const response = await connection.getSignatureStatuses([signature], {
52
+ searchTransactionHistory: true
53
+ });
54
+
55
+ const status = response?.value?.[0];
56
+ if (status) {
57
+ console.log(` [Poll ${pollCount}] Status: ${JSON.stringify(status)}`);
58
+
59
+ // Check for error
60
+ if (status.err) {
61
+ throw new Error(`Transaction failed: ${JSON.stringify(status.err)}`);
62
+ }
63
+
64
+ // Check confirmation level
65
+ if (status.confirmationStatus === 'confirmed' || status.confirmationStatus === 'finalized') {
66
+ console.log(`✅ Transaction confirmed: ${status.confirmationStatus}`);
67
+ return status;
68
+ }
69
+ } else {
70
+ console.log(` [Poll ${pollCount}] Status: null (not found yet)`);
71
+ }
72
+ } catch (pollErr) {
73
+ // If it's a fatal error, rethrow
74
+ if (pollErr.message?.includes('expired') || pollErr.message?.includes('failed')) {
75
+ throw pollErr;
76
+ }
77
+ console.warn(` [Poll ${pollCount}] Error (will retry): ${pollErr.message}`);
78
+ }
79
+
80
+ // Wait before next poll
81
+ await new Promise(resolve => setTimeout(resolve, 1000));
82
+ }
83
+
84
+ throw new Error(`Transaction confirmation timeout after ${timeout}ms`);
85
+ }
86
+
87
+ /**
88
+ * Test 1: Confirm a known finalized transaction
89
+ */
90
+ async function testKnownTransaction(signature) {
91
+ console.log('\n📋 Test 1: Confirm known transaction');
92
+ console.log('------------------------------------');
93
+
94
+ try {
95
+ const status = await pollTransactionConfirmation(signature, null, 10000);
96
+ console.log(`\n✅ TEST PASSED: Transaction confirmed`);
97
+ console.log(` Slot: ${status.slot}`);
98
+ console.log(` Confirmations: ${status.confirmations}`);
99
+ console.log(` Status: ${status.confirmationStatus}`);
100
+ return true;
101
+ } catch (error) {
102
+ console.error(`\n❌ TEST FAILED: ${error.message}`);
103
+ return false;
104
+ }
105
+ }
106
+
107
+ /**
108
+ * Test 2: Verify getSignatureStatuses works with searchTransactionHistory
109
+ */
110
+ async function testSearchTransactionHistory(signature) {
111
+ console.log('\n📋 Test 2: searchTransactionHistory parameter');
112
+ console.log('---------------------------------------------');
113
+
114
+ try {
115
+ // Without searchTransactionHistory (might return null for old txs)
116
+ console.log(' Testing WITHOUT searchTransactionHistory...');
117
+ const responseWithout = await connection.getSignatureStatuses([signature]);
118
+ console.log(` Result: ${responseWithout?.value?.[0] ? 'Found' : 'Not found (null)'}`);
119
+
120
+ // With searchTransactionHistory
121
+ console.log(' Testing WITH searchTransactionHistory...');
122
+ const responseWith = await connection.getSignatureStatuses([signature], {
123
+ searchTransactionHistory: true
124
+ });
125
+ console.log(` Result: ${responseWith?.value?.[0] ? 'Found' : 'Not found (null)'}`);
126
+
127
+ if (responseWith?.value?.[0]) {
128
+ console.log(`\n✅ TEST PASSED: searchTransactionHistory works`);
129
+ return true;
130
+ } else {
131
+ console.log(`\n⚠️ TEST INCONCLUSIVE: Transaction not found (may be too old or invalid)`);
132
+ return true; // Not a failure, just inconclusive
133
+ }
134
+ } catch (error) {
135
+ console.error(`\n❌ TEST FAILED: ${error.message}`);
136
+ return false;
137
+ }
138
+ }
139
+
140
+ /**
141
+ * Test 3: Verify getBlockHeight works
142
+ */
143
+ async function testGetBlockHeight() {
144
+ console.log('\n📋 Test 3: getBlockHeight functionality');
145
+ console.log('---------------------------------------');
146
+
147
+ try {
148
+ const blockHeight = await connection.getBlockHeight('confirmed');
149
+ console.log(` Current block height: ${blockHeight}`);
150
+
151
+ // Get latest blockhash to compare
152
+ const { lastValidBlockHeight } = await connection.getLatestBlockhash('confirmed');
153
+ console.log(` lastValidBlockHeight: ${lastValidBlockHeight}`);
154
+ console.log(` Difference: ${lastValidBlockHeight - blockHeight} blocks`);
155
+
156
+ if (blockHeight > 0 && lastValidBlockHeight > blockHeight) {
157
+ console.log(`\n✅ TEST PASSED: getBlockHeight works correctly`);
158
+ return true;
159
+ } else {
160
+ console.log(`\n❌ TEST FAILED: Unexpected block height values`);
161
+ return false;
162
+ }
163
+ } catch (error) {
164
+ console.error(`\n❌ TEST FAILED: ${error.message}`);
165
+ return false;
166
+ }
167
+ }
168
+
169
+ /**
170
+ * Test 4: Verify error handling for invalid signature
171
+ */
172
+ async function testInvalidSignature() {
173
+ console.log('\n📋 Test 4: Invalid signature handling');
174
+ console.log('-------------------------------------');
175
+
176
+ const invalidSig = 'InvalidSignature123456789012345678901234567890123456789012345678901234567890';
177
+
178
+ try {
179
+ const response = await connection.getSignatureStatuses([invalidSig], {
180
+ searchTransactionHistory: true
181
+ });
182
+
183
+ if (response?.value?.[0] === null) {
184
+ console.log(` Response for invalid sig: null (as expected)`);
185
+ console.log(`\n✅ TEST PASSED: Invalid signatures return null`);
186
+ return true;
187
+ } else {
188
+ console.log(` Unexpected response: ${JSON.stringify(response)}`);
189
+ console.log(`\n⚠️ TEST INCONCLUSIVE`);
190
+ return true;
191
+ }
192
+ } catch (error) {
193
+ // Some errors are expected for truly malformed signatures
194
+ console.log(` Error (expected): ${error.message}`);
195
+ console.log(`\n✅ TEST PASSED: Invalid signatures handled gracefully`);
196
+ return true;
197
+ }
198
+ }
199
+
200
+ /**
201
+ * Main execution
202
+ */
203
+ async function main() {
204
+ const args = process.argv.slice(2);
205
+
206
+ // Use provided signature or a known devnet transaction
207
+ // This is a sample devnet transaction - replace with a real one if needed
208
+ let testSignature = args[0];
209
+
210
+ if (!testSignature) {
211
+ // Try to get a recent transaction from the connection
212
+ console.log('No signature provided, fetching a recent confirmed transaction...');
213
+ try {
214
+ const signatures = await connection.getSignaturesForAddress(
215
+ connection._rpcEndpoint.includes('mainnet')
216
+ ? require('@solana/web3.js').SystemProgram.programId
217
+ : require('@solana/web3.js').SystemProgram.programId,
218
+ { limit: 1 }
219
+ );
220
+ if (signatures.length > 0) {
221
+ testSignature = signatures[0].signature;
222
+ console.log(`Using recent transaction: ${testSignature}\n`);
223
+ }
224
+ } catch (e) {
225
+ console.log('Could not fetch recent transaction, using fallback...');
226
+ }
227
+ }
228
+
229
+ if (!testSignature) {
230
+ console.log('\n⚠️ No test signature available.');
231
+ console.log(' Please provide a signature: node test-poll-confirmation.js <signature>');
232
+ console.log(' Running remaining tests without transaction confirmation...\n');
233
+ }
234
+
235
+ let passed = 0;
236
+ let failed = 0;
237
+
238
+ // Run tests
239
+ if (testSignature) {
240
+ if (await testKnownTransaction(testSignature)) passed++; else failed++;
241
+ if (await testSearchTransactionHistory(testSignature)) passed++; else failed++;
242
+ }
243
+
244
+ if (await testGetBlockHeight()) passed++; else failed++;
245
+ if (await testInvalidSignature()) passed++; else failed++;
246
+
247
+ // Summary
248
+ console.log('\n=====================================');
249
+ console.log('📊 Test Summary');
250
+ console.log('=====================================');
251
+ console.log(` Passed: ${passed}`);
252
+ console.log(` Failed: ${failed}`);
253
+ console.log('');
254
+
255
+ if (failed === 0) {
256
+ console.log('✅ All tests passed! The polling confirmation approach works with your RPC.');
257
+ } else {
258
+ console.log('❌ Some tests failed. Check the output above for details.');
259
+ }
260
+
261
+ process.exit(failed > 0 ? 1 : 0);
262
+ }
263
+
264
+ main().catch(err => {
265
+ console.error('Fatal error:', err);
266
+ process.exit(1);
267
+ });
@@ -0,0 +1,271 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Manual Game Resolution Test Script
4
+ * Use this to manually resolve games and test the notification system
5
+ *
6
+ * Usage:
7
+ * node test-resolve-game.js <gameId> # Resolve specific game
8
+ * node test-resolve-game.js --all # Resolve all pending games
9
+ * node test-resolve-game.js <gameId> --winner=home # Force winner
10
+ * node test-resolve-game.js <gameId> --score=3:2 # Force score
11
+ */
12
+
13
+ require('dotenv').config();
14
+ const { Keypair } = require('@solana/web3.js');
15
+ const fs = require('fs');
16
+ const path = require('path');
17
+ const axios = require('axios');
18
+ const AutomaticGameOracle = require('../services/automaticGameOracle');
19
+
20
+ // Parse command line arguments
21
+ const args = process.argv.slice(2);
22
+ const gameIdArg = args[0];
23
+ const options = {};
24
+
25
+ args.forEach(arg => {
26
+ if (arg.startsWith('--winner=')) {
27
+ options.forceWinner = arg.split('=')[1]; // 'home' or 'away'
28
+ }
29
+ if (arg.startsWith('--score=')) {
30
+ const [home, away] = arg.split('=')[1].split(':');
31
+ options.forceScore = { homeScore: parseInt(home), awayScore: parseInt(away) };
32
+ }
33
+ if (arg === '--all') {
34
+ options.resolveAll = true;
35
+ }
36
+ });
37
+
38
+ // Load oracle wallet
39
+ function loadOracleWallet() {
40
+ if (process.env.ORACLE_WALLET_JSON) {
41
+ console.log('🔑 Loading oracle wallet from environment variable');
42
+ const secretKey = JSON.parse(process.env.ORACLE_WALLET_JSON);
43
+ return Keypair.fromSecretKey(Uint8Array.from(secretKey));
44
+ }
45
+
46
+ const oracleWalletPath = process.env.ORACLE_WALLET_PATH || path.join(__dirname, 'wallets/oracle.json');
47
+
48
+ if (!fs.existsSync(oracleWalletPath)) {
49
+ console.error(`❌ Oracle wallet not found at: ${oracleWalletPath}`);
50
+ console.log('💡 Create one with: solana-keygen new -o wallets/oracle.json');
51
+ process.exit(1);
52
+ }
53
+
54
+ console.log('🔑 Loading oracle wallet from file:', oracleWalletPath);
55
+ const secretKey = JSON.parse(fs.readFileSync(oracleWalletPath, 'utf-8'));
56
+ return Keypair.fromSecretKey(Uint8Array.from(secretKey));
57
+ }
58
+
59
+ // Configuration
60
+ const config = {
61
+ rpcUrl: process.env.SOLANA_NETWORK || 'https://api.devnet.solana.com',
62
+ programId: process.env.PROGRAM_ID || '8DJTkgk6MDr6tPtw4v2VzYAz9WWvmCg6786vZrEK3o5q',
63
+ oracleKeypair: loadOracleWallet(),
64
+ liveScoresApiUrl: process.env.LIVE_SCORES_API_URL || 'http://localhost:3000',
65
+ dubsGamesApiUrl: process.env.DUBS_GAMES_API_URL || 'http://localhost:3001',
66
+ checkIntervalMs: 60000,
67
+ };
68
+
69
+ console.log('🧪 Manual Game Resolution Test');
70
+ console.log('================================');
71
+ console.log(`RPC: ${config.rpcUrl}`);
72
+ console.log(`Program ID: ${config.programId}`);
73
+ console.log(`Oracle: ${config.oracleKeypair.publicKey.toString()}`);
74
+ console.log('');
75
+
76
+ /**
77
+ * Fetch a specific game from Firebase
78
+ */
79
+ async function fetchGame(gameId) {
80
+ try {
81
+ const apiUrl = process.env.DUBS_GAMES_API_URL || 'https://dubs-games-api-dev-76c556653fa3.herokuapp.com';
82
+ const response = await axios.get(`${apiUrl}/api/games/${gameId}`);
83
+
84
+ if (!response.data || !response.data.gameId) {
85
+ throw new Error('Game not found in Firebase');
86
+ }
87
+
88
+ return response.data;
89
+ } catch (error) {
90
+ throw new Error(`Failed to fetch game: ${error.message}`);
91
+ }
92
+ }
93
+
94
+ /**
95
+ * Fetch all pending games
96
+ */
97
+ async function fetchPendingGames() {
98
+ try {
99
+ const apiUrl = process.env.DUBS_GAMES_API_URL || 'https://dubs-games-api-dev-76c556653fa3.herokuapp.com';
100
+ const response = await axios.get(`${apiUrl}/api/auth/games/automatic/pending`);
101
+
102
+ if (!response.data.success || !response.data.games) {
103
+ return [];
104
+ }
105
+
106
+ return response.data.games;
107
+ } catch (error) {
108
+ throw new Error(`Failed to fetch pending games: ${error.message}`);
109
+ }
110
+ }
111
+
112
+ /**
113
+ * Get live score or use forced values
114
+ */
115
+ async function getGameResult(game, options) {
116
+ // If forcing winner/score, use that
117
+ if (options.forceWinner) {
118
+ const score = options.forceScore || { homeScore: 3, awayScore: 2 };
119
+
120
+ // Make sure winner matches score
121
+ if (options.forceWinner === 'home' && score.homeScore <= score.awayScore) {
122
+ score.homeScore = score.awayScore + 1;
123
+ } else if (options.forceWinner === 'away' && score.awayScore <= score.homeScore) {
124
+ score.awayScore = score.homeScore + 1;
125
+ }
126
+
127
+ return {
128
+ isFinal: true,
129
+ status: 'Final',
130
+ winner: options.forceWinner,
131
+ homeScore: score.homeScore,
132
+ awayScore: score.awayScore
133
+ };
134
+ }
135
+
136
+ // Otherwise, try to fetch real score
137
+ const oracle = new AutomaticGameOracle(config);
138
+ const result = await oracle.checkSportsGameResult(game.sportsEvent);
139
+
140
+ if (!result || !result.isFinal) {
141
+ throw new Error('Game not finished yet. Use --winner=home or --winner=away to force resolution.');
142
+ }
143
+
144
+ return result;
145
+ }
146
+
147
+ /**
148
+ * Resolve a specific game
149
+ */
150
+ async function resolveGame(gameId, options) {
151
+ try {
152
+ console.log(`\n📊 Fetching game: ${gameId}`);
153
+ const game = await fetchGame(gameId);
154
+
155
+ console.log(` Title: ${game.title || 'Untitled'}`);
156
+ console.log(` Event: ${game.sportsEvent?.strEvent || 'N/A'}`);
157
+ console.log(` Participants: ${game.participants?.length || 0}`);
158
+ console.log(` Chat ID: ${game.telegramChatId || 'NOT SET'}`);
159
+
160
+ if (!game.telegramChatId) {
161
+ console.log(` ⚠️ WARNING: No chat ID - notifications won't be sent!`);
162
+ }
163
+
164
+ console.log(`\n🔍 Getting game result...`);
165
+ const result = await getGameResult(game, options);
166
+
167
+ console.log(` Status: ${result.status}`);
168
+ console.log(` Winner: ${result.winner}`);
169
+ console.log(` Score: ${result.homeScore}-${result.awayScore}`);
170
+
171
+ console.log(`\n🔗 Resolving on-chain...`);
172
+ const oracle = new AutomaticGameOracle(config);
173
+
174
+ // This will resolve on-chain, update Firebase, and send Telegram notification
175
+ await oracle.resolveGame(game, result);
176
+
177
+ console.log(`\n✅ Game resolved successfully!`);
178
+
179
+ if (game.telegramChatId) {
180
+ console.log(` 📱 Telegram notification sent to chat ${game.telegramChatId}`);
181
+ }
182
+
183
+ } catch (error) {
184
+ console.error(`\n❌ Error resolving game:`, error.message);
185
+
186
+ if (error.message?.includes('AlreadyResolved')) {
187
+ console.log(` ℹ️ Game was already resolved`);
188
+ }
189
+
190
+ throw error;
191
+ }
192
+ }
193
+
194
+ /**
195
+ * Resolve all pending games
196
+ */
197
+ async function resolveAllPendingGames(options) {
198
+ try {
199
+ console.log(`\n🔍 Fetching all pending games...`);
200
+ const games = await fetchPendingGames();
201
+
202
+ if (games.length === 0) {
203
+ console.log(` No pending games found`);
204
+ return;
205
+ }
206
+
207
+ console.log(` Found ${games.length} pending game(s)\n`);
208
+
209
+ for (const game of games) {
210
+ try {
211
+ console.log(`\n${'='.repeat(60)}`);
212
+ await resolveGame(game.gameId, options);
213
+ } catch (error) {
214
+ console.error(` Skipping game ${game.gameId}: ${error.message}`);
215
+ continue;
216
+ }
217
+ }
218
+
219
+ console.log(`\n${'='.repeat(60)}`);
220
+ console.log(`\n✅ Finished processing all games`);
221
+
222
+ } catch (error) {
223
+ console.error(`\n❌ Error:`, error.message);
224
+ throw error;
225
+ }
226
+ }
227
+
228
+ /**
229
+ * Main execution
230
+ */
231
+ async function main() {
232
+ try {
233
+ // Show usage if no arguments
234
+ if (args.length === 0) {
235
+ console.log('Usage:');
236
+ console.log(' node test-resolve-game.js <gameId> # Resolve specific game');
237
+ console.log(' node test-resolve-game.js --all # Resolve all pending games');
238
+ console.log(' node test-resolve-game.js <gameId> --winner=home # Force home team win');
239
+ console.log(' node test-resolve-game.js <gameId> --winner=away # Force away team win');
240
+ console.log(' node test-resolve-game.js <gameId> --score=3:2 # Force specific score');
241
+ console.log('');
242
+ console.log('Examples:');
243
+ console.log(' node test-resolve-game.js sport-1234567890-abc123');
244
+ console.log(' node test-resolve-game.js sport-1234567890-abc123 --winner=home');
245
+ console.log(' node test-resolve-game.js sport-1234567890-abc123 --winner=away --score=5:2');
246
+ console.log(' node test-resolve-game.js --all');
247
+ console.log('');
248
+ process.exit(0);
249
+ }
250
+
251
+ if (options.resolveAll) {
252
+ await resolveAllPendingGames(options);
253
+ } else {
254
+ if (!gameIdArg || gameIdArg.startsWith('--')) {
255
+ throw new Error('Please provide a game ID');
256
+ }
257
+ await resolveGame(gameIdArg, options);
258
+ }
259
+
260
+ console.log('\n✨ Done!\n');
261
+ process.exit(0);
262
+
263
+ } catch (error) {
264
+ console.error('\n❌ Fatal error:', error.message);
265
+ process.exit(1);
266
+ }
267
+ }
268
+
269
+ // Run main function
270
+ main();
271
+