This commit is contained in:
Sewmina 2025-04-09 13:03:05 +05:30
parent de435d89aa
commit 147ef9e669
12 changed files with 80 additions and 61 deletions

View File

@ -8,7 +8,8 @@ import { PriceSelection } from "./PriceSelection";
import { GameSelection } from "./GameSelection"; import { GameSelection } from "./GameSelection";
import { createBet } from "@/shared/solana_helpers"; import { createBet } from "@/shared/solana_helpers";
import { Game } from "@/types/Game"; import { Game } from "@/types/Game";
import { EXPLORER_TX_TEMPLATE } from "@/data/shared"; import { connection, EXPLORER_TX_TEMPLATE } from "@/data/shared";
import { CONFIRMATION_THRESHOLD } from "@/shared/constants";
interface GameModalProps { interface GameModalProps {
isOpen: boolean; isOpen: boolean;
@ -61,23 +62,33 @@ export default function GameModal({ isOpen, onClose }: GameModalProps) {
} }
setIsProcessing(true); setIsProcessing(true);
toast.loading("Creating bet");
try { try {
const tx = await createBet(wallet, user?.id ?? "", selectedPrice, selectedGame.id, false); const tx = await createBet(wallet, user?.id ?? "", selectedPrice, selectedGame.id, false);
const url = EXPLORER_TX_TEMPLATE.replace("{address}", tx); const url = EXPLORER_TX_TEMPLATE.replace("{address}", tx);
if (tx.length > 5) { if (tx.length > 5) {
toast.success(`Bet created successfully!`, { connection.confirmTransaction(tx, CONFIRMATION_THRESHOLD).finally(()=>{
action: { toast.dismiss();
label: "View TX",
onClick: () => window.open(url, "_blank"), toast.success(`Bet created successfully!`, {
}, action: {
}); label: "View TX",
onClick: () => window.open(url, "_blank"),
},
});
})
} }
onClose(); onClose();
} catch (error) { } catch (error) {
console.error("Error creating bet:", error); console.error("Error creating bet:", error);
toast.error("Failed to create bet. Please try again."); toast.error("Failed to create bet. Please try again.");
toast.dismiss();
} finally { } finally {
setIsProcessing(false); setIsProcessing(false);
} }
}; };

View File

