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.
- package/.claude/settings.local.json +280 -0
- package/CLAUDE.md +46 -0
- package/CONNECT4_PRODUCTION_DEPLOY.md +155 -0
- package/CURRENT_SESSION.md +171 -0
- package/CURRENT_SESSION_DRAW.md +516 -0
- package/MARCH_MADNESS_SURVIVOR.md +254 -0
- package/PANDA.md +166 -0
- package/Procfile +4 -0
- package/README.md +476 -0
- package/controllers/livescoresController.js +376 -0
- package/controllers/pickemController.js +554 -0
- package/controllers/survivorAdminController.js +887 -0
- package/controllers/survivorController.js +623 -0
- package/cron/oracleMonitor.js +77 -0
- package/cron/pickemOracleMonitor.js +73 -0
- package/data/jackpot-history.json +952 -0
- package/data/ncaaTeams.js +406 -0
- package/documentation/API_SECURITY_GUIDE.md +327 -0
- package/documentation/ARCADE_API.md +593 -0
- package/documentation/ARCADE_IMPLEMENTATION_SUMMARY.md +399 -0
- package/documentation/ARCADE_QUICKSTART.md +242 -0
- package/documentation/AUTOMATIC_MODE_ORACLE.md +321 -0
- package/documentation/BUG_FIX_COHORT_DATE_DISPLAY.md +171 -0
- package/documentation/CLAIM_MIGRATION_INSTRUCTIONS.md +52 -0
- package/documentation/CLAIM_STATUS_FIX.md +67 -0
- package/documentation/CLI_TOOL_GUIDE.md +372 -0
- package/documentation/COHORT_RETENTION_ANALYSIS.md +295 -0
- package/documentation/COHORT_RETENTION_IMPLEMENTATION_COMPLETE.md +461 -0
- package/documentation/COHORT_RETENTION_SUMMARY.md +204 -0
- package/documentation/COMPLETE_PROJECT_SUMMARY.md +490 -0
- package/documentation/DATABASE_QUERIES.md +269 -0
- package/documentation/DATABASE_RETENTION_POLICY.md +390 -0
- package/documentation/DATABASE_SETUP_GUIDE.md +361 -0
- package/documentation/DATABASE_SETUP_SUMMARY.md +247 -0
- package/documentation/DEMO_API_CURL_COMMANDS.md +656 -0
- package/documentation/DEPLOYMENT_SUMMARY.txt +100 -0
- package/documentation/DUPLICATE_NOTIFICATIONS_FIXED.md +201 -0
- package/documentation/EXCHANGE_RATES_INTEGRATION.md +371 -0
- package/documentation/FINAL_API_PROTECTION_TABLE.md +175 -0
- package/documentation/GAME_START_NOTIFICATIONS_DEPLOYMENT.md +256 -0
- package/documentation/GAME_START_NOTIFICATIONS_INTEGRATION.md +275 -0
- package/documentation/HEROKU_DEPLOYMENT.md +134 -0
- package/documentation/HEROKU_SCHEDULER_SETUP.md +271 -0
- package/documentation/JACKPOT_API.md +521 -0
- package/documentation/JACKPOT_DEPLOYMENT_GUIDE.md +362 -0
- package/documentation/JWT_IMPLEMENTATION_SUMMARY.md +373 -0
- package/documentation/JWT_QUICK_SETUP.md +268 -0
- package/documentation/JWT_TESTING_GUIDE.md +404 -0
- package/documentation/KEEPER_RECOVERY_GUIDE.md +381 -0
- package/documentation/KEEPER_SETUP.md +206 -0
- package/documentation/KEEPER_STATE_MACHINE.md +423 -0
- package/documentation/LATEST_PRODUCTION_SETUP.md +387 -0
- package/documentation/LOCAL_VOTING_TEST.md +279 -0
- package/documentation/ORACLE_FIXES_SUMMARY.md +188 -0
- package/documentation/ORACLE_POSTGRESQL_UPDATE.md +202 -0
- package/documentation/PAYMENT_DEPLOYMENT.md +209 -0
- package/documentation/PNL_TRACKING_SETUP.md +189 -0
- package/documentation/PREVENTING_LOCKUP_ERRORS.md +472 -0
- package/documentation/PRODUCTION_READY_SUMMARY.md +227 -0
- package/documentation/PUBLIC_VS_PRIVATE_ENDPOINTS.md +278 -0
- package/documentation/QUICK_AUTH_SETUP.md +99 -0
- package/documentation/QUICK_DEPLOY.md +224 -0
- package/documentation/QUICK_FIX.md +114 -0
- package/documentation/QUICK_START.md +152 -0
- package/documentation/REFEREE_MODE_GUIDE.md +392 -0
- package/documentation/RETENTION_CORE_ACTION_UPDATE.md +313 -0
- package/documentation/RETENTION_UPDATE_SUMMARY.md +108 -0
- package/documentation/RUN_MIGRATION_NOW.md +39 -0
- package/documentation/SCRIPTS_UPDATE_SUMMARY.md +251 -0
- package/documentation/SETUP_GUIDE.md +184 -0
- package/documentation/STATE_MACHINE_IMPLEMENTATION.md +250 -0
- package/documentation/TELEGRAM_NOTIFICATIONS_DIAGNOSIS.md +361 -0
- package/documentation/UNIFIED_ARCHITECTURE.md +231 -0
- package/documentation/VOTING_DEPLOYMENT_SUMMARY.md +392 -0
- package/documentation/WEBSOCKET_ARCHITECTURE.md +881 -0
- package/documentation/WHAT_WE_BUILT_TODAY.md +369 -0
- package/documentation/latest/LATEST_PRODUCTION_SETUP.md +865 -0
- package/ecosystem.config.js +65 -0
- package/env.template +125 -0
- package/middleware/apiKeyAuth.js +136 -0
- package/middleware/authenticate.js +214 -0
- package/middleware/developerUserAuth.js +76 -0
- package/middleware/socketAuth.js +69 -0
- package/package.json +49 -0
- package/postman/Dubs-API-v1-With-Voting.postman_collection.json +555 -0
- package/postman/Dubs-API-v1.postman_collection.json +205 -0
- package/postman/Dubs_Developer_API.postman_collection.json +662 -0
- package/postman/QUICKSTART.md +118 -0
- package/postman/QUICK_REFERENCE.md +246 -0
- package/postman/README.md +71 -0
- package/postman/VOTING_API_GUIDE.md +426 -0
- package/refactor/Animations.md +148 -0
- package/refactor/Chat.md +252 -0
- package/routes/actionsRoutes.js +699 -0
- package/routes/adminRoutes.js +370 -0
- package/routes/analyticsRoutes.js +1262 -0
- package/routes/arcadeRoutes.js +557 -0
- package/routes/authRoutes.js +2310 -0
- package/routes/avatarRoutes.js +85 -0
- package/routes/botRoutes.js +211 -0
- package/routes/chatRoutes.js +377 -0
- package/routes/cryptoPriceRoutes.js +105 -0
- package/routes/developerRoutes.js +4201 -0
- package/routes/deviceRoutes.js +214 -0
- package/routes/dmRoutes.js +167 -0
- package/routes/esportsRoutes.js +806 -0
- package/routes/exchangeRateRoutes.js +233 -0
- package/routes/gamesRoutes.js +3028 -0
- package/routes/jackpotRoutes.js +754 -0
- package/routes/keeperMonitoringRoutes.js +156 -0
- package/routes/keeperWebhookRoutes.js +466 -0
- package/routes/livescoresRoutes.js +31 -0
- package/routes/pickemAdminRoutes.js +199 -0
- package/routes/pickemRoutes.js +231 -0
- package/routes/playerStatsRoutes.js +147 -0
- package/routes/portfolioRoutes.js +217 -0
- package/routes/promoRoutes.js +418 -0
- package/routes/referralEarningsRoutes.js +392 -0
- package/routes/socialRoutes.js +459 -0
- package/routes/sportsRoutes.js +1271 -0
- package/routes/survivorAdminRoutes.js +345 -0
- package/routes/survivorRoutes.js +756 -0
- package/routes/uploadRoutes.js +256 -0
- package/routes/userProfileRoutes.js +244 -0
- package/routes/whatsNewRoutes.js +331 -0
- package/scripts/.claude/settings.local.json +15 -0
- package/scripts/README.md +170 -0
- package/scripts/RESTART_EVERYTHING.sh +104 -0
- package/scripts/add-claim-columns.sql +48 -0
- package/scripts/add-crypto-prices-cache.sql +27 -0
- package/scripts/add-exchange-rates-cache.sql +40 -0
- package/scripts/add-game-invite-column.sql +23 -0
- package/scripts/add-game-invite-notification.sql +33 -0
- package/scripts/add-game-invite-telegram-pref.sql +16 -0
- package/scripts/add-game-joined-notification.sql +16 -0
- package/scripts/add-game-joined-pref.js +40 -0
- package/scripts/add-game-joined-preference.sql +6 -0
- package/scripts/add-game-start-notifications.sql +41 -0
- package/scripts/add-notification-flags-to-games.sql +55 -0
- package/scripts/add-pending-game-dismissals.sql +19 -0
- package/scripts/add-preferred-currency.sql +34 -0
- package/scripts/add-winner-columns.js +61 -0
- package/scripts/add_mention_system.sql +53 -0
- package/scripts/add_payment_system.sql +96 -0
- package/scripts/add_sports_event_id_column.sql +22 -0
- package/scripts/analyze-cohort-data-heroku.js +276 -0
- package/scripts/analyze-cohort-data.js +295 -0
- package/scripts/analyze-prod-cohorts.sh +10 -0
- package/scripts/backfill-matchup-images.js +245 -0
- package/scripts/backfill-missing-signatures.js +175 -0
- package/scripts/backfill-referral-earnings.js +202 -0
- package/scripts/check-chat-schema.js +130 -0
- package/scripts/check-db.sh +14 -0
- package/scripts/check_oracle_in_game.js +54 -0
- package/scripts/cleanup-database.js +193 -0
- package/scripts/clear-notification-cache.js +85 -0
- package/scripts/convert-mnemonic.js +50 -0
- package/scripts/create-users-table.sql +44 -0
- package/scripts/debug-cohort-counts.js +248 -0
- package/scripts/debug-winner-calc.js +84 -0
- package/scripts/deploy-payment-system.sh +118 -0
- package/scripts/deploy-to-heroku.sh +63 -0
- package/scripts/diagnose-locked-round.js +143 -0
- package/scripts/dubs-cli.js +720 -0
- package/scripts/dump-account.js +65 -0
- package/scripts/find-vrf-offset.js +48 -0
- package/scripts/fix-chat-notifications-constraint.sql +122 -0
- package/scripts/fix-claim-columns.js +124 -0
- package/scripts/fix-constraint-now.js +44 -0
- package/scripts/fix-lock-timestamps.js +96 -0
- package/scripts/fix-locked-round.sh +126 -0
- package/scripts/fix-missing-badges.sql +91 -0
- package/scripts/fix-payment-notifications.sql +41 -0
- package/scripts/force-new-round.js +55 -0
- package/scripts/force-resolve-and-claim.js +278 -0
- package/scripts/important/README.md +115 -0
- package/scripts/important/authority-force-lock.js +197 -0
- package/scripts/important/authority-resolve-game.js +267 -0
- package/scripts/important/check-game-status.js +373 -0
- package/scripts/important/list-pending-games-by-version.js +270 -0
- package/scripts/important/reconcile-v1-v2-payouts.js +270 -0
- package/scripts/initialize-jackpot.js +111 -0
- package/scripts/jackpot/.claude/settings.local.json +10 -0
- package/scripts/jackpot/force-reset.js +84 -0
- package/scripts/jackpot/initialize-mainnet.js +100 -0
- package/scripts/jackpot/keeper.js +742 -0
- package/scripts/jackpot/status.js +107 -0
- package/scripts/jackpot/update-round-duration.js +143 -0
- package/scripts/keeper-bot.js +112 -0
- package/scripts/list-pending-games.js +131 -0
- package/scripts/migrate-chat-v2.js +127 -0
- package/scripts/migrate-chat-winners.js +84 -0
- package/scripts/migrate-chat.sh +17 -0
- package/scripts/migrate-game-invite.js +83 -0
- package/scripts/migrate-heroku-game-notifications.sh +159 -0
- package/scripts/migrations/001_analytics_tables.sql +422 -0
- package/scripts/migrations/002_add_matchup_image_url.sql +14 -0
- package/scripts/migrations/003_referral_earnings.sql +208 -0
- package/scripts/migrations/004_add_whats_new_notification_type.sql +62 -0
- package/scripts/migrations/005_add_connect4_your_turn_notification.sql +61 -0
- package/scripts/migrations/005_push_notifications.sql +55 -0
- package/scripts/migrations/006_add_draw_team_players.sql +28 -0
- package/scripts/migrations/006_add_game_cancelled_notification.sql +62 -0
- package/scripts/migrations/007_add_gif_url.sql +8 -0
- package/scripts/migrations/008_add_connect4_columns.sql +139 -0
- package/scripts/migrations/008_add_pool_tracking.sql +22 -0
- package/scripts/migrations/009_create_survivor_pool_tables.sql +174 -0
- package/scripts/migrations/010_add_survivor_pool_outcome.sql +28 -0
- package/scripts/migrations/011_create_developer_tables.sql +67 -0
- package/scripts/migrations/011_fix_keeper_tables.sql +85 -0
- package/scripts/migrations/012_create_developer_webhooks.sql +31 -0
- package/scripts/migrations/013_add_network_mode.sql +18 -0
- package/scripts/migrations/014_create_developer_app_users.sql +19 -0
- package/scripts/migrations/015_add_ui_config.sql +4 -0
- package/scripts/migrations/016_add_resolution_secret.sql +4 -0
- package/scripts/migrations/017_add_external_game_id.sql +3 -0
- package/scripts/migrations/018_create_pickem_tables.sql +115 -0
- package/scripts/migrations/019_expo_push_tokens.sql +19 -0
- package/scripts/migrations/create_whats_new_tables.sql +88 -0
- package/scripts/migrations/drop_live_games_tables.sql +34 -0
- package/scripts/open-jackpot-round.js +85 -0
- package/scripts/purge-all-data.sh +329 -0
- package/scripts/purge-all-data.sql +142 -0
- package/scripts/purge-heroku-data.sh +149 -0
- package/scripts/purge-heroku-data.sql +62 -0
- package/scripts/rebuild-heroku-database.sh +113 -0
- package/scripts/recover-funds.js +357 -0
- package/scripts/regenerate-epl-images.js +278 -0
- package/scripts/resize-s3-matchup-images.js +374 -0
- package/scripts/resolve-direct.js +88 -0
- package/scripts/resolve-mock-game.js +124 -0
- package/scripts/resolve-pickem-game.js +55 -0
- package/scripts/resolve-round-manual.js +83 -0
- package/scripts/resolve-stuck-game.js +382 -0
- package/scripts/resolve-stuck-round.js +42 -0
- package/scripts/run-connect4-migration.sh +16 -0
- package/scripts/run-mention-migration.sh +32 -0
- package/scripts/run-payment-migration.sh +51 -0
- package/scripts/run-preferred-currency-migration.sh +31 -0
- package/scripts/run-referral-earnings-migration.sh +32 -0
- package/scripts/run-survivor-outcome-migration.sh +16 -0
- package/scripts/seed-test-users.js +346 -0
- package/scripts/setup-auth-tables.js +78 -0
- package/scripts/setup-complete-database.sql +992 -0
- package/scripts/setup-database-fresh.sh +359 -0
- package/scripts/setup-heroku-keeper.sh +48 -0
- package/scripts/setup-keeper-database.js +83 -0
- package/scripts/setup-keeper-state-db.sql +110 -0
- package/scripts/setup-oracle.sh +39 -0
- package/scripts/setup-pnl-tracking.js +111 -0
- package/scripts/start-devnet.sh +14 -0
- package/scripts/test-arcade-devnet.sh +160 -0
- package/scripts/test-arcade-match.sh +109 -0
- package/scripts/test-automatic-mode.sh +239 -0
- package/scripts/test-connect4-cancel-claim.js +370 -0
- package/scripts/test-connect4-e2e.js +369 -0
- package/scripts/test-connect4-resolve.js +369 -0
- package/scripts/test-game-state-endpoint.js +136 -0
- package/scripts/test-invite-notification.js +86 -0
- package/scripts/test-jackpot-api.sh +71 -0
- package/scripts/test-poll-confirmation.js +267 -0
- package/scripts/test-resolve-game.js +271 -0
- package/scripts/test-resolve-signature.js +223 -0
- package/scripts/test-signature-preservation.js +124 -0
- package/scripts/test-state-machine.js +291 -0
- package/scripts/test-webhook-receiver.js +60 -0
- package/scripts/update-notification-constraint.js +52 -0
- package/scripts/verify-account-layout.js +145 -0
- package/scripts/verify-winner-algorithm.js +278 -0
- package/server.js +5259 -0
- package/services/arcadeMatchService.js +763 -0
- package/services/automaticGameOracle.js +1596 -0
- package/services/chatService.js +1612 -0
- package/services/connect4GameService.js +1049 -0
- package/services/connect4NotificationService.js +374 -0
- package/services/cryptoPriceService.js +223 -0
- package/services/customGameResolver.js +260 -0
- package/services/db.js +79 -0
- package/services/directMessageService.js +389 -0
- package/services/discordNotifications.js +160 -0
- package/services/exchangeRateService.js +289 -0
- package/services/expoPushService.js +314 -0
- package/services/gamesCacheService.js +539 -0
- package/services/jackpotHistory.js +331 -0
- package/services/jackpotService.js +856 -0
- package/services/keeperStateService.js +355 -0
- package/services/matchupImageService.js +591 -0
- package/services/notificationCacheService.js +407 -0
- package/services/pickemOracle.js +440 -0
- package/services/playerStatsService.js +389 -0
- package/services/portfolioService.js +555 -0
- package/services/promoService.js +757 -0
- package/services/promoTreasuryService.js +239 -0
- package/services/pushNotifications.js +353 -0
- package/services/redisService.js +422 -0
- package/services/referralEarningsService.js +728 -0
- package/services/s3Service.js +396 -0
- package/services/socialService.js +1202 -0
- package/services/survivorOracle.js +469 -0
- package/services/survivorSimulator.js +475 -0
- package/services/telegramNotifications.js +461 -0
- package/services/userProfileStatsService.js +1185 -0
- package/services/whatsNewService.js +388 -0
- package/utils/urlHelper.js +95 -0
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom Game Resolver Service
|
|
3
|
+
*
|
|
4
|
+
* Handles on-chain resolution of developer custom games (game_mode=6).
|
|
5
|
+
* Extracted from AutomaticGameOracle to avoid circular dependencies
|
|
6
|
+
* with the polling loop / ESPN / PandaScore integration.
|
|
7
|
+
*
|
|
8
|
+
* Shares the same oracle keypair, program ID, and transaction format.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const { Connection, Keypair, PublicKey, Transaction, TransactionInstruction } = require('@solana/web3.js');
|
|
12
|
+
const crypto = require('crypto');
|
|
13
|
+
const fs = require('fs');
|
|
14
|
+
const path = require('path');
|
|
15
|
+
const axios = require('axios');
|
|
16
|
+
|
|
17
|
+
// Must match the contract's hardcoded address
|
|
18
|
+
const OPERATOR_WALLET = new PublicKey('BVZXwZpfgyzTBdRFHohkHZppPHnAyqyctRsKy3vWfQib');
|
|
19
|
+
|
|
20
|
+
// resolve_automatic_game discriminator
|
|
21
|
+
const RESOLVE_AUTO = Buffer.from([245, 33, 115, 150, 82, 150, 28, 193]);
|
|
22
|
+
|
|
23
|
+
// authority_force_lock discriminator — sha256("global:authority_force_lock")[0..8]
|
|
24
|
+
// Sets lock_timestamp = now so resolve_automatic_game passes the lock check
|
|
25
|
+
const AUTHORITY_FORCE_LOCK = Buffer.from([186, 206, 107, 11, 185, 154, 217, 166]);
|
|
26
|
+
|
|
27
|
+
class CustomGameResolver {
|
|
28
|
+
constructor() {
|
|
29
|
+
const rpcUrl = process.env.SOLANA_NETWORK || 'https://api.devnet.solana.com';
|
|
30
|
+
const programId = process.env.PROGRAM_ID || '85wJGp9uc8w2FeKX9CEHsudTo1UVCrmuRFy37oCcaoG1';
|
|
31
|
+
|
|
32
|
+
this.connection = new Connection(rpcUrl, 'confirmed');
|
|
33
|
+
this.programId = new PublicKey(programId);
|
|
34
|
+
this.oracleKeypair = this._loadOracleKeypair();
|
|
35
|
+
this.dubsServerUrl = process.env.DUBS_SERVER_URL || `http://localhost:${process.env.PORT || 3001}`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Load oracle keypair from env var or wallet file.
|
|
40
|
+
* Returns null if unavailable (resolve calls will fail gracefully).
|
|
41
|
+
*/
|
|
42
|
+
_loadOracleKeypair() {
|
|
43
|
+
try {
|
|
44
|
+
if (process.env.ORACLE_WALLET_JSON) {
|
|
45
|
+
const secretKey = JSON.parse(process.env.ORACLE_WALLET_JSON);
|
|
46
|
+
return Keypair.fromSecretKey(Uint8Array.from(secretKey));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const walletPath = process.env.ORACLE_WALLET_PATH || path.join(__dirname, '../wallets/oracle.json');
|
|
50
|
+
if (fs.existsSync(walletPath)) {
|
|
51
|
+
const secretKey = JSON.parse(fs.readFileSync(walletPath, 'utf-8'));
|
|
52
|
+
return Keypair.fromSecretKey(Uint8Array.from(secretKey));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
console.warn('[CustomGameResolver] No oracle keypair found — resolve will be disabled');
|
|
56
|
+
return null;
|
|
57
|
+
} catch (err) {
|
|
58
|
+
console.error('[CustomGameResolver] Failed to load oracle keypair:', err.message);
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Resolve a custom game on-chain and update the database.
|
|
65
|
+
*
|
|
66
|
+
* @param {string} gameId - The game ID
|
|
67
|
+
* @param {string|null} winner - 'home', 'away', 'draw', or null (refund)
|
|
68
|
+
* @param {object} [metadata] - Optional proof/metadata from developer
|
|
69
|
+
* @returns {{ signature: string }} - On-chain transaction signature
|
|
70
|
+
*/
|
|
71
|
+
async resolveGame(gameId, winner, metadata = null) {
|
|
72
|
+
if (!this.oracleKeypair) {
|
|
73
|
+
throw new Error('Oracle keypair not loaded — cannot resolve games');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
console.log(`\n[CustomGameResolver] Resolving game: ${gameId}`);
|
|
77
|
+
console.log(` Winner: ${winner === null ? 'null (REFUND)' : winner}`);
|
|
78
|
+
|
|
79
|
+
// Look up referrer for commission payout
|
|
80
|
+
const referrerWallet = await this.getGameCreatorReferrer(gameId);
|
|
81
|
+
if (referrerWallet) {
|
|
82
|
+
console.log(` Referrer: ${referrerWallet}`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Build + send on-chain resolve transaction
|
|
86
|
+
const signature = await this.buildAndSendResolveTransaction(gameId, winner, referrerWallet);
|
|
87
|
+
console.log(` On-chain signature: ${signature}`);
|
|
88
|
+
|
|
89
|
+
// Update PostgreSQL
|
|
90
|
+
await this.updateGameInPostgreSQL(gameId, winner, signature);
|
|
91
|
+
|
|
92
|
+
console.log(`[CustomGameResolver] Game ${gameId} resolved successfully`);
|
|
93
|
+
return { signature };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Build and send authority_force_lock + resolve_automatic_game in a single transaction.
|
|
98
|
+
*
|
|
99
|
+
* The force-lock instruction sets lock_timestamp = now so the resolve instruction's
|
|
100
|
+
* `current_time >= lock_timestamp` check passes immediately. This lets developers
|
|
101
|
+
* resolve custom games at any time without waiting for an artificial lock timer.
|
|
102
|
+
*/
|
|
103
|
+
async buildAndSendResolveTransaction(gameId, winner, referrerWallet = null) {
|
|
104
|
+
// Derive game PDA
|
|
105
|
+
let gameIdNum;
|
|
106
|
+
if (typeof gameId === 'string' && gameId.includes('-')) {
|
|
107
|
+
const hash = crypto.createHash('sha256').update(gameId).digest();
|
|
108
|
+
gameIdNum = hash.readBigUInt64LE(0);
|
|
109
|
+
} else {
|
|
110
|
+
gameIdNum = BigInt(gameId);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const gameIdBuf = Buffer.alloc(8);
|
|
114
|
+
gameIdBuf.writeBigUInt64LE(gameIdNum);
|
|
115
|
+
|
|
116
|
+
const [gamePDA] = PublicKey.findProgramAddressSync(
|
|
117
|
+
[Buffer.from('game'), gameIdBuf],
|
|
118
|
+
this.programId
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
// Instruction 1: authority_force_lock — sets lock_timestamp = now
|
|
122
|
+
const forceLockData = Buffer.concat([AUTHORITY_FORCE_LOCK, gameIdBuf]);
|
|
123
|
+
const forceLockIx = new TransactionInstruction({
|
|
124
|
+
keys: [
|
|
125
|
+
{ pubkey: gamePDA, isSigner: false, isWritable: true },
|
|
126
|
+
{ pubkey: this.oracleKeypair.publicKey, isSigner: true, isWritable: true },
|
|
127
|
+
],
|
|
128
|
+
programId: this.programId,
|
|
129
|
+
data: forceLockData,
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
// Instruction 2: resolve_automatic_game
|
|
133
|
+
// Encode winning team: Some(Home)=[1,0], Some(Away)=[1,1], Some(Draw)=[1,2], None(refund)=[0]
|
|
134
|
+
let winningTeamBytes;
|
|
135
|
+
if (winner === null) {
|
|
136
|
+
winningTeamBytes = Buffer.from([0]);
|
|
137
|
+
} else if (winner === 'home') {
|
|
138
|
+
winningTeamBytes = Buffer.from([1, 0]);
|
|
139
|
+
} else if (winner === 'away') {
|
|
140
|
+
winningTeamBytes = Buffer.from([1, 1]);
|
|
141
|
+
} else if (winner === 'draw') {
|
|
142
|
+
winningTeamBytes = Buffer.from([1, 2]);
|
|
143
|
+
} else {
|
|
144
|
+
console.error(`[CustomGameResolver] Unexpected winner value: ${winner}, defaulting to refund`);
|
|
145
|
+
winningTeamBytes = Buffer.from([0]);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const resolveData = Buffer.concat([RESOLVE_AUTO, gameIdBuf, winningTeamBytes]);
|
|
149
|
+
|
|
150
|
+
const resolveKeys = [
|
|
151
|
+
{ pubkey: gamePDA, isSigner: false, isWritable: true },
|
|
152
|
+
{ pubkey: this.oracleKeypair.publicKey, isSigner: true, isWritable: true },
|
|
153
|
+
{ pubkey: OPERATOR_WALLET, isSigner: false, isWritable: true },
|
|
154
|
+
];
|
|
155
|
+
|
|
156
|
+
if (referrerWallet) {
|
|
157
|
+
try {
|
|
158
|
+
const referrerPubkey = new PublicKey(referrerWallet);
|
|
159
|
+
resolveKeys.push({ pubkey: referrerPubkey, isSigner: false, isWritable: true });
|
|
160
|
+
} catch (e) {
|
|
161
|
+
console.warn(`[CustomGameResolver] Invalid referrer wallet: ${referrerWallet}`);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const resolveIx = new TransactionInstruction({
|
|
166
|
+
keys: resolveKeys,
|
|
167
|
+
programId: this.programId,
|
|
168
|
+
data: resolveData,
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
// Combine both instructions: force-lock first, then resolve
|
|
172
|
+
const tx = new Transaction().add(forceLockIx).add(resolveIx);
|
|
173
|
+
tx.recentBlockhash = (await this.connection.getLatestBlockhash()).blockhash;
|
|
174
|
+
tx.feePayer = this.oracleKeypair.publicKey;
|
|
175
|
+
tx.sign(this.oracleKeypair);
|
|
176
|
+
|
|
177
|
+
const signature = await this.connection.sendRawTransaction(tx.serialize());
|
|
178
|
+
await this.confirmTransactionPolling(signature);
|
|
179
|
+
|
|
180
|
+
return signature;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Update game status in PostgreSQL via the internal resolve endpoint.
|
|
185
|
+
*/
|
|
186
|
+
async updateGameInPostgreSQL(gameId, winner, signature) {
|
|
187
|
+
try {
|
|
188
|
+
await axios.post(
|
|
189
|
+
`${this.dubsServerUrl}/api/games/${gameId}/resolve`,
|
|
190
|
+
{
|
|
191
|
+
winner,
|
|
192
|
+
homeScore: null,
|
|
193
|
+
awayScore: null,
|
|
194
|
+
resolvedAt: new Date().toISOString(),
|
|
195
|
+
resolvedBy: 'developer',
|
|
196
|
+
resolveSignature: signature,
|
|
197
|
+
},
|
|
198
|
+
{ timeout: 10000, headers: { 'Content-Type': 'application/json' } }
|
|
199
|
+
);
|
|
200
|
+
console.log(` PostgreSQL updated`);
|
|
201
|
+
} catch (error) {
|
|
202
|
+
console.error(`[CustomGameResolver] Error updating PostgreSQL:`, error.message);
|
|
203
|
+
// Don't throw — on-chain resolution is what matters
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Look up the game creator's referrer wallet for commission payout.
|
|
209
|
+
*/
|
|
210
|
+
async getGameCreatorReferrer(gameId) {
|
|
211
|
+
try {
|
|
212
|
+
const response = await axios.get(
|
|
213
|
+
`${this.dubsServerUrl}/api/games/${gameId}/creator-referrer`,
|
|
214
|
+
{ timeout: 5000 }
|
|
215
|
+
);
|
|
216
|
+
if (response.data.success && response.data.referrerWallet) {
|
|
217
|
+
return response.data.referrerWallet;
|
|
218
|
+
}
|
|
219
|
+
return null;
|
|
220
|
+
} catch (error) {
|
|
221
|
+
console.warn(`[CustomGameResolver] Could not fetch referrer for ${gameId}:`, error.message);
|
|
222
|
+
return null;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Poll for transaction confirmation.
|
|
228
|
+
*/
|
|
229
|
+
async confirmTransactionPolling(signature, timeout = 60000) {
|
|
230
|
+
const start = Date.now();
|
|
231
|
+
while (Date.now() - start < timeout) {
|
|
232
|
+
const statuses = await this.connection.getSignatureStatuses([signature]);
|
|
233
|
+
const status = statuses?.value?.[0];
|
|
234
|
+
|
|
235
|
+
if (status?.err) {
|
|
236
|
+
throw new Error(`Transaction failed: ${JSON.stringify(status.err)}`);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
if (status?.confirmationStatus === 'confirmed' || status?.confirmationStatus === 'finalized') {
|
|
240
|
+
console.log(` Transaction confirmed: ${status.confirmationStatus}`);
|
|
241
|
+
return status;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
245
|
+
}
|
|
246
|
+
throw new Error('Transaction confirmation timeout');
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Singleton
|
|
251
|
+
let instance = null;
|
|
252
|
+
|
|
253
|
+
function getCustomGameResolver() {
|
|
254
|
+
if (!instance) {
|
|
255
|
+
instance = new CustomGameResolver();
|
|
256
|
+
}
|
|
257
|
+
return instance;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
module.exports = { CustomGameResolver, getCustomGameResolver };
|
package/services/db.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 🗄️ Shared Database Pool
|
|
3
|
+
*
|
|
4
|
+
* Centralized PostgreSQL connection pool for the entire application.
|
|
5
|
+
* IMPORTANT: All services should import from here instead of creating their own pools.
|
|
6
|
+
*
|
|
7
|
+
* Heroku PostgreSQL connection limits:
|
|
8
|
+
* - Hobby/Basic: 20 connections
|
|
9
|
+
* - Standard-0: 120 connections
|
|
10
|
+
* - Standard-2: 400 connections
|
|
11
|
+
*
|
|
12
|
+
* We set max to 8 to leave room for:
|
|
13
|
+
* - Heroku's own management connections
|
|
14
|
+
* - Multiple dynos (if scaled)
|
|
15
|
+
* - External tools (pgAdmin, migrations, etc.)
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const { Pool } = require('pg');
|
|
19
|
+
|
|
20
|
+
// Determine if we're on Heroku/AWS (need SSL)
|
|
21
|
+
const isProduction = process.env.DATABASE_URL &&
|
|
22
|
+
(process.env.DATABASE_URL.includes('amazonaws') || process.env.DATABASE_URL.includes('heroku'));
|
|
23
|
+
|
|
24
|
+
// Create the shared pool with explicit connection limits
|
|
25
|
+
const pool = process.env.DATABASE_URL ? new Pool({
|
|
26
|
+
connectionString: process.env.DATABASE_URL,
|
|
27
|
+
ssl: isProduction ? { rejectUnauthorized: false } : false,
|
|
28
|
+
|
|
29
|
+
// Connection pool settings - CRITICAL for Heroku
|
|
30
|
+
max: parseInt(process.env.DB_POOL_MAX, 10) || 5, // Maximum connections in pool (reduced from 8)
|
|
31
|
+
min: 0, // Don't hold idle connections unnecessarily
|
|
32
|
+
idleTimeoutMillis: 10000, // Close idle connections after 10s (was 30s)
|
|
33
|
+
connectionTimeoutMillis: 10000, // Timeout acquiring connection after 10s
|
|
34
|
+
allowExitOnIdle: true, // Allow process to exit when pool is idle
|
|
35
|
+
}) : null;
|
|
36
|
+
|
|
37
|
+
// Log pool configuration on startup
|
|
38
|
+
if (pool) {
|
|
39
|
+
console.log('[DB] Shared pool created:', {
|
|
40
|
+
max: pool.options.max,
|
|
41
|
+
min: pool.options.min,
|
|
42
|
+
idleTimeoutMillis: pool.options.idleTimeoutMillis,
|
|
43
|
+
ssl: !!pool.options.ssl
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// Monitor pool events for debugging connection issues
|
|
47
|
+
pool.on('error', (err) => {
|
|
48
|
+
console.error('[DB] Unexpected pool error:', err.message);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
pool.on('connect', () => {
|
|
52
|
+
console.log('[DB] New client connected. Total:', pool.totalCount, 'Idle:', pool.idleCount);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
pool.on('remove', () => {
|
|
56
|
+
console.log('[DB] Client removed. Total:', pool.totalCount, 'Idle:', pool.idleCount);
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Graceful shutdown
|
|
61
|
+
async function closePool() {
|
|
62
|
+
if (pool) {
|
|
63
|
+
console.log('[DB] Closing pool...');
|
|
64
|
+
await pool.end();
|
|
65
|
+
console.log('[DB] Pool closed.');
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Export the shared pool
|
|
70
|
+
module.exports = {
|
|
71
|
+
pool,
|
|
72
|
+
closePool,
|
|
73
|
+
// Convenience method to get a client for transactions
|
|
74
|
+
getClient: async () => pool ? pool.connect() : null,
|
|
75
|
+
// Convenience method to run a query
|
|
76
|
+
query: (...args) => pool ? pool.query(...args) : Promise.reject(new Error('Database not configured')),
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
|