Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 16 additions & 13 deletions app/agreement/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@

import { useParams, useRouter } from "next/navigation";
import { useState, useEffect, useRef } from "react";
import { useAccount, useReadContract, useWriteContract, useWaitForTransactionReceipt, useConfig, useBalance } from "wagmi";
import { useAccount, useReadContract, useWriteContract, useWaitForTransactionReceipt, useConfig, useBalance, useChainId } from "wagmi";
import { baseSepolia } from "wagmi/chains";
import { parseEther, formatEther } from "viem";
import { readContract } from "@wagmi/core";
import { AGREEMENT_FACTORY_ADDRESS, AGREEMENT_FACTORY_ABI } from "@/lib/agreementFactoryABI";
import { getAgreementFactoryAddress, AGREEMENT_FACTORY_ABI } from "@/lib/agreementFactoryABI";
import { BettingOptions } from "@/components/BettingOptions";
import { CountdownTimer } from "@/components/CountdownTimer";
import { ResultSection } from "@/components/ResultSection";
Expand All @@ -26,7 +26,8 @@ export default function AgreementDetailPage() {
const contractId = parseInt(params.id as string);

const { isConnected, address } = useAccount();
const { data: balanceData } = useBalance({ address, chainId: baseSepolia.id, query: { enabled: !!address } });
const chainId = useChainId();
const { data: balanceData } = useBalance({ address, chainId, query: { enabled: !!address } });
const { isCorrectChain, isSwitching, needsSwitch, ensureCorrectChain } = useEnsureChain(); // Auto switch to Base Sepolia
// Toasts removed
const [betError, setBetError] = useState<{ title: string; details: string[] } | null>(null);
Expand Down Expand Up @@ -58,23 +59,23 @@ export default function AgreementDetailPage() {

// Contract data
const { data: contractData, refetch: refetchContract } = useReadContract({
address: AGREEMENT_FACTORY_ADDRESS as `0x${string}`,
address: getAgreementFactoryAddress(chainId) as `0x${string}`,
abi: AGREEMENT_FACTORY_ABI,
functionName: "getContract",
args: [BigInt(contractId)],
});

// Comments data
const { data: commentsData, refetch: refetchComments } = useReadContract({
address: AGREEMENT_FACTORY_ADDRESS as `0x${string}`,
address: getAgreementFactoryAddress(chainId) as `0x${string}`,
abi: AGREEMENT_FACTORY_ABI,
functionName: "getComments",
args: [BigInt(contractId), BigInt(0), BigInt(50)],
});

// Check if user has bet
const { data: hasUserBet, refetch: refetchUserBet } = useReadContract({
address: AGREEMENT_FACTORY_ADDRESS as `0x${string}`,
address: getAgreementFactoryAddress(chainId) as `0x${string}`,
abi: AGREEMENT_FACTORY_ABI,
functionName: "hasUserBet",
args: address ? [BigInt(contractId), address] : undefined,
Expand Down Expand Up @@ -111,7 +112,7 @@ export default function AgreementDetailPage() {
for (const commenter of uniqueCommenters) {
try {
const result = await readContract(config, {
address: AGREEMENT_FACTORY_ADDRESS as `0x${string}`,
address: getAgreementFactoryAddress(chainId) as `0x${string}`,
abi: AGREEMENT_FACTORY_ABI,
functionName: "getUserBetsPaginated",
args: [BigInt(contractId), commenter as `0x${string}`, BigInt(0), BigInt(1)],
Expand Down Expand Up @@ -183,7 +184,7 @@ export default function AgreementDetailPage() {
try {
if (!address) return;
const result = await readContract(config, {
address: AGREEMENT_FACTORY_ADDRESS as `0x${string}`,
address: getAgreementFactoryAddress(chainId) as `0x${string}`,
abi: AGREEMENT_FACTORY_ABI,
functionName: "getUserBetsPaginated",
args: [BigInt(contractId), address as `0x${string}`, BigInt(0), BigInt(1)],
Expand Down Expand Up @@ -355,12 +356,12 @@ export default function AgreementDetailPage() {
});

await writeContract({
address: AGREEMENT_FACTORY_ADDRESS as `0x${string}`,
address: getAgreementFactoryAddress(chainId) as `0x${string}`,
abi: AGREEMENT_FACTORY_ABI,
functionName: "simpleBet",
args: [BigInt(contractId), selectedSide],
value: amount,
chainId: baseSepolia.id,
chainId,
});
} catch (err) {

Expand Down Expand Up @@ -431,11 +432,11 @@ export default function AgreementDetailPage() {
status: (contract?.status === 0 ? "open" : contract?.status === 1 ? "closed" : "resolved"),
});
await writeContract({
address: AGREEMENT_FACTORY_ADDRESS as `0x${string}`,
address: getAgreementFactoryAddress(chainId) as `0x${string}`,
abi: AGREEMENT_FACTORY_ABI,
functionName: "addComment",
args: [BigInt(contractId), comment.trim()],
chainId: baseSepolia.id,
chainId,
});
} catch (err) {
console.error("Error adding comment:", err);
Expand Down Expand Up @@ -559,7 +560,7 @@ export default function AgreementDetailPage() {
contractId,
endedAt: new Date().toISOString(),
bettingEndTime: Number(contract.bettingEndTime ?? 0),
chainId: baseSepolia.id,
chainId,
}),
cache: 'no-store',
keepalive: true,
Expand Down Expand Up @@ -727,6 +728,7 @@ export default function AgreementDetailPage() {
comment={comment}
setComment={setComment}
onSubmitComment={handleComment}
chainId={chainId}
onBetClick={() => {
trackBetEvent(EVENTS.TRANSACTION_INITIATED, {
debate_id: String(contractId),
Expand Down Expand Up @@ -765,6 +767,7 @@ export default function AgreementDetailPage() {
onBet={handleBet}
isPending={isPending}
isConfirming={isConfirming}
chainId={chainId}
isCorrectChain={isCorrectChain}
isSwitching={isSwitching}
needsSwitch={needsSwitch}
Expand Down
9 changes: 5 additions & 4 deletions app/components/AgreementList.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
"use client";

import { useRouter } from "next/navigation";
import { useReadContract, useReadContracts } from "wagmi";
import { AGREEMENT_FACTORY_ADDRESS, AGREEMENT_FACTORY_ABI } from "@/lib/agreementFactoryABI";
import { useReadContract, useReadContracts, useChainId } from "wagmi";
import { getAgreementFactoryAddress, AGREEMENT_FACTORY_ABI } from "@/lib/agreementFactoryABI";

interface AgreementContract {
id: number;
Expand Down Expand Up @@ -145,9 +145,10 @@ function AgreementCard({ contract, onSelect }: AgreementCardProps) {

export function AgreementList() {
const router = useRouter();
const chainId = useChainId();

const { data: contractCounter } = useReadContract({
address: AGREEMENT_FACTORY_ADDRESS as `0x${string}`,
address: getAgreementFactoryAddress(chainId) as `0x${string}`,
abi: AGREEMENT_FACTORY_ABI,
functionName: "contractCounter",
});
Expand All @@ -159,7 +160,7 @@ export function AgreementList() {
const contractReads = [];
for (let i = startIndex; i < count; i++) {
contractReads.push({
address: AGREEMENT_FACTORY_ADDRESS as `0x${string}`,
address: getAgreementFactoryAddress(chainId) as `0x${string}`,
abi: AGREEMENT_FACTORY_ABI,
functionName: 'getContract' as const,
args: [BigInt(i)],
Expand Down
109 changes: 93 additions & 16 deletions app/components/CreateAgreement.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,46 @@
"use client";

import { useState, useCallback, useEffect } from "react";
import { useAccount, useWriteContract, useWaitForTransactionReceipt } from "wagmi";
import { useAccount, useWriteContract, useWaitForTransactionReceipt, useChainId, useConfig, useReconnect } from "wagmi";
import { getChainId } from "wagmi/actions";
import { baseSepolia } from "wagmi/chains";
import { parseEther } from "viem";
import { AGREEMENT_FACTORY_ADDRESS, AGREEMENT_FACTORY_ABI } from "@/lib/agreementFactoryABI";
import { getAgreementFactoryAddress, AGREEMENT_FACTORY_ABI } from "@/lib/agreementFactoryABI";
import { useRouter } from "next/navigation";
import { useAnalytics } from "@/lib/hooks/useAnalytics";
import { EVENTS } from "@/lib/analytics";
import { useEnsureChain } from "@/lib/hooks/useEnsureChain";
import { monadTestnet } from "@/lib/utils/customChains";
// Toasts removed

export function CreateAgreement() {
const { address, isConnected } = useAccount();
const { address, isConnected, chain } = useAccount();
const chainId = useChainId();
const config = useConfig();
const { reconnect } = useReconnect();
const { isCorrectChain, isSwitching, needsSwitch, ensureCorrectChain } = useEnsureChain(); // Auto switch to Base Sepolia

// Debug logging
console.log("useAccount chain:", chain?.id);
console.log("useChainId:", chainId);

// Check for potential state mismatch early
let configChainId;
try {
configChainId = getChainId(config);
console.log("Early config chainId check:", configChainId);
} catch (error) {
console.log("Early config chainId error:", error);
configChainId = chainId;
}

// Use the actual connected chain ID from the account, fallback to useChainId()
const actualChainId = chain?.id ?? chainId;

// Detect mismatch early and show warning
const hasChainMismatch = configChainId !== actualChainId;
console.log("Chain mismatch detected:", hasChainMismatch, "Config:", configChainId, "Hook:", actualChainId);

// Toasts removed
const router = useRouter();
const [topic, setTopic] = useState("");
Expand All @@ -25,6 +52,9 @@ export function CreateAgreement() {
const [minBet, setMinBet] = useState<string>("0.0002");
const [maxBet, setMaxBet] = useState<string>("0.1");
// Betting end time input removed; default duration is fixed (24h)

// Determine currency symbol based on chain
const currencySymbol = actualChainId === monadTestnet.id ? "MON" : "ETH";

useEffect(() => {
// Short delay to ensure smooth transition
Expand Down Expand Up @@ -144,23 +174,23 @@ export function CreateAgreement() {
return;
}
if (minWei < MIN_ALLOWED) {
alert(`Minimum bet must be at least 0.0002 ETH`);
alert(`Minimum bet must be at least 0.0002 ${currencySymbol}`);
return;
}
if (maxWei <= BigInt(0)) {
alert("Maximum bet must be greater than 0");
return;
}
if (maxWei > MAX_ALLOWED) {
alert(`Maximum bet cannot exceed 100 ETH`);
alert(`Maximum bet cannot exceed 100 ${currencySymbol}`);
return;
}
if (minWei > maxWei) {
alert("Maximum bet must be greater than or equal to minimum bet");
return;
}
} catch {
alert("Please enter valid ETH amounts");
alert(`Please enter valid ${currencySymbol} amounts`);
return;
}

Expand All @@ -172,10 +202,42 @@ export function CreateAgreement() {
const ok = await ensureCorrectChain();
if (!ok) { return; }

console.log("Current chainId from useChainId:", chainId);
console.log("Current chainId from useAccount:", chain?.id);
console.log("Using actualChainId:", actualChainId);
console.log("Contract address:", getAgreementFactoryAddress(actualChainId));

// Double-check the actual chain from config
let configChainId;
try {
configChainId = getChainId(config);
console.log("Config chainId:", configChainId);
} catch (error) {
console.error("Failed to get config chain ID:", error);
configChainId = actualChainId;
}

// If there's a mismatch between wagmi state and config, show error and ask user to refresh
if (configChainId && configChainId !== actualChainId) {
console.log("Chain mismatch detected! Config:", configChainId, "vs Hook:", actualChainId);

alert(`Network sync issue detected.\n\nWallet connector: Chain ${configChainId}\nApp state: Chain ${actualChainId}\n\nPlease refresh the page to sync your wallet properly.`);
return;
}

// Use the actual chain ID from the wallet for consistency
const transactionChainId = actualChainId;

// Track create button click (page view again optional)
trackPageView('create_submit');

console.log("Transaction details:", {
address: getAgreementFactoryAddress(transactionChainId),
chainId: transactionChainId
});

await writeContract({
address: AGREEMENT_FACTORY_ADDRESS as `0x${string}`,
address: getAgreementFactoryAddress(transactionChainId) as `0x${string}`,
abi: AGREEMENT_FACTORY_ABI,
functionName: "createContract",
args: [
Expand All @@ -187,7 +249,7 @@ export function CreateAgreement() {
minWei, // User-defined min bet (validated)
maxWei // User-defined max bet (validated)
],
chainId: baseSepolia.id,
chainId: transactionChainId,
});
} catch (err) {
// Check if user rejected the transaction
Expand Down Expand Up @@ -333,7 +395,7 @@ export function CreateAgreement() {
<label className="block text-white text-lg font-medium mb-2">Bet Limits</label>
<div className="grid grid-cols-2 gap-3">
<div>
<label className="block text-gray-300 text-xs mb-1">Min Bet (ETH)</label>
<label className="block text-gray-300 text-xs mb-1">Min Bet ({currencySymbol})</label>
<input
type="number"
value={minBet}
Expand All @@ -353,7 +415,7 @@ export function CreateAgreement() {
/>
</div>
<div>
<label className="block text-gray-300 text-xs mb-1">Max Bet (ETH)</label>
<label className="block text-gray-300 text-xs mb-1">Max Bet ({currencySymbol})</label>
<input
type="number"
value={maxBet}
Expand Down Expand Up @@ -383,13 +445,13 @@ export function CreateAgreement() {
const MIN_ALLOWED = parseEther("0.0002");
const MAX_ALLOWED = parseEther("100");

if (minV < MIN_ALLOWED) return <p className="text-red-400 text-sm mt-2">Min bet must be at least 0.0002 ETH.</p>;
if (minV < MIN_ALLOWED) return <p className="text-red-400 text-sm mt-2">Min bet must be at least 0.0002 {currencySymbol}.</p>;
if (maxV <= BigInt(0)) return <p className="text-red-400 text-sm mt-2">Max bet must be greater than 0.</p>;
if (maxV > MAX_ALLOWED) return <p className="text-red-400 text-sm mt-2">Max bet cannot exceed 100 ETH.</p>;
if (maxV < minV) return <p className="text-red-400 text-sm mt-2">Max bet must be at least {minBet} ETH.</p>;
if (maxV > MAX_ALLOWED) return <p className="text-red-400 text-sm mt-2">Max bet cannot exceed 100 {currencySymbol}.</p>;
if (maxV < minV) return <p className="text-red-400 text-sm mt-2">Max bet must be at least {minBet} {currencySymbol}.</p>;
return null;
} catch {
return <p className="text-red-400 text-sm mt-2">Enter valid ETH amounts (e.g., 0.001).</p>;
return <p className="text-red-400 text-sm mt-2">Enter valid {currencySymbol} amounts (e.g., 0.001).</p>;
}
})()}
</div>
Expand All @@ -400,7 +462,7 @@ export function CreateAgreement() {
<button
onClick={handleCreateContract}
disabled={(() => {
if (!isConnected || isPending || isConfirming || isSwitching || !isCorrectChain || !topic.trim() || !description.trim() || !partyA.trim() || !partyB.trim()) return true;
if (!isConnected || isPending || isConfirming || isSwitching || !isCorrectChain || hasChainMismatch || !topic.trim() || !description.trim() || !partyA.trim() || !partyB.trim()) return true;
try {
const minV = parseEther((minBet || '').trim());
const maxV = parseEther((maxBet || '').trim());
Expand All @@ -424,7 +486,22 @@ export function CreateAgreement() {

{needsSwitch && (
<div className="mt-2 text-center">
<p className="text-yellow-400 text-sm">Please switch to Base Sepolia network first</p>
<p className="text-yellow-400 text-sm">Please switch to a supported network first</p>
</div>
)}

{hasChainMismatch && (
<div className="mt-2 text-center p-3 bg-red-900/20 border border-red-700 rounded-lg">
<p className="text-red-400 text-sm mb-2">Network sync issue detected!</p>
<p className="text-red-300 text-xs mb-2">
Wallet: Chain {configChainId} • App: Chain {actualChainId}
</p>
<button
onClick={() => window.location.reload()}
className="bg-red-600 hover:bg-red-700 text-white text-xs px-3 py-1 rounded"
>
Refresh Page
</button>
</div>
)}
</div>
Expand Down
Loading