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,763 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 🎮 Arcade Match Service
|
|
3
|
+
*
|
|
4
|
+
* Handles head-to-head arcade game matches with the following features:
|
|
5
|
+
* - Create matches with stakes
|
|
6
|
+
* - Join existing matches
|
|
7
|
+
* - Submit scores
|
|
8
|
+
* - Resolve matches with operator fees
|
|
9
|
+
* - Cancel unmatched games
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const { Connection, Keypair, PublicKey, SystemProgram, Transaction, TransactionInstruction } = require('@solana/web3.js');
|
|
13
|
+
const crypto = require('crypto');
|
|
14
|
+
const { v4: uuidv4 } = require('uuid');
|
|
15
|
+
const fs = require('fs');
|
|
16
|
+
const path = require('path');
|
|
17
|
+
|
|
18
|
+
// 🎮 Arcade Program Configuration
|
|
19
|
+
const ARCADE_PROGRAM_ID = new PublicKey('EiNzW28xL1a92m1i9T3MyRZqEEszMcVBtrYB3NkphwRq');
|
|
20
|
+
|
|
21
|
+
// 🔐 HARDCODED OPERATOR WALLET - Must match the contract!
|
|
22
|
+
const OPERATOR_WALLET = new PublicKey('BVZXwZpfgyzTBdRFHohkHZppPHnAyqyctRsKy3vWfQib');
|
|
23
|
+
|
|
24
|
+
// Instruction discriminators (first 8 bytes of SHA256 hash of "global:instruction_name")
|
|
25
|
+
const DISCRIMINATORS = {
|
|
26
|
+
CREATE_MATCH: Buffer.from([107, 2, 184, 145, 70, 142, 17, 165]), // create_match
|
|
27
|
+
JOIN_MATCH: Buffer.from([244, 8, 47, 130, 192, 59, 179, 44]), // join_match
|
|
28
|
+
SUBMIT_SCORE: Buffer.from([130, 100, 1, 137, 70, 252, 96, 3]), // submit_match_score
|
|
29
|
+
RESOLVE_MATCH: Buffer.from([73, 0, 15, 197, 178, 47, 21, 193]), // resolve_match
|
|
30
|
+
CANCEL_MATCH: Buffer.from([142, 136, 247, 45, 92, 112, 180, 83]), // cancel_match
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// Game types
|
|
34
|
+
const GAME_TYPES = {
|
|
35
|
+
ASTEROIDS: { asteroids: {} },
|
|
36
|
+
SNAKE: { snake: {} },
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
// Match types
|
|
40
|
+
const MATCH_TYPES = {
|
|
41
|
+
BEST_OF_1: { bestOf1: {} },
|
|
42
|
+
BEST_OF_3: { bestOf3: {} },
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
// Match status
|
|
46
|
+
const MATCH_STATUS = {
|
|
47
|
+
WAITING_FOR_OPPONENT: 0,
|
|
48
|
+
ACTIVE: 1,
|
|
49
|
+
RESOLVED: 2,
|
|
50
|
+
CANCELLED: 3,
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
class ArcadeMatchService {
|
|
54
|
+
constructor(config) {
|
|
55
|
+
this.connection = new Connection(config.rpcUrl || 'http://127.0.0.1:8899', 'confirmed');
|
|
56
|
+
this.programId = ARCADE_PROGRAM_ID;
|
|
57
|
+
this.operatorWallet = OPERATOR_WALLET;
|
|
58
|
+
this.walletsDir = config.walletsDir || path.join(__dirname, '..', 'wallets');
|
|
59
|
+
|
|
60
|
+
// Ensure wallets directory exists
|
|
61
|
+
if (!fs.existsSync(this.walletsDir)) {
|
|
62
|
+
fs.mkdirSync(this.walletsDir, { recursive: true });
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
console.log('🎮 Arcade Match Service initialized');
|
|
66
|
+
console.log(' Program ID:', this.programId.toString());
|
|
67
|
+
console.log(' Operator:', this.operatorWallet.toString());
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Get match PDA from match ID
|
|
72
|
+
*/
|
|
73
|
+
getMatchPDA(matchId) {
|
|
74
|
+
const matchIdNum = typeof matchId === 'string' ? this.uuidToU64(matchId) : BigInt(matchId);
|
|
75
|
+
const matchIdBuf = Buffer.alloc(8);
|
|
76
|
+
matchIdBuf.writeBigUInt64LE(matchIdNum);
|
|
77
|
+
return PublicKey.findProgramAddressSync(
|
|
78
|
+
[Buffer.from('match'), matchIdBuf],
|
|
79
|
+
this.programId
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Convert UUID to u64 for use as match ID
|
|
85
|
+
*/
|
|
86
|
+
uuidToU64(uuid) {
|
|
87
|
+
const hash = crypto.createHash('sha256').update(uuid).digest();
|
|
88
|
+
return hash.readBigUInt64LE(0);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Get or create a wallet for a player
|
|
93
|
+
*/
|
|
94
|
+
getWallet(playerName) {
|
|
95
|
+
const walletPath = path.join(this.walletsDir, `arcade_${playerName}.json`);
|
|
96
|
+
|
|
97
|
+
if (fs.existsSync(walletPath)) {
|
|
98
|
+
const secretKey = JSON.parse(fs.readFileSync(walletPath, 'utf-8'));
|
|
99
|
+
return Keypair.fromSecretKey(Uint8Array.from(secretKey));
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const newWallet = Keypair.generate();
|
|
103
|
+
fs.writeFileSync(walletPath, JSON.stringify(Array.from(newWallet.secretKey)));
|
|
104
|
+
console.log(`✅ Created arcade wallet for ${playerName}`);
|
|
105
|
+
return newWallet;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Fetch match data from chain
|
|
110
|
+
*/
|
|
111
|
+
async getMatchData(matchId) {
|
|
112
|
+
try {
|
|
113
|
+
const [matchPDA] = this.getMatchPDA(matchId);
|
|
114
|
+
const accountInfo = await this.connection.getAccountInfo(matchPDA);
|
|
115
|
+
|
|
116
|
+
if (!accountInfo) {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Parse match account data (simplified - you may want to use Anchor's IDL parser)
|
|
121
|
+
const data = accountInfo.data;
|
|
122
|
+
|
|
123
|
+
// Basic structure (you'll need to adjust based on actual layout)
|
|
124
|
+
return {
|
|
125
|
+
matchId: matchId,
|
|
126
|
+
pda: matchPDA.toString(),
|
|
127
|
+
exists: true,
|
|
128
|
+
lamports: accountInfo.lamports,
|
|
129
|
+
// Add more parsing as needed
|
|
130
|
+
};
|
|
131
|
+
} catch (error) {
|
|
132
|
+
console.error('Error fetching match data:', error);
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Build UNSIGNED transaction for creating a match
|
|
139
|
+
* Returns base64 encoded transaction for user to sign
|
|
140
|
+
*/
|
|
141
|
+
async buildCreateMatchTransaction(params) {
|
|
142
|
+
const {
|
|
143
|
+
creatorAddress,
|
|
144
|
+
stake,
|
|
145
|
+
gameType = 'asteroids',
|
|
146
|
+
matchType = 'bestOf1',
|
|
147
|
+
deadlineMinutes = 60,
|
|
148
|
+
} = params;
|
|
149
|
+
|
|
150
|
+
try {
|
|
151
|
+
// Generate UUID for Firebase and human-readable sharing
|
|
152
|
+
const matchUuid = uuidv4();
|
|
153
|
+
// Convert to u64 for on-chain storage
|
|
154
|
+
const matchId = this.uuidToU64(matchUuid);
|
|
155
|
+
const [matchPDA] = this.getMatchPDA(matchId);
|
|
156
|
+
|
|
157
|
+
// Calculate deadline
|
|
158
|
+
const currentTime = Math.floor(Date.now() / 1000);
|
|
159
|
+
const deadline = currentTime + (deadlineMinutes * 60);
|
|
160
|
+
|
|
161
|
+
// Build instruction data
|
|
162
|
+
const matchIdBuf = Buffer.alloc(8);
|
|
163
|
+
matchIdBuf.writeBigUInt64LE(matchId);
|
|
164
|
+
|
|
165
|
+
const stakeBuf = Buffer.alloc(8);
|
|
166
|
+
stakeBuf.writeBigUInt64LE(BigInt(stake));
|
|
167
|
+
|
|
168
|
+
const gameTypeBuf = Buffer.from([gameType === 'asteroids' ? 0 : 1]);
|
|
169
|
+
const matchTypeBuf = Buffer.from([matchType === 'bestOf1' ? 0 : 1]);
|
|
170
|
+
|
|
171
|
+
const deadlineBuf = Buffer.alloc(8);
|
|
172
|
+
deadlineBuf.writeBigInt64LE(BigInt(deadline));
|
|
173
|
+
|
|
174
|
+
const instructionData = Buffer.concat([
|
|
175
|
+
DISCRIMINATORS.CREATE_MATCH,
|
|
176
|
+
matchIdBuf,
|
|
177
|
+
stakeBuf,
|
|
178
|
+
gameTypeBuf,
|
|
179
|
+
matchTypeBuf,
|
|
180
|
+
deadlineBuf,
|
|
181
|
+
]);
|
|
182
|
+
|
|
183
|
+
const creatorPubkey = new PublicKey(creatorAddress);
|
|
184
|
+
|
|
185
|
+
const instruction = new TransactionInstruction({
|
|
186
|
+
programId: this.programId,
|
|
187
|
+
keys: [
|
|
188
|
+
{ pubkey: matchPDA, isSigner: false, isWritable: true },
|
|
189
|
+
{ pubkey: creatorPubkey, isSigner: true, isWritable: true },
|
|
190
|
+
{ pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
|
|
191
|
+
],
|
|
192
|
+
data: instructionData,
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
// Build unsigned transaction
|
|
196
|
+
const transaction = new Transaction().add(instruction);
|
|
197
|
+
|
|
198
|
+
// Get recent blockhash
|
|
199
|
+
const { blockhash } = await this.connection.getLatestBlockhash('confirmed');
|
|
200
|
+
transaction.recentBlockhash = blockhash;
|
|
201
|
+
transaction.feePayer = creatorPubkey;
|
|
202
|
+
|
|
203
|
+
// Serialize to base64
|
|
204
|
+
const serialized = transaction.serialize({
|
|
205
|
+
requireAllSignatures: false,
|
|
206
|
+
verifySignatures: false,
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
console.log(`✅ Built create match transaction`);
|
|
210
|
+
console.log(` Match UUID: ${matchUuid}`);
|
|
211
|
+
console.log(` Match PDA: ${matchPDA.toString()}`);
|
|
212
|
+
|
|
213
|
+
return {
|
|
214
|
+
success: true,
|
|
215
|
+
matchId: matchId.toString(), // u64 for on-chain
|
|
216
|
+
matchUuid: matchUuid, // UUID for Firebase/sharing
|
|
217
|
+
matchPDA: matchPDA.toString(),
|
|
218
|
+
transaction: serialized.toString('base64'),
|
|
219
|
+
stake,
|
|
220
|
+
gameType,
|
|
221
|
+
matchType,
|
|
222
|
+
};
|
|
223
|
+
} catch (error) {
|
|
224
|
+
console.error('Error building create match transaction:', error);
|
|
225
|
+
throw error;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Create a new arcade match (LEGACY - for testing with test wallets)
|
|
231
|
+
*/
|
|
232
|
+
async createMatch(params) {
|
|
233
|
+
const {
|
|
234
|
+
matchId,
|
|
235
|
+
creatorName,
|
|
236
|
+
stake,
|
|
237
|
+
gameType = 'asteroids',
|
|
238
|
+
matchType = 'bestOf1',
|
|
239
|
+
deadlineMinutes = 60,
|
|
240
|
+
} = params;
|
|
241
|
+
|
|
242
|
+
try {
|
|
243
|
+
const creator = this.getWallet(creatorName);
|
|
244
|
+
const [matchPDA] = this.getMatchPDA(matchId);
|
|
245
|
+
|
|
246
|
+
// Calculate deadline
|
|
247
|
+
const currentTime = Math.floor(Date.now() / 1000);
|
|
248
|
+
const deadline = currentTime + (deadlineMinutes * 60);
|
|
249
|
+
|
|
250
|
+
// Build instruction data with proper Borsh encoding
|
|
251
|
+
const matchIdNum = typeof matchId === 'string' ? this.uuidToU64(matchId) : BigInt(matchId);
|
|
252
|
+
const matchIdBuf = Buffer.alloc(8);
|
|
253
|
+
matchIdBuf.writeBigUInt64LE(matchIdNum);
|
|
254
|
+
|
|
255
|
+
const stakeBuf = Buffer.alloc(8);
|
|
256
|
+
stakeBuf.writeBigUInt64LE(BigInt(stake));
|
|
257
|
+
|
|
258
|
+
// Borsh enum encoding: variant index (u8) only (no data for these simple enums)
|
|
259
|
+
const gameTypeBuf = Buffer.from([gameType === 'asteroids' ? 0 : 1]);
|
|
260
|
+
const matchTypeBuf = Buffer.from([matchType === 'bestOf1' ? 0 : 1]);
|
|
261
|
+
|
|
262
|
+
const deadlineBuf = Buffer.alloc(8);
|
|
263
|
+
deadlineBuf.writeBigInt64LE(BigInt(deadline));
|
|
264
|
+
|
|
265
|
+
const instructionData = Buffer.concat([
|
|
266
|
+
DISCRIMINATORS.CREATE_MATCH,
|
|
267
|
+
matchIdBuf,
|
|
268
|
+
stakeBuf,
|
|
269
|
+
gameTypeBuf,
|
|
270
|
+
matchTypeBuf,
|
|
271
|
+
deadlineBuf,
|
|
272
|
+
]);
|
|
273
|
+
|
|
274
|
+
const instruction = new TransactionInstruction({
|
|
275
|
+
programId: this.programId,
|
|
276
|
+
keys: [
|
|
277
|
+
{ pubkey: matchPDA, isSigner: false, isWritable: true },
|
|
278
|
+
{ pubkey: creator.publicKey, isSigner: true, isWritable: true },
|
|
279
|
+
{ pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
|
|
280
|
+
],
|
|
281
|
+
data: instructionData,
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
const transaction = new Transaction().add(instruction);
|
|
285
|
+
const signature = await this.connection.sendTransaction(transaction, [creator]);
|
|
286
|
+
await this.connection.confirmTransaction(signature);
|
|
287
|
+
|
|
288
|
+
console.log(`✅ Match created: ${matchId}`);
|
|
289
|
+
console.log(` Signature: ${signature}`);
|
|
290
|
+
|
|
291
|
+
return {
|
|
292
|
+
success: true,
|
|
293
|
+
matchId,
|
|
294
|
+
signature,
|
|
295
|
+
matchPDA: matchPDA.toString(),
|
|
296
|
+
creator: creator.publicKey.toString(),
|
|
297
|
+
stake,
|
|
298
|
+
gameType,
|
|
299
|
+
matchType,
|
|
300
|
+
deadline: new Date(deadline * 1000).toISOString(),
|
|
301
|
+
};
|
|
302
|
+
} catch (error) {
|
|
303
|
+
console.error('Error creating match:', error);
|
|
304
|
+
throw error;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Build UNSIGNED transaction for joining a match
|
|
310
|
+
*/
|
|
311
|
+
async buildJoinMatchTransaction(params) {
|
|
312
|
+
const { matchId, opponentAddress } = params;
|
|
313
|
+
|
|
314
|
+
try {
|
|
315
|
+
const matchIdNum = typeof matchId === 'string' ? BigInt(matchId) : BigInt(matchId);
|
|
316
|
+
const [matchPDA] = this.getMatchPDA(matchIdNum);
|
|
317
|
+
|
|
318
|
+
// Build instruction data
|
|
319
|
+
const matchIdBuf = Buffer.alloc(8);
|
|
320
|
+
matchIdBuf.writeBigUInt64LE(matchIdNum);
|
|
321
|
+
|
|
322
|
+
const instructionData = Buffer.concat([
|
|
323
|
+
DISCRIMINATORS.JOIN_MATCH,
|
|
324
|
+
matchIdBuf,
|
|
325
|
+
]);
|
|
326
|
+
|
|
327
|
+
const opponentPubkey = new PublicKey(opponentAddress);
|
|
328
|
+
|
|
329
|
+
const instruction = new TransactionInstruction({
|
|
330
|
+
programId: this.programId,
|
|
331
|
+
keys: [
|
|
332
|
+
{ pubkey: matchPDA, isSigner: false, isWritable: true },
|
|
333
|
+
{ pubkey: opponentPubkey, isSigner: true, isWritable: true },
|
|
334
|
+
{ pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
|
|
335
|
+
],
|
|
336
|
+
data: instructionData,
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
// Build unsigned transaction
|
|
340
|
+
const transaction = new Transaction().add(instruction);
|
|
341
|
+
|
|
342
|
+
// Get recent blockhash
|
|
343
|
+
const { blockhash } = await this.connection.getLatestBlockhash('confirmed');
|
|
344
|
+
transaction.recentBlockhash = blockhash;
|
|
345
|
+
transaction.feePayer = opponentPubkey;
|
|
346
|
+
|
|
347
|
+
// Serialize to base64
|
|
348
|
+
const serialized = transaction.serialize({
|
|
349
|
+
requireAllSignatures: false,
|
|
350
|
+
verifySignatures: false,
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
console.log(`✅ Built join match transaction`);
|
|
354
|
+
console.log(` Match PDA: ${matchPDA.toString()}`);
|
|
355
|
+
|
|
356
|
+
return {
|
|
357
|
+
success: true,
|
|
358
|
+
matchId: matchId.toString(),
|
|
359
|
+
transaction: serialized.toString('base64'),
|
|
360
|
+
};
|
|
361
|
+
} catch (error) {
|
|
362
|
+
console.error('Error building join match transaction:', error);
|
|
363
|
+
throw error;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Build UNSIGNED transaction for submitting a score
|
|
369
|
+
*/
|
|
370
|
+
async buildSubmitScoreTransaction(params) {
|
|
371
|
+
const { matchId, playerAddress, score, gameRound = 1 } = params;
|
|
372
|
+
|
|
373
|
+
console.log('🎮 [Backend] Building submit score transaction:');
|
|
374
|
+
console.log(' matchId:', matchId);
|
|
375
|
+
console.log(' playerAddress:', playerAddress);
|
|
376
|
+
console.log(' score:', score);
|
|
377
|
+
console.log(' gameRound:', gameRound, 'Type:', typeof gameRound);
|
|
378
|
+
|
|
379
|
+
try {
|
|
380
|
+
const matchIdNum = typeof matchId === 'string' ? BigInt(matchId) : BigInt(matchId);
|
|
381
|
+
const [matchPDA] = this.getMatchPDA(matchIdNum);
|
|
382
|
+
|
|
383
|
+
// Build instruction data
|
|
384
|
+
const matchIdBuf = Buffer.alloc(8);
|
|
385
|
+
matchIdBuf.writeBigUInt64LE(matchIdNum);
|
|
386
|
+
|
|
387
|
+
const scoreBuf = Buffer.alloc(8);
|
|
388
|
+
scoreBuf.writeBigUInt64LE(BigInt(score));
|
|
389
|
+
|
|
390
|
+
const roundBuf = Buffer.from([gameRound]);
|
|
391
|
+
|
|
392
|
+
const instructionData = Buffer.concat([
|
|
393
|
+
DISCRIMINATORS.SUBMIT_SCORE,
|
|
394
|
+
matchIdBuf,
|
|
395
|
+
scoreBuf,
|
|
396
|
+
roundBuf,
|
|
397
|
+
]);
|
|
398
|
+
|
|
399
|
+
const playerPubkey = new PublicKey(playerAddress);
|
|
400
|
+
|
|
401
|
+
const instruction = new TransactionInstruction({
|
|
402
|
+
programId: this.programId,
|
|
403
|
+
keys: [
|
|
404
|
+
{ pubkey: matchPDA, isSigner: false, isWritable: true },
|
|
405
|
+
{ pubkey: playerPubkey, isSigner: true, isWritable: false },
|
|
406
|
+
],
|
|
407
|
+
data: instructionData,
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
// Build unsigned transaction
|
|
411
|
+
const transaction = new Transaction().add(instruction);
|
|
412
|
+
|
|
413
|
+
// Get recent blockhash
|
|
414
|
+
const { blockhash } = await this.connection.getLatestBlockhash('confirmed');
|
|
415
|
+
transaction.recentBlockhash = blockhash;
|
|
416
|
+
transaction.feePayer = playerPubkey;
|
|
417
|
+
|
|
418
|
+
// Serialize to base64
|
|
419
|
+
const serialized = transaction.serialize({
|
|
420
|
+
requireAllSignatures: false,
|
|
421
|
+
verifySignatures: false,
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
console.log(`✅ Built submit score transaction`);
|
|
425
|
+
console.log(` Match PDA: ${matchPDA.toString()}`);
|
|
426
|
+
console.log(` Score: ${score}, Round: ${gameRound}`);
|
|
427
|
+
|
|
428
|
+
return {
|
|
429
|
+
success: true,
|
|
430
|
+
matchId: matchId.toString(),
|
|
431
|
+
transaction: serialized.toString('base64'),
|
|
432
|
+
};
|
|
433
|
+
} catch (error) {
|
|
434
|
+
console.error('Error building submit score transaction:', error);
|
|
435
|
+
throw error;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* Join an existing match (LEGACY - for testing with test wallets)
|
|
441
|
+
*/
|
|
442
|
+
async joinMatch(params) {
|
|
443
|
+
const { matchId, opponentName } = params;
|
|
444
|
+
|
|
445
|
+
try {
|
|
446
|
+
const opponent = this.getWallet(opponentName);
|
|
447
|
+
const [matchPDA] = this.getMatchPDA(matchId);
|
|
448
|
+
|
|
449
|
+
// Build instruction data
|
|
450
|
+
const matchIdNum = typeof matchId === 'string' ? this.uuidToU64(matchId) : BigInt(matchId);
|
|
451
|
+
const matchIdBuf = Buffer.alloc(8);
|
|
452
|
+
matchIdBuf.writeBigUInt64LE(matchIdNum);
|
|
453
|
+
|
|
454
|
+
const instructionData = Buffer.concat([
|
|
455
|
+
DISCRIMINATORS.JOIN_MATCH,
|
|
456
|
+
matchIdBuf,
|
|
457
|
+
]);
|
|
458
|
+
|
|
459
|
+
const instruction = new TransactionInstruction({
|
|
460
|
+
programId: this.programId,
|
|
461
|
+
keys: [
|
|
462
|
+
{ pubkey: matchPDA, isSigner: false, isWritable: true },
|
|
463
|
+
{ pubkey: opponent.publicKey, isSigner: true, isWritable: true },
|
|
464
|
+
{ pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
|
|
465
|
+
],
|
|
466
|
+
data: instructionData,
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
const transaction = new Transaction().add(instruction);
|
|
470
|
+
const signature = await this.connection.sendTransaction(transaction, [opponent]);
|
|
471
|
+
await this.connection.confirmTransaction(signature);
|
|
472
|
+
|
|
473
|
+
console.log(`✅ Player joined match: ${matchId}`);
|
|
474
|
+
console.log(` Signature: ${signature}`);
|
|
475
|
+
|
|
476
|
+
return {
|
|
477
|
+
success: true,
|
|
478
|
+
matchId,
|
|
479
|
+
signature,
|
|
480
|
+
opponent: opponent.publicKey.toString(),
|
|
481
|
+
};
|
|
482
|
+
} catch (error) {
|
|
483
|
+
console.error('Error joining match:', error);
|
|
484
|
+
throw error;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* Submit a score for a match
|
|
490
|
+
*/
|
|
491
|
+
async submitScore(params) {
|
|
492
|
+
const { matchId, playerName, score, gameRound = 1 } = params;
|
|
493
|
+
|
|
494
|
+
try {
|
|
495
|
+
const player = this.getWallet(playerName);
|
|
496
|
+
const [matchPDA] = this.getMatchPDA(matchId);
|
|
497
|
+
|
|
498
|
+
// Build instruction data
|
|
499
|
+
const matchIdNum = typeof matchId === 'string' ? this.uuidToU64(matchId) : BigInt(matchId);
|
|
500
|
+
const matchIdBuf = Buffer.alloc(8);
|
|
501
|
+
matchIdBuf.writeBigUInt64LE(matchIdNum);
|
|
502
|
+
|
|
503
|
+
const scoreBuf = Buffer.alloc(8);
|
|
504
|
+
scoreBuf.writeBigUInt64LE(BigInt(score));
|
|
505
|
+
|
|
506
|
+
const roundBuf = Buffer.alloc(1);
|
|
507
|
+
roundBuf.writeUInt8(gameRound);
|
|
508
|
+
|
|
509
|
+
const instructionData = Buffer.concat([
|
|
510
|
+
DISCRIMINATORS.SUBMIT_SCORE,
|
|
511
|
+
matchIdBuf,
|
|
512
|
+
scoreBuf,
|
|
513
|
+
roundBuf,
|
|
514
|
+
]);
|
|
515
|
+
|
|
516
|
+
const instruction = new TransactionInstruction({
|
|
517
|
+
programId: this.programId,
|
|
518
|
+
keys: [
|
|
519
|
+
{ pubkey: matchPDA, isSigner: false, isWritable: true },
|
|
520
|
+
{ pubkey: player.publicKey, isSigner: true, isWritable: false },
|
|
521
|
+
],
|
|
522
|
+
data: instructionData,
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
const transaction = new Transaction().add(instruction);
|
|
526
|
+
const signature = await this.connection.sendTransaction(transaction, [player]);
|
|
527
|
+
await this.connection.confirmTransaction(signature);
|
|
528
|
+
|
|
529
|
+
console.log(`✅ Score submitted: ${playerName} scored ${score} in round ${gameRound}`);
|
|
530
|
+
console.log(` Signature: ${signature}`);
|
|
531
|
+
|
|
532
|
+
return {
|
|
533
|
+
success: true,
|
|
534
|
+
matchId,
|
|
535
|
+
signature,
|
|
536
|
+
player: player.publicKey.toString(),
|
|
537
|
+
score,
|
|
538
|
+
gameRound,
|
|
539
|
+
};
|
|
540
|
+
} catch (error) {
|
|
541
|
+
console.error('Error submitting score:', error);
|
|
542
|
+
throw error;
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
/**
|
|
547
|
+
* Build UNSIGNED transaction for resolving a match
|
|
548
|
+
*/
|
|
549
|
+
async buildResolveMatchTransaction(params) {
|
|
550
|
+
const { matchId, callerAddress, creatorAddress, opponentAddress } = params;
|
|
551
|
+
|
|
552
|
+
try {
|
|
553
|
+
const matchIdNum = typeof matchId === 'string' ? BigInt(matchId) : BigInt(matchId);
|
|
554
|
+
const [matchPDA] = this.getMatchPDA(matchIdNum);
|
|
555
|
+
|
|
556
|
+
// Build instruction data
|
|
557
|
+
const matchIdBuf = Buffer.alloc(8);
|
|
558
|
+
matchIdBuf.writeBigUInt64LE(matchIdNum);
|
|
559
|
+
|
|
560
|
+
const instructionData = Buffer.concat([
|
|
561
|
+
DISCRIMINATORS.RESOLVE_MATCH,
|
|
562
|
+
matchIdBuf,
|
|
563
|
+
]);
|
|
564
|
+
|
|
565
|
+
const callerPubkey = new PublicKey(callerAddress);
|
|
566
|
+
const creatorPubkey = new PublicKey(creatorAddress);
|
|
567
|
+
const opponentPubkey = new PublicKey(opponentAddress);
|
|
568
|
+
|
|
569
|
+
const instruction = new TransactionInstruction({
|
|
570
|
+
programId: this.programId,
|
|
571
|
+
keys: [
|
|
572
|
+
{ pubkey: matchPDA, isSigner: false, isWritable: true },
|
|
573
|
+
{ pubkey: callerPubkey, isSigner: true, isWritable: false }, // Caller (anyone - pays gas)
|
|
574
|
+
// Remaining accounts for payouts: [0]=Operator, [1]=Creator, [2]=Opponent
|
|
575
|
+
{ pubkey: this.operatorWallet, isSigner: false, isWritable: true },
|
|
576
|
+
{ pubkey: creatorPubkey, isSigner: false, isWritable: true },
|
|
577
|
+
{ pubkey: opponentPubkey, isSigner: false, isWritable: true },
|
|
578
|
+
],
|
|
579
|
+
data: instructionData,
|
|
580
|
+
});
|
|
581
|
+
|
|
582
|
+
// Build unsigned transaction
|
|
583
|
+
const transaction = new Transaction().add(instruction);
|
|
584
|
+
|
|
585
|
+
// Get recent blockhash
|
|
586
|
+
const { blockhash } = await this.connection.getLatestBlockhash('confirmed');
|
|
587
|
+
transaction.recentBlockhash = blockhash;
|
|
588
|
+
transaction.feePayer = callerPubkey; // Caller pays gas
|
|
589
|
+
|
|
590
|
+
// Serialize to base64
|
|
591
|
+
const serialized = transaction.serialize({
|
|
592
|
+
requireAllSignatures: false,
|
|
593
|
+
verifySignatures: false,
|
|
594
|
+
});
|
|
595
|
+
|
|
596
|
+
console.log(`✅ Built resolve match transaction`);
|
|
597
|
+
console.log(` Match PDA: ${matchPDA.toString()}`);
|
|
598
|
+
|
|
599
|
+
return {
|
|
600
|
+
success: true,
|
|
601
|
+
matchId: matchId.toString(),
|
|
602
|
+
transaction: serialized.toString('base64'),
|
|
603
|
+
};
|
|
604
|
+
} catch (error) {
|
|
605
|
+
console.error('Error building resolve match transaction:', error);
|
|
606
|
+
throw error;
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
/**
|
|
611
|
+
* Resolve a match and distribute winnings (LEGACY)
|
|
612
|
+
*/
|
|
613
|
+
async resolveMatch(params) {
|
|
614
|
+
const { matchId, callerName } = params;
|
|
615
|
+
|
|
616
|
+
try {
|
|
617
|
+
const caller = this.getWallet(callerName);
|
|
618
|
+
const [matchPDA] = this.getMatchPDA(matchId);
|
|
619
|
+
|
|
620
|
+
// Fetch match data to determine winner and opponent
|
|
621
|
+
const matchData = await this.getMatchData(matchId);
|
|
622
|
+
if (!matchData) {
|
|
623
|
+
throw new Error('Match not found');
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
// Build instruction data
|
|
627
|
+
const matchIdNum = typeof matchId === 'string' ? this.uuidToU64(matchId) : BigInt(matchId);
|
|
628
|
+
const matchIdBuf = Buffer.alloc(8);
|
|
629
|
+
matchIdBuf.writeBigUInt64LE(matchIdNum);
|
|
630
|
+
|
|
631
|
+
const instructionData = Buffer.concat([
|
|
632
|
+
DISCRIMINATORS.RESOLVE_MATCH,
|
|
633
|
+
matchIdBuf,
|
|
634
|
+
]);
|
|
635
|
+
|
|
636
|
+
// Build accounts - need to include operator and opponent in remaining_accounts
|
|
637
|
+
const instruction = new TransactionInstruction({
|
|
638
|
+
programId: this.programId,
|
|
639
|
+
keys: [
|
|
640
|
+
{ pubkey: matchPDA, isSigner: false, isWritable: true },
|
|
641
|
+
{ pubkey: caller.publicKey, isSigner: true, isWritable: true },
|
|
642
|
+
// Remaining accounts (operator + opponent)
|
|
643
|
+
{ pubkey: this.operatorWallet, isSigner: false, isWritable: true },
|
|
644
|
+
// Note: You'll need to fetch opponent pubkey from match data
|
|
645
|
+
// { pubkey: opponentPubkey, isSigner: false, isWritable: true },
|
|
646
|
+
],
|
|
647
|
+
data: instructionData,
|
|
648
|
+
});
|
|
649
|
+
|
|
650
|
+
const transaction = new Transaction().add(instruction);
|
|
651
|
+
const signature = await this.connection.sendTransaction(transaction, [caller]);
|
|
652
|
+
await this.connection.confirmTransaction(signature);
|
|
653
|
+
|
|
654
|
+
console.log(`✅ Match resolved: ${matchId}`);
|
|
655
|
+
console.log(` Signature: ${signature}`);
|
|
656
|
+
|
|
657
|
+
return {
|
|
658
|
+
success: true,
|
|
659
|
+
matchId,
|
|
660
|
+
signature,
|
|
661
|
+
};
|
|
662
|
+
} catch (error) {
|
|
663
|
+
console.error('Error resolving match:', error);
|
|
664
|
+
throw error;
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
/**
|
|
669
|
+
* Cancel a match (if no opponent joined)
|
|
670
|
+
*/
|
|
671
|
+
async cancelMatch(params) {
|
|
672
|
+
const { matchId, creatorName } = params;
|
|
673
|
+
|
|
674
|
+
try {
|
|
675
|
+
const creator = this.getWallet(creatorName);
|
|
676
|
+
const [matchPDA] = this.getMatchPDA(matchId);
|
|
677
|
+
|
|
678
|
+
// Build instruction data
|
|
679
|
+
const matchIdNum = typeof matchId === 'string' ? this.uuidToU64(matchId) : BigInt(matchId);
|
|
680
|
+
const matchIdBuf = Buffer.alloc(8);
|
|
681
|
+
matchIdBuf.writeBigUInt64LE(matchIdNum);
|
|
682
|
+
|
|
683
|
+
const instructionData = Buffer.concat([
|
|
684
|
+
DISCRIMINATORS.CANCEL_MATCH,
|
|
685
|
+
matchIdBuf,
|
|
686
|
+
]);
|
|
687
|
+
|
|
688
|
+
const instruction = new TransactionInstruction({
|
|
689
|
+
programId: this.programId,
|
|
690
|
+
keys: [
|
|
691
|
+
{ pubkey: matchPDA, isSigner: false, isWritable: true },
|
|
692
|
+
{ pubkey: creator.publicKey, isSigner: true, isWritable: true },
|
|
693
|
+
],
|
|
694
|
+
data: instructionData,
|
|
695
|
+
});
|
|
696
|
+
|
|
697
|
+
const transaction = new Transaction().add(instruction);
|
|
698
|
+
const signature = await this.connection.sendTransaction(transaction, [creator]);
|
|
699
|
+
await this.connection.confirmTransaction(signature);
|
|
700
|
+
|
|
701
|
+
console.log(`✅ Match cancelled: ${matchId}`);
|
|
702
|
+
console.log(` Signature: ${signature}`);
|
|
703
|
+
|
|
704
|
+
return {
|
|
705
|
+
success: true,
|
|
706
|
+
matchId,
|
|
707
|
+
signature,
|
|
708
|
+
};
|
|
709
|
+
} catch (error) {
|
|
710
|
+
console.error('Error cancelling match:', error);
|
|
711
|
+
throw error;
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
/**
|
|
716
|
+
* Airdrop SOL to a wallet (for testing)
|
|
717
|
+
*/
|
|
718
|
+
async airdrop(playerName, amount = 1) {
|
|
719
|
+
try {
|
|
720
|
+
const wallet = this.getWallet(playerName);
|
|
721
|
+
const signature = await this.connection.requestAirdrop(
|
|
722
|
+
wallet.publicKey,
|
|
723
|
+
amount * 1_000_000_000
|
|
724
|
+
);
|
|
725
|
+
await this.connection.confirmTransaction(signature);
|
|
726
|
+
|
|
727
|
+
console.log(`✅ Airdropped ${amount} SOL to ${playerName}`);
|
|
728
|
+
return {
|
|
729
|
+
success: true,
|
|
730
|
+
player: playerName,
|
|
731
|
+
pubkey: wallet.publicKey.toString(),
|
|
732
|
+
amount,
|
|
733
|
+
signature,
|
|
734
|
+
};
|
|
735
|
+
} catch (error) {
|
|
736
|
+
console.error('Error airdropping:', error);
|
|
737
|
+
throw error;
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
/**
|
|
742
|
+
* Get wallet balance
|
|
743
|
+
*/
|
|
744
|
+
async getBalance(playerName) {
|
|
745
|
+
try {
|
|
746
|
+
const wallet = this.getWallet(playerName);
|
|
747
|
+
const balance = await this.connection.getBalance(wallet.publicKey);
|
|
748
|
+
|
|
749
|
+
return {
|
|
750
|
+
player: playerName,
|
|
751
|
+
pubkey: wallet.publicKey.toString(),
|
|
752
|
+
balance: balance / 1_000_000_000,
|
|
753
|
+
lamports: balance,
|
|
754
|
+
};
|
|
755
|
+
} catch (error) {
|
|
756
|
+
console.error('Error getting balance:', error);
|
|
757
|
+
throw error;
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
module.exports = ArcadeMatchService;
|
|
763
|
+
|