import * as anchor from "@coral-xyz/anchor"; import { Program } from "@coral-xyz/anchor"; import { Bets } from "../target/types/bets"; import { PublicKey, SystemProgram, Keypair, LAMPORTS_PER_SOL } from "@solana/web3.js"; import { TOKEN_PROGRAM_ID, createMint, createAccount, mintTo, getAccount } from "@solana/spl-token"; import { assert } from "chai"; describe("bets", () => { // Configure the client to use the local cluster const provider = anchor.AnchorProvider.env(); anchor.setProvider(provider); const program = anchor.workspace.Bets as Program; // Test accounts const owner = Keypair.generate(); const joiner = Keypair.generate(); const feeWallet = Keypair.generate(); const ownerReferrer = Keypair.generate(); const joinerReferrer = Keypair.generate(); // SPL Token accounts let tokenMint: PublicKey; let ownerTokenAccount: PublicKey; let joinerTokenAccount: PublicKey; let betVaultTokenAccount: PublicKey; let feeWalletTokenAccount: PublicKey; let ownerReferrerTokenAccount: PublicKey; let joinerReferrerTokenAccount: PublicKey; // Program state accounts let betsList: PublicKey; let betVault: PublicKey; const gameId = "test-game-1"; const ownerId = "owner123"; const joinerId = "joiner456"; const wager = new anchor.BN(LAMPORTS_PER_SOL); // 1 SOL or 1 token const tokenDecimals = 9; before(async () => { // Airdrop SOL to test accounts await provider.connection.requestAirdrop(owner.publicKey, 10 * LAMPORTS_PER_SOL); await provider.connection.requestAirdrop(joiner.publicKey, 10 * LAMPORTS_PER_SOL); await provider.connection.requestAirdrop(feeWallet.publicKey, 10 * LAMPORTS_PER_SOL); await provider.connection.requestAirdrop(ownerReferrer.publicKey, 10 * LAMPORTS_PER_SOL); await provider.connection.requestAirdrop(joinerReferrer.publicKey, 10 * LAMPORTS_PER_SOL); // Create SPL token mint tokenMint = await createMint( provider.connection, owner, owner.publicKey, null, tokenDecimals ); // Create token accounts ownerTokenAccount = await createAccount( provider.connection, owner, tokenMint, owner.publicKey ); joinerTokenAccount = await createAccount( provider.connection, joiner, tokenMint, joiner.publicKey ); betVaultTokenAccount = await createAccount( provider.connection, owner, tokenMint, owner.publicKey ); feeWalletTokenAccount = await createAccount( provider.connection, feeWallet, tokenMint, feeWallet.publicKey ); ownerReferrerTokenAccount = await createAccount( provider.connection, ownerReferrer, tokenMint, ownerReferrer.publicKey ); joinerReferrerTokenAccount = await createAccount( provider.connection, joinerReferrer, tokenMint, joinerReferrer.publicKey ); // Mint tokens to owner and joiner await mintTo( provider.connection, owner, tokenMint, ownerTokenAccount, owner, 100 * LAMPORTS_PER_SOL ); await mintTo( provider.connection, owner, tokenMint, joinerTokenAccount, owner, 100 * LAMPORTS_PER_SOL ); }); it("Initializes the bets list", async () => { [betsList] = PublicKey.findProgramAddressSync( [Buffer.from("bets_list")], program.programId ); await program.methods .initialize() .accounts({ betsList, payer: provider.wallet.publicKey, systemProgram: SystemProgram.programId, }) .rpc(); const betsListAccount = await program.account.betsList.fetch(betsList); assert.ok(betsListAccount.bets.length === 0); }); describe("Native SOL bets", () => { it("Creates a bet with native SOL", async () => { const nonce = new anchor.BN(Date.now()); [betVault] = PublicKey.findProgramAddressSync( [ Buffer.from("bet_vault"), owner.publicKey.toBuffer(), Buffer.from(gameId), nonce.toArrayLike(Buffer, "le", 8), ], program.programId ); await program.methods .createBet( wager, ownerId, gameId, nonce, true, // isNativeSol null, // tokenMint 0 // tokenDecimals ) .accounts({ payer: owner.publicKey, betsList, betVault, systemProgram: SystemProgram.programId, }) .signers([owner]) .rpc(); const betVaultAccount = await program.account.betVault.fetch(betVault); assert.ok(betVaultAccount.owner.equals(owner.publicKey)); assert.ok(betVaultAccount.ownerId === ownerId); assert.ok(betVaultAccount.gameId === gameId); assert.ok(betVaultAccount.wager.eq(wager)); assert.ok(betVaultAccount.isNativeSol === true); }); it("Joins a bet with native SOL", async () => { await program.methods .joinBet(joinerId, gameId) .accounts({ betVault, payer: joiner.publicKey, systemProgram: SystemProgram.programId, }) .signers([joiner]) .rpc(); const betVaultAccount = await program.account.betVault.fetch(betVault); assert.ok(betVaultAccount.joiner.equals(joiner.publicKey)); assert.ok(betVaultAccount.joinerId === joinerId); }); it("Closes a bet with native SOL", async () => { await program.methods .closeBet(owner.publicKey, ownerId) .accounts({ betsList, betVault, feeWallet: feeWallet.publicKey, winner: owner.publicKey, payer: owner.publicKey, ownerReferrer: ownerReferrer.publicKey, joinerReferrer: joinerReferrer.publicKey, systemProgram: SystemProgram.programId, }) .signers([owner]) .rpc(); // Verify the bet was removed from the list const betsListAccount = await program.account.betsList.fetch(betsList); assert.ok(!betsListAccount.bets.some(bet => bet.equals(betVault))); }); }); describe("SPL Token bets", () => { it("Creates a bet with SPL tokens", async () => { const nonce = new anchor.BN(Date.now()); [betVault] = PublicKey.findProgramAddressSync( [ Buffer.from("bet_vault"), owner.publicKey.toBuffer(), Buffer.from(gameId), nonce.toArrayLike(Buffer, "le", 8), ], program.programId ); await program.methods .createBet( wager, ownerId, gameId, nonce, false, // isNativeSol tokenMint, tokenDecimals ) .accounts({ payer: owner.publicKey, betsList, betVault, systemProgram: SystemProgram.programId, payerTokenAccount: ownerTokenAccount, betVaultTokenAccount, tokenProgram: TOKEN_PROGRAM_ID, }) .signers([owner]) .rpc(); const betVaultAccount = await program.account.betVault.fetch(betVault); assert.ok(betVaultAccount.owner.equals(owner.publicKey)); assert.ok(betVaultAccount.ownerId === ownerId); assert.ok(betVaultAccount.gameId === gameId); assert.ok(betVaultAccount.wager.eq(wager)); assert.ok(betVaultAccount.isNativeSol === false); assert.ok(betVaultAccount.tokenMint.equals(tokenMint)); assert.ok(betVaultAccount.tokenDecimals === tokenDecimals); }); it("Joins a bet with SPL tokens", async () => { await program.methods .joinBet(joinerId, gameId) .accounts({ betVault, payer: joiner.publicKey, systemProgram: SystemProgram.programId, payerTokenAccount: joinerTokenAccount, betVaultTokenAccount, tokenProgram: TOKEN_PROGRAM_ID, }) .signers([joiner]) .rpc(); const betVaultAccount = await program.account.betVault.fetch(betVault); assert.ok(betVaultAccount.joiner.equals(joiner.publicKey)); assert.ok(betVaultAccount.joinerId === joinerId); }); it("Closes a bet with SPL tokens", async () => { await program.methods .closeBet(owner.publicKey, ownerId) .accounts({ betsList, betVault, feeWallet: feeWallet.publicKey, winner: owner.publicKey, payer: owner.publicKey, ownerReferrer: ownerReferrer.publicKey, joinerReferrer: joinerReferrer.publicKey, systemProgram: SystemProgram.programId, betVaultTokenAccount, feeWalletTokenAccount, ownerReferrerTokenAccount, joinerReferrerTokenAccount, tokenProgram: TOKEN_PROGRAM_ID, }) .signers([owner]) .rpc(); // Verify the bet was removed from the list const betsListAccount = await program.account.betsList.fetch(betsList); assert.ok(!betsListAccount.bets.some(bet => bet.equals(betVault))); }); }); describe("Refund functionality", () => { it("Refunds a native SOL bet", async () => { // Create a new bet const nonce = new anchor.BN(Date.now()); [betVault] = PublicKey.findProgramAddressSync( [ Buffer.from("bet_vault"), owner.publicKey.toBuffer(), Buffer.from(gameId), nonce.toArrayLike(Buffer, "le", 8), ], program.programId ); await program.methods .createBet( wager, ownerId, gameId, nonce, true, // isNativeSol null, // tokenMint 0 // tokenDecimals ) .accounts({ payer: owner.publicKey, betsList, betVault, systemProgram: SystemProgram.programId, }) .signers([owner]) .rpc(); // Refund the bet await program.methods .refundBet(owner.publicKey) .accounts({ betsList, betVault, owner: owner.publicKey, payer: owner.publicKey, systemProgram: SystemProgram.programId, }) .signers([owner]) .rpc(); // Verify the bet was removed from the list const betsListAccount = await program.account.betsList.fetch(betsList); assert.ok(!betsListAccount.bets.some(bet => bet.equals(betVault))); }); it("Refunds an SPL token bet", async () => { // Create a new bet const nonce = new anchor.BN(Date.now()); [betVault] = PublicKey.findProgramAddressSync( [ Buffer.from("bet_vault"), owner.publicKey.toBuffer(), Buffer.from(gameId), nonce.toArrayLike(Buffer, "le", 8), ], program.programId ); await program.methods .createBet( wager, ownerId, gameId, nonce, false, // isNativeSol tokenMint, tokenDecimals ) .accounts({ payer: owner.publicKey, betsList, betVault, systemProgram: SystemProgram.programId, payerTokenAccount: ownerTokenAccount, betVaultTokenAccount, tokenProgram: TOKEN_PROGRAM_ID, }) .signers([owner]) .rpc(); // Refund the bet await program.methods .refundBet(owner.publicKey) .accounts({ betsList, betVault, owner: owner.publicKey, payer: owner.publicKey, systemProgram: SystemProgram.programId, betVaultTokenAccount, joinerTokenAccount, tokenProgram: TOKEN_PROGRAM_ID, }) .signers([owner]) .rpc(); // Verify the bet was removed from the list const betsListAccount = await program.account.betsList.fetch(betsList); assert.ok(!betsListAccount.bets.some(bet => bet.equals(betVault))); }); }); });