Forráskód Böngészése

Merge pull request #2019 from cprussin/add-changelog

feat(staking): add changelog
Connor Prussin 1 éve
szülő
commit
2f7d072438

+ 198 - 0
apps/staking/src/components/Changelog/index.tsx

@@ -0,0 +1,198 @@
+"use client";
+
+import type { ReactNode } from "react";
+import { useDateFormatter } from "react-aria";
+
+import { useChangelog } from "../../hooks/use-changelog";
+import { Link } from "../Link";
+import { ModalDialog } from "../ModalDialog";
+
+export const Changelog = () => {
+  const { isOpen, toggleOpen } = useChangelog();
+
+  return (
+    <ModalDialog title="Changelog" isOpen={isOpen} onOpenChange={toggleOpen}>
+      <ul className="flex max-w-prose flex-col divide-y divide-neutral-600/50">
+        {messages.map(({ id, message }) => (
+          <li key={id}>{message}</li>
+        ))}
+      </ul>
+    </ModalDialog>
+  );
+};
+
+type ChangelogMessageProps = {
+  date: Date;
+  children: ReactNode | ReactNode[];
+};
+
+export const ChangelogMessage = ({ date, children }: ChangelogMessageProps) => {
+  const dateFormatter = useDateFormatter({
+    year: "numeric",
+    month: "short",
+    day: "numeric",
+  });
+
+  return (
+    <section className="py-8">
+      <h2 className="text-sm uppercase text-pythpurple-400">
+        {dateFormatter.format(date)}
+      </h2>
+      {children}
+    </section>
+  );
+};
+
+type ChangelogSectionProps = {
+  title: ReactNode;
+  children: ReactNode | ReactNode[];
+};
+
+export const ChangelogSection = ({
+  title,
+  children,
+}: ChangelogSectionProps) => (
+  <section className="mt-4">
+    <h3 className="text-lg font-semibold text-white">{title}</h3>
+    <div className="flex flex-col gap-2 pl-2 text-sm opacity-70">
+      {children}
+    </div>
+  </section>
+);
+
+export const messages = [
+  {
+    id: 1,
+    message: (
+      <ChangelogMessage date={new Date("2024-10-10")}>
+        <ChangelogSection title="Milestones">
+          <div>
+            <p>
+              We are pleased to announce the following Oracle Integrity Staking
+              milestones:
+            </p>
+            <ul className="list-disc pl-8">
+              <li>143M PYTH staked and securing DeFi.</li>
+              <li>9.6K unique stakers participating.</li>
+              <li>237K in PYTH programmatically distributed.</li>
+            </ul>
+          </div>
+          <p>We’re thrilled to see so many community participants.</p>
+        </ChangelogSection>
+        <ChangelogSection title="New Features to the Staking Frontend">
+          <ul className="list-disc pl-4">
+            <li>
+              New sort filter for publishers list. Publishers with self-stake
+              are displayed first by default. You can sort by publisher details,
+              pool composition, and more.
+            </li>
+            <li>
+              Publishers interested in de-anonymizing themselves can have their
+              names displayed in the publisher list.
+            </li>
+            <li>New OIS live stats added to navigation bar.</li>
+            <li>
+              New dialogue added under “Help” where you can view current program
+              parameters.
+            </li>
+            <li>
+              Option to remove PYTH from the smart contract program for parties
+              with restricted access to the staking frontend.
+            </li>
+            <li>
+              Full access to Pyth Governance for certain restricted
+              jurisdictions.
+            </li>
+            <li>APYs are now shown as net of delegation fees.</li>
+            <li>
+              Updates to educational materials (all Guides and FAQs) for clarity
+              and readability.
+            </li>
+            <li>
+              New Oracle Integrity Staking{" "}
+              <Link
+                href="https://forum.pyth.network/c/oracle-integrity-staking-ois-discussion/8"
+                className="underline"
+                target="_blank"
+              >
+                discussion catalogue
+              </Link>{" "}
+              opened in Pyth DAO forum. Let the community know your thoughts and
+              feedback!
+            </li>
+          </ul>
+        </ChangelogSection>
+        <ChangelogSection title="Security">
+          <p>
+            The Pyth contributors take security extremely seriously. The
+            contract code is{" "}
+            <Link
+              href="https://github.com/pyth-network/governance/tree/main/staking/programs/staking"
+              className="underline"
+              target="_blank"
+            >
+              open source
+            </Link>{" "}
+            and the upgrade authority is governed by the Pyth DAO. The official{" "}
+            <Link
+              href="https://github.com/pyth-network/audit-reports/blob/main/2024_09_11/pyth_cip_final_report.pdf"
+              className="underline"
+              target="_blank"
+            >
+              audit report
+            </Link>{" "}
+            is publicly accessible. All on-chain contract codes are verified
+            using{" "}
+            <Link
+              href="https://github.com/Ellipsis-Labs/solana-verifiable-build/"
+              className="underline"
+              target="_blank"
+            >
+              Solana verifiable build
+            </Link>{" "}
+            and the Pyth DAO governs the upgrade authority.
+          </p>
+        </ChangelogSection>
+        <ChangelogSection title="Best Practices">
+          <p>
+            Please remember that publishers have priority for programmatic
+            rewards distributions. By protocol design, if a pool’s stake cap is
+            exceeded, the programmatic reward rate for other stakers
+            participating in that pool will be lower than the Pyth DAO-set
+            maximum reward rate.
+          </p>
+        </ChangelogSection>
+        <ChangelogSection title="Acknowledgements">
+          <p>
+            The Pyth contributors are glad to see so many network participants
+            getting involved with Oracle Integrity Staking to help secure the
+            oracle and protect the wider DeFi industry. OIS wouldn’t be possible
+            without you!
+          </p>
+        </ChangelogSection>
+        <ChangelogSection title="Feedback">
+          <p>
+            Please reach out in the official{" "}
+            <Link
+              href="https://discord.com/invite/PythNetwork"
+              className="underline"
+              target="_blank"
+            >
+              Pyth Discord
+            </Link>{" "}
+            or the{" "}
+            <Link
+              href="https://forum.pyth.network"
+              className="underline"
+              target="_blank"
+            >
+              Pyth DAO Forum
+            </Link>{" "}
+            to share your questions, ideas, or feedback. We want to hear what
+            you think.
+          </p>
+        </ChangelogSection>
+      </ChangelogMessage>
+    ),
+  },
+];

