added subpages for helloworld and clash, added game contract to config.json for usage in frontend

This commit is contained in:
averel10
2025-11-14 22:49:06 +01:00
parent d3b512aa95
commit 15bf0155bb
8 changed files with 678 additions and 275 deletions

View File

@@ -1,6 +1,6 @@
{ {
"API_URL": "https://rpc.hasrv.averel10.app/", "API_URL": "http://185.48.228.49:8545",
"CONTRACT_ADDRESS": "0xC3D2A1471A5e19ce586D4D3cB398Ce560efAF6Ca", "CONTRACT_ADDRESS": "0x375dDb60596f9587756012d95597dba54A247000",
"ABI": [ "ABI": [
{ {
"inputs": [ "inputs": [
@@ -58,5 +58,325 @@
"stateMutability": "nonpayable", "stateMutability": "nonpayable",
"type": "function" "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"
}
] ]
} }

View File

@@ -10,11 +10,21 @@ dotenv.config();
const __filename = fileURLToPath(import.meta.url); const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename); const __dirname = path.dirname(__filename);
async function main() { interface ContractDeploymentConfig {
const artifactPath = path.join( name: string;
__dirname, artifactPath: string;
"../../artifacts/contracts/testcontract.sol/HelloWorld.json" 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)) { if (!fs.existsSync(artifactPath)) {
throw new Error(`Artifact not found at ${artifactPath}. Please compile the contracts first.`); 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 contractABI = artifact.abi;
const contractBytecode = artifact.bytecode; 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 // Create provider and signer
const provider = new ethers.JsonRpcProvider(process.env.API_URL); const provider = new ethers.JsonRpcProvider(process.env.API_URL);
const signer = new ethers.Wallet(process.env.WALLET_PRIVATE_KEY!, provider); const signer = new ethers.Wallet(process.env.WALLET_PRIVATE_KEY!, provider);
// Create contract factory const config: any = {};
const HelloWorldFactory = new ethers.ContractFactory( const deployedContracts: any = {};
contractABI,
contractBytecode,
signer
);
// Deploy the contract // Load existing config if it exists
console.log("Deploying HelloWorld contract..."); const configPath = path.join(__dirname, "../../../config.json");
const hello_world = await HelloWorldFactory.deploy("Hello World!"); if (fs.existsSync(configPath)) {
Object.assign(config, JSON.parse(fs.readFileSync(configPath, "utf8")));
}
// Wait for deployment to complete // Define contracts to deploy
await hello_world.waitForDeployment(); 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"
}
}
];
console.log("Contract deployed to address:", hello_world.target); // 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 contract address to config.json // Save configuration
const configPath = path.join(__dirname, "../../../config.json"); config.API_URL = process.env.API_URL;
let config = {}; fs.writeFileSync(configPath, JSON.stringify(config, null, 4));
if (fs.existsSync(configPath)) {
config = JSON.parse(fs.readFileSync(configPath, "utf8")); console.log("\n✓ All contracts deployed successfully!");
} console.log("✓ Configuration saved to config.json");
(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");
} }
main() main()

View 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>
);
}

View 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>
);
}

View File

@@ -13,8 +13,8 @@ const geistMono = Geist_Mono({
}); });
export const metadata: Metadata = { export const metadata: Metadata = {
title: "Create Next App", title: "Crypto Clash",
description: "Generated by create next app", description: "Challenge yourself with competitive battles",
}; };
export default function RootLayout({ export default function RootLayout({

View File

@@ -1,248 +1,26 @@
"use client"; "use client";
import { useEffect, useState } from "react"; import Link from "next/link";
import Web3 from "web3";
interface Config {
API_URL: string;
CONTRACT_ADDRESS: string;
ABI: any[];
}
export default function Home() { 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 ( 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"> <div className="max-w-4xl mx-auto p-6">
<main className="w-full max-w-2xl mx-auto py-12 px-6"> <main>
<div className="bg-white dark:bg-slate-800 rounded-lg shadow-lg p-8"> <h1 className="text-4xl font-bold mb-3">Crypto Clash</h1>
<h1 className="text-4xl font-bold text-center mb-2 text-slate-900 dark:text-white"> <p className="text-lg text-gray-700 mb-8">Welcome! Choose a page below:</p>
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 */} <ul className="space-y-3">
<div className="mb-8 p-4 bg-slate-100 dark:bg-slate-700 rounded-lg"> <li>
<p className="text-sm text-slate-600 dark:text-slate-300"> <Link href="/clash" className="text-blue-600 hover:bg-gray-100 hover:text-blue-800 px-2 py-1 inline-block">
<span className="font-semibold">Connected Account:</span>{" "} Crypto Clash - Battle with others
{account ? `${account.slice(0, 6)}...${account.slice(-4)}` : "Not connected"} </Link>
</p> </li>
<p className="text-sm text-slate-600 dark:text-slate-300 mt-2"> <li>
<span className="font-semibold">Contract Address:</span>{" "} <Link href="/hello_world" className="text-blue-600 hover:bg-gray-100 hover:text-blue-800 px-2 py-1 inline-block">
{config?.CONTRACT_ADDRESS} Hello World - A simple introduction
</p> </Link>
</div> </li>
</ul>
{/* 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> </main>
</div> </div>
); );

View File

@@ -1,6 +1,6 @@
{ {
"API_URL": "https://rpc.hasrv.averel10.app", "API_URL": "https://rpc.hasrv.averel10.app/",
"CONTRACT_ADDRESS": "0x62A3a88E7D5021AD960426206BD05Ef09eee02a0", "CONTRACT_ADDRESS": "0xC3D2A1471A5e19ce586D4D3cB398Ce560efAF6Ca",
"ABI": [ "ABI": [
{ {
"inputs": [ "inputs": [

View File

@@ -0,0 +1,7 @@
interface Config {
API_URL: string;
CONTRACT_ADDRESS: string;
ABI: any[];
GAME_CONTRACT_ADDRESS: string;
GAME_ABI: any[];
}