From 7f1143eb22e9e8aa4c103254ef85fbe612209409 Mon Sep 17 00:00:00 2001 From: averel10 Date: Sat, 22 Nov 2025 10:58:49 +0100 Subject: [PATCH] Implement timeout handling and commit resolution in Commit and Reveal components - Added state management for opponent's move and commit time left in Commit component. - Implemented automatic checks for opponent's move and commit timeout. - Introduced timeout resolution functionality allowing players to claim victory if the opponent fails to commit in time. - Updated UI to display timeout results and allow players to resolve timeouts. - Enhanced Reveal component with timeout checks and resolution logic. - Added necessary contract methods in config.json for commit time left and timeout resolution. --- config.json | 47 +- crypto_clash_contract/contracts/Game.sol | 116 ++++- crypto_clash_frontend/app/clash/Commit.tsx | 306 ++++++++---- crypto_clash_frontend/app/clash/Reveal.tsx | 526 +++++++++++++-------- crypto_clash_frontend/public/config.json | 47 +- 5 files changed, 745 insertions(+), 297 deletions(-) diff --git a/config.json b/config.json index 3ebd314..415f55d 100644 --- a/config.json +++ b/config.json @@ -59,7 +59,7 @@ "type": "function" } ], - "GAME_CONTRACT_ADDRESS": "0xAA7057A0203539d9BE86EfB471B831Dd833a9e22", + "GAME_CONTRACT_ADDRESS": "0x7C56Fd34374F0244402aDDf5E940490C1a53E92E", "GAME_ABI": [ { "inputs": [], @@ -74,6 +74,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "COMMIT_TIMEOUT", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "REVEAL_TIMEOUT", @@ -87,6 +100,25 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "gameId", + "type": "uint256" + } + ], + "name": "commitTimeLeft", + "outputs": [ + { + "internalType": "int256", + "name": "", + "type": "int256" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "getActiveGameIds", @@ -278,6 +310,19 @@ "stateMutability": "payable", "type": "function" }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "gameId", + "type": "uint256" + } + ], + "name": "resolveTimeout", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [ { diff --git a/crypto_clash_contract/contracts/Game.sol b/crypto_clash_contract/contracts/Game.sol index b43c120..252c768 100644 --- a/crypto_clash_contract/contracts/Game.sol +++ b/crypto_clash_contract/contracts/Game.sol @@ -4,7 +4,8 @@ pragma solidity >=0.7.3; contract Game { uint public constant BET_MIN = 1e16; // The minimum bet (1 BLD) - uint public constant REVEAL_TIMEOUT = 10 minutes; // Max delay of revelation phase + uint public constant REVEAL_TIMEOUT = 0.5 minutes; // Max delay of revelation phase + uint public constant COMMIT_TIMEOUT = 0.5 minutes; // Max delay of commit phase enum Moves { None, @@ -17,7 +18,9 @@ contract Game { None, PlayerA, PlayerB, - Draw + Draw, + PlayerATimeout, + PlayerBTimeout } // Possible outcomes struct Player { @@ -32,6 +35,7 @@ contract Game { Player playerA; Player playerB; Outcomes outcome; + uint firstCommit; uint firstReveal; uint initialBet; uint gameId; @@ -50,11 +54,11 @@ contract Game { // ------------------------- Registration ------------------------- // modifier validBet(uint gameId) { - require(msg.value >= BET_MIN, "Minimum bet not met"); + require(msg.value == BET_MIN, "Minimum bet not met"); require( games[gameId].initialBet == 0 || - msg.value >= games[gameId].initialBet, - "Bet value too low" + msg.value == games[gameId].initialBet, + "Bet value must match initial bet" ); _; } @@ -125,8 +129,18 @@ contract Game { function play(uint gameId, bytes32 encrMove) public isRegistered(gameId) returns (bool) { GameState storage game = games[gameId]; + // Check if game is still active (commit phase not timed out) + require(game.isActive, "Game is no longer active"); + require(game.firstCommit == 0 || block.timestamp <= game.firstCommit + COMMIT_TIMEOUT, "Commit phase timeout expired"); + // Basic sanity checks with explicit errors to help debugging require(encrMove != bytes32(0), "Encrypted move cannot be zero"); + + // Track first commit timestamp + if (game.firstCommit == 0) { + game.firstCommit = block.timestamp; + } + // Ensure the caller hasn't already committed a move if (msg.sender == game.playerA.addr) { require( @@ -166,6 +180,10 @@ contract Game { ) public isRegistered(gameId) commitPhaseEnded(gameId) returns (Moves) { GameState storage game = games[gameId]; + // Check if reveal phase timeout has expired + if( game.firstReveal != 0 ) { + require(block.timestamp <= game.firstReveal + REVEAL_TIMEOUT, "Reveal phase timeout expired"); + } bytes32 encrMove = keccak256(abi.encodePacked(clearMove)); // Hash of clear input (= "move-password") Moves move = Moves(getFirstChar(clearMove)); // Actual move (Rock / Paper / Scissors) @@ -286,6 +304,17 @@ contract Game { } } + // Pay to one player and slash the other (timeout resolution). + function payWithSlash( + address payable winner, + address payable loser, + uint betAmount + ) private { + // Winner gets both bets + winner.transfer(betAmount * 2); + // Loser gets nothing (slashed) + } + // Reset a specific game. function resetGame(uint gameId) private { GameState storage game = games[gameId]; @@ -326,11 +355,86 @@ contract Game { GameState storage game = games[gameId]; if (game.firstReveal != 0) { - return int((game.firstReveal + REVEAL_TIMEOUT) - block.timestamp); + uint deadline = game.firstReveal + REVEAL_TIMEOUT; + if (block.timestamp >= deadline) { + return 0; + } + return int(deadline - block.timestamp); } return int(REVEAL_TIMEOUT); } + // Return time left before the end of the commit phase. + function commitTimeLeft(uint gameId) public view returns (int) { + if (gameId == 0) return int(COMMIT_TIMEOUT); + + GameState storage game = games[gameId]; + if (game.firstCommit != 0) { + uint deadline = game.firstCommit + COMMIT_TIMEOUT; + if (block.timestamp >= deadline) { + return 0; + } + return int(deadline - block.timestamp); + } + return int(COMMIT_TIMEOUT); + } + + // Resolve a game that has timed out. Caller must be the non-offending player. + // The offending player is slashed (loses their bet), winner gets both bets. + function resolveTimeout(uint gameId) public isRegistered(gameId) { + GameState storage game = games[gameId]; + require(game.isActive, "Game is not active"); + + address caller = msg.sender; + address payable offender; + address payable winner = payable(caller); + + bool commitPhaseTimedOut = game.firstCommit != 0 && + block.timestamp > game.firstCommit + COMMIT_TIMEOUT && (game.playerA.encrMove == bytes32(0) || game.playerB.encrMove == bytes32(0)); + bool revealPhaseTimedOut = game.firstReveal != 0 && + block.timestamp > game.firstReveal + REVEAL_TIMEOUT; + + if (commitPhaseTimedOut) { + // Commit phase timeout: player who didn't commit first is offender + require( + (caller == game.playerA.addr && game.playerB.encrMove == bytes32(0)) || + (caller == game.playerB.addr && game.playerA.encrMove == bytes32(0)), + "Caller must be the non-offending player" + ); + + if (caller == game.playerA.addr) { + offender = game.playerB.addr; + game.outcome = Outcomes.PlayerBTimeout; + } else { + offender = game.playerA.addr; + game.outcome = Outcomes.PlayerATimeout; + } + } else if (revealPhaseTimedOut) { + // Reveal phase timeout: player who didn't reveal is offender + require( + (caller == game.playerA.addr && game.playerB.move == Moves.None) || + (caller == game.playerB.addr && game.playerA.move == Moves.None), + "Caller must be the non-offending player" + ); + + if (caller == game.playerA.addr) { + offender = game.playerB.addr; + game.outcome = Outcomes.PlayerBTimeout; + } else { + offender = game.playerA.addr; + game.outcome = Outcomes.PlayerATimeout; + } + } else { + revert("No timeout has occurred"); + } + + // Reset game + resetGame(gameId); + + // Pay winner and slash offender + payWithSlash(winner, offender, game.initialBet); + } + // ------------------------- Game Management ------------------------- // // Get details of a specific game (for viewing any game) diff --git a/crypto_clash_frontend/app/clash/Commit.tsx b/crypto_clash_frontend/app/clash/Commit.tsx index 3ee13f2..4ee3215 100644 --- a/crypto_clash_frontend/app/clash/Commit.tsx +++ b/crypto_clash_frontend/app/clash/Commit.tsx @@ -45,9 +45,12 @@ export default function Commit({ const [loading, setLoading] = useState(false); const [playMove, setPlayMove] = useState(""); const [selfPlayed, setSelfPlayed] = useState(""); + const [opponentPlayed, setOpponentPlayed] = useState(""); const [bothPlayed, setBothPlayed] = useState(""); const [autoCheckInterval, setAutoCheckInterval] = useState(null); const [moveSubmitted, setMoveSubmitted] = useState(false); + const [commitTimeLeft, setCommitTimeLeft] = useState(0); + const [timeoutExpired, setTimeoutExpired] = useState(false); // Update encrypted move when move or secret changes useEffect(() => { @@ -82,6 +85,18 @@ export default function Commit({ checkSelfPlayed(); + const checkOpponentPlayed = async () => { + try { + const opponentKey = whoAmI === "player1" ? "playerB" : "playerA"; + const encrMove = gameDetails[opponentKey].encrMove; + setOpponentPlayed(Number(encrMove) !== 0 ? "true" : "false"); + } catch (err: any) { + console.error("Auto-check opponent played failed:", err.message); + } + }; + + checkOpponentPlayed(); + // Check immediately on mount or when dependencies change const checkBothPlayed = async () => { try { @@ -100,8 +115,27 @@ export default function Commit({ checkBothPlayed(); + // Check commit timeout + const checkCommitTimeout = async () => { + try { + const timeLeft = await contract.methods.commitTimeLeft(gameDetails.returnGameId).call(); + console.log("Commit time left:", timeLeft); + setCommitTimeLeft(Number(timeLeft)); + if (Number(timeLeft) <= 0) { + setTimeoutExpired(true); + } + } catch (err: any) { + console.error("Commit timeout check failed:", err.message); + } + }; + + checkCommitTimeout(); + // Set up interval to check every 2 seconds - const interval = setInterval(checkBothPlayed, 2000); + const interval = setInterval(() => { + checkBothPlayed(); + checkCommitTimeout(); + }, 2000); setAutoCheckInterval(interval); return () => { @@ -151,99 +185,207 @@ export default function Commit({ setSelectedMove(move); }; + const handleResolveTimeout = async () => { + if (!contract || !web3 || !account) return; + setLoading(true); + try { + const tx = contract.methods.resolveTimeout(gameDetails?.returnGameId); + const gas = await tx.estimateGas({ from: account }); + const result = await (globalThis as any).ethereum.request({ + method: "eth_sendTransaction", + params: [ + { + from: account, + to: config?.GAME_CONTRACT_ADDRESS, + data: tx.encodeABI(), + gas: web3.utils.toHex(gas), + chainId: web3.utils.toHex(await web3.eth.net.getId()), + }, + ], + }); + showToast("Timeout resolved: " + result, "success"); + } catch (err: any) { + console.error("Timeout resolution failed:", err); + showToast("Timeout resolution failed: " + err.message, "error"); + } finally { + setLoading(false); + } + }; + + // Check if current player can resolve timeout (they committed, opponent didn't, and game is still active) + const canResolveTimeout = timeoutExpired && selfPlayed === "true" && gameDetails?.isActive; + + // Check if game is finished due to timeout + const isGameFinishedByTimeout = !gameDetails?.isActive && (Number(gameDetails?.outcome) === 4 || Number(gameDetails?.outcome) === 5); + + // Determine if current player won/lost the timeout + const didIWinTimeout = + (whoAmI === "player2" && Number(gameDetails?.outcome) === 4) || + (whoAmI === "player1" && Number(gameDetails?.outcome) === 5); + return (
-

- Select Your Move -

- {moveSubmitted || selfPlayed === "true" ? ( - // Waiting animation after move is submitted + {/* Show timeout result after game is inactive */} + {isGameFinishedByTimeout ? (
-
-
-
-

- Waiting for opponent... -

-

- Your move has been submitted. Stand by while the other player commits. -

+ {didIWinTimeout ? ( +
+

+ 🎉 Victory by Timeout! +

+

+ Your opponent failed to commit in time. You claimed victory! +

+
+ ) : ( +
+

+ ⏱️ Timeout Loss +

+

+ You failed to commit in time. Your opponent claimed victory! +

+
+ )}
) : ( <> - {/* Move Selection */} -
-

- Choose your move: -

-
- {(["1", "2", "3"] as const).map((move) => ( - - ))} + {loading ? "Processing..." : "⚡ Resolve Timeout"} + +
-
- - {/* Secret Input */} -
- -
- handleSecretChange(e.target.value)} - placeholder="Your secret passphrase" - className="flex-1" - /> - + ) : timeoutExpired && !canResolveTimeout ? ( + // Show waiting message to opponent +
+
+
+
+

+ Opponent claiming victory... +

+

+ The timeout has expired. Waiting for the opponent to resolve. +

-

- Keep this secret safe! It's needed to reveal your move later. -

-
+ ) : ( + <> + {/* Time Left Display */} + {commitTimeLeft > 0 && opponentPlayed === "true" && ( +
+

+ ⏱️ Time Left: {commitTimeLeft}s +

+
+ )} - {/* Encrypted Move Display */} -
- -
- - {playMove || "Select a move and enter a secret"} - -
-
+ {moveSubmitted || selfPlayed === "true" ? ( + // Waiting animation after move is submitted +
+
+
+
+

+ Waiting for opponent... +

+

+ Your move has been submitted. Stand by while the other player commits. +

+
+ ) : ( + <> + {/* Move Selection */} +
+

+ Choose your move: +

+
+ {(["1", "2", "3"] as const).map((move) => ( + + ))} +
+
- {/* Action Buttons */} -
- -
+ {/* Secret Input */} +
+ +
+ handleSecretChange(e.target.value)} + placeholder="Your secret passphrase" + className="flex-1" + /> + +
+

+ Keep this secret safe! It's needed to reveal your move later. +

+
+ + {/* Encrypted Move Display */} +
+ +
+ + {playMove || "Select a move and enter a secret"} + +
+
+ + {/* Action Buttons */} +
+ +
+ + )} + + )} )}
diff --git a/crypto_clash_frontend/app/clash/Reveal.tsx b/crypto_clash_frontend/app/clash/Reveal.tsx index bfe247d..f92b3b2 100644 --- a/crypto_clash_frontend/app/clash/Reveal.tsx +++ b/crypto_clash_frontend/app/clash/Reveal.tsx @@ -48,6 +48,8 @@ export default function Reveal({ const [opponentRevealed, setOpponentRevealed] = useState(false); const [bothRevealed, setBothRevealed] = useState(false); const [outcome, setOutcome] = useState(0); + const [revealTimeLeft, setRevealTimeLeft] = useState(0); + const [timeoutExpired, setTimeoutExpired] = useState(false); const clearMove = selectedMove && secret ? `${selectedMove}-${secret}` : ""; @@ -77,6 +79,25 @@ export default function Reveal({ }; setStateFromGameDetails(); + + // Check reveal timeout + const checkRevealTimeout = async () => { + if (!contract || !gameDetails) return; + try { + const timeLeft = await contract.methods.revealTimeLeft(gameDetails.returnGameId).call(); + setRevealTimeLeft(Number(timeLeft)); + if (Number(timeLeft) <= 0) { + setTimeoutExpired(true); + } + } catch (err: any) { + console.error("Reveal timeout check failed:", err.message); + } + }; + + checkRevealTimeout(); + const interval = setInterval(checkRevealTimeout, 2000); + + return () => clearInterval(interval); }, [gameDetails, contract, account, whoAmI]); const handleReveal = async () => { @@ -99,6 +120,7 @@ export default function Reveal({ }); showSuccessToast("Reveal tx sent: " + result); } catch (err: any) { + console.error("Reveal failed:", err); showErrorToast("Reveal failed: " + err.message); } finally { setLoading(false); @@ -132,230 +154,320 @@ export default function Reveal({ } }; + const handleResolveTimeout = async () => { + if (!contract || !web3 || !account) return; + setLoading(true); + try { + const tx = contract.methods.resolveTimeout(gameDetails?.returnGameId); + const gas = await tx.estimateGas({ from: account }); + const result = await (globalThis as any).ethereum.request({ + method: "eth_sendTransaction", + params: [ + { + from: account, + to: config?.GAME_CONTRACT_ADDRESS, + data: tx.encodeABI(), + gas: web3.utils.toHex(gas), + chainId: web3.utils.toHex(await web3.eth.net.getId()), + }, + ], + }); + showSuccessToast("Timeout resolved: " + result); + } catch (err: any) { + console.error(err); + showErrorToast("Timeout resolution failed: " + err.message); + } finally { + setLoading(false); + } + }; + const outcomeData = OUTCOMES[outcome] || OUTCOMES[0]; + // Check if game is finished due to timeout + const isGameFinishedByTimeout = !gameDetails?.isActive && (Number(gameDetails?.outcome) === 4 || Number(gameDetails?.outcome) === 5); + + // Determine if current player won/lost the timeout + const didIWinTimeout = + (whoAmI === "player2" && Number(gameDetails?.outcome) === 4) || + (whoAmI === "player1" && Number(gameDetails?.outcome) === 5); + return (
- {/* Your Move Section - Hidden when both revealed */} - {!bothRevealed && ( -
-

- Your Move -

- {selectedMove ? ( -
-
- {MOVES[selectedMove].icon} - - {MOVES[selectedMove].name} - -
-
-
-

- Clear Move: -

- - {clearMove} - -
+ {/* Show timeout result after game is inactive */} + {isGameFinishedByTimeout && ( +
+ {didIWinTimeout ? ( +
+

+ 🎉 Victory by Timeout! +

+

+ Your opponent failed to reveal in time. You claimed victory! +

) : ( -

- No move selected yet -

+
+

+ ⏱️ Timeout Loss +

+

+ You failed to reveal in time. Your opponent claimed victory! +

+
)}
)} - {/* Game Status Section - Hidden when both revealed */} - {!bothRevealed && ( -
-
-

{selfRevealed ? "✅" : "⏳"}

-

- Me -

-

- {selfRevealed ? "Revealed" : "Waiting"} -

-
-
-

{opponentRevealed ? "✅" : "⏳"}

-

- Opponent -

-

- {opponentRevealed ? "Revealed" : "Waiting"} -

-
-
-

- ⏱️ -

-

- Time Left -

-

- {0} -

-
-
- )} + {!isGameFinishedByTimeout && ( + <> + {/* Your Move Section - Hidden when both revealed */} + {!bothRevealed && !timeoutExpired && ( +
+

+ Your Move +

+ {selectedMove ? ( +
+
+ {MOVES[selectedMove].icon} + + {MOVES[selectedMove].name} + +
+
+
+

+ Clear Move: +

+ + {clearMove} + +
+
+ ) : ( +

+ No move selected yet +

+ )} +
+ )} + {/* Timeout Warning */} + {timeoutExpired && ( +
+

+ ⏱️ Reveal phase timeout expired! +

+

+ {selfRevealed + ? "The opponent failed to reveal in time. You can claim victory!" + : "You failed to reveal in time. The opponent can claim victory!"} +

+ {selfRevealed && ( + )} +
+ )} + + {/* Game Status Section - Hidden when both revealed */} + {!bothRevealed && !timeoutExpired && ( + <> - {/* Reveal Section - Hidden when both revealed */} - {!bothRevealed && ( -
-

- Reveal Your Move -

-

- Submit your clear move and secret to the blockchain. This proves you - didn't cheat! -

- -
- )} - {/* Winner Section - Only show if both revealed */} - {bothRevealed && ( -
- {/* Moves Comparison */} -
-

- Final Moves -

-
- {/* Your Move (always on left) */} -
- - {gameDetails && MOVES[String(whoAmI === "player1" ? gameDetails.playerA.move : gameDetails.playerB.move)]?.icon} - - - You - - - {gameDetails && MOVES[String(whoAmI === "player1" ? gameDetails.playerA.move : gameDetails.playerB.move)]?.name} - +
+
+

{selfRevealed ? "✅" : "⏳"}

+

+ Me +

+

+ {selfRevealed ? "Revealed" : "Waiting"} +

+
+
+

{opponentRevealed ? "✅" : "⏳"}

+

+ Opponent +

+

+ {opponentRevealed ? "Revealed" : "Waiting"} +

+
+
+

+ ⏱️ +

+

+ Time Left +

+

+ {revealTimeLeft}s +

+
+
+ + )} + + {/* Reveal Section - Hidden when both revealed */} + {!bothRevealed && !timeoutExpired && ( +
+

+ Reveal Your Move +

+

+ Submit your clear move and secret to the blockchain. This proves you + didn't cheat! +

+ +
+ )} + + {/* Winner Section - Only show if both revealed */} + {bothRevealed && ( +
+ {/* Moves Comparison */} +
+

+ Final Moves +

+
+ {/* Your Move (always on left) */} +
+ + {gameDetails && MOVES[String(whoAmI === "player1" ? gameDetails.playerA.move : gameDetails.playerB.move)]?.icon} + + + You + + + {gameDetails && MOVES[String(whoAmI === "player1" ? gameDetails.playerA.move : gameDetails.playerB.move)]?.name} + +
+ + {/* VS */} +
+ + VS + +
+ + {/* Opponent Move (always on right) */} +
+ + {gameDetails && MOVES[String(whoAmI === "player1" ? gameDetails.playerB.move : gameDetails.playerA.move)]?.icon} + + + Opponent + + + {gameDetails && MOVES[String(whoAmI === "player1" ? gameDetails.playerB.move : gameDetails.playerA.move)]?.name} + +
+
- {/* VS */} -
- - VS - -
+ {/* Outcome Section */} +
+

+ {outcomeData.emoji} +

+

+ {outcomeData.name} +

- {/* Opponent Move (always on right) */} -
- - {gameDetails && MOVES[String(whoAmI === "player1" ? gameDetails.playerB.move : gameDetails.playerA.move)]?.icon} - - - Opponent - - - {gameDetails && MOVES[String(whoAmI === "player1" ? gameDetails.playerB.move : gameDetails.playerA.move)]?.name} - + {/* Display ETH winnings */} + {gameDetails && outcome !== 0 && ( +
+

+ {outcome === 1 ? "You Won" : outcome === 3 ? "Draw" : "You Lost"} +

+

+ {outcome === 1 ? ( + <> + +{web3?.utils.fromWei(String(BigInt(gameDetails.initialBet) * BigInt(2)), "ether")} ETH + + ) : outcome === 3 ? ( + <> + +{web3?.utils.fromWei(gameDetails.initialBet, "ether")} ETH + + ) : ( + <> + -{web3?.utils.fromWei(gameDetails.initialBet, "ether")} ETH + + )} +

+
+ )} + + {/* Show Claim Coins button only on win or draw and if game is active */} + {(outcome === 1 || outcome === 3) && gameDetails?.isActive && ( + + )}
-
- - {/* Outcome Section */} -
-

- {outcomeData.emoji} -

-

- {outcomeData.name} -

- - {/* Display ETH winnings */} - {gameDetails && ( -
-

- {outcome === 1 ? "You Won" : outcome === 3 ? "Draw" : "You Lost"} -

-

- {outcome === 1 ? ( - <> - +{web3?.utils.fromWei(String(BigInt(gameDetails.initialBet) * BigInt(2)), "ether")} ETH - - ) : outcome === 3 ? ( - <> - +{web3?.utils.fromWei(gameDetails.initialBet, "ether")} ETH - - ) : ( - <> - -{web3?.utils.fromWei(gameDetails.initialBet, "ether")} ETH - - )} -

-
- )} - - {/* Show Claim Coins button only on win or draw and if game is active */} - {(outcome === 1 || outcome === 3) && gameDetails?.isActive && ( - - )} -
-
+ )} + )}
); diff --git a/crypto_clash_frontend/public/config.json b/crypto_clash_frontend/public/config.json index 3ebd314..415f55d 100644 --- a/crypto_clash_frontend/public/config.json +++ b/crypto_clash_frontend/public/config.json @@ -59,7 +59,7 @@ "type": "function" } ], - "GAME_CONTRACT_ADDRESS": "0xAA7057A0203539d9BE86EfB471B831Dd833a9e22", + "GAME_CONTRACT_ADDRESS": "0x7C56Fd34374F0244402aDDf5E940490C1a53E92E", "GAME_ABI": [ { "inputs": [], @@ -74,6 +74,19 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [], + "name": "COMMIT_TIMEOUT", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "REVEAL_TIMEOUT", @@ -87,6 +100,25 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "gameId", + "type": "uint256" + } + ], + "name": "commitTimeLeft", + "outputs": [ + { + "internalType": "int256", + "name": "", + "type": "int256" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "getActiveGameIds", @@ -278,6 +310,19 @@ "stateMutability": "payable", "type": "function" }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "gameId", + "type": "uint256" + } + ], + "name": "resolveTimeout", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [ {