diff --git a/crypto_clash_frontend/app/clash/Commit.tsx b/crypto_clash_frontend/app/clash/Commit.tsx new file mode 100644 index 0000000..0c474f4 --- /dev/null +++ b/crypto_clash_frontend/app/clash/Commit.tsx @@ -0,0 +1,53 @@ +interface CommitProps { + playMove: string; + setPlayMove: (v: string) => void; + handlePlay: () => void; + loading: boolean; + account: string; + contract: any; + bothPlayed: string; + handleBothPlayed: () => void; + revealTimeLeft: string; + handleRevealTimeLeft: () => void; +} + +export default function Commit({ + playMove, + setPlayMove, + handlePlay, + loading, + account, + contract, + bothPlayed, + handleBothPlayed, + revealTimeLeft, + handleRevealTimeLeft, +}: Readonly) { + return ( +
+

play(bytes32 encrMove)

+ setPlayMove(e.target.value)} + className="border px-2 py-1 mr-2 rounded" + /> + + +
+ + {bothPlayed} +
+ + {revealTimeLeft} +
+
+ ); +} diff --git a/crypto_clash_frontend/app/clash/Lobby.tsx b/crypto_clash_frontend/app/clash/Lobby.tsx new file mode 100644 index 0000000..8f6b534 --- /dev/null +++ b/crypto_clash_frontend/app/clash/Lobby.tsx @@ -0,0 +1,122 @@ +interface LobbyProps { + registerGameId: string; + setRegisterGameId: (v: string) => void; + registerBet: string; + setRegisterBet: (v: string) => void; + handleRegister: () => void; + loading: boolean; + account: string; + contract: any; + betMin: string; + handleGetBetMin: () => void; + activeGameIds: string; + handleGetActiveGameIds: () => void; + contractBalance: string; + handleGetContractBalance: () => void; + gameDetailsId: string; + setGameDetailsId: (v: string) => void; + gameDetails: any; + handleGetGameDetails: () => void; + myActiveGameId: string; + handleGetMyActiveGameId: () => void; + pastGameIndex: string; + setPastGameIndex: (v: string) => void; + pastGame: any; + handleGetPastGame: () => void; + pastGamesCount: string; + handleGetPastGamesCount: () => void; + whoAmI: string; + handleWhoAmI: () => void; +} + +export default function Lobby({ + registerGameId, + setRegisterGameId, + registerBet, + setRegisterBet, + handleRegister, + loading, + account, + contract, + betMin, + handleGetBetMin, + activeGameIds, + handleGetActiveGameIds, + contractBalance, + handleGetContractBalance, + gameDetailsId, + setGameDetailsId, + gameDetails, + handleGetGameDetails, + myActiveGameId, + handleGetMyActiveGameId, + pastGameIndex, + setPastGameIndex, + pastGame, + handleGetPastGame, + pastGamesCount, + handleGetPastGamesCount, + whoAmI, + handleWhoAmI, +}: Readonly) { + return ( +
+

register(uint gameId) (payable)

+ setRegisterGameId(e.target.value)} + className="border px-2 py-1 mr-2 rounded" + /> + setRegisterBet(e.target.value)} + className="border px-2 py-1 mr-2 rounded" + /> +
+ Enter amount in ETH (e.g., 0.01 for 0.01 ETH). Entering 1 means 1 full + ETH. +
+ + +
+ + {betMin} +
+ + {activeGameIds} +
+ + {contractBalance} +
+ setGameDetailsId(e.target.value)} className="border px-2 py-1 mr-2 rounded" /> + + {gameDetails &&
{JSON.stringify(gameDetails, null, 2)}
}
+
+ + {myActiveGameId} +
+ setPastGameIndex(e.target.value)} className="border px-2 py-1 mr-2 rounded" /> + + {pastGame &&
{JSON.stringify(pastGame, null, 2)}
}
+
+ + {pastGamesCount} +
+ + {whoAmI} +
+
+ ); +} diff --git a/crypto_clash_frontend/app/clash/Reveal.tsx b/crypto_clash_frontend/app/clash/Reveal.tsx new file mode 100644 index 0000000..7fe31c7 --- /dev/null +++ b/crypto_clash_frontend/app/clash/Reveal.tsx @@ -0,0 +1,67 @@ +interface RevealProps { + revealMove: string; + setRevealMove: (v: string) => void; + handleReveal: () => void; + loading: boolean; + account: string; + contract: any; + bothRevealed: string; + handleBothRevealed: () => void; + playerARevealed: string; + handlePlayerARevealed: () => void; + playerBRevealed: string; + handlePlayerBRevealed: () => void; + revealTimeLeft: string; + handleRevealTimeLeft: () => void; +} + +export default function Reveal({ + revealMove, + setRevealMove, + handleReveal, + loading, + account, + contract, + bothRevealed, + handleBothRevealed, + playerARevealed, + handlePlayerARevealed, + playerBRevealed, + handlePlayerBRevealed, + revealTimeLeft, + handleRevealTimeLeft, +}: Readonly) { + return ( +
+

reveal(string clearMove)

+ setRevealMove(e.target.value)} + className="border px-2 py-1 mr-2 rounded" + /> + + +
+ + {bothRevealed} +
+ + {playerARevealed} +
+ + {playerBRevealed} +
+ + {revealTimeLeft} +
+
+ ); +} diff --git a/crypto_clash_frontend/app/clash/page.tsx b/crypto_clash_frontend/app/clash/page.tsx index 131f58e..d6f102b 100644 --- a/crypto_clash_frontend/app/clash/page.tsx +++ b/crypto_clash_frontend/app/clash/page.tsx @@ -1,11 +1,520 @@ "use client"; +import { useEffect, useState } from "react"; +import Web3 from "web3"; +import Lobby from "./Lobby"; +import Commit from "./Commit"; +import Reveal from "./Reveal"; export default function Clash() { + const [config, setConfig] = useState(null); + const [web3, setWeb3] = useState(null); + const [contract, setContract] = useState(null); + const [account, setAccount] = useState(""); + const [status, setStatus] = useState(""); + const [loading, setLoading] = useState(false); + + // Inputs for contract functions + const [phase, setPhase] = useState<"lobby" | "commit" | "reveal">("lobby"); + + const [registerGameId, setRegisterGameId] = useState("0"); + const [registerBet, setRegisterBet] = useState(""); + const [playMove, setPlayMove] = useState(""); + const [revealMove, setRevealMove] = useState(""); + // State for read-only contract calls + const [betMin, setBetMin] = useState(""); + const [activeGameIds, setActiveGameIds] = useState(""); + const [contractBalance, setContractBalance] = useState(""); + const [gameDetailsId, setGameDetailsId] = useState(""); + const [gameDetails, setGameDetails] = useState(null); + const [myActiveGameId, setMyActiveGameId] = useState(""); + const [pastGameIndex, setPastGameIndex] = useState(""); + const [pastGame, setPastGame] = useState(null); + const [pastGamesCount, setPastGamesCount] = useState(""); + const [whoAmI, setWhoAmI] = useState(""); + const [bothPlayed, setBothPlayed] = useState(""); + const [revealTimeLeft, setRevealTimeLeft] = useState(""); + const [bothRevealed, setBothRevealed] = useState(""); + const [playerARevealed, setPlayerARevealed] = useState(""); + const [playerBRevealed, setPlayerBRevealed] = useState(""); + // Reveal phase read-only handlers + const handleBothRevealed = async () => { + if (!contract) return; + setLoading(true); + try { + const res = await contract.methods.bothRevealed().call(); + setBothRevealed(res ? "true" : "false"); + } catch (err: any) { + setStatus("Failed to fetch bothRevealed: " + err.message); + } finally { + setLoading(false); + } + }; + const handlePlayerARevealed = async () => { + if (!contract) return; + setLoading(true); + try { + const res = await contract.methods.playerARevealed().call(); + setPlayerARevealed(res ? "true" : "false"); + } catch (err: any) { + setStatus("Failed to fetch playerARevealed: " + err.message); + } finally { + setLoading(false); + } + }; + const handlePlayerBRevealed = async () => { + if (!contract) return; + setLoading(true); + try { + const res = await contract.methods.playerBRevealed().call(); + setPlayerBRevealed(res ? "true" : "false"); + } catch (err: any) { + setStatus("Failed to fetch playerBRevealed: " + err.message); + const [bothRevealed, setBothRevealed] = useState(""); + const [playerARevealed, setPlayerARevealed] = useState(""); + const [playerBRevealed, setPlayerBRevealed] = useState(""); + setLoading(false); + } + }; + // Commit phase read-only handlers + const handleBothPlayed = async () => { + if (!contract) return; + setLoading(true); + try { + const res = await contract.methods.bothPlayed().call(); + setBothPlayed(res ? "true" : "false"); + } catch (err: any) { + setStatus("Failed to fetch bothPlayed: " + err.message); + } finally { + setLoading(false); + } + }; + const handleRevealTimeLeft = async () => { + if (!contract) return; + setLoading(true); + try { + const res = await contract.methods.revealTimeLeft().call(); + setRevealTimeLeft(res.toString()); + } catch (err: any) { + setStatus("Failed to fetch revealTimeLeft: " + err.message); + } finally { + setLoading(false); + } + }; + // Read-only contract function handlers + const handleGetBetMin = async () => { + if (!contract || !web3) return; + setLoading(true); + try { + const res = await contract.methods.BET_MIN().call(); + setBetMin(web3.utils.fromWei(res, "ether") + " ETH"); + } catch (err: any) { + setStatus("Failed to fetch BET_MIN: " + err.message); + } finally { + setLoading(false); + } + }; + const handleGetActiveGameIds = async () => { + if (!contract) return; + setLoading(true); + try { + const res = await contract.methods.getActiveGameIds().call(); + setActiveGameIds(res.join(", ")); + } catch (err: any) { + setStatus("Failed to fetch active game IDs: " + err.message); + } finally { + setLoading(false); + } + }; + const handleGetContractBalance = async () => { + if (!contract || !web3) return; + setLoading(true); + try { + const res = await contract.methods.getContractBalance().call(); + setContractBalance(web3.utils.fromWei(res, "ether") + " ETH"); + } catch (err: any) { + setStatus("Failed to fetch contract balance: " + err.message); + } finally { + setLoading(false); + } + }; + const handleGetGameDetails = async () => { + if (!contract) return; + setLoading(true); + try { + const res = await contract.methods + .getGameDetails(Number(gameDetailsId)) + .call(); + setGameDetails(res); + } catch (err: any) { + setStatus("Failed to fetch game details: " + err.message); + } finally { + setLoading(false); + } + }; + const handleGetMyActiveGameId = async () => { + if (!contract || !account) return; + setLoading(true); + try { + const res = await contract.methods + .getMyActiveGameId() + .call({ from: account }); + setMyActiveGameId(res.toString()); + } catch (err: any) { + setStatus("Failed to fetch my active game ID: " + err.message); + } finally { + setLoading(false); + } + }; + const handleGetPastGame = async () => { + // Reveal phase read-only handlers + const handleBothRevealed = async () => { + if (!contract) return; + setLoading(true); + try { + const res = await contract.methods.bothRevealed().call(); + setBothRevealed(res ? "true" : "false"); + } catch (err: any) { + setStatus("Failed to fetch bothRevealed: " + err.message); + } finally { + setLoading(false); + } + }; + const handlePlayerARevealed = async () => { + if (!contract) return; + setLoading(true); + try { + const res = await contract.methods.playerARevealed().call(); + setPlayerARevealed(res ? "true" : "false"); + } catch (err: any) { + setStatus("Failed to fetch playerARevealed: " + err.message); + } finally { + setLoading(false); + } + }; + const handlePlayerBRevealed = async () => { + if (!contract) return; + setLoading(true); + try { + const res = await contract.methods.playerBRevealed().call(); + setPlayerBRevealed(res ? "true" : "false"); + } catch (err: any) { + setStatus("Failed to fetch playerBRevealed: " + err.message); + } finally { + setLoading(false); + } + }; + if (!contract) return; + setLoading(true); + try { + const res = await contract.methods + .getPastGame(Number(pastGameIndex)) + .call(); + setPastGame(res); + } catch (err: any) { + setStatus("Failed to fetch past game: " + err.message); + } finally { + setLoading(false); + } + }; + const handleGetPastGamesCount = async () => { + if (!contract) return; + setLoading(true); + try { + const res = await contract.methods.getPastGamesCount().call(); + setPastGamesCount(res.toString()); + } catch (err: any) { + setStatus("Failed to fetch past games count: " + err.message); + } finally { + setLoading(false); + } + }; + const handleWhoAmI = async () => { + if (!contract || !account) return; + setLoading(true); + try { + const res = await contract.methods.whoAmI().call({ from: account }); + setWhoAmI(res.toString()); + } catch (err: any) { + setStatus("Failed to fetch whoAmI: " + err.message); + } finally { + setLoading(false); + } + }; + // (Removed unused inputs and state) + + // Load config and contract + useEffect(() => { + const loadConfig = async () => { + try { + const response = await fetch("/crypto_clash/config.json"); + const data = await response.json(); + setConfig(data); + const web3Instance = new Web3(data.API_URL); + setWeb3(web3Instance); + const contractInstance = new web3Instance.eth.Contract( + data.GAME_ABI, + data.GAME_CONTRACT_ADDRESS + ); + setContract(contractInstance); + // Get account + if (globalThis.window !== undefined && (globalThis as any).ethereum) { + try { + const accounts = await (globalThis as any).ethereum.request({ + method: "eth_requestAccounts", + }); + setAccount(accounts[0]); + } catch (err: any) { + setStatus( + "MetaMask not available or user denied access: " + err.message + ); + } + } + } catch (err: any) { + setStatus("Failed to load config: " + err.message); + } + }; + loadConfig(); + }, []); + + // (Removed unused helpers) + + // Contract function handlers + const handleRegister = async () => { + if (!contract || !web3 || !account) return; + setLoading(true); + setStatus(""); + try { + console.log(registerBet) + const bet = web3.utils.toWei(registerBet || "0.01", "ether"); + console.log(bet) + console.log(web3.utils.toHex(bet)) + const tx = contract.methods.register(Number(registerGameId || 0)); + const gas = await tx.estimateGas({ from: account, value: bet }); + const result = await (globalThis as any).ethereum.request({ + method: "eth_sendTransaction", + params: [ + { + from: account, + to: config?.GAME_CONTRACT_ADDRESS, + data: tx.encodeABI(), + value: web3.utils.numberToHex(bet), + gas: web3.utils.toHex(gas), + chainId: web3.utils.toHex(await web3.eth.net.getId()), + }, + ], + }); + setStatus("Register tx sent: " + result); + } catch (err: any) { + setStatus("Register failed: " + err.message); + } finally { + setLoading(false); + } + }; + + const handlePlay = async () => { + if (!contract || !web3 || !account) return; + setLoading(true); + setStatus(""); + try { + // playMove should be a hex string (bytes32) + const tx = contract.methods.play(playMove); + 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()), + }, + ], + }); + setStatus("Play tx sent: " + result); + } catch (err: any) { + setStatus("Play failed: " + err.message); + } finally { + setLoading(false); + } + }; + + const handleReveal = async () => { + if (!contract || !web3 || !account) return; + setLoading(true); + setStatus(""); + try { + const tx = contract.methods.reveal(revealMove); + 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()), + }, + ], + }); + setStatus("Reveal tx sent: " + result); + } catch (err: any) { + setStatus("Reveal failed: " + err.message); + } finally { + setLoading(false); + } + }; + return ( -
-

Clash Page

-

This is the Clash page content.

+
+
+
+

+ Crypto Clash +

+

+ {phase === "lobby" && "Register for a game to start."} + {phase === "commit" && "Commit your move."} + {phase === "reveal" && "Reveal your move."} +

+
+

+ Connected Account:{" "} + {account + ? `${account.slice(0, 6)}...${account.slice(-4)}` + : "Not connected"} +

+

+ Game Contract Address:{" "} + {config?.GAME_CONTRACT_ADDRESS} +

+
+
+ + + +
+
+ {phase === "lobby" && ( + + )} + {phase === "commit" && ( + + )} + {phase === "reveal" && ( + + )} +
+ {status && ( +
+

{status}

+
+ )} +
+

ℹ️ Note:

+
    +
  • + MetaMask or a compatible Web3 wallet is required for write + operations +
  • +
  • + Use bytes32 for encrypted move (see contract docs for details) +
  • +
  • ETH values are in Ether (not Wei)
  • +
+
+
+
); } diff --git a/crypto_clash_frontend/package-lock.json b/crypto_clash_frontend/package-lock.json index 508965d..119fb6f 100644 --- a/crypto_clash_frontend/package-lock.json +++ b/crypto_clash_frontend/package-lock.json @@ -1544,6 +1544,66 @@ "node": ">=14.0.0" } }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/core": { + "version": "1.6.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.1.0", + "tslib": "^2.4.0" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime": { + "version": "1.6.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/wasi-threads": { + "version": "1.1.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": { + "version": "1.0.7", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.5.0", + "@emnapi/runtime": "^1.5.0", + "@tybys/wasm-util": "^0.10.1" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/tslib": { + "version": "2.8.1", + "dev": true, + "inBundle": true, + "license": "0BSD", + "optional": true + }, "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { "version": "4.1.17", "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.17.tgz", diff --git a/crypto_clash_frontend/public/config.json b/crypto_clash_frontend/public/config.json index 0356444..ee85ce6 100644 --- a/crypto_clash_frontend/public/config.json +++ b/crypto_clash_frontend/public/config.json @@ -1,6 +1,6 @@ { "API_URL": "https://rpc.hasrv.averel10.app/", - "CONTRACT_ADDRESS": "0xC3D2A1471A5e19ce586D4D3cB398Ce560efAF6Ca", + "CONTRACT_ADDRESS": "0x8B7746B922a80d9B29cA939fD90577932527112C", "ABI": [ { "inputs": [ @@ -58,5 +58,325 @@ "stateMutability": "nonpayable", "type": "function" } + ], + "GAME_CONTRACT_ADDRESS": "0x9230eBBB05c3e953a5F135F032b352c6F0B6E164", + "GAME_ABI": [ + { + "inputs": [], + "name": "BET_MIN", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "REVEAL_TIMEOUT", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "bothPlayed", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "bothRevealed", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getActiveGameIds", + "outputs": [ + { + "internalType": "uint256[]", + "name": "", + "type": "uint256[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getContractBalance", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "gameId", + "type": "uint256" + } + ], + "name": "getGameDetails", + "outputs": [ + { + "internalType": "address", + "name": "playerAAddr", + "type": "address" + }, + { + "internalType": "address", + "name": "playerBAddr", + "type": "address" + }, + { + "internalType": "uint256", + "name": "initialBet", + "type": "uint256" + }, + { + "internalType": "enum Game.Outcomes", + "name": "outcome", + "type": "uint8" + }, + { + "internalType": "bool", + "name": "isActive", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getLastWinner", + "outputs": [ + { + "internalType": "enum Game.Outcomes", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getMyActiveGameId", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getOutcome", + "outputs": [ + { + "internalType": "enum Game.Outcomes", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "index", + "type": "uint256" + } + ], + "name": "getPastGame", + "outputs": [ + { + "internalType": "address", + "name": "playerAAddr", + "type": "address" + }, + { + "internalType": "address", + "name": "playerBAddr", + "type": "address" + }, + { + "internalType": "uint256", + "name": "initialBet", + "type": "uint256" + }, + { + "internalType": "enum Game.Outcomes", + "name": "outcome", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getPastGamesCount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "encrMove", + "type": "bytes32" + } + ], + "name": "play", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "playerARevealed", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "playerBRevealed", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "gameId", + "type": "uint256" + } + ], + "name": "register", + "outputs": [ + { + "internalType": "uint256", + "name": "playerId", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "returnGameId", + "type": "uint256" + } + ], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "clearMove", + "type": "string" + } + ], + "name": "reveal", + "outputs": [ + { + "internalType": "enum Game.Moves", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "revealTimeLeft", + "outputs": [ + { + "internalType": "int256", + "name": "", + "type": "int256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "whoAmI", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + } ] } \ No newline at end of file