Initial commit with 🏗️ create-eth @ 2.0.4

This commit is contained in:
han
2026-01-23 20:20:58 +07:00
commit b330aba2b4
185 changed files with 36981 additions and 0 deletions

View File

@@ -0,0 +1,245 @@
"use client";
import { useState } from "react";
import { IntegerInput } from "@scaffold-ui/debug-contracts";
import { parseEther } from "viem";
import { usePublicClient } from "wagmi";
import TooltipInfo from "~~/components/TooltipInfo";
import { useScaffoldWriteContract } from "~~/hooks/scaffold-eth";
import { useChallengeState } from "~~/services/store/challengeStore";
import { getRandomQuestion } from "~~/utils/helpers";
import { notification } from "~~/utils/scaffold-eth";
const MINIMUM_ASSERTION_WINDOW = 3;
const getStartTimestamp = (timestamp: bigint, startInMinutes: string) => {
if (startInMinutes.length === 0) return 0n;
if (Number(startInMinutes) === 0) return 0n;
return timestamp + BigInt(startInMinutes) * 60n;
};
const getEndTimestamp = (timestamp: bigint, startTimestamp: bigint, durationInMinutes: string) => {
if (durationInMinutes.length === 0) return 0n;
if (Number(durationInMinutes) === MINIMUM_ASSERTION_WINDOW) return 0n;
if (startTimestamp === 0n) return timestamp + BigInt(durationInMinutes) * 60n;
return startTimestamp + BigInt(durationInMinutes) * 60n;
};
interface SubmitAssertionModalProps {
isOpen: boolean;
onClose: () => void;
}
const SubmitAssertionModal = ({ isOpen, onClose }: SubmitAssertionModalProps) => {
const { timestamp } = useChallengeState();
const [isLoading, setIsLoading] = useState(false);
const publicClient = usePublicClient();
const [description, setDescription] = useState("");
const [reward, setReward] = useState<string>("");
const [startInMinutes, setStartInMinutes] = useState<string>("");
const [durationInMinutes, setDurationInMinutes] = useState<string>("");
const { writeContractAsync } = useScaffoldWriteContract({ contractName: "OptimisticOracle" });
const handleRandomQuestion = () => {
setDescription(getRandomQuestion());
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (durationInMinutes.length > 0 && Number(durationInMinutes) < MINIMUM_ASSERTION_WINDOW) {
notification.error(
`Duration must be at least ${MINIMUM_ASSERTION_WINDOW} minutes or leave blank to use default value`,
);
return;
}
if (Number(reward) === 0) {
notification.error(`Reward must be greater than 0 ETH`);
return;
}
if (!publicClient) {
notification.error("Public client not found");
return;
}
try {
setIsLoading(true);
let recentTimestamp = timestamp;
if (!recentTimestamp) {
const block = await publicClient.getBlock();
recentTimestamp = block.timestamp;
}
const startTimestamp = getStartTimestamp(recentTimestamp, startInMinutes);
const endTimestamp = getEndTimestamp(recentTimestamp, startTimestamp, durationInMinutes);
await writeContractAsync({
functionName: "assertEvent",
args: [description.trim(), startTimestamp, endTimestamp],
value: parseEther(reward),
});
// Reset form after successful submission
setDescription("");
setReward("");
setStartInMinutes("");
setDurationInMinutes("");
onClose();
} catch (error) {
console.log("Error with submission", error);
} finally {
setIsLoading(false);
}
};
const handleClose = () => {
onClose();
// Reset form when closing
setDescription("");
setReward("");
setStartInMinutes("");
setDurationInMinutes("");
};
if (!isOpen) return null;
const readyToSubmit = description.trim().length > 0 && reward.trim().length > 0;
return (
<>
<input type="checkbox" id="assertion-modal" className="modal-toggle" checked={isOpen} readOnly />
<label htmlFor="assertion-modal" className="modal cursor-pointer" onClick={handleClose}>
<label className="modal-box relative max-w-md 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={handleClose} className="btn btn-ghost btn-sm btn-circle absolute right-3 top-3">
</button>
<div className="relative">
<TooltipInfo
top={-2}
right={5}
className="tooltip-left"
infoText="Create a new assertion with your reward stake. Leave time inputs blank to use default values."
/>
</div>
{/* Modal Content */}
<div>
{/* Header */}
<div className="text-center mb-6">
<h2 className="text-xl font-bold">Submit New Assertion</h2>
</div>
{/* Form */}
<form onSubmit={handleSubmit} className="space-y-4">
{/* Description Input */}
<div>
<label className="label">
<span className="text-accent font-medium">
Description <span className="text-red-500">*</span>
</span>
</label>
<div className="flex gap-2 items-start">
<div className="flex-1">
<div className="flex border-2 border-base-300 bg-base-200 rounded-full text-accent">
<textarea
name="description"
value={description}
onChange={e => setDescription(e.target.value)}
placeholder="Enter assertion description..."
className="input input-ghost focus-within:border-transparent leading-8 focus:outline-hidden focus:bg-transparent h-auto min-h-[2.2rem] px-4 border w-full font-medium placeholder:text-accent/70 text-base-content/70 focus:text-base-content/70 whitespace-pre-wrap overflow-x-hidden"
rows={2}
/>
</div>
</div>
<button
type="button"
onClick={handleRandomQuestion}
className="btn btn-secondary btn-sm"
title="Select random question"
>
🎲
</button>
</div>
</div>
<div>
<label className="label">
<span className="text-accent font-medium">
Reward (ETH) <span className="text-red-500">*</span>
</span>
</label>
<IntegerInput
name="reward"
placeholder={`0.01`}
value={reward}
onChange={newValue => setReward(newValue)}
disableMultiplyBy1e18
/>
</div>
{/* Start Time and End Time Inputs */}
<div className="grid grid-cols-2 gap-4">
<div>
<label className="label">
<span className="text-accent font-medium">Start in (minutes)</span>
</label>
<IntegerInput
name="startTime"
placeholder="blank = now"
value={startInMinutes}
onChange={newValue => setStartInMinutes(newValue)}
disableMultiplyBy1e18
/>
</div>
<div>
<label className="label">
<span className="text-accent font-medium">Duration (minutes)</span>
</label>
<IntegerInput
name="endTime"
placeholder={`minimum ${MINIMUM_ASSERTION_WINDOW} minutes`}
value={durationInMinutes}
onChange={newValue => setDurationInMinutes(newValue)}
disableMultiplyBy1e18
/>
</div>
</div>
{/* Action Buttons */}
<div className="flex gap-3 mt-6">
<button type="submit" className="btn btn-primary flex-1" disabled={isLoading || !readyToSubmit}>
{isLoading && <span className="loading loading-spinner loading-xs"></span>}
Submit
</button>
</div>
</form>
</div>
</label>
</label>
</>
);
};
export const SubmitAssertionButton = () => {
const [isModalOpen, setIsModalOpen] = useState(false);
const openModal = () => setIsModalOpen(true);
const closeModal = () => setIsModalOpen(false);
return (
<>
{/* Button */}
<div className="my-8 flex justify-center">
<button className="btn btn-primary btn-lg" onClick={openModal}>
Submit New Assertion
</button>
</div>
{/* Modal - only mounted when open */}
{isModalOpen && <SubmitAssertionModal isOpen={isModalOpen} onClose={closeModal} />}
</>
);
};