mirror of
https://github.com/averel10/crypto_clash.git
synced 2026-03-12 19:08:11 +01:00
init contract frontent
This commit is contained in:
53
crypto_clash_frontend/app/clash/Commit.tsx
Normal file
53
crypto_clash_frontend/app/clash/Commit.tsx
Normal file
@@ -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<CommitProps>) {
|
||||
return (
|
||||
<div className="border p-4 rounded-lg">
|
||||
<h2 className="font-semibold mb-2">play(bytes32 encrMove)</h2>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Encrypted Move (bytes32)"
|
||||
value={playMove}
|
||||
onChange={(e) => setPlayMove(e.target.value)}
|
||||
className="border px-2 py-1 mr-2 rounded"
|
||||
/>
|
||||
<button
|
||||
onClick={handlePlay}
|
||||
disabled={loading || !account || !contract}
|
||||
className="bg-blue-500 text-white px-4 py-2 rounded"
|
||||
>
|
||||
Play
|
||||
</button>
|
||||
|
||||
<div className="mt-4 space-y-2">
|
||||
<button onClick={handleBothPlayed} className="bg-gray-200 px-2 py-1 rounded">bothPlayed</button>
|
||||
<span className="ml-2 text-xs">{bothPlayed}</span>
|
||||
<br />
|
||||
<button onClick={handleRevealTimeLeft} className="bg-gray-200 px-2 py-1 rounded">revealTimeLeft</button>
|
||||
<span className="ml-2 text-xs">{revealTimeLeft}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
122
crypto_clash_frontend/app/clash/Lobby.tsx
Normal file
122
crypto_clash_frontend/app/clash/Lobby.tsx
Normal file
@@ -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<LobbyProps>) {
|
||||
return (
|
||||
<div className="border p-4 rounded-lg">
|
||||
<h2 className="font-semibold mb-2">register(uint gameId) (payable)</h2>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Game ID (0 = auto)"
|
||||
value={registerGameId}
|
||||
onChange={(e) => setRegisterGameId(e.target.value)}
|
||||
className="border px-2 py-1 mr-2 rounded"
|
||||
/>
|
||||
<input
|
||||
type="number"
|
||||
min="0.01"
|
||||
step="0.01"
|
||||
placeholder="Bet in ETH (default 0.01)"
|
||||
value={registerBet}
|
||||
onChange={(e) => setRegisterBet(e.target.value)}
|
||||
className="border px-2 py-1 mr-2 rounded"
|
||||
/>
|
||||
<div className="text-xs text-slate-500 mt-1">
|
||||
Enter amount in ETH (e.g., 0.01 for 0.01 ETH). Entering 1 means 1 full
|
||||
ETH.
|
||||
</div>
|
||||
<button
|
||||
onClick={handleRegister}
|
||||
disabled={loading || !account || !contract}
|
||||
className="bg-blue-500 text-white px-4 py-2 rounded"
|
||||
>
|
||||
Register
|
||||
</button>
|
||||
|
||||
<div className="mt-4 space-y-2">
|
||||
<button onClick={handleGetBetMin} className="bg-gray-200 px-2 py-1 rounded">BET_MIN</button>
|
||||
<span className="ml-2 text-xs">{betMin}</span>
|
||||
<br />
|
||||
<button onClick={handleGetActiveGameIds} className="bg-gray-200 px-2 py-1 rounded">getActiveGameIds</button>
|
||||
<span className="ml-2 text-xs">{activeGameIds}</span>
|
||||
<br />
|
||||
<button onClick={handleGetContractBalance} className="bg-gray-200 px-2 py-1 rounded">getContractBalance</button>
|
||||
<span className="ml-2 text-xs">{contractBalance}</span>
|
||||
<br />
|
||||
<input type="text" placeholder="Game ID" value={gameDetailsId} onChange={e => setGameDetailsId(e.target.value)} className="border px-2 py-1 mr-2 rounded" />
|
||||
<button onClick={handleGetGameDetails} className="bg-gray-200 px-2 py-1 rounded">getGameDetails</button>
|
||||
<span className="ml-2 text-xs">{gameDetails && <pre className="inline whitespace-pre-wrap">{JSON.stringify(gameDetails, null, 2)}</pre>}</span>
|
||||
<br />
|
||||
<button onClick={handleGetMyActiveGameId} className="bg-gray-200 px-2 py-1 rounded">getMyActiveGameId</button>
|
||||
<span className="ml-2 text-xs">{myActiveGameId}</span>
|
||||
<br />
|
||||
<input type="text" placeholder="Past Game Index" value={pastGameIndex} onChange={e => setPastGameIndex(e.target.value)} className="border px-2 py-1 mr-2 rounded" />
|
||||
<button onClick={handleGetPastGame} className="bg-gray-200 px-2 py-1 rounded">getPastGame</button>
|
||||
<span className="ml-2 text-xs">{pastGame && <pre className="inline whitespace-pre-wrap">{JSON.stringify(pastGame, null, 2)}</pre>}</span>
|
||||
<br />
|
||||
<button onClick={handleGetPastGamesCount} className="bg-gray-200 px-2 py-1 rounded">getPastGamesCount</button>
|
||||
<span className="ml-2 text-xs">{pastGamesCount}</span>
|
||||
<br />
|
||||
<button onClick={handleWhoAmI} className="bg-gray-200 px-2 py-1 rounded">whoAmI</button>
|
||||
<span className="ml-2 text-xs">{whoAmI}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
67
crypto_clash_frontend/app/clash/Reveal.tsx
Normal file
67
crypto_clash_frontend/app/clash/Reveal.tsx
Normal file
@@ -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<RevealProps>) {
|
||||
return (
|
||||
<div className="border p-4 rounded-lg">
|
||||
<h2 className="font-semibold mb-2">reveal(string clearMove)</h2>
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Clear Move (e.g. 1-password)"
|
||||
value={revealMove}
|
||||
onChange={(e) => setRevealMove(e.target.value)}
|
||||
className="border px-2 py-1 mr-2 rounded"
|
||||
/>
|
||||
<button
|
||||
onClick={handleReveal}
|
||||
disabled={loading || !account || !contract}
|
||||
className="bg-blue-500 text-white px-4 py-2 rounded"
|
||||
>
|
||||
Reveal
|
||||
</button>
|
||||
|
||||
<div className="mt-4 space-y-2">
|
||||
<button onClick={handleBothRevealed} className="bg-gray-200 px-2 py-1 rounded">bothRevealed</button>
|
||||
<span className="ml-2 text-xs">{bothRevealed}</span>
|
||||
<br />
|
||||
<button onClick={handlePlayerARevealed} className="bg-gray-200 px-2 py-1 rounded">playerARevealed</button>
|
||||
<span className="ml-2 text-xs">{playerARevealed}</span>
|
||||
<br />
|
||||
<button onClick={handlePlayerBRevealed} className="bg-gray-200 px-2 py-1 rounded">playerBRevealed</button>
|
||||
<span className="ml-2 text-xs">{playerBRevealed}</span>
|
||||
<br />
|
||||
<button onClick={handleRevealTimeLeft} className="bg-gray-200 px-2 py-1 rounded">revealTimeLeft</button>
|
||||
<span className="ml-2 text-xs">{revealTimeLeft}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -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<Config | null>(null);
|
||||
const [web3, setWeb3] = useState<Web3 | null>(null);
|
||||
const [contract, setContract] = useState<any>(null);
|
||||
const [account, setAccount] = useState<string>("");
|
||||
const [status, setStatus] = useState<string>("");
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
// Inputs for contract functions
|
||||
const [phase, setPhase] = useState<"lobby" | "commit" | "reveal">("lobby");
|
||||
|
||||
const [registerGameId, setRegisterGameId] = useState<string>("0");
|
||||
const [registerBet, setRegisterBet] = useState<string>("");
|
||||
const [playMove, setPlayMove] = useState<string>("");
|
||||
const [revealMove, setRevealMove] = useState<string>("");
|
||||
// State for read-only contract calls
|
||||
const [betMin, setBetMin] = useState<string>("");
|
||||
const [activeGameIds, setActiveGameIds] = useState<string>("");
|
||||
const [contractBalance, setContractBalance] = useState<string>("");
|
||||
const [gameDetailsId, setGameDetailsId] = useState<string>("");
|
||||
const [gameDetails, setGameDetails] = useState<any>(null);
|
||||
const [myActiveGameId, setMyActiveGameId] = useState<string>("");
|
||||
const [pastGameIndex, setPastGameIndex] = useState<string>("");
|
||||
const [pastGame, setPastGame] = useState<any>(null);
|
||||
const [pastGamesCount, setPastGamesCount] = useState<string>("");
|
||||
const [whoAmI, setWhoAmI] = useState<string>("");
|
||||
const [bothPlayed, setBothPlayed] = useState<string>("");
|
||||
const [revealTimeLeft, setRevealTimeLeft] = useState<string>("");
|
||||
const [bothRevealed, setBothRevealed] = useState<string>("");
|
||||
const [playerARevealed, setPlayerARevealed] = useState<string>("");
|
||||
const [playerBRevealed, setPlayerBRevealed] = useState<string>("");
|
||||
// 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<string>("");
|
||||
const [playerARevealed, setPlayerARevealed] = useState<string>("");
|
||||
const [playerBRevealed, setPlayerBRevealed] = useState<string>("");
|
||||
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 (
|
||||
<div>
|
||||
<h1>Clash Page</h1>
|
||||
<p>This is the Clash page content.</p>
|
||||
<div className="flex min-h-screen items-center justify-center bg-gradient-to-br from-blue-50 to-indigo-100 dark:from-slate-900 dark:to-slate-800 font-sans">
|
||||
<main className="w-full max-w-3xl mx-auto py-12 px-6">
|
||||
<div className="bg-white dark:bg-slate-800 rounded-lg shadow-lg p-8">
|
||||
<h1 className="text-4xl font-bold text-center mb-2 text-slate-900 dark:text-white">
|
||||
Crypto Clash
|
||||
</h1>
|
||||
<p className="text-center text-slate-600 dark:text-slate-300 mb-8">
|
||||
{phase === "lobby" && "Register for a game to start."}
|
||||
{phase === "commit" && "Commit your move."}
|
||||
{phase === "reveal" && "Reveal your move."}
|
||||
</p>
|
||||
<div className="mb-8 p-4 bg-slate-100 dark:bg-slate-700 rounded-lg">
|
||||
<p className="text-sm text-slate-600 dark:text-slate-300">
|
||||
<span className="font-semibold">Connected Account:</span>{" "}
|
||||
{account
|
||||
? `${account.slice(0, 6)}...${account.slice(-4)}`
|
||||
: "Not connected"}
|
||||
</p>
|
||||
<p className="text-sm text-slate-600 dark:text-slate-300 mt-2">
|
||||
<span className="font-semibold">Game Contract Address:</span>{" "}
|
||||
{config?.GAME_CONTRACT_ADDRESS}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex justify-center mb-6 space-x-4">
|
||||
<button
|
||||
onClick={() => setPhase("lobby")}
|
||||
className={`px-4 py-2 rounded ${
|
||||
phase === "lobby"
|
||||
? "bg-blue-600 text-white"
|
||||
: "bg-gray-200 dark:bg-gray-700 text-gray-800 dark:text-gray-200"
|
||||
}`}
|
||||
>
|
||||
Lobby
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setPhase("commit")}
|
||||
className={`px-4 py-2 rounded ${
|
||||
phase === "commit"
|
||||
? "bg-blue-600 text-white"
|
||||
: "bg-gray-200 dark:bg-gray-700 text-gray-800 dark:text-gray-200"
|
||||
}`}
|
||||
>
|
||||
Commit
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setPhase("reveal")}
|
||||
className={`px-4 py-2 rounded ${
|
||||
phase === "reveal"
|
||||
? "bg-blue-600 text-white"
|
||||
: "bg-gray-200 dark:bg-gray-700 text-gray-800 dark:text-gray-200"
|
||||
}`}
|
||||
>
|
||||
Reveal
|
||||
</button>
|
||||
</div>
|
||||
<div className="space-y-6">
|
||||
{phase === "lobby" && (
|
||||
<Lobby
|
||||
registerGameId={registerGameId}
|
||||
setRegisterGameId={setRegisterGameId}
|
||||
registerBet={registerBet}
|
||||
setRegisterBet={setRegisterBet}
|
||||
handleRegister={handleRegister}
|
||||
loading={loading}
|
||||
account={account}
|
||||
contract={contract}
|
||||
betMin={betMin}
|
||||
handleGetBetMin={handleGetBetMin}
|
||||
activeGameIds={activeGameIds}
|
||||
handleGetActiveGameIds={handleGetActiveGameIds}
|
||||
contractBalance={contractBalance}
|
||||
handleGetContractBalance={handleGetContractBalance}
|
||||
gameDetailsId={gameDetailsId}
|
||||
setGameDetailsId={setGameDetailsId}
|
||||
gameDetails={gameDetails}
|
||||
handleGetGameDetails={handleGetGameDetails}
|
||||
myActiveGameId={myActiveGameId}
|
||||
handleGetMyActiveGameId={handleGetMyActiveGameId}
|
||||
pastGameIndex={pastGameIndex}
|
||||
setPastGameIndex={setPastGameIndex}
|
||||
pastGame={pastGame}
|
||||
handleGetPastGame={handleGetPastGame}
|
||||
pastGamesCount={pastGamesCount}
|
||||
handleGetPastGamesCount={handleGetPastGamesCount}
|
||||
whoAmI={whoAmI}
|
||||
handleWhoAmI={handleWhoAmI}
|
||||
/>
|
||||
)}
|
||||
{phase === "commit" && (
|
||||
<Commit
|
||||
playMove={playMove}
|
||||
setPlayMove={setPlayMove}
|
||||
handlePlay={handlePlay}
|
||||
loading={loading}
|
||||
account={account}
|
||||
contract={contract}
|
||||
bothPlayed={bothPlayed}
|
||||
handleBothPlayed={handleBothPlayed}
|
||||
revealTimeLeft={revealTimeLeft}
|
||||
handleRevealTimeLeft={handleRevealTimeLeft}
|
||||
/>
|
||||
)}
|
||||
{phase === "reveal" && (
|
||||
<Reveal
|
||||
revealMove={revealMove}
|
||||
setRevealMove={setRevealMove}
|
||||
handleReveal={handleReveal}
|
||||
loading={loading}
|
||||
account={account}
|
||||
contract={contract}
|
||||
bothRevealed={bothRevealed}
|
||||
handleBothRevealed={handleBothRevealed}
|
||||
playerARevealed={playerARevealed}
|
||||
handlePlayerARevealed={handlePlayerARevealed}
|
||||
playerBRevealed={playerBRevealed}
|
||||
handlePlayerBRevealed={handlePlayerBRevealed}
|
||||
revealTimeLeft={revealTimeLeft}
|
||||
handleRevealTimeLeft={handleRevealTimeLeft}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{status && (
|
||||
<div
|
||||
className={`mt-6 p-4 rounded-lg ${
|
||||
status.includes("tx sent")
|
||||
? "bg-green-50 dark:bg-green-900 text-green-800 dark:text-green-200"
|
||||
: "bg-red-50 dark:bg-red-900 text-red-800 dark:text-red-200"
|
||||
}`}
|
||||
>
|
||||
<p className="text-sm break-words">{status}</p>
|
||||
</div>
|
||||
)}
|
||||
<div className="mt-8 p-4 bg-yellow-50 dark:bg-yellow-900 rounded-lg text-sm text-yellow-800 dark:text-yellow-200">
|
||||
<p className="font-semibold mb-2">ℹ️ Note:</p>
|
||||
<ul className="list-disc list-inside space-y-1">
|
||||
<li>
|
||||
MetaMask or a compatible Web3 wallet is required for write
|
||||
operations
|
||||
</li>
|
||||
<li>
|
||||
Use bytes32 for encrypted move (see contract docs for details)
|
||||
</li>
|
||||
<li>ETH values are in Ether (not Wei)</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user