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,720 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * šŸŽ® Dubs CLI - Interactive Testing Tool
5
+ *
6
+ * Test the complete Dubs voting system with a beautiful interactive menu!
7
+ * No Postman, no manual curls - just select and test!
8
+ */
9
+
10
+ const readline = require('readline');
11
+ const https = require('https');
12
+ const http = require('http');
13
+
14
+ // Simple fetch wrapper for Node.js
15
+ async function fetch(url, options = {}) {
16
+ return new Promise((resolve, reject) => {
17
+ const urlObj = new URL(url);
18
+ const client = urlObj.protocol === 'https:' ? https : http;
19
+
20
+ const req = client.request(url, {
21
+ method: options.method || 'GET',
22
+ headers: options.headers || {},
23
+ }, (res) => {
24
+ let data = '';
25
+ res.on('data', chunk => data += chunk);
26
+ res.on('end', () => {
27
+ resolve({
28
+ ok: res.statusCode >= 200 && res.statusCode < 300,
29
+ json: async () => JSON.parse(data),
30
+ text: async () => data,
31
+ });
32
+ });
33
+ });
34
+
35
+ req.on('error', reject);
36
+
37
+ if (options.body) {
38
+ req.write(options.body);
39
+ }
40
+
41
+ req.end();
42
+ });
43
+ }
44
+
45
+ // Configuration
46
+ const BASE_URL = process.env.DUBS_URL || 'http://localhost:3001';
47
+ let currentGameId = null;
48
+ let playerAddresses = {};
49
+
50
+ // Colors for terminal
51
+ const colors = {
52
+ reset: '\x1b[0m',
53
+ bright: '\x1b[1m',
54
+ red: '\x1b[31m',
55
+ green: '\x1b[32m',
56
+ yellow: '\x1b[33m',
57
+ blue: '\x1b[34m',
58
+ magenta: '\x1b[35m',
59
+ cyan: '\x1b[36m',
60
+ };
61
+
62
+ const rl = readline.createInterface({
63
+ input: process.stdin,
64
+ output: process.stdout
65
+ });
66
+
67
+ // Helper to ask questions
68
+ function ask(question) {
69
+ return new Promise((resolve) => {
70
+ rl.question(question, resolve);
71
+ });
72
+ }
73
+
74
+ // Pretty print JSON
75
+ function printJson(data) {
76
+ console.log(colors.cyan + JSON.stringify(data, null, 2) + colors.reset);
77
+ }
78
+
79
+ // Success message
80
+ function success(msg) {
81
+ console.log(colors.green + 'āœ… ' + msg + colors.reset);
82
+ }
83
+
84
+ // Error message
85
+ function error(msg) {
86
+ console.log(colors.red + 'āŒ ' + msg + colors.reset);
87
+ }
88
+
89
+ // Info message
90
+ function info(msg) {
91
+ console.log(colors.blue + 'ā„¹ļø ' + msg + colors.reset);
92
+ }
93
+
94
+ // Main menu
95
+ async function showMenu() {
96
+ console.clear();
97
+ console.log(colors.bright + colors.magenta);
98
+ console.log('╔═══════════════════════════════════════════════════════╗');
99
+ console.log('ā•‘ šŸŽ® DUBS CLI - VOTING TESTER šŸŽ® ā•‘');
100
+ console.log('ā•šā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•');
101
+ console.log(colors.reset);
102
+ console.log(colors.cyan + `šŸ“” Server: ${BASE_URL}` + colors.reset);
103
+ if (currentGameId) {
104
+ console.log(colors.yellow + `šŸŽ® Current Game: ${currentGameId}` + colors.reset);
105
+ }
106
+ console.log('');
107
+ console.log(colors.bright + '═══ GAME MANAGEMENT ═══' + colors.reset);
108
+ console.log('1. šŸ“‹ List Demo Wallets');
109
+ console.log('2. šŸ’ø Airdrop SOL to Player');
110
+ console.log('3. šŸŽ® Create New Game');
111
+ console.log('4. šŸ“Š Get Game Info');
112
+ console.log('');
113
+ console.log(colors.bright + '═══ GAME PLAY ═══' + colors.reset);
114
+ console.log('5. šŸ‘„ Player Joins Game');
115
+ console.log('6. šŸ—³ļø Cast Vote (NEW!)');
116
+ console.log('7. šŸ† Distribute by Vote (NEW!)');
117
+ console.log('8. šŸ’° Distribute Manually');
118
+ console.log('');
119
+ console.log(colors.bright + '═══ QUICK TESTS ═══' + colors.reset);
120
+ console.log('9. šŸš€ Full Voting Test (Auto)');
121
+ console.log('10. šŸŽ² 3-Way Tie Test');
122
+ console.log('');
123
+ console.log('0. āŒ Exit');
124
+ console.log('');
125
+ }
126
+
127
+ // 1. List wallets
128
+ async function listWallets() {
129
+ try {
130
+ info('Fetching demo wallets...');
131
+ const response = await fetch(`${BASE_URL}/api/v1/demo/wallets`);
132
+ const data = await response.json();
133
+
134
+ console.log('');
135
+ console.log(colors.bright + 'šŸ‘„ Demo Wallets:' + colors.reset);
136
+ console.log('─────────────────────────────────────────────────────');
137
+ data.wallets.forEach(wallet => {
138
+ console.log(`${colors.yellow}${wallet.name.padEnd(12)}${colors.reset} ${wallet.address} ${colors.green}(${wallet.balance} SOL)${colors.reset}`);
139
+ playerAddresses[wallet.name] = wallet.address;
140
+ });
141
+ console.log('');
142
+ } catch (err) {
143
+ error('Failed to fetch wallets: ' + err.message);
144
+ }
145
+ }
146
+
147
+ // 2. Airdrop
148
+ async function airdrop() {
149
+ const player = await ask('Enter player name (alice/bob/charlie): ');
150
+
151
+ try {
152
+ info(`Airdropping 2 SOL to ${player}...`);
153
+ const response = await fetch(`${BASE_URL}/api/v1/demo/airdrop`, {
154
+ method: 'POST',
155
+ headers: { 'Content-Type': 'application/json' },
156
+ body: JSON.stringify({ player })
157
+ });
158
+ const data = await response.json();
159
+
160
+ if (data.success) {
161
+ success(data.message);
162
+ console.log(` Balance: ${data.balance} SOL`);
163
+ } else {
164
+ error(data.error || 'Airdrop failed');
165
+ }
166
+ } catch (err) {
167
+ error('Failed: ' + err.message);
168
+ }
169
+ }
170
+
171
+ // 3. Create game
172
+ async function createGame() {
173
+ console.log('');
174
+ const player = await ask('Creator name (default: creator): ') || 'creator';
175
+ const buyIn = await ask('Buy-in amount (default: 0.5 SOL): ') || '0.5';
176
+ const maxPlayers = await ask('Max players (default: 3): ') || '3';
177
+ const operatorFee = await ask('Operator fee % (default: 10): ') || '10';
178
+ const operatorPlayer = await ask('Operator player (default: jelli): ') || 'jelli';
179
+
180
+ try {
181
+ info('Creating game...');
182
+ const response = await fetch(`${BASE_URL}/api/v1/demo/game/create`, {
183
+ method: 'POST',
184
+ headers: { 'Content-Type': 'application/json' },
185
+ body: JSON.stringify({
186
+ player,
187
+ buyIn: parseFloat(buyIn),
188
+ maxPlayers: parseInt(maxPlayers),
189
+ operatorFee: parseInt(operatorFee),
190
+ operatorPlayer
191
+ })
192
+ });
193
+ const data = await response.json();
194
+
195
+ if (data.success) {
196
+ success('Game created!');
197
+ console.log(` Game ID: ${colors.yellow}${data.gameId}${colors.reset}`);
198
+ console.log(` Transaction: ${data.transaction}`);
199
+ currentGameId = data.gameId;
200
+ } else {
201
+ error(data.error || 'Create failed');
202
+ }
203
+ } catch (err) {
204
+ error('Failed: ' + err.message);
205
+ }
206
+ }
207
+
208
+ // 4. Get game info
209
+ async function getGameInfo() {
210
+ const gameId = await ask(`Game ID (current: ${currentGameId || 'none'}): `) || currentGameId;
211
+
212
+ if (!gameId) {
213
+ error('No game ID provided');
214
+ return;
215
+ }
216
+
217
+ try {
218
+ info('Fetching game info...');
219
+ const response = await fetch(`${BASE_URL}/api/v1/demo/game/${gameId}`);
220
+ const data = await response.json();
221
+
222
+ if (data.exists) {
223
+ console.log('');
224
+ printJson(data);
225
+
226
+ // Save player addresses
227
+ if (data.game.players) {
228
+ data.game.players.forEach((addr, i) => {
229
+ playerAddresses[`player${i}`] = addr;
230
+ });
231
+ }
232
+
233
+ // Show voting status
234
+ if (data.game.votingEnabled) {
235
+ console.log('');
236
+ info(`Voting: ${data.game.votes?.length || 0} votes cast`);
237
+ const majority = Math.floor(data.game.players.length / 2) + 1;
238
+ console.log(` Majority needed: ${majority}`);
239
+ if (data.game.votes?.length >= majority) {
240
+ success('Majority reached! Can distribute by vote!');
241
+ }
242
+ }
243
+ } else {
244
+ error('Game not found');
245
+ }
246
+ } catch (err) {
247
+ error('Failed: ' + err.message);
248
+ }
249
+ }
250
+
251
+ // 5. Join game
252
+ async function joinGame() {
253
+ const gameId = await ask(`Game ID (current: ${currentGameId || 'none'}): `) || currentGameId;
254
+ const player = await ask('Player name (alice/bob/charlie): ');
255
+
256
+ if (!gameId || !player) {
257
+ error('Game ID and player name required');
258
+ return;
259
+ }
260
+
261
+ try {
262
+ info(`${player} joining game...`);
263
+ const response = await fetch(`${BASE_URL}/api/v1/demo/game/join`, {
264
+ method: 'POST',
265
+ headers: { 'Content-Type': 'application/json' },
266
+ body: JSON.stringify({ player, gameId })
267
+ });
268
+ const data = await response.json();
269
+
270
+ if (data.success) {
271
+ success(data.message);
272
+ console.log(` Players: ${data.playersCount}`);
273
+ console.log(` Transaction: ${data.transaction}`);
274
+ } else {
275
+ error(data.error || 'Join failed');
276
+ }
277
+ } catch (err) {
278
+ error('Failed: ' + err.message);
279
+ }
280
+ }
281
+
282
+ // 6. Cast vote
283
+ async function castVote() {
284
+ const gameId = await ask(`Game ID (current: ${currentGameId || 'none'}): `) || currentGameId;
285
+ const player = await ask('Voter name (creator/alice/bob): ');
286
+
287
+ console.log('');
288
+ console.log('Available addresses:');
289
+ Object.keys(playerAddresses).forEach(name => {
290
+ console.log(` ${name}: ${playerAddresses[name]}`);
291
+ });
292
+ console.log('');
293
+
294
+ const votedFor = await ask('Vote for address (paste from above): ');
295
+
296
+ if (!gameId || !player || !votedFor) {
297
+ error('All fields required');
298
+ return;
299
+ }
300
+
301
+ try {
302
+ info(`${player} casting vote...`);
303
+ const response = await fetch(`${BASE_URL}/api/v1/demo/vote/cast`, {
304
+ method: 'POST',
305
+ headers: { 'Content-Type': 'application/json' },
306
+ body: JSON.stringify({ player, gameId, votedFor })
307
+ });
308
+ const data = await response.json();
309
+
310
+ if (data.success) {
311
+ success(data.message);
312
+ console.log(` Votes: ${data.votingProgress}`);
313
+ console.log(` Transaction: ${data.transaction}`);
314
+ } else {
315
+ error(data.error || 'Vote failed');
316
+ }
317
+ } catch (err) {
318
+ error('Failed: ' + err.message);
319
+ }
320
+ }
321
+
322
+ // 7. Distribute by vote
323
+ async function distributeByVote() {
324
+ const gameId = await ask(`Game ID (current: ${currentGameId || 'none'}): `) || currentGameId;
325
+
326
+ if (!gameId) {
327
+ error('Game ID required');
328
+ return;
329
+ }
330
+
331
+ try {
332
+ info('Distributing by vote...');
333
+ const response = await fetch(`${BASE_URL}/api/v1/demo/vote/distribute`, {
334
+ method: 'POST',
335
+ headers: { 'Content-Type': 'application/json' },
336
+ body: JSON.stringify({ gameId })
337
+ });
338
+ const data = await response.json();
339
+
340
+ if (data.success) {
341
+ success(data.message);
342
+ console.log('');
343
+ console.log(colors.bright + 'šŸ† Winners:' + colors.reset);
344
+ data.winners.forEach(w => console.log(` ${w}`));
345
+ console.log('');
346
+ console.log(colors.bright + 'šŸ“Š Vote Counts:' + colors.reset);
347
+ Object.entries(data.voteCounts).forEach(([addr, count]) => {
348
+ console.log(` ${addr.slice(0, 8)}... : ${count} votes`);
349
+ });
350
+ console.log('');
351
+ console.log(` Transaction: ${data.transaction}`);
352
+ } else {
353
+ error(data.error || 'Distribution failed');
354
+ }
355
+ } catch (err) {
356
+ error('Failed: ' + err.message);
357
+ }
358
+ }
359
+
360
+ // 8. Distribute manually
361
+ async function distributeManually() {
362
+ const gameId = await ask(`Game ID (current: ${currentGameId || 'none'}): `) || currentGameId;
363
+
364
+ console.log('');
365
+ console.log('Available addresses:');
366
+ Object.keys(playerAddresses).forEach(name => {
367
+ console.log(` ${name}: ${playerAddresses[name]}`);
368
+ });
369
+ console.log('');
370
+
371
+ const winners = await ask('Winner addresses (comma-separated): ');
372
+
373
+ if (!gameId || !winners) {
374
+ error('Game ID and winners required');
375
+ return;
376
+ }
377
+
378
+ const winnerArray = winners.split(',').map(w => w.trim());
379
+
380
+ try {
381
+ info('Distributing winnings...');
382
+ const response = await fetch(`${BASE_URL}/api/v1/demo/game/distribute`, {
383
+ method: 'POST',
384
+ headers: { 'Content-Type': 'application/json' },
385
+ body: JSON.stringify({ gameId, winners: winnerArray })
386
+ });
387
+ const data = await response.json();
388
+
389
+ if (data.success) {
390
+ success(data.message);
391
+ printJson(data);
392
+ } else {
393
+ error(data.error || 'Distribution failed');
394
+ }
395
+ } catch (err) {
396
+ error('Failed: ' + err.message);
397
+ }
398
+ }
399
+
400
+ // 9. Full voting test (automated)
401
+ async function fullVotingTest() {
402
+ console.log('');
403
+ console.log(colors.bright + 'šŸš€ Running Full Voting Test...' + colors.reset);
404
+ console.log('');
405
+
406
+ try {
407
+ // Create game
408
+ info('1/7 Creating game...');
409
+ let response = await fetch(`${BASE_URL}/api/v1/demo/game/create`, {
410
+ method: 'POST',
411
+ headers: { 'Content-Type': 'application/json' },
412
+ body: JSON.stringify({
413
+ player: 'creator',
414
+ buyIn: 0.5,
415
+ maxPlayers: 3,
416
+ operatorFee: 10,
417
+ operatorPlayer: 'jelli'
418
+ })
419
+ });
420
+ let data = await response.json();
421
+ const gameId = data.gameId;
422
+ currentGameId = gameId;
423
+ success(`Game created: ${gameId}`);
424
+
425
+ // Creator joins (must join to play!)
426
+ info('2/7 Creator joining...');
427
+ response = await fetch(`${BASE_URL}/api/v1/demo/game/join`, {
428
+ method: 'POST',
429
+ headers: { 'Content-Type': 'application/json' },
430
+ body: JSON.stringify({ player: 'creator', gameId })
431
+ });
432
+ data = await response.json();
433
+ if (!data.success) {
434
+ error(`Creator join failed: ${data.error}`);
435
+ throw new Error('Creator failed to join');
436
+ }
437
+ success(data.message);
438
+
439
+ // Alice joins
440
+ info('3/7 Alice joining...');
441
+ response = await fetch(`${BASE_URL}/api/v1/demo/game/join`, {
442
+ method: 'POST',
443
+ headers: { 'Content-Type': 'application/json' },
444
+ body: JSON.stringify({ player: 'alice', gameId })
445
+ });
446
+ data = await response.json();
447
+ if (!data.success) {
448
+ error(`Alice join failed: ${data.error}`);
449
+ throw new Error('Alice failed to join');
450
+ }
451
+ success(data.message);
452
+
453
+ // Bob joins
454
+ info('4/7 Bob joining...');
455
+ response = await fetch(`${BASE_URL}/api/v1/demo/game/join`, {
456
+ method: 'POST',
457
+ headers: { 'Content-Type': 'application/json' },
458
+ body: JSON.stringify({ player: 'bob', gameId })
459
+ });
460
+ data = await response.json();
461
+ if (!data.success) {
462
+ error(`Bob join failed: ${data.error}`);
463
+ throw new Error('Bob failed to join');
464
+ }
465
+ success(data.message);
466
+
467
+ // Get game info to get addresses
468
+ info('5/8 Getting player addresses...');
469
+ response = await fetch(`${BASE_URL}/api/v1/demo/game/${gameId}`);
470
+ data = await response.json();
471
+
472
+ if (!data.game || !data.game.players || data.game.players.length < 3) {
473
+ error(`Expected 3 players, got ${data.game?.players?.length || 0}`);
474
+ console.log('Players:', data.game?.players);
475
+ throw new Error(`Expected 3 players, got ${data.game?.players?.length || 0}`);
476
+ }
477
+
478
+ const creatorAddr = data.game.players[0]; // Creator is 1st
479
+ const aliceAddr = data.game.players[1]; // Alice is 2nd
480
+ const bobAddr = data.game.players[2]; // Bob is 3rd
481
+
482
+ success(`Creator: ${creatorAddr.slice(0, 8)}...`);
483
+ success(`Alice: ${aliceAddr.slice(0, 8)}...`);
484
+ success(`Bob: ${bobAddr.slice(0, 8)}...`);
485
+ info(`Total players: ${data.game.players.length}`);
486
+
487
+ // Creator votes for Alice
488
+ info('6/8 Creator voting for Alice...');
489
+ response = await fetch(`${BASE_URL}/api/v1/demo/vote/cast`, {
490
+ method: 'POST',
491
+ headers: { 'Content-Type': 'application/json' },
492
+ body: JSON.stringify({ player: 'creator', gameId, votedFor: aliceAddr })
493
+ });
494
+ data = await response.json();
495
+
496
+ if (!data.success) {
497
+ error(`Creator vote failed: ${data.error || 'Unknown error'}`);
498
+ throw new Error('Creator vote failed');
499
+ }
500
+
501
+ success(`${data.message} (${data.votingProgress})`);
502
+
503
+ // Alice votes for herself
504
+ info('7/8 Alice voting for herself...');
505
+ response = await fetch(`${BASE_URL}/api/v1/demo/vote/cast`, {
506
+ method: 'POST',
507
+ headers: { 'Content-Type': 'application/json' },
508
+ body: JSON.stringify({ player: 'alice', gameId, votedFor: aliceAddr })
509
+ });
510
+ data = await response.json();
511
+
512
+ if (!data.success) {
513
+ error(`Alice vote failed: ${data.error || 'Unknown error'}`);
514
+ throw new Error('Alice vote failed');
515
+ }
516
+
517
+ const votesNeeded = Math.floor(3 / 2) + 1; // 3 players = need 2 votes
518
+ const hasMajority = data.votesCount >= votesNeeded;
519
+ success(`${data.message} (${data.votingProgress})${hasMajority ? ' - MAJORITY REACHED!' : ''}`);
520
+
521
+ // Distribute by vote
522
+ info('8/8 Distributing by vote...');
523
+ response = await fetch(`${BASE_URL}/api/v1/demo/vote/distribute`, {
524
+ method: 'POST',
525
+ headers: { 'Content-Type': 'application/json' },
526
+ body: JSON.stringify({ gameId })
527
+ });
528
+ data = await response.json();
529
+
530
+ if (data.success) {
531
+ console.log('');
532
+ success('šŸŽ‰ DEMOCRACY WINS!');
533
+ console.log('');
534
+ console.log(colors.bright + 'šŸ† Winners:' + colors.reset);
535
+ data.winners.forEach(w => console.log(` ${w}`));
536
+ console.log('');
537
+ console.log(colors.bright + 'šŸ“Š Vote Results:' + colors.reset);
538
+ Object.entries(data.voteCounts).forEach(([addr, count]) => {
539
+ console.log(` ${addr.slice(0, 8)}... : ${colors.green}${count} votes${colors.reset}`);
540
+ });
541
+ console.log('');
542
+ console.log(` Transaction: ${data.transaction}`);
543
+ }
544
+
545
+ } catch (err) {
546
+ error('Test failed: ' + err.message);
547
+ }
548
+ }
549
+
550
+ // 10. Three-way tie test
551
+ async function threeWayTieTest() {
552
+ console.log('');
553
+ console.log(colors.bright + 'šŸŽ² Running 3-Way Tie Test...' + colors.reset);
554
+ console.log('');
555
+
556
+ try {
557
+ // Create game
558
+ info('Creating game...');
559
+ let response = await fetch(`${BASE_URL}/api/v1/demo/game/create`, {
560
+ method: 'POST',
561
+ headers: { 'Content-Type': 'application/json' },
562
+ body: JSON.stringify({
563
+ player: 'creator',
564
+ buyIn: 0.5,
565
+ maxPlayers: 3,
566
+ operatorFee: 10,
567
+ operatorPlayer: 'jelli'
568
+ })
569
+ });
570
+ let data = await response.json();
571
+ const gameId = data.gameId;
572
+ currentGameId = gameId;
573
+ success(`Game created: ${gameId}`);
574
+
575
+ // Players join
576
+ info('Players joining...');
577
+ await fetch(`${BASE_URL}/api/v1/demo/game/join`, {
578
+ method: 'POST',
579
+ headers: { 'Content-Type': 'application/json' },
580
+ body: JSON.stringify({ player: 'alice', gameId })
581
+ });
582
+ await fetch(`${BASE_URL}/api/v1/demo/game/join`, {
583
+ method: 'POST',
584
+ headers: { 'Content-Type': 'application/json' },
585
+ body: JSON.stringify({ player: 'bob', gameId })
586
+ });
587
+ success('All players joined');
588
+
589
+ // Get addresses
590
+ response = await fetch(`${BASE_URL}/api/v1/demo/game/${gameId}`);
591
+ data = await response.json();
592
+ const [creatorAddr, aliceAddr, bobAddr] = data.game.players;
593
+
594
+ // Each votes for themselves (3-way tie!)
595
+ info('Each player votes for themselves (3-way tie)...');
596
+ await fetch(`${BASE_URL}/api/v1/demo/vote/cast`, {
597
+ method: 'POST',
598
+ headers: { 'Content-Type': 'application/json' },
599
+ body: JSON.stringify({ player: 'creator', gameId, votedFor: creatorAddr })
600
+ });
601
+ await fetch(`${BASE_URL}/api/v1/demo/vote/cast`, {
602
+ method: 'POST',
603
+ headers: { 'Content-Type': 'application/json' },
604
+ body: JSON.stringify({ player: 'alice', gameId, votedFor: aliceAddr })
605
+ });
606
+ await fetch(`${BASE_URL}/api/v1/demo/vote/cast`, {
607
+ method: 'POST',
608
+ headers: { 'Content-Type': 'application/json' },
609
+ body: JSON.stringify({ player: 'bob', gameId, votedFor: bobAddr })
610
+ });
611
+ success('All votes cast - each player has 1 vote (TIE!)');
612
+
613
+ // Distribute
614
+ info('Distributing (should split 3 ways)...');
615
+ response = await fetch(`${BASE_URL}/api/v1/demo/vote/distribute`, {
616
+ method: 'POST',
617
+ headers: { 'Content-Type': 'application/json' },
618
+ body: JSON.stringify({ gameId })
619
+ });
620
+ data = await response.json();
621
+
622
+ if (data.success) {
623
+ console.log('');
624
+ success('šŸŽ‰ 3-WAY TIE HANDLED!');
625
+ console.log(` Winners: ${data.winnersCount} (all 3!)`);
626
+ console.log(` Each gets: 33.33% of prize pool`);
627
+ }
628
+
629
+ } catch (err) {
630
+ error('Test failed: ' + err.message);
631
+ }
632
+ }
633
+
634
+ // Main loop
635
+ async function main() {
636
+ console.log(colors.bright + colors.green);
637
+ console.log('╔═══════════════════════════════════════════════════════╗');
638
+ console.log('ā•‘ šŸŽ® Welcome to Dubs CLI Testing Tool šŸŽ® ā•‘');
639
+ console.log('ā•‘ ā•‘');
640
+ console.log('ā•‘ Test democratic voting without Postman! ā•‘');
641
+ console.log('ā•šā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•ā•');
642
+ console.log(colors.reset);
643
+ console.log('');
644
+
645
+ // Test connection
646
+ try {
647
+ const response = await fetch(`${BASE_URL}/health`);
648
+ const data = await response.json();
649
+ success(`Connected to server: ${data.message}`);
650
+ } catch (err) {
651
+ error(`Cannot connect to server at ${BASE_URL}`);
652
+ console.log('');
653
+ console.log('Make sure the server is running:');
654
+ console.log(' cd dubs-server && node server.js');
655
+ process.exit(1);
656
+ }
657
+
658
+ console.log('');
659
+ await ask('Press Enter to continue...');
660
+
661
+ while (true) {
662
+ await showMenu();
663
+ const choice = await ask('Select option: ');
664
+ console.log('');
665
+
666
+ switch (choice) {
667
+ case '1':
668
+ await listWallets();
669
+ break;
670
+ case '2':
671
+ await airdrop();
672
+ break;
673
+ case '3':
674
+ await createGame();
675
+ break;
676
+ case '4':
677
+ await getGameInfo();
678
+ break;
679
+ case '5':
680
+ await joinGame();
681
+ break;
682
+ case '6':
683
+ await castVote();
684
+ break;
685
+ case '7':
686
+ await distributeByVote();
687
+ break;
688
+ case '8':
689
+ await distributeManually();
690
+ break;
691
+ case '9':
692
+ await fullVotingTest();
693
+ break;
694
+ case '10':
695
+ await threeWayTieTest();
696
+ break;
697
+ case '0':
698
+ console.log(colors.green + '\nšŸ‘‹ Goodbye! Happy gaming! šŸŽ®\n' + colors.reset);
699
+ rl.close();
700
+ process.exit(0);
701
+ default:
702
+ error('Invalid option');
703
+ }
704
+
705
+ console.log('');
706
+ await ask('Press Enter to continue...');
707
+ }
708
+ }
709
+
710
+ // Handle exit
711
+ rl.on('close', () => {
712
+ process.exit(0);
713
+ });
714
+
715
+ // Run
716
+ main().catch(err => {
717
+ console.error('Fatal error:', err);
718
+ process.exit(1);
719
+ });
720
+