duelfi/src/shared/solana_helpers.ts
2025-04-08 12:40:27 +05:30

289 lines
9.1 KiB
TypeScript

import { CLUSTER_URL } from "@/data/shared";
import { Bets } from "@/idl/bets";
import { AnchorProvider, BN, Program } from "@coral-xyz/anchor";
import { ConnectedSolanaWallet } from "@privy-io/react-auth";
import { Connection, LAMPORTS_PER_SOL, PublicKey } from "@solana/web3.js";
import idl from "../idl/bets_idl.json";
import { Bet } from "@/types/Bet";
import { toast } from "sonner";
import { Game } from "@/types/Game";
import { CONFIRMATION_THRESHOLD, FEE_COLLECTOR_PUBKEY } from "./constants";
export const fetchOpenBets = async (wallets: ConnectedSolanaWallet): Promise<Bet[]> => {
try {
if (!wallets) return [];
const connection = new Connection(CLUSTER_URL);
const wallet = {
publicKey: new PublicKey(wallets.address),
signTransaction: wallets.signTransaction,
signAllTransactions: wallets.signAllTransactions,
};
const provider = new AnchorProvider(connection, wallet, {
preflightCommitment: CONFIRMATION_THRESHOLD,
});
const program = new Program<Bets>(idl, provider);
const [bet_list_pda] = await PublicKey.findProgramAddress(
[Buffer.from("bets_list")],
program.programId
);
// Fetch all open bet accounts
const bet_list = await program.account.betsList.fetch(bet_list_pda);
// Extract required bet data
const formattedBets = await Promise.all(
bet_list.bets.map(async (bet) => {
const betAcc = await program.account.betVault.fetch(bet);
// console.log(betAcc.gameId);
return {
address: bet.toString(),
id: betAcc.gameId,
owner: betAcc.owner.toBase58(),
owner_id: betAcc.ownerId,
joiner: betAcc.joiner ? betAcc.joiner.toBase58() : "Open",
joiner_id: betAcc.joinerId,
wager: betAcc.wager.toNumber() / LAMPORTS_PER_SOL
};
})
);
// console.log(`Got ${formattedBets.length} bets`);
return formattedBets;
} catch (error) {
console.error("Error fetching open bets:", error);
}
return [];
};
export async function getVaultByAddress(wallets: ConnectedSolanaWallet, address: string): Promise<Bet | undefined> {
try {
if (!wallets) return undefined;
const connection = new Connection(CLUSTER_URL);
const wallet = {
publicKey: new PublicKey(wallets.address),
signTransaction: wallets.signTransaction,
signAllTransactions: wallets.signAllTransactions,
};
const provider = new AnchorProvider(connection, wallet, {
preflightCommitment: CONFIRMATION_THRESHOLD,
});
const program = new Program<Bets>(idl, provider);
// Extract required bet data
const betAcc = await program.account.betVault.fetch(address);
// console.log(betAcc.gameId);
return {
address: address.toString(),
id: betAcc.gameId,
owner: betAcc.owner.toBase58(),
owner_id: betAcc.ownerId,
joiner: betAcc.joiner ? betAcc.joiner.toBase58() : "Open",
joiner_id: betAcc.joinerId,
wager: betAcc.wager.toNumber() / LAMPORTS_PER_SOL
};
} catch (error) {
console.error("Error fetching open bets:", error);
}
return undefined;
}
export async function closeBet(wallets: ConnectedSolanaWallet, betId: string): Promise<string> {
try {
const connection = new Connection(CLUSTER_URL);
const wallet = {
publicKey: new PublicKey(wallets.address),
signTransaction: wallets.signTransaction,
signAllTransactions: wallets.signAllTransactions,
};
const provider = new AnchorProvider(connection, wallet, {
preflightCommitment: CONFIRMATION_THRESHOLD,
});
const program = new Program<Bets>(idl, provider);
const [bet_list_pda] = await PublicKey.findProgramAddress(
[Buffer.from("bets_list")],
program.programId
);
// Fetch the bet list
const betList = await program.account.betsList.fetch(bet_list_pda);
let chosenBet: PublicKey | null = null;
for (const bet of betList.bets) {
const betAcc = await program.account.betVault.fetch(bet);
if (betAcc.owner.toBase58() === wallets.address && betAcc.gameId.toString() === betId) {
chosenBet = bet;
break;
}
}
if (!chosenBet) {
console.error("Bet not found or not owned by the user");
return "";
}
const winner = new PublicKey(wallets.address);
// Execute the closeBet transaction
const tx = await program.methods
.closeBet(winner)
.accounts({
betVault: chosenBet,
betsList: bet_list_pda,
winner: winner,
feeWallet: FEE_COLLECTOR_PUBKEY
})
.transaction();
tx.feePayer = new PublicKey(wallets.address);
tx.recentBlockhash = (await connection.getLatestBlockhash()).blockhash;
// Sign transaction with Privy
const signedTx = await wallet.signTransaction(tx);
// Send transaction// Replace with correct RPC endpoint
const txId = await connection.sendRawTransaction(signedTx.serialize());
console.log(`Transaction: ${tx}`);
return txId;
} catch (error) {
console.error("Error closing bet:", error);
}
return "";
}
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 = {
publicKey: new PublicKey(wallets.address),
signTransaction: wallets.signTransaction,
signAllTransactions: wallets.signAllTransactions,
};
const provider = new AnchorProvider(connection, wallet, {
preflightCommitment: CONFIRMATION_THRESHOLD,
});
const program = new Program<Bets>(idl, provider);
try {
toast.loading("Creating bet...");
const nonce = getRandomInt(100000000);
const [bet_list_pda] = await PublicKey.findProgramAddress(
[Buffer.from("bets_list")],
program.programId
);
console.log(`bets list : ${bet_list_pda}`);
// Create transaction
const tx = await program.methods
.createBet(new BN(selectedPrice * 1000000000), uid, selectedGame, new BN(nonce))
.accounts({
betsList: bet_list_pda,
})
.transaction(); // Get transaction object instead of RPC call
tx.feePayer = new PublicKey(wallets.address);
tx.recentBlockhash = (await connection.getLatestBlockhash()).blockhash;
// Sign transaction with Privy
const signedTx = await wallet.signTransaction(tx);
// Send transaction
const txId = await connection.sendRawTransaction(signedTx.serialize());
console.log(`Transaction sent: ${txId}`);
toast.success("Bet created successfully");
console.log(`Transaction confirmed: ${txId}`);
if (get_account_address) {
const gameIdBytes = Buffer.from(selectedGame); // selectedGame is a string (game_id)
const nonceBytes = new BN(nonce).toArrayLike(Buffer, "le", 8); // _nonce.to_le_bytes()
const [betVaultPda] = await PublicKey.findProgramAddress(
[
Buffer.from("bet_vault"),
new PublicKey(wallets.address).toBuffer(), // payer
gameIdBytes,
nonceBytes,
],
program.programId
);
return betVaultPda.toString();
} else {
return txId;
}
} catch (error: unknown) {
toast.dismiss();
const errorMessage = String(error); // Converts error to string safely
if (errorMessage.includes("already in use")) {
toast.error("You can't make 2 bets for the same game.");
} else {
toast.error("Failed to create bet.");
console.error(error);
}
}
return "";
}
export async function joinBet(wallets: ConnectedSolanaWallet, uid: string, gameId: string, address: string): Promise<string> {
const connection = new Connection(CLUSTER_URL);
const wallet = {
publicKey: new PublicKey(wallets.address),
signTransaction: wallets.signTransaction,
signAllTransactions: wallets.signAllTransactions,
};
const provider = new AnchorProvider(connection, wallet, {
preflightCommitment: CONFIRMATION_THRESHOLD,
});
const program = new Program<Bets>(idl, provider);
try {
const [bet_list_pda] = await PublicKey.findProgramAddress(
[Buffer.from("bets_list")],
program.programId
);
const connection = new Connection(CLUSTER_URL, CONFIRMATION_THRESHOLD);
const betVaultPubkey = new PublicKey(address);
console.log(`bets list : ${bet_list_pda}`);
// Create transaction
const tx = await program.methods
.joinBet(uid, gameId)
.accounts({
betVault: betVaultPubkey,
})
.transaction(); // Get transaction object instead of RPC call
tx.feePayer = new PublicKey(wallets.address);
tx.recentBlockhash = (await connection.getLatestBlockhash()).blockhash;
// Sign transaction with Privy
const signedTx = await wallet.signTransaction(tx);
// Send transaction// Replace with correct RPC endpoint
const txId = await connection.sendRawTransaction(signedTx.serialize());
console.log(`Transaction ID: ${txId}`);
return txId;
} catch (error: unknown) {
// const errorMessage = String(error); // Converts error to string safely
toast.error("Failed to create bet.");
console.error(error);
}
return "";
}
function getRandomInt(max: number): number {
return Math.floor(Math.random() * max);
}