diff --git a/config.json b/config.json index 0356444..810d1ce 100644 --- a/config.json +++ b/config.json @@ -1,6 +1,6 @@ { - "API_URL": "https://rpc.hasrv.averel10.app/", - "CONTRACT_ADDRESS": "0xC3D2A1471A5e19ce586D4D3cB398Ce560efAF6Ca", + "API_URL": "http://185.48.228.49:8545", + "CONTRACT_ADDRESS": "0x375dDb60596f9587756012d95597dba54A247000", "ABI": [ { "inputs": [ @@ -58,5 +58,325 @@ "stateMutability": "nonpayable", "type": "function" } + ], + "GAME_CONTRACT_ADDRESS": "0x3209690ae3785924525453997d553624123871e6", + "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 diff --git a/crypto_clash_contract/scripts/deploy.ts b/crypto_clash_contract/scripts/deploy.ts index 7c5c03c..e335b27 100644 --- a/crypto_clash_contract/scripts/deploy.ts +++ b/crypto_clash_contract/scripts/deploy.ts @@ -10,11 +10,21 @@ dotenv.config(); const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); -async function main() { - const artifactPath = path.join( - __dirname, - "../../artifacts/contracts/testcontract.sol/HelloWorld.json" - ); +interface ContractDeploymentConfig { + name: string; + artifactPath: string; + deployArgs?: unknown[]; + configKeys: { + address: string; + abi: string; + }; +} + +async function deployContract( + signer: ethers.Signer, + config: ContractDeploymentConfig +): Promise<{ address: string; abi: unknown }> { + const artifactPath = path.join(__dirname, "../../artifacts/contracts", config.artifactPath); if (!fs.existsSync(artifactPath)) { throw new Error(`Artifact not found at ${artifactPath}. Please compile the contracts first.`); @@ -24,37 +34,69 @@ async function main() { const contractABI = artifact.abi; const contractBytecode = artifact.bytecode; + const factory = new ethers.ContractFactory(contractABI, contractBytecode, signer); + + console.log(`Deploying ${config.name} contract...`); + const deployedContract = await factory.deploy(...(config.deployArgs || [])); + await deployedContract.waitForDeployment(); + + console.log(`✓ ${config.name} deployed to:`, deployedContract.target); + + return { + address: deployedContract.target as string, + abi: contractABI + }; +} + +async function main() { // Create provider and signer const provider = new ethers.JsonRpcProvider(process.env.API_URL); const signer = new ethers.Wallet(process.env.WALLET_PRIVATE_KEY!, provider); - // Create contract factory - const HelloWorldFactory = new ethers.ContractFactory( - contractABI, - contractBytecode, - signer - ); + const config: any = {}; + const deployedContracts: any = {}; - // Deploy the contract - console.log("Deploying HelloWorld contract..."); - const hello_world = await HelloWorldFactory.deploy("Hello World!"); - - // Wait for deployment to complete - await hello_world.waitForDeployment(); - - console.log("Contract deployed to address:", hello_world.target); + // Load existing config if it exists + const configPath = path.join(__dirname, "../../../config.json"); + if (fs.existsSync(configPath)) { + Object.assign(config, JSON.parse(fs.readFileSync(configPath, "utf8"))); + } - // Save contract address to config.json - const configPath = path.join(__dirname, "../../../config.json"); - let config = {}; - if (fs.existsSync(configPath)) { - config = JSON.parse(fs.readFileSync(configPath, "utf8")); - } - (config as any).CONTRACT_ADDRESS = hello_world.target; - (config as any).API_URL = process.env.API_URL; - (config as any).ABI = contractABI; - fs.writeFileSync(configPath, JSON.stringify(config, null, 4)); - console.log("✓ Contract address saved to config.json"); + // Define contracts to deploy + const contractsToDeploy: ContractDeploymentConfig[] = [ + { + name: "HelloWorld", + artifactPath: "testcontract.sol/HelloWorld.json", + deployArgs: ["Hello World!"], + configKeys: { + address: "CONTRACT_ADDRESS", + abi: "ABI" + } + }, + { + name: "Game", + artifactPath: "Game.sol/Game.json", + deployArgs: [], + configKeys: { + address: "GAME_CONTRACT_ADDRESS", + abi: "GAME_ABI" + } + } + ]; + + // Deploy all contracts + for (const contractConfig of contractsToDeploy) { + const deployed = await deployContract(signer, contractConfig); + config[contractConfig.configKeys.address] = deployed.address; + config[contractConfig.configKeys.abi] = deployed.abi; + } + + // Save configuration + config.API_URL = process.env.API_URL; + fs.writeFileSync(configPath, JSON.stringify(config, null, 4)); + + console.log("\n✓ All contracts deployed successfully!"); + console.log("✓ Configuration saved to config.json"); } main() diff --git a/crypto_clash_frontend/app/clash/page.tsx b/crypto_clash_frontend/app/clash/page.tsx new file mode 100644 index 0000000..131f58e --- /dev/null +++ b/crypto_clash_frontend/app/clash/page.tsx @@ -0,0 +1,11 @@ +"use client"; + + +export default function Clash() { + return ( +
+

Clash Page

+

This is the Clash page content.

+
+ ); +} diff --git a/crypto_clash_frontend/app/hello_world/page.tsx b/crypto_clash_frontend/app/hello_world/page.tsx new file mode 100644 index 0000000..836bb68 --- /dev/null +++ b/crypto_clash_frontend/app/hello_world/page.tsx @@ -0,0 +1,245 @@ +"use client"; + +import { useEffect, useState } from "react"; +import Web3 from "web3"; + + + +export default function Home() { + const [config, setConfig] = useState(null); + const [web3, setWeb3] = useState(null); + const [contract, setContract] = useState(null); + const [message, setMessage] = useState(""); + const [newMessage, setNewMessage] = useState(""); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(""); + const [account, setAccount] = useState(""); + + // Initialize Web3 and contract + useEffect(() => { + const loadConfig = async () => { + try { + const response = await fetch("/crypto_clash/config.json"); + const data = await response.json(); + setConfig(data); + + // Initialize Web3 + const web3Instance = new Web3(data.API_URL); + setWeb3(web3Instance); + + // Create contract instance + const contractInstance = new web3Instance.eth.Contract( + data.ABI, + data.CONTRACT_ADDRESS + ); + setContract(contractInstance); + + // Try to get connected account (if MetaMask or similar is available) + if (typeof window !== "undefined" && (window as any).ethereum) { + try { + const accounts = await (window as any).ethereum.request({ + method: "eth_requestAccounts", + }); + setAccount(accounts[0]); + + // Get the network ID from the RPC endpoint + const networkId = await web3Instance.eth.net.getId(); + const currentChainId = await (window as any).ethereum.request({ + method: "eth_chainId", + }); + + // If on different network, notify user (they may need to switch networks manually) + console.log( + `RPC Network ID: ${networkId}, MetaMask Chain ID: ${currentChainId}` + ); + } catch (err) { + console.log("MetaMask not available or user denied access"); + } + } + + // Load initial message + await readMessage(contractInstance); + } catch (err) { + setError(`Failed to load config: ${err}`); + console.error(err); + } + }; + + loadConfig(); + }, []); + + // Read message from contract + const readMessage = async (contractInstance: any) => { + try { + setLoading(true); + setError(""); + const result = await contractInstance.methods.message().call(); + setMessage(result); + } catch (err) { + setError(`Failed to read message: ${err}`); + console.error(err); + } finally { + setLoading(false); + } + }; + + // Update message on contract + const updateMessage = async () => { + if (!newMessage.trim()) { + setError("Please enter a message"); + return; + } + + try { + setLoading(true); + setError(""); + + if (!web3 || !contract) { + throw new Error("Web3 or contract not initialized"); + } + + // Check if MetaMask is available + if (!account && typeof window !== "undefined" && !(window as any).ethereum) { + throw new Error( + "MetaMask not available. Please install MetaMask to update the message." + ); + } + + // Create transaction + const tx = contract.methods.update(newMessage); + const gas = await tx.estimateGas({ from: account }); + + console.log(await web3.eth.net.getId()); + + // Send transaction via MetaMask + const result = await (window as any).ethereum.request({ + method: "eth_sendTransaction", + params: [ + { + from: account, + to: config?.CONTRACT_ADDRESS, + data: tx.encodeABI(), + gas: web3.utils.toHex(gas), + chainId: web3.utils.toHex(await web3.eth.net.getId()), + }, + ], + }); + + + + setError(`Transaction sent: ${result}`); + setNewMessage(""); + + // Wait a bit and refresh message from the RPC endpoint + setTimeout(() => { + if (contract) { + readMessage(contract); + } + }, 2000); + } catch (err) { + setError(`Failed to update message: ${err}`); + console.error(err); + } finally { + setLoading(false); + } + }; + + return ( +
+
+
+

+ Smart Contract Interaction +

+

+ Read and update messages on the blockchain +

+ + {/* Status Section */} +
+

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

+

+ Contract Address:{" "} + {config?.CONTRACT_ADDRESS} +

+
+ + {/* Current Message Display */} +
+

+ Current Message: +

+ {loading && !message ? ( +

Loading...

+ ) : message ? ( +

+ {message} +

+ ) : ( +

+ No message yet +

+ )} +
+ + {/* Refresh Button */} + + + {/* Update Message Form */} +
+

+ Update Message: +

+ setNewMessage(e.target.value)} + placeholder="Enter new message..." + className="w-full px-4 py-3 border border-slate-300 dark:border-slate-600 rounded-lg bg-white dark:bg-slate-700 text-slate-900 dark:text-white placeholder-slate-400 dark:placeholder-slate-500 focus:outline-none focus:ring-2 focus:ring-indigo-500" + /> + +
+ + {/* Error/Status Messages */} + {error && ( +
+

{error}

+
+ )} + + {/* Info Section */} +
+

