From d6ec7c83096bc010a9d6244aff4cc2ecf98211ff Mon Sep 17 00:00:00 2001 From: NicoSerranoP Date: Wed, 14 May 2025 16:52:12 -0500 Subject: [PATCH] chore(plugins): delete tokenVoting plugin --- .env.example | 4 - constants.ts | 6 - hooks/useShuffled.ts | 17 - hooks/useTransactionManager.ts | 75 --- plugins/index.ts | 11 +- .../tokenVoting/artifacts/TokenVoting.sol.tsx | 460 ------------------ .../components/proposal/header.tsx | 83 ---- .../tokenVoting/components/proposal/index.tsx | 119 ----- plugins/tokenVoting/components/vote/tally.tsx | 35 -- .../components/vote/votes-section.tsx | 42 -- .../tokenVoting/hooks/useCanCreateProposal.ts | 47 -- plugins/tokenVoting/hooks/useCanVote.ts | 23 - .../tokenVoting/hooks/useCreateProposal.ts | 109 ----- plugins/tokenVoting/hooks/usePastSupply.ts | 16 - plugins/tokenVoting/hooks/useProposal.ts | 134 ----- .../tokenVoting/hooks/useProposalExecute.ts | 58 --- .../hooks/useProposalVariantStatus.ts | 67 --- .../tokenVoting/hooks/useProposalVoteList.ts | 37 -- .../tokenVoting/hooks/useProposalVoting.ts | 35 -- plugins/tokenVoting/hooks/useToken.ts | 35 -- plugins/tokenVoting/index.tsx | 21 - plugins/tokenVoting/pages/new.tsx | 283 ----------- plugins/tokenVoting/pages/proposal-list.tsx | 100 ---- plugins/tokenVoting/pages/proposal.tsx | 275 ----------- plugins/tokenVoting/utils/proposal-status.ts | 40 -- plugins/tokenVoting/utils/types.tsx | 48 -- 26 files changed, 1 insertion(+), 2179 deletions(-) delete mode 100644 hooks/useShuffled.ts delete mode 100644 hooks/useTransactionManager.ts delete mode 100644 plugins/tokenVoting/artifacts/TokenVoting.sol.tsx delete mode 100644 plugins/tokenVoting/components/proposal/header.tsx delete mode 100644 plugins/tokenVoting/components/proposal/index.tsx delete mode 100644 plugins/tokenVoting/components/vote/tally.tsx delete mode 100644 plugins/tokenVoting/components/vote/votes-section.tsx delete mode 100644 plugins/tokenVoting/hooks/useCanCreateProposal.ts delete mode 100644 plugins/tokenVoting/hooks/useCanVote.ts delete mode 100644 plugins/tokenVoting/hooks/useCreateProposal.ts delete mode 100644 plugins/tokenVoting/hooks/usePastSupply.ts delete mode 100644 plugins/tokenVoting/hooks/useProposal.ts delete mode 100644 plugins/tokenVoting/hooks/useProposalExecute.ts delete mode 100644 plugins/tokenVoting/hooks/useProposalVariantStatus.ts delete mode 100644 plugins/tokenVoting/hooks/useProposalVoteList.ts delete mode 100644 plugins/tokenVoting/hooks/useProposalVoting.ts delete mode 100644 plugins/tokenVoting/hooks/useToken.ts delete mode 100644 plugins/tokenVoting/index.tsx delete mode 100644 plugins/tokenVoting/pages/new.tsx delete mode 100644 plugins/tokenVoting/pages/proposal-list.tsx delete mode 100644 plugins/tokenVoting/pages/proposal.tsx delete mode 100644 plugins/tokenVoting/utils/proposal-status.ts delete mode 100644 plugins/tokenVoting/utils/types.tsx diff --git a/.env.example b/.env.example index 818aea4..8b36e41 100644 --- a/.env.example +++ b/.env.example @@ -5,10 +5,6 @@ NEXT_PUBLIC_DAO_ADDRESS=0xd9A924bF3FaE756417b9B9DCC94C24681534b8F7 NEXT_PUBLIC_TOKEN_ADDRESS=0x9A3218197C77F54BB2510FBBcc7Da3A4D2bE0DeE # Plugin addresses -NEXT_PUBLIC_TOKEN_VOTING_PLUGIN_ADDRESS=0x7AdF2545e746E014887916e476DfCB3Fb57D78b0 -NEXT_PUBLIC_PUBLIC_KEY_REGISTRY_CONTRACT_ADDRESS=0x4BA2de07E5B7FB284d363DBb4c481F330c25b2A5 - -NEXT_PUBLIC_BRIDGE_ADDRESS=0x0000000000000000000000000000001234567890 # Network and services NEXT_PUBLIC_CHAIN_NAME=sepolia diff --git a/constants.ts b/constants.ts index 4ad29f3..950329b 100644 --- a/constants.ts +++ b/constants.ts @@ -5,12 +5,6 @@ import { type ChainName, getChain } from "./utils/chains"; export const PUB_DAO_ADDRESS = (process.env.NEXT_PUBLIC_DAO_ADDRESS ?? "") as Address; export const PUB_TOKEN_ADDRESS = (process.env.NEXT_PUBLIC_TOKEN_ADDRESS ?? "") as Address; -export const PUB_TOKEN_VOTING_PLUGIN_ADDRESS = (process.env.NEXT_PUBLIC_TOKEN_VOTING_PLUGIN_ADDRESS ?? "") as Address; -export const PUB_PUBLIC_KEY_REGISTRY_CONTRACT_ADDRESS = (process.env.NEXT_PUBLIC_PUBLIC_KEY_REGISTRY_CONTRACT_ADDRESS ?? - "") as Address; - -export const PUB_BRIDGE_ADDRESS = (process.env.NEXT_PUBLIC_BRIDGE_ADDRESS ?? "") as Address; - // Target chain export const PUB_CHAIN_NAME = (process.env.NEXT_PUBLIC_CHAIN_NAME ?? "holesky") as ChainName; export const PUB_CHAIN = getChain(PUB_CHAIN_NAME); diff --git a/hooks/useShuffled.ts b/hooks/useShuffled.ts deleted file mode 100644 index c4fc663..0000000 --- a/hooks/useShuffled.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { useEffect, useState } from "react"; - -export function useShuffled(array?: T[] | readonly T[]) { - const [shuffledArray, setShuffledArray] = useState([]); - - useEffect(() => { - if (!array) return; - - // If the length changes, shuffle and update - const newArray = ([] as T[]).concat(array); - newArray.sort(() => (Math.random() >= 0.5 ? 1 : -1)); - - setShuffledArray(newArray); - }, [array?.length]); - - return shuffledArray; -} diff --git a/hooks/useTransactionManager.ts b/hooks/useTransactionManager.ts deleted file mode 100644 index c91dd44..0000000 --- a/hooks/useTransactionManager.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { useEffect } from "react"; -import { useAlerts } from "@/context/Alerts"; -import { useWaitForTransactionReceipt, useWriteContract } from "wagmi"; - -export type TxLifecycleParams = { - onSuccessMessage?: string; - onSuccessDescription?: string; - onSuccess?: () => any; - onErrorMessage?: string; - onErrorDescription?: string; - onError?: () => any; -}; - -export function useTransactionManager(params: TxLifecycleParams) { - const { onSuccess, onError } = params; - const { writeContract, data: hash, error, status } = useWriteContract(); - const { isLoading: isConfirming, isSuccess: isConfirmed } = useWaitForTransactionReceipt({ hash }); - const { addAlert } = useAlerts(); - - useEffect(() => { - if (status === "idle" || status === "pending") { - return; - } else if (status === "error") { - if (error?.message?.startsWith("User rejected the request")) { - addAlert("The transaction signature was declined", { - description: "Nothing has been sent to the network", - timeout: 4 * 1000, - }); - } else { - console.error(error); - let description = "The proposal may contain actions with invalid operations"; - if (error?.toString()) { - const found = error.toString().match(/ror: ActionFailed\(uint256 index\)\n\s+\(([0-9]+)\)/); - if (found && found[1] && typeof parseInt(found[1]) === "number") { - description = `Action ${parseInt(found[1]) + 1} failed to complete successfully`; - } - } - addAlert(params.onErrorMessage || "Could not fulfill the transaction", { - type: "error", - description, - }); - } - - if (typeof onError === "function") { - onError(); - } - return; - } - - // TX submitted - if (!hash) { - return; - } else if (isConfirming) { - addAlert("Transaction submitted", { - description: "Waiting for the transaction to be validated", - txHash: hash, - }); - return; - } else if (!isConfirmed) { - return; - } - - addAlert(params.onSuccessMessage || "Transaction fulfilled", { - description: params.onSuccessDescription || "The transaction has been validated on the network", - type: "success", - txHash: hash, - }); - - if (typeof onSuccess === "function") { - onSuccess(); - } - }, [status, hash, isConfirming, isConfirmed]); - - return { writeContract, hash, status, isConfirming, isConfirmed }; -} diff --git a/plugins/index.ts b/plugins/index.ts index 810b53a..1ce8397 100644 --- a/plugins/index.ts +++ b/plugins/index.ts @@ -1,4 +1,3 @@ -import { PUB_TOKEN_VOTING_PLUGIN_ADDRESS } from "@/constants"; import { type IconType } from "@aragon/ods"; type PluginItem = { @@ -12,12 +11,4 @@ type PluginItem = { pluginAddress: string; }; -export const plugins: PluginItem[] = [ - { - id: "token-voting", - folderName: "tokenVoting", - title: "Token Voting", - // icon: IconType.BLOCKCHAIN_BLOCKCHAIN, - pluginAddress: PUB_TOKEN_VOTING_PLUGIN_ADDRESS, - }, -]; +export const plugins: PluginItem[] = []; diff --git a/plugins/tokenVoting/artifacts/TokenVoting.sol.tsx b/plugins/tokenVoting/artifacts/TokenVoting.sol.tsx deleted file mode 100644 index 4a2547b..0000000 --- a/plugins/tokenVoting/artifacts/TokenVoting.sol.tsx +++ /dev/null @@ -1,460 +0,0 @@ -export const TokenVotingAbi = [ - { - inputs: [ - { internalType: "address", name: "dao", type: "address" }, - { internalType: "address", name: "where", type: "address" }, - { internalType: "address", name: "who", type: "address" }, - { internalType: "bytes32", name: "permissionId", type: "bytes32" }, - ], - name: "DaoUnauthorized", - type: "error", - }, - { - inputs: [ - { internalType: "uint64", name: "limit", type: "uint64" }, - { internalType: "uint64", name: "actual", type: "uint64" }, - ], - name: "DateOutOfBounds", - type: "error", - }, - { - inputs: [ - { internalType: "uint64", name: "limit", type: "uint64" }, - { internalType: "uint64", name: "actual", type: "uint64" }, - ], - name: "MinDurationOutOfBounds", - type: "error", - }, - { inputs: [], name: "NoVotingPower", type: "error" }, - { - inputs: [{ internalType: "address", name: "sender", type: "address" }], - name: "ProposalCreationForbidden", - type: "error", - }, - { - inputs: [{ internalType: "uint256", name: "proposalId", type: "uint256" }], - name: "ProposalExecutionForbidden", - type: "error", - }, - { - inputs: [ - { internalType: "uint256", name: "limit", type: "uint256" }, - { internalType: "uint256", name: "actual", type: "uint256" }, - ], - name: "RatioOutOfBounds", - type: "error", - }, - { - inputs: [ - { internalType: "uint256", name: "proposalId", type: "uint256" }, - { internalType: "address", name: "account", type: "address" }, - { internalType: "enum IMajorityVoting.VoteOption", name: "voteOption", type: "uint8" }, - ], - name: "VoteCastForbidden", - type: "error", - }, - { - anonymous: false, - inputs: [ - { indexed: false, internalType: "address", name: "previousAdmin", type: "address" }, - { indexed: false, internalType: "address", name: "newAdmin", type: "address" }, - ], - name: "AdminChanged", - type: "event", - }, - { - anonymous: false, - inputs: [{ indexed: true, internalType: "address", name: "beacon", type: "address" }], - name: "BeaconUpgraded", - type: "event", - }, - { - anonymous: false, - inputs: [{ indexed: false, internalType: "uint8", name: "version", type: "uint8" }], - name: "Initialized", - type: "event", - }, - { - anonymous: false, - inputs: [{ indexed: false, internalType: "address[]", name: "members", type: "address[]" }], - name: "MembersAdded", - type: "event", - }, - { - anonymous: false, - inputs: [{ indexed: false, internalType: "address[]", name: "members", type: "address[]" }], - name: "MembersRemoved", - type: "event", - }, - { - anonymous: false, - inputs: [{ indexed: true, internalType: "address", name: "definingContract", type: "address" }], - name: "MembershipContractAnnounced", - type: "event", - }, - { - anonymous: false, - inputs: [ - { indexed: true, internalType: "uint256", name: "proposalId", type: "uint256" }, - { indexed: true, internalType: "address", name: "creator", type: "address" }, - { indexed: false, internalType: "uint64", name: "startDate", type: "uint64" }, - { indexed: false, internalType: "uint64", name: "endDate", type: "uint64" }, - { indexed: false, internalType: "bytes", name: "metadata", type: "bytes" }, - { - components: [ - { internalType: "address", name: "to", type: "address" }, - { internalType: "uint256", name: "value", type: "uint256" }, - { internalType: "bytes", name: "data", type: "bytes" }, - ], - indexed: false, - internalType: "struct IDAO.Action[]", - name: "actions", - type: "tuple[]", - }, - { indexed: false, internalType: "uint256", name: "allowFailureMap", type: "uint256" }, - ], - name: "ProposalCreated", - type: "event", - }, - { - anonymous: false, - inputs: [{ indexed: true, internalType: "uint256", name: "proposalId", type: "uint256" }], - name: "ProposalExecuted", - type: "event", - }, - { - anonymous: false, - inputs: [{ indexed: true, internalType: "address", name: "implementation", type: "address" }], - name: "Upgraded", - type: "event", - }, - { - anonymous: false, - inputs: [ - { indexed: true, internalType: "uint256", name: "proposalId", type: "uint256" }, - { indexed: true, internalType: "address", name: "voter", type: "address" }, - { indexed: false, internalType: "enum IMajorityVoting.VoteOption", name: "voteOption", type: "uint8" }, - { indexed: false, internalType: "uint256", name: "votingPower", type: "uint256" }, - ], - name: "VoteCast", - type: "event", - }, - { - anonymous: false, - inputs: [ - { indexed: false, internalType: "enum MajorityVotingBase.VotingMode", name: "votingMode", type: "uint8" }, - { indexed: false, internalType: "uint32", name: "supportThreshold", type: "uint32" }, - { indexed: false, internalType: "uint32", name: "minParticipation", type: "uint32" }, - { indexed: false, internalType: "uint64", name: "minDuration", type: "uint64" }, - { indexed: false, internalType: "uint256", name: "minProposerVotingPower", type: "uint256" }, - ], - name: "VotingSettingsUpdated", - type: "event", - }, - { - inputs: [], - name: "UPDATE_VOTING_SETTINGS_PERMISSION_ID", - outputs: [{ internalType: "bytes32", name: "", type: "bytes32" }], - stateMutability: "view", - type: "function", - }, - { - inputs: [], - name: "UPGRADE_PLUGIN_PERMISSION_ID", - outputs: [{ internalType: "bytes32", name: "", type: "bytes32" }], - stateMutability: "view", - type: "function", - }, - { - inputs: [{ internalType: "uint256", name: "_proposalId", type: "uint256" }], - name: "canExecute", - outputs: [{ internalType: "bool", name: "", type: "bool" }], - stateMutability: "view", - type: "function", - }, - { - inputs: [ - { internalType: "uint256", name: "_proposalId", type: "uint256" }, - { internalType: "address", name: "_voter", type: "address" }, - { internalType: "enum IMajorityVoting.VoteOption", name: "_voteOption", type: "uint8" }, - ], - name: "canVote", - outputs: [{ internalType: "bool", name: "", type: "bool" }], - stateMutability: "view", - type: "function", - }, - { - inputs: [ - { internalType: "bytes", name: "_metadata", type: "bytes" }, - { - components: [ - { internalType: "address", name: "to", type: "address" }, - { internalType: "uint256", name: "value", type: "uint256" }, - { internalType: "bytes", name: "data", type: "bytes" }, - ], - internalType: "struct IDAO.Action[]", - name: "_actions", - type: "tuple[]", - }, - { internalType: "uint256", name: "_allowFailureMap", type: "uint256" }, - { internalType: "uint64", name: "_startDate", type: "uint64" }, - { internalType: "uint64", name: "_endDate", type: "uint64" }, - { internalType: "enum IMajorityVoting.VoteOption", name: "_voteOption", type: "uint8" }, - { internalType: "bool", name: "_tryEarlyExecution", type: "bool" }, - ], - name: "createProposal", - outputs: [{ internalType: "uint256", name: "proposalId", type: "uint256" }], - stateMutability: "nonpayable", - type: "function", - }, - { - inputs: [], - name: "dao", - outputs: [{ internalType: "contract IDAO", name: "", type: "address" }], - stateMutability: "view", - type: "function", - }, - { - inputs: [{ internalType: "uint256", name: "_proposalId", type: "uint256" }], - name: "execute", - outputs: [], - stateMutability: "nonpayable", - type: "function", - }, - { - inputs: [{ internalType: "uint256", name: "_proposalId", type: "uint256" }], - name: "getProposal", - outputs: [ - { internalType: "bool", name: "open", type: "bool" }, - { internalType: "bool", name: "executed", type: "bool" }, - { - components: [ - { internalType: "enum MajorityVotingBase.VotingMode", name: "votingMode", type: "uint8" }, - { internalType: "uint32", name: "supportThreshold", type: "uint32" }, - { internalType: "uint64", name: "startDate", type: "uint64" }, - { internalType: "uint64", name: "endDate", type: "uint64" }, - { internalType: "uint64", name: "snapshotBlock", type: "uint64" }, - { internalType: "uint256", name: "minVotingPower", type: "uint256" }, - ], - internalType: "struct MajorityVotingBase.ProposalParameters", - name: "parameters", - type: "tuple", - }, - { - components: [ - { internalType: "uint256", name: "abstain", type: "uint256" }, - { internalType: "uint256", name: "yes", type: "uint256" }, - { internalType: "uint256", name: "no", type: "uint256" }, - ], - internalType: "struct MajorityVotingBase.Tally", - name: "tally", - type: "tuple", - }, - { - components: [ - { internalType: "address", name: "to", type: "address" }, - { internalType: "uint256", name: "value", type: "uint256" }, - { internalType: "bytes", name: "data", type: "bytes" }, - ], - internalType: "struct IDAO.Action[]", - name: "actions", - type: "tuple[]", - }, - { internalType: "uint256", name: "allowFailureMap", type: "uint256" }, - ], - stateMutability: "view", - type: "function", - }, - { - inputs: [ - { internalType: "uint256", name: "_proposalId", type: "uint256" }, - { internalType: "address", name: "_voter", type: "address" }, - ], - name: "getVoteOption", - outputs: [{ internalType: "enum IMajorityVoting.VoteOption", name: "", type: "uint8" }], - stateMutability: "view", - type: "function", - }, - { - inputs: [], - name: "getVotingToken", - outputs: [{ internalType: "contract IVotesUpgradeable", name: "", type: "address" }], - stateMutability: "view", - type: "function", - }, - { - inputs: [], - name: "implementation", - outputs: [{ internalType: "address", name: "", type: "address" }], - stateMutability: "view", - type: "function", - }, - { - inputs: [ - { internalType: "contract IDAO", name: "_dao", type: "address" }, - { - components: [ - { internalType: "enum MajorityVotingBase.VotingMode", name: "votingMode", type: "uint8" }, - { internalType: "uint32", name: "supportThreshold", type: "uint32" }, - { internalType: "uint32", name: "minParticipation", type: "uint32" }, - { internalType: "uint64", name: "minDuration", type: "uint64" }, - { internalType: "uint256", name: "minProposerVotingPower", type: "uint256" }, - ], - internalType: "struct MajorityVotingBase.VotingSettings", - name: "_votingSettings", - type: "tuple", - }, - { internalType: "contract IVotesUpgradeable", name: "_token", type: "address" }, - ], - name: "initialize", - outputs: [], - stateMutability: "nonpayable", - type: "function", - }, - { - inputs: [{ internalType: "address", name: "_account", type: "address" }], - name: "isMember", - outputs: [{ internalType: "bool", name: "", type: "bool" }], - stateMutability: "view", - type: "function", - }, - { - inputs: [{ internalType: "uint256", name: "_proposalId", type: "uint256" }], - name: "isMinParticipationReached", - outputs: [{ internalType: "bool", name: "", type: "bool" }], - stateMutability: "view", - type: "function", - }, - { - inputs: [{ internalType: "uint256", name: "_proposalId", type: "uint256" }], - name: "isSupportThresholdReached", - outputs: [{ internalType: "bool", name: "", type: "bool" }], - stateMutability: "view", - type: "function", - }, - { - inputs: [{ internalType: "uint256", name: "_proposalId", type: "uint256" }], - name: "isSupportThresholdReachedEarly", - outputs: [{ internalType: "bool", name: "", type: "bool" }], - stateMutability: "view", - type: "function", - }, - { - inputs: [], - name: "minDuration", - outputs: [{ internalType: "uint64", name: "", type: "uint64" }], - stateMutability: "view", - type: "function", - }, - { - inputs: [], - name: "minParticipation", - outputs: [{ internalType: "uint32", name: "", type: "uint32" }], - stateMutability: "view", - type: "function", - }, - { - inputs: [], - name: "minProposerVotingPower", - outputs: [{ internalType: "uint256", name: "", type: "uint256" }], - stateMutability: "view", - type: "function", - }, - { - inputs: [], - name: "pluginType", - outputs: [{ internalType: "enum IPlugin.PluginType", name: "", type: "uint8" }], - stateMutability: "pure", - type: "function", - }, - { - inputs: [], - name: "proposalCount", - outputs: [{ internalType: "uint256", name: "", type: "uint256" }], - stateMutability: "view", - type: "function", - }, - { - inputs: [], - name: "proxiableUUID", - outputs: [{ internalType: "bytes32", name: "", type: "bytes32" }], - stateMutability: "view", - type: "function", - }, - { - inputs: [], - name: "supportThreshold", - outputs: [{ internalType: "uint32", name: "", type: "uint32" }], - stateMutability: "view", - type: "function", - }, - { - inputs: [{ internalType: "bytes4", name: "_interfaceId", type: "bytes4" }], - name: "supportsInterface", - outputs: [{ internalType: "bool", name: "", type: "bool" }], - stateMutability: "view", - type: "function", - }, - { - inputs: [{ internalType: "uint256", name: "_blockNumber", type: "uint256" }], - name: "totalVotingPower", - outputs: [{ internalType: "uint256", name: "", type: "uint256" }], - stateMutability: "view", - type: "function", - }, - { - inputs: [ - { - components: [ - { internalType: "enum MajorityVotingBase.VotingMode", name: "votingMode", type: "uint8" }, - { internalType: "uint32", name: "supportThreshold", type: "uint32" }, - { internalType: "uint32", name: "minParticipation", type: "uint32" }, - { internalType: "uint64", name: "minDuration", type: "uint64" }, - { internalType: "uint256", name: "minProposerVotingPower", type: "uint256" }, - ], - internalType: "struct MajorityVotingBase.VotingSettings", - name: "_votingSettings", - type: "tuple", - }, - ], - name: "updateVotingSettings", - outputs: [], - stateMutability: "nonpayable", - type: "function", - }, - { - inputs: [{ internalType: "address", name: "newImplementation", type: "address" }], - name: "upgradeTo", - outputs: [], - stateMutability: "nonpayable", - type: "function", - }, - { - inputs: [ - { internalType: "address", name: "newImplementation", type: "address" }, - { internalType: "bytes", name: "data", type: "bytes" }, - ], - name: "upgradeToAndCall", - outputs: [], - stateMutability: "payable", - type: "function", - }, - { - inputs: [ - { internalType: "uint256", name: "_proposalId", type: "uint256" }, - { internalType: "enum IMajorityVoting.VoteOption", name: "_voteOption", type: "uint8" }, - { internalType: "bool", name: "_tryEarlyExecution", type: "bool" }, - ], - name: "vote", - outputs: [], - stateMutability: "nonpayable", - type: "function", - }, - { - inputs: [], - name: "votingMode", - outputs: [{ internalType: "enum MajorityVotingBase.VotingMode", name: "", type: "uint8" }], - stateMutability: "view", - type: "function", - }, -] as const; diff --git a/plugins/tokenVoting/components/proposal/header.tsx b/plugins/tokenVoting/components/proposal/header.tsx deleted file mode 100644 index eb7b49b..0000000 --- a/plugins/tokenVoting/components/proposal/header.tsx +++ /dev/null @@ -1,83 +0,0 @@ -import { AvatarIcon, Breadcrumbs, Heading, IBreadcrumbsLink, IconType, ProposalStatus, Tag } from "@aragon/ods"; -import { Publisher } from "@/components/publisher"; -import { Proposal } from "../../utils/types"; -import { useProposalStatus } from "../../hooks/useProposalVariantStatus"; -import { Else, ElseIf, If, Then } from "@/components/if"; -import { getSimpleRelativeTimeFromDate } from "@/utils/dates"; -import { HeaderSection } from "@/components/layout/header-section"; -import { getTagVariantFromStatus } from "@/utils/ui-variants"; -import { capitalizeFirstLetter } from "@/utils/text"; -import dayjs from "dayjs"; - -const DEFAULT_PROPOSAL_TITLE = "(No proposal title)"; -const DEFAULT_PROPOSAL_SUMMARY = "(No proposal summary)"; - -interface ProposalHeaderProps { - proposalIdx: number; - proposal: Proposal; -} - -const ProposalHeader: React.FC = ({ proposalIdx, proposal }) => { - const proposalStatus = useProposalStatus(proposal); - const tagVariant = getTagVariantFromStatus(proposalStatus); - - const breadcrumbs: IBreadcrumbsLink[] = [{ label: "Proposals", href: "#/" }, { label: proposalIdx.toString() }]; - const isEmergency = proposal.parameters.startDate === 0n; - const endDateIsInThePast = Number(proposal.parameters.endDate) * 1000 < Date.now(); - - return ( -
- {/* Wrapper */} - - - {/* Title & description */} -
-
- {proposal.title || DEFAULT_PROPOSAL_TITLE} - {isEmergency && } -
-

