Files
sre-06-oracles/packages/nextjs/components/oracle/optimistic/AssertionModal.tsx
2026-01-23 20:20:58 +07:00

261 lines
8.8 KiB
TypeScript

"use client";
import { useState } from "react";
import { AssertionWithIdAndState } from "../types";
import { Address } from "@scaffold-ui/components";
import { formatEther } from "viem";
import { useScaffoldWriteContract } from "~~/hooks/scaffold-eth";
import { useChallengeState } from "~~/services/store/challengeStore";
import { ZERO_ADDRESS } from "~~/utils/scaffold-eth/common";
const getStateName = (state: number) => {
switch (state) {
case 0:
return "Invalid";
case 1:
return "Asserted";
case 2:
return "Proposed";
case 3:
return "Disputed";
case 4:
return "Settled";
case 5:
return "Expired";
default:
return "Invalid";
}
};
// Helper function to format timestamp to UTC
const formatTimestamp = (timestamp: bigint | string | number) => {
const timestampNumber = Number(timestamp);
const date = new Date(timestampNumber * 1000); // Convert from seconds to milliseconds
return date.toLocaleString();
};
const Description = ({ assertion }: { assertion: AssertionWithIdAndState }) => {
return (
<div className="bg-base-200 p-4 rounded-lg space-y-2 mb-4">
<div>
<span className="font-bold">AssertionId:</span> {assertion.assertionId}
</div>
<div>
<span className="font-bold">Description:</span> {assertion.description}
</div>
<div>
<span className="font-bold">Bond:</span> {formatEther(assertion.bond)} ETH
</div>
<div>
<span className="font-bold">Reward:</span> {formatEther(assertion.reward)} ETH
</div>
<div>
<span className="font-bold">Start Time:</span>
<span className="text-sm"> UTC: {formatTimestamp(assertion.startTime)}</span>
<span className="text-sm"> Timestamp: {assertion.startTime}</span>
</div>
<div>
<span className="font-bold">End Time:</span>
<span className="text-sm"> UTC: {formatTimestamp(assertion.endTime)}</span>
<span className="text-sm"> Timestamp: {assertion.endTime}</span>
</div>
{assertion.proposer !== ZERO_ADDRESS && (
<div>
<span className="font-bold">Proposed Outcome:</span> {assertion.proposedOutcome ? "True" : "False"}
</div>
)}
{assertion.proposer !== ZERO_ADDRESS && (
<div>
<span className="font-bold">Proposer:</span>{" "}
<Address address={assertion.proposer} format="short" onlyEnsOrAddress disableAddressLink size="sm" />
</div>
)}
{assertion.disputer !== ZERO_ADDRESS && (
<div>
<span className="font-bold">Disputer:</span>{" "}
<Address address={assertion.disputer} format="short" onlyEnsOrAddress disableAddressLink size="sm" />
</div>
)}
</div>
);
};
export const AssertionModal = () => {
const [isActionPending, setIsActionPending] = useState(false);
const { refetchAssertionStates, openAssertion, closeAssertionModal } = useChallengeState();
const isOpen = !!openAssertion;
const { writeContractAsync: writeOOContractAsync } = useScaffoldWriteContract({
contractName: "OptimisticOracle",
});
const { writeContractAsync: writeDeciderContractAsync } = useScaffoldWriteContract({
contractName: "Decider",
});
const handleAction = async (args: any) => {
if (!openAssertion) return;
try {
setIsActionPending(true);
if (args.functionName === "settleDispute") {
await writeDeciderContractAsync(args);
} else {
await writeOOContractAsync(args);
}
refetchAssertionStates();
closeAssertionModal();
} catch (error) {
console.log(error);
} finally {
setIsActionPending(false);
}
};
if (!openAssertion) return null;
return (
<>
<input type="checkbox" id="challenge-modal" className="modal-toggle" checked={isOpen} readOnly />
<label htmlFor="challenge-modal" className="modal cursor-pointer" onClick={closeAssertionModal}>
<label
className="modal-box relative max-w-2xl w-full bg-base-100"
htmlFor=""
onClick={e => e.stopPropagation()}
>
{/* dummy input to capture event onclick on modal box */}
<input className="h-0 w-0 absolute top-0 left-0" />
{/* Close button */}
<button onClick={closeAssertionModal} className="btn btn-ghost btn-sm btn-circle absolute right-3 top-3">
</button>
{/* Modal Content */}
<div className="">
{/* Header with Current State */}
<div className="text-center mb-6">
<h2 className="text-lg">
Current State: <span className="font-bold">{getStateName(openAssertion.state)}</span>
</h2>
</div>
<Description assertion={openAssertion} />
{openAssertion.state === 1 && (
<>
{/* Proposed Outcome Section */}
<div className="rounded-lg p-4">
<div className="flex justify-center mb-4">
<span className="font-medium">Propose Outcome</span>
</div>
{isActionPending && <span className="loading loading-spinner loading-xs"></span>}
<div className="flex justify-center gap-4">
<button
className="btn btn-primary flex-1"
onClick={() =>
handleAction({
functionName: "proposeOutcome",
args: [BigInt(openAssertion.assertionId), true],
value: openAssertion.bond,
})
}
disabled={isActionPending}
>
True
</button>
<button
className="btn btn-primary flex-1"
onClick={() =>
handleAction({
functionName: "proposeOutcome",
args: [BigInt(openAssertion.assertionId), false],
value: openAssertion.bond,
})
}
disabled={isActionPending}
>
False
</button>
</div>
</div>
</>
)}
{openAssertion.state === 2 && (
<div className="rounded-lg p-4">
<div className="flex justify-center mb-4">
<span className="font-medium">Submit Dispute</span>
</div>
{isActionPending && <span className="loading loading-spinner loading-xs"></span>}
<div className="flex justify-center gap-4">
<button
className="btn btn-primary flex-1"
onClick={() =>
handleAction({
functionName: "disputeOutcome",
args: [BigInt(openAssertion.assertionId)],
value: openAssertion.bond,
})
}
disabled={isActionPending}
>
{!openAssertion.proposedOutcome ? "True" : "False"}
</button>
</div>
</div>
)}
{openAssertion.state === 3 && (
<div className="rounded-lg p-4">
<div className="flex flex-col items-center gap-2 mb-4">
<span className="text-2xl font-medium">Impersonate Decider</span>
<span className="font-medium">Resolve Answer to</span>
</div>
{isActionPending && <span className="loading loading-spinner loading-xs"></span>}
<div className="flex justify-center gap-4">
<button
className="btn btn-primary flex-1"
onClick={() =>
handleAction({
functionName: "settleDispute",
args: [BigInt(openAssertion.assertionId), true],
})
}
disabled={isActionPending}
>
True
</button>
<button
className="btn btn-primary flex-1"
onClick={() =>
handleAction({
functionName: "settleDispute",
args: [BigInt(openAssertion.assertionId), false],
})
}
disabled={isActionPending}
>
False
</button>
</div>
</div>
)}
</div>
</label>
</label>
</>
);
};