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,40 @@
import { generatePrivateKey, privateKeyToAccount } from "viem/accounts";
import { usePublicClient, useWalletClient } from "wagmi";
import { PlusIcon } from "@heroicons/react/24/outline";
import { useScaffoldWriteContract } from "~~/hooks/scaffold-eth";
import { notification } from "~~/utils/scaffold-eth";
export const AddOracleButton = () => {
const { data: walletClient } = useWalletClient();
const publicClient = usePublicClient();
const { writeContractAsync: writeWhitelistOracle } = useScaffoldWriteContract({ contractName: "WhitelistOracle" });
const handleAddOracle = async () => {
if (!walletClient || !publicClient) {
notification.error("Please connect wallet and enter both oracle owner address and initial price");
return;
}
try {
// Generate a new oracle address
const privateKey = generatePrivateKey();
const oracleAddress = privateKeyToAccount(privateKey).address;
// Add oracle to whitelist
await writeWhitelistOracle({
functionName: "addOracle",
args: [oracleAddress],
});
} catch (error: any) {
console.log("Error adding oracle:", error);
}
};
return (
<button className="btn btn-primary h-full btn-sm font-normal gap-1" onClick={handleAddOracle}>
<PlusIcon className="h-4 w-4" />
<span>Add Oracle Node</span>
</button>
);
};

View File

@@ -0,0 +1,67 @@
import { useEffect } from "react";
import { EditableCell } from "../EditableCell";
import { Address } from "@scaffold-ui/components";
import { formatEther } from "viem";
import { useBlockNumber, useReadContract } from "wagmi";
import { HighlightedCell } from "~~/components/oracle/HighlightedCell";
import { TimeAgo } from "~~/components/oracle/TimeAgo";
import { WhitelistRowProps } from "~~/components/oracle/types";
import { useScaffoldReadContract, useSelectedNetwork } from "~~/hooks/scaffold-eth";
import { SIMPLE_ORACLE_ABI } from "~~/utils/constants";
import { getHighlightColorForPrice } from "~~/utils/helpers";
export const WhitelistRow = ({ address }: WhitelistRowProps) => {
const selectedNetwork = useSelectedNetwork();
const { data, refetch } = useReadContract({
address: address,
abi: SIMPLE_ORACLE_ABI,
functionName: "getPrice",
query: {
enabled: true,
},
}) as { data: readonly [bigint, bigint] | undefined; refetch: () => void };
const { data: blockNumber } = useBlockNumber({
watch: true,
chainId: selectedNetwork.id,
query: {
enabled: true,
},
});
useEffect(() => {
refetch();
}, [blockNumber, refetch]);
const { data: medianPrice } = useScaffoldReadContract({
contractName: "WhitelistOracle",
functionName: "getPrice",
watch: true,
}) as { data: bigint | undefined };
const { data: staleWindow } = useScaffoldReadContract({
contractName: "WhitelistOracle",
functionName: "STALE_DATA_WINDOW",
}) as { data: bigint | undefined };
const isNotReported = data !== undefined && data[0] === 0n && data[1] === 0n;
const lastReportedPriceFormatted =
data === undefined || isNotReported ? "Not reported" : Number(parseFloat(formatEther(data?.[0] ?? 0n)).toFixed(2));
return (
<tr className={`table-fixed`}>
<td>
<Address address={address} size="sm" format="short" onlyEnsOrAddress={true} />
</td>
<EditableCell
value={lastReportedPriceFormatted}
address={address}
highlightColor={getHighlightColorForPrice(data?.[0], medianPrice)}
/>
<HighlightedCell value={0} highlightColor={""}>
<TimeAgo timestamp={data?.[1]} staleWindow={staleWindow} />
</HighlightedCell>
</tr>
);
};

View File

@@ -0,0 +1,110 @@
import TooltipInfo from "~~/components/TooltipInfo";
import { AddOracleButton } from "~~/components/oracle/whitelist/AddOracleButton";
import { WhitelistRow } from "~~/components/oracle/whitelist/WhitelistRow";
import { useScaffoldEventHistory, useScaffoldReadContract } from "~~/hooks/scaffold-eth";
const LoadingRow = () => {
return (
<tr>
<td className="animate-pulse">
<div className="h-8 bg-secondary rounded w-32"></div>
</td>
<td className="animate-pulse">
<div className="h-8 bg-secondary rounded w-20"></div>
</td>
<td className="animate-pulse">
<div className="h-8 bg-secondary rounded w-24"></div>
</td>
</tr>
);
};
const NoNodesRow = () => {
return (
<tr>
<td colSpan={3} className="text-center">
No nodes found
</td>
</tr>
);
};
export const WhitelistTable = () => {
const { data: oraclesAdded, isLoading: isLoadingOraclesAdded } = useScaffoldEventHistory({
contractName: "WhitelistOracle",
eventName: "OracleAdded",
watch: true,
});
const { data: oraclesRemoved, isLoading: isLoadingOraclesRemoved } = useScaffoldEventHistory({
contractName: "WhitelistOracle",
eventName: "OracleRemoved",
watch: true,
});
const { data: activeOracleNodes } = useScaffoldReadContract({
contractName: "WhitelistOracle",
functionName: "getActiveOracleNodes",
watch: true,
});
const isLoading = isLoadingOraclesAdded || isLoadingOraclesRemoved;
const oracleAddresses = oraclesAdded
?.map((item, index) => ({
address: item?.args?.oracleAddress as string,
originalIndex: index,
}))
?.filter(item => !oraclesRemoved?.some(removedOracle => removedOracle?.args?.oracleAddress === item.address));
const tooltipText = `This table displays registered oracle nodes that provide price data to the system. Nodes are considered active if they've reported within the last 24 seconds. You can add a new oracle node by clicking the "Add Oracle Node" button or edit the price of an oracle node.`;
return (
<div className="flex flex-col gap-2">
<div className="flex gap-2 justify-between">
<div className="flex items-center gap-2">
<h2 className="text-xl font-bold">Oracle Nodes</h2>
<span className="text-sm text-gray-500">
<TooltipInfo infoText={tooltipText} className="tooltip-right" />
</span>
</div>
<div className="flex gap-2">
<AddOracleButton />
</div>
</div>
<div className="bg-base-100 rounded-lg p-4 relative">
<div className="overflow-x-auto">
<table className="table w-full">
<thead>
<tr>
<th>Node Address</th>
<th>
<div className="flex items-center gap-1">
Last Reported Price (USD)
<TooltipInfo infoText="Color shows proximity to median price" />
</div>
</th>
<th>Last Reported Time</th>
</tr>
</thead>
<tbody>
{isLoading ? (
<LoadingRow />
) : oracleAddresses?.length === 0 ? (
<NoNodesRow />
) : (
oracleAddresses?.map(item => (
<WhitelistRow
key={item.address}
index={item.originalIndex}
address={item.address}
isActive={activeOracleNodes?.includes(item.address) ?? false}
/>
))
)}
</tbody>
</table>
</div>
</div>
</div>
);
};