{proposal.summary || DEFAULT_PROPOSAL_SUMMARY}

-
- {/* Metadata */} -
-
- - -
-
- -
- - - The proposal has been accepted - - - The proposal has been rejected - - - The voting period is over - - - Active for - - {getSimpleRelativeTimeFromDate(dayjs(Number(proposal.parameters.endDate) * 1000))} - - - -
-
-
-
-
- ); -}; - -export default ProposalHeader; diff --git a/plugins/tokenVoting/components/proposal/index.tsx b/plugins/tokenVoting/components/proposal/index.tsx deleted file mode 100644 index a5334fd..0000000 --- a/plugins/tokenVoting/components/proposal/index.tsx +++ /dev/null @@ -1,119 +0,0 @@ -import Link from "next/link"; -import { Card, ProposalStatus, ProposalDataListItem, IProposalDataListItemStructureProps } from "@aragon/ods"; -import { PleaseWaitSpinner } from "@/components/please-wait"; -import { useProposal } from "../../hooks/useProposal"; -import { useProposalStatus } from "../../hooks/useProposalVariantStatus"; -// import { usePastSupply } from "../../hooks/usePastSupply"; -import { useToken } from "../../hooks/useToken"; -import { useAccount } from "wagmi"; -import { formatEther } from "viem"; -import { PUB_TOKEN_SYMBOL } from "@/constants"; -import { useProposalVoteList } from "../../hooks/useProposalVoteList"; - -const DEFAULT_PROPOSAL_METADATA_TITLE = "(No proposal title)"; -const DEFAULT_PROPOSAL_METADATA_SUMMARY = "(The metadata of the proposal is not available)"; - -type ProposalInputs = { - proposalIndex: number; -}; - -export default function ProposalCard(props: ProposalInputs) { - const { address } = useAccount(); - const { proposal, status: proposalFetchStatus } = useProposal(props.proposalIndex); - const votes = useProposalVoteList(props.proposalIndex, proposal); - // const pastSupply = usePastSupply(proposal?.parameters.snapshotBlock); - const { symbol: tokenSymbol } = useToken(); - const proposalStatus = useProposalStatus(proposal!); - const showLoading = getShowProposalLoading(proposal, proposalFetchStatus); - - const hasVoted = votes?.some((vote) => vote.voter === address); - const totalVotes = - (proposal?.tally.yes || BigInt(0)) + (proposal?.tally.no || BigInt(0)) + (proposal?.tally.abstain || BigInt(0)); - - if (!proposal && showLoading) { - return ( -
- - - - - -
- ); - } else if (!proposal?.title && !proposal?.summary) { - // We have the proposal but no metadata yet - return ( - - - - - - - - ); - } else if (proposalFetchStatus.metadataReady && !proposal?.title) { - return ( - - -
-

- {Number(props.proposalIndex) + 1} - {DEFAULT_PROPOSAL_METADATA_TITLE} -

-

{DEFAULT_PROPOSAL_METADATA_SUMMARY}

-
-
- - ); - } - - let result = { option: "", voteAmount: "", votePercentage: 0 }; - if (proposal?.tally.yes > proposal?.tally.no && proposal?.tally.yes > proposal?.tally.abstain) { - result = { - option: "Yes", - voteAmount: formatEther(proposal.tally.yes) + " " + (tokenSymbol || PUB_TOKEN_SYMBOL), - votePercentage: Number(((proposal?.tally.yes || BigInt(0)) * BigInt(10_000)) / (totalVotes || BigInt(1))) / 100, - }; - } else if (proposal?.tally.no > proposal?.tally.yes && proposal?.tally.no > proposal?.tally.abstain) { - result = { - option: "No", - voteAmount: formatEther(proposal.tally.no) + " " + (tokenSymbol || PUB_TOKEN_SYMBOL), - votePercentage: Number(((proposal?.tally.no || BigInt(0)) * BigInt(10_000)) / (totalVotes || BigInt(1))) / 100, - }; - } else if (proposal?.tally.abstain > proposal?.tally.no && proposal?.tally.abstain > proposal?.tally.yes) { - result = { - option: "Abstain", - voteAmount: formatEther(proposal.tally.abstain) + " " + (tokenSymbol || PUB_TOKEN_SYMBOL), - votePercentage: - Number(((proposal?.tally.abstain || BigInt(0)) * BigInt(10_000)) / (totalVotes || BigInt(1))) / 100, - }; - } - - return ( - - ); -} - -function getShowProposalLoading( - proposal: ReturnType["proposal"], - status: ReturnType["status"] -) { - if (!proposal || status.proposalLoading) return true; - else if (status.metadataLoading && !status.metadataError) return true; - else if (!proposal?.title && !status.metadataError) return true; - - return false; -} diff --git a/plugins/tokenVoting/components/vote/tally.tsx b/plugins/tokenVoting/components/vote/tally.tsx deleted file mode 100644 index 82913b1..0000000 --- a/plugins/tokenVoting/components/vote/tally.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { compactNumber } from "@/utils/numbers"; -import { FC, ReactNode } from "react"; -import { formatUnits } from "viem"; - -interface VoteTallyProps { - voteCount: bigint; - votePercentage: number; -} - -const VetoTally: FC = ({ voteCount, votePercentage }) => ( - -
-

Vetoed

-

{compactNumber(formatUnits(voteCount || BigInt(0), 18))}

-
-
-
-
- -); - -// This should be encapsulated as soon as ODS exports this widget -const Card = function ({ children }: { children: ReactNode }) { - return ( -
- {children} -
- ); -}; - -export default VetoTally; diff --git a/plugins/tokenVoting/components/vote/votes-section.tsx b/plugins/tokenVoting/components/vote/votes-section.tsx deleted file mode 100644 index d56bf19..0000000 --- a/plugins/tokenVoting/components/vote/votes-section.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import Blockies from "react-blockies"; -import { VoteCastEvent } from "../../utils/types"; -import { formatUnits } from "viem"; -import { AddressText } from "@/components/text/address"; -import { Card } from "@aragon/ods"; -import { compactNumber } from "@/utils/numbers"; -import { If } from "@/components/if"; - -export default function VotesSection({ votes }: { votes: Array }) { - return ( -
-
- -

The proposal has no votes

-
-
- {votes.map((veto, i) => ( - - ))} -
-
-
- ); -} - -const VetoCard = function ({ veto }: { veto: VoteCastEvent }) { - return ( - -
-
- -
- {veto?.voter} -

- {compactNumber(formatUnits(veto.votingPower || BigInt(0), 18))} votes -

-
-
-
-
- ); -}; diff --git a/plugins/tokenVoting/hooks/useCanCreateProposal.ts b/plugins/tokenVoting/hooks/useCanCreateProposal.ts deleted file mode 100644 index 5347c8f..0000000 --- a/plugins/tokenVoting/hooks/useCanCreateProposal.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { Address } from "viem"; -import { useState, useEffect } from "react"; -import { useBalance, useAccount, useReadContracts } from "wagmi"; -import { TokenVotingAbi } from "../artifacts/TokenVoting.sol"; -import { PUB_CHAIN, PUB_TOKEN_VOTING_PLUGIN_ADDRESS } from "@/constants"; - -export function useCanCreateProposal() { - const { address } = useAccount(); - const [minProposerVotingPower, setMinProposerVotingPower] = useState(); - const [votingToken, setVotingToken] = useState
(); - const { data: balance } = useBalance({ - address, - token: votingToken, - chainId: PUB_CHAIN.id, - }); - - const { data: contractReads } = useReadContracts({ - contracts: [ - { - chainId: PUB_CHAIN.id, - address: PUB_TOKEN_VOTING_PLUGIN_ADDRESS, - abi: TokenVotingAbi, - functionName: "minProposerVotingPower", - }, - { - chainId: PUB_CHAIN.id, - address: PUB_TOKEN_VOTING_PLUGIN_ADDRESS, - abi: TokenVotingAbi, - functionName: "getVotingToken", - }, - ], - }); - - useEffect(() => { - if (!contractReads?.length || contractReads?.length < 2) return; - - setMinProposerVotingPower(contractReads[0].result as bigint); - setVotingToken(contractReads[1].result as Address); - }, [contractReads?.[0]?.status, contractReads?.[1]?.status]); - - if (!address) return false; - else if (!minProposerVotingPower) return true; - else if (!balance) return false; - else if (balance?.value >= minProposerVotingPower) return true; - - return false; -} diff --git a/plugins/tokenVoting/hooks/useCanVote.ts b/plugins/tokenVoting/hooks/useCanVote.ts deleted file mode 100644 index a86cfad..0000000 --- a/plugins/tokenVoting/hooks/useCanVote.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { useAccount, useBlockNumber, useReadContract } from "wagmi"; -import { TokenVotingAbi } from "../artifacts/TokenVoting.sol"; -import { useEffect } from "react"; -import { PUB_TOKEN_VOTING_PLUGIN_ADDRESS } from "@/constants"; - -export function useCanVote(proposalId: number) { - const { address } = useAccount(); - const { data: blockNumber } = useBlockNumber({ watch: true }); - - const { data: canVote, refetch: refreshCanVote } = useReadContract({ - address: PUB_TOKEN_VOTING_PLUGIN_ADDRESS, - abi: TokenVotingAbi, - functionName: "canVote", - args: [BigInt(proposalId), address!, 1], - query: { enabled: !!address }, - }); - - useEffect(() => { - refreshCanVote(); - }, [blockNumber]); - - return canVote; -} diff --git a/plugins/tokenVoting/hooks/useCreateProposal.ts b/plugins/tokenVoting/hooks/useCreateProposal.ts deleted file mode 100644 index 5b68a1d..0000000 --- a/plugins/tokenVoting/hooks/useCreateProposal.ts +++ /dev/null @@ -1,109 +0,0 @@ -import { useRouter } from "next/router"; -import { useState } from "react"; -import { ProposalMetadata, RawAction } from "@/utils/types"; -import { useAlerts } from "@/context/Alerts"; -import { PUB_APP_NAME, PUB_CHAIN, PUB_TOKEN_VOTING_PLUGIN_ADDRESS, PUB_PROJECT_URL } from "@/constants"; -import { uploadToPinata } from "@/utils/ipfs"; -import { TokenVotingAbi } from "../artifacts/TokenVoting.sol"; -import { URL_PATTERN } from "@/utils/input-values"; -import { toHex } from "viem"; -import { VotingMode } from "../utils/types"; -import { useTransactionManager } from "@/hooks/useTransactionManager"; - -const UrlRegex = new RegExp(URL_PATTERN); - -export function useCreateProposal() { - const { push } = useRouter(); - const { addAlert } = useAlerts(); - const [isCreating, setIsCreating] = useState(false); - const [title, setTitle] = useState(""); - const [summary, setSummary] = useState(""); - const [description, setDescription] = useState(""); - const [actions, setActions] = useState([]); - const [resources, setResources] = useState<{ name: string; url: string }[]>([ - { name: PUB_APP_NAME, url: PUB_PROJECT_URL }, - ]); - - const { writeContract: createProposalWrite, isConfirming } = useTransactionManager({ - onSuccessMessage: "Proposal created", - onSuccess() { - setTimeout(() => { - push("#/"); - window.scroll(0, 0); - }, 1000 * 2); - }, - onErrorMessage: "Could not create the proposal", - onError: () => setIsCreating(false), - }); - - const submitProposal = async () => { - // Check metadata - if (!title.trim()) { - return addAlert("Invalid proposal details", { - description: "Please enter a title", - type: "error", - }); - } - - if (!summary.trim()) { - return addAlert("Invalid proposal details", { - description: "Please enter a summary of what the proposal is about", - type: "error", - }); - } - - for (const item of resources) { - if (!item.name.trim()) { - return addAlert("Invalid resource name", { - description: "Please enter a name for all the resources", - type: "error", - }); - } else if (!UrlRegex.test(item.url.trim())) { - return addAlert("Invalid resource URL", { - description: "Please enter valid URL for all the resources", - type: "error", - }); - } - } - - try { - setIsCreating(true); - const proposalMetadataJsonObject: ProposalMetadata = { - title, - summary, - description, - resources, - }; - - const ipfsPin = await uploadToPinata(JSON.stringify(proposalMetadataJsonObject)); - const startDate = BigInt(0); - const endDate = BigInt(0); - - const tryEarlyExecution = false; - createProposalWrite({ - chainId: PUB_CHAIN.id, - abi: TokenVotingAbi, - address: PUB_TOKEN_VOTING_PLUGIN_ADDRESS, - functionName: "createProposal", - args: [toHex(ipfsPin), actions, BigInt(0), startDate, endDate, VotingMode.Standard, tryEarlyExecution], - }); - } catch (err) { - setIsCreating(false); - } - }; - - return { - isCreating: isCreating || isConfirming || status === "pending", - title, - summary, - description, - actions, - resources, - setTitle, - setSummary, - setDescription, - setActions, - setResources, - submitProposal, - }; -} diff --git a/plugins/tokenVoting/hooks/usePastSupply.ts b/plugins/tokenVoting/hooks/usePastSupply.ts deleted file mode 100644 index caec6bc..0000000 --- a/plugins/tokenVoting/hooks/usePastSupply.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { PUB_TOKEN_ADDRESS } from "@/constants"; -import { useReadContract } from "wagmi"; -import { parseAbi } from "viem"; - -const erc20Votes = parseAbi(["function getPastTotalSupply(uint256 blockNumber) view returns (uint256)"]); - -export function usePastSupply(snapshotBlock: bigint | undefined) { - const { data: pastSupply } = useReadContract({ - address: PUB_TOKEN_ADDRESS, - abi: erc20Votes, - functionName: "getPastTotalSupply", - args: [BigInt(snapshotBlock || 0)], - }); - - return pastSupply || BigInt(0); -} diff --git a/plugins/tokenVoting/hooks/useProposal.ts b/plugins/tokenVoting/hooks/useProposal.ts deleted file mode 100644 index 9f07d05..0000000 --- a/plugins/tokenVoting/hooks/useProposal.ts +++ /dev/null @@ -1,134 +0,0 @@ -import { useState, useEffect } from "react"; -import { useBlockNumber, usePublicClient, useReadContract } from "wagmi"; -import { Hex, fromHex, getAbiItem } from "viem"; -import { TokenVotingAbi } from "@/plugins/tokenVoting/artifacts/TokenVoting.sol"; -import { RawAction, ProposalMetadata } from "@/utils/types"; -import { Proposal, ProposalParameters, Tally } from "../utils/types"; -import { PUB_TOKEN_VOTING_PLUGIN_ADDRESS } from "@/constants"; -import { useMetadata } from "@/hooks/useMetadata"; - -type ProposalCreatedLogResponse = { - args: { - actions: RawAction[]; - allowFailureMap: bigint; - creator: string; - endDate: bigint; - startDate: bigint; - metadata: string; - proposalId: bigint; - }; -}; - -const ProposalCreatedEvent = getAbiItem({ - abi: TokenVotingAbi, - name: "ProposalCreated", -}); - -export function useProposal(proposalId: number, autoRefresh = false) { - const publicClient = usePublicClient(); - const [proposalCreationEvent, setProposalCreationEvent] = useState(); - const [metadataUri, setMetadataUri] = useState(); - const { data: blockNumber } = useBlockNumber(); - - // Proposal on-chain data - const { - data: proposalResult, - error: proposalError, - fetchStatus: proposalFetchStatus, - refetch: proposalRefetch, - } = useReadContract({ - address: PUB_TOKEN_VOTING_PLUGIN_ADDRESS, - abi: TokenVotingAbi, - functionName: "getProposal", - args: [BigInt(proposalId)], - }); - const proposalData = decodeProposalResultData(proposalResult as any); - - useEffect(() => { - if (autoRefresh) proposalRefetch(); - }, [blockNumber]); - - // Creation event - useEffect(() => { - if (!proposalData || !publicClient) return; - - publicClient - .getLogs({ - address: PUB_TOKEN_VOTING_PLUGIN_ADDRESS, - event: ProposalCreatedEvent, - args: { - proposalId: BigInt(proposalId), - }, - fromBlock: proposalData.parameters.snapshotBlock, - toBlock: proposalData.parameters.startDate, - }) - .then((logs) => { - if (!logs || !logs.length) throw new Error("No creation logs"); - - const log: ProposalCreatedLogResponse = logs[0] as any; - setProposalCreationEvent(log.args); - setMetadataUri(fromHex(log.args.metadata as Hex, "string")); - }) - .catch((err) => { - console.error("Could not fetch the proposal details", err); - }); - }, [proposalData?.tally.yes, proposalData?.tally.no, proposalData?.tally.abstain, !!publicClient]); - - // JSON metadata - const { - data: metadataContent, - isLoading: metadataLoading, - error: metadataError, - } = useMetadata(metadataUri); - - const proposal = arrangeProposalData(proposalData, proposalCreationEvent, metadataContent); - - return { - proposal, - status: { - proposalReady: proposalFetchStatus === "idle", - proposalLoading: proposalFetchStatus === "fetching", - proposalError, - metadataReady: !metadataError && !metadataLoading && !!metadataContent, - metadataLoading, - metadataError: metadataError !== undefined, - }, - }; -} - -// Helpers - -function decodeProposalResultData(data?: Array) { - if (!data?.length || data.length < 6) return null; - - return { - active: data[0] as boolean, - executed: data[1] as boolean, - parameters: data[2] as ProposalParameters, - tally: data[3] as Tally, - actions: data[4] as Array, - allowFailureMap: data[5] as bigint, - }; -} - -function arrangeProposalData( - proposalData?: ReturnType, - creationEvent?: ProposalCreatedLogResponse["args"], - metadata?: ProposalMetadata -): Proposal | null { - if (!proposalData) return null; - - return { - actions: proposalData.actions, - active: proposalData.active, - executed: proposalData.executed, - parameters: proposalData.parameters, - tally: proposalData.tally, - allowFailureMap: proposalData.allowFailureMap, - creator: creationEvent?.creator || "", - title: metadata?.title || "", - summary: metadata?.summary || "", - description: metadata?.description || "", - resources: metadata?.resources || [], - }; -} diff --git a/plugins/tokenVoting/hooks/useProposalExecute.ts b/plugins/tokenVoting/hooks/useProposalExecute.ts deleted file mode 100644 index 1d66306..0000000 --- a/plugins/tokenVoting/hooks/useProposalExecute.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { useState } from "react"; -import { useReadContract } from "wagmi"; -import { TokenVotingAbi } from "../artifacts/TokenVoting.sol"; -import { useRouter } from "next/router"; -import { PUB_CHAIN, PUB_TOKEN_VOTING_PLUGIN_ADDRESS } from "@/constants"; -import { useTransactionManager } from "@/hooks/useTransactionManager"; -import { DaoAbi } from "@/artifacts/DAO.sol"; - -export function useProposalExecute(proposalId: number) { - const { reload } = useRouter(); - const [isExecuting, setIsExecuting] = useState(false); - - const { - data: canExecute, - isError: isCanVoteError, - isLoading: isCanVoteLoading, - } = useReadContract({ - address: PUB_TOKEN_VOTING_PLUGIN_ADDRESS, - abi: TokenVotingAbi, - chainId: PUB_CHAIN.id, - functionName: "canExecute", - args: [BigInt(proposalId)], - }); - - const { writeContract, isConfirming, isConfirmed } = useTransactionManager({ - onSuccessMessage: "Proposal executed", - onSuccess() { - setTimeout(() => reload(), 1000 * 2); - }, - onErrorMessage: "Could not execute the proposal", - onErrorDescription: "The proposal may contain actions with invalid operations", - onError() { - setIsExecuting(false); - }, - }); - - const executeProposal = () => { - if (!canExecute) return; - else if (typeof proposalId === "undefined") return; - - setIsExecuting(true); - - writeContract({ - chainId: PUB_CHAIN.id, - abi: TokenVotingAbi.concat(DaoAbi as any), - address: PUB_TOKEN_VOTING_PLUGIN_ADDRESS, - functionName: "execute", - args: [BigInt(proposalId)], - }); - }; - - return { - executeProposal, - canExecute: !isCanVoteError && !isCanVoteLoading && !isConfirmed && !!canExecute, - isConfirming: isExecuting || isConfirming, - isConfirmed, - }; -} diff --git a/plugins/tokenVoting/hooks/useProposalVariantStatus.ts b/plugins/tokenVoting/hooks/useProposalVariantStatus.ts deleted file mode 100644 index 2c50e41..0000000 --- a/plugins/tokenVoting/hooks/useProposalVariantStatus.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { useState, useEffect } from "react"; -import { Proposal } from "../utils/types"; -import { ProposalStatus } from "@aragon/ods"; -import { useToken } from "./useToken"; - -export const useProposalVariantStatus = (proposal: Proposal) => { - const [status, setStatus] = useState({ variant: "", label: "" }); - const { tokenSupply: totalSupply } = useToken(); - - useEffect(() => { - if (!proposal || !proposal?.parameters || !totalSupply) return; - - const minVotingPower = (totalSupply * BigInt(proposal.parameters.minVotingPower)) / BigInt(1_000_000); - const totalVotes = proposal.tally.yes + proposal.tally.no + proposal.tally.abstain; - - if (proposal?.active) { - setStatus({ variant: "info", label: "Active" }); - } else if (proposal?.executed) { - setStatus({ variant: "primary", label: "Executed" }); - } else if (totalVotes < minVotingPower) { - setStatus({ variant: "critical", label: "Low turnout" }); - } else if (proposal.tally.yes > proposal.tally.no && proposal.tally.yes > proposal.tally.abstain) { - setStatus({ variant: "success", label: "Executable" }); - } else if (proposal.tally.no > proposal.tally.yes && proposal.tally.no > proposal.tally.abstain) { - setStatus({ variant: "critical", label: "Defeated" }); - } else if (proposal.tally.abstain > proposal.tally.no && proposal.tally.abstain > proposal.tally.yes) { - if (proposal.tally.yes > proposal.tally.no) { - setStatus({ variant: "success", label: "Executable" }); - } else { - setStatus({ variant: "critical", label: "Defeated" }); - } - } - }, [proposal?.tally, proposal?.active, proposal?.executed, proposal?.parameters?.minVotingPower, totalSupply]); - - return status; -}; - -export const useProposalStatus = (proposal: Proposal) => { - const [status, setStatus] = useState(); - const { tokenSupply: totalSupply } = useToken(); - useEffect(() => { - if (!proposal || !proposal?.parameters || !totalSupply) return; - - const minVotingPower = (totalSupply * BigInt(proposal.parameters.minVotingPower)) / BigInt(1_000_000); - const totalVotes = proposal.tally.yes + proposal.tally.no + proposal.tally.abstain; - - if (proposal?.active) { - setStatus(ProposalStatus.ACTIVE); - } else if (proposal?.executed) { - setStatus(ProposalStatus.EXECUTED); - } else if (totalVotes < minVotingPower) { - setStatus(ProposalStatus.FAILED); - } else if (proposal.tally.yes > proposal.tally.no && proposal.tally.yes > proposal.tally.abstain) { - setStatus(ProposalStatus.EXECUTABLE); - } else if (proposal.tally.no > proposal.tally.yes && proposal.tally.no > proposal.tally.abstain) { - setStatus(ProposalStatus.REJECTED); - } else if (proposal.tally.abstain > proposal.tally.no && proposal.tally.abstain > proposal.tally.yes) { - if (proposal.tally.yes > proposal.tally.no) { - setStatus(ProposalStatus.EXECUTABLE); - } else { - setStatus(ProposalStatus.REJECTED); - } - } - }, [proposal?.tally, proposal?.active, proposal?.executed, proposal?.parameters?.minVotingPower, totalSupply]); - - return status; -}; diff --git a/plugins/tokenVoting/hooks/useProposalVoteList.ts b/plugins/tokenVoting/hooks/useProposalVoteList.ts deleted file mode 100644 index da88e1c..0000000 --- a/plugins/tokenVoting/hooks/useProposalVoteList.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { useState, useEffect } from "react"; -import { getAbiItem } from "viem"; -import { TokenVotingAbi } from "../artifacts/TokenVoting.sol"; -import { Proposal, VoteCastEvent } from "../utils/types"; -import { usePublicClient } from "wagmi"; -import { PUB_TOKEN_VOTING_PLUGIN_ADDRESS } from "@/constants"; - -const event = getAbiItem({ abi: TokenVotingAbi, name: "VoteCast" }); - -export function useProposalVoteList(proposalId: number, proposal: Proposal | null) { - const publicClient = usePublicClient(); - const [proposalLogs, setLogs] = useState([]); - - async function getLogs() { - if (!proposal?.parameters?.snapshotBlock) return; - else if (!publicClient) return; - - const logs = await publicClient.getLogs({ - address: PUB_TOKEN_VOTING_PLUGIN_ADDRESS, - event: event, - args: { - proposalId: BigInt(proposalId), - }, - fromBlock: proposal.parameters.snapshotBlock, - toBlock: "latest", // TODO: Make this variable between 'latest' and proposal last block - }); - - const newLogs = logs.flatMap((log) => log.args); - if (newLogs.length > proposalLogs.length) setLogs(newLogs); - } - - useEffect(() => { - getLogs(); - }, [proposalId, proposal?.parameters?.snapshotBlock]); - - return proposalLogs; -} diff --git a/plugins/tokenVoting/hooks/useProposalVoting.ts b/plugins/tokenVoting/hooks/useProposalVoting.ts deleted file mode 100644 index f0934a5..0000000 --- a/plugins/tokenVoting/hooks/useProposalVoting.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { TokenVotingAbi } from "@/plugins/tokenVoting/artifacts/TokenVoting.sol"; -import { useRouter } from "next/router"; -import { PUB_TOKEN_VOTING_PLUGIN_ADDRESS } from "@/constants"; -import { useTransactionManager } from "@/hooks/useTransactionManager"; - -export function useProposalVoting(proposalIdx: number) { - const { reload } = useRouter(); - - const { - writeContract, - status: votingStatus, - isConfirming, - isConfirmed, - } = useTransactionManager({ - onSuccessMessage: "Vote registered", - onSuccess: reload, - onErrorMessage: "Could not submit the vote", - }); - - const voteProposal = (votingOption: number, autoExecute: boolean = false) => { - writeContract({ - abi: TokenVotingAbi, - address: PUB_TOKEN_VOTING_PLUGIN_ADDRESS, - functionName: "vote", - args: [BigInt(proposalIdx), votingOption, autoExecute], - }); - }; - - return { - voteProposal, - status: votingStatus, - isConfirming, - isConfirmed, - }; -} diff --git a/plugins/tokenVoting/hooks/useToken.ts b/plugins/tokenVoting/hooks/useToken.ts deleted file mode 100644 index dd4f6dc..0000000 --- a/plugins/tokenVoting/hooks/useToken.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { erc20Abi } from "viem"; -import { useReadContract } from "wagmi"; -import { PUB_TOKEN_ADDRESS } from "@/constants"; - -export function useToken() { - const { - data: tokenSupply, - isError: isError1, - isLoading: isLoading1, - } = useReadContract({ - address: PUB_TOKEN_ADDRESS, - abi: erc20Abi, - functionName: "totalSupply", - }); - - const { - data: tokenSymbol, - isError: isError2, - isLoading: isLoading2, - } = useReadContract({ - address: PUB_TOKEN_ADDRESS, - abi: erc20Abi, - functionName: "symbol", - }); - - return { - address: PUB_TOKEN_ADDRESS, - tokenSupply, - symbol: tokenSymbol, - status: { - isLoading: isLoading1 || isLoading2, - isError: isError1 || isError2, - }, - }; -} diff --git a/plugins/tokenVoting/index.tsx b/plugins/tokenVoting/index.tsx deleted file mode 100644 index 5d029fb..0000000 --- a/plugins/tokenVoting/index.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { NotFound } from "@/components/not-found"; -import ProposalCreate from "./pages/new"; -import ProposalList from "./pages/proposal-list"; -import ProposalDetail from "./pages/proposal"; -import { useUrl } from "@/hooks/useUrl"; - -export default function PluginPage() { - // Select the inner pages to display depending on the URL hash - const { hash } = useUrl(); - - if (!hash || hash === "#/") return ; - else if (hash === "#/new") return ; - else if (hash.startsWith("#/proposals/")) { - const id = hash.replace("#/proposals/", ""); - - return ; - } - - // Default not found page - return ; -} diff --git a/plugins/tokenVoting/pages/new.tsx b/plugins/tokenVoting/pages/new.tsx deleted file mode 100644 index 76329cb..0000000 --- a/plugins/tokenVoting/pages/new.tsx +++ /dev/null @@ -1,283 +0,0 @@ -import { Button, IconType, InputText, TextAreaRichText, Tag } from "@aragon/ods"; -import React, { ReactNode, useState } from "react"; -import { RawAction } from "@/utils/types"; -import { Else, ElseIf, If, Then } from "@/components/if"; -import { MainSection } from "@/components/layout/main-section"; -import { useCreateProposal } from "../hooks/useCreateProposal"; -import { useAccount } from "wagmi"; -import { useCanCreateProposal } from "../hooks/useCanCreateProposal"; -import { MissingContentView } from "@/components/MissingContentView"; -import { useWeb3Modal } from "@web3modal/wagmi/react"; -import { Address } from "viem"; -import { NewActionDialog, NewActionType } from "@/components/dialogs/NewActionDialog"; -import { AddActionCard } from "@/components/cards/AddActionCard"; -import { ProposalActions } from "@/components/proposalActions/proposalActions"; -import { downloadAsFile } from "@/utils/download-as-file"; -import { encodeActionsAsJson } from "@/utils/json-actions"; - -export default function Create() { - const { address: selfAddress, isConnected } = useAccount(); - const canCreate = useCanCreateProposal(); - const [addActionType, setAddActionType] = useState(""); - const { - title, - summary, - description, - actions, - resources, - setTitle, - setSummary, - setDescription, - setActions, - setResources, - isCreating, - submitProposal, - } = useCreateProposal(); - - const handleTitleInput = (event: React.ChangeEvent) => { - setTitle(event?.target?.value); - }; - const handleSummaryInput = (event: React.ChangeEvent) => { - setSummary(event?.target?.value); - }; - const handleNewActionDialogClose = (newAction: RawAction[] | null) => { - if (!newAction) { - setAddActionType(""); - return; - } - - setActions(actions.concat(newAction)); - setAddActionType(""); - }; - const onRemoveAction = (idx: number) => { - actions.splice(idx, 1); - setActions([].concat(actions as any)); - }; - const removeResource = (idx: number) => { - resources.splice(idx, 1); - setResources([].concat(resources as any)); - }; - const onResourceNameChange = (event: React.ChangeEvent, idx: number) => { - resources[idx].name = event.target.value; - setResources([].concat(resources as any)); - }; - const onResourceUrlChange = (event: React.ChangeEvent, idx: number) => { - resources[idx].url = event.target.value; - setResources([].concat(resources as any)); - }; - - const exportAsJson = () => { - if (!actions.length) return; - - const strResult = encodeActionsAsJson(actions); - downloadAsFile("actions.json", strResult, "text/json"); - }; - - return ( - -
-

- Create Proposal -

- - -
- -
-
- -
-
- -
- -
-
-
-

Resources

- -
-

- Add links to external resources -

-
-
- -

- There are no resources yet. Click the button below to add the first one. -

-
- {resources.map((resource, idx) => { - return ( -
-
- onResourceNameChange(e, idx)} - placeholder="GitHub, Twitter, etc." - /> -
- onResourceUrlChange(e, idx)} - placeholder="https://..." - readOnly={isCreating} - /> -
- ); - })} -
- - - -
- - {/* Actions */} - - onRemoveAction(idx)} - /> - - - - - -
- setAddActionType("withdrawal")} - /> - setAddActionType("select-abi-function")} - /> - setAddActionType("calldata")} - /> - setAddActionType("import-json")} - /> -
- - {/* Dialog */} - - handleNewActionDialogClose(newActions)} - /> - - {/* Submit */} - -
- -
-
-
-
- ); -} - -const PlaceHolderOr = ({ - selfAddress, - isConnected, - canCreate, - children, -}: { - selfAddress: Address | undefined; - isConnected: boolean; - canCreate: boolean | undefined; - children: ReactNode; -}) => { - const { open } = useWeb3Modal(); - return ( - - - {/* Not connected */} - open()}> - Please connect your wallet to continue. - - - - {/* Not a member */} - - You cannot create proposals on the multisig because you are not currently defined as a member. - - - {children} - - ); -}; diff --git a/plugins/tokenVoting/pages/proposal-list.tsx b/plugins/tokenVoting/pages/proposal-list.tsx deleted file mode 100644 index ce90b93..0000000 --- a/plugins/tokenVoting/pages/proposal-list.tsx +++ /dev/null @@ -1,100 +0,0 @@ -import { useAccount, useBlockNumber, useReadContract } from "wagmi"; -import { type ReactNode, useEffect } from "react"; -import ProposalCard from "../components/proposal"; -import { TokenVotingAbi } from "../artifacts/TokenVoting.sol"; -import { Button, DataList, IconType, ProposalDataListItemSkeleton, type DataListState } from "@aragon/ods"; -import { useCanCreateProposal } from "../hooks/useCanCreateProposal"; -import Link from "next/link"; -import { Else, If, Then } from "@/components/if"; -import { PUB_TOKEN_VOTING_PLUGIN_ADDRESS, PUB_CHAIN } from "@/constants"; -import { MainSection } from "@/components/layout/main-section"; -import { MissingContentView } from "@/components/MissingContentView"; - -const DEFAULT_PAGE_SIZE = 6; - -export default function Proposals() { - const { isConnected } = useAccount(); - const canCreate = useCanCreateProposal(); - const { data: blockNumber } = useBlockNumber({ watch: true }); - - const { - data: proposalCountResponse, - error: isError, - isLoading, - isFetching: isFetchingNextPage, - refetch, - } = useReadContract({ - address: PUB_TOKEN_VOTING_PLUGIN_ADDRESS, - abi: TokenVotingAbi, - functionName: "proposalCount", - chainId: PUB_CHAIN.id, - }); - const proposalCount = Number(proposalCountResponse); - - useEffect(() => { - refetch(); - }, [blockNumber]); - - const entityLabel = proposalCount === 1 ? "Proposal" : "Proposals"; - - let dataListState: DataListState = "idle"; - if (isLoading && !proposalCount) { - dataListState = "initialLoading"; - } else if (isError) { - dataListState = "error"; - } else if (isFetchingNextPage) { - dataListState = "fetchingNextPage"; - } - - return ( - - -

- Proposals -

-
- - - - - -
-
- - - - - No proposals have been created yet. Here you will see the available proposals.{" "} - Create your first proposal. - - - - - - {proposalCount && - Array.from(Array(proposalCount || 0)?.keys()) - .reverse() - ?.map((proposalIndex) => ( - // TODO: update with router agnostic ODS DataListItem - - ))} - - - - - -
- ); -} - -function SectionView({ children }: { children: ReactNode }) { - return
{children}
; -} diff --git a/plugins/tokenVoting/pages/proposal.tsx b/plugins/tokenVoting/pages/proposal.tsx deleted file mode 100644 index cbb8b58..0000000 --- a/plugins/tokenVoting/pages/proposal.tsx +++ /dev/null @@ -1,275 +0,0 @@ -import { useProposal } from "../hooks/useProposal"; -import ProposalHeader from "../components/proposal/header"; -import { PleaseWaitSpinner } from "@/components/please-wait"; -import { useProposalVoting } from "../hooks/useProposalVoting"; -import { useProposalExecute } from "../hooks/useProposalExecute"; -import { BodySection } from "@/components/proposal/proposalBodySection"; -import { type IBreakdownMajorityVotingResult, ProposalVoting } from "@/components/proposalVoting"; -import type { ITransformedStage, IVote } from "@/utils/types"; -import { ProposalStages } from "@/utils/types"; -import { useProposalStatus } from "../hooks/useProposalVariantStatus"; -import dayjs from "dayjs"; -import { ProposalActions } from "@/components/proposalActions/proposalActions"; -import { CardResources } from "@/components/proposal/cardResources"; -import { type Address, formatEther } from "viem"; -import { useToken } from "../hooks/useToken"; -// import { usePastSupply } from "../hooks/usePastSupply"; -import { ElseIf, If, Then } from "@/components/if"; -import { AlertCard, Button, DialogContent, DialogFooter, DialogHeader, DialogRoot, ProposalStatus } from "@aragon/ods"; -import { useAccount } from "wagmi"; -import { ADDRESS_ZERO } from "@/utils/evm"; -import { AddressText } from "@/components/text/address"; -import Link from "next/link"; -import { useCanVote } from "../hooks/useCanVote"; -import { useState } from "react"; -import { PUB_TOKEN_SYMBOL } from "@/constants"; -import { useProposalVoteList } from "../hooks/useProposalVoteList"; - -const ZERO = BigInt(0); -const ABSTAIN_VALUE = 1; -const VOTE_YES_VALUE = 2; -const VOTE_NO_VALUE = 3; - -export default function ProposalDetail({ index: proposalIdx }: { index: number }) { - const { address } = useAccount(); - const { voteProposal, isConfirming: isConfirmingVote } = useProposalVoting(proposalIdx); - const { proposal, status: proposalFetchStatus } = useProposal(proposalIdx); - const canVote = useCanVote(proposalIdx); - // const pastSupply = usePastSupply(proposal?.parameters.snapshotBlock); - const votes = useProposalVoteList(proposalIdx, proposal); - const { symbol: tokenSymbol } = useToken(); - const balance = 0; - const delegatesTo = "0x0" as Address; - const [showVotingModal, setShowVotingModal] = useState(false); - const { executeProposal, canExecute, isConfirming: isConfirmingExecution } = useProposalExecute(proposalIdx); - const showProposalLoading = getShowProposalLoading(proposal, proposalFetchStatus); - const proposalStatus = useProposalStatus(proposal!); - - const startDate = dayjs(Number(proposal?.parameters.startDate) * 1000).toString(); - const endDate = dayjs(Number(proposal?.parameters.endDate) * 1000).toString(); - const totalVotes = - (proposal?.tally.yes || BigInt(0)) + (proposal?.tally.no || BigInt(0)) + (proposal?.tally.abstain || BigInt(0)); - - let cta: IBreakdownMajorityVotingResult["cta"]; - if (proposal?.executed) { - cta = { - disabled: true, - label: "Executed", - }; - } else if (proposalStatus === ProposalStatus.ACCEPTED) { - cta = { - disabled: !canExecute || !proposal?.actions.length, - isLoading: isConfirmingExecution, - label: proposal?.actions.length ? "Execute" : "No actions to execute", - onClick: executeProposal, - }; - } else if (proposalStatus === ProposalStatus.ACTIVE) { - cta = { - disabled: !canVote, - isLoading: isConfirmingVote, - label: "Vote", - onClick: (option?: number) => (option ? onVote(option) : null), - }; - } - - const onVote = (voteOption: number | null) => { - switch (voteOption) { - case 1: - return voteProposal(VOTE_YES_VALUE, true); - case 2: - return voteProposal(VOTE_NO_VALUE, true); - case 3: - return voteProposal(ABSTAIN_VALUE, true); - } - }; - - const proposalStage: ITransformedStage[] = [ - { - id: "1", - type: ProposalStages.TOKEN_VOTING, - variant: "majorityVoting", - title: "Token voting", - status: proposalStatus!, - disabled: false, - proposalId: proposalIdx.toString(), - providerId: "1", - result: { - cta, - votingScores: [ - { - option: "Yes", - voteAmount: formatEther(proposal?.tally.yes || BigInt(0)), - votePercentage: - Number(((proposal?.tally.yes || BigInt(0)) * BigInt(10_000)) / (totalVotes || BigInt(1))) / 100, - tokenSymbol: tokenSymbol || PUB_TOKEN_SYMBOL, - }, - { - option: "No", - voteAmount: formatEther(proposal?.tally.no || BigInt(0)), - votePercentage: - Number(((proposal?.tally.no || BigInt(0)) * BigInt(10_000)) / (totalVotes || BigInt(1))) / 100, - tokenSymbol: tokenSymbol || PUB_TOKEN_SYMBOL, - }, - { - option: "Abstain", - voteAmount: formatEther(proposal?.tally.abstain || BigInt(0)), - votePercentage: - Number(((proposal?.tally.abstain || BigInt(0)) * BigInt(10_000)) / (totalVotes || BigInt(1))) / 100, - tokenSymbol: tokenSymbol || PUB_TOKEN_SYMBOL, - }, - ], - proposalId: proposalIdx.toString(), - }, - details: { - censusTimestamp: Number(proposal?.parameters.snapshotBlock || 0) || 0, - startDate, - endDate, - strategy: "Token voting", - options: "Vote", - }, - votes: votes.map( - ({ voter, voteOption: opt }) => - ({ - address: voter, - variant: opt === ABSTAIN_VALUE ? "abstain" : opt === VOTE_YES_VALUE ? "yes" : "no", - }) as IVote - ), - }, - ]; - - const hasBalance = !!balance && balance > ZERO; - const delegatingToSomeoneElse = !!delegatesTo && delegatesTo !== address && delegatesTo !== ADDRESS_ZERO; - const delegatedToZero = !!delegatesTo && delegatesTo === ADDRESS_ZERO; - - if (!proposal || showProposalLoading) { - return ( -
- -
- ); - } - - return ( -
- - -
-
-
- - - - - - -
-
- -
-
-
- -
- ); -} - -export const VoteOptionDialog: React.FC<{ show: boolean; onClose: (voteOption: number | null) => void }> = (props) => { - const { show, onClose } = props; - - const dismiss = () => { - onClose(null); - }; - - if (!show) { - return <>; - } - - return ( - - dismiss()} onBackClick={() => dismiss()} /> - -
- - - - -
-
- -
- ); -}; - -const NoVotePowerWarning = ({ - delegatingToSomeoneElse, - delegatesTo, - delegatedToZero, - address, - canVote, -}: { - delegatingToSomeoneElse: boolean; - delegatesTo: Address | undefined; - delegatedToZero: boolean; - address: Address | undefined; - canVote: boolean; -}) => { - return ( - - - - You are currently delegating your voting power to {delegatesTo}. - If you wish to participate by yourself in future proposals, - - - You have not self delegated your voting power to participate in the DAO. If you wish to participate in - future proposals, - - -  make sure that{" "} - - your voting power is self delegated - - . - - } - message={ - delegatingToSomeoneElse - ? "Your voting power is currently delegated" - : canVote - ? "You cannot vote on new proposals" - : "You cannot vote" - } - variant="info" - /> - ); -}; - -function getShowProposalLoading( - proposal: ReturnType["proposal"], - status: ReturnType["status"] -) { - if (!proposal && status.proposalLoading) return true; - else if (status.metadataLoading && !status.metadataError) return true; - else if (!proposal?.title && !status.metadataError) return true; - - return false; -} diff --git a/plugins/tokenVoting/utils/proposal-status.ts b/plugins/tokenVoting/utils/proposal-status.ts deleted file mode 100644 index 2bce2c6..0000000 --- a/plugins/tokenVoting/utils/proposal-status.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { Proposal, VotingMode } from "./types"; -export const RATIO_BASE = 1_000_000; - -export function getProposalStatusVariant(proposal: Proposal, tokenSupply: bigint) { - // Terminal cases - if (!proposal?.tally) return { variant: "info", label: "(Loading)" }; - else if (proposal.executed) return { variant: "primary", label: "Executed" }; - - const supportThreshold = proposal.parameters.supportThreshold; - - if (!proposal.active) { - // Defeated or executable? - const yesNoVotes = proposal.tally.no + proposal.tally.yes; - if (!yesNoVotes) return { variant: "critical", label: "Defeated" }; - - const totalVotes = proposal.tally.abstain + yesNoVotes; - if (totalVotes < proposal.parameters.minVotingPower) { - return { variant: "critical", label: "Low turnout" }; - } - - const finalRatio = (BigInt(RATIO_BASE) * proposal.tally.yes) / yesNoVotes; - - if (finalRatio > BigInt(supportThreshold)) { - return { variant: "success", label: "Executable" }; - } - return { variant: "critical", label: "Defeated" }; - } - - // Active or early execution? - const noVotesWorstCase = tokenSupply - proposal.tally.yes - proposal.tally.abstain; - const totalYesNoWc = proposal.tally.yes + noVotesWorstCase; - - if (proposal.parameters.votingMode == VotingMode.EarlyExecution) { - const currentRatio = (BigInt(RATIO_BASE) * proposal.tally.yes) / totalYesNoWc; - if (currentRatio > BigInt(supportThreshold)) { - return { variant: "success", label: "Executable" }; - } - } - return { variant: "info", label: "Active" }; -} diff --git a/plugins/tokenVoting/utils/types.tsx b/plugins/tokenVoting/utils/types.tsx deleted file mode 100644 index 9ff0434..0000000 --- a/plugins/tokenVoting/utils/types.tsx +++ /dev/null @@ -1,48 +0,0 @@ -import { Address } from "viem"; -import { IProposalResource, RawAction } from "@/utils/types"; - -export type ProposalInputs = { - proposalId: bigint; -}; - -export enum VotingMode { - Standard, - EarlyExecution, - VoteReplacement, -} - -export type ProposalParameters = { - votingMode: VotingMode; - supportThreshold: number; - startDate: bigint; - endDate: bigint; - snapshotBlock: bigint; - minVotingPower: bigint; -}; - -export type Tally = { - abstain: bigint; - yes: bigint; - no: bigint; -}; - -export type Proposal = { - active: boolean; - executed: boolean; - parameters: ProposalParameters; - tally: Tally; - actions: RawAction[]; - allowFailureMap: bigint; - creator: string; - title: string; - summary: string; - description: string; - resources: IProposalResource[]; -}; - -export type VoteCastEvent = { - voter?: Address; - proposalId?: bigint; - voteOption?: number; - votingPower?: bigint; -};