+ 12 - 11
apps/staking/src/components/Header/help-menu.tsx

@@ -9,6 +9,7 @@ import { MenuTrigger, Button } from "react-aria-components";
 
 import { ProgramParameters } from "./program-parameters";
 import { StateType, useApi } from "../../hooks/use-api";
+import { useChangelog } from "../../hooks/use-changelog";
 import { GeneralFaq } from "../GeneralFaq";
 import { GovernanceGuide } from "../GovernanceGuide";
 import { Menu, MenuItem, Section, Separator } from "../Menu";
@@ -41,6 +42,7 @@ export const HelpMenu = () => {
   const openParameters = useCallback(() => {
     setParametersOpen(true);
   }, [setParametersOpen]);
+  const { open: openChangelog } = useChangelog();
 
   return (
     <>
@@ -73,17 +75,16 @@ export const HelpMenu = () => {
               Data Publisher Guide
             </MenuItem>
           </Section>
-          {(api.type === StateType.Loaded ||
-            api.type === StateType.LoadedNoStakeAccount) && (
-            <>
-              <Separator />
-              <Section>
-                <MenuItem onAction={openParameters}>
-                  Current Program Parameters
-                </MenuItem>
-              </Section>
-            </>
-          )}
+          <Separator />
+          <Section>
+            {(api.type === StateType.Loaded ||
+              api.type === StateType.LoadedNoStakeAccount) && (
+              <MenuItem onAction={openParameters}>
+                Current Program Parameters
+              </MenuItem>
+            )}
+            <MenuItem onAction={openChangelog}>Changelog</MenuItem>
+          </Section>
         </Menu>
       </MenuTrigger>
       <GeneralFaq isOpen={faqOpen} onOpenChange={setFaqOpen} />

+ 2 - 0
apps/staking/src/components/ModalDialog/index.tsx

@@ -1,3 +1,5 @@
+"use client";
+
 import { XMarkIcon } from "@heroicons/react/24/outline";
 import clsx from "clsx";
 import type { ComponentProps, ReactNode } from "react";

+ 2 - 0
apps/staking/src/components/Root/index.tsx

@@ -20,6 +20,7 @@ import { LoggerProvider } from "../../hooks/use-logger";
 import { NetworkProvider } from "../../hooks/use-network";
 import { ToastProvider } from "../../hooks/use-toast";
 import { Amplitude } from "../Amplitude";
+import { Changelog } from "../Changelog";
 import { Footer } from "../Footer";
 import { Header } from "../Header";
 import { MaxWidth } from "../MaxWidth";
@@ -64,6 +65,7 @@ export const Root = ({ children }: Props) => (
       </MaxWidth>
       <Footer className="z-10" />
       <ToastRegion />
+      <Changelog />
     </body>
     {GOOGLE_ANALYTICS_ID && <GoogleAnalytics gaId={GOOGLE_ANALYTICS_ID} />}
     {AMPLITUDE_API_KEY && <Amplitude apiKey={AMPLITUDE_API_KEY} />}

+ 41 - 0
apps/staking/src/hooks/use-changelog.ts

@@ -0,0 +1,41 @@
+import { useLocalStorageValue } from "@react-hookz/web";
+import { useCallback, useMemo } from "react";
+
+import { messages } from "../components/Changelog";
+
+export const useChangelog = () => {
+  const lastMessageSeen = useLocalStorageValue<number>(
+    "last-changelog-message-seen",
+    {
+      parse: (value) =>
+        // eslint-disable-next-line unicorn/no-null
+        value === null || value === "" ? null : Number.parseInt(value, 10),
+      stringify: (value) => value.toString(),
+    },
+  );
+
+  const isOpen = useMemo(() => {
+    const lastClosed = lastMessageSeen.value;
+    return (
+      lastClosed === undefined ||
+      messages.some((message) => message.id > lastClosed)
+    );
+  }, [lastMessageSeen.value]);
+
+  const toggleOpen = useCallback(
+    (isOpen: boolean) => {
+      if (isOpen) {
+        lastMessageSeen.remove();
+      } else {
+        lastMessageSeen.set(Math.max(...messages.map(({ id }) => id)));
+      }
+    },
+    [lastMessageSeen],
+  );
+
+  const open = useCallback(() => {
+    toggleOpen(true);
+  }, [toggleOpen]);
+
+  return { isOpen, toggleOpen, open };
+};