mirror of
https://github.com/averel10/crypto_clash.git
synced 2026-03-12 10:58:11 +01:00
added subpages for helloworld and clash, added game contract to config.json for usage in frontend
This commit is contained in:
324
config.json
324
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"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
// Save contract address to config.json
|
||||
// Load existing config if it exists
|
||||
const configPath = path.join(__dirname, "../../../config.json");
|
||||
let config = {};
|
||||
if (fs.existsSync(configPath)) {
|
||||
config = JSON.parse(fs.readFileSync(configPath, "utf8"));
|
||||
Object.assign(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;
|
||||
|
||||
// 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("✓ Contract address saved to config.json");
|
||||
|
||||
console.log("\n✓ All contracts deployed successfully!");
|
||||
console.log("✓ Configuration saved to config.json");
|
||||
}
|
||||
|
||||
main()
|
||||
|
||||
11
crypto_clash_frontend/app/clash/page.tsx
Normal file
11
crypto_clash_frontend/app/clash/page.tsx
Normal file
@@ -0,0 +1,11 @@
|
||||
"use client";
|
||||
|
||||
|
||||
export default function Clash() {
|
||||
return (
|
||||
<div>
|
||||
<h1>Clash Page</h1>
|
||||
<p>This is the Clash page content.</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
245
crypto_clash_frontend/app/hello_world/page.tsx
Normal file
245
crypto_clash_frontend/app/hello_world/page.tsx
Normal file
@@ -0,0 +1,245 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import Web3 from "web3";
|
||||
|
||||
|
||||
|
||||
export default function Home() {
|
||||
const [config, setConfig] = useState<Config | null>(null);
|
||||
const [web3, setWeb3] = useState<Web3 | null>(null);
|
||||
const [contract, setContract] = useState<any>(null);
|
||||
const [message, setMessage] = useState<string>("");
|
||||
const [newMessage, setNewMessage] = useState<string>("");
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string>("");
|
||||
const [account, setAccount] = useState<string>("");
|
||||
|
||||
// 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 (
|
||||
<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-2xl 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">
|
||||
Smart Contract Interaction
|
||||
</h1>
|
||||
<p className="text-center text-slate-600 dark:text-slate-300 mb-8">
|
||||
Read and update messages on the blockchain
|
||||
</p>
|
||||
|
||||
{/* Status Section */}
|
||||
<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">Contract Address:</span>{" "}
|
||||
{config?.CONTRACT_ADDRESS}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Current Message Display */}
|
||||
<div className="mb-8 p-6 bg-indigo-50 dark:bg-slate-700 rounded-lg border-2 border-indigo-200 dark:border-slate-600">
|
||||
<h2 className="text-lg font-semibold text-slate-900 dark:text-white mb-2">
|
||||
Current Message:
|
||||
</h2>
|
||||
{loading && !message ? (
|
||||
<p className="text-slate-600 dark:text-slate-300 italic">Loading...</p>
|
||||
) : message ? (
|
||||
<p className="text-xl text-indigo-900 dark:text-indigo-200 break-words">
|
||||
{message}
|
||||
</p>
|
||||
) : (
|
||||
<p className="text-slate-500 dark:text-slate-400 italic">
|
||||
No message yet
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Refresh Button */}
|
||||
<button
|
||||
onClick={() => readMessage(contract)}
|
||||
disabled={loading || !contract}
|
||||
className="w-full mb-6 py-3 px-4 bg-blue-500 hover:bg-blue-600 disabled:bg-slate-400 text-white font-semibold rounded-lg transition-colors"
|
||||
>
|
||||
{loading ? "Loading..." : "Refresh Message"}
|
||||
</button>
|
||||
|
||||
{/* Update Message Form */}
|
||||
<div className="space-y-4">
|
||||
<h2 className="text-lg font-semibold text-slate-900 dark:text-white">
|
||||
Update Message:
|
||||
</h2>
|
||||
<input
|
||||
type="text"
|
||||
value={newMessage}
|
||||
onChange={(e) => 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"
|
||||
/>
|
||||
<button
|
||||
onClick={updateMessage}
|
||||
disabled={loading || !account || !contract}
|
||||
className="w-full py-3 px-4 bg-green-500 hover:bg-green-600 disabled:bg-slate-400 text-white font-semibold rounded-lg transition-colors"
|
||||
>
|
||||
{loading ? "Updating..." : "Update Message"}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Error/Status Messages */}
|
||||
{error && (
|
||||
<div
|
||||
className={`mt-6 p-4 rounded-lg ${
|
||||
error.includes("Transaction 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">{error}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Info Section */}
|
||||
<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>To update messages, you need MetaMask or a compatible Web3 wallet</li>
|
||||
<li>Make sure your MetaMask is connected to the correct test network</li>
|
||||
<li>Reading messages is free and doesn't require a wallet</li>
|
||||
<li>Updates are written to the blockchain and may take time to confirm</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -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({
|
||||
|
||||
@@ -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<Config | null>(null);
|
||||
const [web3, setWeb3] = useState<Web3 | null>(null);
|
||||
const [contract, setContract] = useState<any>(null);
|
||||
const [message, setMessage] = useState<string>("");
|
||||
const [newMessage, setNewMessage] = useState<string>("");
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string>("");
|
||||
const [account, setAccount] = useState<string>("");
|
||||
|
||||
// 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 (
|
||||
<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-2xl 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">
|
||||
Smart Contract Interaction
|
||||
</h1>
|
||||
<p className="text-center text-slate-600 dark:text-slate-300 mb-8">
|
||||
Read and update messages on the blockchain
|
||||
</p>
|
||||
<div className="max-w-4xl mx-auto p-6">
|
||||
<main>
|
||||
<h1 className="text-4xl font-bold mb-3">Crypto Clash</h1>
|
||||
<p className="text-lg text-gray-700 mb-8">Welcome! Choose a page below:</p>
|
||||
|
||||
{/* Status Section */}
|
||||
<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">Contract Address:</span>{" "}
|
||||
{config?.CONTRACT_ADDRESS}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Current Message Display */}
|
||||
<div className="mb-8 p-6 bg-indigo-50 dark:bg-slate-700 rounded-lg border-2 border-indigo-200 dark:border-slate-600">
|
||||
<h2 className="text-lg font-semibold text-slate-900 dark:text-white mb-2">
|
||||
Current Message:
|
||||
</h2>
|
||||
{loading && !message ? (
|
||||
<p className="text-slate-600 dark:text-slate-300 italic">Loading...</p>
|
||||
) : message ? (
|
||||
<p className="text-xl text-indigo-900 dark:text-indigo-200 break-words">
|
||||
{message}
|
||||
</p>
|
||||
) : (
|
||||
<p className="text-slate-500 dark:text-slate-400 italic">
|
||||
No message yet
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Refresh Button */}
|
||||
<button
|
||||
onClick={() => readMessage(contract)}
|
||||
disabled={loading || !contract}
|
||||
className="w-full mb-6 py-3 px-4 bg-blue-500 hover:bg-blue-600 disabled:bg-slate-400 text-white font-semibold rounded-lg transition-colors"
|
||||
>
|
||||
{loading ? "Loading..." : "Refresh Message"}
|
||||
</button>
|
||||
|
||||
{/* Update Message Form */}
|
||||
<div className="space-y-4">
|
||||
<h2 className="text-lg font-semibold text-slate-900 dark:text-white">
|
||||
Update Message:
|
||||
</h2>
|
||||
<input
|
||||
type="text"
|
||||
value={newMessage}
|
||||
onChange={(e) => 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"
|
||||
/>
|
||||
<button
|
||||
onClick={updateMessage}
|
||||
disabled={loading || !account || !contract}
|
||||
className="w-full py-3 px-4 bg-green-500 hover:bg-green-600 disabled:bg-slate-400 text-white font-semibold rounded-lg transition-colors"
|
||||
>
|
||||
{loading ? "Updating..." : "Update Message"}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Error/Status Messages */}
|
||||
{error && (
|
||||
<div
|
||||
className={`mt-6 p-4 rounded-lg ${
|
||||
error.includes("Transaction 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">{error}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Info Section */}
|
||||
<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>To update messages, you need MetaMask or a compatible Web3 wallet</li>
|
||||
<li>Make sure your MetaMask is connected to the correct test network</li>
|
||||
<li>Reading messages is free and doesn't require a wallet</li>
|
||||
<li>Updates are written to the blockchain and may take time to confirm</li>
|
||||
<ul className="space-y-3">
|
||||
<li>
|
||||
<Link href="/clash" className="text-blue-600 hover:bg-gray-100 hover:text-blue-800 px-2 py-1 inline-block">
|
||||
Crypto Clash - Battle with others
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link href="/hello_world" className="text-blue-600 hover:bg-gray-100 hover:text-blue-800 px-2 py-1 inline-block">
|
||||
Hello World - A simple introduction
|
||||
</Link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -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": [
|
||||
|
||||
7
crypto_clash_frontend/types.ts
Normal file
7
crypto_clash_frontend/types.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
interface Config {
|
||||
API_URL: string;
|
||||
CONTRACT_ADDRESS: string;
|
||||
ABI: any[];
|
||||
GAME_CONTRACT_ADDRESS: string;
|
||||
GAME_ABI: any[];
|
||||
}
|
||||
Reference in New Issue
Block a user