Compare commits
28 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0105ba063a | |||
| e409d45e61 | |||
| 3a9fa10f43 | |||
| 364b01c2d3 | |||
| 20184fdc48 | |||
| 230f98975a | |||
| 9c17c33e35 | |||
| 1939acaf45 | |||
| 60b0ed6641 | |||
| 5681511a0d | |||
| db24f57690 | |||
| 87f612c901 | |||
| a39517f801 | |||
| d3b618fe5a | |||
| 00da84183e | |||
| 3b6421a3f5 | |||
| 87e72eb655 | |||
| a8cdcb54f3 | |||
| 682a927049 | |||
| 23a98aea9c | |||
| efedc6ab71 | |||
| 8dedb47f8e | |||
| c84d8a4a6d | |||
| cc73ac1feb | |||
| d8db404453 | |||
| b32b041a14 | |||
| fee9ac44f4 | |||
| cf8ef365b2 |
|
|
@ -4,15 +4,19 @@
|
|||
resolution = true
|
||||
skip-lint = false
|
||||
|
||||
[programs.localnet]
|
||||
[programs.devnet]
|
||||
bets = "Haj94DF925qNRgcoRwQfNsVLKgSmFhG4bjgtvusMkkpD"
|
||||
|
||||
[registry]
|
||||
url = "https://api.apr.dev"
|
||||
|
||||
[provider]
|
||||
cluster = "Localnet"
|
||||
cluster = "Devnet"
|
||||
wallet = "~/.config/solana/id.json"
|
||||
|
||||
[scripts]
|
||||
test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts"
|
||||
|
||||
[test]
|
||||
startup_wait = 10000
|
||||
validator = { url = "http://127.0.0.1:8899" }
|
||||
|
|
|
|||
821
Cargo.lock
generated
821
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
1
bfk3.json
Normal file
1
bfk3.json
Normal file
|
|
@ -0,0 +1 @@
|
|||
[35,174,214,54,242,1,199,216,163,198,236,156,136,174,255,23,227,139,41,35,8,67,70,64,116,179,59,110,68,243,2,102,222,40,2,247,155,101,212,33,251,2,171,60,245,235,194,209,166,57,123,68,77,134,242,10,253,213,189,51,50,119,177,7]
|
||||
1
bk2.json
Normal file
1
bk2.json
Normal file
|
|
@ -0,0 +1 @@
|
|||
[148,41,155,174,198,60,158,184,194,95,32,154,56,15,105,221,184,104,78,95,198,191,73,206,7,204,237,221,77,118,10,185,141,91,28,223,24,106,252,190,71,7,81,159,21,139,95,162,137,133,175,102,147,41,2,59,136,124,237,109,240,37,214,136]
|
||||
1
bk5.json
Normal file
1
bk5.json
Normal file
|
|
@ -0,0 +1 @@
|
|||
[175,127,97,183,211,241,79,174,178,61,65,104,189,207,155,197,146,250,192,93,8,137,68,245,101,185,15,191,159,76,243,215,221,10,222,76,239,168,2,136,183,93,220,122,236,171,209,22,233,114,77,52,207,194,223,80,251,189,50,24,185,22,89,66]
|
||||
2606
package-lock.json
generated
Normal file
2606
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
|
|
@ -8,13 +8,16 @@
|
|||
"@coral-xyz/anchor": "^0.30.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@solana/spl-token": "^0.4.13",
|
||||
"@types/bn.js": "^5.1.0",
|
||||
"@types/chai": "^5.2.1",
|
||||
"@types/mocha": "^10.0.10",
|
||||
"@types/node": "^22.14.0",
|
||||
"anchor-bankrun": "^0.5.0",
|
||||
"chai": "^4.3.4",
|
||||
"mocha": "^9.0.3",
|
||||
"prettier": "^2.6.2",
|
||||
"solana-bankrun": "^0.4.0",
|
||||
"ts-mocha": "^10.0.0",
|
||||
"typescript": "^4.3.5"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,7 +14,12 @@ cpi = ["no-entrypoint"]
|
|||
no-entrypoint = []
|
||||
no-idl = []
|
||||
no-log-ix-name = []
|
||||
idl-build = ["anchor-lang/idl-build"]
|
||||
idl-build = [
|
||||
"anchor-lang/idl-build",
|
||||
"anchor-spl/idl-build"
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
anchor-lang = "0.30.1"
|
||||
anchor-lang = {version="0.30.1", features=["init-if-needed"]}
|
||||
anchor-spl="0.30.1"
|
||||
solana-program= "=2.0.3"
|
||||
|
|
|
|||
|
|
@ -5,3 +5,18 @@ pub const SEED: &str = "anchor";
|
|||
|
||||
#[constant]
|
||||
pub const FEE_COLLECTOR: &str= "9esrj2X33pr5og6fdkDMjaW6fdnnb9hT1cWshamxTdL4";
|
||||
|
||||
#[constant]
|
||||
pub const TICKET_SALE_VAULT_ADDRESS: &str= "9esrj2X33pr5og6fdkDMjaW6fdnnb9hT1cWshamxTdL4";
|
||||
|
||||
#[constant]
|
||||
pub const RECEIPT_ACCOUNT_SEED: &[u8] = b"receipt_account";
|
||||
|
||||
#[constant]
|
||||
pub const TICKET_LEADERBOARD_LIST_SEED: &[u8] = b"ticket_leaderboards_list";
|
||||
|
||||
#[constant]
|
||||
pub const TICKET_LEADERBOARD_SEED: &[u8] = b"ticket_leaderboard";
|
||||
|
||||
#[constant]
|
||||
pub const LEADERBOARD_ENTRY_SEED: &[u8] = b"leaderboard_entry";
|
||||
|
|
@ -17,6 +17,26 @@ pub enum BettingError {
|
|||
InvalidWinner,
|
||||
#[msg("Please use the correct fee collector address")]
|
||||
InvalidFeeCollector,
|
||||
#[msg("")]
|
||||
JoinerAccountNotProvided
|
||||
#[msg("Joiner account not provided")]
|
||||
JoinerAccountNotProvided,
|
||||
#[msg("Insufficient funds")]
|
||||
InsufficientFunds,
|
||||
#[msg("Fee calculation error")]
|
||||
FeeCalculationError,
|
||||
#[msg("Invalid ticket sale vault address")]
|
||||
InvalidTicketSaleVault,
|
||||
}
|
||||
|
||||
#[error_code]
|
||||
pub enum LeaderboardError {
|
||||
#[msg("Invalid id")]
|
||||
InvalidId,
|
||||
#[msg("Not active")]
|
||||
NotActive,
|
||||
}
|
||||
|
||||
#[error_code]
|
||||
pub enum ArithmeticError {
|
||||
#[msg("Arithmetic error")]
|
||||
ArithmeticError,
|
||||
}
|
||||
81
programs/bets/src/instructions/buy_tickets.rs
Normal file
81
programs/bets/src/instructions/buy_tickets.rs
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
use anchor_lang::prelude::*;
|
||||
use anchor_spl::{associated_token::AssociatedToken, token::{transfer_checked, TransferChecked}, token_interface::{TokenAccount, TokenInterface}};
|
||||
use crate::*;
|
||||
|
||||
|
||||
pub fn buy(ctx: Context<BuyTickets>, amount: u64) -> Result<()> {
|
||||
|
||||
let transfer_amount = 100000000; // 0.1 SOL in lamports
|
||||
|
||||
let ix = anchor_lang::solana_program::system_instruction::transfer(
|
||||
&ctx.accounts.payer.key(),
|
||||
&ctx.accounts.ticket_leaderboard_list.key(),
|
||||
transfer_amount
|
||||
);
|
||||
|
||||
anchor_lang::solana_program::program::invoke(
|
||||
&ix,
|
||||
&[
|
||||
ctx.accounts.payer.to_account_info(),
|
||||
ctx.accounts.ticket_leaderboard_list.to_account_info(),
|
||||
],
|
||||
)?;
|
||||
|
||||
// Transfer tokens from leaderboard vault to user vault
|
||||
let cpi_program = ctx.accounts.token_program.to_account_info();
|
||||
let cpi_accounts = TransferChecked {
|
||||
from: ctx.accounts.ticket_leaderboard_list_vault.to_account_info(),
|
||||
to: ctx.accounts.user_token_vault.to_account_info(),
|
||||
authority: ctx.accounts.ticket_leaderboard_list.to_account_info(),
|
||||
mint: ctx.accounts.token_mint.to_account_info(),
|
||||
};
|
||||
|
||||
let seeds = &[
|
||||
TICKET_LEADERBOARD_LIST_SEED.as_ref(),
|
||||
&[ctx.bumps.ticket_leaderboard_list][..]
|
||||
];
|
||||
|
||||
let signer_seeds = &[&seeds[..]];
|
||||
|
||||
let cpi_ctx = CpiContext::new_with_signer(cpi_program, cpi_accounts, signer_seeds);
|
||||
transfer_checked(cpi_ctx, amount, 9)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct BuyTickets<'info>{
|
||||
#[account(mut)]
|
||||
pub payer: Signer<'info>,
|
||||
|
||||
#[account(
|
||||
mut,
|
||||
seeds = [TICKET_LEADERBOARD_LIST_SEED],
|
||||
bump
|
||||
)]
|
||||
pub ticket_leaderboard_list: Account<'info, TicketLeaderboardList>,
|
||||
|
||||
#[account(
|
||||
init_if_needed,
|
||||
payer=payer,
|
||||
associated_token::mint = token_mint,
|
||||
associated_token::authority = ticket_leaderboard_list,
|
||||
associated_token::token_program = token_program
|
||||
)]
|
||||
pub ticket_leaderboard_list_vault: InterfaceAccount<'info, TokenAccount>,
|
||||
|
||||
#[account(
|
||||
init_if_needed,
|
||||
payer = payer,
|
||||
associated_token::mint = token_mint,
|
||||
associated_token::authority = payer,
|
||||
associated_token::token_program = token_program
|
||||
)]
|
||||
pub user_token_vault: InterfaceAccount<'info, TokenAccount>,
|
||||
|
||||
pub token_mint: InterfaceAccount<'info, anchor_spl::token_interface::Mint>,
|
||||
|
||||
pub system_program: Program<'info, System>,
|
||||
pub token_program: Interface<'info, TokenInterface>,
|
||||
pub associated_token_program: Program<'info, AssociatedToken>,
|
||||
}
|
||||
22
programs/bets/src/instructions/clear_bets_list.rs
Normal file
22
programs/bets/src/instructions/clear_bets_list.rs
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
use anchor_lang::prelude::*;
|
||||
use crate::*;
|
||||
|
||||
pub fn clear(ctx: Context<ClearBetsList>) -> Result<()> {
|
||||
let bets_list = &mut ctx.accounts.bets_list;
|
||||
bets_list.bets.clear();
|
||||
msg!("Cleared all bets from the list!");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct ClearBetsList<'info> {
|
||||
#[account(
|
||||
mut,
|
||||
seeds = [b"bets_list"],
|
||||
bump
|
||||
)]
|
||||
pub bets_list: Account<'info, BetsList>,
|
||||
|
||||
#[account(mut)]
|
||||
pub payer: Signer<'info>,
|
||||
}
|
||||
|
|
@ -1,5 +1,3 @@
|
|||
use std::str::FromStr;
|
||||
|
||||
use anchor_lang::prelude::*;
|
||||
use crate::{error::BettingError, *};
|
||||
|
||||
|
|
@ -10,40 +8,9 @@ pub fn close(ctx: Context<CloseBet>, winner:Pubkey, userid:String)->Result<()>{
|
|||
BettingError::InvalidWinner
|
||||
);
|
||||
|
||||
let fee_collector_pubkey = Pubkey::from_str(FEE_COLLECTOR).map_err(|_| BettingError::InvalidFeeCollector)?;
|
||||
require_keys_eq!(
|
||||
ctx.accounts.fee_wallet.key(),
|
||||
fee_collector_pubkey,
|
||||
BettingError::InvalidFeeCollector
|
||||
);
|
||||
|
||||
// Calculate the 5% fee
|
||||
let total_lamports = **bet_vault.to_account_info().lamports.borrow();
|
||||
let mut fee = total_lamports / 20; // 5%
|
||||
let referrer_fee = fee / 4; //50% for each referrer
|
||||
|
||||
// In the close function:
|
||||
if let Some(owner_ref) = &ctx.accounts.owner_referrer {
|
||||
fee -= referrer_fee;
|
||||
**bet_vault.to_account_info().try_borrow_mut_lamports()? -= referrer_fee;
|
||||
**owner_ref.try_borrow_mut_lamports()? += referrer_fee;
|
||||
}
|
||||
|
||||
if let Some(joiner_ref) = &ctx.accounts.joiner_referrer {
|
||||
fee -= referrer_fee;
|
||||
**bet_vault.to_account_info().try_borrow_mut_lamports()? -= referrer_fee;
|
||||
**joiner_ref.try_borrow_mut_lamports()? += referrer_fee;
|
||||
}
|
||||
|
||||
**bet_vault.to_account_info().try_borrow_mut_lamports()? -= fee;
|
||||
**ctx.accounts.fee_wallet.to_account_info().try_borrow_mut_lamports()? += fee;
|
||||
|
||||
let bets_list = &mut ctx.accounts.bets_list;
|
||||
// Remove the bet_vault public key from the list
|
||||
bets_list.bets.retain(|&bet| bet != bet_vault.key());
|
||||
|
||||
msg!("Bet {} closed by {}", bet_vault.key(), winner);
|
||||
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -57,21 +24,11 @@ pub struct CloseBet<'info>{
|
|||
#[account(mut, close=winner)]
|
||||
pub bet_vault: Account<'info, BetVault>,
|
||||
|
||||
/// CHECK: This is validated against the FEE_COLLECTOR constant.
|
||||
#[account(mut)]
|
||||
pub fee_wallet: AccountInfo<'info>,
|
||||
|
||||
#[account(mut)]
|
||||
pub winner: SystemAccount<'info>,
|
||||
|
||||
#[account(mut)]
|
||||
pub payer: Signer<'info>,
|
||||
|
||||
#[account(mut)]
|
||||
pub owner_referrer: Option<AccountInfo<'info>>,
|
||||
|
||||
#[account(mut)]
|
||||
pub joiner_referrer: Option<AccountInfo<'info>>,
|
||||
|
||||
pub system_program: Program<'info, System>
|
||||
}
|
||||
86
programs/bets/src/instructions/close_bet_token.rs
Normal file
86
programs/bets/src/instructions/close_bet_token.rs
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
use anchor_lang::prelude::*;
|
||||
use anchor_spl::{
|
||||
associated_token::AssociatedToken,
|
||||
token_interface::{TokenAccount, TokenInterface},
|
||||
};
|
||||
use crate::{error::BettingError, shared::transfer_tokens_pda, state::{BetVault, BetsList}};
|
||||
|
||||
pub fn close_token_bet(ctx: Context<CloseBetToken>, winner: Pubkey, userid: String, owner:Pubkey, game_id:String, nonce:u64) -> Result<()> {
|
||||
let bet_vault = &mut ctx.accounts.bet_vault;
|
||||
require!(
|
||||
bet_vault.owner == winner || bet_vault.joiner == winner || bet_vault.owner_id == userid || bet_vault.joiner_id == userid,
|
||||
BettingError::InvalidWinner
|
||||
);
|
||||
let seeds = &[
|
||||
b"bet_vault",
|
||||
bet_vault.owner.as_ref(),
|
||||
bet_vault.game_id.as_bytes(),
|
||||
&bet_vault.nonce.to_le_bytes(),
|
||||
];
|
||||
|
||||
// Get the current token balance of the bet vault
|
||||
let token_balance = ctx.accounts.token_vault.amount;
|
||||
|
||||
// Transfer all tokens from vault to winner
|
||||
transfer_tokens_pda(
|
||||
&ctx.accounts.token_vault,
|
||||
&ctx.accounts.winner_token_account,
|
||||
token_balance,
|
||||
&ctx.accounts.token_mint,
|
||||
&bet_vault,
|
||||
&ctx.accounts.token_program,
|
||||
seeds,
|
||||
ctx.bumps.bet_vault,
|
||||
)?;
|
||||
|
||||
// Remove bet from global list
|
||||
let bets_list = &mut ctx.accounts.bets_list;
|
||||
if let Some(pos) = bets_list.bets.iter().position(|x| *x == bet_vault.key()) {
|
||||
bets_list.bets.remove(pos);
|
||||
}
|
||||
|
||||
msg!("Bet closed and {} tokens transferred to winner {}", token_balance, winner);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
#[instruction( winner: Pubkey, userid: String,owner:Pubkey, game_id:String, nonce:u64)]
|
||||
pub struct CloseBetToken<'info> {
|
||||
#[account(mut)]
|
||||
pub bets_list: Account<'info, BetsList>,
|
||||
|
||||
#[account(
|
||||
mut,
|
||||
seeds = [b"bet_vault", owner.as_ref(), game_id.as_bytes(), &nonce.to_le_bytes()],
|
||||
bump
|
||||
)]
|
||||
pub bet_vault: Account<'info, BetVault>,
|
||||
|
||||
#[account(mut)]
|
||||
pub winner: SystemAccount<'info>,
|
||||
|
||||
#[account(mut)]
|
||||
pub payer: Signer<'info>,
|
||||
|
||||
pub token_mint: InterfaceAccount<'info, anchor_spl::token_interface::Mint>,
|
||||
|
||||
#[account(
|
||||
mut,
|
||||
associated_token::mint = token_mint,
|
||||
associated_token::authority = bet_vault,
|
||||
associated_token::token_program = token_program
|
||||
)]
|
||||
pub token_vault: InterfaceAccount<'info, TokenAccount>,
|
||||
|
||||
#[account(
|
||||
mut,
|
||||
associated_token::mint = token_mint,
|
||||
associated_token::authority = winner,
|
||||
associated_token::token_program = token_program
|
||||
)]
|
||||
pub winner_token_account: InterfaceAccount<'info, TokenAccount>,
|
||||
|
||||
pub system_program: Program<'info, System>,
|
||||
pub token_program: Interface<'info, TokenInterface>,
|
||||
pub associated_token_program: Program<'info, AssociatedToken>,
|
||||
}
|
||||
|
|
@ -12,7 +12,7 @@ pub fn create(ctx: Context<CreateBet>, wager: u64, user_id:String, game_id:Strin
|
|||
bet_vault.owner = payer.key();
|
||||
bet_vault.owner_id= user_id;
|
||||
bet_vault.wager = wager;
|
||||
|
||||
bet_vault.nonce = _nonce;
|
||||
// Transfer SOL from the payer to the bet vault
|
||||
let cpi_accounts = anchor_lang::system_program::Transfer {
|
||||
from: payer.to_account_info(),
|
||||
|
|
|
|||
78
programs/bets/src/instructions/create_bet_token.rs
Normal file
78
programs/bets/src/instructions/create_bet_token.rs
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
use anchor_lang::prelude::*;
|
||||
use anchor_spl::{
|
||||
associated_token::AssociatedToken,
|
||||
token_interface::{Mint, TokenAccount, TokenInterface},
|
||||
};
|
||||
|
||||
use crate::{shared::transfer_tokens, *};
|
||||
|
||||
pub fn create_token_bet(ctx: Context<CreateBetToken>, wager: u64, user_id:String, game_id:String, _nonce:u64) -> Result<()> {
|
||||
let bets_list = &mut ctx.accounts.bets_list;
|
||||
let bet_vault = &mut ctx.accounts.bet_vault;
|
||||
let payer = &ctx.accounts.payer;
|
||||
|
||||
// Store bet details
|
||||
bet_vault.game_id = game_id;
|
||||
bet_vault.owner = payer.key();
|
||||
bet_vault.owner_id= user_id;
|
||||
bet_vault.wager = wager;
|
||||
bet_vault.token_mint = ctx.accounts.token_mint.key();
|
||||
bet_vault.nonce = _nonce;
|
||||
|
||||
transfer_tokens(
|
||||
&ctx.accounts.payer_token_account,
|
||||
&ctx.accounts.token_vault,
|
||||
&wager, &ctx.accounts.token_mint,
|
||||
&ctx.accounts.payer,
|
||||
&ctx.accounts.token_program
|
||||
)?;
|
||||
|
||||
// Add this bet to the global list
|
||||
bets_list.bets.push(bet_vault.key());
|
||||
|
||||
msg!("New bet {} created with {} lamports!", bet_vault.key(), wager);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
#[instruction(wager: u64, user_id: String, game_id: String, _nonce: u64)]
|
||||
pub struct CreateBetToken<'info> {
|
||||
#[account(mut)]
|
||||
pub payer: Signer<'info>,
|
||||
|
||||
#[account(mint::token_program= token_program)]
|
||||
pub token_mint: InterfaceAccount<'info, Mint>,
|
||||
|
||||
#[account(mut)]
|
||||
pub bets_list: Account<'info, BetsList>,
|
||||
|
||||
#[account(
|
||||
init,
|
||||
payer = payer,
|
||||
space = 8 + BetVault::INIT_SPACE, // Owner (Pubkey) + Wager (u64)
|
||||
seeds = [b"bet_vault", payer.key().as_ref(), &game_id.as_bytes(), &_nonce.to_le_bytes()],
|
||||
bump
|
||||
)]
|
||||
pub bet_vault: Account<'info, BetVault>,
|
||||
|
||||
#[account(
|
||||
mut,
|
||||
associated_token::mint= token_mint,
|
||||
associated_token::authority= payer,
|
||||
associated_token::token_program= token_program
|
||||
)]
|
||||
pub payer_token_account: InterfaceAccount<'info, TokenAccount>,
|
||||
|
||||
#[account(
|
||||
init,
|
||||
payer= payer,
|
||||
associated_token::mint= token_mint,
|
||||
associated_token::authority= bet_vault,
|
||||
associated_token::token_program= token_program
|
||||
)]
|
||||
pub token_vault: InterfaceAccount<'info, TokenAccount>,
|
||||
|
||||
pub system_program: Program<'info, System>,
|
||||
pub token_program: Interface<'info, TokenInterface>,
|
||||
pub associated_token_program: Program<'info, AssociatedToken>,
|
||||
}
|
||||
58
programs/bets/src/instructions/create_leaderboard.rs
Normal file
58
programs/bets/src/instructions/create_leaderboard.rs
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
use anchor_lang::prelude::*;
|
||||
use anchor_spl::{associated_token::AssociatedToken, token_interface::{TokenAccount, TokenInterface}};
|
||||
use crate::*;
|
||||
|
||||
pub fn create(ctx: Context<CreateLeaderboard>, id:u64, game_id:String, entry_ticket_count:u64, max_attempt_count:u64) -> Result<()> {
|
||||
let ticket_leaderboard_list = &mut ctx.accounts.ticket_leaderboard_list;
|
||||
let ticket_leaderboard = &mut ctx.accounts.ticket_leaderboard;
|
||||
|
||||
ticket_leaderboard.id = id;
|
||||
ticket_leaderboard.players = vec![];
|
||||
ticket_leaderboard.game_id = game_id;
|
||||
ticket_leaderboard.is_over = false;
|
||||
ticket_leaderboard.entry_ticket_count = entry_ticket_count;
|
||||
ticket_leaderboard.max_attempts = max_attempt_count;
|
||||
|
||||
ticket_leaderboard_list.leaderboards.push(ticket_leaderboard.key());
|
||||
msg!("Leaderboard created!");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
#[derive(Accounts)]
|
||||
#[instruction(id:u64)]
|
||||
pub struct CreateLeaderboard<'info>{
|
||||
#[account(mut)]
|
||||
pub payer: Signer<'info>,
|
||||
|
||||
#[account(
|
||||
mut,
|
||||
seeds = [TICKET_LEADERBOARD_LIST_SEED],
|
||||
bump
|
||||
)]
|
||||
pub ticket_leaderboard_list: Account<'info, TicketLeaderboardList>,
|
||||
|
||||
#[account(
|
||||
init,
|
||||
payer = payer,
|
||||
space = 8 + TicketLeaderboard::INIT_SPACE,
|
||||
seeds = [TICKET_LEADERBOARD_SEED, id.to_le_bytes().as_ref()],
|
||||
bump
|
||||
)]
|
||||
pub ticket_leaderboard: Account<'info, TicketLeaderboard>,
|
||||
|
||||
#[account(
|
||||
init,
|
||||
payer = payer,
|
||||
associated_token::mint = token_mint,
|
||||
associated_token::authority = ticket_leaderboard,
|
||||
associated_token::token_program = token_program
|
||||
)]
|
||||
pub token_vault: InterfaceAccount<'info, TokenAccount>,
|
||||
|
||||
pub token_mint: InterfaceAccount<'info, anchor_spl::token_interface::Mint>,
|
||||
|
||||
pub system_program: Program<'info, System>,
|
||||
pub token_program: Interface<'info, TokenInterface>,
|
||||
pub associated_token_program: Program<'info, AssociatedToken>,
|
||||
}
|
||||
101
programs/bets/src/instructions/deduct_fees.rs
Normal file
101
programs/bets/src/instructions/deduct_fees.rs
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
use std::str::FromStr;
|
||||
|
||||
use anchor_lang::prelude::*;
|
||||
use crate::{error::BettingError, *};
|
||||
|
||||
pub fn deduct(ctx: Context<DeductFees>, winner:Pubkey, userid:String)->Result<()>{
|
||||
let bet_vault = &mut ctx.accounts.bet_vault;
|
||||
require!(
|
||||
bet_vault.owner == winner || bet_vault.joiner == winner || bet_vault.owner_id == userid || bet_vault.joiner_id == userid,
|
||||
BettingError::InvalidWinner
|
||||
);
|
||||
|
||||
let fee_collector_pubkey = Pubkey::from_str(FEE_COLLECTOR).map_err(|_| BettingError::InvalidFeeCollector)?;
|
||||
require_keys_eq!(
|
||||
ctx.accounts.fee_wallet.key(),
|
||||
fee_collector_pubkey,
|
||||
BettingError::InvalidFeeCollector
|
||||
);
|
||||
|
||||
// Calculate the 5% fee
|
||||
let total_lamports = **bet_vault.to_account_info().lamports.borrow();
|
||||
msg!("Total lamports: {}", total_lamports);
|
||||
|
||||
let fee = total_lamports / 40; // 2.5%
|
||||
// Calculate rent-exempt minimum balance
|
||||
let rent_exempt_minimum = Rent::get()?.minimum_balance(bet_vault.to_account_info().data_len());
|
||||
msg!("Rent exempt minimum: {}", rent_exempt_minimum);
|
||||
|
||||
let referrer_fee = total_lamports / 80; //1.25% for each referrer
|
||||
msg!("Fee: {}, Referrer fee: {}", fee, referrer_fee);
|
||||
msg!("Total to be sent: {}", fee + referrer_fee + referrer_fee);
|
||||
|
||||
// Calculate rent-exempt minimum for referrer accounts
|
||||
let referrer_rent_exempt = Rent::get()?.minimum_balance(0); // 0 bytes for basic account
|
||||
msg!("Referrer rent exempt minimum: {}", referrer_rent_exempt);
|
||||
|
||||
// Verify we have enough funds for all transfers
|
||||
let total_transfer = fee.checked_add(referrer_fee)
|
||||
.and_then(|sum| sum.checked_add(referrer_fee))
|
||||
.ok_or(BettingError::InsufficientFunds)?;
|
||||
|
||||
require!(
|
||||
total_transfer <= total_lamports,
|
||||
BettingError::InsufficientFunds
|
||||
);
|
||||
|
||||
// Check if referrer accounts have enough SOL for rent
|
||||
let owner_referrer_balance = **ctx.accounts.owner_referrer.lamports.borrow();
|
||||
let joiner_referrer_balance = **ctx.accounts.joiner_referrer.lamports.borrow();
|
||||
|
||||
require!(
|
||||
owner_referrer_balance + referrer_fee >= referrer_rent_exempt,
|
||||
BettingError::InsufficientFunds
|
||||
);
|
||||
|
||||
require!(
|
||||
joiner_referrer_balance + referrer_fee >= referrer_rent_exempt,
|
||||
BettingError::InsufficientFunds
|
||||
);
|
||||
|
||||
// Transfer referrer fees
|
||||
**bet_vault.to_account_info().try_borrow_mut_lamports()? -= referrer_fee;
|
||||
**ctx.accounts.owner_referrer.try_borrow_mut_lamports()? += referrer_fee;
|
||||
msg!("After owner referrer transfer - Vault balance: {}", **bet_vault.to_account_info().lamports.borrow());
|
||||
|
||||
**bet_vault.to_account_info().try_borrow_mut_lamports()? -= referrer_fee;
|
||||
**ctx.accounts.joiner_referrer.try_borrow_mut_lamports()? += referrer_fee;
|
||||
msg!("After joiner referrer transfer - Vault balance: {}", **bet_vault.to_account_info().lamports.borrow());
|
||||
|
||||
**bet_vault.to_account_info().try_borrow_mut_lamports()? -= fee;
|
||||
**ctx.accounts.fee_wallet.to_account_info().try_borrow_mut_lamports()? += fee;
|
||||
msg!("After fee transfer - Vault balance: {}", **bet_vault.to_account_info().lamports.borrow());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct DeductFees<'info>{
|
||||
#[account(mut)]
|
||||
pub bets_list: Account<'info, BetsList>,
|
||||
|
||||
#[account(mut)]
|
||||
pub bet_vault: Account<'info, BetVault>,
|
||||
|
||||
/// CHECK: This is validated against the FEE_COLLECTOR constant.
|
||||
#[account(mut)]
|
||||
pub fee_wallet: AccountInfo<'info>,
|
||||
|
||||
#[account(mut)]
|
||||
pub payer: Signer<'info>,
|
||||
/// CHECK: No constraints are applied to this account.
|
||||
#[account(mut)]
|
||||
pub owner_referrer: AccountInfo<'info>,
|
||||
|
||||
/// CHECK: No constraints are applied to this account.
|
||||
#[account(mut)]
|
||||
pub joiner_referrer: AccountInfo<'info>,
|
||||
|
||||
pub system_program: Program<'info, System>
|
||||
}
|
||||
157
programs/bets/src/instructions/deduct_fees_token.rs
Normal file
157
programs/bets/src/instructions/deduct_fees_token.rs
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
use std::str::FromStr;
|
||||
|
||||
use anchor_lang::prelude::*;
|
||||
use anchor_spl::{
|
||||
associated_token::AssociatedToken,
|
||||
token_interface::{TokenAccount, TokenInterface},
|
||||
};
|
||||
use crate::{error::BettingError, shared::transfer_tokens_pda, *};
|
||||
|
||||
pub fn handle_deduct_fees_token(ctx: Context<DeductFeesToken>, winner: Pubkey, userid: String, owner:Pubkey, game_id:String, nonce:u64) -> Result<()> {
|
||||
let bet_vault = &mut ctx.accounts.bet_vault;
|
||||
require!(
|
||||
bet_vault.owner == winner || bet_vault.joiner == winner || bet_vault.owner_id == userid || bet_vault.joiner_id == userid,
|
||||
BettingError::InvalidWinner
|
||||
);
|
||||
|
||||
let fee_collector_pubkey = Pubkey::from_str(FEE_COLLECTOR).map_err(|_| BettingError::InvalidFeeCollector)?;
|
||||
require_keys_eq!(
|
||||
ctx.accounts.fee_wallet.key(),
|
||||
fee_collector_pubkey,
|
||||
BettingError::InvalidFeeCollector
|
||||
);
|
||||
|
||||
// Calculate the fees (2.5% platform fee, 1.25% for each referrer)
|
||||
let total_tokens = bet_vault.wager;
|
||||
let fee = total_tokens.checked_div(40).ok_or(BettingError::InsufficientFunds)?; // 2.5%
|
||||
let referrer_fee = total_tokens.checked_div(80).ok_or(BettingError::InsufficientFunds)?; // 1.25% for each referrer
|
||||
|
||||
msg!("Total tokens: {}", total_tokens);
|
||||
msg!("Fee: {}, Referrer fee: {}", fee, referrer_fee);
|
||||
msg!("Total to be sent: {}", fee + referrer_fee + referrer_fee);
|
||||
|
||||
// Verify we have enough funds for all transfers
|
||||
let total_transfer = fee.checked_add(referrer_fee)
|
||||
.and_then(|sum| sum.checked_add(referrer_fee))
|
||||
.ok_or(BettingError::InsufficientFunds)?;
|
||||
|
||||
require!(
|
||||
total_transfer <= total_tokens,
|
||||
BettingError::InsufficientFunds
|
||||
);
|
||||
|
||||
// Get PDA seeds and bump
|
||||
let seeds = &[
|
||||
b"bet_vault",
|
||||
bet_vault.owner.as_ref(),
|
||||
bet_vault.game_id.as_bytes(),
|
||||
&bet_vault.nonce.to_le_bytes(),
|
||||
];
|
||||
|
||||
// Transfer referrer fees
|
||||
transfer_tokens_pda(
|
||||
&ctx.accounts.token_vault,
|
||||
&ctx.accounts.owner_referrer_token_account,
|
||||
referrer_fee,
|
||||
&ctx.accounts.token_mint,
|
||||
&bet_vault,
|
||||
&ctx.accounts.token_program,
|
||||
seeds,
|
||||
ctx.bumps.bet_vault,
|
||||
)?;
|
||||
msg!("Transferred {} tokens to owner referrer", referrer_fee);
|
||||
|
||||
transfer_tokens_pda(
|
||||
&ctx.accounts.token_vault,
|
||||
&ctx.accounts.joiner_referrer_token_account,
|
||||
referrer_fee,
|
||||
&ctx.accounts.token_mint,
|
||||
&bet_vault,
|
||||
&ctx.accounts.token_program,
|
||||
seeds,
|
||||
ctx.bumps.bet_vault,
|
||||
)?;
|
||||
msg!("Transferred {} tokens to joiner referrer", referrer_fee);
|
||||
|
||||
// Transfer platform fee
|
||||
transfer_tokens_pda(
|
||||
&ctx.accounts.token_vault,
|
||||
&ctx.accounts.fee_wallet_token_account,
|
||||
fee,
|
||||
&ctx.accounts.token_mint,
|
||||
&bet_vault,
|
||||
&ctx.accounts.token_program,
|
||||
seeds,
|
||||
ctx.bumps.bet_vault,
|
||||
)?;
|
||||
msg!("Transferred {} tokens to fee wallet", fee);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
#[instruction(winner: Pubkey, userid: String,owner:Pubkey, game_id:String, nonce:u64)]
|
||||
pub struct DeductFeesToken<'info> {
|
||||
#[account(mut)]
|
||||
pub bets_list: Account<'info, BetsList>,
|
||||
|
||||
#[account(
|
||||
mut,
|
||||
seeds = [b"bet_vault", owner.as_ref(), game_id.as_bytes(), &nonce.to_le_bytes()],
|
||||
bump
|
||||
)]
|
||||
pub bet_vault: Account<'info, BetVault>,
|
||||
|
||||
/// CHECK: This is validated against the FEE_COLLECTOR constant.
|
||||
#[account(mut)]
|
||||
pub fee_wallet: AccountInfo<'info>,
|
||||
|
||||
#[account(mut)]
|
||||
pub payer: Signer<'info>,
|
||||
|
||||
/// CHECK: No constraints are applied to this account.
|
||||
#[account(mut)]
|
||||
pub owner_referrer: AccountInfo<'info>,
|
||||
|
||||
/// CHECK: No constraints are applied to this account.
|
||||
#[account(mut)]
|
||||
pub joiner_referrer: AccountInfo<'info>,
|
||||
|
||||
pub token_mint: InterfaceAccount<'info, anchor_spl::token_interface::Mint>,
|
||||
|
||||
#[account(
|
||||
mut,
|
||||
associated_token::mint = token_mint,
|
||||
associated_token::authority = bet_vault,
|
||||
associated_token::token_program = token_program
|
||||
)]
|
||||
pub token_vault: InterfaceAccount<'info, TokenAccount>,
|
||||
|
||||
#[account(
|
||||
mut,
|
||||
associated_token::mint = token_mint,
|
||||
associated_token::authority = fee_wallet,
|
||||
associated_token::token_program = token_program
|
||||
)]
|
||||
pub fee_wallet_token_account: InterfaceAccount<'info, TokenAccount>,
|
||||
|
||||
#[account(
|
||||
mut,
|
||||
associated_token::mint = token_mint,
|
||||
associated_token::authority = owner_referrer,
|
||||
associated_token::token_program = token_program
|
||||
)]
|
||||
pub owner_referrer_token_account: InterfaceAccount<'info, TokenAccount>,
|
||||
|
||||
#[account(
|
||||
mut,
|
||||
associated_token::mint = token_mint,
|
||||
associated_token::authority = joiner_referrer,
|
||||
associated_token::token_program = token_program
|
||||
)]
|
||||
pub joiner_referrer_token_account: InterfaceAccount<'info, TokenAccount>,
|
||||
|
||||
pub system_program: Program<'info, System>,
|
||||
pub token_program: Interface<'info, TokenInterface>,
|
||||
pub associated_token_program: Program<'info, AssociatedToken>,
|
||||
}
|
||||
75
programs/bets/src/instructions/enter_leaderboard.rs
Normal file
75
programs/bets/src/instructions/enter_leaderboard.rs
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
use anchor_lang::prelude::*;
|
||||
use anchor_spl::{associated_token::AssociatedToken, token_interface::{TokenAccount, TokenInterface}};
|
||||
use crate::*;
|
||||
|
||||
pub fn enter(ctx: Context<EnterLeaderboard>, id:u64, uid:String) -> Result<()> {
|
||||
let payer = &ctx.accounts.payer;
|
||||
let receipt = &mut ctx.accounts.receipt_account;
|
||||
let leaderboard = &ctx.accounts.ticket_leaderboard;
|
||||
|
||||
// Transfer 1 token from user's vault to leaderboard vault
|
||||
let transfer_amount = 1u64 * leaderboard.entry_ticket_count;
|
||||
|
||||
let cpi_program = ctx.accounts.token_program.to_account_info();
|
||||
let cpi_accounts = anchor_spl::token_interface::TransferChecked {
|
||||
from: ctx.accounts.user_token_vault.to_account_info(),
|
||||
to: ctx.accounts.token_vault.to_account_info(),
|
||||
authority: payer.to_account_info(),
|
||||
mint: ctx.accounts.token_mint.to_account_info(),
|
||||
};
|
||||
let cpi_ctx = CpiContext::new(cpi_program, cpi_accounts);
|
||||
anchor_spl::token_interface::transfer_checked(cpi_ctx, transfer_amount, ctx.accounts.token_mint.decimals)?;
|
||||
|
||||
receipt.active = true;
|
||||
receipt.id= id;
|
||||
receipt.uid = uid;
|
||||
|
||||
msg!("Successfully entered leaderboard");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
#[derive(Accounts)]
|
||||
#[instruction(id:u64)]
|
||||
pub struct EnterLeaderboard<'info>{
|
||||
#[account(mut)]
|
||||
pub payer: Signer<'info>,
|
||||
|
||||
#[account(
|
||||
mut,
|
||||
seeds = [TICKET_LEADERBOARD_SEED, id.to_le_bytes().as_ref()],
|
||||
bump
|
||||
)]
|
||||
pub ticket_leaderboard: Account<'info, TicketLeaderboard>,
|
||||
|
||||
#[account(
|
||||
init_if_needed,
|
||||
payer= payer,
|
||||
space = 8 + TicketReceiptVault::INIT_SPACE,
|
||||
seeds = [RECEIPT_ACCOUNT_SEED, payer.key().as_ref()],
|
||||
bump,
|
||||
)]
|
||||
pub receipt_account: Account<'info, TicketReceiptVault>,
|
||||
|
||||
#[account(
|
||||
mut,
|
||||
associated_token::mint = token_mint,
|
||||
associated_token::authority = ticket_leaderboard,
|
||||
associated_token::token_program = token_program
|
||||
)]
|
||||
pub token_vault: InterfaceAccount<'info, TokenAccount>,
|
||||
|
||||
#[account(
|
||||
mut,
|
||||
associated_token::mint = token_mint,
|
||||
associated_token::authority = payer,
|
||||
associated_token::token_program = token_program
|
||||
)]
|
||||
pub user_token_vault: InterfaceAccount<'info, TokenAccount>,
|
||||
|
||||
pub token_mint: InterfaceAccount<'info, anchor_spl::token_interface::Mint>,
|
||||
|
||||
pub system_program: Program<'info, System>,
|
||||
pub token_program: Interface<'info, TokenInterface>,
|
||||
pub associated_token_program: Program<'info, AssociatedToken>,
|
||||
}
|
||||
26
programs/bets/src/instructions/init_leaderboard_list.rs
Normal file
26
programs/bets/src/instructions/init_leaderboard_list.rs
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
use anchor_lang::prelude::*;
|
||||
use crate::*;
|
||||
|
||||
pub fn init(ctx: Context<InitLeaderboardList>) -> Result<()> {
|
||||
ctx.accounts.ticket_leaderboard_list.leaderboards = vec![];
|
||||
msg!("Initialized Leaderboard List!");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
#[derive(Accounts)]
|
||||
pub struct InitLeaderboardList<'info>{
|
||||
#[account(mut)]
|
||||
pub payer: Signer<'info>,
|
||||
|
||||
#[account(
|
||||
init,
|
||||
payer = payer,
|
||||
space = 8 + TicketLeaderboardList::INIT_SPACE,
|
||||
seeds = [TICKET_LEADERBOARD_LIST_SEED],
|
||||
bump
|
||||
)]
|
||||
pub ticket_leaderboard_list: Account<'info, TicketLeaderboardList>,
|
||||
|
||||
pub system_program: Program<'info, System>,
|
||||
}
|
||||
63
programs/bets/src/instructions/join_bet_token.rs
Normal file
63
programs/bets/src/instructions/join_bet_token.rs
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
use anchor_lang::prelude::*;
|
||||
use anchor_spl::{
|
||||
associated_token::AssociatedToken,
|
||||
token_interface::{Mint, TokenAccount, TokenInterface},
|
||||
};
|
||||
use crate::{error::BettingError, shared::transfer_tokens, *};
|
||||
|
||||
pub fn join_token_bet(ctx: Context<JoinBetToken>, user_id: String, _game_id: String) -> Result<()> {
|
||||
let bet_vault = &mut ctx.accounts.bet_vault;
|
||||
require!(
|
||||
bet_vault.joiner == Pubkey::default(),
|
||||
BettingError::BetAlreadyFilled
|
||||
);
|
||||
let payer = &ctx.accounts.payer;
|
||||
|
||||
// Transfer tokens from payer to bet vault
|
||||
transfer_tokens(
|
||||
&ctx.accounts.payer_token_account,
|
||||
&ctx.accounts.token_vault,
|
||||
&bet_vault.wager,
|
||||
&ctx.accounts.token_mint,
|
||||
&ctx.accounts.payer,
|
||||
&ctx.accounts.token_program
|
||||
)?;
|
||||
|
||||
bet_vault.joiner = payer.key();
|
||||
bet_vault.joiner_id = user_id;
|
||||
|
||||
msg!("Joined bet {} with {} tokens!", bet_vault.key(), bet_vault.wager);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
#[instruction(_game_id: String)]
|
||||
pub struct JoinBetToken<'info> {
|
||||
#[account(mut)]
|
||||
pub bet_vault: Account<'info, BetVault>,
|
||||
|
||||
#[account(mut)]
|
||||
pub payer: Signer<'info>,
|
||||
|
||||
pub token_mint: InterfaceAccount<'info, Mint>,
|
||||
|
||||
#[account(
|
||||
mut,
|
||||
associated_token::mint = token_mint,
|
||||
associated_token::authority = payer,
|
||||
associated_token::token_program = token_program
|
||||
)]
|
||||
pub payer_token_account: InterfaceAccount<'info, TokenAccount>,
|
||||
|
||||
#[account(
|
||||
mut,
|
||||
associated_token::mint = token_mint,
|
||||
associated_token::authority = bet_vault,
|
||||
associated_token::token_program = token_program
|
||||
)]
|
||||
pub token_vault: InterfaceAccount<'info, TokenAccount>,
|
||||
|
||||
pub system_program: Program<'info, System>,
|
||||
pub token_program: Interface<'info, TokenInterface>,
|
||||
pub associated_token_program: Program<'info, AssociatedToken>,
|
||||
}
|
||||
|
|
@ -4,11 +4,53 @@ pub use initialize_bets_list::*;
|
|||
pub mod create_bet;
|
||||
pub use create_bet::*;
|
||||
|
||||
pub mod create_bet_token;
|
||||
pub use create_bet_token::*;
|
||||
|
||||
pub mod join_bet;
|
||||
pub use join_bet::*;
|
||||
|
||||
pub mod join_bet_token;
|
||||
pub use join_bet_token::*;
|
||||
|
||||
pub mod close_bet;
|
||||
pub use close_bet::*;
|
||||
|
||||
pub mod close_bet_token;
|
||||
pub use close_bet_token::*;
|
||||
|
||||
pub mod refund_bet;
|
||||
pub use refund_bet::*;
|
||||
pub use refund_bet::*;
|
||||
|
||||
pub mod deduct_fees;
|
||||
pub use deduct_fees::*;
|
||||
|
||||
pub mod deduct_fees_token;
|
||||
pub use deduct_fees_token::*;
|
||||
|
||||
pub mod shared;
|
||||
pub use shared::*;
|
||||
|
||||
pub mod clear_bets_list;
|
||||
pub use clear_bets_list::*;
|
||||
|
||||
pub mod init_leaderboard_list;
|
||||
pub use init_leaderboard_list::*;
|
||||
|
||||
pub mod create_leaderboard;
|
||||
pub use create_leaderboard::*;
|
||||
|
||||
pub mod update_leaderboard;
|
||||
pub use update_leaderboard::*;
|
||||
|
||||
pub mod buy_tickets;
|
||||
pub use buy_tickets::*;
|
||||
|
||||
pub mod enter_leaderboard;
|
||||
pub use enter_leaderboard::*;
|
||||
|
||||
pub mod remove_leaderboard;
|
||||
pub use remove_leaderboard::*;
|
||||
|
||||
pub mod reward_leaderboard;
|
||||
pub use reward_leaderboard::*;
|
||||
38
programs/bets/src/instructions/remove_leaderboard.rs
Normal file
38
programs/bets/src/instructions/remove_leaderboard.rs
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
use anchor_lang::prelude::*;
|
||||
use crate::*;
|
||||
|
||||
pub fn remove(ctx: Context<RemoveLeaderboard>, id:u64) -> Result<()> {
|
||||
|
||||
let ticket_leaderboard_list = &mut ctx.accounts.ticket_leaderboard_list;
|
||||
let ticket_leaderboard = &mut ctx.accounts.ticket_leaderboard;
|
||||
|
||||
ticket_leaderboard_list.leaderboards.retain(|&leaderboard| leaderboard != ticket_leaderboard.key());
|
||||
|
||||
msg!("Successfully removed leaderboard with id: {}", id);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
#[derive(Accounts)]
|
||||
#[instruction(id:u64)]
|
||||
pub struct RemoveLeaderboard<'info>{
|
||||
#[account(mut)]
|
||||
pub payer: Signer<'info>,
|
||||
|
||||
#[account(
|
||||
mut,
|
||||
seeds = [TICKET_LEADERBOARD_LIST_SEED],
|
||||
bump
|
||||
)]
|
||||
pub ticket_leaderboard_list: Account<'info, TicketLeaderboardList>,
|
||||
|
||||
#[account(
|
||||
mut,
|
||||
seeds = [TICKET_LEADERBOARD_SEED, id.to_le_bytes().as_ref()],
|
||||
bump
|
||||
)]
|
||||
pub ticket_leaderboard: Account<'info, TicketLeaderboard>,
|
||||
|
||||
|
||||
pub system_program: Program<'info, System>,
|
||||
}
|
||||
116
programs/bets/src/instructions/reward_leaderboard.rs
Normal file
116
programs/bets/src/instructions/reward_leaderboard.rs
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
use anchor_lang::prelude::*;
|
||||
use anchor_spl::{associated_token::AssociatedToken, token::{transfer_checked, TransferChecked}, token_interface::{TokenAccount, TokenInterface}};
|
||||
use crate::{error::ArithmeticError, *};
|
||||
|
||||
pub fn reward(ctx:Context<RewardLeaderboard>, id:u64)->Result<()>{
|
||||
|
||||
let leaderboard = &mut ctx.accounts.ticket_leaderboard;
|
||||
|
||||
let ticket_leaderboard_vault = &ctx.accounts.ticket_leaderboard_vault;
|
||||
let ticket_leaderboard_list_vault = &ctx.accounts.ticket_leaderboard_list_vault;
|
||||
let winner = &ctx.accounts.winner;
|
||||
|
||||
// Get the token balance in the ticket_leaderboard vault
|
||||
let token_balance = ticket_leaderboard_vault.amount;
|
||||
|
||||
// Calculate SOL reward: each token is worth 0.1 SOL (100,000,000 lamports)
|
||||
let sol_reward = match token_balance.checked_mul(100_000_000) {
|
||||
Some(reward) => reward,
|
||||
None => return Err(Error::from(ArithmeticError::ArithmeticError)),
|
||||
};
|
||||
|
||||
// Transfer SOL from ticket_leaderboard_list to winner
|
||||
let ix = anchor_lang::solana_program::system_instruction::transfer(
|
||||
&ctx.accounts.ticket_leaderboard_list.key(),
|
||||
&winner.key(),
|
||||
sol_reward
|
||||
);
|
||||
|
||||
let seeds = &[
|
||||
TICKET_LEADERBOARD_LIST_SEED.as_ref(),
|
||||
&[ctx.bumps.ticket_leaderboard_list][..]
|
||||
];
|
||||
let signer_seeds = &[&seeds[..]];
|
||||
|
||||
anchor_lang::solana_program::program::invoke_signed(
|
||||
&ix,
|
||||
&[
|
||||
ctx.accounts.ticket_leaderboard_list.to_account_info(),
|
||||
winner.to_account_info(),
|
||||
],
|
||||
signer_seeds,
|
||||
)?;
|
||||
|
||||
// Transfer all tokens from ticket_leaderboard vault to ticket_leaderboard_list vault
|
||||
let cpi_program = ctx.accounts.token_program.to_account_info();
|
||||
let cpi_accounts = TransferChecked {
|
||||
from: ticket_leaderboard_vault.to_account_info(),
|
||||
to: ticket_leaderboard_list_vault.to_account_info(),
|
||||
authority: leaderboard.to_account_info(),
|
||||
mint: ctx.accounts.token_mint.to_account_info(),
|
||||
};
|
||||
|
||||
let seeds = &[
|
||||
TICKET_LEADERBOARD_SEED,
|
||||
&id.to_le_bytes()[..],
|
||||
&[ctx.bumps.ticket_leaderboard][..]
|
||||
];
|
||||
let signer_seeds = &[&seeds[..]];
|
||||
|
||||
let cpi_ctx = CpiContext::new_with_signer(cpi_program, cpi_accounts, signer_seeds);
|
||||
transfer_checked(cpi_ctx, token_balance, ctx.accounts.token_mint.decimals)?;
|
||||
|
||||
leaderboard.is_over = true;
|
||||
|
||||
msg!("Successfully rewarded winner with {} SOL and transferred {} tokens to leaderboard list",
|
||||
sol_reward as f64 / 1_000_000_000.0, token_balance);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Accounts)]
|
||||
#[instruction(id:u64)]
|
||||
pub struct RewardLeaderboard<'info>{
|
||||
#[account(mut)]
|
||||
pub payer: Signer<'info>,
|
||||
|
||||
/// CHECK: The winner account
|
||||
#[account(mut)]
|
||||
pub winner: SystemAccount<'info>,
|
||||
|
||||
#[account(
|
||||
mut,
|
||||
seeds = [TICKET_LEADERBOARD_LIST_SEED],
|
||||
bump
|
||||
)]
|
||||
pub ticket_leaderboard_list: Account<'info, TicketLeaderboardList>,
|
||||
|
||||
#[account(
|
||||
mut,
|
||||
seeds = [TICKET_LEADERBOARD_SEED, id.to_le_bytes().as_ref()],
|
||||
bump
|
||||
)]
|
||||
pub ticket_leaderboard: Account<'info, TicketLeaderboard>,
|
||||
|
||||
#[account(
|
||||
mut,
|
||||
associated_token::mint = token_mint,
|
||||
associated_token::authority = ticket_leaderboard,
|
||||
associated_token::token_program = token_program
|
||||
)]
|
||||
pub ticket_leaderboard_vault: InterfaceAccount<'info, TokenAccount>,
|
||||
|
||||
#[account(
|
||||
mut,
|
||||
associated_token::mint = token_mint,
|
||||
associated_token::authority = ticket_leaderboard_list,
|
||||
associated_token::token_program = token_program
|
||||
)]
|
||||
pub ticket_leaderboard_list_vault: InterfaceAccount<'info, TokenAccount>,
|
||||
|
||||
pub token_mint: InterfaceAccount<'info, anchor_spl::token_interface::Mint>,
|
||||
|
||||
pub system_program: Program<'info, System>,
|
||||
pub token_program: Interface<'info, TokenInterface>,
|
||||
pub associated_token_program: Program<'info, AssociatedToken>,
|
||||
}
|
||||
56
programs/bets/src/instructions/shared.rs
Normal file
56
programs/bets/src/instructions/shared.rs
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
use anchor_lang::prelude::*;
|
||||
use anchor_spl::token_interface::{
|
||||
transfer_checked, Mint, TokenAccount, TokenInterface, TransferChecked,
|
||||
};
|
||||
use crate::*;
|
||||
|
||||
pub fn transfer_tokens<'info>(
|
||||
from: &InterfaceAccount<'info, TokenAccount>,
|
||||
to: &InterfaceAccount<'info, TokenAccount>,
|
||||
amount: &u64,
|
||||
mint: &InterfaceAccount<'info, Mint>,
|
||||
authority: &Signer<'info>,
|
||||
token_program: &Interface<'info, TokenInterface>,
|
||||
) -> Result<()> {
|
||||
let transfer_accounts_options = TransferChecked {
|
||||
from: from.to_account_info(),
|
||||
mint: mint.to_account_info(),
|
||||
to: to.to_account_info(),
|
||||
authority: authority.to_account_info(),
|
||||
};
|
||||
|
||||
let cpi_context = CpiContext::new(token_program.to_account_info(), transfer_accounts_options);
|
||||
|
||||
transfer_checked(cpi_context, *amount, mint.decimals)
|
||||
}
|
||||
|
||||
pub fn transfer_tokens_pda<'info>(
|
||||
from: &InterfaceAccount<'info, TokenAccount>,
|
||||
to: &InterfaceAccount<'info, TokenAccount>,
|
||||
amount: u64,
|
||||
mint: &InterfaceAccount<'info, Mint>,
|
||||
pda_account: &Account<'info, BetVault>,
|
||||
token_program: &Interface<'info, TokenInterface>,
|
||||
seeds: &[&[u8]],
|
||||
bump: u8,
|
||||
) -> Result<()> {
|
||||
let transfer_accounts_options = TransferChecked {
|
||||
from: from.to_account_info(),
|
||||
mint: mint.to_account_info(),
|
||||
to: to.to_account_info(),
|
||||
authority: pda_account.to_account_info(),
|
||||
};
|
||||
|
||||
let bump_seed = [bump];
|
||||
let mut all_seeds = seeds.to_vec();
|
||||
all_seeds.push(&bump_seed);
|
||||
let signer_seeds: &[&[&[u8]]] = &[&all_seeds];
|
||||
|
||||
let cpi_context = CpiContext::new_with_signer(
|
||||
token_program.to_account_info(),
|
||||
transfer_accounts_options,
|
||||
signer_seeds,
|
||||
);
|
||||
|
||||
transfer_checked(cpi_context, amount, mint.decimals)
|
||||
}
|
||||
68
programs/bets/src/instructions/update_leaderboard.rs
Normal file
68
programs/bets/src/instructions/update_leaderboard.rs
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
use anchor_lang::prelude::*;
|
||||
use crate::{error::LeaderboardError, *};
|
||||
|
||||
pub fn update(ctx: Context<UpdateLeaderboard>, id:u64, player:Pubkey, new_score:u64) -> Result<()> {
|
||||
let leaderboard_entry: &mut Account<LeaderboardEntry> = &mut ctx.accounts.leaderboard_entry;
|
||||
let leaderboard: &mut Account<TicketLeaderboard> = &mut ctx.accounts.ticket_leaderboard;
|
||||
|
||||
let receipt = &mut ctx.accounts.receipt_account;
|
||||
|
||||
if receipt.id != id {
|
||||
return Err(LeaderboardError::InvalidId.into());
|
||||
}
|
||||
|
||||
if !receipt.active {
|
||||
return Err(LeaderboardError::NotActive.into());
|
||||
}
|
||||
|
||||
// Find existing entry for this payer
|
||||
if leaderboard_entry.highscore < new_score {
|
||||
leaderboard_entry.highscore = new_score;
|
||||
}
|
||||
|
||||
leaderboard_entry.total_tickets += 1;
|
||||
|
||||
if !leaderboard.players.contains(&player) {
|
||||
leaderboard.players.push(player);
|
||||
}
|
||||
|
||||
leaderboard_entry.uid = receipt.uid.clone();
|
||||
receipt.active = false;
|
||||
|
||||
msg!("Successfully updated leaderboard with score: {}", new_score);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
#[derive(Accounts)]
|
||||
#[instruction(id:u64, player:Pubkey, new_score:u64)]
|
||||
pub struct UpdateLeaderboard<'info>{
|
||||
#[account(mut)]
|
||||
pub payer: Signer<'info>,
|
||||
|
||||
#[account(
|
||||
mut,
|
||||
seeds = [TICKET_LEADERBOARD_SEED, id.to_le_bytes().as_ref()],
|
||||
bump
|
||||
)]
|
||||
pub ticket_leaderboard: Account<'info, TicketLeaderboard>,
|
||||
|
||||
#[account(
|
||||
init_if_needed,
|
||||
payer= payer,
|
||||
space = 8 + LeaderboardEntry::INIT_SPACE,
|
||||
seeds = [LEADERBOARD_ENTRY_SEED, ticket_leaderboard.key().as_ref(), player.as_ref()],
|
||||
bump
|
||||
)]
|
||||
pub leaderboard_entry: Account<'info, LeaderboardEntry>,
|
||||
|
||||
#[account(
|
||||
mut,
|
||||
seeds = [RECEIPT_ACCOUNT_SEED, player.as_ref()],
|
||||
bump
|
||||
)]
|
||||
pub receipt_account: Account<'info, TicketReceiptVault>,
|
||||
|
||||
|
||||
pub system_program: Program<'info, System>,
|
||||
}
|
||||
|
|
@ -34,4 +34,58 @@ pub mod bets {
|
|||
pub fn refund_bet(ctx:Context<RefundBet>, owner:Pubkey)->Result<()>{
|
||||
refund_bet::refund(ctx, owner)
|
||||
}
|
||||
|
||||
pub fn deduct_fees(ctx:Context<DeductFees>, winner:Pubkey, userid:String)->Result<()>{
|
||||
deduct_fees::deduct(ctx, winner, userid)
|
||||
}
|
||||
|
||||
|
||||
pub fn create_bet_token(ctx:Context<CreateBetToken>, wager:u64,user_id:String, game_id:String, nonce:u64)->Result<()>{
|
||||
create_bet_token::create_token_bet(ctx, wager, user_id, game_id, nonce)
|
||||
}
|
||||
|
||||
pub fn join_bet_token(ctx:Context<JoinBetToken>, user_id:String, game_id:String)->Result<()>{
|
||||
join_bet_token::join_token_bet(ctx, user_id, game_id)
|
||||
}
|
||||
|
||||
pub fn deduct_fees_token(ctx:Context<DeductFeesToken>, winner:Pubkey, userid:String, owner:Pubkey, game_id:String, nonce:u64)->Result<()>{
|
||||
deduct_fees_token::handle_deduct_fees_token(ctx, winner, userid, owner, game_id, nonce)
|
||||
}
|
||||
|
||||
pub fn close_bet_token(ctx:Context<CloseBetToken>, winner:Pubkey, userid:String, owner:Pubkey, game_id:String, nonce:u64)->Result<()>{
|
||||
close_bet_token::close_token_bet(ctx, winner, userid, owner, game_id, nonce)
|
||||
}
|
||||
pub fn clear_bets_list(ctx: Context<ClearBetsList>) -> Result<()> {
|
||||
clear_bets_list::clear(ctx)
|
||||
}
|
||||
|
||||
pub fn init_leaderboard_list(ctx:Context<InitLeaderboardList>) -> Result<()>{
|
||||
init_leaderboard_list::init(ctx)
|
||||
}
|
||||
|
||||
pub fn create_leaderboard(ctx:Context<CreateLeaderboard>, id:u64, game_id:String, entry_ticket_count:u64, max_attempt_count:u64)->Result<()>{
|
||||
create_leaderboard::create(ctx, id, game_id, entry_ticket_count, max_attempt_count)
|
||||
}
|
||||
|
||||
pub fn update_leaderboard(ctx:Context<UpdateLeaderboard>, id:u64, player:Pubkey, new_score:u64)->Result<()>{
|
||||
update_leaderboard::update(ctx, id, player, new_score)
|
||||
}
|
||||
|
||||
pub fn buy_tickets(ctx:Context<BuyTickets>, amount:u64)->Result<()>{
|
||||
buy_tickets::buy(ctx,amount)
|
||||
}
|
||||
|
||||
pub fn enter_leaderboard(ctx:Context<EnterLeaderboard>, id:u64, uid:String)->Result<()>{
|
||||
enter_leaderboard::enter(ctx, id, uid)
|
||||
}
|
||||
|
||||
pub fn remove_leaderboard(ctx:Context<RemoveLeaderboard>, id:u64)->Result<()>{
|
||||
remove_leaderboard::remove(ctx, id)
|
||||
}
|
||||
|
||||
pub fn reward_leaderboard(ctx:Context<RewardLeaderboard>, id:u64)->Result<()>{
|
||||
reward_leaderboard::reward(ctx, id)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ use crate::*;
|
|||
#[account]
|
||||
#[derive(InitSpace)]
|
||||
pub struct BetVault {
|
||||
#[max_len(10)]
|
||||
pub nonce: u64,
|
||||
#[max_len(10)]
|
||||
pub game_id: String,
|
||||
pub owner: Pubkey,
|
||||
|
|
@ -12,5 +14,6 @@ pub struct BetVault {
|
|||
pub joiner: Pubkey,
|
||||
#[max_len(40)]
|
||||
pub joiner_id: String,
|
||||
pub token_mint:Pubkey,
|
||||
pub wager:u64
|
||||
}
|
||||
|
|
@ -2,4 +2,16 @@ pub mod bets_list;
|
|||
pub use bets_list::*;
|
||||
|
||||
pub mod bet_vault;
|
||||
pub use bet_vault::*;
|
||||
pub use bet_vault::*;
|
||||
|
||||
pub mod ticket_leaderboard;
|
||||
pub use ticket_leaderboard::*;
|
||||
|
||||
pub mod ticket_leaderboard_account;
|
||||
pub use ticket_leaderboard_account::*;
|
||||
|
||||
pub mod ticket_leaderboard_list;
|
||||
pub use ticket_leaderboard_list::*;
|
||||
|
||||
pub mod ticket_receipt_vault;
|
||||
pub use ticket_receipt_vault::*;
|
||||
15
programs/bets/src/state/ticket_leaderboard.rs
Normal file
15
programs/bets/src/state/ticket_leaderboard.rs
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
use anchor_lang::*;
|
||||
use crate::*;
|
||||
|
||||
#[account]
|
||||
#[derive(InitSpace)]
|
||||
pub struct TicketLeaderboard{
|
||||
pub id: u64,
|
||||
#[max_len(16)]
|
||||
pub game_id: String,
|
||||
#[max_len(150)]
|
||||
pub players: Vec<Pubkey>,
|
||||
pub entry_ticket_count: u64,
|
||||
pub max_attempts: u64,
|
||||
pub is_over: bool,
|
||||
}
|
||||
11
programs/bets/src/state/ticket_leaderboard_account.rs
Normal file
11
programs/bets/src/state/ticket_leaderboard_account.rs
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
use anchor_lang::*;
|
||||
use crate::*;
|
||||
|
||||
#[account]
|
||||
#[derive(InitSpace)]
|
||||
pub struct LeaderboardEntry{
|
||||
#[max_len(50)]
|
||||
pub uid: String,
|
||||
pub highscore: u64,
|
||||
pub total_tickets: u64
|
||||
}
|
||||
9
programs/bets/src/state/ticket_leaderboard_list.rs
Normal file
9
programs/bets/src/state/ticket_leaderboard_list.rs
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
use anchor_lang::*;
|
||||
use crate::*;
|
||||
|
||||
#[account]
|
||||
#[derive(InitSpace)]
|
||||
pub struct TicketLeaderboardList{
|
||||
#[max_len(300)]
|
||||
pub leaderboards: Vec<Pubkey>
|
||||
}
|
||||
11
programs/bets/src/state/ticket_receipt_vault.rs
Normal file
11
programs/bets/src/state/ticket_receipt_vault.rs
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
use anchor_lang::*;
|
||||
use crate::*;
|
||||
|
||||
#[account]
|
||||
#[derive(InitSpace)]
|
||||
pub struct TicketReceiptVault{
|
||||
pub id: u64,
|
||||
#[max_len(50)]
|
||||
pub uid: String,
|
||||
pub active: bool
|
||||
}
|
||||
231
tests/bets.ts
231
tests/bets.ts
|
|
@ -1,16 +1,233 @@
|
|||
import * as anchor from "@coral-xyz/anchor";
|
||||
import { Program } from "@coral-xyz/anchor";
|
||||
import { Bets } from "../target/types/bets";
|
||||
import { Keypair, PublicKey, LAMPORTS_PER_SOL, Transaction, SystemProgram } from "@solana/web3.js";
|
||||
import { ProgramTestContext, startAnchor } from "solana-bankrun";
|
||||
import { BankrunProvider } from "anchor-bankrun";
|
||||
import { expect } from "chai";
|
||||
import { BN } from "@coral-xyz/anchor";
|
||||
|
||||
const IDL = require('../target/idl/bets.json');
|
||||
const betsAddress = new PublicKey("Haj94DF925qNRgcoRwQfNsVLKgSmFhG4bjgtvusMkkpD");
|
||||
|
||||
describe("bets", () => {
|
||||
// Configure the client to use the local cluster.
|
||||
anchor.setProvider(anchor.AnchorProvider.env());
|
||||
let context: ProgramTestContext;
|
||||
let provider: BankrunProvider;
|
||||
let betsProgram: Program<Bets>;
|
||||
let user1: Keypair;
|
||||
let user2: Keypair;
|
||||
let gameId = "game123";
|
||||
let userId1 = "user123";
|
||||
let userId2 = "user456";
|
||||
let wager = new BN(LAMPORTS_PER_SOL); // 1 SOL
|
||||
|
||||
const program = anchor.workspace.Bets as Program<Bets>;
|
||||
before(async () => {
|
||||
context = await startAnchor("", [{ name: "bets", programId: betsAddress }], []);
|
||||
provider = new BankrunProvider(context);
|
||||
betsProgram = new Program<Bets>(IDL, provider);
|
||||
|
||||
it("Is initialized!", async () => {
|
||||
// Add your test here.
|
||||
const tx = await program.methods.initialize().rpc();
|
||||
console.log("Your transaction signature", tx);
|
||||
// Create test users
|
||||
user1 = Keypair.generate();
|
||||
user2 = Keypair.generate();
|
||||
|
||||
// Fund accounts using bankrun
|
||||
const fundAmount = 10 * LAMPORTS_PER_SOL;
|
||||
|
||||
// Create accounts first by sending a transaction
|
||||
const tx = new Transaction().add(
|
||||
SystemProgram.createAccount({
|
||||
fromPubkey: context.payer.publicKey,
|
||||
newAccountPubkey: user1.publicKey,
|
||||
lamports: fundAmount,
|
||||
space: 0,
|
||||
programId: SystemProgram.programId,
|
||||
}),
|
||||
SystemProgram.createAccount({
|
||||
fromPubkey: context.payer.publicKey,
|
||||
newAccountPubkey: user2.publicKey,
|
||||
lamports: fundAmount,
|
||||
space: 0,
|
||||
programId: SystemProgram.programId,
|
||||
})
|
||||
);
|
||||
|
||||
// Get recent blockhash and set it on the transaction
|
||||
const [blockhash] = await context.banksClient.getLatestBlockhash();
|
||||
tx.recentBlockhash = blockhash;
|
||||
tx.feePayer = context.payer.publicKey;
|
||||
|
||||
// Sign the transaction with all required signers
|
||||
tx.sign(context.payer, user1, user2);
|
||||
await context.banksClient.processTransaction(tx);
|
||||
});
|
||||
|
||||
it("Initializes the bets list", async () => {
|
||||
await betsProgram.methods.initialize().rpc();
|
||||
|
||||
const [betsListAddress] = PublicKey.findProgramAddressSync(
|
||||
[Buffer.from("bets_list")],
|
||||
betsAddress
|
||||
);
|
||||
const betsList = await betsProgram.account.betsList.fetch(betsListAddress);
|
||||
|
||||
expect(betsList.bets).to.be.an('array').that.is.empty;
|
||||
});
|
||||
|
||||
it("Creates a new bet", async () => {
|
||||
const nonce = new BN(Date.now());
|
||||
await betsProgram.methods
|
||||
.createBet(wager, userId1, gameId, nonce)
|
||||
.accounts({
|
||||
payer: user1.publicKey,
|
||||
betsList: (await PublicKey.findProgramAddressSync(
|
||||
[Buffer.from("bets_list")],
|
||||
betsAddress
|
||||
))[0],
|
||||
})
|
||||
.signers([user1])
|
||||
.rpc();
|
||||
|
||||
const [betVaultAddress] = PublicKey.findProgramAddressSync(
|
||||
[
|
||||
Buffer.from("bet_vault"),
|
||||
user1.publicKey.toBuffer(),
|
||||
Buffer.from(gameId),
|
||||
nonce.toArrayLike(Buffer, "le", 8),
|
||||
],
|
||||
betsAddress
|
||||
);
|
||||
|
||||
const betVault = await betsProgram.account.betVault.fetch(betVaultAddress);
|
||||
|
||||
expect(betVault.gameId).to.equal(gameId);
|
||||
expect(betVault.owner).to.eql(user1.publicKey);
|
||||
// expect(betVault.ownerId).to.equal(userId1);
|
||||
// expect(betVault.wager).to.equal(wager);
|
||||
});
|
||||
|
||||
it("Joins an existing bet", async () => {
|
||||
const nonce = new BN(Date.now());
|
||||
// First create a bet
|
||||
await betsProgram.methods
|
||||
.createBet(wager, userId1, gameId, nonce)
|
||||
.accounts({
|
||||
payer: user1.publicKey,
|
||||
betsList: (await PublicKey.findProgramAddressSync(
|
||||
[Buffer.from("bets_list")],
|
||||
betsAddress
|
||||
))[0],
|
||||
})
|
||||
.signers([user1])
|
||||
.rpc();
|
||||
|
||||
// Then join the bet
|
||||
await betsProgram.methods
|
||||
.joinBet(userId2, gameId)
|
||||
.accounts({
|
||||
betVault: (await PublicKey.findProgramAddressSync(
|
||||
[
|
||||
Buffer.from("bet_vault"),
|
||||
user1.publicKey.toBuffer(),
|
||||
Buffer.from(gameId),
|
||||
nonce.toArrayLike(Buffer, "le", 8),
|
||||
],
|
||||
betsAddress
|
||||
))[0],
|
||||
payer: user2.publicKey
|
||||
})
|
||||
.signers([user2])
|
||||
.rpc();
|
||||
|
||||
const [betVaultAddress] = PublicKey.findProgramAddressSync(
|
||||
[
|
||||
Buffer.from("bet_vault"),
|
||||
user1.publicKey.toBuffer(),
|
||||
Buffer.from(gameId),
|
||||
nonce.toArrayLike(Buffer, "le", 8),
|
||||
],
|
||||
betsAddress
|
||||
);
|
||||
|
||||
const betVault = await betsProgram.account.betVault.fetch(betVaultAddress);
|
||||
|
||||
expect(betVault.joiner).to.eql(user2.publicKey);
|
||||
expect(betVault.joinerId).to.equal(userId2);
|
||||
});
|
||||
|
||||
it("Closes a bet with a winner", async () => {
|
||||
const nonce = new BN(Date.now());
|
||||
// First create a bet
|
||||
await betsProgram.methods
|
||||
.createBet(wager, userId1, gameId, nonce)
|
||||
.accounts({
|
||||
payer: user1.publicKey,
|
||||
betsList: (await PublicKey.findProgramAddressSync(
|
||||
[Buffer.from("bets_list")],
|
||||
betsAddress
|
||||
))[0],
|
||||
|
||||
})
|
||||
.signers([user1])
|
||||
.rpc();
|
||||
|
||||
// Join the bet
|
||||
await betsProgram.methods
|
||||
.joinBet(userId2, gameId)
|
||||
.accounts({
|
||||
betVault: (await PublicKey.findProgramAddressSync(
|
||||
[
|
||||
Buffer.from("bet_vault"),
|
||||
user1.publicKey.toBuffer(),
|
||||
Buffer.from(gameId),
|
||||
nonce.toArrayLike(Buffer, "le", 8),
|
||||
],
|
||||
betsAddress
|
||||
))[0],
|
||||
payer: user2.publicKey
|
||||
})
|
||||
.signers([user2])
|
||||
.rpc();
|
||||
|
||||
// Close the bet with user1 as winner
|
||||
await betsProgram.methods
|
||||
.closeBet(user1.publicKey, userId1)
|
||||
.accounts({
|
||||
betsList: (await PublicKey.findProgramAddressSync(
|
||||
[Buffer.from("bets_list")],
|
||||
betsAddress
|
||||
))[0],
|
||||
betVault: (await PublicKey.findProgramAddressSync(
|
||||
[
|
||||
Buffer.from("bet_vault"),
|
||||
user1.publicKey.toBuffer(),
|
||||
Buffer.from(gameId),
|
||||
nonce.toArrayLike(Buffer, "le", 8),
|
||||
],
|
||||
betsAddress
|
||||
))[0],
|
||||
winner: user1.publicKey,
|
||||
payer: user1.publicKey,
|
||||
|
||||
})
|
||||
.signers([user1])
|
||||
.rpc();
|
||||
|
||||
// Verify the bet is removed from the bets list
|
||||
const [betsListAddress] = PublicKey.findProgramAddressSync(
|
||||
[Buffer.from("bets_list")],
|
||||
betsAddress
|
||||
);
|
||||
const betsList = await betsProgram.account.betsList.fetch(betsListAddress);
|
||||
const [betVaultAddress] = PublicKey.findProgramAddressSync(
|
||||
[
|
||||
Buffer.from("bet_vault"),
|
||||
user1.publicKey.toBuffer(),
|
||||
Buffer.from(gameId),
|
||||
nonce.toArrayLike(Buffer, "le", 8),
|
||||
],
|
||||
betsAddress
|
||||
);
|
||||
|
||||
expect(betsList.bets).to.not.include(betVaultAddress);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user