@ -7,7 +7,7 @@ import GameModal from "./GameModal";
import { HowItWorksModal } from "./HowItWorksModal"; import { HowItWorksModal } from "./HowItWorksModal";
import YourGames from "./YourGames"; import YourGames from "./YourGames";
import { Bet } from "@/types/Bet"; import { Bet } from "@/types/Bet";
import { fetchOpenBets, createBet, joinBet, getVaultByAddress } from "@/shared/solana_helpers"; import { fetchOpenBets, createBet, getVaultByAddress } from "@/shared/solana_helpers";
import { ConnectedSolanaWallet, usePrivy, useSolanaWallets } from "@privy-io/react-auth"; import { ConnectedSolanaWallet, usePrivy, useSolanaWallets } from "@privy-io/react-auth";
import { RematchModal } from "./RematchModal"; import { RematchModal } from "./RematchModal";
@ -21,7 +21,6 @@ export default function HeroSection() {
const [lastActiveBet, setLastActiveBet] = useState<Bet>(); const [lastActiveBet, setLastActiveBet] = useState<Bet>();
const [rematchInProgress, setRematchInProgress] = useState(false); const [rematchInProgress, setRematchInProgress] = useState(false);
const [rematchTxError, setRematchTxError] = useState(false); const [rematchTxError, setRematchTxError] = useState(false);
const [rematchBetAddress, setRematchBetAddress] = useState<string | null>(null);
const { wallets, ready } = useSolanaWallets(); const { wallets, ready } = useSolanaWallets();
const { user } = usePrivy(); const { user } = usePrivy();
@ -102,8 +101,8 @@ export default function HeroSection() {
const pollUntilFound = async ( const pollUntilFound = async (
maxRetries = 10, maxRetries = 10,
delayMs = 3000 delayMs = 3000
): Promise<any> => { ): Promise<Bet | undefined> => {
for (let i = 0; i < maxRetries; i++) { for (let i:number = 0; i < maxRetries; i++) {
const vault = await getVaultByAddress(solWallet, tx); const vault = await getVaultByAddress(solWallet, tx);
if (vault) return vault; if (vault) return vault;
console.log(`Waiting for vault account... (${i + 1}/${maxRetries})`); console.log(`Waiting for vault account... (${i + 1}/${maxRetries})`);
@ -166,8 +165,8 @@ export default function HeroSection() {
address: string, address: string,
maxRetries = 10, maxRetries = 10,
delayMs = 3000 delayMs = 3000
): Promise<any> => { ): Promise<Bet | undefined> => {
for (let i = 0; i < maxRetries; i++) { for (let i:number = 0; i < maxRetries; i++) {
console.log(`Polling vault for address ${address}... (${i + 1}/${maxRetries})`); console.log(`Polling vault for address ${address}... (${i + 1}/${maxRetries})`);
const vault = await getVaultByAddress(solWallet, address); const vault = await getVaultByAddress(solWallet, address);
if (vault) return vault; if (vault) return vault;
@ -175,13 +174,14 @@ export default function HeroSection() {
} }
console.warn("Vault not found after max retries."); console.warn("Vault not found after max retries.");
return null; return;
}; };
const rematchVault:Bet = await pollForVault(rematchAddress); const rematchVault:Bet | undefined = await pollForVault(rematchAddress);
if (rematchVault) { if (rematchVault) {
const tx = await joinBet(solWallet, user?.id!, rematchVault.id, rematchVault.address); // const tx = await joinBet(solWallet, user?.id!, rematchVault.id, rematchVault.address);
setMyActiveBet(rematchVault); setMyActiveBet(rematchVault);
setLastActiveBet(rematchVault); setLastActiveBet(rematchVault);
setRematch(false); setRematch(false);
@ -286,6 +286,7 @@ export default function HeroSection() {
return ( return (
<> <>
{myActiveBet && !rematch ? ( {myActiveBet && !rematch ? (
(myActiveBet.owner_id && myActiveBet.joiner_id) ? (
<div className="w-full h-screen flex justify-center items-center bg-black"> <div className="w-full h-screen flex justify-center items-center bg-black">
<iframe <iframe
ref={iframeRef} ref={iframeRef}
@ -294,6 +295,10 @@ export default function HeroSection() {
allowFullScreen allowFullScreen
/> />
</div> </div>
) : <>
<div className="w-full h-screen flex justify-center items-center bg-black">
<label>Loading...</label>
</div></>
) : ( ) : (
<section className="flex flex-col items-center text-center py-16"> <section className="flex flex-col items-center text-center py-16">
<Image <Image

View File

@ -6,8 +6,8 @@ import { joinBet } from "@/shared/solana_helpers";
import { Bet } from "../types/Bet"; import { Bet } from "../types/Bet";
import { fetchUserById } from "@/shared/data_fetcher"; import { fetchUserById } from "@/shared/data_fetcher";
import { toast } from "sonner"; import { toast } from "sonner";
import { EXPLORER_TX_TEMPLATE } from "@/data/shared"; import { connection, EXPLORER_TX_TEMPLATE } from "@/data/shared";
import { WAGER_PRIZE_MULT } from "@/shared/constants"; import { CONFIRMATION_THRESHOLD, WAGER_PRIZE_MULT } from "@/shared/constants";
interface GameModalProps { interface GameModalProps {
bets: Bet[]; bets: Bet[];
@ -29,17 +29,22 @@ export default function YourGames({ bets }: GameModalProps) {
wallet = _wallet; wallet = _wallet;
} }
}); });
toast.loading("Joining Bet");
const tx = await joinBet(wallet, user?.id ?? "", selectedBet.id, selectedBet.address); const tx = await joinBet(wallet, user?.id ?? "", selectedBet.id, selectedBet.address);
const url = EXPLORER_TX_TEMPLATE.replace("{address}", tx); const url = EXPLORER_TX_TEMPLATE.replace("{address}", tx);
if (tx.length > 5) { if (tx.length > 5) {
toast.success("Joined game successfully!", { connection.confirmTransaction(tx, CONFIRMATION_THRESHOLD).finally(()=>{
action: { toast.dismiss();
label: "View TX",
onClick: () => window.open(url, "_blank"), toast.success("Joined game successfully!", {
}, action: {
}); label: "View TX",
onClick: () => window.open(url, "_blank"),
},
});
})
} else { } else {
toast.error("Failed to join this game"); toast.error("Failed to join this game");
} }

View File

@ -1,7 +1,5 @@
"use client"; "use client";
import { Bet } from "@/types/Bet"; import React from "react";
import React, { useEffect } from "react";
import { fetchOpenBets } from "../shared/solana_helpers"; // adjust path as needed
import { ConnectedSolanaWallet } from "@privy-io/react-auth"; // import your wallet type import { ConnectedSolanaWallet } from "@privy-io/react-auth"; // import your wallet type
interface RematchModalProps { interface RematchModalProps {
@ -13,9 +11,7 @@ interface RematchModalProps {
export const RematchModal: React.FC<RematchModalProps> = ({ export const RematchModal: React.FC<RematchModalProps> = ({
isOwner, isOwner,
inProgress,
hasError, hasError,
wallets
} : RematchModalProps) => { } : RematchModalProps) => {

View File

@ -7,7 +7,8 @@ import { useSolanaWallets } from "@privy-io/react-auth";
import { closeBet } from "@/shared/solana_helpers"; import { closeBet } from "@/shared/solana_helpers";
import { Bet } from "../types/Bet"; import { Bet } from "../types/Bet";
import { toast } from "sonner"; import { toast } from "sonner";
import { EXPLORER_TX_TEMPLATE } from "@/data/shared"; import { connection, EXPLORER_TX_TEMPLATE } from "@/data/shared";
import { CONFIRMATION_THRESHOLD } from "@/shared/constants";
interface GameModalProps { interface GameModalProps {
bets: Bet[]; bets: Bet[];
} }
@ -41,6 +42,7 @@ export default function YourGames({bets}:GameModalProps) {
const handleCloseBet = async () => { const handleCloseBet = async () => {
if (!selectedBet) return; if (!selectedBet) return;
setIsProcessing(true); setIsProcessing(true);
toast.loading("Closing bet");
let wallet = wallets[0]; let wallet = wallets[0];
wallets.forEach((_wallet) => { wallets.forEach((_wallet) => {
if (wallet.type === "solana") { if (wallet.type === "solana") {
@ -49,15 +51,19 @@ export default function YourGames({bets}:GameModalProps) {
}); });
try { try {
const tx = await closeBet(wallet, selectedBet.id); const tx = await closeBet(wallet, selectedBet.id);
const url = EXPLORER_TX_TEMPLATE.replace("{address}", tx);
toast.success(`Closed the bet successfully!`, { const url = EXPLORER_TX_TEMPLATE.replace("{address}", tx);
action: { connection.confirmTransaction(tx, CONFIRMATION_THRESHOLD).finally(()=>{
label: "View TX", toast.dismiss();
onClick: () => window.open(url, "_blank"), toast.success(`Closed the bet successfully!`, {
}, action: {
}); label: "View TX",
updateBets(); onClick: () => window.open(url, "_blank"),
},
});
updateBets();
})
} catch (error) { } catch (error) {
console.error("Error closing bet:", error); console.error("Error closing bet:", error);
toast.message("Failed to close the bet. Please try again."); toast.message("Failed to close the bet. Please try again.");

View File

@ -1,3 +1,6 @@
import { Connection } from "@solana/web3.js";
export const CLUSTER_URL = "https://api.devnet.solana.com"; export const CLUSTER_URL = "https://api.devnet.solana.com";
export const EXPLORER_ADDRESS_TEMPLATE = "https://explorer.solana.com/address/{address}?cluster=devnet"; export const EXPLORER_ADDRESS_TEMPLATE = "https://explorer.solana.com/address/{address}?cluster=devnet";
export const EXPLORER_TX_TEMPLATE = "https://explorer.solana.com/tx/{address}?cluster=devnet"; export const EXPLORER_TX_TEMPLATE = "https://explorer.solana.com/tx/{address}?cluster=devnet";
export const connection = new Connection(CLUSTER_URL);

View File

@ -3,4 +3,4 @@ import { Commitment, PublicKey } from "@solana/web3.js";
export const FEE_COLLECTOR_PUBKEY = new PublicKey("cocD4r4yNpHxPq7CzUebxEMyLki3X4d2Y3HcTX5ptUc"); export const FEE_COLLECTOR_PUBKEY = new PublicKey("cocD4r4yNpHxPq7CzUebxEMyLki3X4d2Y3HcTX5ptUc");
export const WAGER_PRIZE_MULT = 0.95; export const WAGER_PRIZE_MULT = 0.95;
export const CONFIRMATION_THRESHOLD:Commitment = 'processed'; export const CONFIRMATION_THRESHOLD:Commitment = 'finalized';

View File

@ -1,4 +1,4 @@
import { CLUSTER_URL } from "@/data/shared"; import { CLUSTER_URL, connection } from "@/data/shared";
import { Bets } from "@/idl/bets"; import { Bets } from "@/idl/bets";
import { AnchorProvider, BN, Program } from "@coral-xyz/anchor"; import { AnchorProvider, BN, Program } from "@coral-xyz/anchor";
import { ConnectedSolanaWallet } from "@privy-io/react-auth"; import { ConnectedSolanaWallet } from "@privy-io/react-auth";
@ -6,14 +6,12 @@ import { Connection, LAMPORTS_PER_SOL, PublicKey } from "@solana/web3.js";
import idl from "../idl/bets_idl.json"; import idl from "../idl/bets_idl.json";
import { Bet } from "@/types/Bet"; import { Bet } from "@/types/Bet";
import { toast } from "sonner"; import { toast } from "sonner";
import { Game } from "@/types/Game";
import { CONFIRMATION_THRESHOLD, FEE_COLLECTOR_PUBKEY } from "./constants"; import { CONFIRMATION_THRESHOLD, FEE_COLLECTOR_PUBKEY } from "./constants";
export const fetchOpenBets = async (wallets: ConnectedSolanaWallet): Promise<Bet[]> => { export const fetchOpenBets = async (wallets: ConnectedSolanaWallet): Promise<Bet[]> => {
try { try {
if (!wallets) return []; if (!wallets) return [];
const connection = new Connection(CLUSTER_URL);
const wallet = { const wallet = {
publicKey: new PublicKey(wallets.address), publicKey: new PublicKey(wallets.address),
signTransaction: wallets.signTransaction, signTransaction: wallets.signTransaction,
@ -61,7 +59,6 @@ export async function getVaultByAddress(wallets: ConnectedSolanaWallet, address:
try { try {
if (!wallets) return undefined; if (!wallets) return undefined;
const connection = new Connection(CLUSTER_URL);
const wallet = { const wallet = {
publicKey: new PublicKey(wallets.address), publicKey: new PublicKey(wallets.address),
signTransaction: wallets.signTransaction, signTransaction: wallets.signTransaction,
@ -93,7 +90,6 @@ export async function getVaultByAddress(wallets: ConnectedSolanaWallet, address:
export async function closeBet(wallets: ConnectedSolanaWallet, betId: string): Promise<string> { export async function closeBet(wallets: ConnectedSolanaWallet, betId: string): Promise<string> {
try { try {
const connection = new Connection(CLUSTER_URL);
const wallet = { const wallet = {
publicKey: new PublicKey(wallets.address), publicKey: new PublicKey(wallets.address),
signTransaction: wallets.signTransaction, signTransaction: wallets.signTransaction,
@ -147,6 +143,7 @@ export async function closeBet(wallets: ConnectedSolanaWallet, betId: string): P
// Send transaction// Replace with correct RPC endpoint // Send transaction// Replace with correct RPC endpoint
const txId = await connection.sendRawTransaction(signedTx.serialize()); const txId = await connection.sendRawTransaction(signedTx.serialize());
console.log(`Transaction: ${tx}`); console.log(`Transaction: ${tx}`);
return txId; return txId;
} catch (error) { } catch (error) {
@ -156,7 +153,7 @@ export async function closeBet(wallets: ConnectedSolanaWallet, betId: string): P
} }
export async function createBet(wallets: ConnectedSolanaWallet, uid: string, selectedPrice: number, selectedGame: string, get_account_address: boolean): Promise<string> { export async function createBet(wallets: ConnectedSolanaWallet, uid: string, selectedPrice: number, selectedGame: string, get_account_address: boolean): Promise<string> {
const connection = new Connection(CLUSTER_URL);
const wallet = { const wallet = {
publicKey: new PublicKey(wallets.address), publicKey: new PublicKey(wallets.address),
signTransaction: wallets.signTransaction, signTransaction: wallets.signTransaction,
@ -169,7 +166,6 @@ export async function createBet(wallets: ConnectedSolanaWallet, uid: string, sel
const program = new Program<Bets>(idl, provider); const program = new Program<Bets>(idl, provider);
try { try {
toast.loading("Creating bet...");
const nonce = getRandomInt(100000000); const nonce = getRandomInt(100000000);
const [bet_list_pda] = await PublicKey.findProgramAddress( const [bet_list_pda] = await PublicKey.findProgramAddress(
[Buffer.from("bets_list")], [Buffer.from("bets_list")],
@ -195,8 +191,6 @@ export async function createBet(wallets: ConnectedSolanaWallet, uid: string, sel
// Send transaction // Send transaction
const txId = await connection.sendRawTransaction(signedTx.serialize()); const txId = await connection.sendRawTransaction(signedTx.serialize());
console.log(`Transaction sent: ${txId}`); console.log(`Transaction sent: ${txId}`);
toast.success("Bet created successfully");
console.log(`Transaction confirmed: ${txId}`); console.log(`Transaction confirmed: ${txId}`);
if (get_account_address) { if (get_account_address) {
const gameIdBytes = Buffer.from(selectedGame); // selectedGame is a string (game_id) const gameIdBytes = Buffer.from(selectedGame); // selectedGame is a string (game_id)
@ -231,7 +225,6 @@ export async function createBet(wallets: ConnectedSolanaWallet, uid: string, sel
} }
export async function joinBet(wallets: ConnectedSolanaWallet, uid: string, gameId: string, address: string): Promise<string> { export async function joinBet(wallets: ConnectedSolanaWallet, uid: string, gameId: string, address: string): Promise<string> {
const connection = new Connection(CLUSTER_URL);
const wallet = { const wallet = {
publicKey: new PublicKey(wallets.address), publicKey: new PublicKey(wallets.address),
signTransaction: wallets.signTransaction, signTransaction: wallets.signTransaction,

View File

@ -1,10 +1,10 @@
interface GameHistory { // interface GameHistory {
address: string; // address: string;
master_score: string; // master_score: string;
client_score: string; // client_score: string;
winner: string; // winner: string;
wager: string; // wager: string;
master_id: string; // master_id: string;
client_id: string; // client_id: string;
game:string; // game:string;
} // }

View File

@ -2,7 +2,7 @@ export interface Submission{
username: string; username: string;
master_score: number; master_score: number;
client_score: number; client_score: number;
leaderboard: any; leaderboard: string;
master_id:string; master_id:string;
client_id:string; client_id:string;
winner:string; winner:string;