Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 0 additions & 55 deletions contracts/game_contract/src/game_state.rs

This file was deleted.

54 changes: 36 additions & 18 deletions contracts/game_contract/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ pub struct Game {
pub created_at: u64,
pub winner: Option<Address>,
pub last_move_at: u64, // Ledger sequence of last move
pub board_fen: Bytes,
}

#[contracttype]
Expand Down Expand Up @@ -178,6 +179,7 @@ impl GameContract {
env: Env,
player1: Address,
wager_amount: i128,
initial_board: Bytes,
) -> Result<u64, ContractError> {
let max_stake: i128 = env.storage().instance().get(&MAX_STAKE).unwrap_or(1_000);
if wager_amount > max_stake {
Expand Down Expand Up @@ -210,6 +212,7 @@ impl GameContract {
created_at: env.ledger().sequence() as u64,
winner: None,
last_move_at: env.ledger().sequence() as u64,
board_fen: initial_board,
};

let mut games: Map<u64, Game> = env
Expand Down Expand Up @@ -291,6 +294,8 @@ impl GameContract {
game_id: u64,
player: Address,
move_data: Vec<u32>,
new_board: Bytes,
game_status: u32,
) -> Result<(), ContractError> {
let mut games: Map<u64, Game> = env
.storage()
Expand Down Expand Up @@ -330,6 +335,19 @@ impl GameContract {
game.moves.push_back(chess_move);
game.current_turn = if game.current_turn == 1 { 2 } else { 1 };
game.last_move_at = env.ledger().sequence() as u64;
game.board_fen = new_board;

// Auto settlement logic based on status flag
if game_status == 1 {
// Checkmate: submitting player wins
game.state = GameState::Completed;
game.winner = Some(player.clone());
Self::process_payout(&env, &game, &player)?;
} else if game_status == 2 {
// Draw
game.state = GameState::Drawn;
Self::process_draw_payout(&env, &game)?;
}

games.set(game_id, game);
env.storage().instance().set(&GAMES, &games);
Expand Down Expand Up @@ -1291,7 +1309,7 @@ mod tests {

// Create & join game with wager = 5 (pool = 10)
let wager: i128 = 5;
let game_id = client.create_game(&player1, &wager);
let game_id = client.create_game(&player1, &wager, &Bytes::new(&env));
client.join_game(&game_id, &player2);

// player1 forfeits → player2 wins
Expand Down Expand Up @@ -1350,7 +1368,7 @@ mod tests {
client.set_max_stake(&1_000i128);

let wager: i128 = 500; // pool = 1000
let game_id = client.create_game(&player1, &wager);
let game_id = client.create_game(&player1, &wager, &Bytes::new(&env));
client.join_game(&game_id, &player2);
client.forfeit(&game_id, &player1); // player2 wins

Expand Down Expand Up @@ -1389,7 +1407,7 @@ mod tests {
client.initialize_token(&admin, &token_address);

let initial_wager: i128 = 100;
let game_id = client.create_game(&player1, &initial_wager);
let game_id = client.create_game(&player1, &initial_wager, &Bytes::new(&env));
client.join_game(&game_id, &player2);
client.forfeit(&game_id, &player1);

Expand Down Expand Up @@ -1505,7 +1523,7 @@ mod tests {
client.set_max_stake(&1_000i128);

let wager: i128 = 100;
let game_id = client.create_game(&player1, &wager);
let game_id = client.create_game(&player1, &wager, &Bytes::new(&env));
client.join_game(&game_id, &player2);

env.as_contract(&contract_id, || {
Expand Down Expand Up @@ -1556,7 +1574,7 @@ mod tests {
client.set_max_stake(&1_000i128);

let wager: i128 = 100;
let game_id = client.create_game(&player1, &wager);
let game_id = client.create_game(&player1, &wager, &Bytes::new(&env));
client.join_game(&game_id, &player2);

let result = client.try_claim_timeout_win(&game_id, &player2);
Expand Down Expand Up @@ -1596,7 +1614,7 @@ mod tests {
client.set_max_stake(&1_000i128);

let wager: i128 = 100;
let game_id = client.create_game(&player1, &wager);
let game_id = client.create_game(&player1, &wager, &Bytes::new(&env));
client.join_game(&game_id, &player2);

let remaining = client.get_timeout_remaining(&game_id);
Expand Down Expand Up @@ -1648,7 +1666,7 @@ mod tests {
client.set_max_stake(&1_000i128);

let wager: i128 = 100;
let game_id = client.create_game(&player1, &wager);
let game_id = client.create_game(&player1, &wager, &Bytes::new(&env));
client.join_game(&game_id, &player2);

let reason = Bytes::from_slice(&env, b"Engine abuse");
Expand Down Expand Up @@ -1697,7 +1715,7 @@ mod tests {
client.set_max_stake(&1_000i128);

let wager: i128 = 100;
let game_id = client.create_game(&player1, &wager);
let game_id = client.create_game(&player1, &wager, &Bytes::new(&env));
client.join_game(&game_id, &player2);

let reason = Bytes::from_slice(&env, b"Illegal move");
Expand Down Expand Up @@ -1749,7 +1767,7 @@ mod tests {
client.set_max_stake(&1_000i128);

let wager: i128 = 100;
let game_id = client.create_game(&player1, &wager);
let game_id = client.create_game(&player1, &wager, &Bytes::new(&env));
client.join_game(&game_id, &player2);
client.forfeit(&game_id, &player1);

Expand Down Expand Up @@ -1792,7 +1810,7 @@ mod tests {
client.set_max_stake(&1_000i128);

let wager: i128 = 100;
let game_id = client.create_game(&player1, &wager);
let game_id = client.create_game(&player1, &wager, &Bytes::new(&env));
client.join_game(&game_id, &player2);

let reason = Bytes::from_slice(&env, b"Illegal move");
Expand Down Expand Up @@ -1843,7 +1861,7 @@ mod tests {
client.set_max_stake(&1_000i128);

let wager: i128 = 100;
let game_id = client.create_game(&player1, &wager);
let game_id = client.create_game(&player1, &wager, &Bytes::new(&env));
client.join_game(&game_id, &player2);

env.as_contract(&contract_id, || {
Expand Down Expand Up @@ -1884,16 +1902,16 @@ mod tests {
client.set_max_stake(&1_000i128);

let wager: i128 = 100;
let game_id = client.create_game(&player1, &wager);
let game_id = client.create_game(&player1, &wager, &Bytes::new(&env));
client.join_game(&game_id, &player2);

let first_move = Vec::from_array(&env, [12u32, 28u32]);
client.submit_move(&game_id, &player1, &first_move);
client.submit_move(&game_id, &player1, &first_move, &Bytes::new(&env), &0u32);

env.ledger().set_sequence_number(2);

let second_move = Vec::from_array(&env, [52u32, 36u32]);
client.submit_move(&game_id, &player2, &second_move);
client.submit_move(&game_id, &player2, &second_move, &Bytes::new(&env), &0u32);

let game = client.get_game(&game_id);
assert_eq!(game.current_turn, 1);
Expand Down Expand Up @@ -1942,15 +1960,15 @@ mod tests {
client.set_max_stake(&1_000i128);

let wager: i128 = 100;
let game_id = client.create_game(&player1, &wager);
let game_id = client.create_game(&player1, &wager, &Bytes::new(&env));
client.join_game(&game_id, &player2);

let early_move = Vec::from_array(&env, [52u32, 36u32]);
let result = client.try_submit_move(&game_id, &player2, &early_move);
let result = client.try_submit_move(&game_id, &player2, &early_move, &Bytes::new(&env), &0u32);
assert_eq!(result, Err(Ok(ContractError::NotYourTurn)));

let empty_move = Vec::new(&env);
let result = client.try_submit_move(&game_id, &player1, &empty_move);
let result = client.try_submit_move(&game_id, &player1, &empty_move, &Bytes::new(&env), &0u32);
assert_eq!(result, Err(Ok(ContractError::InvalidMove)));
}

Expand Down Expand Up @@ -1989,7 +2007,7 @@ mod tests {
client.set_max_stake(&1_000i128);

let wager: i128 = 100;
let game_id = client.create_game(&player1, &wager);
let game_id = client.create_game(&player1, &wager, &Bytes::new(&env));
client.join_game(&game_id, &player2);

// File dispute
Expand Down
9 changes: 5 additions & 4 deletions contracts/game_contract/src/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ fn seed_completed_game(
created_at: 0,
winner: None,
last_move_at: 0,
board_fen: Bytes::new(env),
};
let mut games: Map<u64, Game> = Map::new(env);
games.set(game_id, game);
Expand Down Expand Up @@ -190,7 +191,7 @@ fn test_create_game_exceeds_max_stake() {
let player1 = Address::generate(&env);
let wager = 1001; // Exceeds default 1000

let res = client.try_create_game(&player1, &wager);
let res = client.try_create_game(&player1, &wager, &Bytes::new(&env));
assert!(res.is_err());

// The error should be StakeLimitExceeded (15)
Expand Down Expand Up @@ -226,11 +227,11 @@ fn test_set_max_stake() {
client.set_max_stake(&500);

// Try to create game with 600
let res = client.try_create_game(&player1, &600);
let res = client.try_create_game(&player1, &600, &Bytes::new(&env));
assert!(res.is_err());

// Try to create game with 500
let game_id_res = client.try_create_game(&player1, &500);
let game_id_res = client.try_create_game(&player1, &500, &Bytes::new(&env));
assert!(game_id_res.is_ok());
}

Expand Down Expand Up @@ -263,7 +264,7 @@ fn test_payout_with_fee() {
stellar_asset_client.mint(&player1, &wager);
stellar_asset_client.mint(&player2, &wager);

let game_id = client.create_game(&player1, &wager);
let game_id = client.create_game(&player1, &wager, &Bytes::new(&env));
client.join_game(&game_id, &player2);

// Force complete the game and set winner
Expand Down
13 changes: 8 additions & 5 deletions frontend/components/WalletConnectModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -110,9 +110,12 @@ export function WalletConnectModal({
<Dialog.Overlay className="fixed inset-0 z-50 bg-black/60 backdrop-blur-sm animate-overlay-in" />

{/* Content */}
<Dialog.Content className="fixed left-1/2 top-1/2 z-50 w-full max-w-md -translate-x-1/2 -translate-y-1/2 rounded-2xl border border-gray-700/80 bg-gray-900/95 backdrop-blur-xl p-0 shadow-2xl animate-modal-in focus:outline-none">
<Dialog.Content className="fixed left-1/2 top-1/2 z-50 w-full max-w-md -translate-x-1/2 -translate-y-1/2 rounded-2xl border border-gray-600/50 bg-gray-900/80 backdrop-blur-2xl p-0 shadow-[0_0_50px_-12px_rgba(20,184,166,0.3)] animate-modal-in focus:outline-none">
{/* Glowing Aura Background */}
<div className="absolute -inset-1 rounded-2xl bg-gradient-to-r from-teal-500/20 to-blue-600/20 blur-xl z-[-1] opacity-70 pointer-events-none"></div>

Comment on lines +113 to +116

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Glowing aura will likely be invisible behind the modal's own background.

The aura is absolute z-[-1] inside a Dialog.Content that itself has bg-gray-900/80 backdrop-blur-2xl. Negative z within the same stacking context puts the aura behind the parent's painted background, so the blurred gradient won't show. Options: move the aura outside Dialog.Content (as a sibling in the portal) or use a ::before pseudo-element with the aura positioned outside the clipped rounded container so it glows through the edges.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/components/WalletConnectModal.tsx` around lines 113 - 116, In
WalletConnectModal, the glowing aura div is inside Dialog.Content with z-[-1],
so it's rendered behind the dialog's semi-opaque background and becomes
invisible; move the aura out of Dialog.Content and place it as a sibling (e.g.,
directly under the Dialog.Portal or the Dialog wrapper) so it lives in the same
portal but outside the dialog's painted background, keep its positioning classes
(absolute, -inset-1, rounded-2xl, bg-gradient-to-r, blur-xl, opacity-70) and
adjust stacking (give the aura a lower z-index than the dialog container but not
inside the dialog itself) so the glow is visible around the modal edges;
alternatively, if you prefer CSS, implement the aura as a ::before
pseudo-element on the dialog wrapper element (e.g., the element rendered by
Dialog.Content) positioned outside the clipped rounded container so the gradient
shows through the edges.

{/* Header gradient bar */}
<div className="h-1 w-full rounded-t-2xl bg-gradient-to-r from-teal-500 via-blue-600 to-indigo-500" />
<div className="h-1.5 w-full rounded-t-2xl bg-gradient-to-r from-teal-400 via-blue-500 to-indigo-500 shadow-[0_2px_10px_rgba(45,212,191,0.5)]" />

<div className="p-6 space-y-5">
{/* Close button */}
Expand Down Expand Up @@ -290,10 +293,10 @@ export function WalletConnectModal({
<button
onClick={handleConnect}
disabled={isConnecting}
className="w-full group relative py-3 rounded-xl font-semibold text-white overflow-hidden transition-all duration-300 hover:scale-[1.02] active:scale-[0.98] disabled:opacity-50 disabled:cursor-not-allowed"
className="w-full group relative py-3.5 rounded-xl font-bold text-white overflow-hidden transition-all duration-300 hover:scale-[1.03] active:scale-[0.98] disabled:opacity-50 disabled:cursor-not-allowed shadow-[0_0_20px_rgba(20,184,166,0.15)] hover:shadow-[0_0_30px_rgba(20,184,166,0.4)]"
>
<div className="absolute inset-0 bg-gradient-to-r from-teal-500 to-blue-700 transition-all duration-300 group-hover:from-teal-600 group-hover:to-blue-800" />
<div className="absolute inset-0 opacity-0 group-hover:opacity-100 transition-opacity duration-300 bg-gradient-to-r from-teal-400/20 to-blue-600/20" />
<div className="absolute inset-0 bg-gradient-to-r from-teal-500 via-blue-600 to-indigo-600 transition-all duration-300 group-hover:bg-gradient-to-l group-hover:scale-105" />
<div className="absolute inset-0 opacity-0 group-hover:opacity-100 transition-opacity duration-300 bg-[radial-gradient(circle_at_center,rgba(255,255,255,0.2)_0%,transparent_100%)]" />
<span className="relative z-10 flex items-center justify-center gap-2">
{isConnecting ? (
<>
Expand Down
29 changes: 17 additions & 12 deletions frontend/components/Web3StatusBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,28 +45,33 @@ export function Web3StatusBar() {
`${addr.slice(0, 6)}...${addr.slice(-4)}`;

return (
<div className="flex items-center gap-3 px-4 py-2 rounded-xl bg-gray-800/40 border border-gray-700/30 text-sm" role="status" aria-label="Wallet connection status">
<span className={`w-2 h-2 rounded-full ${cfg.dot}`} aria-hidden="true" />
<span className={`${cfg.color} font-medium`}>{cfg.label}</span>
<div className="group relative flex items-center gap-3 px-5 py-2.5 rounded-full bg-gray-900/60 backdrop-blur-md border border-gray-700/50 shadow-lg hover:shadow-xl hover:border-gray-600/50 transition-all duration-300 text-sm cursor-default" role="status" aria-label="Wallet connection status">
<div className="absolute inset-0 rounded-full bg-gradient-to-r from-teal-500/10 to-blue-500/10 opacity-0 group-hover:opacity-100 transition-opacity duration-300" />

<span className="relative flex h-2.5 w-2.5 items-center justify-center">
{status === "connected" && <span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-emerald-400 opacity-75"></span>}
<span className={`relative inline-flex h-2.5 w-2.5 rounded-full ${cfg.dot}`} aria-hidden="true" />
</span>
<span className={`relative ${cfg.color} font-semibold tracking-wide drop-shadow-sm`}>{cfg.label}</span>
{address && (
<>
<span className="text-gray-600">|</span>
<span className="font-mono text-gray-300 text-xs">
<span className="relative text-gray-600">|</span>
<span className="relative font-mono text-gray-300 text-xs font-medium bg-gray-800/50 px-2 py-0.5 rounded-md border border-gray-700/30 shadow-inner">
{truncateAddress(address)}
</span>
</>
)}
<span className="text-gray-600">|</span>
<span className="text-xs text-gray-500 flex items-center gap-1">
<span className="w-1.5 h-1.5 rounded-full bg-yellow-500" aria-hidden="true" />
<span className="relative text-gray-600">|</span>
<span className="relative text-xs text-gray-400 font-medium flex items-center gap-1.5 bg-yellow-500/10 text-yellow-200/90 px-2.5 py-0.5 rounded-md border border-yellow-500/20 transition-all hover:bg-yellow-500/20">
<span className="w-1.5 h-1.5 rounded-full bg-yellow-500 shadow-[0_0_8px_rgba(234,179,8,0.8)]" aria-hidden="true" />
Testnet
</span>
{activeCount > 0 && (
<>
<span className="text-gray-600">|</span>
<span className="text-xs text-blue-400 flex items-center gap-1">
<span className="w-1.5 h-1.5 rounded-full bg-blue-400 animate-pulse" aria-hidden="true" />
{activeCount} tx
<span className="relative text-gray-600">|</span>
<span className="relative text-xs flex items-center gap-1.5 bg-blue-500/10 text-blue-300 px-2.5 py-0.5 rounded-md border border-blue-500/20 shadow-[0_0_10px_rgba(59,130,246,0.15)] transition-all">
<span className="w-1.5 h-1.5 rounded-full bg-blue-400 animate-pulse shadow-[0_0_8px_rgba(96,165,250,0.8)]" aria-hidden="true" />
{activeCount} tx active
</span>
</>
)}
Expand Down
Loading
Loading