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,270 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* V1 vs V2 Payout Reconciliation Script
|
|
4
|
+
*
|
|
5
|
+
* Shows what each player would receive in V1 (equal split) vs V2 (proportional),
|
|
6
|
+
* and calculates the difference you would owe them to make it fair.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* DATABASE_URL=$(heroku config:get DATABASE_URL -a dubs-server-prod) node scripts/reconcile-v1-v2-payouts.js <gameId>
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const { Pool } = require('pg');
|
|
13
|
+
|
|
14
|
+
const DATABASE_URL = process.env.DATABASE_URL;
|
|
15
|
+
|
|
16
|
+
if (!DATABASE_URL) {
|
|
17
|
+
console.error('❌ DATABASE_URL not set. Run with:');
|
|
18
|
+
console.error(' DATABASE_URL=$(heroku config:get DATABASE_URL -a dubs-server-prod) node scripts/reconcile-v1-v2-payouts.js <gameId>');
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const pool = new Pool({
|
|
23
|
+
connectionString: DATABASE_URL,
|
|
24
|
+
ssl: { rejectUnauthorized: false }
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
async function reconcilePayouts(gameId) {
|
|
28
|
+
try {
|
|
29
|
+
// Get game data
|
|
30
|
+
const gameResult = await pool.query(`
|
|
31
|
+
SELECT
|
|
32
|
+
g.game_id,
|
|
33
|
+
g.buy_in,
|
|
34
|
+
g.is_resolved,
|
|
35
|
+
g.home_team_players,
|
|
36
|
+
g.away_team_players,
|
|
37
|
+
g.draw_team_players,
|
|
38
|
+
g.player_amounts,
|
|
39
|
+
g.sports_event->>'strHomeTeam' as home_team,
|
|
40
|
+
g.sports_event->>'strAwayTeam' as away_team,
|
|
41
|
+
g.sports_event->'finalScore'->>'winner' as winner,
|
|
42
|
+
g.game_type
|
|
43
|
+
FROM games g
|
|
44
|
+
WHERE g.game_id = $1
|
|
45
|
+
`, [gameId]);
|
|
46
|
+
|
|
47
|
+
if (gameResult.rows.length === 0) {
|
|
48
|
+
console.error('❌ Game not found');
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const game = gameResult.rows[0];
|
|
53
|
+
const homeTeam = game.home_team || 'Home';
|
|
54
|
+
const awayTeam = game.away_team || 'Away';
|
|
55
|
+
|
|
56
|
+
// Get participants
|
|
57
|
+
const participantsResult = await pool.query(`
|
|
58
|
+
SELECT
|
|
59
|
+
ugr.wallet_address,
|
|
60
|
+
u.username,
|
|
61
|
+
ugr.team_choice
|
|
62
|
+
FROM user_game_refs ugr
|
|
63
|
+
LEFT JOIN users u ON ugr.wallet_address = u.wallet_address
|
|
64
|
+
WHERE ugr.game_id = $1
|
|
65
|
+
ORDER BY ugr.team_choice, ugr.joined_at
|
|
66
|
+
`, [gameId]);
|
|
67
|
+
|
|
68
|
+
const playerAmounts = game.player_amounts || {};
|
|
69
|
+
const buyIn = parseFloat(game.buy_in);
|
|
70
|
+
|
|
71
|
+
const getPlayerAmount = (wallet) => {
|
|
72
|
+
if (playerAmounts[wallet] !== undefined) {
|
|
73
|
+
return parseFloat(playerAmounts[wallet]);
|
|
74
|
+
}
|
|
75
|
+
return buyIn;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
// Calculate pools
|
|
79
|
+
const homePlayers = game.home_team_players || [];
|
|
80
|
+
const awayPlayers = game.away_team_players || [];
|
|
81
|
+
const drawPlayers = game.draw_team_players || [];
|
|
82
|
+
|
|
83
|
+
const homeTeamPool = homePlayers.reduce((sum, wallet) => sum + getPlayerAmount(wallet), 0);
|
|
84
|
+
const awayTeamPool = awayPlayers.reduce((sum, wallet) => sum + getPlayerAmount(wallet), 0);
|
|
85
|
+
const drawTeamPool = drawPlayers.reduce((sum, wallet) => sum + getPlayerAmount(wallet), 0);
|
|
86
|
+
const totalPot = homeTeamPool + awayTeamPool + drawTeamPool;
|
|
87
|
+
const platformFee = totalPot * 0.06;
|
|
88
|
+
const winnerPool = totalPot - platformFee;
|
|
89
|
+
|
|
90
|
+
// Check if V1 or V2
|
|
91
|
+
const totalPlayerCount = homePlayers.length + awayPlayers.length + drawPlayers.length;
|
|
92
|
+
const playerAmountsCount = Object.keys(playerAmounts).length;
|
|
93
|
+
const isPureV2 = totalPlayerCount > 0 && playerAmountsCount === totalPlayerCount;
|
|
94
|
+
|
|
95
|
+
// Build player list with details
|
|
96
|
+
const players = participantsResult.rows.map(p => ({
|
|
97
|
+
username: p.username || p.wallet_address.slice(0, 8) + '...',
|
|
98
|
+
wallet: p.wallet_address,
|
|
99
|
+
team: p.team_choice,
|
|
100
|
+
teamName: p.team_choice === 'home' ? homeTeam : p.team_choice === 'away' ? awayTeam : 'Draw',
|
|
101
|
+
bet: getPlayerAmount(p.wallet_address)
|
|
102
|
+
}));
|
|
103
|
+
|
|
104
|
+
const homePlayersList = players.filter(p => p.team === 'home');
|
|
105
|
+
const awayPlayersList = players.filter(p => p.team === 'away');
|
|
106
|
+
const drawPlayersList = players.filter(p => p.team === 'draw');
|
|
107
|
+
|
|
108
|
+
// Print header
|
|
109
|
+
console.log('');
|
|
110
|
+
console.log('╔═══════════════════════════════════════════════════════════════════════════════╗');
|
|
111
|
+
console.log('║ V1 vs V2 PAYOUT RECONCILIATION ║');
|
|
112
|
+
console.log('╚═══════════════════════════════════════════════════════════════════════════════╝');
|
|
113
|
+
console.log('');
|
|
114
|
+
console.log(`Game ID: ${gameId}`);
|
|
115
|
+
console.log(`Matchup: ${homeTeam} vs ${awayTeam}`);
|
|
116
|
+
console.log(`Type: ${game.game_type || 'sports'}`);
|
|
117
|
+
console.log(`Status: ${game.is_resolved ? 'Resolved' : 'Pending'}`);
|
|
118
|
+
if (game.winner) {
|
|
119
|
+
console.log(`Winner: ${game.winner === 'home' ? homeTeam : game.winner === 'away' ? awayTeam : 'Draw'}`);
|
|
120
|
+
}
|
|
121
|
+
console.log('');
|
|
122
|
+
console.log(`Mode: ${isPureV2 ? 'V2 (Pure Pari-Mutuel) ✅' : 'V1/Hybrid (Legacy) ⚠️'}`);
|
|
123
|
+
console.log('');
|
|
124
|
+
console.log('┌─────────────────────────────────────────────────────────────────────────────────┐');
|
|
125
|
+
console.log('│ POT DETAILS │');
|
|
126
|
+
console.log('├─────────────────────────────────────────────────────────────────────────────────┤');
|
|
127
|
+
console.log(`│ Total Pot: ${totalPot.toFixed(4).padStart(10)} SOL │`);
|
|
128
|
+
console.log(`│ Platform Fee (6%): ${platformFee.toFixed(4).padStart(9)} SOL │`);
|
|
129
|
+
console.log(`│ Winner Pool: ${winnerPool.toFixed(4).padStart(10)} SOL │`);
|
|
130
|
+
console.log('├─────────────────────────────────────────────────────────────────────────────────┤');
|
|
131
|
+
console.log(`│ ${homeTeam.padEnd(20)}: ${homeTeamPool.toFixed(4).padStart(8)} SOL (${homePlayers.length} players) │`);
|
|
132
|
+
console.log(`│ ${awayTeam.padEnd(20)}: ${awayTeamPool.toFixed(4).padStart(8)} SOL (${awayPlayers.length} players) │`);
|
|
133
|
+
if (drawPlayers.length > 0) {
|
|
134
|
+
console.log(`│ Draw : ${drawTeamPool.toFixed(4).padStart(8)} SOL (${drawPlayers.length} players) │`);
|
|
135
|
+
}
|
|
136
|
+
console.log('└─────────────────────────────────────────────────────────────────────────────────┘');
|
|
137
|
+
console.log('');
|
|
138
|
+
|
|
139
|
+
// Function to calculate and display scenario
|
|
140
|
+
function displayScenario(winningTeam, winningTeamName, winnersList, winnersPool, losersList) {
|
|
141
|
+
const v1PayoutPerWinner = winnersList.length > 0 ? winnerPool / winnersList.length : 0;
|
|
142
|
+
|
|
143
|
+
console.log('┌─────────────────────────────────────────────────────────────────────────────────┐');
|
|
144
|
+
console.log(`│ SCENARIO: ${winningTeamName.toUpperCase()} WINS`.padEnd(82) + '│');
|
|
145
|
+
console.log('├─────────────────────────────────────────────────────────────────────────────────┤');
|
|
146
|
+
console.log('│ WINNERS: │');
|
|
147
|
+
console.log('│ ┌────────────────┬──────────┬─────────────┬─────────────┬─────────────────────┐│');
|
|
148
|
+
console.log('│ │ Player │ Bet │ V1 (Equal) │ V2 (Prop) │ You Owe ││');
|
|
149
|
+
console.log('│ ├────────────────┼──────────┼─────────────┼─────────────┼─────────────────────┤│');
|
|
150
|
+
|
|
151
|
+
let totalOwed = 0;
|
|
152
|
+
const reconciliation = [];
|
|
153
|
+
|
|
154
|
+
for (const p of winnersList) {
|
|
155
|
+
const v2Payout = winnersPool > 0 ? (p.bet / winnersPool) * winnerPool : 0;
|
|
156
|
+
const difference = v2Payout - v1PayoutPerWinner;
|
|
157
|
+
const owed = difference > 0 ? difference : 0;
|
|
158
|
+
totalOwed += owed;
|
|
159
|
+
|
|
160
|
+
reconciliation.push({
|
|
161
|
+
username: p.username,
|
|
162
|
+
wallet: p.wallet,
|
|
163
|
+
bet: p.bet,
|
|
164
|
+
v1Payout: v1PayoutPerWinner,
|
|
165
|
+
v2Payout: v2Payout,
|
|
166
|
+
difference: difference,
|
|
167
|
+
owed: owed
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
const username = p.username.slice(0, 14).padEnd(14);
|
|
171
|
+
const bet = p.bet.toFixed(4).padStart(8);
|
|
172
|
+
const v1 = v1PayoutPerWinner.toFixed(4).padStart(9) + ' SOL';
|
|
173
|
+
const v2 = v2Payout.toFixed(4).padStart(9) + ' SOL';
|
|
174
|
+
const owedStr = owed > 0 ? `+${owed.toFixed(4)} SOL`.padStart(17) : '0 (overpaid)'.padStart(17);
|
|
175
|
+
|
|
176
|
+
console.log(`│ │ ${username} │ ${bet} │ ${v1} │ ${v2} │ ${owedStr} ││`);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
console.log('│ └────────────────┴──────────┴─────────────┴─────────────┴─────────────────────┘│');
|
|
180
|
+
console.log('│ │');
|
|
181
|
+
console.log(`│ TOTAL YOU OWE IF ${winningTeamName.toUpperCase()} WINS: ${totalOwed.toFixed(4)} SOL`.padEnd(82) + '│');
|
|
182
|
+
console.log('│ │');
|
|
183
|
+
console.log('│ LOSERS: (get nothing in both V1 and V2) │');
|
|
184
|
+
|
|
185
|
+
if (losersList.length === 0) {
|
|
186
|
+
console.log('│ (none) │');
|
|
187
|
+
} else {
|
|
188
|
+
const loserNames = losersList.map(p => p.username).join(', ');
|
|
189
|
+
const truncatedLosers = loserNames.length > 70 ? loserNames.slice(0, 67) + '...' : loserNames;
|
|
190
|
+
console.log(`│ ${truncatedLosers.padEnd(75)}│`);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
console.log('└─────────────────────────────────────────────────────────────────────────────────┘');
|
|
194
|
+
console.log('');
|
|
195
|
+
|
|
196
|
+
return { totalOwed, reconciliation };
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Scenario 1: Home wins
|
|
200
|
+
const homeWinsResult = displayScenario('home', homeTeam, homePlayersList, homeTeamPool, [...awayPlayersList, ...drawPlayersList]);
|
|
201
|
+
|
|
202
|
+
// Scenario 2: Away wins
|
|
203
|
+
const awayWinsResult = displayScenario('away', awayTeam, awayPlayersList, awayTeamPool, [...homePlayersList, ...drawPlayersList]);
|
|
204
|
+
|
|
205
|
+
// Scenario 3: Draw wins (if applicable)
|
|
206
|
+
let drawWinsResult = null;
|
|
207
|
+
if (drawPlayers.length > 0) {
|
|
208
|
+
drawWinsResult = displayScenario('draw', 'Draw', drawPlayersList, drawTeamPool, [...homePlayersList, ...awayPlayersList]);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Summary
|
|
212
|
+
console.log('╔═══════════════════════════════════════════════════════════════════════════════╗');
|
|
213
|
+
console.log('║ SUMMARY ║');
|
|
214
|
+
console.log('╠═══════════════════════════════════════════════════════════════════════════════╣');
|
|
215
|
+
console.log(`║ If ${homeTeam.padEnd(20)} wins: Owe ${homeWinsResult.totalOwed.toFixed(4).padStart(8)} SOL ║`);
|
|
216
|
+
console.log(`║ If ${awayTeam.padEnd(20)} wins: Owe ${awayWinsResult.totalOwed.toFixed(4).padStart(8)} SOL ║`);
|
|
217
|
+
if (drawWinsResult) {
|
|
218
|
+
console.log(`║ If Draw wins: Owe ${drawWinsResult.totalOwed.toFixed(4).padStart(8)} SOL ║`);
|
|
219
|
+
}
|
|
220
|
+
console.log('╚═══════════════════════════════════════════════════════════════════════════════╝');
|
|
221
|
+
console.log('');
|
|
222
|
+
|
|
223
|
+
// If game is already resolved, show what actually needs to happen
|
|
224
|
+
if (game.winner) {
|
|
225
|
+
console.log('┌─────────────────────────────────────────────────────────────────────────────────┐');
|
|
226
|
+
console.log('│ ⚠️ GAME IS RESOLVED - ACTION NEEDED │');
|
|
227
|
+
console.log('├─────────────────────────────────────────────────────────────────────────────────┤');
|
|
228
|
+
|
|
229
|
+
const actualResult = game.winner === 'home' ? homeWinsResult :
|
|
230
|
+
game.winner === 'away' ? awayWinsResult : drawWinsResult;
|
|
231
|
+
|
|
232
|
+
if (actualResult) {
|
|
233
|
+
console.log(`│ Winner: ${game.winner === 'home' ? homeTeam : game.winner === 'away' ? awayTeam : 'Draw'}`.padEnd(82) + '│');
|
|
234
|
+
console.log('│ │');
|
|
235
|
+
console.log('│ Players to reimburse: │');
|
|
236
|
+
|
|
237
|
+
for (const p of actualResult.reconciliation) {
|
|
238
|
+
if (p.owed > 0) {
|
|
239
|
+
console.log(`│ ${p.username.padEnd(20)} → Send ${p.owed.toFixed(4)} SOL`.padEnd(82) + '│');
|
|
240
|
+
console.log(`│ Wallet: ${p.wallet}`.padEnd(82) + '│');
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
console.log('│ │');
|
|
245
|
+
console.log(`│ TOTAL TO SEND: ${actualResult.totalOwed.toFixed(4)} SOL`.padEnd(82) + '│');
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
console.log('└─────────────────────────────────────────────────────────────────────────────────┘');
|
|
249
|
+
console.log('');
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
} catch (error) {
|
|
253
|
+
console.error('❌ Error:', error.message);
|
|
254
|
+
process.exit(1);
|
|
255
|
+
} finally {
|
|
256
|
+
await pool.end();
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Main
|
|
261
|
+
const gameId = process.argv[2];
|
|
262
|
+
if (!gameId) {
|
|
263
|
+
console.log('Usage: node scripts/reconcile-v1-v2-payouts.js <gameId>');
|
|
264
|
+
console.log('');
|
|
265
|
+
console.log('Example:');
|
|
266
|
+
console.log(' DATABASE_URL=$(heroku config:get DATABASE_URL -a dubs-server-prod) node scripts/reconcile-v1-v2-payouts.js sport-1769102132440-xmirk1omx');
|
|
267
|
+
process.exit(1);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
reconcilePayouts(gameId);
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 🎰 Initialize Dubs Jackpot Protocol
|
|
5
|
+
*
|
|
6
|
+
* One-time setup to configure the jackpot protocol
|
|
7
|
+
* Uses oracle wallet as admin
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const { Connection, Keypair, PublicKey, Transaction } = require('@solana/web3.js');
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
|
|
14
|
+
const PROGRAM_ID = new PublicKey('BHidyz25KWkNPdTHgeANzMg25MM2KEiNnG4yE5F46XUz');
|
|
15
|
+
const RPC_URL = process.env.SOLANA_NETWORK || 'https://api.devnet.solana.com';
|
|
16
|
+
|
|
17
|
+
async function main() {
|
|
18
|
+
console.log('🎰 Initializing Dubs Jackpot Protocol\n');
|
|
19
|
+
|
|
20
|
+
// Load oracle wallet (will be the admin)
|
|
21
|
+
const oracleKeyPath = path.join(__dirname, '..', 'wallets', 'jackpot_oracle.json');
|
|
22
|
+
|
|
23
|
+
let oracleWallet;
|
|
24
|
+
if (fs.existsSync(oracleKeyPath)) {
|
|
25
|
+
const secretKey = JSON.parse(fs.readFileSync(oracleKeyPath, 'utf-8'));
|
|
26
|
+
oracleWallet = Keypair.fromSecretKey(Uint8Array.from(secretKey));
|
|
27
|
+
console.log('✅ Loaded oracle wallet from', oracleKeyPath);
|
|
28
|
+
} else {
|
|
29
|
+
// Use the main operator wallet as fallback
|
|
30
|
+
const mainWalletPath = path.join(require('os').homedir(), '.config/solana/id.json');
|
|
31
|
+
if (!fs.existsSync(mainWalletPath)) {
|
|
32
|
+
console.error('❌ No wallet found!');
|
|
33
|
+
console.log('Create one with: solana-keygen new');
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
const secretKey = JSON.parse(fs.readFileSync(mainWalletPath, 'utf-8'));
|
|
37
|
+
oracleWallet = Keypair.fromSecretKey(Uint8Array.from(secretKey));
|
|
38
|
+
console.log('✅ Using main wallet:', oracleWallet.publicKey.toString());
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const connection = new Connection(RPC_URL, 'confirmed');
|
|
42
|
+
|
|
43
|
+
// Check balance
|
|
44
|
+
const balance = await connection.getBalance(oracleWallet.publicKey);
|
|
45
|
+
console.log('💰 Balance:', (balance / 1e9).toFixed(4), 'SOL');
|
|
46
|
+
|
|
47
|
+
if (balance < 0.1 * 1e9) {
|
|
48
|
+
console.log('⚠️ Low balance! You may need to airdrop:');
|
|
49
|
+
console.log(` solana airdrop 1 ${oracleWallet.publicKey.toString()} --url devnet`);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Configuration
|
|
53
|
+
const adminAddress = oracleWallet.publicKey;
|
|
54
|
+
const treasuryAddress = oracleWallet.publicKey; // Use same wallet for treasury
|
|
55
|
+
const feeBasisPoints = 500; // 5% fee
|
|
56
|
+
const roundDurationSlots = 150; // 1 MINUTE rounds! (~60 seconds)
|
|
57
|
+
|
|
58
|
+
console.log('\n📝 Configuration:');
|
|
59
|
+
console.log(' Admin:', adminAddress.toString());
|
|
60
|
+
console.log(' Treasury:', treasuryAddress.toString());
|
|
61
|
+
console.log(' Fee:', feeBasisPoints, 'bps (5%)');
|
|
62
|
+
console.log(' Round Duration:', roundDurationSlots, 'slots (~1 minute)');
|
|
63
|
+
console.log(' Network:', RPC_URL);
|
|
64
|
+
|
|
65
|
+
// Build transaction using API
|
|
66
|
+
console.log('\n🔨 Building initialize transaction...');
|
|
67
|
+
|
|
68
|
+
const API_BASE = 'http://localhost:3001';
|
|
69
|
+
const axios = require('axios');
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
const { data } = await axios.post(`${API_BASE}/jackpot/build/initialize`, {
|
|
73
|
+
adminAddress: adminAddress.toString(),
|
|
74
|
+
treasuryAddress: treasuryAddress.toString(),
|
|
75
|
+
feeBasisPoints,
|
|
76
|
+
roundDurationSlots,
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
console.log('✅ Transaction built');
|
|
80
|
+
console.log(' Config PDA:', data.configPda);
|
|
81
|
+
|
|
82
|
+
// Deserialize and get fresh blockhash
|
|
83
|
+
const tx = Transaction.from(Buffer.from(data.transaction, 'base64'));
|
|
84
|
+
tx.recentBlockhash = (await connection.getLatestBlockhash()).blockhash;
|
|
85
|
+
tx.feePayer = oracleWallet.publicKey;
|
|
86
|
+
tx.sign(oracleWallet);
|
|
87
|
+
|
|
88
|
+
console.log('\n📤 Sending transaction...');
|
|
89
|
+
const signature = await connection.sendRawTransaction(tx.serialize());
|
|
90
|
+
console.log(' Signature:', signature);
|
|
91
|
+
|
|
92
|
+
console.log('\n⏳ Confirming...');
|
|
93
|
+
await connection.confirmTransaction(signature);
|
|
94
|
+
|
|
95
|
+
console.log('\n🎉 PROTOCOL INITIALIZED!');
|
|
96
|
+
console.log('\n✅ Next steps:');
|
|
97
|
+
console.log(' 1. Open first round: node scripts/open-jackpot-round.js');
|
|
98
|
+
console.log(' 2. Refresh frontend: http://localhost:3000');
|
|
99
|
+
console.log(' 3. Start betting! 🎰');
|
|
100
|
+
|
|
101
|
+
} catch (error) {
|
|
102
|
+
console.error('\n❌ Error:', error.message);
|
|
103
|
+
if (error.response) {
|
|
104
|
+
console.error(' API Error:', error.response.data);
|
|
105
|
+
}
|
|
106
|
+
process.exit(1);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
main().catch(console.error);
|
|
111
|
+
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Force reset a stuck jackpot round
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* node scripts/jackpot/force-reset.js
|
|
8
|
+
*
|
|
9
|
+
* Environment:
|
|
10
|
+
* SOLANA_RPC_URL - RPC endpoint (default: devnet)
|
|
11
|
+
* API_BASE - Server URL (default: http://localhost:3001)
|
|
12
|
+
* KEEPER_PRIVATE_KEY - Keeper wallet (optional, falls back to wallet files)
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const { Connection, Keypair, Transaction } = require('@solana/web3.js');
|
|
16
|
+
const axios = require('axios');
|
|
17
|
+
const fs = require('fs');
|
|
18
|
+
const path = require('path');
|
|
19
|
+
|
|
20
|
+
const RPC_URL = process.env.SOLANA_RPC_URL || 'https://api.devnet.solana.com';
|
|
21
|
+
const API_BASE = process.env.API_BASE || 'http://localhost:3001';
|
|
22
|
+
|
|
23
|
+
function loadWallet() {
|
|
24
|
+
if (process.env.KEEPER_PRIVATE_KEY) {
|
|
25
|
+
console.log('Loading wallet from KEEPER_PRIVATE_KEY env var');
|
|
26
|
+
return Keypair.fromSecretKey(Uint8Array.from(JSON.parse(process.env.KEEPER_PRIVATE_KEY)));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const oracleKeyPath = path.join(__dirname, '..', '..', 'wallets', 'jackpot_oracle.json');
|
|
30
|
+
if (fs.existsSync(oracleKeyPath)) {
|
|
31
|
+
console.log('Loading wallet from wallets/jackpot_oracle.json');
|
|
32
|
+
return Keypair.fromSecretKey(Uint8Array.from(JSON.parse(fs.readFileSync(oracleKeyPath, 'utf-8'))));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const defaultPath = path.join(require('os').homedir(), '.config/solana/id.json');
|
|
36
|
+
if (fs.existsSync(defaultPath)) {
|
|
37
|
+
console.log('Loading wallet from ~/.config/solana/id.json');
|
|
38
|
+
return Keypair.fromSecretKey(Uint8Array.from(JSON.parse(fs.readFileSync(defaultPath, 'utf-8'))));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
console.error('No wallet found. Set KEEPER_PRIVATE_KEY or add a wallet file.');
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function main() {
|
|
46
|
+
const wallet = loadWallet();
|
|
47
|
+
const connection = new Connection(RPC_URL, 'confirmed');
|
|
48
|
+
|
|
49
|
+
console.log('\n♻️ Force resetting round...');
|
|
50
|
+
console.log(' Wallet:', wallet.publicKey.toString());
|
|
51
|
+
console.log(' API:', API_BASE);
|
|
52
|
+
console.log(' RPC:', RPC_URL.includes('devnet') ? 'Devnet' : RPC_URL.slice(0, 50) + '...');
|
|
53
|
+
console.log();
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
const { data } = await axios.post(`${API_BASE}/jackpot/build/reset-round`, {
|
|
57
|
+
keeperAddress: wallet.publicKey.toString(),
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const tx = Transaction.from(Buffer.from(data.transaction, 'base64'));
|
|
61
|
+
tx.recentBlockhash = (await connection.getLatestBlockhash()).blockhash;
|
|
62
|
+
tx.feePayer = wallet.publicKey;
|
|
63
|
+
tx.sign(wallet);
|
|
64
|
+
|
|
65
|
+
const signature = await connection.sendRawTransaction(tx.serialize(), { skipPreflight: false });
|
|
66
|
+
console.log('Signature:', signature);
|
|
67
|
+
|
|
68
|
+
// Poll for confirmation (Alchemy doesn't support signatureSubscribe)
|
|
69
|
+
const start = Date.now();
|
|
70
|
+
while (Date.now() - start < 60000) {
|
|
71
|
+
const statuses = await connection.getSignatureStatuses([signature]);
|
|
72
|
+
const status = statuses?.value?.[0];
|
|
73
|
+
if (status?.err) throw new Error(`Transaction failed: ${JSON.stringify(status.err)}`);
|
|
74
|
+
if (status?.confirmationStatus === 'confirmed' || status?.confirmationStatus === 'finalized') break;
|
|
75
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
console.log(`\n✅ Round reset! New round: ${data.roundId}`);
|
|
79
|
+
} catch (error) {
|
|
80
|
+
console.error('❌ Error:', error.response?.data || error.message);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
main();
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Initialize Jackpot Protocol on Mainnet
|
|
5
|
+
*
|
|
6
|
+
* Standalone script — does NOT require the server to be running.
|
|
7
|
+
* Builds, signs, and sends the initialize transaction directly.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const { Connection, Keypair, PublicKey, SystemProgram, Transaction, TransactionInstruction } = require('@solana/web3.js');
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
|
|
14
|
+
// ── Configuration ──────────────────────────────────────────────
|
|
15
|
+
const RPC_URL = process.env.SOLANA_RPC_URL || 'https://api.mainnet-beta.solana.com';
|
|
16
|
+
const PROGRAM_ID = new PublicKey('BHidyz25KWkNPdTHgeANzMg25MM2KEiNnG4yE5F46XUz');
|
|
17
|
+
const TREASURY = new PublicKey('BVZXwZpfgyzTBdRFHohkHZppPHnAyqyctRsKy3vWfQib');
|
|
18
|
+
const FEE_BPS = 500; // 5%
|
|
19
|
+
const ROUND_DURATION_SLOTS = 1512000; // ~7 days at 400ms/slot
|
|
20
|
+
|
|
21
|
+
// Anchor discriminator for "global:initialize"
|
|
22
|
+
const INITIALIZE_DISC = Buffer.from([175, 175, 109, 31, 13, 152, 155, 237]);
|
|
23
|
+
|
|
24
|
+
// ── Helpers ────────────────────────────────────────────────────
|
|
25
|
+
function serializeU16(v) { const b = Buffer.alloc(2); b.writeUInt16LE(v); return b; }
|
|
26
|
+
function serializeU64(v) { const b = Buffer.alloc(8); b.writeBigUInt64LE(BigInt(v)); return b; }
|
|
27
|
+
|
|
28
|
+
// ── Main ───────────────────────────────────────────────────────
|
|
29
|
+
async function main() {
|
|
30
|
+
console.log('🎰 Initialize Jackpot Protocol — MAINNET\n');
|
|
31
|
+
|
|
32
|
+
// Load admin wallet (default Solana CLI wallet = upgrade authority)
|
|
33
|
+
const walletPath = path.join(require('os').homedir(), '.config/solana/id.json');
|
|
34
|
+
if (!fs.existsSync(walletPath)) {
|
|
35
|
+
console.error('❌ No wallet at', walletPath);
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
const admin = Keypair.fromSecretKey(Uint8Array.from(JSON.parse(fs.readFileSync(walletPath, 'utf-8'))));
|
|
39
|
+
|
|
40
|
+
const connection = new Connection(RPC_URL, 'confirmed');
|
|
41
|
+
const balance = await connection.getBalance(admin.publicKey);
|
|
42
|
+
|
|
43
|
+
// Derive config PDA
|
|
44
|
+
const [configPda] = PublicKey.findProgramAddressSync([Buffer.from('config')], PROGRAM_ID);
|
|
45
|
+
|
|
46
|
+
console.log(' RPC: ', RPC_URL.slice(0, 50) + '...');
|
|
47
|
+
console.log(' Program: ', PROGRAM_ID.toString());
|
|
48
|
+
console.log(' Admin: ', admin.publicKey.toString());
|
|
49
|
+
console.log(' Treasury: ', TREASURY.toString());
|
|
50
|
+
console.log(' Config PDA:', configPda.toString());
|
|
51
|
+
console.log(' Fee: ', FEE_BPS, 'bps (5%)');
|
|
52
|
+
console.log(' Duration: ', ROUND_DURATION_SLOTS.toLocaleString(), 'slots (~7 days)');
|
|
53
|
+
console.log(' Balance: ', (balance / 1e9).toFixed(4), 'SOL\n');
|
|
54
|
+
|
|
55
|
+
// Check if already initialized
|
|
56
|
+
const existing = await connection.getAccountInfo(configPda);
|
|
57
|
+
if (existing) {
|
|
58
|
+
console.log('⚠️ Config PDA already exists! Protocol may already be initialized.');
|
|
59
|
+
console.log(' Account size:', existing.data.length, 'bytes');
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Build instruction
|
|
64
|
+
const data = Buffer.concat([
|
|
65
|
+
INITIALIZE_DISC,
|
|
66
|
+
serializeU16(FEE_BPS),
|
|
67
|
+
serializeU64(ROUND_DURATION_SLOTS),
|
|
68
|
+
]);
|
|
69
|
+
|
|
70
|
+
const keys = [
|
|
71
|
+
{ pubkey: configPda, isSigner: false, isWritable: true },
|
|
72
|
+
{ pubkey: admin.publicKey, isSigner: true, isWritable: true },
|
|
73
|
+
{ pubkey: TREASURY, isSigner: false, isWritable: false },
|
|
74
|
+
{ pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
const ix = new TransactionInstruction({ keys, programId: PROGRAM_ID, data });
|
|
78
|
+
const tx = new Transaction().add(ix);
|
|
79
|
+
tx.feePayer = admin.publicKey;
|
|
80
|
+
tx.recentBlockhash = (await connection.getLatestBlockhash()).blockhash;
|
|
81
|
+
tx.sign(admin);
|
|
82
|
+
|
|
83
|
+
console.log('📤 Sending initialize transaction...');
|
|
84
|
+
const signature = await connection.sendRawTransaction(tx.serialize(), { skipPreflight: false });
|
|
85
|
+
console.log(' Signature:', signature);
|
|
86
|
+
|
|
87
|
+
console.log('⏳ Confirming...');
|
|
88
|
+
await connection.confirmTransaction(signature, 'confirmed');
|
|
89
|
+
|
|
90
|
+
console.log('\n🎉 PROTOCOL INITIALIZED ON MAINNET!');
|
|
91
|
+
console.log(' Config PDA:', configPda.toString());
|
|
92
|
+
console.log(' TX:', signature);
|
|
93
|
+
console.log('\n Next: deploy backend with keeper to start rounds.');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
main().catch(err => {
|
|
97
|
+
console.error('❌ Error:', err.message);
|
|
98
|
+
if (err.logs) console.error('Logs:', err.logs);
|
|
99
|
+
process.exit(1);
|
|
100
|
+
});
|