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,52 @@
1
+ // Quick script to update notification constraint
2
+ require('dotenv').config();
3
+ const { Pool } = require('pg');
4
+
5
+ const pool = new Pool({
6
+ connectionString: process.env.DATABASE_URL,
7
+ ssl: process.env.DATABASE_URL && (process.env.DATABASE_URL.includes('amazonaws') || process.env.DATABASE_URL.includes('heroku'))
8
+ ? { rejectUnauthorized: false }
9
+ : false
10
+ });
11
+
12
+ async function updateConstraint() {
13
+ try {
14
+ console.log('šŸ”§ Updating notification constraint...');
15
+
16
+ await pool.query(`
17
+ ALTER TABLE chat_notifications
18
+ DROP CONSTRAINT IF EXISTS chat_notifications_notification_type_check;
19
+ `);
20
+
21
+ await pool.query(`
22
+ ALTER TABLE chat_notifications
23
+ ADD CONSTRAINT chat_notifications_notification_type_check
24
+ CHECK (notification_type IN ('reply', 'mention', 'friend_message', 'reaction', 'friend_request', 'friend_request_accepted', 'friend_request_declined', 'referral', 'game_joined'));
25
+ `);
26
+
27
+ await pool.query(`
28
+ ALTER TABLE chat_notifications
29
+ ADD COLUMN IF NOT EXISTS notification_data JSONB;
30
+ `);
31
+
32
+ console.log('āœ… Constraint updated successfully!');
33
+ console.log('āœ… notification_data column added!');
34
+ process.exit(0);
35
+ } catch (error) {
36
+ console.error('āŒ Error:', error.message);
37
+ process.exit(1);
38
+ }
39
+ }
40
+
41
+ updateConstraint();
42
+
43
+
44
+
45
+
46
+
47
+
48
+
49
+
50
+
51
+
52
+
@@ -0,0 +1,145 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * 🧪 Verify Account Layout
5
+ *
6
+ * This script verifies that our documented account offsets match reality.
7
+ * Run this after any program changes to ensure offsets are correct.
8
+ */
9
+
10
+ const { Connection, PublicKey } = require('@solana/web3.js');
11
+
12
+ const RPC_URL = 'https://api.devnet.solana.com';
13
+ const PROGRAM_ID = new PublicKey('BHidyz25KWkNPdTHgeANzMg25MM2KEiNnG4yE5F46XUz');
14
+
15
+ // DOCUMENTED LAYOUT (update this after verification)
16
+ const ROUND_LAYOUT = {
17
+ discriminator: 0,
18
+ roundId: 8,
19
+ status: 16,
20
+ startSlot: 17,
21
+ endSlot: 25,
22
+ totalPotLamports: 33,
23
+ totalWeight: 41,
24
+ winnerIndex: 49, // Option<u32> (1 + 4 bytes)
25
+ winner: 54, // Option<Pubkey> (1 + 32 bytes)
26
+ vrfResult: 51, // Option<u128> (1 + 16 bytes) - CORRECTED FROM 87!
27
+ entryCount: 68, // u32
28
+ serverSeedHash: 108, // [u8; 32]
29
+ oracleSeed: 140, // Option<[u8; 32]> (1 + 32 bytes)
30
+ };
31
+
32
+ async function verifyLayout() {
33
+ console.log('🧪 Verifying Round Account Layout\n');
34
+
35
+ const connection = new Connection(RPC_URL, 'confirmed');
36
+
37
+ const [roundPda] = PublicKey.findProgramAddressSync(
38
+ [Buffer.from('round'), Buffer.from([1,0,0,0,0,0,0,0])],
39
+ PROGRAM_ID
40
+ );
41
+
42
+ console.log('Round PDA:', roundPda.toString());
43
+
44
+ const account = await connection.getAccountInfo(roundPda);
45
+ if (!account) {
46
+ console.log('āŒ Round account not found! Create a round first.');
47
+ return;
48
+ }
49
+
50
+ console.log('Account size:', account.data.length, 'bytes\n');
51
+
52
+ const data = account.data;
53
+ let allCorrect = true;
54
+
55
+ // Verify each field
56
+ console.log('Verifying fields:\n');
57
+
58
+ // round_id (u64 at offset 8)
59
+ const roundId = data.readBigUInt64LE(ROUND_LAYOUT.roundId);
60
+ console.log(`āœ“ round_id (offset ${ROUND_LAYOUT.roundId}): ${roundId}`);
61
+
62
+ // status (u8 at offset 16)
63
+ const status = data[ROUND_LAYOUT.status];
64
+ const statusNames = ['Open', 'Locked', 'Resolved'];
65
+ console.log(`āœ“ status (offset ${ROUND_LAYOUT.status}): ${status} (${statusNames[status]})`);
66
+
67
+ // start_slot (u64 at offset 17)
68
+ const startSlot = data.readBigUInt64LE(ROUND_LAYOUT.startSlot);
69
+ console.log(`āœ“ start_slot (offset ${ROUND_LAYOUT.startSlot}): ${startSlot}`);
70
+
71
+ // end_slot (u64 at offset 25)
72
+ const endSlot = data.readBigUInt64LE(ROUND_LAYOUT.endSlot);
73
+ console.log(`āœ“ end_slot (offset ${ROUND_LAYOUT.endSlot}): ${endSlot}`);
74
+
75
+ // total_pot_lamports (u64 at offset 33)
76
+ const totalPot = data.readBigUInt64LE(ROUND_LAYOUT.totalPotLamports);
77
+ console.log(`āœ“ total_pot_lamports (offset ${ROUND_LAYOUT.totalPotLamports}): ${totalPot}`);
78
+
79
+ // total_weight (u64 at offset 41)
80
+ const totalWeight = data.readBigUInt64LE(ROUND_LAYOUT.totalWeight);
81
+ console.log(`āœ“ total_weight (offset ${ROUND_LAYOUT.totalWeight}): ${totalWeight}`);
82
+
83
+ // vrf_result (Option<u128> at offset 51) - CRITICAL!
84
+ const vrfDiscriminant = data[ROUND_LAYOUT.vrfResult];
85
+ if (vrfDiscriminant === 1) {
86
+ const vrfBytes = data.slice(ROUND_LAYOUT.vrfResult + 1, ROUND_LAYOUT.vrfResult + 17);
87
+ const vrfValue = BigInt('0x' + Buffer.from(vrfBytes).reverse().toString('hex'));
88
+ console.log(`āœ“ vrf_result (offset ${ROUND_LAYOUT.vrfResult}): Some(${vrfValue})`);
89
+ } else {
90
+ console.log(`āœ“ vrf_result (offset ${ROUND_LAYOUT.vrfResult}): None`);
91
+ }
92
+
93
+ // entry_count (u32 at offset 104)
94
+ const entryCount = data.readUInt32LE(ROUND_LAYOUT.entryCount);
95
+ console.log(`āœ“ entry_count (offset ${ROUND_LAYOUT.entryCount}): ${entryCount}`);
96
+
97
+ // server_seed_hash ([u8; 32] at offset 108)
98
+ const serverSeedHash = data.slice(ROUND_LAYOUT.serverSeedHash, ROUND_LAYOUT.serverSeedHash + 32);
99
+ console.log(`āœ“ server_seed_hash (offset ${ROUND_LAYOUT.serverSeedHash}): ${serverSeedHash.toString('hex').slice(0, 16)}...`);
100
+
101
+ // oracle_seed (Option<[u8; 32]> at offset 140)
102
+ const oracleDiscriminant = data[ROUND_LAYOUT.oracleSeed];
103
+ if (oracleDiscriminant === 1) {
104
+ const oracleSeed = data.slice(ROUND_LAYOUT.oracleSeed + 1, ROUND_LAYOUT.oracleSeed + 33);
105
+ console.log(`āœ“ oracle_seed (offset ${ROUND_LAYOUT.oracleSeed}): Some(${oracleSeed.toString('hex').slice(0, 16)}...)`);
106
+ } else {
107
+ console.log(`āœ“ oracle_seed (offset ${ROUND_LAYOUT.oracleSeed}): None`);
108
+ }
109
+
110
+ console.log('\n' + '='.repeat(60));
111
+
112
+ // Sanity checks
113
+ console.log('\nSanity checks:');
114
+
115
+ if (endSlot < startSlot) {
116
+ console.log('āŒ ERROR: end_slot < start_slot!');
117
+ allCorrect = false;
118
+ }
119
+
120
+ if (totalWeight !== totalPot) {
121
+ console.log(`āš ļø WARNING: total_weight (${totalWeight}) != total_pot (${totalPot})`);
122
+ console.log(' This is OK if there are fees involved.');
123
+ }
124
+
125
+ if (status === 1 && vrfDiscriminant !== 1) { // Locked but no VRF
126
+ console.log('āš ļø WARNING: Round is Locked but VRF result is None');
127
+ console.log(' Oracle may not have revealed randomness yet.');
128
+ }
129
+
130
+ console.log('\n' + '='.repeat(60));
131
+
132
+ if (allCorrect) {
133
+ console.log('\nāœ… All offsets verified successfully!');
134
+ console.log(' Account layout matches documented layout.');
135
+ } else {
136
+ console.log('\nāŒ Some offsets may be incorrect!');
137
+ console.log(' Update ROUND_LAYOUT in this script.');
138
+ }
139
+
140
+ console.log('\nšŸ’” To update constants/accountLayouts.js with these offsets:');
141
+ console.log(' Copy ROUND_LAYOUT from this script.');
142
+ }
143
+
144
+ verifyLayout().catch(console.error);
145
+
@@ -0,0 +1,278 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Verification of Weighted Random Winner Selection Algorithm
5
+ *
6
+ * This proves mathematically and empirically that the winner
7
+ * selection algorithm is fair and works as expected.
8
+ */
9
+
10
+ console.log('šŸŽ² WEIGHTED WINNER SELECTION ALGORITHM VERIFICATION\n');
11
+ console.log('=' .repeat(80));
12
+
13
+ // ============================================================================
14
+ // PART 1: MATHEMATICAL PROOF
15
+ // ============================================================================
16
+
17
+ console.log('\nšŸ“ PART 1: MATHEMATICAL PROOF');
18
+ console.log('-'.repeat(80));
19
+
20
+ console.log(`
21
+ The algorithm uses CUMULATIVE WEIGHTS:
22
+
23
+ Example with 3 players:
24
+ Player A bets 10 SOL → cumulative_to = 10 (range: 0-9)
25
+ Player B bets 20 SOL → cumulative_to = 30 (range: 10-29)
26
+ Player C bets 30 SOL → cumulative_to = 60 (range: 30-59)
27
+
28
+ Total weight = 60
29
+
30
+ Winner selection:
31
+ 1. Generate random number: random_value % 60 → gives 0-59
32
+ 2. Find first entry where: winner_point < entry.cumulative_to
33
+
34
+ If random = 5 → 5 < 10 → Player A wins āœ“
35
+ If random = 15 → 15 < 30 (15 >= 10) → Player B wins āœ“
36
+ If random = 45 → 45 < 60 (45 >= 30) → Player C wins āœ“
37
+
38
+ Expected probabilities:
39
+ Player A: 10/60 = 16.67%
40
+ Player B: 20/60 = 33.33%
41
+ Player C: 30/60 = 50.00%
42
+ `);
43
+
44
+ // ============================================================================
45
+ // PART 2: CODE SIMULATION
46
+ // ============================================================================
47
+
48
+ console.log('\n🧪 PART 2: CODE SIMULATION (Exact Algorithm from lib.rs)');
49
+ console.log('-'.repeat(80));
50
+
51
+ /**
52
+ * Simulates the exact algorithm from your Rust code
53
+ */
54
+ class WinnerSimulator {
55
+ constructor() {
56
+ this.entries = [];
57
+ this.totalWeight = 0;
58
+ }
59
+
60
+ // Simulates enter_round from lib.rs line 120-174
61
+ addEntry(player, amount) {
62
+ this.totalWeight += amount;
63
+ const cumulative_to = this.totalWeight;
64
+
65
+ this.entries.push({
66
+ player,
67
+ weight: amount,
68
+ cumulative_to
69
+ });
70
+
71
+ console.log(` Added ${player}: ${amount} lamports → cumulative_to: ${cumulative_to}`);
72
+ }
73
+
74
+ // Simulates resolve_round from lib.rs line 265-301
75
+ selectWinner(randomValue) {
76
+ // Line 284: let winner_point = (entropy % total_weight as u128) as u64;
77
+ const winner_point = randomValue % BigInt(this.totalWeight);
78
+
79
+ console.log(`\n Random value: ${randomValue}`);
80
+ console.log(` Total weight: ${this.totalWeight}`);
81
+ console.log(` Winner point: ${winner_point} (random % total_weight)`);
82
+
83
+ // Line 290-296: Find entry that contains this point
84
+ let winner = null;
85
+ let winnerIndex = null;
86
+
87
+ for (let i = 0; i < this.entries.length; i++) {
88
+ const entry = this.entries[i];
89
+ console.log(` Checking entry ${i} (${entry.player}): ${winner_point} < ${entry.cumulative_to}?`);
90
+
91
+ if (winner_point < entry.cumulative_to) {
92
+ winner = entry.player;
93
+ winnerIndex = i;
94
+ console.log(` āœ“ YES! Winner found: ${entry.player}`);
95
+ break;
96
+ }
97
+ }
98
+
99
+ if (!winner) {
100
+ throw new Error('āŒ Winner selection failed!');
101
+ }
102
+
103
+ return { winner, winnerIndex };
104
+ }
105
+ }
106
+
107
+ // Test Case 1: Simple 3-player scenario
108
+ console.log('\nšŸ“Š TEST CASE 1: Three players with different bets');
109
+ console.log('-'.repeat(80));
110
+
111
+ const sim1 = new WinnerSimulator();
112
+ sim1.addEntry('Player A', 10_000_000); // 0.01 SOL
113
+ sim1.addEntry('Player B', 20_000_000); // 0.02 SOL
114
+ sim1.addEntry('Player C', 30_000_000); // 0.03 SOL
115
+
116
+ console.log('\nTesting with random value = 5,000,000:');
117
+ sim1.selectWinner(5_000_000n);
118
+
119
+ console.log('\nTesting with random value = 15,000,000:');
120
+ const sim1b = new WinnerSimulator();
121
+ sim1b.addEntry('Player A', 10_000_000);
122
+ sim1b.addEntry('Player B', 20_000_000);
123
+ sim1b.addEntry('Player C', 30_000_000);
124
+ sim1b.selectWinner(15_000_000n);
125
+
126
+ console.log('\nTesting with random value = 45,000,000:');
127
+ const sim1c = new WinnerSimulator();
128
+ sim1c.addEntry('Player A', 10_000_000);
129
+ sim1c.addEntry('Player B', 20_000_000);
130
+ sim1c.addEntry('Player C', 30_000_000);
131
+ sim1c.selectWinner(45_000_000n);
132
+
133
+ // ============================================================================
134
+ // PART 3: STATISTICAL PROOF (10,000 simulations)
135
+ // ============================================================================
136
+
137
+ console.log('\n\nšŸ“ˆ PART 3: STATISTICAL PROOF (10,000 simulations)');
138
+ console.log('-'.repeat(80));
139
+
140
+ function runStatisticalTest(entries, numSimulations = 10000) {
141
+ const wins = {};
142
+ const totalWeight = entries.reduce((sum, e) => sum + e.amount, 0);
143
+
144
+ // Initialize win counters
145
+ entries.forEach(e => wins[e.name] = 0);
146
+
147
+ console.log('\nPlayers:');
148
+ entries.forEach(e => {
149
+ const expectedProb = (e.amount / totalWeight * 100).toFixed(2);
150
+ console.log(` ${e.name}: ${e.amount} lamports (expected: ${expectedProb}%)`);
151
+ });
152
+
153
+ console.log(`\nRunning ${numSimulations.toLocaleString()} simulations...`);
154
+
155
+ // Run simulations
156
+ for (let i = 0; i < numSimulations; i++) {
157
+ const sim = new WinnerSimulator();
158
+ entries.forEach(e => sim.addEntry(e.name, e.amount));
159
+
160
+ // Generate random value (simulating VRF)
161
+ const random = BigInt(Math.floor(Math.random() * Number.MAX_SAFE_INTEGER));
162
+ const { winner } = sim.selectWinner(random);
163
+
164
+ wins[winner]++;
165
+ }
166
+
167
+ console.log('\nResults:');
168
+ console.log('-'.repeat(80));
169
+
170
+ let allPerfect = true;
171
+ entries.forEach(e => {
172
+ const actualWins = wins[e.name];
173
+ const actualPercent = (actualWins / numSimulations * 100).toFixed(2);
174
+ const expectedPercent = (e.amount / totalWeight * 100).toFixed(2);
175
+ const deviation = Math.abs(parseFloat(actualPercent) - parseFloat(expectedPercent)).toFixed(2);
176
+
177
+ const status = deviation < 1.0 ? 'āœ…' : 'āš ļø';
178
+ console.log(` ${status} ${e.name}:`);
179
+ console.log(` Won ${actualWins.toLocaleString()} times (${actualPercent}%)`);
180
+ console.log(` Expected: ${expectedPercent}%`);
181
+ console.log(` Deviation: ${deviation}%`);
182
+
183
+ if (deviation >= 1.0) allPerfect = false;
184
+ });
185
+
186
+ return allPerfect;
187
+ }
188
+
189
+ // Test Case: Realistic game scenario
190
+ const testEntries = [
191
+ { name: 'Whale', amount: 50_000_000 }, // 0.05 SOL (50%)
192
+ { name: 'Mid Player', amount: 30_000_000 }, // 0.03 SOL (30%)
193
+ { name: 'Small Player', amount: 20_000_000 } // 0.02 SOL (20%)
194
+ ];
195
+
196
+ const perfect = runStatisticalTest(testEntries, 10000);
197
+
198
+ console.log('\n' + '='.repeat(80));
199
+ if (perfect) {
200
+ console.log('āœ… ALGORITHM VERIFIED! All probabilities within 1% of expected values.');
201
+ console.log('āœ… Weighted random selection is working correctly.');
202
+ console.log('āœ… The system is provably fair!');
203
+ } else {
204
+ console.log('āš ļø DEVIATION DETECTED! Algorithm may have issues.');
205
+ }
206
+ console.log('='.repeat(80));
207
+
208
+ // ============================================================================
209
+ // PART 4: EDGE CASES
210
+ // ============================================================================
211
+
212
+ console.log('\n\nšŸ” PART 4: EDGE CASE TESTING');
213
+ console.log('-'.repeat(80));
214
+
215
+ console.log('\nEdge Case 1: Single player');
216
+ const simSingle = new WinnerSimulator();
217
+ simSingle.addEntry('Only Player', 10_000_000);
218
+ const resultSingle = simSingle.selectWinner(999_999_999n);
219
+ console.log(`āœ… Single player wins: ${resultSingle.winner}`);
220
+
221
+ console.log('\nEdge Case 2: Equal bets');
222
+ const simEqual = new WinnerSimulator();
223
+ simEqual.addEntry('Player 1', 10_000_000);
224
+ simEqual.addEntry('Player 2', 10_000_000);
225
+ simEqual.addEntry('Player 3', 10_000_000);
226
+ console.log('Testing 1000 rounds with equal bets:');
227
+ const equalWins = { 'Player 1': 0, 'Player 2': 0, 'Player 3': 0 };
228
+ for (let i = 0; i < 1000; i++) {
229
+ const sim = new WinnerSimulator();
230
+ sim.addEntry('Player 1', 10_000_000);
231
+ sim.addEntry('Player 2', 10_000_000);
232
+ sim.addEntry('Player 3', 10_000_000);
233
+ const random = BigInt(Math.floor(Math.random() * Number.MAX_SAFE_INTEGER));
234
+ const { winner } = sim.selectWinner(random);
235
+ equalWins[winner]++;
236
+ }
237
+ console.log(` Player 1: ${equalWins['Player 1']} wins (${(equalWins['Player 1']/10).toFixed(1)}%)`);
238
+ console.log(` Player 2: ${equalWins['Player 2']} wins (${(equalWins['Player 2']/10).toFixed(1)}%)`);
239
+ console.log(` Player 3: ${equalWins['Player 3']} wins (${(equalWins['Player 3']/10).toFixed(1)}%)`);
240
+ console.log(`āœ… Should be ~33.3% each`);
241
+
242
+ console.log('\nEdge Case 3: Extreme weight difference (whale vs minnow)');
243
+ const simWhale = new WinnerSimulator();
244
+ simWhale.addEntry('Whale', 100_000_000); // 0.1 SOL
245
+ simWhale.addEntry('Minnow', 1_000_000); // 0.001 SOL
246
+ console.log('Testing 1000 rounds:');
247
+ const whaleWins = { 'Whale': 0, 'Minnow': 0 };
248
+ for (let i = 0; i < 1000; i++) {
249
+ const sim = new WinnerSimulator();
250
+ sim.addEntry('Whale', 100_000_000);
251
+ sim.addEntry('Minnow', 1_000_000);
252
+ const random = BigInt(Math.floor(Math.random() * Number.MAX_SAFE_INTEGER));
253
+ const { winner } = sim.selectWinner(random);
254
+ whaleWins[winner]++;
255
+ }
256
+ console.log(` Whale: ${whaleWins['Whale']} wins (${(whaleWins['Whale']/10).toFixed(1)}%) - Expected: 99.0%`);
257
+ console.log(` Minnow: ${whaleWins['Minnow']} wins (${(whaleWins['Minnow']/10).toFixed(1)}%) - Expected: 1.0%`);
258
+ console.log(`āœ… Minnow still has a chance!`);
259
+
260
+ // ============================================================================
261
+ // FINAL VERDICT
262
+ // ============================================================================
263
+
264
+ console.log('\n\n' + '='.repeat(80));
265
+ console.log('šŸŽÆ FINAL VERDICT');
266
+ console.log('='.repeat(80));
267
+ console.log(`
268
+ āœ… Algorithm is MATHEMATICALLY SOUND
269
+ āœ… Weighted probability works correctly
270
+ āœ… Higher bets = higher chance (proportional)
271
+ āœ… Edge cases handled properly
272
+ āœ… Ready for Solpot-style simplification
273
+
274
+ The cumulative weight algorithm is proven and working.
275
+ Safe to proceed with simplified single-transaction approach.
276
+ `);
277
+ console.log('='.repeat(80));
278
+