ℹ️ Note:

+
    +
  • To update messages, you need MetaMask or a compatible Web3 wallet
  • +
  • Make sure your MetaMask is connected to the correct test network
  • +
  • Reading messages is free and doesn't require a wallet
  • +
  • Updates are written to the blockchain and may take time to confirm
  • +
+
+
+
+
+ ); +} diff --git a/crypto_clash_frontend/app/layout.tsx b/crypto_clash_frontend/app/layout.tsx index f7fa87e..cb2b4e2 100644 --- a/crypto_clash_frontend/app/layout.tsx +++ b/crypto_clash_frontend/app/layout.tsx @@ -13,8 +13,8 @@ const geistMono = Geist_Mono({ }); export const metadata: Metadata = { - title: "Create Next App", - description: "Generated by create next app", + title: "Crypto Clash", + description: "Challenge yourself with competitive battles", }; export default function RootLayout({ diff --git a/crypto_clash_frontend/app/page.tsx b/crypto_clash_frontend/app/page.tsx index c740e19..66c4ab9 100644 --- a/crypto_clash_frontend/app/page.tsx +++ b/crypto_clash_frontend/app/page.tsx @@ -1,248 +1,26 @@ "use client"; -import { useEffect, useState } from "react"; -import Web3 from "web3"; - -interface Config { - API_URL: string; - CONTRACT_ADDRESS: string; - ABI: any[]; -} +import Link from "next/link"; export default function Home() { - const [config, setConfig] = useState(null); - const [web3, setWeb3] = useState(null); - const [contract, setContract] = useState(null); - const [message, setMessage] = useState(""); - const [newMessage, setNewMessage] = useState(""); - const [loading, setLoading] = useState(false); - const [error, setError] = useState(""); - const [account, setAccount] = useState(""); - - // Initialize Web3 and contract - useEffect(() => { - const loadConfig = async () => { - try { - const response = await fetch("/crypto_clash/config.json"); - const data = await response.json(); - setConfig(data); - - // Initialize Web3 - const web3Instance = new Web3(data.API_URL); - setWeb3(web3Instance); - - // Create contract instance - const contractInstance = new web3Instance.eth.Contract( - data.ABI, - data.CONTRACT_ADDRESS - ); - setContract(contractInstance); - - // Try to get connected account (if MetaMask or similar is available) - if (typeof window !== "undefined" && (window as any).ethereum) { - try { - const accounts = await (window as any).ethereum.request({ - method: "eth_requestAccounts", - }); - setAccount(accounts[0]); - - // Get the network ID from the RPC endpoint - const networkId = await web3Instance.eth.net.getId(); - const currentChainId = await (window as any).ethereum.request({ - method: "eth_chainId", - }); - - // If on different network, notify user (they may need to switch networks manually) - console.log( - `RPC Network ID: ${networkId}, MetaMask Chain ID: ${currentChainId}` - ); - } catch (err) { - console.log("MetaMask not available or user denied access"); - } - } - - // Load initial message - await readMessage(contractInstance); - } catch (err) { - setError(`Failed to load config: ${err}`); - console.error(err); - } - }; - - loadConfig(); - }, []); - - // Read message from contract - const readMessage = async (contractInstance: any) => { - try { - setLoading(true); - setError(""); - const result = await contractInstance.methods.message().call(); - setMessage(result); - } catch (err) { - setError(`Failed to read message: ${err}`); - console.error(err); - } finally { - setLoading(false); - } - }; - - // Update message on contract - const updateMessage = async () => { - if (!newMessage.trim()) { - setError("Please enter a message"); - return; - } - - try { - setLoading(true); - setError(""); - - if (!web3 || !contract) { - throw new Error("Web3 or contract not initialized"); - } - - // Check if MetaMask is available - if (!account && typeof window !== "undefined" && !(window as any).ethereum) { - throw new Error( - "MetaMask not available. Please install MetaMask to update the message." - ); - } - - // Create transaction - const tx = contract.methods.update(newMessage); - const gas = await tx.estimateGas({ from: account }); - - console.log(await web3.eth.net.getId()); - - // Send transaction via MetaMask - const result = await (window as any).ethereum.request({ - method: "eth_sendTransaction", - params: [ - { - from: account, - to: config?.CONTRACT_ADDRESS, - data: tx.encodeABI(), - gas: web3.utils.toHex(gas), - chainId: web3.utils.toHex(await web3.eth.net.getId()), - }, - ], - }); - - - - setError(`Transaction sent: ${result}`); - setNewMessage(""); - - // Wait a bit and refresh message from the RPC endpoint - setTimeout(() => { - if (contract) { - readMessage(contract); - } - }, 2000); - } catch (err) { - setError(`Failed to update message: ${err}`); - console.error(err); - } finally { - setLoading(false); - } - }; - return ( -
-
-
-

- Smart Contract Interaction -

-

- Read and update messages on the blockchain -

+
+
+

Crypto Clash

+

Welcome! Choose a page below:

- {/* Status Section */} -
-

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

-

- Contract Address:{" "} - {config?.CONTRACT_ADDRESS} -

-
- - {/* Current Message Display */} -
-

- Current Message: -

- {loading && !message ? ( -

Loading...

- ) : message ? ( -

- {message} -

- ) : ( -

- No message yet -

- )} -
- - {/* Refresh Button */} - - - {/* Update Message Form */} -
-

- Update Message: -

- setNewMessage(e.target.value)} - placeholder="Enter new message..." - className="w-full px-4 py-3 border border-slate-300 dark:border-slate-600 rounded-lg bg-white dark:bg-slate-700 text-slate-900 dark:text-white placeholder-slate-400 dark:placeholder-slate-500 focus:outline-none focus:ring-2 focus:ring-indigo-500" - /> - -
- - {/* Error/Status Messages */} - {error && ( -
-

{error}

-
- )} - - {/* Info Section */} -
-

ℹ️ Note:

-
    -
  • To update messages, you need MetaMask or a compatible Web3 wallet
  • -
  • Make sure your MetaMask is connected to the correct test network
  • -
  • Reading messages is free and doesn't require a wallet
  • -
  • Updates are written to the blockchain and may take time to confirm
  • -
-
-
+
    +
  • + + Crypto Clash - Battle with others + +
  • +
  • + + Hello World - A simple introduction + +
  • +
); diff --git a/crypto_clash_frontend/public/config.json b/crypto_clash_frontend/public/config.json index c598b44..0356444 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": "0x62A3a88E7D5021AD960426206BD05Ef09eee02a0", + "API_URL": "https://rpc.hasrv.averel10.app/", + "CONTRACT_ADDRESS": "0xC3D2A1471A5e19ce586D4D3cB398Ce560efAF6Ca", "ABI": [ { "inputs": [ diff --git a/crypto_clash_frontend/types.ts b/crypto_clash_frontend/types.ts new file mode 100644 index 0000000..82d6898 --- /dev/null +++ b/crypto_clash_frontend/types.ts @@ -0,0 +1,7 @@ +interface Config { + API_URL: string; + CONTRACT_ADDRESS: string; + ABI: any[]; + GAME_CONTRACT_ADDRESS: string; + GAME_ABI: any[]; +} \ No newline at end of file