From 991727d800ddde6ac7b768ff225fc92a960e3c9b Mon Sep 17 00:00:00 2001 From: brightiron Date: Sun, 31 Mar 2024 13:02:29 -0500 Subject: [PATCH 01/12] governance --- package.json | 4 +- src/App.tsx | 6 +- src/abi/OlympusGovernorBravo.json | 657 ++++++++++ src/abi/Timelock.json | 205 ++++ src/constants/addresses.ts | 5 + src/constants/contracts.ts | 8 + src/helpers/index.tsx | 5 + src/hooks/wagmi.ts | 5 +- src/themes/dark.js | 6 + src/themes/light.js | 6 + src/views/Governance/Components/CallData.tsx | 205 ++++ .../Components/ContractParameters.tsx | 106 ++ .../Governance/Components/CurrentVotes.tsx | 74 ++ .../{ => Components}/GovernanceTableRow.tsx | 0 .../Components/ProposalContainer.tsx | 93 ++ src/views/Governance/Components/Status.tsx | 117 ++ src/views/Governance/Components/VoteModal.tsx | 104 ++ .../Components/VotingOutcomeBar.tsx | 70 ++ .../DelegateVotingModal.tsx} | 12 +- .../Delegation/DelegationMessage.tsx | 35 + src/views/Governance/Delegation/index.tsx | 118 ++ src/views/Governance/Governance.tsx | 153 --- src/views/Governance/Proposals/index.tsx | 197 +++ .../helpers/fetchFunctionInterface.ts | 25 + src/views/Governance/helpers/index.ts | 40 + .../hooks/dev/GovernanceDevTools.tsx | 33 + .../Governance/hooks/dev/useAddChain.tsx | 41 + .../hooks/dev/useCancelProposal.tsx | 16 + .../hooks/dev/useCreateProposal.tsx | 164 +++ .../Governance/hooks/dev/useMineBlocks.tsx | 16 + .../Governance/hooks/dev/useVetoProposal.tsx | 17 + .../Governance/hooks/useActivateProposal.tsx | 26 + .../Governance/hooks/useExecuteProposal.tsx | 26 + .../Governance/hooks/useGetCanceledTime.tsx | 28 + .../hooks/useGetContractParameters.tsx | 42 + .../hooks/useGetCurrentBlockTime.tsx | 9 + .../Governance/hooks/useGetExecutedTime.tsx | 28 + .../hooks/useGetProposalDetails.tsx | 53 + .../Governance/hooks/useGetProposals.tsx | 45 + .../Governance/hooks/useGetQueuedTime.tsx | 24 + src/views/Governance/hooks/useGetReceipt.tsx | 20 + .../Governance/hooks/useGetVetoedTime.tsx | 28 + .../Governance/hooks/useGetVotingWeight.tsx | 34 + .../hooks/useGovernanceDelegationCheck.tsx | 54 + .../Governance/hooks/useQueueProposal.tsx | 26 + .../Governance/hooks/useVoteForProposal.tsx | 35 + src/views/Governance/index.tsx | 54 + yarn.lock | 1091 +++++++++++------ 48 files changed, 3653 insertions(+), 513 deletions(-) create mode 100644 src/abi/OlympusGovernorBravo.json create mode 100644 src/abi/Timelock.json create mode 100644 src/views/Governance/Components/CallData.tsx create mode 100644 src/views/Governance/Components/ContractParameters.tsx create mode 100644 src/views/Governance/Components/CurrentVotes.tsx rename src/views/Governance/{ => Components}/GovernanceTableRow.tsx (100%) create mode 100644 src/views/Governance/Components/ProposalContainer.tsx create mode 100644 src/views/Governance/Components/Status.tsx create mode 100644 src/views/Governance/Components/VoteModal.tsx create mode 100644 src/views/Governance/Components/VotingOutcomeBar.tsx rename src/views/Governance/{DelegateVoting.tsx => Delegation/DelegateVotingModal.tsx} (90%) create mode 100644 src/views/Governance/Delegation/DelegationMessage.tsx create mode 100644 src/views/Governance/Delegation/index.tsx delete mode 100644 src/views/Governance/Governance.tsx create mode 100644 src/views/Governance/Proposals/index.tsx create mode 100644 src/views/Governance/helpers/fetchFunctionInterface.ts create mode 100644 src/views/Governance/helpers/index.ts create mode 100644 src/views/Governance/hooks/dev/GovernanceDevTools.tsx create mode 100644 src/views/Governance/hooks/dev/useAddChain.tsx create mode 100644 src/views/Governance/hooks/dev/useCancelProposal.tsx create mode 100644 src/views/Governance/hooks/dev/useCreateProposal.tsx create mode 100644 src/views/Governance/hooks/dev/useMineBlocks.tsx create mode 100644 src/views/Governance/hooks/dev/useVetoProposal.tsx create mode 100644 src/views/Governance/hooks/useActivateProposal.tsx create mode 100644 src/views/Governance/hooks/useExecuteProposal.tsx create mode 100644 src/views/Governance/hooks/useGetCanceledTime.tsx create mode 100644 src/views/Governance/hooks/useGetContractParameters.tsx create mode 100644 src/views/Governance/hooks/useGetCurrentBlockTime.tsx create mode 100644 src/views/Governance/hooks/useGetExecutedTime.tsx create mode 100644 src/views/Governance/hooks/useGetProposalDetails.tsx create mode 100644 src/views/Governance/hooks/useGetProposals.tsx create mode 100644 src/views/Governance/hooks/useGetQueuedTime.tsx create mode 100644 src/views/Governance/hooks/useGetReceipt.tsx create mode 100644 src/views/Governance/hooks/useGetVetoedTime.tsx create mode 100644 src/views/Governance/hooks/useGetVotingWeight.tsx create mode 100644 src/views/Governance/hooks/useGovernanceDelegationCheck.tsx create mode 100644 src/views/Governance/hooks/useQueueProposal.tsx create mode 100644 src/views/Governance/hooks/useVoteForProposal.tsx create mode 100644 src/views/Governance/index.tsx diff --git a/package.json b/package.json index bdf59068f2..2d19dc64f5 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "@mui/x-data-grid": "^6.18.5", "@olympusdao/component-library": "3.1.7", "@olympusdao/treasury-subgraph-client": "^1.1.0", + "@openchainxyz/abi-guesser": "^1.0.2", "@rainbow-me/rainbowkit": "^0.12.18", "@reduxjs/toolkit": "^1.9.3", "@tanstack/react-query": "^4.2.3", @@ -58,12 +59,13 @@ "react-ga": "^3.3.1", "react-ga4": "^2.1.0", "react-hot-toast": "^2.4.1", - "react-markdown": "^8.0.7", + "react-markdown": "^9.0.1", "react-redux": "^8.0.5", "react-router-dom": "^6.21.1", "react-step-progress-bar": "^1.0.3", "react-uid": "^2.3.2", "recharts": "^2.10.3", + "remark-gfm": "^4.0.0", "tinycolor2": "^1.6.0", "typescript": "^5.3.3", "wagmi": "^0.12.19" diff --git a/src/App.tsx b/src/App.tsx index 170f3e77dd..ce8260c14c 100755 --- a/src/App.tsx +++ b/src/App.tsx @@ -33,7 +33,9 @@ import { girth as gTheme } from "src/themes/girth.js"; import { light as lightTheme } from "src/themes/light.js"; import { BondModalContainer } from "src/views/Bond/components/BondModal/BondModal"; import { BondModalContainerV3 } from "src/views/Bond/components/BondModal/BondModalContainerV3"; -import { Governance } from "src/views/Governance/Governance"; +import { Governance } from "src/views/Governance"; +import { Delegate } from "src/views/Governance/Delegation"; +import { ProposalPage } from "src/views/Governance/Proposals"; import { Cooler } from "src/views/Lending/Cooler"; import { Vault } from "src/views/Liquidity/Vault"; import { MyBalances } from "src/views/MyBalances"; @@ -255,6 +257,8 @@ function App() { } /> } /> } /> + } /> + } /> } /> diff --git a/src/abi/OlympusGovernorBravo.json b/src/abi/OlympusGovernorBravo.json new file mode 100644 index 0000000000..952df57c06 --- /dev/null +++ b/src/abi/OlympusGovernorBravo.json @@ -0,0 +1,657 @@ +{ + "abi": [ + { "inputs": [], "name": "GovernorBravo_AddressZero", "type": "error" }, + { "inputs": [], "name": "GovernorBravo_AlreadyInitialized", "type": "error" }, + { "inputs": [], "name": "GovernorBravo_Cancel_AboveThreshold", "type": "error" }, + { "inputs": [], "name": "GovernorBravo_Cancel_AlreadyExecuted", "type": "error" }, + { "inputs": [], "name": "GovernorBravo_Cancel_WhitelistedProposer", "type": "error" }, + { "inputs": [], "name": "GovernorBravo_Emergency_SupplyTooLow", "type": "error" }, + { "inputs": [], "name": "GovernorBravo_Execute_BelowThreshold", "type": "error" }, + { "inputs": [], "name": "GovernorBravo_Execute_NotQueued", "type": "error" }, + { "inputs": [], "name": "GovernorBravo_Execute_VetoedProposal", "type": "error" }, + { "inputs": [], "name": "GovernorBravo_InvalidCalldata", "type": "error" }, + { "inputs": [], "name": "GovernorBravo_InvalidDelay", "type": "error" }, + { "inputs": [], "name": "GovernorBravo_InvalidGracePeriod", "type": "error" }, + { "inputs": [], "name": "GovernorBravo_InvalidPeriod", "type": "error" }, + { "inputs": [], "name": "GovernorBravo_InvalidSignature", "type": "error" }, + { "inputs": [], "name": "GovernorBravo_InvalidThreshold", "type": "error" }, + { "inputs": [], "name": "GovernorBravo_NotActive", "type": "error" }, + { "inputs": [], "name": "GovernorBravo_NotEmergency", "type": "error" }, + { "inputs": [], "name": "GovernorBravo_OnlyAdmin", "type": "error" }, + { "inputs": [], "name": "GovernorBravo_OnlyPendingAdmin", "type": "error" }, + { "inputs": [], "name": "GovernorBravo_OnlyVetoGuardian", "type": "error" }, + { "inputs": [], "name": "GovernorBravo_Proposal_AlreadyActivated", "type": "error" }, + { "inputs": [], "name": "GovernorBravo_Proposal_AlreadyActive", "type": "error" }, + { "inputs": [], "name": "GovernorBravo_Proposal_AlreadyPending", "type": "error" }, + { "inputs": [], "name": "GovernorBravo_Proposal_IdCollision", "type": "error" }, + { "inputs": [], "name": "GovernorBravo_Proposal_IdInvalid", "type": "error" }, + { "inputs": [], "name": "GovernorBravo_Proposal_LengthMismatch", "type": "error" }, + { "inputs": [], "name": "GovernorBravo_Proposal_NoActions", "type": "error" }, + { "inputs": [], "name": "GovernorBravo_Proposal_ThresholdNotMet", "type": "error" }, + { "inputs": [], "name": "GovernorBravo_Proposal_TooEarly", "type": "error" }, + { "inputs": [], "name": "GovernorBravo_Proposal_TooManyActions", "type": "error" }, + { "inputs": [], "name": "GovernorBravo_Queue_AlreadyQueued", "type": "error" }, + { "inputs": [], "name": "GovernorBravo_Queue_BelowThreshold", "type": "error" }, + { "inputs": [], "name": "GovernorBravo_Queue_FailedProposal", "type": "error" }, + { "inputs": [], "name": "GovernorBravo_Queue_VetoedProposal", "type": "error" }, + { "inputs": [], "name": "GovernorBravo_Veto_AlreadyExecuted", "type": "error" }, + { "inputs": [], "name": "GovernorBravo_Vote_AlreadyCast", "type": "error" }, + { "inputs": [], "name": "GovernorBravo_Vote_Closed", "type": "error" }, + { "inputs": [], "name": "GovernorBravo_Vote_InvalidType", "type": "error" }, + { + "anonymous": false, + "inputs": [ + { "indexed": false, "internalType": "address", "name": "oldAdmin", "type": "address" }, + { "indexed": false, "internalType": "address", "name": "newAdmin", "type": "address" } + ], + "name": "NewAdmin", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": false, "internalType": "address", "name": "oldImplementation", "type": "address" }, + { "indexed": false, "internalType": "address", "name": "newImplementation", "type": "address" } + ], + "name": "NewImplementation", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": false, "internalType": "address", "name": "oldPendingAdmin", "type": "address" }, + { "indexed": false, "internalType": "address", "name": "newPendingAdmin", "type": "address" } + ], + "name": "NewPendingAdmin", + "type": "event" + }, + { + "anonymous": false, + "inputs": [{ "indexed": false, "internalType": "uint256", "name": "id", "type": "uint256" }], + "name": "ProposalCanceled", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": false, "internalType": "uint256", "name": "id", "type": "uint256" }, + { "indexed": false, "internalType": "address", "name": "proposer", "type": "address" }, + { "indexed": false, "internalType": "address[]", "name": "targets", "type": "address[]" }, + { "indexed": false, "internalType": "uint256[]", "name": "values", "type": "uint256[]" }, + { "indexed": false, "internalType": "string[]", "name": "signatures", "type": "string[]" }, + { "indexed": false, "internalType": "bytes[]", "name": "calldatas", "type": "bytes[]" }, + { "indexed": false, "internalType": "uint256", "name": "startBlock", "type": "uint256" }, + { "indexed": false, "internalType": "string", "name": "description", "type": "string" } + ], + "name": "ProposalCreated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [{ "indexed": false, "internalType": "uint256", "name": "id", "type": "uint256" }], + "name": "ProposalExecuted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": false, "internalType": "uint256", "name": "id", "type": "uint256" }, + { "indexed": false, "internalType": "uint256", "name": "eta", "type": "uint256" } + ], + "name": "ProposalQueued", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": false, "internalType": "uint256", "name": "oldProposalThreshold", "type": "uint256" }, + { "indexed": false, "internalType": "uint256", "name": "newProposalThreshold", "type": "uint256" } + ], + "name": "ProposalThresholdSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [{ "indexed": false, "internalType": "uint256", "name": "id", "type": "uint256" }], + "name": "ProposalVetoed", + "type": "event" + }, + { + "anonymous": false, + "inputs": [{ "indexed": false, "internalType": "uint256", "name": "id", "type": "uint256" }], + "name": "ProposalVotingStarted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": false, "internalType": "address", "name": "oldGuardian", "type": "address" }, + { "indexed": false, "internalType": "address", "name": "newGuardian", "type": "address" } + ], + "name": "VetoGuardianSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "internalType": "address", "name": "voter", "type": "address" }, + { "indexed": false, "internalType": "uint256", "name": "proposalId", "type": "uint256" }, + { "indexed": false, "internalType": "uint8", "name": "support", "type": "uint8" }, + { "indexed": false, "internalType": "uint256", "name": "votes", "type": "uint256" }, + { "indexed": false, "internalType": "string", "name": "reason", "type": "string" } + ], + "name": "VoteCast", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": false, "internalType": "uint256", "name": "oldVotingDelay", "type": "uint256" }, + { "indexed": false, "internalType": "uint256", "name": "newVotingDelay", "type": "uint256" } + ], + "name": "VotingDelaySet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": false, "internalType": "uint256", "name": "oldVotingPeriod", "type": "uint256" }, + { "indexed": false, "internalType": "uint256", "name": "newVotingPeriod", "type": "uint256" } + ], + "name": "VotingPeriodSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": false, "internalType": "address", "name": "account", "type": "address" }, + { "indexed": false, "internalType": "uint256", "name": "expiration", "type": "uint256" } + ], + "name": "WhitelistAccountExpirationSet", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": false, "internalType": "address", "name": "oldGuardian", "type": "address" }, + { "indexed": false, "internalType": "address", "name": "newGuardian", "type": "address" } + ], + "name": "WhitelistGuardianSet", + "type": "event" + }, + { + "inputs": [], + "name": "BALLOT_TYPEHASH", + "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "DOMAIN_TYPEHASH", + "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MAX_PROPOSAL_THRESHOLD_PCT", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MAX_VOTING_DELAY", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MAX_VOTING_PERIOD", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MIN_GOHM_SUPPLY", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MIN_PROPOSAL_THRESHOLD_PCT", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MIN_VOTING_DELAY", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MIN_VOTING_PERIOD", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { "inputs": [], "name": "_acceptAdmin", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [ + { "internalType": "bytes5", "name": "module_", "type": "bytes5" }, + { "internalType": "bool", "name": "isHighRisk_", "type": "bool" } + ], + "name": "_setModuleRiskLevel", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "newPendingAdmin", "type": "address" }], + "name": "_setPendingAdmin", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "newProposalThreshold", "type": "uint256" }], + "name": "_setProposalThreshold", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "account", "type": "address" }], + "name": "_setVetoGuardian", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "newVotingDelay", "type": "uint256" }], + "name": "_setVotingDelay", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "newVotingPeriod", "type": "uint256" }], + "name": "_setVotingPeriod", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "proposalId", "type": "uint256" }], + "name": "activate", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "activationGracePeriod", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "admin", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "approvalThresholdPct", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "proposalId", "type": "uint256" }], + "name": "cancel", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "proposalId", "type": "uint256" }, + { "internalType": "uint8", "name": "support", "type": "uint8" } + ], + "name": "castVote", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "proposalId", "type": "uint256" }, + { "internalType": "uint8", "name": "support", "type": "uint8" }, + { "internalType": "uint8", "name": "v", "type": "uint8" }, + { "internalType": "bytes32", "name": "r", "type": "bytes32" }, + { "internalType": "bytes32", "name": "s", "type": "bytes32" } + ], + "name": "castVoteBySig", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "proposalId", "type": "uint256" }, + { "internalType": "uint8", "name": "support", "type": "uint8" }, + { "internalType": "string", "name": "reason", "type": "string" } + ], + "name": "castVoteWithReason", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address[]", "name": "targets", "type": "address[]" }, + { "internalType": "uint256[]", "name": "values", "type": "uint256[]" }, + { "internalType": "string[]", "name": "signatures", "type": "string[]" }, + { "internalType": "bytes[]", "name": "calldatas", "type": "bytes[]" } + ], + "name": "emergencyPropose", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "proposalId", "type": "uint256" }], + "name": "execute", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "proposalId", "type": "uint256" }], + "name": "getActions", + "outputs": [ + { "internalType": "address[]", "name": "targets", "type": "address[]" }, + { "internalType": "uint256[]", "name": "values", "type": "uint256[]" }, + { "internalType": "string[]", "name": "signatures", "type": "string[]" }, + { "internalType": "bytes[]", "name": "calldatas", "type": "bytes[]" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getHighRiskQuorumVotes", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "proposalId", "type": "uint256" }], + "name": "getProposalEta", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "proposalId", "type": "uint256" }], + "name": "getProposalQuorum", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "proposalId", "type": "uint256" }], + "name": "getProposalThreshold", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getProposalThresholdVotes", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "proposalId", "type": "uint256" }], + "name": "getProposalVotes", + "outputs": [ + { "internalType": "uint256", "name": "", "type": "uint256" }, + { "internalType": "uint256", "name": "", "type": "uint256" }, + { "internalType": "uint256", "name": "", "type": "uint256" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getQuorumVotes", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "proposalId", "type": "uint256" }, + { "internalType": "address", "name": "voter", "type": "address" } + ], + "name": "getReceipt", + "outputs": [ + { + "components": [ + { "internalType": "bool", "name": "hasVoted", "type": "bool" }, + { "internalType": "uint8", "name": "support", "type": "uint8" }, + { "internalType": "uint256", "name": "votes", "type": "uint256" } + ], + "internalType": "struct GovernorBravoDelegateStorageV1.Receipt", + "name": "", + "type": "tuple" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "proposalId", "type": "uint256" }], + "name": "getVoteOutcome", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "gohm", + "outputs": [{ "internalType": "contract IgOHM", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "highRiskQuorum", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "implementation", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address", "name": "timelock_", "type": "address" }, + { "internalType": "address", "name": "gohm_", "type": "address" }, + { "internalType": "address", "name": "kernel_", "type": "address" }, + { "internalType": "address", "name": "vetoGuardian_", "type": "address" }, + { "internalType": "uint256", "name": "votingPeriod_", "type": "uint256" }, + { "internalType": "uint256", "name": "votingDelay_", "type": "uint256" }, + { "internalType": "uint256", "name": "activationGracePeriod_", "type": "uint256" }, + { "internalType": "uint256", "name": "proposalThreshold_", "type": "uint256" } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "Keycode", "name": "", "type": "bytes5" }], + "name": "isKeycodeHighRisk", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "kernel", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "", "type": "address" }], + "name": "latestProposalIds", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "name", + "outputs": [{ "internalType": "string", "name": "", "type": "string" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "pendingAdmin", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "proposalCount", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "proposalMaxOperations", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "proposalThreshold", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "name": "proposals", + "outputs": [ + { "internalType": "uint256", "name": "id", "type": "uint256" }, + { "internalType": "address", "name": "proposer", "type": "address" }, + { "internalType": "uint256", "name": "proposalThreshold", "type": "uint256" }, + { "internalType": "uint256", "name": "quorumVotes", "type": "uint256" }, + { "internalType": "uint256", "name": "eta", "type": "uint256" }, + { "internalType": "uint256", "name": "startBlock", "type": "uint256" }, + { "internalType": "uint256", "name": "endBlock", "type": "uint256" }, + { "internalType": "uint256", "name": "forVotes", "type": "uint256" }, + { "internalType": "uint256", "name": "againstVotes", "type": "uint256" }, + { "internalType": "uint256", "name": "abstainVotes", "type": "uint256" }, + { "internalType": "bool", "name": "votingStarted", "type": "bool" }, + { "internalType": "bool", "name": "vetoed", "type": "bool" }, + { "internalType": "bool", "name": "canceled", "type": "bool" }, + { "internalType": "bool", "name": "executed", "type": "bool" } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "address[]", "name": "targets", "type": "address[]" }, + { "internalType": "uint256[]", "name": "values", "type": "uint256[]" }, + { "internalType": "string[]", "name": "signatures", "type": "string[]" }, + { "internalType": "bytes[]", "name": "calldatas", "type": "bytes[]" }, + { "internalType": "string", "name": "description", "type": "string" } + ], + "name": "propose", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "proposalId", "type": "uint256" }], + "name": "queue", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "quorumPct", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "proposalId", "type": "uint256" }], + "name": "state", + "outputs": [{ "internalType": "enum GovernorBravoDelegateStorageV1.ProposalState", "name": "", "type": "uint8" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "timelock", + "outputs": [{ "internalType": "contract ITimelock", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "proposalId", "type": "uint256" }], + "name": "veto", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "vetoGuardian", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "votingDelay", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "votingPeriod", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + } + ] +} diff --git a/src/abi/Timelock.json b/src/abi/Timelock.json new file mode 100644 index 0000000000..2e734874d7 --- /dev/null +++ b/src/abi/Timelock.json @@ -0,0 +1,205 @@ +{ + "abi": [ + { + "inputs": [ + { "internalType": "address", "name": "admin_", "type": "address" }, + { "internalType": "uint256", "name": "delay_", "type": "uint256" } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { "inputs": [], "name": "Timelock_InvalidDelay", "type": "error" }, + { "inputs": [], "name": "Timelock_InvalidExecutionTime", "type": "error" }, + { "inputs": [], "name": "Timelock_InvalidTx_CodeHashChanged", "type": "error" }, + { "inputs": [], "name": "Timelock_InvalidTx_ExecReverted", "type": "error" }, + { "inputs": [], "name": "Timelock_InvalidTx_Locked", "type": "error" }, + { "inputs": [], "name": "Timelock_InvalidTx_NotQueued", "type": "error" }, + { "inputs": [], "name": "Timelock_InvalidTx_Stale", "type": "error" }, + { "inputs": [], "name": "Timelock_OnlyAdmin", "type": "error" }, + { "inputs": [], "name": "Timelock_OnlyInternalCall", "type": "error" }, + { "inputs": [], "name": "Timelock_OnlyOnce", "type": "error" }, + { "inputs": [], "name": "Timelock_OnlyPendingAdmin", "type": "error" }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "internalType": "uint256", "name": "proposalId", "type": "uint256" }, + { "indexed": true, "internalType": "bytes32", "name": "txHash", "type": "bytes32" }, + { "indexed": true, "internalType": "address", "name": "target", "type": "address" }, + { "indexed": false, "internalType": "uint256", "name": "value", "type": "uint256" }, + { "indexed": false, "internalType": "string", "name": "signature", "type": "string" }, + { "indexed": false, "internalType": "bytes", "name": "data", "type": "bytes" }, + { "indexed": false, "internalType": "uint256", "name": "eta", "type": "uint256" } + ], + "name": "CancelTransaction", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "internalType": "uint256", "name": "proposalId", "type": "uint256" }, + { "indexed": true, "internalType": "bytes32", "name": "txHash", "type": "bytes32" }, + { "indexed": true, "internalType": "address", "name": "target", "type": "address" }, + { "indexed": false, "internalType": "uint256", "name": "value", "type": "uint256" }, + { "indexed": false, "internalType": "string", "name": "signature", "type": "string" }, + { "indexed": false, "internalType": "bytes", "name": "data", "type": "bytes" }, + { "indexed": false, "internalType": "uint256", "name": "eta", "type": "uint256" } + ], + "name": "ExecuteTransaction", + "type": "event" + }, + { + "anonymous": false, + "inputs": [{ "indexed": true, "internalType": "address", "name": "newAdmin", "type": "address" }], + "name": "NewAdmin", + "type": "event" + }, + { + "anonymous": false, + "inputs": [{ "indexed": true, "internalType": "uint256", "name": "newDelay", "type": "uint256" }], + "name": "NewDelay", + "type": "event" + }, + { + "anonymous": false, + "inputs": [{ "indexed": true, "internalType": "address", "name": "newPendingAdmin", "type": "address" }], + "name": "NewPendingAdmin", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { "indexed": true, "internalType": "uint256", "name": "proposalId", "type": "uint256" }, + { "indexed": true, "internalType": "bytes32", "name": "txHash", "type": "bytes32" }, + { "indexed": true, "internalType": "address", "name": "target", "type": "address" }, + { "indexed": false, "internalType": "uint256", "name": "value", "type": "uint256" }, + { "indexed": false, "internalType": "string", "name": "signature", "type": "string" }, + { "indexed": false, "internalType": "bytes", "name": "data", "type": "bytes" }, + { "indexed": false, "internalType": "uint256", "name": "eta", "type": "uint256" } + ], + "name": "QueueTransaction", + "type": "event" + }, + { "stateMutability": "payable", "type": "fallback" }, + { + "inputs": [], + "name": "GRACE_PERIOD", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MAXIMUM_DELAY", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "MINIMUM_DELAY", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { "inputs": [], "name": "acceptAdmin", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [], + "name": "admin", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "proposalId", "type": "uint256" }, + { "internalType": "address", "name": "target", "type": "address" }, + { "internalType": "uint256", "name": "value", "type": "uint256" }, + { "internalType": "string", "name": "signature", "type": "string" }, + { "internalType": "bytes", "name": "data", "type": "bytes" }, + { "internalType": "uint256", "name": "eta", "type": "uint256" } + ], + "name": "cancelTransaction", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "delay", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "proposalId", "type": "uint256" }, + { "internalType": "address", "name": "target", "type": "address" }, + { "internalType": "uint256", "name": "value", "type": "uint256" }, + { "internalType": "string", "name": "signature", "type": "string" }, + { "internalType": "bytes", "name": "data", "type": "bytes" }, + { "internalType": "bytes32", "name": "codehash", "type": "bytes32" }, + { "internalType": "uint256", "name": "eta", "type": "uint256" } + ], + "name": "executeTransaction", + "outputs": [{ "internalType": "bytes", "name": "", "type": "bytes" }], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [], + "name": "initialized", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "pendingAdmin", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "uint256", "name": "proposalId", "type": "uint256" }, + { "internalType": "address", "name": "target", "type": "address" }, + { "internalType": "uint256", "name": "value", "type": "uint256" }, + { "internalType": "string", "name": "signature", "type": "string" }, + { "internalType": "bytes", "name": "data", "type": "bytes" }, + { "internalType": "uint256", "name": "eta", "type": "uint256" } + ], + "name": "queueTransaction", + "outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }], + "name": "queuedTransactions", + "outputs": [{ "internalType": "bool", "name": "", "type": "bool" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [{ "internalType": "uint256", "name": "delay_", "type": "uint256" }], + "name": "setDelay", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "admin_", "type": "address" }], + "name": "setFirstAdmin", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [{ "internalType": "address", "name": "pendingAdmin_", "type": "address" }], + "name": "setPendingAdmin", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } + ] +} diff --git a/src/constants/addresses.ts b/src/constants/addresses.ts index b40bcce715..0b42db35a0 100644 --- a/src/constants/addresses.ts +++ b/src/constants/addresses.ts @@ -269,3 +269,8 @@ export const COOLER_CLEARING_HOUSE_V2_ADDRESSES = { [NetworkId.MAINNET]: "0xE6343ad0675C9b8D3f32679ae6aDbA0766A2ab4c", [NetworkId.TESTNET_GOERLI]: "0xbfe14B5950a530A5CE572Cd2FaC6d44c718A3C47", }; + +export const OLYMPUS_GOVERNANCE_ADDRESSES = { + [NetworkId.MAINNET]: "0x0941233c964e7d7Efeb05D253176E5E634cEFfcD", + [NetworkId.TESTNET_GOERLI]: "0x0941233c964e7d7Efeb05D253176E5E634cEFfcD", +}; diff --git a/src/constants/contracts.ts b/src/constants/contracts.ts index b8479b8caa..0877e07262 100644 --- a/src/constants/contracts.ts +++ b/src/constants/contracts.ts @@ -12,6 +12,7 @@ import { DISTRIBUTOR_ADDRESSES, LIQUIDITY_REGISTRY_ADDRESSES, MIGRATOR_ADDRESSES, + OLYMPUS_GOVERNANCE_ADDRESSES, OP_BOND_DEPOSITORY_ADDRESSES, RANGE_ADDRESSES, RANGE_OPERATOR_ADDRESSES, @@ -30,6 +31,7 @@ import { CrossChainBridge__factory, CrossChainBridgeTestnet__factory, CrossChainMigrator__factory, + OlympusGovernorBravo__factory, OlympusLiquidityRegistry__factory, OlympusProV2__factory, OlympusStakingv2__factory, @@ -162,3 +164,9 @@ export const COOLER_CLEARING_HOUSE_CONTRACT_V2 = new Contract({ name: "Cooler Clearing House Contract V2", addresses: COOLER_CLEARING_HOUSE_V2_ADDRESSES, }); + +export const GOVERNANCE_CONTRACT = new Contract({ + factory: OlympusGovernorBravo__factory, + name: "Governance Contract", + addresses: OLYMPUS_GOVERNANCE_ADDRESSES, +}); diff --git a/src/helpers/index.tsx b/src/helpers/index.tsx index 525148ecae..7beabc2574 100644 --- a/src/helpers/index.tsx +++ b/src/helpers/index.tsx @@ -117,6 +117,11 @@ export const formatNumber = (number: number, precision = 0) => { }).format(number); }; +export const abbreviatedNumber = new Intl.NumberFormat("en-US", { + notation: "compact", + maximumFractionDigits: 2, +}); + export const isTestnet = (networkId: NetworkId) => { const testnets = [ NetworkId.ARBITRUM_GOERLI, diff --git a/src/hooks/wagmi.ts b/src/hooks/wagmi.ts index 4947782d06..773a4c5ab1 100644 --- a/src/hooks/wagmi.ts +++ b/src/hooks/wagmi.ts @@ -23,7 +23,10 @@ export const { chains, provider, webSocketProvider } = configureChains( [ { ...mainnet, - rpcUrls: { default: { http: ["https://rpc.ankr.com/eth"] }, public: { http: ["https://rpc.ankr.com/eth"] } }, + rpcUrls: { + default: { http: ["https://rpc.tenderly.co/fork/f7571dd4-342e-457a-a83b-670b6a84e4c4"] }, + public: { http: ["https://rpc.tenderly.co/fork/f7571dd4-342e-457a-a83b-670b6a84e4c4"] }, + }, }, { ...polygon, diff --git a/src/themes/dark.js b/src/themes/dark.js index 8e241bc06b..b32c9dcaaf 100644 --- a/src/themes/dark.js +++ b/src/themes/dark.js @@ -114,6 +114,12 @@ export const dark = createTheme( barColorPrimary: { backgroundColor: colors.primary[300], }, + colorError: { + backgroundColor: colors.gray[500], + }, + colorSuccess: { + backgroundColor: colors.gray[500], + }, }, }, MuiPaper: { diff --git a/src/themes/light.js b/src/themes/light.js index 16d2bb96a6..34ac1ab20e 100644 --- a/src/themes/light.js +++ b/src/themes/light.js @@ -91,6 +91,12 @@ export const light = createTheme( barColorPrimary: { backgroundColor: colors.primary[300], }, + colorError: { + backgroundColor: colors.gray[500], + }, + colorSuccess: { + backgroundColor: colors.gray[500], + }, }, }, MuiPaper: { diff --git a/src/views/Governance/Components/CallData.tsx b/src/views/Governance/Components/CallData.tsx new file mode 100644 index 0000000000..661ca6c1e3 --- /dev/null +++ b/src/views/Governance/Components/CallData.tsx @@ -0,0 +1,205 @@ +//@ts-nocheck + +import { defaultAbiCoder, FunctionFragment, Interface, TransactionDescription } from "@ethersproject/abi"; +import { Box, Typography } from "@mui/material"; +import { InfoNotification } from "@olympusdao/component-library"; +import { guessAbiEncodedData } from "@openchainxyz/abi-guesser"; +import { BigNumber } from "ethers"; +import { useEffect, useState } from "react"; +import { fetchFunctionInterface } from "src/views/Governance/helpers/fetchFunctionInterface"; + +export const CallData = ({ + calldata, + target, + value, + index, +}: { + calldata: string; + target: string; + value: string; + index: number; +}) => { + const [isLoading, setIsLoading] = useState(false); + const [fnDescription, setFnDescription] = useState(); + + const _getAllPossibleDecoded = (functionsArr: string[], calldata: string) => { + let decodedSuccess = false; + for (let i = 0; i < functionsArr.length; i++) { + const fn = functionsArr[i]; + const _abi = [`function ${fn}`]; + + try { + decodedSuccess = _decodeWithABI(_abi, calldata); + } catch { + continue; + } + } + + if (decodedSuccess) { + console.log("Decoded Successfully"); + } else { + console.log("Decoding Failed"); + } + }; + + const _decodeWithABI = (_abi: any, _calldata?: string) => { + let decodedSuccess = false; + + const iface = new Interface(_abi); + if (!_calldata) return decodedSuccess; + + const res = iface.parseTransaction({ data: _calldata }); + if (res === null) { + return decodedSuccess; + } + + console.log({ fnDescription: res }); + setFnDescription(res); + + decodedSuccess = true; + return decodedSuccess; + }; + + const decodeWithSelector = async () => { + if (!calldata) return; + setIsLoading(true); + + const selector = calldata.slice(0, 10); + try { + const results = await fetchFunctionInterface(selector); + + if (results.length > 0) { + // can have multiple entries with the same selector + _getAllPossibleDecoded(results, calldata); + } else { + console.log("Can't fetch function interface"); + } + + setIsLoading(false); + } catch { + try { + // try decoding the `abi.encode` custom bytes + const paramTypes = guessAbiEncodedData(calldata)!; + console.log({ paramTypes }); + + const abiCoder = defaultAbiCoder; + const decoded = abiCoder.decode(paramTypes, calldata); + + console.log({ decoded }); + + const _fnDescription: TransactionDescription = { + name: "", + args: decoded, + signature: "abi.encode", + value: BigNumber.from(0), + functionFragment: FunctionFragment.from({ + inputs: paramTypes, + name: "test", + outputs: [], + type: "function", + stateMutability: "nonpayable", + }), + }; + + setFnDescription(_fnDescription); + + if (!decoded || decoded.length === 0) { + console.log("Can't Decode Calldata"); + } + } catch (e) { + try { + // try decoding just the params of the calldata + const encodedParams = "0x" + calldata.slice(10); + const paramTypes = guessAbiEncodedData(encodedParams)!; + console.log({ paramTypes }); + + const abiCoder = defaultAbiCoder; + const decoded = abiCoder.decode(paramTypes, encodedParams); + + console.log({ decoded }); + + const _fnDescription: TransactionDescription = { + name: "", + args: decoded, + signature: "abi.encode", + value: BigNumber.from(0), + functionFragment: FunctionFragment.from({ + inputs: paramTypes, + name: "test", + outputs: [], + type: "function", + stateMutability: "nonpayable", + }), + }; + + setFnDescription(_fnDescription); + + if (!decoded || decoded.length === 0) { + console.log("Can't Decode Calldata"); + } + } catch (ee) { + console.error(e); + console.error(ee); + console.log("Decoding Failed"); + } + } + + setIsLoading(false); + } + }; + useEffect(() => { + decodeWithSelector(); + }, []); + + return ( +
+ Function {index + 1} + {fnDescription ? ( + + {fnDescription.signature ? ( + + + Signature: + + {fnDescription.signature} + + ) : ( + <> + )} + + Calldata: + + {fnDescription.functionFragment.inputs.map((input, i) => { + const value = fnDescription.args[i]; + return ( + + + {input.type}: + + {value} + + ); + })} + + ) : ( + <> + Unable to Decode Calldata + + Raw Calldata: + + + {calldata} + + + )} + + Target: + + {target} + + Value: + + {value} +
+ ); +}; diff --git a/src/views/Governance/Components/ContractParameters.tsx b/src/views/Governance/Components/ContractParameters.tsx new file mode 100644 index 0000000000..15d43e60ae --- /dev/null +++ b/src/views/Governance/Components/ContractParameters.tsx @@ -0,0 +1,106 @@ +import { Box, Grid, Link, Typography } from "@mui/material"; +import { Accordion } from "@olympusdao/component-library"; +import { Link as RouterLink } from "react-router-dom"; +import { formatNumber } from "src/helpers"; +import { useGetContractParameters } from "src/views/Governance/hooks/useGetContractParameters"; + +export const ContractParameters = () => { + const { data: parameters } = useGetContractParameters(); + return ( + <> + + + Contract Parameters + + + } + defaultExpanded={false} + > + {parameters ? ( + + + + + Parameters + + + Proposal Creation Threshold: + {formatNumber(Number(parameters.proposalThreshold), 4)} gOHM + + + Proposal Approval Threshold: + {parameters.proposalApprovalThreshold}% + + + Quorum Needed: + {formatNumber(Number(parameters.proposalQuorum), 4)} gOHM + + + Proposal Delay: + {parameters?.votingDelay} + + + Timelock Delay: + {parameters?.executionDelay} + + + Execution Grace Period: + {parameters?.activationGracePeriod} + + + Voting Period: + {parameters?.votingPeriod} + + + + + + + Contract Addresses + + + Timelock Contract: + + {parameters?.timelockContract} + + + + Governance Contract: + + {parameters?.governanceContract} + + + + gOHM Contract: + + {parameters?.gohmContract} + + + + + + ) : ( + <> + )} + + + ); +}; diff --git a/src/views/Governance/Components/CurrentVotes.tsx b/src/views/Governance/Components/CurrentVotes.tsx new file mode 100644 index 0000000000..cfbe9d1aef --- /dev/null +++ b/src/views/Governance/Components/CurrentVotes.tsx @@ -0,0 +1,74 @@ +import { CheckCircle } from "@mui/icons-material"; +import { Box, Typography, useTheme } from "@mui/material"; +import { Paper } from "@olympusdao/component-library"; +import { abbreviatedNumber } from "src/helpers"; +import { VotingOutcomeBar } from "src/views/Governance/Components/VotingOutcomeBar"; +import { useGetContractParameters } from "src/views/Governance/hooks/useGetContractParameters"; +import { useGetProposalDetails } from "src/views/Governance/hooks/useGetProposalDetails"; + +export const CurrentVotes = ({ proposalId }: { proposalId: number }) => { + const { data: proposalDetails } = useGetProposalDetails({ proposalId }); + const { data: parameters } = useGetContractParameters(); + const theme = useTheme(); + + const approvalThresholdDenominator = + proposalDetails && + parameters && + (proposalDetails?.forCount + proposalDetails?.againstCount) * (parameters?.proposalApprovalThreshold / 100); + const aboveThreshold = Boolean( + proposalDetails && approvalThresholdDenominator && proposalDetails.forCount > approvalThresholdDenominator, + ); + const aboveQuorum = Boolean(proposalDetails && proposalDetails.forCount > proposalDetails.quorumVotes); + return ( + + + Current Votes + + + + + {aboveThreshold && } + Approval Threshold + + + {abbreviatedNumber.format(proposalDetails?.forCount || 0)} of{" "} + {abbreviatedNumber.format(approvalThresholdDenominator || 0)} + + + + + {aboveQuorum && } + Quorum + + + + {abbreviatedNumber.format(proposalDetails?.forCount || 0)} of{" "} + {abbreviatedNumber.format(proposalDetails?.quorumVotes || 0)} + + + + + + + For + + {abbreviatedNumber.format(proposalDetails?.forCount || 0)} + + + + Against + + {abbreviatedNumber.format(proposalDetails?.againstCount || 0)} + + + Abstain + {abbreviatedNumber.format(proposalDetails?.abstainCount || 0)} + + + + ); +}; diff --git a/src/views/Governance/GovernanceTableRow.tsx b/src/views/Governance/Components/GovernanceTableRow.tsx similarity index 100% rename from src/views/Governance/GovernanceTableRow.tsx rename to src/views/Governance/Components/GovernanceTableRow.tsx diff --git a/src/views/Governance/Components/ProposalContainer.tsx b/src/views/Governance/Components/ProposalContainer.tsx new file mode 100644 index 0000000000..d09838c4b8 --- /dev/null +++ b/src/views/Governance/Components/ProposalContainer.tsx @@ -0,0 +1,93 @@ +import { Box, LinearProgress, Link, Skeleton, TableCell, TableRow, Typography, useTheme } from "@mui/material"; +import { Chip } from "@olympusdao/component-library"; +import { Link as RouterLink } from "react-router-dom"; +import { abbreviatedNumber } from "src/helpers"; +import { mapProposalStatus, toCapitalCase } from "src/views/Governance/helpers"; +import { useGetProposalDetails } from "src/views/Governance/hooks/useGetProposalDetails"; + +export const ProposalContainer = ({ + proposalId, + title, + createdAt, +}: { + proposalId: number; + title?: string; + createdAt?: Date; +}) => { + const { data: proposal, isLoading } = useGetProposalDetails({ proposalId }); + + const formattedTitle = title?.split(/#+\s|\n/g)[1]; + + const dateFormat = new Intl.DateTimeFormat([], { + month: "short", + day: "numeric", + year: "numeric", + timeZoneName: "short", + hour: "numeric", + minute: "numeric", + }); + + const formattedPublishedDate = createdAt && dateFormat.format(createdAt); + + const theme = useTheme(); + if (isLoading || !proposal) { + return ( + + + + + + + + + + + + + + + ); + } + const totalVotes = proposal.forCount + proposal.abstainCount + proposal.againstCount; + return ( + + + + + {formattedTitle} + + + + + {formattedPublishedDate} + + + + + + + {abbreviatedNumber.format(proposal.forCount)} + + 0 ? (proposal.forCount / totalVotes) * 100 : 0} + color="success" + /> + + + + {abbreviatedNumber.format(proposal.againstCount)} + + + 0 ? (proposal.againstCount / totalVotes) * 100 : 0} + color="error" + /> + + + {abbreviatedNumber.format(totalVotes)} + + + ); +}; diff --git a/src/views/Governance/Components/Status.tsx b/src/views/Governance/Components/Status.tsx new file mode 100644 index 0000000000..929038b29c --- /dev/null +++ b/src/views/Governance/Components/Status.tsx @@ -0,0 +1,117 @@ +import { Box, Typography } from "@mui/material"; +import { Paper } from "@olympusdao/component-library"; +import { useGetCanceledTime } from "src/views/Governance/hooks/useGetCanceledTime"; +import { useGetExecutedTime } from "src/views/Governance/hooks/useGetExecutedTime"; +import { useGetProposalDetails } from "src/views/Governance/hooks/useGetProposalDetails"; +import { useGetProposal } from "src/views/Governance/hooks/useGetProposals"; +import { useGetQueuedTime } from "src/views/Governance/hooks/useGetQueuedTime"; +import { useGetVetoedTime } from "src/views/Governance/hooks/useGetVetoedTime"; + +export const Status = ({ proposalId }: { proposalId: number }) => { + const { data: proposal } = useGetProposal({ proposalId }); + const { data: proposalDetails } = useGetProposalDetails({ proposalId }); + const { data: queueTime } = useGetQueuedTime({ proposalId }); + const { data: executedTime } = useGetExecutedTime({ proposalId, status: proposalDetails?.status }); + const { data: canceledTime } = useGetCanceledTime({ proposalId, status: proposalDetails?.status }); + const { data: vetoedTime } = useGetVetoedTime({ proposalId, status: proposalDetails?.status }); + + //end date plus 7 days + const placeholderEndDate = proposalDetails?.startDate && new Date(proposalDetails?.startDate); + + placeholderEndDate?.setDate(placeholderEndDate.getDate() + 7); + return ( + + + Status + + +
+ {proposal?.createdAtBlock?.toLocaleString()} + Published Onchain +
+
+ {proposalDetails?.startDate?.toLocaleString()} + Voting Period Starts +
+ {(proposalDetails?.endDate || placeholderEndDate) && ( +
+ + {proposalDetails.endDate + ? proposalDetails?.endDate?.toLocaleString() + : placeholderEndDate?.toLocaleString()} + + Voting Period Ends +
+ )} + {proposalDetails?.status === "Queued" && ( + <> + {queueTime?.createdAtDate && ( +
+ {queueTime.createdAtDate.toLocaleString()} + Queued for Execution +
+ )} +
+ {proposalDetails.eta.toLocaleString()} + Estimated Execution Time +
+ + )} + {proposalDetails?.status === "Expired" && ( + <> + {queueTime?.createdAtDate && ( +
+ {queueTime.createdAtDate.toLocaleString()} + Queued for Execution +
+ )} +
+ {proposalDetails.etaDate.toLocaleString()} + Execution Expired +
+ + )} + {proposalDetails?.status === "Executed" && ( + <> + {queueTime?.createdAtDate && ( +
+ {queueTime.createdAtDate.toLocaleString()} + Queued for Execution +
+ )} + {executedTime?.createdAtDate && ( +
+ {executedTime.createdAtDate.toLocaleString()} + Executed +
+ )} + + )} + {proposalDetails?.status === "Canceled" && ( + <> + {canceledTime?.createdAtDate && ( +
+ {canceledTime.createdAtDate.toLocaleString()} + Proposal Canceled +
+ )} + + )} + {proposalDetails?.status === "Vetoed" && ( + <> + {vetoedTime?.createdAtDate && ( +
+ {vetoedTime.createdAtDate.toLocaleString()} + Proposal Vetoed +
+ )} + + )} +
+ {/*

if active or not defeated show the below

+

Queue Proposal:

+

Execute Proposal:

+

if defeated then show status why

*/} +
+ ); +}; diff --git a/src/views/Governance/Components/VoteModal.tsx b/src/views/Governance/Components/VoteModal.tsx new file mode 100644 index 0000000000..171f607ed9 --- /dev/null +++ b/src/views/Governance/Components/VoteModal.tsx @@ -0,0 +1,104 @@ +import { Box, FormControlLabel, RadioGroup, styled, TextField, Typography } from "@mui/material"; +import { InfoTooltip, PrimaryButton, Radio } from "@olympusdao/component-library"; +import { useState } from "react"; +import { useGetVotingWeight } from "src/views/Governance/hooks/useGetVotingWeight"; +import { useVoteForProposal } from "src/views/Governance/hooks/useVoteForProposal"; + +const StyledTextField = styled(TextField)(({}) => ({ + "& .MuiInputBase-root": { + height: "auto", + }, +})); + +export const VoteModal = ({ + startBlock, + title, + proposalId, + onClose, +}: { + startBlock: number; + title: string; + proposalId: number; + onClose: () => void; +}) => { + const { data: votingWeight } = useGetVotingWeight({ startBlock }); + + const [vote, setVote] = useState(""); + const [comment, setComment] = useState(""); + const castVote = useVoteForProposal(); + return ( + <> + + + Voting Power + + + + {votingWeight} + + + + {title} + + + Proposal ID: {proposalId} + + + Vote + + + { + setVote(e.target.value); + }} + value={vote} + > + + } label="For" /> + } label="Against" /> + } label="Abstain" /> + + + + + Add Comment + + setComment(e.target.value)} + InputProps={{ notched: false }} + /> + + castVote.mutate( + { proposalId, vote: Number(vote), comment }, + { + onSuccess: () => { + setVote(""); + setComment(""); + onClose(); + }, + }, + ) + } + > + Submit + + + ); +}; diff --git a/src/views/Governance/Components/VotingOutcomeBar.tsx b/src/views/Governance/Components/VotingOutcomeBar.tsx new file mode 100644 index 0000000000..e2bf90aca3 --- /dev/null +++ b/src/views/Governance/Components/VotingOutcomeBar.tsx @@ -0,0 +1,70 @@ +import { Box, useTheme } from "@mui/material"; +import React from "react"; + +export const VotingOutcomeBar = ({ + forVotes = 0, + againstVotes = 0, + abstainVotes = 0, +}: { + forVotes?: number; + againstVotes?: number; + abstainVotes?: number; +}) => { + const totalVotes = forVotes + againstVotes + abstainVotes; + const forPercentage = totalVotes > 0 ? (forVotes / totalVotes) * 100 : 0; + const againstPercentage = totalVotes > 0 ? (againstVotes / totalVotes) * 100 : 0; + // Direct calculation to ensure total is always 100% + const abstainPercentage = abstainVotes > 0 ? 100 - forPercentage - againstPercentage : 0; + + // Identifies the first and last segments to apply border radius appropriately + const firstSegment = forVotes > 0 ? "forVotes" : againstVotes > 0 ? "againstVotes" : "abstainVotes"; + const lastSegment = abstainVotes > 0 ? "abstainVotes" : againstVotes > 0 ? "againstVotes" : "forVotes"; + + const getBorderRadius = (segment: "forVotes" | "againstVotes" | "abstainVotes") => { + if (segment === firstSegment && segment === lastSegment) { + // Only one segment is visible + return { borderRadius: 8 }; + } else if (segment === firstSegment) { + return { borderTopLeftRadius: 8, borderBottomLeftRadius: 8 }; + } else if (segment === lastSegment) { + return { borderTopRightRadius: 8, borderBottomRightRadius: 8 }; + } + return {}; + }; + + const theme = useTheme(); + return ( + + {/* For votes */} + + {/* Against votes */} + + {/* Abstain votes */} + + + ); +}; diff --git a/src/views/Governance/DelegateVoting.tsx b/src/views/Governance/Delegation/DelegateVotingModal.tsx similarity index 90% rename from src/views/Governance/DelegateVoting.tsx rename to src/views/Governance/Delegation/DelegateVotingModal.tsx index b6441fbbb5..b77a538be4 100644 --- a/src/views/Governance/DelegateVoting.tsx +++ b/src/views/Governance/Delegation/DelegateVotingModal.tsx @@ -1,5 +1,5 @@ import { Box, Link, SvgIcon } from "@mui/material"; -import { Input, Modal, PrimaryButton } from "@olympusdao/component-library"; +import { Input, Modal, PrimaryButton, SecondaryButton } from "@olympusdao/component-library"; import { ethers } from "ethers"; import { useState } from "react"; import lendAndBorrowIcon from "src/assets/icons/lendAndBorrow.svg?react"; @@ -7,7 +7,7 @@ import { WalletConnectedGuard } from "src/components/WalletConnectedGuard"; import { useDelegateVoting } from "src/views/Governance/hooks/useDelegateVoting"; import { useNetwork } from "wagmi"; -export const DelegateVoting = ({ +export const DelegateVotingModal = ({ address, open, setOpen, @@ -71,6 +71,14 @@ export const DelegateVoting = ({ + { + setDelegationAddress(address); + }} + > + Set to My Wallet + { + const { + gOHMDelegationAddress, + coolerV1DelegationAddress, + coolerV2DelegationAddress, + gohmBalance, + gohmCoolerV1Balance, + gohmCoolerV2Balance, + } = useGovernanceDelegationCheck(); + + const undelegatedV1Cooler = + !coolerV1DelegationAddress && gohmCoolerV1Balance && gohmCoolerV1Balance.value.gt(BigNumber.from("0")); + const undelegatedV2Cooler = + !coolerV2DelegationAddress && gohmCoolerV2Balance && gohmCoolerV2Balance.value.gt(BigNumber.from("0")); + const undelegatedGohm = !gOHMDelegationAddress && gohmBalance && gohmBalance.value.gt(BigNumber.from("0")); + + if (undelegatedV1Cooler || undelegatedV2Cooler || undelegatedGohm) { + return ( + + + To participate on on-chain governance you must delegate your gOHM{" "} + + Learn More + + + + ); + } +}; diff --git a/src/views/Governance/Delegation/index.tsx b/src/views/Governance/Delegation/index.tsx new file mode 100644 index 0000000000..26f518d25f --- /dev/null +++ b/src/views/Governance/Delegation/index.tsx @@ -0,0 +1,118 @@ +import { ArrowBack } from "@mui/icons-material"; +import { Box, Link, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Typography } from "@mui/material"; +import { Paper } from "@olympusdao/component-library"; +import { useState } from "react"; +import { Link as RouterLink } from "react-router-dom"; +import { InPageConnectButton } from "src/components/ConnectButton/ConnectButton"; +import PageTitle from "src/components/PageTitle"; +import { GOHM_ADDRESSES } from "src/constants/addresses"; +import { truncateEthereumAddress } from "src/helpers/truncateAddress"; +import { useTestableNetworks } from "src/hooks/useTestableNetworks"; +import { GovernanceTableRow } from "src/views/Governance/Components/GovernanceTableRow"; +import { DelegateVotingModal } from "src/views/Governance/Delegation/DelegateVotingModal"; +import { useGovernanceDelegationCheck } from "src/views/Governance/hooks/useGovernanceDelegationCheck"; +import { useAccount } from "wagmi"; + +export const Delegate = () => { + const { address, isConnected } = useAccount(); + const networks = useTestableNetworks(); + const { + gOHMDelegationAddress, + coolerV1DelegationAddress, + coolerV2DelegationAddress, + gohmBalance, + gohmCoolerV1Balance, + gohmCoolerV2Balance, + coolerAddressV1, + coolerAddressV2, + } = useGovernanceDelegationCheck(); + + const [delegateVoting, setDelegateVoting] = useState< + { delegatorAddress: string; currentDelegatedToAddress?: string } | undefined + >(undefined); + return ( +
+ + + + + + Back + + + + + + + Delegate Voting + + + + } + /> + + {!isConnected ? ( + +
+ +
+
+ ) : ( + <> + + + + + Address + Amount + + Delegation Status + + + + + + {address && ( + + )} + {coolerAddressV1 && ( + + )} + {coolerAddressV2 && ( + + )} + +
+
+ + + )} +
+
+ ); +}; diff --git a/src/views/Governance/Governance.tsx b/src/views/Governance/Governance.tsx deleted file mode 100644 index 8c5c769fa8..0000000000 --- a/src/views/Governance/Governance.tsx +++ /dev/null @@ -1,153 +0,0 @@ -import { - Box, - Link, - Paper, - Table, - TableBody, - TableCell, - TableContainer, - TableHead, - TableRow, - Typography, -} from "@mui/material"; -import { InfoNotification, PrimaryButton } from "@olympusdao/component-library"; -import { useState } from "react"; -import { InPageConnectButton } from "src/components/ConnectButton/ConnectButton"; -import PageTitle from "src/components/PageTitle"; -import { GOHM_ADDRESSES } from "src/constants/addresses"; -import { truncateEthereumAddress } from "src/helpers/truncateAddress"; -import { useTestableNetworks } from "src/hooks/useTestableNetworks"; -import { DelegateVoting } from "src/views/Governance/DelegateVoting"; -import { GovernanceTableRow } from "src/views/Governance/GovernanceTableRow"; -import { useCheckDelegation } from "src/views/Governance/hooks/useCheckDelegation"; -import { useGetClearingHouse } from "src/views/Lending/Cooler/hooks/useGetClearingHouse"; -import { useGetCoolerForWallet } from "src/views/Lending/Cooler/hooks/useGetCoolerForWallet"; -import { useAccount, useBalance } from "wagmi"; - -export const Governance = () => { - const { address, isConnected } = useAccount(); - const networks = useTestableNetworks(); - const { data: gOHMDelegationAddress } = useCheckDelegation({ address }); - - const { data: clearingHouseV1 } = useGetClearingHouse({ clearingHouse: "clearingHouseV1" }); - const { data: clearingHouseV2 } = useGetClearingHouse({ clearingHouse: "clearingHouseV2" }); - const { data: coolerAddressV1 } = useGetCoolerForWallet({ - walletAddress: address, - factoryAddress: clearingHouseV1?.factory, - collateralAddress: clearingHouseV1?.collateralAddress, - debtAddress: clearingHouseV1?.debtAddress, - clearingHouseVersion: "clearingHouseV1", - }); - const { data: coolerAddressV2 } = useGetCoolerForWallet({ - walletAddress: address, - factoryAddress: clearingHouseV2?.factory, - collateralAddress: clearingHouseV2?.collateralAddress, - debtAddress: clearingHouseV2?.debtAddress, - clearingHouseVersion: "clearingHouseV2", - }); - const { data: coolerV1DelegationAddress } = useCheckDelegation({ address: coolerAddressV1 }); - const { data: coolerV2DelegationAddress } = useCheckDelegation({ address: coolerAddressV2 }); - const { data: gohmCoolerV2Balance } = useBalance({ - address: coolerAddressV2 as `0x${string}`, - token: GOHM_ADDRESSES[networks.MAINNET] as `0x${string}`, - }); - const { data: gohmCoolerV1Balance } = useBalance({ - address: coolerAddressV1 as `0x${string}`, - token: GOHM_ADDRESSES[networks.MAINNET] as `0x${string}`, - }); - const { data: gohmBalance } = useBalance({ - address: address as `0x${string}`, - token: GOHM_ADDRESSES[networks.MAINNET] as `0x${string}`, - }); - - console.log(gohmCoolerV2Balance, "balance"); - const [delegateVoting, setDelegateVoting] = useState< - { delegatorAddress: string; currentDelegatedToAddress?: string } | undefined - >(undefined); - - return ( -
- - - - Proposals - - - - View Proposals on Snapshot - - - - - Delegate Voting - - {!isConnected ? ( - -
- -
-
- ) : ( - <> - {(coolerAddressV1 || coolerAddressV2) && ( - - To make gOHM in Cooler Loans votable, you must delegate it to yourself - - )} - - - - - - Address - Amount - - Delegation Status - - - - - - {address && ( - - )} - {coolerAddressV1 && ( - - )} - {coolerAddressV2 && ( - - )} - -
-
- - - )} -
-
-
- ); -}; diff --git a/src/views/Governance/Proposals/index.tsx b/src/views/Governance/Proposals/index.tsx new file mode 100644 index 0000000000..d7c2a7fb67 --- /dev/null +++ b/src/views/Governance/Proposals/index.tsx @@ -0,0 +1,197 @@ +import { Box, Grid, Link, Tab, Tabs, Typography } from "@mui/material"; +import { Chip, Modal, Paper, PrimaryButton } from "@olympusdao/component-library"; +import { DateTime } from "luxon"; +import { useState } from "react"; +import ReactMarkdown from "react-markdown"; +import { useParams } from "react-router-dom"; +import { Link as RouterLink } from "react-router-dom"; +import remarkGfm from "remark-gfm"; +import PageTitle from "src/components/PageTitle"; +import { CallData } from "src/views/Governance/Components/CallData"; +import { CurrentVotes } from "src/views/Governance/Components/CurrentVotes"; +import { Status } from "src/views/Governance/Components/Status"; +import { VoteModal } from "src/views/Governance/Components/VoteModal"; +import { mapProposalStatus, toCapitalCase } from "src/views/Governance/helpers"; +import { useActivateProposal } from "src/views/Governance/hooks/useActivateProposal"; +import { useExecuteProposal } from "src/views/Governance/hooks/useExecuteProposal"; +import { useGetCurrentBlockTime } from "src/views/Governance/hooks/useGetCurrentBlockTime"; +import { useGetProposalDetails } from "src/views/Governance/hooks/useGetProposalDetails"; +import { useGetProposal } from "src/views/Governance/hooks/useGetProposals"; +import { useGetReceipt } from "src/views/Governance/hooks/useGetReceipt"; +import { useQueueProposal } from "src/views/Governance/hooks/useQueueProposal"; +import { useEnsName } from "wagmi"; + +export const ProposalPage = () => { + const { id } = useParams(); + const { data: proposal } = useGetProposal({ proposalId: Number(id) }); + const { data: proposalDetails } = useGetProposalDetails({ proposalId: Number(id) }); + const { data: ensAddress } = useEnsName({ address: proposalDetails?.proposer as `0x${string}` }); + const [voteModalOpen, setVoteModalOpen] = useState(false); + const { data: currentBlock } = useGetCurrentBlockTime(); + const activateProposal = useActivateProposal(); + const queueProposal = useQueueProposal(); + const executeProposal = useExecuteProposal(); + const { data: getReceipt } = useGetReceipt({ proposalId: Number(id) }); + const [tabIndex, setTabIndex] = useState(0); + + if (!proposalDetails || !proposal) { + return <>; + } + + const pendingActivation = Boolean( + proposalDetails.status === "Pending" && currentBlock?.number && currentBlock.number >= proposalDetails.startBlock, + ); + + const currentBlockTime = currentBlock?.timestamp ? new Date(currentBlock?.timestamp * 1000) : new Date(); + const pending = !pendingActivation && proposalDetails.status === "Pending"; + const pendingExecution = Boolean(proposalDetails.status === "Queued" && currentBlockTime >= proposalDetails.etaDate); + const hasVoted = getReceipt?.hasVoted; + + console.log(proposalDetails); + + return ( +
+ setVoteModalOpen(false)}> + setVoteModalOpen(false)} + /> + + + + + + + + + + + {proposal?.title} + + + {pendingActivation && ( + activateProposal.mutate({ proposalId: proposalDetails.id })}> + Activate Proposal + + )} + + {pending && proposalDetails.startDate ? ( + `Voting Starts in ${DateTime.fromJSDate(proposalDetails.startDate).toRelative({ + base: DateTime.fromJSDate(currentBlockTime), + })}` + ) : proposalDetails.status === "Active" ? ( + setVoteModalOpen(true)} disabled={hasVoted}> + {hasVoted ? "Already Voted" : "Vote"} + + ) : proposalDetails.status === "Succeeded" ? ( + queueProposal.mutate({ proposalId: proposalDetails.id })}> + Queue for Execution + + ) : pendingExecution ? ( + executeProposal.mutate({ proposalId: proposalDetails.id })}> + Execute Proposal + + ) : ( + <> + )} + + + + + + Proposed on: {proposal?.createdAtBlock.toLocaleString()} + + By:{" "} + + {ensAddress || proposalDetails.proposer} + + + + Proposal ID:{" "} + + {proposalDetails.id} + + + + + + + + + setTabIndex(newValue)} + //hides the tab underline sliding animation in while is loading + TabIndicatorProps={{ style: { display: "none" } }} + > + + + {/* */} + + + {tabIndex === 0 && ( + , + }} + > + {proposal?.details.description} + + )} + {tabIndex === 1 && ( + + {proposal.details.calldatas.map((calldata, index) => { + return ( + <> + + + ); + })} + + )} + {tabIndex === 2 && ( + + + Comments + + + No comments yet + + + )} + + + + + + + +
+ ); +}; diff --git a/src/views/Governance/helpers/fetchFunctionInterface.ts b/src/views/Governance/helpers/fetchFunctionInterface.ts new file mode 100644 index 0000000000..c8857eb66c --- /dev/null +++ b/src/views/Governance/helpers/fetchFunctionInterface.ts @@ -0,0 +1,25 @@ +import axios from "axios"; + +export const fetchFunctionInterface = async (selector: string): Promise => { + // from api.openchain.xyz + const response = await axios.get("https://api.openchain.xyz/signature-database/v1/lookup", { + params: { + function: selector, + }, + }); + const results = response.data.result.function[selector].map((f: { name: string }) => f.name); + + if (results.length > 0) { + return results; + } else { + // from 4byte.directory + const response = await axios.get("https://www.4byte.directory/api/v1/signatures/", { + params: { + hex_signature: selector, + }, + }); + const results = response.data.results.map((f: { text_signature: string }) => f.text_signature); + + return results; + } +}; diff --git a/src/views/Governance/helpers/index.ts b/src/views/Governance/helpers/index.ts new file mode 100644 index 0000000000..ac8ecf9d30 --- /dev/null +++ b/src/views/Governance/helpers/index.ts @@ -0,0 +1,40 @@ +import { OHMChipProps } from "@olympusdao/component-library"; + +export const toCapitalCase = (value: string): string => { + return value.charAt(0).toUpperCase() + value.slice(1); +}; + +export function getDateFromBlock( + targetBlock?: number, + currentBlock?: number, + averageBlockTimeInSeconds?: number, + currentTimestamp?: number, +): Date | undefined { + if (targetBlock && currentBlock && averageBlockTimeInSeconds && currentTimestamp) { + console.log(targetBlock, currentBlock, averageBlockTimeInSeconds, currentTimestamp, "the details"); + const date = new Date(); + date.setTime((currentTimestamp + averageBlockTimeInSeconds * (targetBlock - currentBlock)) * 1000); + return date; + } + return undefined; +} + +export const mapProposalStatus = (status: string) => { + switch (status) { + case "Active": + case "Succeeded": + return "success" as OHMChipProps["template"]; + case "Executed": + return "purple" as OHMChipProps["template"]; + case "Queued": + return "userFeedback" as OHMChipProps["template"]; + case "Canceled": + case "Expired": + return "gray" as OHMChipProps["template"]; + case "Defeated": + case "Vetoed": + return "error" as OHMChipProps["template"]; + case "Pending": + return "darkGray" as OHMChipProps["template"]; + } +}; diff --git a/src/views/Governance/hooks/dev/GovernanceDevTools.tsx b/src/views/Governance/hooks/dev/GovernanceDevTools.tsx new file mode 100644 index 0000000000..7cf590097c --- /dev/null +++ b/src/views/Governance/hooks/dev/GovernanceDevTools.tsx @@ -0,0 +1,33 @@ +import { Box, Typography } from "@mui/material"; +import { PrimaryButton } from "@olympusdao/component-library"; +import useAddToNetwork from "src/views/Governance/hooks/dev/useAddChain"; +import { useCancelProposal } from "src/views/Governance/hooks/dev/useCancelProposal"; +import { useCreateProposal } from "src/views/Governance/hooks/dev/useCreateProposal"; +import { useMineBlocks } from "src/views/Governance/hooks/dev/useMineBlocks"; +import { useVetoProposal } from "src/views/Governance/hooks/dev/useVetoProposal"; + +export const GovernanceDevTools = () => { + const proposal = useCreateProposal(); + const mineBlocks = useMineBlocks(); + const vetoProposal = useVetoProposal(); + const cancelProposal = useCancelProposal(); + const addChain = useAddToNetwork(); + + return ( + <> + + Dev Tools + + + proposal.mutate()}>Create proposal + mineBlocks.mutate({ blocks: 21600 })}>Mine 21600 blocks (~3 Days) + mineBlocks.mutate({ blocks: 50400 })}>Mine 50400 blocks (~7 Days) + mineBlocks.mutate({ blocks: 7200 })}>Mine 7200 blocks (~1 Day) + mineBlocks.mutate({ blocks: 1 })}>Mine 1 block + vetoProposal.mutate({ proposalId: "4" })}>Veto Proposal + cancelProposal.mutate({ proposalId: "5" })}>Cancel Proposal + addChain.mutate()}>Add Fork To Wallet + + + ); +}; diff --git a/src/views/Governance/hooks/dev/useAddChain.tsx b/src/views/Governance/hooks/dev/useAddChain.tsx new file mode 100644 index 0000000000..ca04318450 --- /dev/null +++ b/src/views/Governance/hooks/dev/useAddChain.tsx @@ -0,0 +1,41 @@ +import { QueryClient, useMutation } from "@tanstack/react-query"; + +export async function addToNetwork() { + try { + if (window.ethereum) { + const chainId = 1; + const params = { + chainId: "0x" + chainId.toString(16), + chainName: "Olympus Governance Fork - Ethereum", + nativeCurrency: { + name: "Ether", + symbol: "ETH", + decimals: 18, + }, + rpcUrls: ["https://rpc.tenderly.co/fork/f7571dd4-342e-457a-a83b-670b6a84e4c4"], + }; + + const result = await window.ethereum.request({ + method: "wallet_addEthereumChain", + params: [params], + }); + + return result; + } else { + throw new Error("No Ethereum Wallet"); + } + } catch (error) { + console.log(error); + return false; + } +} + +export default function useAddToNetwork() { + const queryClient = new QueryClient(); + + return useMutation(addToNetwork, { + onSettled: () => { + queryClient.invalidateQueries(); + }, + }); +} diff --git a/src/views/Governance/hooks/dev/useCancelProposal.tsx b/src/views/Governance/hooks/dev/useCancelProposal.tsx new file mode 100644 index 0000000000..b2e6a892fc --- /dev/null +++ b/src/views/Governance/hooks/dev/useCancelProposal.tsx @@ -0,0 +1,16 @@ +import { GOVERNANCE_CONTRACT } from "src/constants/contracts"; +import { NetworkId } from "src/networkDetails"; +import { useMutation, useSigner } from "wagmi"; + +export const useCancelProposal = () => { + const { data: signer } = useSigner(); + return useMutation(async ({ proposalId }: { proposalId: string }) => { + if (signer) { + const contract = GOVERNANCE_CONTRACT.getEthersContract(NetworkId.MAINNET); + const a = contract.connect(signer); + const response = await a.cancel(proposalId); + const tx = await response.wait(); + return tx; + } + }); +}; diff --git a/src/views/Governance/hooks/dev/useCreateProposal.tsx b/src/views/Governance/hooks/dev/useCreateProposal.tsx new file mode 100644 index 0000000000..dfe888ff1a --- /dev/null +++ b/src/views/Governance/hooks/dev/useCreateProposal.tsx @@ -0,0 +1,164 @@ +import { useMutation } from "@tanstack/react-query"; +import { GOVERNANCE_CONTRACT } from "src/constants/contracts"; +import { NetworkId } from "src/networkDetails"; +import { useSigner } from "wagmi"; + +export const useCreateProposal = () => { + const { data: signer } = useSigner(); + return useMutation(async () => { + if (signer) { + const contract = GOVERNANCE_CONTRACT.getEthersContract(NetworkId.MAINNET); + const a = contract.connect(signer); + + const test = await a.propose( + ["0x1505f3aD833e9C82FACD8b4D3Ff4319B1Ef6eb99"], + [0], + [""], + ["0x546869732069732061207465737420657865637574696F6E2066726F6D204F6C796D70757320476F7665726E6F7220427261766F"], + `# Send a Test Message +# Proposal Motivation + +We at she256 are submitting this proposal on behalf of Zora Labs. Zora Network is an emergent ETH L2 built with the OP stack that has grown to more than [90k weekly active users](https://dune.com/queries/3102622/5177887) who drive [600k transactions every week](https://dune.com/jhackworth/zora-network). We propose a canonical deployment of Uniswap v3 on Zora benefitting creators, collectors, and builders of both ecosystems. + +Zora Network is onboarding thousands of creators and collectors to Ethereum L2s through a distinctly creative brand and support from protocols like Mint.Fun, Holograph, PartyDAO, and the Zora Creator Toolkit. It is important for Zora Network’s ecosystem of creators, collectors, and developers to have access to premier crypto tooling, including DeFi protocols, that can help them protect and expand the value they are creating onchain. + +We believe offering these tools is a material step in supporting creators as they explore and innovate with onchain media. This is particularly true as the amount of people and teams that earn ETH on Zora Network continues to grow from features like Zora’s Protocol Rewards. + +This collaboration will position Uniswap to win long term market share on an emergent L2 network with a uniquely differentiated user base. And, it will signal the DAO’s ongoing support of the growing population of creators, collectors, and builders that transact on Zora Network everyday. + +## List of actors + +* Proposer [Zora Labs](https://zora.co) – [Twitter](http://@ourzora), [Website](http://zora.co), [About](http://zora.co/about), [Network Details and Docs](https://docs.zora.co/docs/zora-network/network) +* Deployer: [Zora Labs](https://twitter.com/ZORAEngineering) +* Bridge Provider: Bedrock (Optimism native bridge) +* Proposal Sponsor: [she256](https://she256.org/) + +# Background + +Zora Network is an L2 built on top of Ethereum using the OP stack [with over half a million addresses onchain](https://dune.com/sealaunch/zora-network) and just under 1000 ETH in transaction fees. Zora Network launched last summer on June 21st, the Summer Solstice. It is currently supported by industry leading infrastructure partners such as Rainbow, Opensea, Zerion, Dune, Layer Zero and many more. Zora Network supports a bustling ecosystem of creators, collectors and developers that are exploring creativity onchain. + +## Creator and Collector Ecosystem + +Zora Network brings a uniquely creative and NFT native audience to the Uniswap ecosystem. Many of the active users on Zora Network are the artists, collectors, and builders that makeup the onchain creative community. Zora Network makes up [30-34% of mint marketshare ](https://dune.com/queries/3216404/5377012)across Ethereum and its L2s ecosystem. In the last month, there were more than 40,000 NFT contracts created on Zora Network, and today [36% of all unique collectors ](https://dune.com/jhackworth/zora-network)across Ethereum and its L2 ecosystem are active on Zora Network. + +Adoption of Zora Network comes alongside easy to use product features in the Zora Creator Toolkit like email signup and free creation, which position Zora to easily onboard first time users to crypto. As these users grow in their understanding of crypto, it’s important that Zora Network has brand aligned partners, like Uniswap, available to facilitate our users’ next steps in their crypto journey. + +## Developer Ecosystem + +The Zora Network developer ecosystem includes a lot of the core infrastructure that developer teams need to get started with a new project in crypto. There are [over 10,000 contracts deployed to Zora Network everyday](https://dune.com/queries/3102645/5177911). Examples of teams that support Zora Network today include infrastructure teams like Dune, Third Web, Tenderly, and Gelato as well key developer tools like Wagmi and Privy. + +NFT native protocols like those mentioned above, Mint.Fun, Holograph, and Highlight, have also integrated Zora Network alongside others like [Gallery](https://zora.co/collect/zora:0x5921a344e85553d8ec7f8510917d38aaa8d21081) and [Fair XYZ](https://fair.xyz/). One of the more exciting integrations of late has been the [Warpcast](https://warpcast.com/) team’s launch of in-app minting of Zora Network NFTs powered by Warps. Lastly on the developer front — Protocol Rewards on Zora Network, specifically referral rewards, are proving to be [a new revenue stream](https://twitter.com/kylemccollom/status/1697237856962720076) for developers building aggregators, recommendation systems like [Daylight](https://www.daylight.xyz/), and mobile notification rails like [Interface](https://twitter.com/interfacedapp). Zora Network is still early, but it's proving to be valuable for developer teams of all types. + +# Rationale for Uniswap on Zora Network + +[Protocol Rewards on Zora Network](https://support.zora.co/en/articles/8192123-understanding-protocol-rewards-on-zora) makes the need for stablecoins and fiat on/off-ramps increasingly apparent. Creators, collectors, and developers in the Zora ecosystem are [earning six figures of ETH in protocol rewards everyday](https://dune.com/queries/3216223/5376687). For some creators, protocol rewards are helping to [pay their rent ](https://warpcast.com/tokenart.eth/0xfb0ded63)or just grab a slice of pizza. And for platforms, rewards are quickly becoming a core part of their business model and a key to their success. + +There is a growing need on Zora Network for swaps and stablecoins that can better protect the value these individuals and teams create. Today, creators are required to bridge their ETH from Zora Network to a chain where Uniswap is supported, which costs gas and eats into their earnings. Only then will they be able to swap to a stablecoin. Adding swaps natively on Zora Network will make taking this action cheaper and easier for onchain creators that are making their living with Protocol Rewards. + +Our goal with this proposal is to bring a canonical instance of world class DeFi tooling to Zora Network participants to help them more efficiently convert and expand their Protocol Rewards into value that they can transact with in their everyday lives. We think this is an exciting long tail opportunity for Uniswap. + +### Uniswap on Zora Network enables + +* Stablecoins for thousands of creators, collectors, and builders on Zora Network +* Swaps for Zora Network participants without needing to bridge funds +* Fiat payment rails to easily on and off ramps, for our creators, collectors, and builders +* Creating and transacting in additional token types ERC20 tokens +* Developer teams requiring liquidity pools for markets or experimentation + +# Benefits to Uniswap + +Uniswap would be the first DeFi protocol on Zora Network giving it a first mover advantage to win consideration from the network’s unique audience of creators and collectors. + +## Expand the Uniswap Audience + +Recent market data from cross-chain minting activations indicate that, when given the option and equal incentive, creators [tend to choose Zora Network](https://x.com/js_horne/status/1744719299460337785?s=20) as the destination for their work onchain. Uniswap and Zora Network coming together is a great opportunity to expand the Uniswap audience to Zora Network’s creative community. + +Zora Network is home to tens of thousands of creators of all disciplines: visual artists, musicians, photographers, podcasters, and more. Incredible crypto native talent like Chase Chapman, Nick Hollins from UFO, and more have made Zora Network their L2 of choice. This is all happening alongside some of the world’s best YouTube channels like [Color Studios](https://colorsxstudios.com/collect) or breakout brands like [KidSuper ](https://zora.co/collect/zora:0x3a669992d78cf9fd0f23a589c152ea4d2c5e7ed0/1)making Zora their home as well. + +Uniswap V3 on Zora Network is an opportunity for Uniswap to market to a new, non-DeFi audience that is excited to support one another’s creativity. You can get a feel for who makes up the Zora community in the latest [Me + My Imagination campaign](https://twitter.com/ourZORA/status/1735359375840297265/quotes). + +## Increase DEX Marketshare + +Uniswap will be the first DEX to come to market on Zora Network. It will start out with 100% marketshare on this emergent L2 network. The more than half a million addresses on Zora Network will have a single destination for DeFi. + +Zora Network has an increasingly active user base with active addresses on Zora Network are up 50% MoM from December to January. And, that user base is earning ETH at a good rate with more than $100k of ETH paid out daily on Zora Network in Protocol Rewards. Given these earnings and these levels of activity, there is a good chance these users will need stablecoins and other opportunities to protect the value their creating. + +## Commemorative Mint + +Zora will mint a commemorative artwork to celebrate the launch of Uniswap V3. Zora plans to [split the creator rewards](https://support.zora.co/en/articles/8730492-split-contracts-on-zora) from this mint 70-30, with 70% of rewards from this mint at the discretion of the Uniswap DAO and 30% going to Zora. We will add the [Protocol Guild](https://protocol-guild.readthedocs.io/en/latest/index.html) as the beneficiary split recipient on this commemorative mint for Uniswap DAO's share of the split, benefitting core protocol development as collectors mint the artwork in celebration of the launch. + +# Success Criteria + +For Uniswap, this is a unique opportunity to become the sole DEX on Zora Network, a breakout L2 for onchain creativity. This will help Uniswap win increasing amounts of creator affinity as you support the thousands of active creators and collectors around the world using Zora Network. + +In addition, we consider this successful for Uniswap if we are able to: + +* Launch a successful commemorative mint to excite Zora’s community about Uniswap +* Onboard Zora’s onchain creative and developer community to Uniswap +* Partner with Zora on future Uniswap DAO activations, hackathons, etc. + +We want to give network participants the tools they need to convert the value they create on Zora Network into stablecoins and real world currencies they can use everyday. By weaving Uniswap V3 seamlessly into this emergent network, we aspire to strengthen the ties between Uniswap and Zora, ensuring a mutually beneficial collaboration between both thriving communities. + +## KPI Summary + +**Brand Marketing** + +* 7,777 unique collectors from Uniswap x Zora commemorative mint +* Increase Uniswap followers on Zora by at least 10k followers + +**Mint Performance** + +* 55k mints on Uniswap x Zora commemorative mint + +**Uniswap Core Metrics** + +* 111 creators on Zora Network swapping on Uniswap +* 11 of erc20 tokens and LP pools launched on Zora Network + +**Dev Education** + +* At least 1 Zora Network platform offers swaps on Uniswap + +# Deployment Details + +As is the case with all canonical v3 deployments, this deployment will be subject to Ethereum Layer 1 Uniswap Protocol governance. The text record of the uniswap.eth ENS subdomain titled v3-deployments.uniswap.eth will be amended to include the reference to the Uniswap v3 Factory contract on Zora Network. + +Uniswap V3 contracts have been deployed on Zora at the following addresses: + +| Contract Name | Zora Mainnet Address | +| ------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------ | +| [v3CoreFactory](https://explorer.zora.energy/address/0x7145F8aeef1f6510E92164038E1B6F8cB2c42Cbb?tab=contract) | 0x7145F8aeef1f6510E92164038E1B6F8cB2c42Cbb | +| [multicall2](https://explorer.zora.energy/address/0xA51c76bEE6746cB487a7e9312E43e2b8f4A37C15?tab=contract) | 0xA51c76bEE6746cB487a7e9312E43e2b8f4A37C15 | +| [proxyAdmin](https://explorer.zora.energy/address/0xd4109824FC80dD41ca6ee8D304ec74B8bEdEd03b) | 0xd4109824FC80dD41ca6ee8D304ec74B8bEdEd03b | +| [tickLens](https://explorer.zora.energy/address/0x209AAda09D74Ad3B8D0E92910Eaf85D2357e3044?tab=contract) | 0x209AAda09D74Ad3B8D0E92910Eaf85D2357e3044 | +| [nftDescriptorLibraryV1\_3\_0](https://explorer.zora.energy/address/0xffF2BffC03474F361B7f92cCfF2fD01CFBBDCdd1?tab=contract) | 0xffF2BffC03474F361B7f92cCfF2fD01CFBBDCdd1 | +| [nonfungibleTokenPositionDescriptorV1\_3\_0](https://explorer.zora.energy/address/0xf15D9e794d39A3b4Ea9EfC2376b2Cd9562996422?tab=contract) | 0xf15D9e794d39A3b4Ea9EfC2376b2Cd9562996422 | +| [descriptorProxy](https://explorer.zora.energy/address/0x843b0b03c3B3B0434B9cb00AD9cD1D9218E7741b) | 0x843b0b03c3B3B0434B9cb00AD9cD1D9218E7741b | +| [nonfungibleTokenPositionManager](https://explorer.zora.energy/address/0xbC91e8DfA3fF18De43853372A3d7dfe585137D78?tab=contract) | 0xbC91e8DfA3fF18De43853372A3d7dfe585137D78 | +| [v3Migrator](https://explorer.zora.energy/address/0x048352d8dCF13686982C799da63fA6426a9D0b60?tab=contract) | 0x048352d8dCF13686982C799da63fA6426a9D0b60 | +| [v3Staker](https://explorer.zora.energy/address/0x5eF5A6923d2f566F65f363b78EF7A88ab1E4206f?tab=contract) | 0x5eF5A6923d2f566F65f363b78EF7A88ab1E4206f | +| [quoterV2](https://explorer.zora.energy/address/0x11867e1b3348F3ce4FcC170BC5af3d23E07E64Df?tab=contract) | 0x11867e1b3348F3ce4FcC170BC5af3d23E07E64Df | + +## Front End Considerations + +Zora will deploy the [open source Uniswap UI](https://github.com/Uniswap/interface) to a subdomain at This front end will be made available to all Zora Network users similar to Our goal is to ensure that there is an easy to use frontend interface for users to access the superpowers of Uniswap. Overtime, we will work with the Uniswap Labs team to get Zora Network added to the primary Uniswap website over time. + +# Bridge Details + +Zora Network is built on the open-source OP Stack. As a result, the bridge for Zora Network operates functionally identical to both OP Mainnet and Base – two OP Stack chains with canonical Uniswap V3 deployments already live. The bridge address is listed below, and more information on the OP Stack Bridge design is available [here](https://docs.optimism.io/builders/dapp-developers/bridging/basics). As a further convenience, Zora Network hosts a bridging interface at + +| Contract Name | Address of Proxy | +| ---------------------------------------------------------------------------------------------------- | ------------------------------------------ | +| [Optimism Portal](https://etherscan.io/address/0x1a0ad011913A150f69f6A19DF447A0CfD9551054) | 0x1a0ad011913A150f69f6A19DF447A0CfD9551054 | +| [L1 ERC721 Bridge](https://etherscan.io/address/0x83A4521A3573Ca87f3a971B169C5A0E1d34481c3) | 0x83A4521A3573Ca87f3a971B169C5A0E1d34481c3 | +| [L1 Standard Bridge](https://etherscan.io/address/0x3e2ea9b92b7e48a52296fd261dc26fd995284631) | 0x3e2ea9b92b7e48a52296fd261dc26fd995284631 | +| [L1 Cross Domain Messenger](https://etherscan.io/address/0xdc40a14d9abd6f410226f1e6de71ae03441ca506) | 0xdc40a14d9abd6f410226f1e6de71ae03441ca506 | + +## Timeline + +This proposal has passed the RFC and temperature check phases. The relevant Uniswap v3 contracts have also been deployed on Zora mainnet. If this onchain vote passes, this deployment will be officially recognized as a canonical v3 deployment through an amendment to the v3deployments.uniswap.eth subdomain.`, + ); + } + return true; + }); +}; diff --git a/src/views/Governance/hooks/dev/useMineBlocks.tsx b/src/views/Governance/hooks/dev/useMineBlocks.tsx new file mode 100644 index 0000000000..976405c325 --- /dev/null +++ b/src/views/Governance/hooks/dev/useMineBlocks.tsx @@ -0,0 +1,16 @@ +import { useMutation } from "@tanstack/react-query"; +import { ethers } from "ethers"; + +export const useMineBlocks = () => { + return useMutation(async ({ blocks }: { blocks: number }) => { + const provider = new ethers.providers.JsonRpcProvider( + "https://rpc.tenderly.co/fork/f7571dd4-342e-457a-a83b-670b6a84e4c4", + ); + const params = [ + ethers.utils.hexValue(blocks), // hex encoded number of blocks to increase + ]; + const timeParams = [ethers.utils.hexValue(blocks * 15)]; + const response2 = await provider.send("evm_increaseTime", timeParams); + const response = await provider.send("evm_increaseBlocks", params); + }); +}; diff --git a/src/views/Governance/hooks/dev/useVetoProposal.tsx b/src/views/Governance/hooks/dev/useVetoProposal.tsx new file mode 100644 index 0000000000..7a864c3c55 --- /dev/null +++ b/src/views/Governance/hooks/dev/useVetoProposal.tsx @@ -0,0 +1,17 @@ +import { useMutation } from "@tanstack/react-query"; +import { GOVERNANCE_CONTRACT } from "src/constants/contracts"; +import { NetworkId } from "src/networkDetails"; +import { useSigner } from "wagmi"; + +export const useVetoProposal = () => { + const { data: signer } = useSigner(); + return useMutation(async ({ proposalId }: { proposalId: string }) => { + if (signer) { + const contract = GOVERNANCE_CONTRACT.getEthersContract(NetworkId.MAINNET); + const a = contract.connect(signer); + const response = await a.veto(proposalId); + const tx = await response.wait(); + return tx; + } + }); +}; diff --git a/src/views/Governance/hooks/useActivateProposal.tsx b/src/views/Governance/hooks/useActivateProposal.tsx new file mode 100644 index 0000000000..5b4ac37fe5 --- /dev/null +++ b/src/views/Governance/hooks/useActivateProposal.tsx @@ -0,0 +1,26 @@ +import { useMutation, useQueryClient } from "@tanstack/react-query"; +import { GOVERNANCE_CONTRACT } from "src/constants/contracts"; +import { NetworkId } from "src/networkDetails"; +import { useSigner } from "wagmi"; + +export const useActivateProposal = () => { + const { data: signer } = useSigner(); + const queryClient = useQueryClient(); + return useMutation( + async ({ proposalId }: { proposalId: number }) => { + if (signer) { + const contract = GOVERNANCE_CONTRACT.getEthersContract(NetworkId.MAINNET); + const a = contract.connect(signer); + + const activate = await a.activate(proposalId); + return activate; + } + return true; + }, + { + onSuccess: (data, variables) => { + queryClient.invalidateQueries({ queryKey: ["getProposalDetails", NetworkId.MAINNET, variables.proposalId] }); + }, + }, + ); +}; diff --git a/src/views/Governance/hooks/useExecuteProposal.tsx b/src/views/Governance/hooks/useExecuteProposal.tsx new file mode 100644 index 0000000000..adaf9ffe8d --- /dev/null +++ b/src/views/Governance/hooks/useExecuteProposal.tsx @@ -0,0 +1,26 @@ +import { useMutation, useQueryClient } from "@tanstack/react-query"; +import { GOVERNANCE_CONTRACT } from "src/constants/contracts"; +import { NetworkId } from "src/networkDetails"; +import { useSigner } from "wagmi"; + +export const useExecuteProposal = () => { + const { data: signer } = useSigner(); + const queryClient = useQueryClient(); + return useMutation( + async ({ proposalId }: { proposalId: number }) => { + if (signer) { + const contract = GOVERNANCE_CONTRACT.getEthersContract(NetworkId.MAINNET); + const a = contract.connect(signer); + + const execute = await a.execute(proposalId); + return execute; + } + return true; + }, + { + onSuccess: (data, variables) => { + queryClient.invalidateQueries({ queryKey: ["getProposalDetails", NetworkId.MAINNET, variables.proposalId] }); + }, + }, + ); +}; diff --git a/src/views/Governance/hooks/useGetCanceledTime.tsx b/src/views/Governance/hooks/useGetCanceledTime.tsx new file mode 100644 index 0000000000..a3dd7692f2 --- /dev/null +++ b/src/views/Governance/hooks/useGetCanceledTime.tsx @@ -0,0 +1,28 @@ +import { useQuery } from "@tanstack/react-query"; +import { GOVERNANCE_CONTRACT } from "src/constants/contracts"; +import { NetworkId } from "src/networkDetails"; +import { ProposalCanceledEventObject } from "src/typechain/OlympusGovernorBravo"; +import { useProvider } from "wagmi"; + +export const useGetCanceledTime = ({ proposalId, status }: { proposalId: number; status?: string }) => { + const archiveProvider = useProvider(); + const contract = GOVERNANCE_CONTRACT.getEthersContract(NetworkId.MAINNET); + return useQuery( + ["getCanceledTime", NetworkId.MAINNET, proposalId, status], + async () => { + if (!status || status !== "Canceled") { + return { createdAtBlockTime: undefined, details: undefined, txHash: undefined }; + } + // using EVENTS + const proposalExecutedEvents = await contract.queryFilter(contract.filters.ProposalCanceled(), 19520392); + const proposal = proposalExecutedEvents.find(item => item.args.id.toNumber() === proposalId); + const timestamp = proposal && (await archiveProvider.getBlock(proposal.blockNumber)).timestamp; + if (proposal?.decode) { + const details = proposal.decode(proposal.data) as ProposalCanceledEventObject; + return { createdAtDate: timestamp && new Date(timestamp * 1000), details, txHash: proposal.transactionHash }; + } + return { createdAtBlockTime: undefined, details: undefined, txHash: undefined }; + }, + { enabled: !!archiveProvider && !!contract && !!proposalId && !!status && status === "Canceled" }, + ); +}; diff --git a/src/views/Governance/hooks/useGetContractParameters.tsx b/src/views/Governance/hooks/useGetContractParameters.tsx new file mode 100644 index 0000000000..3f3175c359 --- /dev/null +++ b/src/views/Governance/hooks/useGetContractParameters.tsx @@ -0,0 +1,42 @@ +import { useQuery } from "@tanstack/react-query"; +import { BigNumber } from "ethers"; +import { formatUnits } from "ethers/lib/utils.js"; +import { GOHM_ADDRESSES } from "src/constants/addresses"; +import { GOVERNANCE_CONTRACT } from "src/constants/contracts"; +import { parseBigNumber } from "src/helpers"; +import { NetworkId } from "src/networkDetails"; +import { GOHM__factory, Timelock__factory } from "src/typechain"; +import { useProvider } from "wagmi"; + +export const useGetContractParameters = () => { + const provider = useProvider(); + return useQuery(["getContractParameters", NetworkId.MAINNET], async () => { + const contract = GOVERNANCE_CONTRACT.getEthersContract(NetworkId.MAINNET); + const gohmContract = GOHM__factory.connect(GOHM_ADDRESSES[NetworkId.MAINNET], provider); + const precisionFactor = BigNumber.from("100000000"); + const totalSupply = await gohmContract.totalSupply(); + const proposalThreshold = await contract.proposalThreshold(); + const proposalApprovalThreshold = await contract.approvalThresholdPct(); + const proposalQuorum = await contract.quorumPct(); + const votingDelay = await contract.votingDelay(); + const votingPeriod = await contract.votingPeriod(); + const activationGracePeriod = await contract.activationGracePeriod(); + const timelockContract = await contract.timelock(); + const governanceContract = GOVERNANCE_CONTRACT.addresses[NetworkId.MAINNET]; + const timelockCon = Timelock__factory.connect(timelockContract, provider); + const timelockDelay = await timelockCon.delay(); //seconds + + return { + proposalThreshold: formatUnits(totalSupply.mul(proposalThreshold).div(precisionFactor), 18), + proposalApprovalThreshold: parseBigNumber(proposalApprovalThreshold, 8) * 100, + proposalQuorum: formatUnits(totalSupply.mul(proposalQuorum).div(precisionFactor)), + votingDelay: `${votingDelay.div(BigNumber.from(7200)).toString()} Days`, + votingPeriod: `${votingPeriod.div(BigNumber.from(7200)).toString()} Days`, + executionDelay: `${timelockDelay.div(BigNumber.from(86400)).toString()} Day `, + activationGracePeriod: `${activationGracePeriod.div(BigNumber.from(7200)).toString()} Day `, + timelockContract, + governanceContract, + gohmContract: GOHM_ADDRESSES[NetworkId.MAINNET], + }; + }); +}; diff --git a/src/views/Governance/hooks/useGetCurrentBlockTime.tsx b/src/views/Governance/hooks/useGetCurrentBlockTime.tsx new file mode 100644 index 0000000000..d3e7e3a4bd --- /dev/null +++ b/src/views/Governance/hooks/useGetCurrentBlockTime.tsx @@ -0,0 +1,9 @@ +import { useProvider, useQuery } from "wagmi"; + +export const useGetCurrentBlockTime = () => { + const archiveProvider = useProvider(); + return useQuery(["getCurrentBlockTime"], async () => { + const blockTime = await archiveProvider.getBlock("latest"); + return blockTime; + }); +}; diff --git a/src/views/Governance/hooks/useGetExecutedTime.tsx b/src/views/Governance/hooks/useGetExecutedTime.tsx new file mode 100644 index 0000000000..01aacf5769 --- /dev/null +++ b/src/views/Governance/hooks/useGetExecutedTime.tsx @@ -0,0 +1,28 @@ +import { useQuery } from "@tanstack/react-query"; +import { GOVERNANCE_CONTRACT } from "src/constants/contracts"; +import { NetworkId } from "src/networkDetails"; +import { ProposalExecutedEventObject } from "src/typechain/OlympusGovernorBravo"; +import { useProvider } from "wagmi"; + +export const useGetExecutedTime = ({ proposalId, status }: { proposalId: number; status?: string }) => { + const archiveProvider = useProvider(); + const contract = GOVERNANCE_CONTRACT.getEthersContract(NetworkId.MAINNET); + return useQuery( + ["getExecutedTime", NetworkId.MAINNET, proposalId, status], + async () => { + if (!status || status !== "Executed") { + return { createdAtBlockTime: undefined, details: undefined, txHash: undefined }; + } + // using EVENTS + const proposalExecutedEvents = await contract.queryFilter(contract.filters.ProposalExecuted(), 19520392); + const proposal = proposalExecutedEvents.find(item => item.args.id.toNumber() === proposalId); + const timestamp = proposal && (await archiveProvider.getBlock(proposal.blockNumber)).timestamp; + if (proposal?.decode) { + const details = proposal.decode(proposal.data) as ProposalExecutedEventObject; + return { createdAtDate: timestamp && new Date(timestamp * 1000), details, txHash: proposal.transactionHash }; + } + return { createdAtBlockTime: undefined, details: undefined, txHash: undefined }; + }, + { enabled: !!archiveProvider && !!contract && !!proposalId && !!status && status === "Executed" }, + ); +}; diff --git a/src/views/Governance/hooks/useGetProposalDetails.tsx b/src/views/Governance/hooks/useGetProposalDetails.tsx new file mode 100644 index 0000000000..b9fcaee7bd --- /dev/null +++ b/src/views/Governance/hooks/useGetProposalDetails.tsx @@ -0,0 +1,53 @@ +import { useQuery } from "@tanstack/react-query"; +import { formatEther } from "ethers/lib/utils"; +import { GOVERNANCE_CONTRACT } from "src/constants/contracts"; +import { NetworkId } from "src/networkDetails"; +import { getDateFromBlock } from "src/views/Governance/helpers"; +import { useProvider } from "wagmi"; + +export const useGetProposalDetails = ({ proposalId }: { proposalId: number }) => { + const archiveProvider = useProvider(); + const contract = GOVERNANCE_CONTRACT.getEthersContract(NetworkId.MAINNET); + return useQuery(["getProposalDetails", NetworkId.MAINNET, proposalId], { + queryFn: async () => { + const state = await contract.state(Number(proposalId)); + const proposalDetails = await contract.proposals(proposalId); + const blockTime = await archiveProvider.getBlock("latest"); + const endDateBlockTimestamp = await archiveProvider.getBlock(Number(proposalDetails.endBlock)); + const startDateBlockTimestamp = await archiveProvider.getBlock(Number(proposalDetails.startBlock)); + const startDate = getDateFromBlock(Number(proposalDetails.startBlock), blockTime.number, 15, blockTime.timestamp); + const endDate = getDateFromBlock(Number(proposalDetails.endBlock), blockTime.number, 15, blockTime.timestamp); + + return { + id: proposalDetails.id.toNumber(), + proposer: proposalDetails.proposer, + status: proposalStates[state], + forCount: Number(formatEther(proposalDetails.forVotes)), + againstCount: Number(formatEther(proposalDetails.againstVotes)), + abstainCount: Number(formatEther(proposalDetails.abstainVotes)), + startBlock: proposalDetails.startBlock.toNumber(), + endBlock: proposalDetails.endBlock.toNumber(), + eta: proposalDetails.eta.toNumber(), + etaDate: new Date(Number(proposalDetails?.eta) * 1000), + quorumVotes: Number(formatEther(proposalDetails.quorumVotes)), + proposalThreshold: Number(formatEther(proposalDetails.proposalThreshold)), + startDate: startDateBlockTimestamp?.timestamp ? new Date(startDateBlockTimestamp.timestamp * 1000) : startDate, + endDate: endDateBlockTimestamp?.timestamp ? new Date(endDateBlockTimestamp.timestamp * 1000) : endDate, + }; + }, + enabled: !!archiveProvider && !!contract && !!proposalId, + }); +}; + +export const proposalStates = [ + "Pending", + "Active", + "Canceled", + "Defeated", + "Succeeded", + "Queued", + "Expired", + "Executed", + "Vetoed", + "Emergency", +]; diff --git a/src/views/Governance/hooks/useGetProposals.tsx b/src/views/Governance/hooks/useGetProposals.tsx new file mode 100644 index 0000000000..7df43ce92e --- /dev/null +++ b/src/views/Governance/hooks/useGetProposals.tsx @@ -0,0 +1,45 @@ +import { useQuery } from "@tanstack/react-query"; +import { GOVERNANCE_CONTRACT } from "src/constants/contracts"; +import { NetworkId } from "src/networkDetails"; +import { ProposalCreatedEventObject } from "src/typechain/OlympusGovernorBravo"; +import { useProvider } from "wagmi"; + +export const useGetProposals = () => { + const archiveProvider = useProvider(); + const contract = GOVERNANCE_CONTRACT.getEthersContract(NetworkId.MAINNET); + return useQuery( + ["getProposals", NetworkId.MAINNET], + async () => { + // using EVENTS + const proposalCreatedEvents = await contract.queryFilter(contract.filters.ProposalCreated(), 19520392); + + const proposals = Promise.all( + proposalCreatedEvents.map(async item => { + const timestamp = (await archiveProvider.getBlock(item.blockNumber)).timestamp; + if (item.decode) { + const details = { ...item.decode(item.data), values: item.args[3] } as ProposalCreatedEventObject; + return { + createdAtBlock: new Date(timestamp * 1000), + details, + title: details.description.split(/#+\s|\n/g)[1] || `${details.description.slice(0, 20)}...`, + txHash: item.transactionHash, + }; + } + }), + ); + return proposals; + }, + { enabled: !!archiveProvider && !!contract }, + ); +}; + +export const useGetProposal = ({ proposalId }: { proposalId: number }) => { + const proposals = useGetProposals(); + return useQuery( + ["getProposal", NetworkId.MAINNET, proposalId], + async () => { + return proposals.data?.find(item => Number(item?.details.id) === proposalId); + }, + { enabled: !!proposals.data }, + ); +}; diff --git a/src/views/Governance/hooks/useGetQueuedTime.tsx b/src/views/Governance/hooks/useGetQueuedTime.tsx new file mode 100644 index 0000000000..0440381433 --- /dev/null +++ b/src/views/Governance/hooks/useGetQueuedTime.tsx @@ -0,0 +1,24 @@ +import { GOVERNANCE_CONTRACT } from "src/constants/contracts"; +import { NetworkId } from "src/networkDetails"; +import { ProposalQueuedEventObject } from "src/typechain/OlympusGovernorBravo"; +import { useProvider, useQuery } from "wagmi"; + +export const useGetQueuedTime = ({ proposalId }: { proposalId: number }) => { + const archiveProvider = useProvider(); + const contract = GOVERNANCE_CONTRACT.getEthersContract(NetworkId.MAINNET); + return useQuery( + ["getQueuedTime", NetworkId.MAINNET, proposalId], + async () => { + // using EVENTS + const proposalQueuedEvents = await contract.queryFilter(contract.filters.ProposalQueued(), 19520392); + const proposal = proposalQueuedEvents.find(item => item.args.id.toNumber() === proposalId); + const timestamp = proposal && (await archiveProvider.getBlock(proposal.blockNumber)).timestamp; + if (proposal?.decode) { + const details = proposal.decode(proposal.data) as ProposalQueuedEventObject; + return { createdAtDate: timestamp && new Date(timestamp * 1000), details, txHash: proposal.transactionHash }; + } + return { createdAtBlockTime: undefined, details: undefined, txHash: undefined }; + }, + { enabled: !!archiveProvider && !!contract && !!proposalId }, + ); +}; diff --git a/src/views/Governance/hooks/useGetReceipt.tsx b/src/views/Governance/hooks/useGetReceipt.tsx new file mode 100644 index 0000000000..0c5292736f --- /dev/null +++ b/src/views/Governance/hooks/useGetReceipt.tsx @@ -0,0 +1,20 @@ +import { useQuery } from "@tanstack/react-query"; +import { GOVERNANCE_CONTRACT } from "src/constants/contracts"; +import { NetworkId } from "src/networkDetails"; +import { useAccount, useProvider } from "wagmi"; + +export const useGetReceipt = ({ proposalId }: { proposalId: number }) => { + const { address } = useAccount(); + const provider = useProvider(); + return useQuery( + ["getReceipt", NetworkId.MAINNET, proposalId, address], + async () => { + if (!provider || !address) return; + const contract = GOVERNANCE_CONTRACT.getEthersContract(NetworkId.MAINNET); + const a = contract.connect(provider); + const receipt = await a.getReceipt(proposalId, address); + return receipt; + }, + { enabled: !!provider && !!address }, + ); +}; diff --git a/src/views/Governance/hooks/useGetVetoedTime.tsx b/src/views/Governance/hooks/useGetVetoedTime.tsx new file mode 100644 index 0000000000..99f7f8a1c4 --- /dev/null +++ b/src/views/Governance/hooks/useGetVetoedTime.tsx @@ -0,0 +1,28 @@ +import { useQuery } from "@tanstack/react-query"; +import { GOVERNANCE_CONTRACT } from "src/constants/contracts"; +import { NetworkId } from "src/networkDetails"; +import { ProposalVetoedEventObject } from "src/typechain/OlympusGovernorBravo"; +import { useProvider } from "wagmi"; + +export const useGetVetoedTime = ({ proposalId, status }: { proposalId: number; status?: string }) => { + const archiveProvider = useProvider(); + const contract = GOVERNANCE_CONTRACT.getEthersContract(NetworkId.MAINNET); + return useQuery( + ["getVetoedTime", NetworkId.MAINNET, proposalId, status], + async () => { + if (!status || status !== "Vetoed") { + return { createdAtBlockTime: undefined, details: undefined, txHash: undefined }; + } + // using EVENTS + const proposalExecutedEvents = await contract.queryFilter(contract.filters.ProposalVetoed(), 19520392); + const proposal = proposalExecutedEvents.find(item => item.args.id.toNumber() === proposalId); + const timestamp = proposal && (await archiveProvider.getBlock(proposal.blockNumber)).timestamp; + if (proposal?.decode) { + const details = proposal.decode(proposal.data) as ProposalVetoedEventObject; + return { createdAtDate: timestamp && new Date(timestamp * 1000), details, txHash: proposal.transactionHash }; + } + return { createdAtBlockTime: undefined, details: undefined, txHash: undefined }; + }, + { enabled: !!archiveProvider && !!contract && !!proposalId && !!status && status === "Vetoed" }, + ); +}; diff --git a/src/views/Governance/hooks/useGetVotingWeight.tsx b/src/views/Governance/hooks/useGetVotingWeight.tsx new file mode 100644 index 0000000000..da152a4f95 --- /dev/null +++ b/src/views/Governance/hooks/useGetVotingWeight.tsx @@ -0,0 +1,34 @@ +import { BigNumber } from "ethers"; +import { formatEther } from "ethers/lib/utils.js"; +import { GOHM_ADDRESSES } from "src/constants/addresses"; +import { useTestableNetworks } from "src/hooks/useTestableNetworks"; +import { GOHM__factory } from "src/typechain"; +import { useAccount, useProvider, useQuery } from "wagmi"; + +export const useGetVotingWeight = ({ startBlock }: { startBlock: number }) => { + const archiveProvider = useProvider(); + const { address } = useAccount(); + const networks = useTestableNetworks(); + + return useQuery( + ["votingWeight", address, startBlock], + async () => { + const currentBlock = await archiveProvider.getBlock("latest"); + if (!address) return "0"; + const contract = GOHM__factory.connect(GOHM_ADDRESSES[networks.MAINNET], archiveProvider); + //votes at activation + const currentVotes = await contract.getCurrentVotes(address); + + //if we're not activated yet + if (currentBlock.number < startBlock) { + return formatEther(currentVotes); + } else { + //we're activated and need to return how contract determines weight. votes at activation or current votes, whichever is less + const originalVotes = await contract.getPriorVotes(address, BigNumber.from(startBlock)); + const votes = originalVotes.gt(currentVotes) ? originalVotes : currentVotes; + return formatEther(votes); + } + }, + { enabled: !!archiveProvider && !!address }, + ); +}; diff --git a/src/views/Governance/hooks/useGovernanceDelegationCheck.tsx b/src/views/Governance/hooks/useGovernanceDelegationCheck.tsx new file mode 100644 index 0000000000..c8d206cb0d --- /dev/null +++ b/src/views/Governance/hooks/useGovernanceDelegationCheck.tsx @@ -0,0 +1,54 @@ +import { GOHM_ADDRESSES } from "src/constants/addresses"; +import { useTestableNetworks } from "src/hooks/useTestableNetworks"; +import { useCheckDelegation } from "src/views/Governance/hooks/useCheckDelegation"; +import { useGetClearingHouse } from "src/views/Lending/Cooler/hooks/useGetClearingHouse"; +import { useGetCoolerForWallet } from "src/views/Lending/Cooler/hooks/useGetCoolerForWallet"; +import { useAccount, useBalance } from "wagmi"; + +export const useGovernanceDelegationCheck = () => { + const { address } = useAccount(); + const networks = useTestableNetworks(); + + const { data: clearingHouseV1 } = useGetClearingHouse({ clearingHouse: "clearingHouseV1" }); + const { data: clearingHouseV2 } = useGetClearingHouse({ clearingHouse: "clearingHouseV2" }); + const { data: coolerAddressV1 } = useGetCoolerForWallet({ + walletAddress: address, + factoryAddress: clearingHouseV1?.factory, + collateralAddress: clearingHouseV1?.collateralAddress, + debtAddress: clearingHouseV1?.debtAddress, + clearingHouseVersion: "clearingHouseV1", + }); + const { data: coolerAddressV2 } = useGetCoolerForWallet({ + walletAddress: address, + factoryAddress: clearingHouseV2?.factory, + collateralAddress: clearingHouseV2?.collateralAddress, + debtAddress: clearingHouseV2?.debtAddress, + clearingHouseVersion: "clearingHouseV2", + }); + const { data: gOHMDelegationAddress } = useCheckDelegation({ address }); + const { data: coolerV1DelegationAddress } = useCheckDelegation({ address: coolerAddressV1 }); + const { data: coolerV2DelegationAddress } = useCheckDelegation({ address: coolerAddressV2 }); + + const { data: gohmCoolerV2Balance } = useBalance({ + address: coolerAddressV2 as `0x${string}`, + token: GOHM_ADDRESSES[networks.MAINNET] as `0x${string}`, + }); + const { data: gohmCoolerV1Balance } = useBalance({ + address: coolerAddressV1 as `0x${string}`, + token: GOHM_ADDRESSES[networks.MAINNET] as `0x${string}`, + }); + const { data: gohmBalance } = useBalance({ + address: address as `0x${string}`, + token: GOHM_ADDRESSES[networks.MAINNET] as `0x${string}`, + }); + return { + gOHMDelegationAddress, + coolerV1DelegationAddress, + coolerV2DelegationAddress, + gohmBalance, + gohmCoolerV1Balance, + gohmCoolerV2Balance, + coolerAddressV1, + coolerAddressV2, + }; +}; diff --git a/src/views/Governance/hooks/useQueueProposal.tsx b/src/views/Governance/hooks/useQueueProposal.tsx new file mode 100644 index 0000000000..f81162eed2 --- /dev/null +++ b/src/views/Governance/hooks/useQueueProposal.tsx @@ -0,0 +1,26 @@ +import { useMutation, useQueryClient } from "@tanstack/react-query"; +import { GOVERNANCE_CONTRACT } from "src/constants/contracts"; +import { NetworkId } from "src/networkDetails"; +import { useSigner } from "wagmi"; + +export const useQueueProposal = () => { + const { data: signer } = useSigner(); + const queryClient = useQueryClient(); + return useMutation( + async ({ proposalId }: { proposalId: number }) => { + if (signer) { + const contract = GOVERNANCE_CONTRACT.getEthersContract(NetworkId.MAINNET); + const a = contract.connect(signer); + + const queue = await a.queue(proposalId); + return queue; + } + return true; + }, + { + onSuccess: (data, variables) => { + queryClient.invalidateQueries({ queryKey: ["getProposalDetails", NetworkId.MAINNET, variables.proposalId] }); + }, + }, + ); +}; diff --git a/src/views/Governance/hooks/useVoteForProposal.tsx b/src/views/Governance/hooks/useVoteForProposal.tsx new file mode 100644 index 0000000000..2b7bd068eb --- /dev/null +++ b/src/views/Governance/hooks/useVoteForProposal.tsx @@ -0,0 +1,35 @@ +import { useMutation } from "@tanstack/react-query"; +import { useQueryClient } from "@tanstack/react-query"; +import { GOVERNANCE_CONTRACT } from "src/constants/contracts"; +import { NetworkId } from "src/networkDetails"; +import { useSigner } from "wagmi"; + +export const useVoteForProposal = () => { + const { data: signer } = useSigner(); + const queryClient = useQueryClient(); + return useMutation( + async ({ proposalId, vote, comment }: { proposalId: number; vote: number; comment?: string }) => { + if (signer) { + const contract = GOVERNANCE_CONTRACT.getEthersContract(NetworkId.MAINNET); + const a = contract.connect(signer); + //. 0=against, 1=for, 2=abstain + if (comment) { + const voteResponse = await a.castVoteWithReason(proposalId, vote, comment); + const receipt = await voteResponse.wait(); + return receipt; + } else { + const voteResponse = await a.castVote(proposalId, vote); + const receipt = await voteResponse.wait(); + return receipt; + } + } + return true; + }, + { + onSuccess: (data, variables) => { + queryClient.invalidateQueries({ queryKey: ["getProposalDetails", NetworkId.MAINNET, variables.proposalId] }); + queryClient.invalidateQueries({ queryKey: ["getReceipt", NetworkId.MAINNET, variables.proposalId] }); + }, + }, + ); +}; diff --git a/src/views/Governance/index.tsx b/src/views/Governance/index.tsx new file mode 100644 index 0000000000..96eb9e8ff5 --- /dev/null +++ b/src/views/Governance/index.tsx @@ -0,0 +1,54 @@ +import { Box, Link, Paper, Table, TableBody, TableCell, TableContainer, TableHead, TableRow } from "@mui/material"; +import { PrimaryButton } from "@olympusdao/component-library"; +import { Link as RouterLink } from "react-router-dom"; +import PageTitle from "src/components/PageTitle"; +import { ContractParameters } from "src/views/Governance/Components/ContractParameters"; +import { ProposalContainer } from "src/views/Governance/Components/ProposalContainer"; +import { DelegationMessage } from "src/views/Governance/Delegation/DelegationMessage"; +import { GovernanceDevTools } from "src/views/Governance/hooks/dev/GovernanceDevTools"; +import { useGetProposals } from "src/views/Governance/hooks/useGetProposals"; + +export const Governance = () => { + const { data: proposals } = useGetProposals(); + return ( +
+ + + + + + + My Voting Power + + + + + + + Proposal + Votes for + Votes against + + Total votes + + + + + {proposals?.map((item, index) => { + return ( + + ); + })} + +
+
+ +
+
+ ); +}; diff --git a/yarn.lock b/yarn.lock index 7d22caecd4..b3a616da9c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7,6 +7,11 @@ resolved "https://registry.yarnpkg.com/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz#bd9154aec9983f77b3a034ecaa015c2e4201f6cf" integrity sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA== +"@adraffy/ens-normalize@1.10.1": + version "1.10.1" + resolved "https://registry.yarnpkg.com/@adraffy/ens-normalize/-/ens-normalize-1.10.1.tgz#63430d04bd8c5e74f8d7d049338f1cd9d4f02069" + integrity sha512-96Z2IP3mYmF1Xg2cDm8f1gWGf/HUVedQ3FMifV4kG/PQ4yEP51xDtRAEfhVNt5f/uzpNkZHwWQuUcu6D6K+Ekw== + "@ampproject/remapping@^2.2.0", "@ampproject/remapping@^2.2.1": version "2.2.1" resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.1.tgz#99e8e11851128b8702cd57c33684f1d0f260b630" @@ -2766,11 +2771,23 @@ resolved "https://registry.yarnpkg.com/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.3.tgz#323d72dd25103d0c4fbdce89dadf574a787b1f9b" integrity sha512-s88O1aVtXftvp5bCPB7WnmXc5IwOZZ7YPuwNPt+GtOOXpPvad1LfbmjYv+qII7zP6RU2QGnqve27dnLycEnyEQ== +"@noble/curves@1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.2.0.tgz#92d7e12e4e49b23105a2555c6984d41733d65c35" + integrity sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw== + dependencies: + "@noble/hashes" "1.3.2" + "@noble/ed25519@^1.7.0": version "1.7.3" resolved "https://registry.yarnpkg.com/@noble/ed25519/-/ed25519-1.7.3.tgz#57e1677bf6885354b466c38e2b620c62f45a7123" integrity sha512-iR8GBkDt0Q3GyaVcIu7mSsVIqnFbkbRzGLWlvhwunacoLwt4J3swfKhfaM6rN6WY+TBGoYT1GtT1mIh2/jGbRQ== +"@noble/hashes@1.3.2": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.2.tgz#6f26dbc8fbc7205873ce3cee2f690eba0d421b39" + integrity sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ== + "@noble/hashes@^1.1.2": version "1.3.0" resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.0.tgz#085fd70f6d7d9d109671090ccae1d3bec62554a1" @@ -2874,6 +2891,13 @@ graphql-compose "9.0.10" graphql-scalars "^1.22.2" +"@openchainxyz/abi-guesser@^1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@openchainxyz/abi-guesser/-/abi-guesser-1.0.2.tgz#83ed50eaa227a2d21b354c797d8fb65dde1b3025" + integrity sha512-Qs5vdCbVa519SEz45mxm/CTmJAzqgBQuWI09iWtO0sXmcnKyeqS2nMXlRsQOD/XGtDW7+UssVTiv9ovE8tDfqQ== + dependencies: + ethers "^6.0.2" + "@opentelemetry/api-logs@0.39.1": version "0.39.1" resolved "https://registry.yarnpkg.com/@opentelemetry/api-logs/-/api-logs-0.39.1.tgz#3ea1e9dda11c35f993cb60dc5e52780b8175e702" @@ -4255,6 +4279,18 @@ dependencies: "@types/node" "*" +"@types/estree-jsx@^1.0.0": + version "1.0.5" + resolved "https://registry.yarnpkg.com/@types/estree-jsx/-/estree-jsx-1.0.5.tgz#858a88ea20f34fe65111f005a689fa1ebf70dc18" + integrity sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg== + dependencies: + "@types/estree" "*" + +"@types/estree@*": + version "1.0.5" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4" + integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw== + "@types/estree@^1.0.0": version "1.0.0" resolved "https://registry.npmjs.org/@types/estree/-/estree-1.0.0.tgz" @@ -4280,10 +4316,10 @@ dependencies: graphql "*" -"@types/hast@^2.0.0": - version "2.3.4" - resolved "https://registry.npmjs.org/@types/hast/-/hast-2.3.4.tgz" - integrity sha512-wLEm0QvaoawEDoTRwzTXp4b4jpwiJDvR5KMnFnVodm3scufTlBOWRD6N1OBf9TZMhjlNsSfcO5V+7AF4+Vy+9g== +"@types/hast@^3.0.0": + version "3.0.4" + resolved "https://registry.yarnpkg.com/@types/hast/-/hast-3.0.4.tgz#1d6b39993b82cea6ad783945b0508c25903e15aa" + integrity sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ== dependencies: "@types/unist" "*" @@ -4343,14 +4379,14 @@ "@types/linkify-it" "*" "@types/mdurl" "*" -"@types/mdast@^3.0.0": - version "3.0.10" - resolved "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.10.tgz" - integrity sha512-W864tg/Osz1+9f4lrGTZpCSO5/z4608eUp19tbozkq2HJK6i3z1kT0H9tlADXuYIb1YYOBByU4Jsqkk75q48qA== +"@types/mdast@^4.0.0": + version "4.0.3" + resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-4.0.3.tgz#1e011ff013566e919a4232d1701ad30d70cab333" + integrity sha512-LsjtqsyF+d2/yFOYaN22dHZI1Cpwkrj+g06G8+qtUKlhovPW89YhqSnfKtMbkgmEtYpH2gydRNULd6y8mciAFg== dependencies: "@types/unist" "*" -"@types/mdurl@*", "@types/mdurl@^1.0.0": +"@types/mdurl@*": version "1.0.2" resolved "https://registry.npmjs.org/@types/mdurl/-/mdurl-1.0.2.tgz" integrity sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA== @@ -4372,6 +4408,11 @@ dependencies: undici-types "~5.26.4" +"@types/node@18.15.13": + version "18.15.13" + resolved "https://registry.yarnpkg.com/@types/node/-/node-18.15.13.tgz#f64277c341150c979e42b00e4ac289290c9df469" + integrity sha512-N+0kuo9KgrUQ1Sn/ifDXsvg0TTleP7rIy4zOBGECxAljqvqfqpTfzx0Q1NUedOixRMBfe2Whhb056a42cWs26Q== + "@types/node@^12.12.54": version "12.20.55" resolved "https://registry.npmjs.org/@types/node/-/node-12.20.55.tgz" @@ -4402,7 +4443,7 @@ resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.7.3.tgz#3e51a17e291d01d17d3fc61422015a933af7a08f" integrity sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA== -"@types/prop-types@*", "@types/prop-types@^15.0.0", "@types/prop-types@^15.7.11": +"@types/prop-types@*", "@types/prop-types@^15.7.11": version "15.7.11" resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.11.tgz#2596fb352ee96a1379c657734d4b913a613ad563" integrity sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng== @@ -4498,6 +4539,11 @@ resolved "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz" integrity sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ== +"@types/unist@^3.0.0": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@types/unist/-/unist-3.0.2.tgz#6dd61e43ef60b34086287f83683a5c1b2dc53d20" + integrity sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ== + "@types/urijs@^1.19.19": version "1.19.20" resolved "https://registry.yarnpkg.com/@types/urijs/-/urijs-1.19.20.tgz#7ea4254f4c2cdbd7d34e47d483e76fa1b81e19a4" @@ -4609,7 +4655,7 @@ "@typescript-eslint/types" "5.62.0" eslint-visitor-keys "^3.3.0" -"@ungap/structured-clone@^1.2.0": +"@ungap/structured-clone@^1.0.0", "@ungap/structured-clone@^1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406" integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ== @@ -5430,6 +5476,11 @@ aes-js@3.0.0: resolved "https://registry.npmjs.org/aes-js/-/aes-js-3.0.0.tgz" integrity sha1-4h3xCtbCBTKVvLuNq0Cwnb6ofk0= +aes-js@4.0.0-beta.5: + version "4.0.0-beta.5" + resolved "https://registry.yarnpkg.com/aes-js/-/aes-js-4.0.0-beta.5.tgz#8d2452c52adedebc3a3e28465d858c11ca315873" + integrity sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q== + aes-js@^3.1.2: version "3.1.2" resolved "https://registry.npmjs.org/aes-js/-/aes-js-3.1.2.tgz" @@ -6092,6 +6143,11 @@ case-anything@^2.1.13: resolved "https://registry.yarnpkg.com/case-anything/-/case-anything-2.1.13.tgz#0cdc16278cb29a7fcdeb072400da3f342ba329e9" integrity sha512-zlOQ80VrQ2Ue+ymH5OuM/DlDq64mEm+B9UTdHULv5osUMD6HalNTblf2b1u/m6QecjsnOkBpqVZ+XPwIVsy7Ng== +ccount@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/ccount/-/ccount-2.0.1.tgz#17a3bf82302e0870d6da43a01311a8bc02a3ecf5" + integrity sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg== + chai@^4.3.10: version "4.3.10" resolved "https://registry.yarnpkg.com/chai/-/chai-4.3.10.tgz#d784cec635e3b7e2ffb66446a63b4e33bd390384" @@ -6158,11 +6214,26 @@ change-case@^4.1.2: snake-case "^3.0.4" tslib "^2.0.3" +character-entities-html4@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/character-entities-html4/-/character-entities-html4-2.1.0.tgz#1f1adb940c971a4b22ba39ddca6b618dc6e56b2b" + integrity sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA== + +character-entities-legacy@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz#76bc83a90738901d7bc223a9e93759fdd560125b" + integrity sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ== + character-entities@^2.0.0: version "2.0.2" resolved "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz" integrity sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ== +character-reference-invalid@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz#85c66b041e43b47210faf401278abf808ac45cb9" + integrity sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw== + charset@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/charset/-/charset-1.0.1.tgz#8d59546c355be61049a8fa9164747793319852bd" @@ -6863,6 +6934,13 @@ detect-node-es@^1.1.0: resolved "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz" integrity sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ== +devlop@^1.0.0, devlop@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/devlop/-/devlop-1.1.0.tgz#4db7c2ca4dc6e0e834c30be70c94bbc976dc7018" + integrity sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA== + dependencies: + dequal "^2.0.0" + diff-sequences@^29.4.3: version "29.6.3" resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.6.3.tgz#4deaf894d11407c51efc8418012f9e70b84ea921" @@ -6873,11 +6951,6 @@ diff@^4.0.1: resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== -diff@^5.0.0: - version "5.1.0" - resolved "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz" - integrity sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw== - dijkstrajs@^1.0.1: version "1.0.2" resolved "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.2.tgz" @@ -7358,6 +7431,11 @@ escape-string-regexp@^4.0.0: resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz" integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== +escape-string-regexp@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz#4683126b500b61762f2dbebace1806e8be31b1c8" + integrity sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw== + escodegen@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz" @@ -7615,6 +7693,11 @@ estraverse@^5.1.0, estraverse@^5.2.0, estraverse@^5.3.0: resolved "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz" integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== +estree-util-is-identifier-name@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz#0b5ef4c4ff13508b34dcd01ecfa945f61fce5dbd" + integrity sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg== + estree-walker@^0.6.1: version "0.6.1" resolved "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz" @@ -7709,6 +7792,19 @@ ethers@^5.7.2: "@ethersproject/web" "5.7.1" "@ethersproject/wordlists" "5.7.0" +ethers@^6.0.2: + version "6.11.1" + resolved "https://registry.yarnpkg.com/ethers/-/ethers-6.11.1.tgz#96aae00b627c2e35f9b0a4d65c7ab658259ee6af" + integrity sha512-mxTAE6wqJQAbp5QAe/+o+rXOID7Nw91OZXvgpjDa1r4fAbq2Nu314oEZSbjoRLacuCzs7kUC3clEvkCQowffGg== + dependencies: + "@adraffy/ens-normalize" "1.10.1" + "@noble/curves" "1.2.0" + "@noble/hashes" "1.3.2" + "@types/node" "18.15.13" + aes-js "4.0.0-beta.5" + tslib "2.4.0" + ws "8.5.0" + event-emitter@^0.3.5: version "0.3.5" resolved "https://registry.yarnpkg.com/event-emitter/-/event-emitter-0.3.5.tgz#df8c69eef1647923c7157b9ce83840610b02cc39" @@ -8527,10 +8623,33 @@ hasown@^2.0.0: dependencies: function-bind "^1.1.2" -hast-util-whitespace@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-2.0.0.tgz" - integrity sha512-Pkw+xBHuV6xFeJprJe2BBEoDV+AvQySaz3pPDRUs5PNZEMQjpXJJueqrpcHIXxnWTcAGi/UOCgVShlkY6kLoqg== +hast-util-to-jsx-runtime@^2.0.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.0.tgz#3ed27caf8dc175080117706bf7269404a0aa4f7c" + integrity sha512-H/y0+IWPdsLLS738P8tDnrQ8Z+dj12zQQ6WC11TIM21C8WFVoIxcqWXf2H3hiTVZjF1AWqoimGwrTWecWrnmRQ== + dependencies: + "@types/estree" "^1.0.0" + "@types/hast" "^3.0.0" + "@types/unist" "^3.0.0" + comma-separated-tokens "^2.0.0" + devlop "^1.0.0" + estree-util-is-identifier-name "^3.0.0" + hast-util-whitespace "^3.0.0" + mdast-util-mdx-expression "^2.0.0" + mdast-util-mdx-jsx "^3.0.0" + mdast-util-mdxjs-esm "^2.0.0" + property-information "^6.0.0" + space-separated-tokens "^2.0.0" + style-to-object "^1.0.0" + unist-util-position "^5.0.0" + vfile-message "^4.0.0" + +hast-util-whitespace@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz#7778ed9d3c92dd9e8c5c8f648a49c21fc51cb621" + integrity sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw== + dependencies: + "@types/hast" "^3.0.0" header-case@^2.0.4: version "2.0.4" @@ -8578,6 +8697,11 @@ html-escaper@^2.0.0: resolved "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz" integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg== +html-url-attributes@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/html-url-attributes/-/html-url-attributes-3.0.0.tgz#fc4abf0c3fb437e2329c678b80abb3c62cff6f08" + integrity sha512-/sXbVCWayk6GDVg3ctOX6nxaVj7So40FcFAnWlWGNAB1LpYKcV5Cd10APjPjW80O7zYW2MsjBV4zZ7IZO5fVow== + http-errors@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3" @@ -8708,10 +8832,10 @@ inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, i resolved "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== -inline-style-parser@0.1.1: - version "0.1.1" - resolved "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.1.1.tgz" - integrity sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q== +inline-style-parser@0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/inline-style-parser/-/inline-style-parser-0.2.2.tgz#d498b4e6de0373458fc610ff793f6b14ebf45633" + integrity sha512-EcKzdTHVe8wFVOGEYXiW9WmJXPjqi1T+234YpJr98RiFYKHV3cdy1+3mkTE+KHTHxFFLH51SfaGOoUdW+v7ViQ== internal-slot@^1.0.3, internal-slot@^1.0.5: version "1.0.5" @@ -8744,6 +8868,19 @@ ipaddr.js@1.9.1: resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== +is-alphabetical@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-alphabetical/-/is-alphabetical-2.0.1.tgz#01072053ea7c1036df3c7d19a6daaec7f19e789b" + integrity sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ== + +is-alphanumerical@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz#7c03fbe96e3e931113e57f964b0a368cc2dfd875" + integrity sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw== + dependencies: + is-alphabetical "^2.0.0" + is-decimal "^2.0.0" + is-arguments@^1.0.4, is-arguments@^1.1.0, is-arguments@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.1.tgz#15b3f88fda01f2a97fec84ca761a560f123efa9b" @@ -8804,11 +8941,6 @@ is-boolean-object@^1.1.0: call-bind "^1.0.2" has-tostringtag "^1.0.0" -is-buffer@^2.0.0: - version "2.0.5" - resolved "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz" - integrity sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ== - is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.4, is-callable@^1.2.7: version "1.2.7" resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" @@ -8828,6 +8960,11 @@ is-date-object@^1.0.1, is-date-object@^1.0.5: dependencies: has-tostringtag "^1.0.0" +is-decimal@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-decimal/-/is-decimal-2.0.1.tgz#9469d2dc190d0214fd87d78b78caecc0cc14eef7" + integrity sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A== + is-docker@^2.0.0: version "2.2.1" resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" @@ -8874,6 +9011,11 @@ is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: dependencies: is-extglob "^2.1.1" +is-hexadecimal@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz#86b5bf668fca307498d319dfc03289d781a90027" + integrity sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg== + is-inside-container@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-inside-container/-/is-inside-container-1.0.0.tgz#e81fba699662eb31dbdaf26766a61d4814717ea4" @@ -9403,11 +9545,6 @@ kleur@^3.0.3: resolved "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz" integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== -kleur@^4.0.3: - version "4.1.5" - resolved "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz" - integrity sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ== - language-subtag-registry@^0.3.20: version "0.3.22" resolved "https://registry.yarnpkg.com/language-subtag-registry/-/language-subtag-registry-0.3.22.tgz#2e1500861b2e457eba7e7ae86877cbd08fa1fd1d" @@ -9663,6 +9800,11 @@ long@^5.0.0, long@^5.2.0, long@^5.2.3: resolved "https://registry.yarnpkg.com/long/-/long-5.2.3.tgz#a3ba97f3877cf1d778eccbcb048525ebb77499e1" integrity sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q== +longest-streak@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/longest-streak/-/longest-streak-3.1.0.tgz#62fa67cd958742a1574af9f39866364102d90cd4" + integrity sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g== + loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0: version "1.4.0" resolved "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz" @@ -9778,64 +9920,195 @@ mark.js@^8.11.1: resolved "https://registry.yarnpkg.com/mark.js/-/mark.js-8.11.1.tgz#180f1f9ebef8b0e638e4166ad52db879beb2ffc5" integrity sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ== +markdown-table@^3.0.0: + version "3.0.3" + resolved "https://registry.yarnpkg.com/markdown-table/-/markdown-table-3.0.3.tgz#e6331d30e493127e031dd385488b5bd326e4a6bd" + integrity sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw== + marked@^4.0.15: version "4.3.0" resolved "https://registry.yarnpkg.com/marked/-/marked-4.3.0.tgz#796362821b019f734054582038b116481b456cf3" integrity sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A== -mdast-util-definitions@^5.0.0: - version "5.1.1" - resolved "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-5.1.1.tgz" - integrity sha512-rQ+Gv7mHttxHOBx2dkF4HWTg+EE+UR78ptQWDylzPKaQuVGdG4HIoY3SrS/pCp80nZ04greFvXbVFHT+uf0JVQ== +mdast-util-find-and-replace@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.1.tgz#a6fc7b62f0994e973490e45262e4bc07607b04e0" + integrity sha512-SG21kZHGC3XRTSUhtofZkBzZTJNM5ecCi0SK2IMKmSXR8vO3peL+kb1O0z7Zl83jKtutG4k5Wv/W7V3/YHvzPA== dependencies: - "@types/mdast" "^3.0.0" - "@types/unist" "^2.0.0" - unist-util-visit "^4.0.0" + "@types/mdast" "^4.0.0" + escape-string-regexp "^5.0.0" + unist-util-is "^6.0.0" + unist-util-visit-parents "^6.0.0" -mdast-util-from-markdown@^1.0.0: - version "1.2.0" - resolved "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-1.2.0.tgz" - integrity sha512-iZJyyvKD1+K7QX1b5jXdE7Sc5dtoTry1vzV28UZZe8Z1xVnB/czKntJ7ZAkG0tANqRnBF6p3p7GpU1y19DTf2Q== +mdast-util-from-markdown@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.0.tgz#52f14815ec291ed061f2922fd14d6689c810cb88" + integrity sha512-n7MTOr/z+8NAX/wmhhDji8O3bRvPTV/U0oTCaZJkjhPSKTPhS3xufVhKGF8s1pJ7Ox4QgoIU7KHseh09S+9rTA== dependencies: - "@types/mdast" "^3.0.0" - "@types/unist" "^2.0.0" + "@types/mdast" "^4.0.0" + "@types/unist" "^3.0.0" decode-named-character-reference "^1.0.0" - mdast-util-to-string "^3.1.0" - micromark "^3.0.0" - micromark-util-decode-numeric-character-reference "^1.0.0" - micromark-util-decode-string "^1.0.0" - micromark-util-normalize-identifier "^1.0.0" - micromark-util-symbol "^1.0.0" - micromark-util-types "^1.0.0" - unist-util-stringify-position "^3.0.0" - uvu "^0.5.0" - -mdast-util-to-hast@^12.1.0: - version "12.2.0" - resolved "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-12.2.0.tgz" - integrity sha512-YDwT5KhGzLgPpSnQhAlK1+WpCW4gsPmNNAxUNMkMTDhxQyPp2eX86WOelnKnLKEvSpfxqJbPbInHFkefXZBhEA== - dependencies: - "@types/hast" "^2.0.0" - "@types/mdast" "^3.0.0" - "@types/mdurl" "^1.0.0" - mdast-util-definitions "^5.0.0" - mdurl "^1.0.0" - micromark-util-sanitize-uri "^1.0.0" - trim-lines "^3.0.0" - unist-builder "^3.0.0" - unist-util-generated "^2.0.0" - unist-util-position "^4.0.0" - unist-util-visit "^4.0.0" + devlop "^1.0.0" + mdast-util-to-string "^4.0.0" + micromark "^4.0.0" + micromark-util-decode-numeric-character-reference "^2.0.0" + micromark-util-decode-string "^2.0.0" + micromark-util-normalize-identifier "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + unist-util-stringify-position "^4.0.0" + +mdast-util-gfm-autolink-literal@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/mdast-util-gfm-autolink-literal/-/mdast-util-gfm-autolink-literal-2.0.0.tgz#5baf35407421310a08e68c15e5d8821e8898ba2a" + integrity sha512-FyzMsduZZHSc3i0Px3PQcBT4WJY/X/RCtEJKuybiC6sjPqLv7h1yqAkmILZtuxMSsUyaLUWNp71+vQH2zqp5cg== + dependencies: + "@types/mdast" "^4.0.0" + ccount "^2.0.0" + devlop "^1.0.0" + mdast-util-find-and-replace "^3.0.0" + micromark-util-character "^2.0.0" -mdast-util-to-string@^3.1.0: - version "3.1.0" - resolved "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-3.1.0.tgz" - integrity sha512-n4Vypz/DZgwo0iMHLQL49dJzlp7YtAJP+N07MZHpjPf/5XJuHUWstviF4Mn2jEiR/GNmtnRRqnwsXExk3igfFA== +mdast-util-gfm-footnote@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/mdast-util-gfm-footnote/-/mdast-util-gfm-footnote-2.0.0.tgz#25a1753c7d16db8bfd53cd84fe50562bd1e6d6a9" + integrity sha512-5jOT2boTSVkMnQ7LTrd6n/18kqwjmuYqo7JUPe+tRCY6O7dAuTFMtTPauYYrMPpox9hlN0uOx/FL8XvEfG9/mQ== + dependencies: + "@types/mdast" "^4.0.0" + devlop "^1.1.0" + mdast-util-from-markdown "^2.0.0" + mdast-util-to-markdown "^2.0.0" + micromark-util-normalize-identifier "^2.0.0" -mdurl@^1.0.0: - version "1.0.1" - resolved "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz" - integrity sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4= +mdast-util-gfm-strikethrough@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/mdast-util-gfm-strikethrough/-/mdast-util-gfm-strikethrough-2.0.0.tgz#d44ef9e8ed283ac8c1165ab0d0dfd058c2764c16" + integrity sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg== + dependencies: + "@types/mdast" "^4.0.0" + mdast-util-from-markdown "^2.0.0" + mdast-util-to-markdown "^2.0.0" + +mdast-util-gfm-table@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/mdast-util-gfm-table/-/mdast-util-gfm-table-2.0.0.tgz#7a435fb6223a72b0862b33afbd712b6dae878d38" + integrity sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg== + dependencies: + "@types/mdast" "^4.0.0" + devlop "^1.0.0" + markdown-table "^3.0.0" + mdast-util-from-markdown "^2.0.0" + mdast-util-to-markdown "^2.0.0" + +mdast-util-gfm-task-list-item@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/mdast-util-gfm-task-list-item/-/mdast-util-gfm-task-list-item-2.0.0.tgz#e68095d2f8a4303ef24094ab642e1047b991a936" + integrity sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ== + dependencies: + "@types/mdast" "^4.0.0" + devlop "^1.0.0" + mdast-util-from-markdown "^2.0.0" + mdast-util-to-markdown "^2.0.0" + +mdast-util-gfm@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/mdast-util-gfm/-/mdast-util-gfm-3.0.0.tgz#3f2aecc879785c3cb6a81ff3a243dc11eca61095" + integrity sha512-dgQEX5Amaq+DuUqf26jJqSK9qgixgd6rYDHAv4aTBuA92cTknZlKpPfa86Z/s8Dj8xsAQpFfBmPUHWJBWqS4Bw== + dependencies: + mdast-util-from-markdown "^2.0.0" + mdast-util-gfm-autolink-literal "^2.0.0" + mdast-util-gfm-footnote "^2.0.0" + mdast-util-gfm-strikethrough "^2.0.0" + mdast-util-gfm-table "^2.0.0" + mdast-util-gfm-task-list-item "^2.0.0" + mdast-util-to-markdown "^2.0.0" + +mdast-util-mdx-expression@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.0.tgz#4968b73724d320a379110d853e943a501bfd9d87" + integrity sha512-fGCu8eWdKUKNu5mohVGkhBXCXGnOTLuFqOvGMvdikr+J1w7lDJgxThOKpwRWzzbyXAU2hhSwsmssOY4yTokluw== + dependencies: + "@types/estree-jsx" "^1.0.0" + "@types/hast" "^3.0.0" + "@types/mdast" "^4.0.0" + devlop "^1.0.0" + mdast-util-from-markdown "^2.0.0" + mdast-util-to-markdown "^2.0.0" + +mdast-util-mdx-jsx@^3.0.0: + version "3.1.2" + resolved "https://registry.yarnpkg.com/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.1.2.tgz#daae777c72f9c4a106592e3025aa50fb26068e1b" + integrity sha512-eKMQDeywY2wlHc97k5eD8VC+9ASMjN8ItEZQNGwJ6E0XWKiW/Z0V5/H8pvoXUf+y+Mj0VIgeRRbujBmFn4FTyA== + dependencies: + "@types/estree-jsx" "^1.0.0" + "@types/hast" "^3.0.0" + "@types/mdast" "^4.0.0" + "@types/unist" "^3.0.0" + ccount "^2.0.0" + devlop "^1.1.0" + mdast-util-from-markdown "^2.0.0" + mdast-util-to-markdown "^2.0.0" + parse-entities "^4.0.0" + stringify-entities "^4.0.0" + unist-util-remove-position "^5.0.0" + unist-util-stringify-position "^4.0.0" + vfile-message "^4.0.0" + +mdast-util-mdxjs-esm@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz#019cfbe757ad62dd557db35a695e7314bcc9fa97" + integrity sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg== + dependencies: + "@types/estree-jsx" "^1.0.0" + "@types/hast" "^3.0.0" + "@types/mdast" "^4.0.0" + devlop "^1.0.0" + mdast-util-from-markdown "^2.0.0" + mdast-util-to-markdown "^2.0.0" + +mdast-util-phrasing@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz#7cc0a8dec30eaf04b7b1a9661a92adb3382aa6e3" + integrity sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w== + dependencies: + "@types/mdast" "^4.0.0" + unist-util-is "^6.0.0" + +mdast-util-to-hast@^13.0.0: + version "13.1.0" + resolved "https://registry.yarnpkg.com/mdast-util-to-hast/-/mdast-util-to-hast-13.1.0.tgz#1ae54d903150a10fe04d59f03b2b95fd210b2124" + integrity sha512-/e2l/6+OdGp/FB+ctrJ9Avz71AN/GRH3oi/3KAx/kMnoUsD6q0woXlDT8lLEeViVKE7oZxE7RXzvO3T8kF2/sA== + dependencies: + "@types/hast" "^3.0.0" + "@types/mdast" "^4.0.0" + "@ungap/structured-clone" "^1.0.0" + devlop "^1.0.0" + micromark-util-sanitize-uri "^2.0.0" + trim-lines "^3.0.0" + unist-util-position "^5.0.0" + unist-util-visit "^5.0.0" + vfile "^6.0.0" + +mdast-util-to-markdown@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.0.tgz#9813f1d6e0cdaac7c244ec8c6dabfdb2102ea2b4" + integrity sha512-SR2VnIEdVNCJbP6y7kVTJgPLifdr8WEU440fQec7qHoHOUz/oJ2jmNRqdDQ3rbiStOXb2mCDGTuwsK5OPUgYlQ== + dependencies: + "@types/mdast" "^4.0.0" + "@types/unist" "^3.0.0" + longest-streak "^3.0.0" + mdast-util-phrasing "^4.0.0" + mdast-util-to-string "^4.0.0" + micromark-util-decode-string "^2.0.0" + unist-util-visit "^5.0.0" + zwitch "^2.0.0" + +mdast-util-to-string@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz#7a5121475556a04e7eddeb67b264aae79d312814" + integrity sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg== + dependencies: + "@types/mdast" "^4.0.0" media-query-parser@^2.0.2: version "2.0.2" @@ -9868,200 +10141,278 @@ merge2@^1.3.0, merge2@^1.4.1: resolved "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz" integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== -micromark-core-commonmark@^1.0.1: - version "1.0.6" - resolved "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-1.0.6.tgz" - integrity sha512-K+PkJTxqjFfSNkfAhp4GB+cZPfQd6dxtTXnf+RjZOV7T4EEXnvgzOcnp+eSTmpGk9d1S9sL6/lqrgSNn/s0HZA== +micromark-core-commonmark@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/micromark-core-commonmark/-/micromark-core-commonmark-2.0.0.tgz#50740201f0ee78c12a675bf3e68ffebc0bf931a3" + integrity sha512-jThOz/pVmAYUtkroV3D5c1osFXAMv9e0ypGDOIZuCeAe91/sD6BoE2Sjzt30yuXtwOYUmySOhMas/PVyh02itA== dependencies: decode-named-character-reference "^1.0.0" - micromark-factory-destination "^1.0.0" - micromark-factory-label "^1.0.0" - micromark-factory-space "^1.0.0" - micromark-factory-title "^1.0.0" - micromark-factory-whitespace "^1.0.0" - micromark-util-character "^1.0.0" - micromark-util-chunked "^1.0.0" - micromark-util-classify-character "^1.0.0" - micromark-util-html-tag-name "^1.0.0" - micromark-util-normalize-identifier "^1.0.0" - micromark-util-resolve-all "^1.0.0" - micromark-util-subtokenize "^1.0.0" - micromark-util-symbol "^1.0.0" - micromark-util-types "^1.0.1" - uvu "^0.5.0" - -micromark-factory-destination@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-1.0.0.tgz" - integrity sha512-eUBA7Rs1/xtTVun9TmV3gjfPz2wEwgK5R5xcbIM5ZYAtvGF6JkyaDsj0agx8urXnO31tEO6Ug83iVH3tdedLnw== + devlop "^1.0.0" + micromark-factory-destination "^2.0.0" + micromark-factory-label "^2.0.0" + micromark-factory-space "^2.0.0" + micromark-factory-title "^2.0.0" + micromark-factory-whitespace "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-chunked "^2.0.0" + micromark-util-classify-character "^2.0.0" + micromark-util-html-tag-name "^2.0.0" + micromark-util-normalize-identifier "^2.0.0" + micromark-util-resolve-all "^2.0.0" + micromark-util-subtokenize "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-extension-gfm-autolink-literal@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/micromark-extension-gfm-autolink-literal/-/micromark-extension-gfm-autolink-literal-2.0.0.tgz#f1e50b42e67d441528f39a67133eddde2bbabfd9" + integrity sha512-rTHfnpt/Q7dEAK1Y5ii0W8bhfJlVJFnJMHIPisfPK3gpVNuOP0VnRl96+YJ3RYWV/P4gFeQoGKNlT3RhuvpqAg== dependencies: - micromark-util-character "^1.0.0" - micromark-util-symbol "^1.0.0" - micromark-util-types "^1.0.0" + micromark-util-character "^2.0.0" + micromark-util-sanitize-uri "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" -micromark-factory-label@^1.0.0: - version "1.0.2" - resolved "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-1.0.2.tgz" - integrity sha512-CTIwxlOnU7dEshXDQ+dsr2n+yxpP0+fn271pu0bwDIS8uqfFcumXpj5mLn3hSC8iw2MUr6Gx8EcKng1dD7i6hg== +micromark-extension-gfm-footnote@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/micromark-extension-gfm-footnote/-/micromark-extension-gfm-footnote-2.0.0.tgz#91afad310065a94b636ab1e9dab2c60d1aab953c" + integrity sha512-6Rzu0CYRKDv3BfLAUnZsSlzx3ak6HAoI85KTiijuKIz5UxZxbUI+pD6oHgw+6UtQuiRwnGRhzMmPRv4smcz0fg== + dependencies: + devlop "^1.0.0" + micromark-core-commonmark "^2.0.0" + micromark-factory-space "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-normalize-identifier "^2.0.0" + micromark-util-sanitize-uri "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-extension-gfm-strikethrough@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/micromark-extension-gfm-strikethrough/-/micromark-extension-gfm-strikethrough-2.0.0.tgz#6917db8e320da70e39ffbf97abdbff83e6783e61" + integrity sha512-c3BR1ClMp5fxxmwP6AoOY2fXO9U8uFMKs4ADD66ahLTNcwzSCyRVU4k7LPV5Nxo/VJiR4TdzxRQY2v3qIUceCw== dependencies: - micromark-util-character "^1.0.0" - micromark-util-symbol "^1.0.0" - micromark-util-types "^1.0.0" - uvu "^0.5.0" + devlop "^1.0.0" + micromark-util-chunked "^2.0.0" + micromark-util-classify-character "^2.0.0" + micromark-util-resolve-all "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" -micromark-factory-space@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-1.0.0.tgz" - integrity sha512-qUmqs4kj9a5yBnk3JMLyjtWYN6Mzfcx8uJfi5XAveBniDevmZasdGBba5b4QsvRcAkmvGo5ACmSUmyGiKTLZew== +micromark-extension-gfm-table@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/micromark-extension-gfm-table/-/micromark-extension-gfm-table-2.0.0.tgz#2cf3fe352d9e089b7ef5fff003bdfe0da29649b7" + integrity sha512-PoHlhypg1ItIucOaHmKE8fbin3vTLpDOUg8KAr8gRCF1MOZI9Nquq2i/44wFvviM4WuxJzc3demT8Y3dkfvYrw== dependencies: - micromark-util-character "^1.0.0" - micromark-util-types "^1.0.0" + devlop "^1.0.0" + micromark-factory-space "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" -micromark-factory-title@^1.0.0: - version "1.0.2" - resolved "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-1.0.2.tgz" - integrity sha512-zily+Nr4yFqgMGRKLpTVsNl5L4PMu485fGFDOQJQBl2NFpjGte1e86zC0da93wf97jrc4+2G2GQudFMHn3IX+A== +micromark-extension-gfm-tagfilter@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/micromark-extension-gfm-tagfilter/-/micromark-extension-gfm-tagfilter-2.0.0.tgz#f26d8a7807b5985fba13cf61465b58ca5ff7dc57" + integrity sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg== dependencies: - micromark-factory-space "^1.0.0" - micromark-util-character "^1.0.0" - micromark-util-symbol "^1.0.0" - micromark-util-types "^1.0.0" - uvu "^0.5.0" + micromark-util-types "^2.0.0" -micromark-factory-whitespace@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-1.0.0.tgz" - integrity sha512-Qx7uEyahU1lt1RnsECBiuEbfr9INjQTGa6Err+gF3g0Tx4YEviPbqqGKNv/NrBaE7dVHdn1bVZKM/n5I/Bak7A== +micromark-extension-gfm-task-list-item@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-extension-gfm-task-list-item/-/micromark-extension-gfm-task-list-item-2.0.1.tgz#ee8b208f1ced1eb9fb11c19a23666e59d86d4838" + integrity sha512-cY5PzGcnULaN5O7T+cOzfMoHjBW7j+T9D2sucA5d/KbsBTPcYdebm9zUd9zzdgJGCwahV+/W78Z3nbulBYVbTw== dependencies: - micromark-factory-space "^1.0.0" - micromark-util-character "^1.0.0" - micromark-util-symbol "^1.0.0" - micromark-util-types "^1.0.0" + devlop "^1.0.0" + micromark-factory-space "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" -micromark-util-character@^1.0.0: - version "1.1.0" - resolved "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-1.1.0.tgz" - integrity sha512-agJ5B3unGNJ9rJvADMJ5ZiYjBRyDpzKAOk01Kpi1TKhlT1APx3XZk6eN7RtSz1erbWHC2L8T3xLZ81wdtGRZzg== +micromark-extension-gfm@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/micromark-extension-gfm/-/micromark-extension-gfm-3.0.0.tgz#3e13376ab95dd7a5cfd0e29560dfe999657b3c5b" + integrity sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w== + dependencies: + micromark-extension-gfm-autolink-literal "^2.0.0" + micromark-extension-gfm-footnote "^2.0.0" + micromark-extension-gfm-strikethrough "^2.0.0" + micromark-extension-gfm-table "^2.0.0" + micromark-extension-gfm-tagfilter "^2.0.0" + micromark-extension-gfm-task-list-item "^2.0.0" + micromark-util-combine-extensions "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-factory-destination@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/micromark-factory-destination/-/micromark-factory-destination-2.0.0.tgz#857c94debd2c873cba34e0445ab26b74f6a6ec07" + integrity sha512-j9DGrQLm/Uhl2tCzcbLhy5kXsgkHUrjJHg4fFAeoMRwJmJerT9aw4FEhIbZStWN8A3qMwOp1uzHr4UL8AInxtA== dependencies: - micromark-util-symbol "^1.0.0" - micromark-util-types "^1.0.0" + micromark-util-character "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" -micromark-util-chunked@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-1.0.0.tgz" - integrity sha512-5e8xTis5tEZKgesfbQMKRCyzvffRRUX+lK/y+DvsMFdabAicPkkZV6gO+FEWi9RfuKKoxxPwNL+dFF0SMImc1g== +micromark-factory-label@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/micromark-factory-label/-/micromark-factory-label-2.0.0.tgz#17c5c2e66ce39ad6f4fc4cbf40d972f9096f726a" + integrity sha512-RR3i96ohZGde//4WSe/dJsxOX6vxIg9TimLAS3i4EhBAFx8Sm5SmqVfR8E87DPSR31nEAjZfbt91OMZWcNgdZw== dependencies: - micromark-util-symbol "^1.0.0" + devlop "^1.0.0" + micromark-util-character "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" -micromark-util-classify-character@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-1.0.0.tgz" - integrity sha512-F8oW2KKrQRb3vS5ud5HIqBVkCqQi224Nm55o5wYLzY/9PwHGXC01tr3d7+TqHHz6zrKQ72Okwtvm/xQm6OVNZA== +micromark-factory-space@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/micromark-factory-space/-/micromark-factory-space-2.0.0.tgz#5e7afd5929c23b96566d0e1ae018ae4fcf81d030" + integrity sha512-TKr+LIDX2pkBJXFLzpyPyljzYK3MtmllMUMODTQJIUfDGncESaqB90db9IAUcz4AZAJFdd8U9zOp9ty1458rxg== dependencies: - micromark-util-character "^1.0.0" - micromark-util-symbol "^1.0.0" - micromark-util-types "^1.0.0" + micromark-util-character "^2.0.0" + micromark-util-types "^2.0.0" -micromark-util-combine-extensions@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-1.0.0.tgz" - integrity sha512-J8H058vFBdo/6+AsjHp2NF7AJ02SZtWaVUjsayNFeAiydTxUwViQPxN0Hf8dp4FmCQi0UUFovFsEyRSUmFH3MA== +micromark-factory-title@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/micromark-factory-title/-/micromark-factory-title-2.0.0.tgz#726140fc77892af524705d689e1cf06c8a83ea95" + integrity sha512-jY8CSxmpWLOxS+t8W+FG3Xigc0RDQA9bKMY/EwILvsesiRniiVMejYTE4wumNc2f4UbAa4WsHqe3J1QS1sli+A== dependencies: - micromark-util-chunked "^1.0.0" - micromark-util-types "^1.0.0" + micromark-factory-space "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" -micromark-util-decode-numeric-character-reference@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-1.0.0.tgz" - integrity sha512-OzO9AI5VUtrTD7KSdagf4MWgHMtET17Ua1fIpXTpuhclCqD8egFWo85GxSGvxgkGS74bEahvtM0WP0HjvV0e4w== +micromark-factory-whitespace@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.0.tgz#9e92eb0f5468083381f923d9653632b3cfb5f763" + integrity sha512-28kbwaBjc5yAI1XadbdPYHX/eDnqaUFVikLwrO7FDnKG7lpgxnvk/XGRhX/PN0mOZ+dBSZ+LgunHS+6tYQAzhA== dependencies: - micromark-util-symbol "^1.0.0" + micromark-factory-space "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" -micromark-util-decode-string@^1.0.0: - version "1.0.2" - resolved "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-1.0.2.tgz" - integrity sha512-DLT5Ho02qr6QWVNYbRZ3RYOSSWWFuH3tJexd3dgN1odEuPNxCngTCXJum7+ViRAd9BbdxCvMToPOD/IvVhzG6Q== +micromark-util-character@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/micromark-util-character/-/micromark-util-character-2.1.0.tgz#31320ace16b4644316f6bf057531689c71e2aee1" + integrity sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ== + dependencies: + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-util-chunked@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/micromark-util-chunked/-/micromark-util-chunked-2.0.0.tgz#e51f4db85fb203a79dbfef23fd41b2f03dc2ef89" + integrity sha512-anK8SWmNphkXdaKgz5hJvGa7l00qmcaUQoMYsBwDlSKFKjc6gjGXPDw3FNL3Nbwq5L8gE+RCbGqTw49FK5Qyvg== + dependencies: + micromark-util-symbol "^2.0.0" + +micromark-util-classify-character@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/micromark-util-classify-character/-/micromark-util-classify-character-2.0.0.tgz#8c7537c20d0750b12df31f86e976d1d951165f34" + integrity sha512-S0ze2R9GH+fu41FA7pbSqNWObo/kzwf8rN/+IGlW/4tC6oACOs8B++bh+i9bVyNnwCcuksbFwsBme5OCKXCwIw== + dependencies: + micromark-util-character "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-util-combine-extensions@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.0.tgz#75d6ab65c58b7403616db8d6b31315013bfb7ee5" + integrity sha512-vZZio48k7ON0fVS3CUgFatWHoKbbLTK/rT7pzpJ4Bjp5JjkZeasRfrS9wsBdDJK2cJLHMckXZdzPSSr1B8a4oQ== + dependencies: + micromark-util-chunked "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-util-decode-numeric-character-reference@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.1.tgz#2698bbb38f2a9ba6310e359f99fcb2b35a0d2bd5" + integrity sha512-bmkNc7z8Wn6kgjZmVHOX3SowGmVdhYS7yBpMnuMnPzDq/6xwVA604DuOXMZTO1lvq01g+Adfa0pE2UKGlxL1XQ== + dependencies: + micromark-util-symbol "^2.0.0" + +micromark-util-decode-string@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/micromark-util-decode-string/-/micromark-util-decode-string-2.0.0.tgz#7dfa3a63c45aecaa17824e656bcdb01f9737154a" + integrity sha512-r4Sc6leeUTn3P6gk20aFMj2ntPwn6qpDZqWvYmAG6NgvFTIlj4WtrAudLi65qYoaGdXYViXYw2pkmn7QnIFasA== dependencies: decode-named-character-reference "^1.0.0" - micromark-util-character "^1.0.0" - micromark-util-decode-numeric-character-reference "^1.0.0" - micromark-util-symbol "^1.0.0" + micromark-util-character "^2.0.0" + micromark-util-decode-numeric-character-reference "^2.0.0" + micromark-util-symbol "^2.0.0" -micromark-util-encode@^1.0.0: - version "1.0.1" - resolved "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-1.0.1.tgz" - integrity sha512-U2s5YdnAYexjKDel31SVMPbfi+eF8y1U4pfiRW/Y8EFVCy/vgxk/2wWTxzcqE71LHtCuCzlBDRU2a5CQ5j+mQA== +micromark-util-encode@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/micromark-util-encode/-/micromark-util-encode-2.0.0.tgz#0921ac7953dc3f1fd281e3d1932decfdb9382ab1" + integrity sha512-pS+ROfCXAGLWCOc8egcBvT0kf27GoWMqtdarNfDcjb6YLuV5cM3ioG45Ys2qOVqeqSbjaKg72vU+Wby3eddPsA== -micromark-util-html-tag-name@^1.0.0: - version "1.1.0" - resolved "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-1.1.0.tgz" - integrity sha512-BKlClMmYROy9UiV03SwNmckkjn8QHVaWkqoAqzivabvdGcwNGMMMH/5szAnywmsTBUzDsU57/mFi0sp4BQO6dA== +micromark-util-html-tag-name@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.0.tgz#ae34b01cbe063363847670284c6255bb12138ec4" + integrity sha512-xNn4Pqkj2puRhKdKTm8t1YHC/BAjx6CEwRFXntTaRf/x16aqka6ouVoutm+QdkISTlT7e2zU7U4ZdlDLJd2Mcw== -micromark-util-normalize-identifier@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-1.0.0.tgz" - integrity sha512-yg+zrL14bBTFrQ7n35CmByWUTFsgst5JhA4gJYoty4Dqzj4Z4Fr/DHekSS5aLfH9bdlfnSvKAWsAgJhIbogyBg== +micromark-util-normalize-identifier@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.0.tgz#91f9a4e65fe66cc80c53b35b0254ad67aa431d8b" + integrity sha512-2xhYT0sfo85FMrUPtHcPo2rrp1lwbDEEzpx7jiH2xXJLqBuy4H0GgXk5ToU8IEwoROtXuL8ND0ttVa4rNqYK3w== dependencies: - micromark-util-symbol "^1.0.0" + micromark-util-symbol "^2.0.0" -micromark-util-resolve-all@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-1.0.0.tgz" - integrity sha512-CB/AGk98u50k42kvgaMM94wzBqozSzDDaonKU7P7jwQIuH2RU0TeBqGYJz2WY1UdihhjweivStrJ2JdkdEmcfw== +micromark-util-resolve-all@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.0.tgz#189656e7e1a53d0c86a38a652b284a252389f364" + integrity sha512-6KU6qO7DZ7GJkaCgwBNtplXCvGkJToU86ybBAUdavvgsCiG8lSSvYxr9MhwmQ+udpzywHsl4RpGJsYWG1pDOcA== dependencies: - micromark-util-types "^1.0.0" + micromark-util-types "^2.0.0" -micromark-util-sanitize-uri@^1.0.0: - version "1.0.0" - resolved "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-1.0.0.tgz" - integrity sha512-cCxvBKlmac4rxCGx6ejlIviRaMKZc0fWm5HdCHEeDWRSkn44l6NdYVRyU+0nT1XC72EQJMZV8IPHF+jTr56lAg== +micromark-util-sanitize-uri@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.0.tgz#ec8fbf0258e9e6d8f13d9e4770f9be64342673de" + integrity sha512-WhYv5UEcZrbAtlsnPuChHUAsu/iBPOVaEVsntLBIdpibO0ddy8OzavZz3iL2xVvBZOpolujSliP65Kq0/7KIYw== dependencies: - micromark-util-character "^1.0.0" - micromark-util-encode "^1.0.0" - micromark-util-symbol "^1.0.0" + micromark-util-character "^2.0.0" + micromark-util-encode "^2.0.0" + micromark-util-symbol "^2.0.0" -micromark-util-subtokenize@^1.0.0: - version "1.0.2" - resolved "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-1.0.2.tgz" - integrity sha512-d90uqCnXp/cy4G881Ub4psE57Sf8YD0pim9QdjCRNjfas2M1u6Lbt+XZK9gnHL2XFhnozZiEdCa9CNfXSfQ6xA== +micromark-util-subtokenize@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/micromark-util-subtokenize/-/micromark-util-subtokenize-2.0.0.tgz#9f412442d77e0c5789ffdf42377fa8a2bcbdf581" + integrity sha512-vc93L1t+gpR3p8jxeVdaYlbV2jTYteDje19rNSS/H5dlhxUYll5Fy6vJ2cDwP8RnsXi818yGty1ayP55y3W6fg== dependencies: - micromark-util-chunked "^1.0.0" - micromark-util-symbol "^1.0.0" - micromark-util-types "^1.0.0" - uvu "^0.5.0" + devlop "^1.0.0" + micromark-util-chunked "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" -micromark-util-symbol@^1.0.0: - version "1.0.1" - resolved "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-1.0.1.tgz" - integrity sha512-oKDEMK2u5qqAptasDAwWDXq0tG9AssVwAx3E9bBF3t/shRIGsWIRG+cGafs2p/SnDSOecnt6hZPCE2o6lHfFmQ== +micromark-util-symbol@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/micromark-util-symbol/-/micromark-util-symbol-2.0.0.tgz#12225c8f95edf8b17254e47080ce0862d5db8044" + integrity sha512-8JZt9ElZ5kyTnO94muPxIGS8oyElRJaiJO8EzV6ZSyGQ1Is8xwl4Q45qU5UOg+bGH4AikWziz0iN4sFLWs8PGw== -micromark-util-types@^1.0.0, micromark-util-types@^1.0.1: - version "1.0.2" - resolved "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-1.0.2.tgz" - integrity sha512-DCfg/T8fcrhrRKTPjRrw/5LLvdGV7BHySf/1LOZx7TzWZdYRjogNtyNq885z3nNallwr3QUKARjqvHqX1/7t+w== +micromark-util-types@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/micromark-util-types/-/micromark-util-types-2.0.0.tgz#63b4b7ffeb35d3ecf50d1ca20e68fc7caa36d95e" + integrity sha512-oNh6S2WMHWRZrmutsRmDDfkzKtxF+bc2VxLC9dvtrDIRFln627VsFP6fLMgTryGDljgLPjkrzQSDcPrjPyDJ5w== -micromark@^3.0.0: - version "3.0.10" - resolved "https://registry.npmjs.org/micromark/-/micromark-3.0.10.tgz" - integrity sha512-ryTDy6UUunOXy2HPjelppgJ2sNfcPz1pLlMdA6Rz9jPzhLikWXv/irpWV/I2jd68Uhmny7hHxAlAhk4+vWggpg== +micromark@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/micromark/-/micromark-4.0.0.tgz#84746a249ebd904d9658cfabc1e8e5f32cbc6249" + integrity sha512-o/sd0nMof8kYff+TqcDx3VSrgBTcZpSvYcAHIfHhv5VAuNmisCxjhx6YmxS8PFEpb9z5WKWKPdzf0jM23ro3RQ== dependencies: "@types/debug" "^4.0.0" debug "^4.0.0" decode-named-character-reference "^1.0.0" - micromark-core-commonmark "^1.0.1" - micromark-factory-space "^1.0.0" - micromark-util-character "^1.0.0" - micromark-util-chunked "^1.0.0" - micromark-util-combine-extensions "^1.0.0" - micromark-util-decode-numeric-character-reference "^1.0.0" - micromark-util-encode "^1.0.0" - micromark-util-normalize-identifier "^1.0.0" - micromark-util-resolve-all "^1.0.0" - micromark-util-sanitize-uri "^1.0.0" - micromark-util-subtokenize "^1.0.0" - micromark-util-symbol "^1.0.0" - micromark-util-types "^1.0.1" - uvu "^0.5.0" + devlop "^1.0.0" + micromark-core-commonmark "^2.0.0" + micromark-factory-space "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-chunked "^2.0.0" + micromark-util-combine-extensions "^2.0.0" + micromark-util-decode-numeric-character-reference "^2.0.0" + micromark-util-encode "^2.0.0" + micromark-util-normalize-identifier "^2.0.0" + micromark-util-resolve-all "^2.0.0" + micromark-util-sanitize-uri "^2.0.0" + micromark-util-subtokenize "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" micromatch@4.0.5, micromatch@^4.0.4, micromatch@^4.0.5: version "4.0.5" @@ -10210,11 +10561,6 @@ motion@10.16.2: "@motionone/utils" "^10.15.1" "@motionone/vue" "^10.16.2" -mri@^1.1.0: - version "1.2.0" - resolved "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz" - integrity sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA== - mrmime@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/mrmime/-/mrmime-1.0.1.tgz#5f90c825fad4bdd41dc914eff5d1a8cfdaf24f27" @@ -10706,6 +11052,20 @@ parent-module@^1.0.0: dependencies: callsites "^3.0.0" +parse-entities@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/parse-entities/-/parse-entities-4.0.1.tgz#4e2a01111fb1c986549b944af39eeda258fc9e4e" + integrity sha512-SWzvYcSJh4d/SGLIOQfZ/CoNv6BTlI6YEQ7Nj82oDVnRpwe/Z/F1EMx42x3JAOwGBlCjeCH0BRJQbQ/opHL17w== + dependencies: + "@types/unist" "^2.0.0" + character-entities "^2.0.0" + character-entities-legacy "^3.0.0" + character-reference-invalid "^2.0.0" + decode-named-character-reference "^1.0.0" + is-alphanumerical "^2.0.0" + is-decimal "^2.0.0" + is-hexadecimal "^2.0.0" + parse-json@^5.0.0, parse-json@^5.2.0: version "5.2.0" resolved "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz" @@ -11055,7 +11415,7 @@ prompts@2.4.2: kleur "^3.0.3" sisteransi "^1.0.5" -prop-types@^15.0.0, prop-types@^15.5.0, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1: +prop-types@^15.5.0, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1: version "15.8.1" resolved "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz" integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== @@ -11256,26 +11616,21 @@ react-lifecycles-compat@^3.0.4: resolved "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz" integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA== -react-markdown@^8.0.7: - version "8.0.7" - resolved "https://registry.yarnpkg.com/react-markdown/-/react-markdown-8.0.7.tgz#c8dbd1b9ba5f1c5e7e5f2a44de465a3caafdf89b" - integrity sha512-bvWbzG4MtOU62XqBx3Xx+zB2raaFFsq4mYiAzfjXJMEz2sixgeAfraA3tvzULF02ZdOMUOKTBFFaZJDDrq+BJQ== - dependencies: - "@types/hast" "^2.0.0" - "@types/prop-types" "^15.0.0" - "@types/unist" "^2.0.0" - comma-separated-tokens "^2.0.0" - hast-util-whitespace "^2.0.0" - prop-types "^15.0.0" - property-information "^6.0.0" - react-is "^18.0.0" - remark-parse "^10.0.0" - remark-rehype "^10.0.0" - space-separated-tokens "^2.0.0" - style-to-object "^0.4.0" - unified "^10.0.0" - unist-util-visit "^4.0.0" - vfile "^5.0.0" +react-markdown@^9.0.1: + version "9.0.1" + resolved "https://registry.yarnpkg.com/react-markdown/-/react-markdown-9.0.1.tgz#c05ddbff67fd3b3f839f8c648e6fb35d022397d1" + integrity sha512-186Gw/vF1uRkydbsOIkcGXw7aHq0sZOCRFFjGrr7b9+nVZg4UfA4enXCaxm4fUzecU38sWfrNDitGhshuU7rdg== + dependencies: + "@types/hast" "^3.0.0" + devlop "^1.0.0" + hast-util-to-jsx-runtime "^2.0.0" + html-url-attributes "^3.0.0" + mdast-util-to-hast "^13.0.0" + remark-parse "^11.0.0" + remark-rehype "^11.0.0" + unified "^11.0.0" + unist-util-visit "^5.0.0" + vfile "^6.0.0" react-redux@^8.0.5: version "8.0.5" @@ -11592,24 +11947,47 @@ regjsparser@^0.9.1: dependencies: jsesc "~0.5.0" -remark-parse@^10.0.0: - version "10.0.1" - resolved "https://registry.npmjs.org/remark-parse/-/remark-parse-10.0.1.tgz" - integrity sha512-1fUyHr2jLsVOkhbvPRBJ5zTKZZyD6yZzYaWCS6BPBdQ8vEMBCH+9zNCDA6tET/zHCi/jLqjCWtlJZUPk+DbnFw== +remark-gfm@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/remark-gfm/-/remark-gfm-4.0.0.tgz#aea777f0744701aa288b67d28c43565c7e8c35de" + integrity sha512-U92vJgBPkbw4Zfu/IiW2oTZLSL3Zpv+uI7My2eq8JxKgqraFdU8YUGicEJCEgSbeaG+QDFqIcwwfMTOEelPxuA== dependencies: - "@types/mdast" "^3.0.0" - mdast-util-from-markdown "^1.0.0" - unified "^10.0.0" + "@types/mdast" "^4.0.0" + mdast-util-gfm "^3.0.0" + micromark-extension-gfm "^3.0.0" + remark-parse "^11.0.0" + remark-stringify "^11.0.0" + unified "^11.0.0" -remark-rehype@^10.0.0: - version "10.1.0" - resolved "https://registry.npmjs.org/remark-rehype/-/remark-rehype-10.1.0.tgz" - integrity sha512-EFmR5zppdBp0WQeDVZ/b66CWJipB2q2VLNFMabzDSGR66Z2fQii83G5gTBbgGEnEEA0QRussvrFHxk1HWGJskw== +remark-parse@^11.0.0: + version "11.0.0" + resolved "https://registry.yarnpkg.com/remark-parse/-/remark-parse-11.0.0.tgz#aa60743fcb37ebf6b069204eb4da304e40db45a1" + integrity sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA== dependencies: - "@types/hast" "^2.0.0" - "@types/mdast" "^3.0.0" - mdast-util-to-hast "^12.1.0" - unified "^10.0.0" + "@types/mdast" "^4.0.0" + mdast-util-from-markdown "^2.0.0" + micromark-util-types "^2.0.0" + unified "^11.0.0" + +remark-rehype@^11.0.0: + version "11.1.0" + resolved "https://registry.yarnpkg.com/remark-rehype/-/remark-rehype-11.1.0.tgz#d5f264f42bcbd4d300f030975609d01a1697ccdc" + integrity sha512-z3tJrAs2kIs1AqIIy6pzHmAHlF1hWQ+OdY4/hv+Wxe35EhyLKcajL33iUEn3ScxtFox9nUvRufR/Zre8Q08H/g== + dependencies: + "@types/hast" "^3.0.0" + "@types/mdast" "^4.0.0" + mdast-util-to-hast "^13.0.0" + unified "^11.0.0" + vfile "^6.0.0" + +remark-stringify@^11.0.0: + version "11.0.0" + resolved "https://registry.yarnpkg.com/remark-stringify/-/remark-stringify-11.0.0.tgz#4c5b01dd711c269df1aaae11743eb7e2e7636fd3" + integrity sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw== + dependencies: + "@types/mdast" "^4.0.0" + mdast-util-to-markdown "^2.0.0" + unified "^11.0.0" remeda@^1.9.1: version "1.27.0" @@ -11805,13 +12183,6 @@ rxjs@^6.6.3: dependencies: tslib "^1.9.0" -sade@^1.7.3: - version "1.8.1" - resolved "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz" - integrity sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A== - dependencies: - mri "^1.1.0" - safe-array-concat@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/safe-array-concat/-/safe-array-concat-1.0.1.tgz#91686a63ce3adbea14d61b14c99572a8ff84754c" @@ -12346,6 +12717,14 @@ string_decoder@^1.1.1, string_decoder@^1.3.0: dependencies: safe-buffer "~5.2.0" +stringify-entities@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/stringify-entities/-/stringify-entities-4.0.3.tgz#cfabd7039d22ad30f3cc435b0ca2c1574fc88ef8" + integrity sha512-BP9nNHMhhfcMbiuQKCqMjhDP5yBCAxsPu4pHFFzJ6Alo9dZgY4VLDPutXqIjpRiMoKdp7Av85Gr73Q5uH9k7+g== + dependencies: + character-entities-html4 "^2.0.0" + character-entities-legacy "^3.0.0" + strip-ansi@6.0.1, strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" @@ -12392,12 +12771,12 @@ strnum@^1.0.5: resolved "https://registry.yarnpkg.com/strnum/-/strnum-1.0.5.tgz#5c4e829fe15ad4ff0d20c3db5ac97b73c9b072db" integrity sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA== -style-to-object@^0.4.0: - version "0.4.1" - resolved "https://registry.yarnpkg.com/style-to-object/-/style-to-object-0.4.1.tgz#53cf856f7cf7f172d72939d9679556469ba5de37" - integrity sha512-HFpbb5gr2ypci7Qw+IOhnP2zOU7e77b+rzM+wTzXzfi1PrtBCX0E7Pk4wL4iTLnhzZ+JgEGAhX81ebTg/aYjQw== +style-to-object@^1.0.0: + version "1.0.5" + resolved "https://registry.yarnpkg.com/style-to-object/-/style-to-object-1.0.5.tgz#5e918349bc3a39eee3a804497d97fcbbf2f0d7c0" + integrity sha512-rDRwHtoDD3UMMrmZ6BzOW0naTjMsVZLIjsGleSKS/0Oz+cgCfAPRspaqJuE8rDzpKha/nEvnM0IF4seEAZUTKQ== dependencies: - inline-style-parser "0.1.1" + inline-style-parser "0.2.2" styled-components@^6.0.7: version "6.0.8" @@ -12804,6 +13183,11 @@ tslib@1.14.1, tslib@^1.14.1, tslib@^1.8.1, tslib@^1.9.0: resolved "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== +tslib@2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3" + integrity sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ== + tslib@2.6.0, tslib@^2.0.0, tslib@^2.0.1, tslib@^2.1.0: version "2.6.0" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.0.tgz#b295854684dbda164e181d259a22cd779dcd7bc3" @@ -13030,66 +13414,64 @@ unicode-property-aliases-ecmascript@^2.0.0: resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz#43d41e3be698bd493ef911077c9b131f827e8ccd" integrity sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w== -unified@^10.0.0: - version "10.1.2" - resolved "https://registry.npmjs.org/unified/-/unified-10.1.2.tgz" - integrity sha512-pUSWAi/RAnVy1Pif2kAoeWNBa3JVrx0MId2LASj8G+7AiHWoKZNTomq6LG326T68U7/e263X6fTdcXIy7XnF7Q== +unified@^11.0.0: + version "11.0.4" + resolved "https://registry.yarnpkg.com/unified/-/unified-11.0.4.tgz#f4be0ac0fe4c88cb873687c07c64c49ed5969015" + integrity sha512-apMPnyLjAX+ty4OrNap7yumyVAMlKx5IWU2wlzzUdYJO9A8f1p9m/gywF/GM2ZDFcjQPrx59Mc90KwmxsoklxQ== dependencies: - "@types/unist" "^2.0.0" + "@types/unist" "^3.0.0" bail "^2.0.0" + devlop "^1.0.0" extend "^3.0.0" - is-buffer "^2.0.0" is-plain-obj "^4.0.0" trough "^2.0.0" - vfile "^5.0.0" + vfile "^6.0.0" -unist-builder@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/unist-builder/-/unist-builder-3.0.0.tgz" - integrity sha512-GFxmfEAa0vi9i5sd0R2kcrI9ks0r82NasRq5QHh2ysGngrc6GiqD5CDf1FjPenY4vApmFASBIIlk/jj5J5YbmQ== +unist-util-is@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-6.0.0.tgz#b775956486aff107a9ded971d996c173374be424" + integrity sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw== dependencies: - "@types/unist" "^2.0.0" + "@types/unist" "^3.0.0" -unist-util-generated@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/unist-util-generated/-/unist-util-generated-2.0.0.tgz" - integrity sha512-TiWE6DVtVe7Ye2QxOVW9kqybs6cZexNwTwSMVgkfjEReqy/xwGpAXb99OxktoWwmL+Z+Epb0Dn8/GNDYP1wnUw== - -unist-util-is@^5.0.0: - version "5.1.1" - resolved "https://registry.npmjs.org/unist-util-is/-/unist-util-is-5.1.1.tgz" - integrity sha512-F5CZ68eYzuSvJjGhCLPL3cYx45IxkqXSetCcRgUXtbcm50X2L9oOWQlfUfDdAf+6Pd27YDblBfdtmsThXmwpbQ== +unist-util-position@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/unist-util-position/-/unist-util-position-5.0.0.tgz#678f20ab5ca1207a97d7ea8a388373c9cf896be4" + integrity sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA== + dependencies: + "@types/unist" "^3.0.0" -unist-util-position@^4.0.0: - version "4.0.3" - resolved "https://registry.npmjs.org/unist-util-position/-/unist-util-position-4.0.3.tgz" - integrity sha512-p/5EMGIa1qwbXjA+QgcBXaPWjSnZfQ2Sc3yBEEfgPwsEmJd8Qh+DSk3LGnmOM4S1bY2C0AjmMnB8RuEYxpPwXQ== +unist-util-remove-position@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/unist-util-remove-position/-/unist-util-remove-position-5.0.0.tgz#fea68a25658409c9460408bc6b4991b965b52163" + integrity sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q== dependencies: - "@types/unist" "^2.0.0" + "@types/unist" "^3.0.0" + unist-util-visit "^5.0.0" -unist-util-stringify-position@^3.0.0: - version "3.0.2" - resolved "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-3.0.2.tgz" - integrity sha512-7A6eiDCs9UtjcwZOcCpM4aPII3bAAGv13E96IkawkOAW0OhH+yRxtY0lzo8KiHpzEMfH7Q+FizUmwp8Iqy5EWg== +unist-util-stringify-position@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz#449c6e21a880e0855bf5aabadeb3a740314abac2" + integrity sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ== dependencies: - "@types/unist" "^2.0.0" + "@types/unist" "^3.0.0" -unist-util-visit-parents@^5.0.0: - version "5.1.0" - resolved "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-5.1.0.tgz" - integrity sha512-y+QVLcY5eR/YVpqDsLf/xh9R3Q2Y4HxkZTp7ViLDU6WtJCEcPmRzW1gpdWDCDIqIlhuPDXOgttqPlykrHYDekg== +unist-util-visit-parents@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz#4d5f85755c3b8f0dc69e21eca5d6d82d22162815" + integrity sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw== dependencies: - "@types/unist" "^2.0.0" - unist-util-is "^5.0.0" + "@types/unist" "^3.0.0" + unist-util-is "^6.0.0" -unist-util-visit@^4.0.0: - version "4.1.0" - resolved "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-4.1.0.tgz" - integrity sha512-n7lyhFKJfVZ9MnKtqbsqkQEk5P1KShj0+//V7mAcoI6bpbUjh3C/OG8HVD+pBihfh6Ovl01m8dkcv9HNqYajmQ== +unist-util-visit@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-5.0.0.tgz#a7de1f31f72ffd3519ea71814cccf5fd6a9217d6" + integrity sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg== dependencies: - "@types/unist" "^2.0.0" - unist-util-is "^5.0.0" - unist-util-visit-parents "^5.0.0" + "@types/unist" "^3.0.0" + unist-util-is "^6.0.0" + unist-util-visit-parents "^6.0.0" universalify@^0.1.0: version "0.1.2" @@ -13242,16 +13624,6 @@ uuid@8.3.2, uuid@^8.3.2: resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== -uvu@^0.5.0: - version "0.5.6" - resolved "https://registry.npmjs.org/uvu/-/uvu-0.5.6.tgz" - integrity sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA== - dependencies: - dequal "^2.0.0" - diff "^5.0.0" - kleur "^4.0.3" - sade "^1.7.3" - v8-compile-cache-lib@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" @@ -13289,23 +13661,22 @@ value-or-promise@^1.0.12: resolved "https://registry.yarnpkg.com/value-or-promise/-/value-or-promise-1.0.12.tgz#0e5abfeec70148c78460a849f6b003ea7986f15c" integrity sha512-Z6Uz+TYwEqE7ZN50gwn+1LCVo9ZVrpxRPOhOLnncYkY1ZzOYtrX8Fwf/rFktZ8R5mJms6EZf5TqNOMeZmnPq9Q== -vfile-message@^3.0.0: - version "3.1.2" - resolved "https://registry.npmjs.org/vfile-message/-/vfile-message-3.1.2.tgz" - integrity sha512-QjSNP6Yxzyycd4SVOtmKKyTsSvClqBPJcd00Z0zuPj3hOIjg0rUPG6DbFGPvUKRgYyaIWLPKpuEclcuvb3H8qA== +vfile-message@^4.0.0: + version "4.0.2" + resolved "https://registry.yarnpkg.com/vfile-message/-/vfile-message-4.0.2.tgz#c883c9f677c72c166362fd635f21fc165a7d1181" + integrity sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw== dependencies: - "@types/unist" "^2.0.0" - unist-util-stringify-position "^3.0.0" + "@types/unist" "^3.0.0" + unist-util-stringify-position "^4.0.0" -vfile@^5.0.0: - version "5.3.4" - resolved "https://registry.npmjs.org/vfile/-/vfile-5.3.4.tgz" - integrity sha512-KI+7cnst03KbEyN1+JE504zF5bJBZa+J+CrevLeyIMq0aPU681I2rQ5p4PlnQ6exFtWiUrg26QUdFMnAKR6PIw== +vfile@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/vfile/-/vfile-6.0.1.tgz#1e8327f41eac91947d4fe9d237a2dd9209762536" + integrity sha512-1bYqc7pt6NIADBJ98UiG0Bn/CHIVOoZ/IyEkqIruLg0mE1BKzkOXY2D6CSqQIcKqgadppE5lrxgWXJmXd7zZJw== dependencies: - "@types/unist" "^2.0.0" - is-buffer "^2.0.0" - unist-util-stringify-position "^3.0.0" - vfile-message "^3.0.0" + "@types/unist" "^3.0.0" + unist-util-stringify-position "^4.0.0" + vfile-message "^4.0.0" victory-vendor@^36.6.8: version "36.6.8" @@ -13658,6 +14029,11 @@ ws@7.4.6: resolved "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz" integrity sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A== +ws@8.5.0: + version "8.5.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.5.0.tgz#bfb4be96600757fe5382de12c670dab984a1ed4f" + integrity sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg== + ws@^7.4.2, ws@^7.4.5, ws@^7.5.1: version "7.5.9" resolved "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz" @@ -13815,3 +14191,8 @@ zustand@^4.3.1: integrity sha512-dY8ERwB9Nd21ellgkBZFhudER8KVlelZm8388B5nDAXhO/+FZDhYMuRnqDgu5SYyRgz/iaf8RKnbUs/cHfOGlQ== dependencies: use-sync-external-store "1.2.0" + +zwitch@^2.0.0: + version "2.0.4" + resolved "https://registry.yarnpkg.com/zwitch/-/zwitch-2.0.4.tgz#c827d4b0acb76fc3e685a4c6ec2902d51070e9d7" + integrity sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A== From 1d633bcf23997960ccccda2e7354ba66ff665fc0 Mon Sep 17 00:00:00 2001 From: brightiron Date: Sun, 31 Mar 2024 13:19:31 -0500 Subject: [PATCH 02/12] rpc --- src/helpers/environment/Environment/Environment.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/helpers/environment/Environment/Environment.ts b/src/helpers/environment/Environment/Environment.ts index 7637567b84..58ec48c469 100644 --- a/src/helpers/environment/Environment/Environment.ts +++ b/src/helpers/environment/Environment/Environment.ts @@ -57,10 +57,7 @@ export class Environment { public static getNodeUrls = (networkId: NetworkId) => { switch (networkId) { case NetworkId.MAINNET: - return this._get({ - key: `VITE_ETHEREUM_NODE_URL`, - fallback: "https://rpc.ankr.com/eth", - }); + return ["https://rpc.tenderly.co/fork/f7571dd4-342e-457a-a83b-670b6a84e4c4"]; //TODO: revert this after testing. this is like this so we dont have to mess w/ fleek environments for fork testing case NetworkId.TESTNET_GOERLI: return this._get({ key: `VITE_ETHEREUM_TESTNET_NODE_URL`, From 438cc9641856c89129838c8fe3a8f267c07b75c0 Mon Sep 17 00:00:00 2001 From: brightiron Date: Tue, 16 Apr 2024 21:42:30 -0500 Subject: [PATCH 03/12] more feedback tweaks --- src/components/WalletConnectedGuard.tsx | 9 +- src/views/Governance/Components/CallData.tsx | 6 + .../Governance/Components/CurrentVotes.tsx | 102 +++++-- .../Components/GovernanceTableRow.tsx | 76 ++--- .../Components/ProposalContainer.tsx | 144 +++++++--- src/views/Governance/Components/Status.tsx | 2 +- src/views/Governance/Components/VoteModal.tsx | 29 +- .../Components/VotingOutcomeBar.tsx | 82 ++++-- .../Delegation/DelegateVotingModal.tsx | 4 +- .../Delegation/DelegationMessage.tsx | 7 +- src/views/Governance/Delegation/index.tsx | 123 ++++---- src/views/Governance/Proposals/index.tsx | 268 +++++++++--------- src/views/Governance/helpers/index.ts | 1 - .../Governance/hooks/useDelegateVoting.tsx | 1 + .../hooks/useGetContractParameters.tsx | 2 + .../Governance/hooks/useGetVotingWeight.tsx | 4 +- src/views/Governance/index.tsx | 143 ++++++++-- src/views/MyBalances/index.tsx | 2 + 18 files changed, 608 insertions(+), 397 deletions(-) diff --git a/src/components/WalletConnectedGuard.tsx b/src/components/WalletConnectedGuard.tsx index d4c7f71652..8e27907b9d 100644 --- a/src/components/WalletConnectedGuard.tsx +++ b/src/components/WalletConnectedGuard.tsx @@ -2,12 +2,17 @@ import { Box, Typography } from "@mui/material"; import { InPageConnectButton } from "src/components/ConnectButton/ConnectButton"; import { useAccount } from "wagmi"; -export const WalletConnectedGuard: React.FC<{ message?: string; fullWidth?: boolean; children: any }> = props => { +export const WalletConnectedGuard: React.FC<{ + message?: string; + fullWidth?: boolean; + children: any; + buttonText?: string; +}> = props => { const { isConnected } = useAccount(); if (!isConnected) return ( <> - + {props.message && ( diff --git a/src/views/Governance/Components/CallData.tsx b/src/views/Governance/Components/CallData.tsx index 661ca6c1e3..9ba55ecb0b 100644 --- a/src/views/Governance/Components/CallData.tsx +++ b/src/views/Governance/Components/CallData.tsx @@ -13,11 +13,13 @@ export const CallData = ({ target, value, index, + signature, }: { calldata: string; target: string; value: string; index: number; + signature: string; }) => { const [isLoading, setIsLoading] = useState(false); const [fnDescription, setFnDescription] = useState(); @@ -192,6 +194,10 @@ export const CallData = ({ )} + + Signature: + + {signature} Target: diff --git a/src/views/Governance/Components/CurrentVotes.tsx b/src/views/Governance/Components/CurrentVotes.tsx index cfbe9d1aef..3ee0e110b4 100644 --- a/src/views/Governance/Components/CurrentVotes.tsx +++ b/src/views/Governance/Components/CurrentVotes.tsx @@ -1,14 +1,20 @@ -import { CheckCircle } from "@mui/icons-material"; -import { Box, Typography, useTheme } from "@mui/material"; -import { Paper } from "@olympusdao/component-library"; +import { CheckCircle, HighlightOffOutlined } from "@mui/icons-material"; +import { Box, Tooltip, Typography, useTheme } from "@mui/material"; +import { Paper, PrimaryButton } from "@olympusdao/component-library"; +import { WalletConnectedGuard } from "src/components/WalletConnectedGuard"; import { abbreviatedNumber } from "src/helpers"; import { VotingOutcomeBar } from "src/views/Governance/Components/VotingOutcomeBar"; import { useGetContractParameters } from "src/views/Governance/hooks/useGetContractParameters"; import { useGetProposalDetails } from "src/views/Governance/hooks/useGetProposalDetails"; +import { useGetReceipt } from "src/views/Governance/hooks/useGetReceipt"; -export const CurrentVotes = ({ proposalId }: { proposalId: number }) => { +export const CurrentVotes = ({ proposalId, onVoteClick }: { proposalId: number; onVoteClick: () => void }) => { const { data: proposalDetails } = useGetProposalDetails({ proposalId }); const { data: parameters } = useGetContractParameters(); + const { data: getReceipt } = useGetReceipt({ proposalId }); + const hasVoted = getReceipt?.hasVoted; + const support = getReceipt?.support === 0 ? "Against" : getReceipt?.support === 1 ? "For" : "Abstain"; + const theme = useTheme(); const approvalThresholdDenominator = @@ -19,38 +25,72 @@ export const CurrentVotes = ({ proposalId }: { proposalId: number }) => { proposalDetails && approvalThresholdDenominator && proposalDetails.forCount > approvalThresholdDenominator, ); const aboveQuorum = Boolean(proposalDetails && proposalDetails.forCount > proposalDetails.quorumVotes); + + const approvalPercentage = proposalDetails + ? (proposalDetails.forCount / (proposalDetails.forCount + proposalDetails.againstCount)) * 100 + : 0; + + const totalSupply = + proposalDetails?.quorumVotes && parameters?.proposalQuorumPercent + ? proposalDetails?.quorumVotes / (parameters?.proposalQuorumPercent / 100) + : 0; + const quorumPercentage = (proposalDetails && (proposalDetails?.forCount / totalSupply) * 100) || 0; + + const totalVotes = + (proposalDetails && proposalDetails?.forCount + proposalDetails?.againstCount + proposalDetails?.abstainCount) || 0; + return ( - Current Votes + Progress - + - {aboveThreshold && } - Approval Threshold + {aboveThreshold ? ( + + ) : ( + + )} + Approval - - {abbreviatedNumber.format(proposalDetails?.forCount || 0)} of{" "} - {abbreviatedNumber.format(approvalThresholdDenominator || 0)} - + {parameters?.proposalThresholdPercent && proposalDetails?.forCount && proposalDetails.quorumVotes && ( + + + + + + )} - + - {aboveQuorum && } + {aboveQuorum ? ( + + ) : ( + + )} Quorum - - - {abbreviatedNumber.format(proposalDetails?.forCount || 0)} of{" "} - {abbreviatedNumber.format(proposalDetails?.quorumVotes || 0)} - + {parameters?.proposalQuorumPercent && proposalDetails?.forCount && proposalDetails.quorumVotes && ( + + + + + + )} - @@ -68,6 +108,20 @@ export const CurrentVotes = ({ proposalId }: { proposalId: number }) => { Abstain {abbreviatedNumber.format(proposalDetails?.abstainCount || 0)} + + Total Votes + {abbreviatedNumber.format(totalVotes || 0)} + + + + + {hasVoted ? `Already Voted (${support})` : "Vote"} + + ); diff --git a/src/views/Governance/Components/GovernanceTableRow.tsx b/src/views/Governance/Components/GovernanceTableRow.tsx index d56596784f..d299aa60e3 100644 --- a/src/views/Governance/Components/GovernanceTableRow.tsx +++ b/src/views/Governance/Components/GovernanceTableRow.tsx @@ -1,5 +1,5 @@ -import { Box, TableCell, TableRow } from "@mui/material"; -import { PrimaryButton } from "@olympusdao/component-library"; +import { Box, Tooltip, Typography, useTheme } from "@mui/material"; +import { PrimaryButton, SecondaryButton } from "@olympusdao/component-library"; import { ethers } from "ethers"; import { truncateEthereumAddress } from "src/helpers/truncateAddress"; import { useDelegateVoting } from "src/views/Governance/hooks/useDelegateVoting"; @@ -10,6 +10,7 @@ export const GovernanceTableRow = ({ delegatorAddress, setDelegateVoting, balance, + address, }: { tokenName: string; delegationAddress?: string; @@ -24,44 +25,51 @@ export const GovernanceTableRow = ({ > >; balance?: string; + address: string; }) => { const delegateVoting = useDelegateVoting(); + const theme = useTheme(); return ( - - + + {tokenName} - - + {" "} + + + <>{truncateEthereumAddress(address, 9)} + + + + Balance: {Number(balance || 0).toFixed(2)} gOHM - - - {delegationAddress ? `Delegated to ${truncateEthereumAddress(delegationAddress)} ` : "Undelegated"} - - - - - - setDelegateVoting({ currentDelegatedToAddress: delegationAddress, delegatorAddress })} - > - Delegate - - - {delegationAddress && ( - { - delegateVoting.mutate({ address: delegatorAddress, delegationAddress: ethers.constants.AddressZero }); - }} - loading={delegateVoting.isLoading && !delegationAddress} - > - Revoke - - )} + + + + Status: + {delegationAddress ? "Delegated" : "Undelegated"} + + + + + setDelegateVoting({ currentDelegatedToAddress: delegationAddress, delegatorAddress })} + > + Delegate + - - + {delegationAddress && ( + { + delegateVoting.mutate({ address: delegatorAddress, delegationAddress: ethers.constants.AddressZero }); + }} + loading={delegateVoting.isLoading && !delegationAddress} + > + Revoke + + )} +
+
); }; diff --git a/src/views/Governance/Components/ProposalContainer.tsx b/src/views/Governance/Components/ProposalContainer.tsx index d09838c4b8..0a84174d8a 100644 --- a/src/views/Governance/Components/ProposalContainer.tsx +++ b/src/views/Governance/Components/ProposalContainer.tsx @@ -1,20 +1,34 @@ -import { Box, LinearProgress, Link, Skeleton, TableCell, TableRow, Typography, useTheme } from "@mui/material"; +import { Box, Link, Skeleton, TableCell, TableRow, Typography, useTheme } from "@mui/material"; import { Chip } from "@olympusdao/component-library"; +import { useEffect } from "react"; import { Link as RouterLink } from "react-router-dom"; import { abbreviatedNumber } from "src/helpers"; +import { VotingOutcomeBar } from "src/views/Governance/Components/VotingOutcomeBar"; import { mapProposalStatus, toCapitalCase } from "src/views/Governance/helpers"; +import { useGetContractParameters } from "src/views/Governance/hooks/useGetContractParameters"; import { useGetProposalDetails } from "src/views/Governance/hooks/useGetProposalDetails"; export const ProposalContainer = ({ proposalId, title, createdAt, + active, + proposals, + setProposals, + setIsLoading, + proposalsCount, }: { proposalId: number; title?: string; createdAt?: Date; + active?: boolean; + proposals: number[]; + setProposals: React.Dispatch>; + setIsLoading: React.Dispatch>; + proposalsCount: number; }) => { const { data: proposal, isLoading } = useGetProposalDetails({ proposalId }); + const { data: parameters } = useGetContractParameters(); const formattedTitle = title?.split(/#+\s|\n/g)[1]; @@ -22,15 +36,25 @@ export const ProposalContainer = ({ month: "short", day: "numeric", year: "numeric", - timeZoneName: "short", - hour: "numeric", - minute: "numeric", }); const formattedPublishedDate = createdAt && dateFormat.format(createdAt); const theme = useTheme(); - if (isLoading || !proposal) { + useEffect(() => { + if (proposal) { + if ( + (active && (proposal.status === "Active" || proposal.status === "Pending")) || + (!active && proposal.status !== "Active" && proposal.status !== "Pending") + ) { + setProposals(prev => [...prev, proposalId]); + } + } + if (proposals.length === proposalId) { + setIsLoading(false); + } + }, [proposal]); + if (isLoading || !proposal || !parameters) { return ( @@ -48,46 +72,76 @@ export const ProposalContainer = ({ ); } + const totalVotes = proposal.forCount + proposal.abstainCount + proposal.againstCount; - return ( - - - - - {formattedTitle} - - - - - {formattedPublishedDate} - - - - - - - {abbreviatedNumber.format(proposal.forCount)} - - 0 ? (proposal.forCount / totalVotes) * 100 : 0} - color="success" - /> - - - - {abbreviatedNumber.format(proposal.againstCount)} - + const approvalPercentage = (proposal.forCount / (proposal.forCount + proposal.againstCount)) * 100; + const aboveQuorum = Boolean(proposal.forCount > proposal.quorumVotes); + + const totalSupply = + proposal?.quorumVotes && parameters?.proposalQuorumPercent + ? proposal?.quorumVotes / (parameters?.proposalQuorumPercent / 100) + : 0; + const quorumPercentage = (proposal.forCount / totalSupply) * 100; + const approvalThresholdDenominator = + (proposal.forCount + proposal.againstCount) * (parameters?.proposalApprovalThreshold / 100); + const aboveThreshold = Boolean(approvalThresholdDenominator && proposal.forCount > approvalThresholdDenominator); - 0 ? (proposal.againstCount / totalVotes) * 100 : 0} - color="error" - /> - - - {abbreviatedNumber.format(totalVotes)} - - - ); + if ( + (active && (proposal.status === "Active" || proposal.status === "Pending")) || + (!active && proposal.status !== "Active" && proposal.status !== "Pending") + ) { + return ( + + + + + {formattedTitle} + + + + + {formattedPublishedDate} + + + + + + + {approvalPercentage.toFixed(0)}% + + + + + + + + {quorumPercentage.toFixed(0)}% + + + + + + + {abbreviatedNumber.format(totalVotes)} + + + ); + } + return <>; }; diff --git a/src/views/Governance/Components/Status.tsx b/src/views/Governance/Components/Status.tsx index 929038b29c..079d5c6774 100644 --- a/src/views/Governance/Components/Status.tsx +++ b/src/views/Governance/Components/Status.tsx @@ -22,7 +22,7 @@ export const Status = ({ proposalId }: { proposalId: number }) => { return ( - Status + Timeline
diff --git a/src/views/Governance/Components/VoteModal.tsx b/src/views/Governance/Components/VoteModal.tsx index 171f607ed9..223777cd5a 100644 --- a/src/views/Governance/Components/VoteModal.tsx +++ b/src/views/Governance/Components/VoteModal.tsx @@ -12,12 +12,10 @@ const StyledTextField = styled(TextField)(({}) => ({ export const VoteModal = ({ startBlock, - title, proposalId, onClose, }: { startBlock: number; - title: string; proposalId: number; onClose: () => void; }) => { @@ -28,9 +26,9 @@ export const VoteModal = ({ const castVote = useVoteForProposal(); return ( <> - + - Voting Power + Your Voting Power - {votingWeight} + {Number(votingWeight).toFixed(2)} gOHM - - {title} - - - Proposal ID: {proposalId} - + Vote @@ -67,9 +60,10 @@ This behavior is intended to prevent users from changing the outcome of a vote i - - Add Comment - + + Add Comment + + castVote.mutate( { proposalId, vote: Number(vote), comment }, @@ -97,8 +91,11 @@ This behavior is intended to prevent users from changing the outcome of a vote i ) } > - Submit + {Number(votingWeight) > 0 ? "Cast Vote" : "No Voting Power"} + + All voting is final. You cannot change your vote once it has been cast. + ); }; diff --git a/src/views/Governance/Components/VotingOutcomeBar.tsx b/src/views/Governance/Components/VotingOutcomeBar.tsx index e2bf90aca3..a9339dde1c 100644 --- a/src/views/Governance/Components/VotingOutcomeBar.tsx +++ b/src/views/Governance/Components/VotingOutcomeBar.tsx @@ -1,14 +1,16 @@ -import { Box, useTheme } from "@mui/material"; +import { Box, Typography, useTheme } from "@mui/material"; import React from "react"; export const VotingOutcomeBar = ({ forVotes = 0, againstVotes = 0, abstainVotes = 0, + threshold, }: { forVotes?: number; againstVotes?: number; abstainVotes?: number; + threshold?: string; }) => { const totalVotes = forVotes + againstVotes + abstainVotes; const forPercentage = totalVotes > 0 ? (forVotes / totalVotes) * 100 : 0; @@ -34,37 +36,55 @@ export const VotingOutcomeBar = ({ const theme = useTheme(); return ( - - {/* For votes */} + - {/* Against votes */} - - {/* Abstain votes */} - + display="flex" + alignItems="center" + width="100%" + height="8px" + borderRadius={8} + bgcolor={theme.colors.gray[500]} + > + {/* For votes */} + + {/* Against votes */} + + {/* Abstain votes */} + + + {threshold && ( + + + + | + + + + )} ); }; diff --git a/src/views/Governance/Delegation/DelegateVotingModal.tsx b/src/views/Governance/Delegation/DelegateVotingModal.tsx index b77a538be4..0ca3b7b4d0 100644 --- a/src/views/Governance/Delegation/DelegateVotingModal.tsx +++ b/src/views/Governance/Delegation/DelegateVotingModal.tsx @@ -12,6 +12,7 @@ export const DelegateVotingModal = ({ open, setOpen, currentDelegateAddress, + currentWalletAddress, }: { address?: string; open: boolean; @@ -25,6 +26,7 @@ export const DelegateVotingModal = ({ > >; currentDelegateAddress?: string; + currentWalletAddress?: string; }) => { const [delegationAddress, setDelegationAddress] = useState(""); const delegateVoting = useDelegateVoting(); @@ -74,7 +76,7 @@ export const DelegateVotingModal = ({ { - setDelegationAddress(address); + currentWalletAddress && setDelegationAddress(currentWalletAddress); }} > Set to My Wallet diff --git a/src/views/Governance/Delegation/DelegationMessage.tsx b/src/views/Governance/Delegation/DelegationMessage.tsx index 0f8bf3314d..456a48c03d 100644 --- a/src/views/Governance/Delegation/DelegationMessage.tsx +++ b/src/views/Governance/Delegation/DelegationMessage.tsx @@ -15,10 +15,11 @@ export const DelegationMessage = () => { } = useGovernanceDelegationCheck(); const undelegatedV1Cooler = - !coolerV1DelegationAddress && gohmCoolerV1Balance && gohmCoolerV1Balance.value.gt(BigNumber.from("0")); + !coolerV1DelegationAddress && gohmCoolerV1Balance && gohmCoolerV1Balance.value.gt(BigNumber.from("1000000000000")); const undelegatedV2Cooler = - !coolerV2DelegationAddress && gohmCoolerV2Balance && gohmCoolerV2Balance.value.gt(BigNumber.from("0")); - const undelegatedGohm = !gOHMDelegationAddress && gohmBalance && gohmBalance.value.gt(BigNumber.from("0")); + !coolerV2DelegationAddress && gohmCoolerV2Balance && gohmCoolerV2Balance.value.gt(BigNumber.from("1000000000000")); + const undelegatedGohm = + !gOHMDelegationAddress && gohmBalance && gohmBalance.value.gt(BigNumber.from("1000000000000")); if (undelegatedV1Cooler || undelegatedV2Cooler || undelegatedGohm) { return ( diff --git a/src/views/Governance/Delegation/index.tsx b/src/views/Governance/Delegation/index.tsx index 26f518d25f..15999a2126 100644 --- a/src/views/Governance/Delegation/index.tsx +++ b/src/views/Governance/Delegation/index.tsx @@ -1,12 +1,9 @@ -import { ArrowBack } from "@mui/icons-material"; -import { Box, Link, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Typography } from "@mui/material"; -import { Paper } from "@olympusdao/component-library"; +import { Box } from "@mui/material"; +import { Modal } from "@olympusdao/component-library"; import { useState } from "react"; -import { Link as RouterLink } from "react-router-dom"; +import { useNavigate } from "react-router-dom"; import { InPageConnectButton } from "src/components/ConnectButton/ConnectButton"; -import PageTitle from "src/components/PageTitle"; import { GOHM_ADDRESSES } from "src/constants/addresses"; -import { truncateEthereumAddress } from "src/helpers/truncateAddress"; import { useTestableNetworks } from "src/hooks/useTestableNetworks"; import { GovernanceTableRow } from "src/views/Governance/Components/GovernanceTableRow"; import { DelegateVotingModal } from "src/views/Governance/Delegation/DelegateVotingModal"; @@ -26,33 +23,21 @@ export const Delegate = () => { coolerAddressV1, coolerAddressV2, } = useGovernanceDelegationCheck(); + const navigate = useNavigate(); const [delegateVoting, setDelegateVoting] = useState< { delegatorAddress: string; currentDelegatedToAddress?: string } | undefined >(undefined); return ( -
- - - - - - Back - - - - - - - Delegate Voting - - - - } - /> - + navigate("/governance")} + maxWidth="450px" + minHeight="300px" + > + <> {!isConnected ? (
@@ -60,59 +45,47 @@ export const Delegate = () => {
) : ( - <> - - - - - Address - Amount - - Delegation Status - - - - - - {address && ( - - )} - {coolerAddressV1 && ( - - )} - {coolerAddressV2 && ( - - )} - -
-
+ + {address && ( + + )} + {coolerAddressV1 && ( + + )} + {coolerAddressV2 && ( + + )} - + )} -
-
+ + ); }; diff --git a/src/views/Governance/Proposals/index.tsx b/src/views/Governance/Proposals/index.tsx index d7c2a7fb67..162e184dba 100644 --- a/src/views/Governance/Proposals/index.tsx +++ b/src/views/Governance/Proposals/index.tsx @@ -1,4 +1,4 @@ -import { Box, Grid, Link, Tab, Tabs, Typography } from "@mui/material"; +import { Box, Grid, Link, Tab, Tabs, Typography, useTheme } from "@mui/material"; import { Chip, Modal, Paper, PrimaryButton } from "@olympusdao/component-library"; import { DateTime } from "luxon"; import { useState } from "react"; @@ -17,7 +17,6 @@ import { useExecuteProposal } from "src/views/Governance/hooks/useExecuteProposa import { useGetCurrentBlockTime } from "src/views/Governance/hooks/useGetCurrentBlockTime"; import { useGetProposalDetails } from "src/views/Governance/hooks/useGetProposalDetails"; import { useGetProposal } from "src/views/Governance/hooks/useGetProposals"; -import { useGetReceipt } from "src/views/Governance/hooks/useGetReceipt"; import { useQueueProposal } from "src/views/Governance/hooks/useQueueProposal"; import { useEnsName } from "wagmi"; @@ -31,8 +30,8 @@ export const ProposalPage = () => { const activateProposal = useActivateProposal(); const queueProposal = useQueueProposal(); const executeProposal = useExecuteProposal(); - const { data: getReceipt } = useGetReceipt({ proposalId: Number(id) }); const [tabIndex, setTabIndex] = useState(0); + const theme = useTheme(); if (!proposalDetails || !proposal) { return <>; @@ -45,153 +44,156 @@ export const ProposalPage = () => { const currentBlockTime = currentBlock?.timestamp ? new Date(currentBlock?.timestamp * 1000) : new Date(); const pending = !pendingActivation && proposalDetails.status === "Pending"; const pendingExecution = Boolean(proposalDetails.status === "Queued" && currentBlockTime >= proposalDetails.etaDate); - const hasVoted = getReceipt?.hasVoted; - - console.log(proposalDetails); return (
- setVoteModalOpen(false)}> + setVoteModalOpen(false)} + maxWidth="450px" + minHeight="400px" + > setVoteModalOpen(false)} /> - - - - - - - - - {proposal?.title} - - - {pendingActivation && ( - activateProposal.mutate({ proposalId: proposalDetails.id })}> - Activate Proposal - - )} - - {pending && proposalDetails.startDate ? ( - `Voting Starts in ${DateTime.fromJSDate(proposalDetails.startDate).toRelative({ - base: DateTime.fromJSDate(currentBlockTime), - })}` - ) : proposalDetails.status === "Active" ? ( - setVoteModalOpen(true)} disabled={hasVoted}> - {hasVoted ? "Already Voted" : "Vote"} - - ) : proposalDetails.status === "Succeeded" ? ( - queueProposal.mutate({ proposalId: proposalDetails.id })}> - Queue for Execution - - ) : pendingExecution ? ( - executeProposal.mutate({ proposalId: proposalDetails.id })}> - Execute Proposal + + + + + + + + + + {proposal?.title} + + + {pendingActivation && ( + activateProposal.mutate({ proposalId: proposalDetails.id })}> + Activate Proposal - ) : ( - <> )} + + {pending && proposalDetails.startDate ? ( + `Voting Starts in ${DateTime.fromJSDate(proposalDetails.startDate).toRelative({ + base: DateTime.fromJSDate(currentBlockTime), + })}` + ) : proposalDetails.status === "Succeeded" ? ( + queueProposal.mutate({ proposalId: proposalDetails.id })}> + Queue for Execution + + ) : pendingExecution ? ( + executeProposal.mutate({ proposalId: proposalDetails.id })}> + Execute Proposal + + ) : ( + <> + )} + + + + + + Proposed on: {proposal?.createdAtBlock.toLocaleString()} + + By:{" "} + + {ensAddress || proposalDetails.proposer} + + + + Proposal ID:{" "} + + {proposalDetails.id} + - - - - Proposed on: {proposal?.createdAtBlock.toLocaleString()} - - By:{" "} - - {ensAddress || proposalDetails.proposer} - - - - Proposal ID:{" "} - + + + + + setTabIndex(newValue)} + //hides the tab underline sliding animation in while is loading + TabIndicatorProps={{ style: { display: "none" } }} + > + + + {/* */} + + {/* */} + + {tabIndex === 0 && ( + , + }} > - {proposalDetails.id} - - + {proposal?.details.description} + + )} + {tabIndex === 1 && ( + <> + {proposal.details.calldatas.map((calldata, index) => { + return ( + <> + + + ); + })} + + )} + {tabIndex === 2 && ( + <> + + Comments + + + No comments yet + + + )} - - - - - - setTabIndex(newValue)} - //hides the tab underline sliding animation in while is loading - TabIndicatorProps={{ style: { display: "none" } }} - > - - - {/* */} - - - {tabIndex === 0 && ( - , - }} - > - {proposal?.details.description} - - )} - {tabIndex === 1 && ( - - {proposal.details.calldatas.map((calldata, index) => { - return ( - <> - - - ); - })} - - )} - {tabIndex === 2 && ( - - - Comments - - - No comments yet - - - )} - - - - - + + + setVoteModalOpen(true)} /> + + - +
); }; diff --git a/src/views/Governance/helpers/index.ts b/src/views/Governance/helpers/index.ts index ac8ecf9d30..d570755adf 100644 --- a/src/views/Governance/helpers/index.ts +++ b/src/views/Governance/helpers/index.ts @@ -11,7 +11,6 @@ export function getDateFromBlock( currentTimestamp?: number, ): Date | undefined { if (targetBlock && currentBlock && averageBlockTimeInSeconds && currentTimestamp) { - console.log(targetBlock, currentBlock, averageBlockTimeInSeconds, currentTimestamp, "the details"); const date = new Date(); date.setTime((currentTimestamp + averageBlockTimeInSeconds * (targetBlock - currentBlock)) * 1000); return date; diff --git a/src/views/Governance/hooks/useDelegateVoting.tsx b/src/views/Governance/hooks/useDelegateVoting.tsx index c4097ae7b8..f443767162 100644 --- a/src/views/Governance/hooks/useDelegateVoting.tsx +++ b/src/views/Governance/hooks/useDelegateVoting.tsx @@ -33,6 +33,7 @@ export const useDelegateVoting = () => { }, onSuccess: async tx => { queryClient.invalidateQueries({ queryKey: ["checkDelegation"] }); + queryClient.invalidateQueries({ queryKey: ["votingWeight"] }); toast(`Successfully Delegated Voting`); }, }, diff --git a/src/views/Governance/hooks/useGetContractParameters.tsx b/src/views/Governance/hooks/useGetContractParameters.tsx index 3f3175c359..36c58bc4d4 100644 --- a/src/views/Governance/hooks/useGetContractParameters.tsx +++ b/src/views/Governance/hooks/useGetContractParameters.tsx @@ -28,8 +28,10 @@ export const useGetContractParameters = () => { return { proposalThreshold: formatUnits(totalSupply.mul(proposalThreshold).div(precisionFactor), 18), + proposalThresholdPercent: parseBigNumber(proposalThreshold, 8), proposalApprovalThreshold: parseBigNumber(proposalApprovalThreshold, 8) * 100, proposalQuorum: formatUnits(totalSupply.mul(proposalQuorum).div(precisionFactor)), + proposalQuorumPercent: parseBigNumber(proposalQuorum, 8) * 100, votingDelay: `${votingDelay.div(BigNumber.from(7200)).toString()} Days`, votingPeriod: `${votingPeriod.div(BigNumber.from(7200)).toString()} Days`, executionDelay: `${timelockDelay.div(BigNumber.from(86400)).toString()} Day `, diff --git a/src/views/Governance/hooks/useGetVotingWeight.tsx b/src/views/Governance/hooks/useGetVotingWeight.tsx index da152a4f95..ffbe8fa00b 100644 --- a/src/views/Governance/hooks/useGetVotingWeight.tsx +++ b/src/views/Governance/hooks/useGetVotingWeight.tsx @@ -5,7 +5,7 @@ import { useTestableNetworks } from "src/hooks/useTestableNetworks"; import { GOHM__factory } from "src/typechain"; import { useAccount, useProvider, useQuery } from "wagmi"; -export const useGetVotingWeight = ({ startBlock }: { startBlock: number }) => { +export const useGetVotingWeight = ({ startBlock }: { startBlock?: number }) => { const archiveProvider = useProvider(); const { address } = useAccount(); const networks = useTestableNetworks(); @@ -20,7 +20,7 @@ export const useGetVotingWeight = ({ startBlock }: { startBlock: number }) => { const currentVotes = await contract.getCurrentVotes(address); //if we're not activated yet - if (currentBlock.number < startBlock) { + if ((startBlock && currentBlock.number < startBlock) || !startBlock) { return formatEther(currentVotes); } else { //we're activated and need to return how contract determines weight. votes at activation or current votes, whichever is less diff --git a/src/views/Governance/index.tsx b/src/views/Governance/index.tsx index 96eb9e8ff5..02165086d3 100644 --- a/src/views/Governance/index.tsx +++ b/src/views/Governance/index.tsx @@ -1,5 +1,18 @@ -import { Box, Link, Paper, Table, TableBody, TableCell, TableContainer, TableHead, TableRow } from "@mui/material"; -import { PrimaryButton } from "@olympusdao/component-library"; +import { + Box, + Link, + Paper, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + Typography, + useTheme, +} from "@mui/material"; +import { Metric, PrimaryButton } from "@olympusdao/component-library"; +import { useState } from "react"; import { Link as RouterLink } from "react-router-dom"; import PageTitle from "src/components/PageTitle"; import { ContractParameters } from "src/views/Governance/Components/ContractParameters"; @@ -7,46 +20,118 @@ import { ProposalContainer } from "src/views/Governance/Components/ProposalConta import { DelegationMessage } from "src/views/Governance/Delegation/DelegationMessage"; import { GovernanceDevTools } from "src/views/Governance/hooks/dev/GovernanceDevTools"; import { useGetProposals } from "src/views/Governance/hooks/useGetProposals"; +import { useGetVotingWeight } from "src/views/Governance/hooks/useGetVotingWeight"; export const Governance = () => { const { data: proposals } = useGetProposals(); + const { data: currentVotingWeight } = useGetVotingWeight({}); + const theme = useTheme(); + const [activeProposals, setActiveProposals] = useState([]); + const [pastProposals, setPastProposals] = useState([]); + const [pastProposalsLoading, setPastProposalsLoading] = useState(true); + const [activeProposalsLoading, setActiveProposalsLoading] = useState(true); + return (
+ - My Voting Power + Manage Voting Delegation - - - - - Proposal - Votes for - Votes against - - Total votes - - - - - {proposals?.map((item, index) => { - return ( - - ); - })} - -
-
+ + + Upcoming & Active Proposals + + + + + + Proposal + + Approval + + + Quorum + + + Total votes + + + + + {proposals?.map((item, index) => { + return ( + + ); + })} + +
+
+ {activeProposals.length === 0 && !activeProposalsLoading ? ( + + No Upcoming or Active Proposals + + ) : null} +
+ + + Past Proposals + + + + + + Proposal + + Approval + + + Quorum + + + Total votes + + + + + {proposals?.map((item, index) => { + return ( + + ); + })} + +
+
+ {pastProposals.length === 0 && !activeProposalsLoading ? ( + + No Past Proposals + + ) : null} +
diff --git a/src/views/MyBalances/index.tsx b/src/views/MyBalances/index.tsx index 20638b05d7..62edd579a1 100644 --- a/src/views/MyBalances/index.tsx +++ b/src/views/MyBalances/index.tsx @@ -141,6 +141,8 @@ export const MyBalances: FC = () => { }, ]; + console.log(totalWsohmBalance); + const walletTotalValueUSD = Object.values(tokenArray).reduce((totalValue, token) => totalValue + token.assetValue, 0); const myOhmBalancesTotalValueUSD = Object.values(myOhmBalances).reduce( (totalValue, token) => totalValue + token.assetValue, From 495e2076ccc217a4ac4f197bbd70497d49fb8dd4 Mon Sep 17 00:00:00 2001 From: brightiron Date: Tue, 16 Apr 2024 21:57:28 -0500 Subject: [PATCH 04/12] more tweaks --- .../Governance/Components/CurrentVotes.tsx | 49 +++++++++---------- .../Components/ProposalContainer.tsx | 4 +- src/views/Governance/Proposals/index.tsx | 2 +- 3 files changed, 26 insertions(+), 29 deletions(-) diff --git a/src/views/Governance/Components/CurrentVotes.tsx b/src/views/Governance/Components/CurrentVotes.tsx index 3ee0e110b4..9a7acbba61 100644 --- a/src/views/Governance/Components/CurrentVotes.tsx +++ b/src/views/Governance/Components/CurrentVotes.tsx @@ -54,18 +54,16 @@ export const CurrentVotes = ({ proposalId, onVoteClick }: { proposalId: number; )} Approval
- {parameters?.proposalThresholdPercent && proposalDetails?.forCount && proposalDetails.quorumVotes && ( - - - - - - )} + + + + +
@@ -76,20 +74,19 @@ export const CurrentVotes = ({ proposalId, onVoteClick }: { proposalId: number; )} Quorum - {parameters?.proposalQuorumPercent && proposalDetails?.forCount && proposalDetails.quorumVotes && ( - - - - - - )} + + + + + + diff --git a/src/views/Governance/Components/ProposalContainer.tsx b/src/views/Governance/Components/ProposalContainer.tsx index 0a84174d8a..02e46fe9dd 100644 --- a/src/views/Governance/Components/ProposalContainer.tsx +++ b/src/views/Governance/Components/ProposalContainer.tsx @@ -74,14 +74,14 @@ export const ProposalContainer = ({ } const totalVotes = proposal.forCount + proposal.abstainCount + proposal.againstCount; - const approvalPercentage = (proposal.forCount / (proposal.forCount + proposal.againstCount)) * 100; + const approvalPercentage = (proposal.forCount / (proposal.forCount + proposal.againstCount)) * 100 || 0; const aboveQuorum = Boolean(proposal.forCount > proposal.quorumVotes); const totalSupply = proposal?.quorumVotes && parameters?.proposalQuorumPercent ? proposal?.quorumVotes / (parameters?.proposalQuorumPercent / 100) : 0; - const quorumPercentage = (proposal.forCount / totalSupply) * 100; + const quorumPercentage = (proposal.forCount / totalSupply) * 100 || 0; const approvalThresholdDenominator = (proposal.forCount + proposal.againstCount) * (parameters?.proposalApprovalThreshold / 100); const aboveThreshold = Boolean(approvalThresholdDenominator && proposal.forCount > approvalThresholdDenominator); diff --git a/src/views/Governance/Proposals/index.tsx b/src/views/Governance/Proposals/index.tsx index 162e184dba..f5c6ba315d 100644 --- a/src/views/Governance/Proposals/index.tsx +++ b/src/views/Governance/Proposals/index.tsx @@ -82,7 +82,7 @@ export const ProposalPage = () => { Activate Proposal )} - + {pending && proposalDetails.startDate ? ( `Voting Starts in ${DateTime.fromJSDate(proposalDetails.startDate).toRelative({ base: DateTime.fromJSDate(currentBlockTime), From d79613ca8e79968b362ee0ce922a3dfadb1aea34 Mon Sep 17 00:00:00 2001 From: brightiron Date: Tue, 16 Apr 2024 22:02:35 -0500 Subject: [PATCH 05/12] =?UTF-8?q?proposal=20loading=C3=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/views/Governance/Components/ProposalContainer.tsx | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/views/Governance/Components/ProposalContainer.tsx b/src/views/Governance/Components/ProposalContainer.tsx index 02e46fe9dd..30b2c80df2 100644 --- a/src/views/Governance/Components/ProposalContainer.tsx +++ b/src/views/Governance/Components/ProposalContainer.tsx @@ -50,10 +50,15 @@ export const ProposalContainer = ({ setProposals(prev => [...prev, proposalId]); } } - if (proposals.length === proposalId) { - setIsLoading(false); - } }, [proposal]); + + useEffect(() => { + if (proposal && parameters && !isLoading) { + if (proposalsCount === proposalId) { + setIsLoading(false); + } + } + }, [isLoading, proposal, parameters, proposalsCount, proposalId]); if (isLoading || !proposal || !parameters) { return ( From 1da020612d1970469e4597701558d264f42f666d Mon Sep 17 00:00:00 2001 From: brightiron Date: Tue, 16 Apr 2024 22:06:29 -0500 Subject: [PATCH 06/12] nan fix --- src/views/Governance/index.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/views/Governance/index.tsx b/src/views/Governance/index.tsx index 02165086d3..96fc4a160a 100644 --- a/src/views/Governance/index.tsx +++ b/src/views/Governance/index.tsx @@ -35,9 +35,9 @@ export const Governance = () => {
- + {import.meta.env.VITE_GOVERNANCE_DEV && } - + Manage Voting Delegation @@ -118,7 +118,7 @@ export const Governance = () => { createdAt={item?.createdAtBlock} setProposals={setPastProposals} proposals={pastProposals} - setIsLoading={setActiveProposalsLoading} + setIsLoading={setPastProposalsLoading} proposalsCount={proposals.length} /> ); @@ -126,7 +126,7 @@ export const Governance = () => { - {pastProposals.length === 0 && !activeProposalsLoading ? ( + {pastProposals.length === 0 && !pastProposalsLoading ? ( No Past Proposals From 2e98a6349cb374399a91e0bc328879fe3b63ec88 Mon Sep 17 00:00:00 2001 From: brightiron Date: Wed, 17 Apr 2024 16:00:20 -0500 Subject: [PATCH 07/12] nan --- src/views/Governance/Components/VoteModal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/views/Governance/Components/VoteModal.tsx b/src/views/Governance/Components/VoteModal.tsx index 223777cd5a..9e8f936bf0 100644 --- a/src/views/Governance/Components/VoteModal.tsx +++ b/src/views/Governance/Components/VoteModal.tsx @@ -38,7 +38,7 @@ This behavior is intended to prevent users from changing the outcome of a vote i /> - {Number(votingWeight).toFixed(2)} gOHM + {Number(votingWeight || 0).toFixed(2)} gOHM From 0d3604f709cc03c6d6d14efdca3ff68e8fd590df Mon Sep 17 00:00:00 2001 From: brightiron Date: Thu, 18 Apr 2024 10:20:07 -0500 Subject: [PATCH 08/12] minor tweaks --- .../Governance/Components/CurrentVotes.tsx | 24 ++++++++++++------- src/views/Governance/Components/Status.tsx | 18 ++++++++++---- 2 files changed, 29 insertions(+), 13 deletions(-) diff --git a/src/views/Governance/Components/CurrentVotes.tsx b/src/views/Governance/Components/CurrentVotes.tsx index 9a7acbba61..f892b52084 100644 --- a/src/views/Governance/Components/CurrentVotes.tsx +++ b/src/views/Governance/Components/CurrentVotes.tsx @@ -13,7 +13,7 @@ export const CurrentVotes = ({ proposalId, onVoteClick }: { proposalId: number; const { data: parameters } = useGetContractParameters(); const { data: getReceipt } = useGetReceipt({ proposalId }); const hasVoted = getReceipt?.hasVoted; - const support = getReceipt?.support === 0 ? "Against" : getReceipt?.support === 1 ? "For" : "Abstain"; + const support = getReceipt?.support === 0 ? "Against" : getReceipt?.support === 1 ? "For" : " to Abstain"; const theme = useTheme(); @@ -75,9 +75,7 @@ export const CurrentVotes = ({ proposalId, onVoteClick }: { proposalId: number; Quorum - + For - {abbreviatedNumber.format(proposalDetails?.forCount || 0)} + + {abbreviatedNumber.format(proposalDetails?.forCount || 0)} + Against - {abbreviatedNumber.format(proposalDetails?.againstCount || 0)} + + {abbreviatedNumber.format(proposalDetails?.againstCount || 0)} + Abstain - {abbreviatedNumber.format(proposalDetails?.abstainCount || 0)} + + {abbreviatedNumber.format(proposalDetails?.abstainCount || 0)} + Total Votes - {abbreviatedNumber.format(totalVotes || 0)} + + {abbreviatedNumber.format(totalVotes || 0)} + - {hasVoted ? `Already Voted (${support})` : "Vote"} + {hasVoted ? `Voted (${support})` : "Vote"} diff --git a/src/views/Governance/Components/Status.tsx b/src/views/Governance/Components/Status.tsx index 079d5c6774..73b771191a 100644 --- a/src/views/Governance/Components/Status.tsx +++ b/src/views/Governance/Components/Status.tsx @@ -107,11 +107,21 @@ export const Status = ({ proposalId }: { proposalId: number }) => { )} )} + {proposalDetails?.status === "Defeated" && ( + <> + {(proposalDetails?.endDate || placeholderEndDate) && ( +
+ + {proposalDetails.endDate + ? proposalDetails?.endDate?.toLocaleString() + : placeholderEndDate?.toLocaleString()} + + Proposal Defeated +
+ )} + + )} - {/*

if active or not defeated show the below

-

Queue Proposal:

-

Execute Proposal:

-

if defeated then show status why

*/} ); }; From 06d963aee6fed429260536ac56086bfe7233981c Mon Sep 17 00:00:00 2001 From: brightiron Date: Thu, 18 Apr 2024 14:29:58 -0500 Subject: [PATCH 09/12] hide vote if past active --- src/views/Governance/Components/CurrentVotes.tsx | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/views/Governance/Components/CurrentVotes.tsx b/src/views/Governance/Components/CurrentVotes.tsx index f892b52084..ab876c7dbb 100644 --- a/src/views/Governance/Components/CurrentVotes.tsx +++ b/src/views/Governance/Components/CurrentVotes.tsx @@ -120,11 +120,15 @@ export const CurrentVotes = ({ proposalId, onVoteClick }: { proposalId: number; againstVotes={proposalDetails?.againstCount} abstainVotes={proposalDetails?.abstainCount} /> - - - {hasVoted ? `Voted (${support})` : "Vote"} - - + {(proposalDetails?.status == "Active" || + proposalDetails?.status == "Pending" || + proposalDetails?.status == "Emergency") && ( + + + {hasVoted ? `Voted (${support})` : "Vote"} + + + )} ); From 5ee30ba152f57991d9b23459ed18a13760b47fe1 Mon Sep 17 00:00:00 2001 From: brightiron Date: Fri, 19 Apr 2024 12:07:37 -0500 Subject: [PATCH 10/12] network checks. make sure were using static provider --- .../environment/Environment/Environment.ts | 14 +- src/hooks/wagmi.ts | 5 +- .../Governance/Components/CurrentVotes.tsx | 17 +- src/views/Governance/Components/VoteModal.tsx | 39 ++-- .../Delegation/DelegationMessage.tsx | 12 +- src/views/Governance/Proposals/index.tsx | 171 ++++++++++-------- .../Governance/hooks/useGetCanceledTime.tsx | 10 +- .../Governance/hooks/useGetExecutedTime.tsx | 10 +- .../Governance/hooks/useGetProposals.tsx | 12 +- .../Governance/hooks/useGetQueuedTime.tsx | 11 +- .../Governance/hooks/useGetVetoedTime.tsx | 10 +- src/views/Governance/index.tsx | 14 +- 12 files changed, 194 insertions(+), 131 deletions(-) diff --git a/src/helpers/environment/Environment/Environment.ts b/src/helpers/environment/Environment/Environment.ts index 58ec48c469..47622ee086 100644 --- a/src/helpers/environment/Environment/Environment.ts +++ b/src/helpers/environment/Environment/Environment.ts @@ -54,10 +54,22 @@ export class Environment { fallback: "false", }); + public static getGovernanceStartBlock = (): number => + parseInt( + this._get({ + first: true, + key: "VITE_GOVERNANCE_START_BLOCK", + fallback: "0", + }), + ); + public static getNodeUrls = (networkId: NetworkId) => { switch (networkId) { case NetworkId.MAINNET: - return ["https://rpc.tenderly.co/fork/f7571dd4-342e-457a-a83b-670b6a84e4c4"]; //TODO: revert this after testing. this is like this so we dont have to mess w/ fleek environments for fork testing + return this._get({ + key: `VITE_ETHEREUM_NODE_URL`, + fallback: "https://rpc.ankr.com/eth", + }); case NetworkId.TESTNET_GOERLI: return this._get({ key: `VITE_ETHEREUM_TESTNET_NODE_URL`, diff --git a/src/hooks/wagmi.ts b/src/hooks/wagmi.ts index 773a4c5ab1..4947782d06 100644 --- a/src/hooks/wagmi.ts +++ b/src/hooks/wagmi.ts @@ -23,10 +23,7 @@ export const { chains, provider, webSocketProvider } = configureChains( [ { ...mainnet, - rpcUrls: { - default: { http: ["https://rpc.tenderly.co/fork/f7571dd4-342e-457a-a83b-670b6a84e4c4"] }, - public: { http: ["https://rpc.tenderly.co/fork/f7571dd4-342e-457a-a83b-670b6a84e4c4"] }, - }, + rpcUrls: { default: { http: ["https://rpc.ankr.com/eth"] }, public: { http: ["https://rpc.ankr.com/eth"] } }, }, { ...polygon, diff --git a/src/views/Governance/Components/CurrentVotes.tsx b/src/views/Governance/Components/CurrentVotes.tsx index ab876c7dbb..4f8a422c23 100644 --- a/src/views/Governance/Components/CurrentVotes.tsx +++ b/src/views/Governance/Components/CurrentVotes.tsx @@ -3,15 +3,22 @@ import { Box, Tooltip, Typography, useTheme } from "@mui/material"; import { Paper, PrimaryButton } from "@olympusdao/component-library"; import { WalletConnectedGuard } from "src/components/WalletConnectedGuard"; import { abbreviatedNumber } from "src/helpers"; +import { useTestableNetworks } from "src/hooks/useTestableNetworks"; +import { NetworkId } from "src/networkDetails"; import { VotingOutcomeBar } from "src/views/Governance/Components/VotingOutcomeBar"; import { useGetContractParameters } from "src/views/Governance/hooks/useGetContractParameters"; import { useGetProposalDetails } from "src/views/Governance/hooks/useGetProposalDetails"; import { useGetReceipt } from "src/views/Governance/hooks/useGetReceipt"; +import { useNetwork, useSwitchNetwork } from "wagmi"; export const CurrentVotes = ({ proposalId, onVoteClick }: { proposalId: number; onVoteClick: () => void }) => { const { data: proposalDetails } = useGetProposalDetails({ proposalId }); const { data: parameters } = useGetContractParameters(); const { data: getReceipt } = useGetReceipt({ proposalId }); + const { chain } = useNetwork(); + const networks = useTestableNetworks(); + const { switchNetwork } = useSwitchNetwork(); + const hasVoted = getReceipt?.hasVoted; const support = getReceipt?.support === 0 ? "Against" : getReceipt?.support === 1 ? "For" : " to Abstain"; @@ -124,9 +131,13 @@ export const CurrentVotes = ({ proposalId, onVoteClick }: { proposalId: number; proposalDetails?.status == "Pending" || proposalDetails?.status == "Emergency") && ( - - {hasVoted ? `Voted (${support})` : "Vote"} - + {networks.MAINNET === chain?.id ? ( + + {hasVoted ? `Voted (${support})` : "Vote"} + + ) : ( + switchNetwork?.(NetworkId.MAINNET)}>Switch Network + )} )} diff --git a/src/views/Governance/Components/VoteModal.tsx b/src/views/Governance/Components/VoteModal.tsx index 9e8f936bf0..4d95b48a3b 100644 --- a/src/views/Governance/Components/VoteModal.tsx +++ b/src/views/Governance/Components/VoteModal.tsx @@ -37,7 +37,7 @@ For votes that come from tokens that you own, if you delegated to yourself after This behavior is intended to prevent users from changing the outcome of a vote in progress by buying or borrowing additional votes." /> - + {Number(votingWeight || 0).toFixed(2)} gOHM @@ -70,29 +70,30 @@ This behavior is intended to prevent users from changing the outcome of a vote i rows={4} variant="outlined" fullWidth - margin="normal" value={comment} onChange={e => setComment(e.target.value)} InputProps={{ notched: false }} /> - - castVote.mutate( - { proposalId, vote: Number(vote), comment }, - { - onSuccess: () => { - setVote(""); - setComment(""); - onClose(); + + + castVote.mutate( + { proposalId, vote: Number(vote), comment }, + { + onSuccess: () => { + setVote(""); + setComment(""); + onClose(); + }, }, - }, - ) - } - > - {Number(votingWeight) > 0 ? "Cast Vote" : "No Voting Power"} - + ) + } + > + {Number(votingWeight) > 0 ? "Cast Vote" : "No Voting Power"} + + All voting is final. You cannot change your vote once it has been cast. diff --git a/src/views/Governance/Delegation/DelegationMessage.tsx b/src/views/Governance/Delegation/DelegationMessage.tsx index 456a48c03d..56c188eb72 100644 --- a/src/views/Governance/Delegation/DelegationMessage.tsx +++ b/src/views/Governance/Delegation/DelegationMessage.tsx @@ -1,4 +1,4 @@ -import { Link, Typography } from "@mui/material"; +import { Link } from "@mui/material"; import { InfoNotification } from "@olympusdao/component-library"; import { BigNumber } from "ethers"; import { Link as RouterLink } from "react-router-dom"; @@ -24,12 +24,10 @@ export const DelegationMessage = () => { if (undelegatedV1Cooler || undelegatedV2Cooler || undelegatedGohm) { return ( - - To participate on on-chain governance you must delegate your gOHM{" "} - - Learn More - - + To participate on on-chain governance you must delegate your gOHM{" "} + + Learn More + ); } diff --git a/src/views/Governance/Proposals/index.tsx b/src/views/Governance/Proposals/index.tsx index f5c6ba315d..d4cffa21db 100644 --- a/src/views/Governance/Proposals/index.tsx +++ b/src/views/Governance/Proposals/index.tsx @@ -1,5 +1,5 @@ import { Box, Grid, Link, Tab, Tabs, Typography, useTheme } from "@mui/material"; -import { Chip, Modal, Paper, PrimaryButton } from "@olympusdao/component-library"; +import { Chip, Modal, PrimaryButton } from "@olympusdao/component-library"; import { DateTime } from "luxon"; import { useState } from "react"; import ReactMarkdown from "react-markdown"; @@ -7,6 +7,10 @@ import { useParams } from "react-router-dom"; import { Link as RouterLink } from "react-router-dom"; import remarkGfm from "remark-gfm"; import PageTitle from "src/components/PageTitle"; +import { WalletConnectedGuard } from "src/components/WalletConnectedGuard"; +import { truncateEthereumAddress } from "src/helpers/truncateAddress"; +import { useTestableNetworks } from "src/hooks/useTestableNetworks"; +import { NetworkId } from "src/networkDetails"; import { CallData } from "src/views/Governance/Components/CallData"; import { CurrentVotes } from "src/views/Governance/Components/CurrentVotes"; import { Status } from "src/views/Governance/Components/Status"; @@ -18,7 +22,7 @@ import { useGetCurrentBlockTime } from "src/views/Governance/hooks/useGetCurrent import { useGetProposalDetails } from "src/views/Governance/hooks/useGetProposalDetails"; import { useGetProposal } from "src/views/Governance/hooks/useGetProposals"; import { useQueueProposal } from "src/views/Governance/hooks/useQueueProposal"; -import { useEnsName } from "wagmi"; +import { useEnsName, useNetwork, useSwitchNetwork } from "wagmi"; export const ProposalPage = () => { const { id } = useParams(); @@ -30,6 +34,9 @@ export const ProposalPage = () => { const activateProposal = useActivateProposal(); const queueProposal = useQueueProposal(); const executeProposal = useExecuteProposal(); + const { chain } = useNetwork(); + const networks = useTestableNetworks(); + const { switchNetwork } = useSwitchNetwork(); const [tabIndex, setTabIndex] = useState(0); const theme = useTheme(); @@ -65,87 +72,99 @@ export const ProposalPage = () => { - - - - - - - {proposal?.title} - - - {pendingActivation && ( - activateProposal.mutate({ proposalId: proposalDetails.id })}> - Activate Proposal - + + + + + + {proposal?.title} + + + {pendingActivation && ( + activateProposal.mutate({ proposalId: proposalDetails.id })}> + Activate Proposal + + )} + + {pending && proposalDetails.startDate ? ( + `Voting Starts in ${DateTime.fromJSDate(proposalDetails.startDate).toRelative({ + base: DateTime.fromJSDate(currentBlockTime), + })}` + ) : proposalDetails.status === "Succeeded" ? ( + + {networks.MAINNET === chain?.id ? ( + queueProposal.mutate({ proposalId: proposalDetails.id })}> + Queue for Execution + + ) : ( + switchNetwork?.(NetworkId.MAINNET)}>Switch Network + )} + + ) : pendingExecution ? ( + + {networks.MAINNET === chain?.id ? ( + executeProposal.mutate({ proposalId: proposalDetails.id })}> + Execute Proposal + + ) : ( + switchNetwork?.(NetworkId.MAINNET)}>Switch Network + )} + + ) : ( + <> )} - - {pending && proposalDetails.startDate ? ( - `Voting Starts in ${DateTime.fromJSDate(proposalDetails.startDate).toRelative({ - base: DateTime.fromJSDate(currentBlockTime), - })}` - ) : proposalDetails.status === "Succeeded" ? ( - queueProposal.mutate({ proposalId: proposalDetails.id })}> - Queue for Execution - - ) : pendingExecution ? ( - executeProposal.mutate({ proposalId: proposalDetails.id })}> - Execute Proposal - - ) : ( - <> - )} - - - - - - Proposed on: {proposal?.createdAtBlock.toLocaleString()} - - By:{" "} - - {ensAddress || proposalDetails.proposer} - - - - Proposal ID:{" "} - - {proposalDetails.id} - - + + + + + Proposed on: {proposal?.createdAtBlock.toLocaleString()} | By:{" "} + + {ensAddress || truncateEthereumAddress(proposalDetails.proposer)} + + + + + Proposal ID:{" "} + + {proposalDetails.id} + + + + + setTabIndex(newValue)} + //hides the tab underline sliding animation in while is loading + TabIndicatorProps={{ style: { display: "none" } }} + > + + + {/* */} + + - setTabIndex(newValue)} - //hides the tab underline sliding animation in while is loading - TabIndicatorProps={{ style: { display: "none" } }} - > - - - {/* */} - {/* */} {tabIndex === 0 && ( diff --git a/src/views/Governance/hooks/useGetCanceledTime.tsx b/src/views/Governance/hooks/useGetCanceledTime.tsx index a3dd7692f2..fdbd6e60e5 100644 --- a/src/views/Governance/hooks/useGetCanceledTime.tsx +++ b/src/views/Governance/hooks/useGetCanceledTime.tsx @@ -1,11 +1,12 @@ import { useQuery } from "@tanstack/react-query"; import { GOVERNANCE_CONTRACT } from "src/constants/contracts"; +import { Environment } from "src/helpers/environment/Environment/Environment"; +import { Providers } from "src/helpers/providers/Providers/Providers"; import { NetworkId } from "src/networkDetails"; import { ProposalCanceledEventObject } from "src/typechain/OlympusGovernorBravo"; -import { useProvider } from "wagmi"; export const useGetCanceledTime = ({ proposalId, status }: { proposalId: number; status?: string }) => { - const archiveProvider = useProvider(); + const archiveProvider = Providers.getStaticProvider(NetworkId.MAINNET); const contract = GOVERNANCE_CONTRACT.getEthersContract(NetworkId.MAINNET); return useQuery( ["getCanceledTime", NetworkId.MAINNET, proposalId, status], @@ -14,7 +15,10 @@ export const useGetCanceledTime = ({ proposalId, status }: { proposalId: number; return { createdAtBlockTime: undefined, details: undefined, txHash: undefined }; } // using EVENTS - const proposalExecutedEvents = await contract.queryFilter(contract.filters.ProposalCanceled(), 19520392); + const proposalExecutedEvents = await contract.queryFilter( + contract.filters.ProposalCanceled(), + Environment.getGovernanceStartBlock(), + ); const proposal = proposalExecutedEvents.find(item => item.args.id.toNumber() === proposalId); const timestamp = proposal && (await archiveProvider.getBlock(proposal.blockNumber)).timestamp; if (proposal?.decode) { diff --git a/src/views/Governance/hooks/useGetExecutedTime.tsx b/src/views/Governance/hooks/useGetExecutedTime.tsx index 01aacf5769..245dbb5498 100644 --- a/src/views/Governance/hooks/useGetExecutedTime.tsx +++ b/src/views/Governance/hooks/useGetExecutedTime.tsx @@ -1,11 +1,12 @@ import { useQuery } from "@tanstack/react-query"; import { GOVERNANCE_CONTRACT } from "src/constants/contracts"; +import { Environment } from "src/helpers/environment/Environment/Environment"; +import { Providers } from "src/helpers/providers/Providers/Providers"; import { NetworkId } from "src/networkDetails"; import { ProposalExecutedEventObject } from "src/typechain/OlympusGovernorBravo"; -import { useProvider } from "wagmi"; export const useGetExecutedTime = ({ proposalId, status }: { proposalId: number; status?: string }) => { - const archiveProvider = useProvider(); + const archiveProvider = Providers.getStaticProvider(NetworkId.MAINNET); const contract = GOVERNANCE_CONTRACT.getEthersContract(NetworkId.MAINNET); return useQuery( ["getExecutedTime", NetworkId.MAINNET, proposalId, status], @@ -14,7 +15,10 @@ export const useGetExecutedTime = ({ proposalId, status }: { proposalId: number; return { createdAtBlockTime: undefined, details: undefined, txHash: undefined }; } // using EVENTS - const proposalExecutedEvents = await contract.queryFilter(contract.filters.ProposalExecuted(), 19520392); + const proposalExecutedEvents = await contract.queryFilter( + contract.filters.ProposalExecuted(), + Environment.getGovernanceStartBlock(), + ); const proposal = proposalExecutedEvents.find(item => item.args.id.toNumber() === proposalId); const timestamp = proposal && (await archiveProvider.getBlock(proposal.blockNumber)).timestamp; if (proposal?.decode) { diff --git a/src/views/Governance/hooks/useGetProposals.tsx b/src/views/Governance/hooks/useGetProposals.tsx index 7df43ce92e..29dca80c9c 100644 --- a/src/views/Governance/hooks/useGetProposals.tsx +++ b/src/views/Governance/hooks/useGetProposals.tsx @@ -1,17 +1,23 @@ import { useQuery } from "@tanstack/react-query"; import { GOVERNANCE_CONTRACT } from "src/constants/contracts"; +import { Environment } from "src/helpers/environment/Environment/Environment"; +import { Providers } from "src/helpers/providers/Providers/Providers"; import { NetworkId } from "src/networkDetails"; import { ProposalCreatedEventObject } from "src/typechain/OlympusGovernorBravo"; -import { useProvider } from "wagmi"; export const useGetProposals = () => { - const archiveProvider = useProvider(); + const archiveProvider = Providers.getStaticProvider(NetworkId.MAINNET); const contract = GOVERNANCE_CONTRACT.getEthersContract(NetworkId.MAINNET); return useQuery( ["getProposals", NetworkId.MAINNET], async () => { // using EVENTS - const proposalCreatedEvents = await contract.queryFilter(contract.filters.ProposalCreated(), 19520392); + const proposalCreatedEvents = await contract.queryFilter( + contract.filters.ProposalCreated(), + Environment.getGovernanceStartBlock(), + ); + + console.log(proposalCreatedEvents); const proposals = Promise.all( proposalCreatedEvents.map(async item => { diff --git a/src/views/Governance/hooks/useGetQueuedTime.tsx b/src/views/Governance/hooks/useGetQueuedTime.tsx index 0440381433..4de8596b17 100644 --- a/src/views/Governance/hooks/useGetQueuedTime.tsx +++ b/src/views/Governance/hooks/useGetQueuedTime.tsx @@ -1,16 +1,21 @@ import { GOVERNANCE_CONTRACT } from "src/constants/contracts"; +import { Environment } from "src/helpers/environment/Environment/Environment"; +import { Providers } from "src/helpers/providers/Providers/Providers"; import { NetworkId } from "src/networkDetails"; import { ProposalQueuedEventObject } from "src/typechain/OlympusGovernorBravo"; -import { useProvider, useQuery } from "wagmi"; +import { useQuery } from "wagmi"; export const useGetQueuedTime = ({ proposalId }: { proposalId: number }) => { - const archiveProvider = useProvider(); + const archiveProvider = Providers.getStaticProvider(NetworkId.MAINNET); const contract = GOVERNANCE_CONTRACT.getEthersContract(NetworkId.MAINNET); return useQuery( ["getQueuedTime", NetworkId.MAINNET, proposalId], async () => { // using EVENTS - const proposalQueuedEvents = await contract.queryFilter(contract.filters.ProposalQueued(), 19520392); + const proposalQueuedEvents = await contract.queryFilter( + contract.filters.ProposalQueued(), + Environment.getGovernanceStartBlock(), + ); const proposal = proposalQueuedEvents.find(item => item.args.id.toNumber() === proposalId); const timestamp = proposal && (await archiveProvider.getBlock(proposal.blockNumber)).timestamp; if (proposal?.decode) { diff --git a/src/views/Governance/hooks/useGetVetoedTime.tsx b/src/views/Governance/hooks/useGetVetoedTime.tsx index 99f7f8a1c4..2c114dbb91 100644 --- a/src/views/Governance/hooks/useGetVetoedTime.tsx +++ b/src/views/Governance/hooks/useGetVetoedTime.tsx @@ -1,11 +1,12 @@ import { useQuery } from "@tanstack/react-query"; import { GOVERNANCE_CONTRACT } from "src/constants/contracts"; +import { Environment } from "src/helpers/environment/Environment/Environment"; +import { Providers } from "src/helpers/providers/Providers/Providers"; import { NetworkId } from "src/networkDetails"; import { ProposalVetoedEventObject } from "src/typechain/OlympusGovernorBravo"; -import { useProvider } from "wagmi"; export const useGetVetoedTime = ({ proposalId, status }: { proposalId: number; status?: string }) => { - const archiveProvider = useProvider(); + const archiveProvider = Providers.getStaticProvider(NetworkId.MAINNET); const contract = GOVERNANCE_CONTRACT.getEthersContract(NetworkId.MAINNET); return useQuery( ["getVetoedTime", NetworkId.MAINNET, proposalId, status], @@ -14,7 +15,10 @@ export const useGetVetoedTime = ({ proposalId, status }: { proposalId: number; s return { createdAtBlockTime: undefined, details: undefined, txHash: undefined }; } // using EVENTS - const proposalExecutedEvents = await contract.queryFilter(contract.filters.ProposalVetoed(), 19520392); + const proposalExecutedEvents = await contract.queryFilter( + contract.filters.ProposalVetoed(), + Environment.getGovernanceStartBlock(), + ); const proposal = proposalExecutedEvents.find(item => item.args.id.toNumber() === proposalId); const timestamp = proposal && (await archiveProvider.getBlock(proposal.blockNumber)).timestamp; if (proposal?.decode) { diff --git a/src/views/Governance/index.tsx b/src/views/Governance/index.tsx index 96fc4a160a..9cd97377ac 100644 --- a/src/views/Governance/index.tsx +++ b/src/views/Governance/index.tsx @@ -23,7 +23,7 @@ import { useGetProposals } from "src/views/Governance/hooks/useGetProposals"; import { useGetVotingWeight } from "src/views/Governance/hooks/useGetVotingWeight"; export const Governance = () => { - const { data: proposals } = useGetProposals(); + const { data: proposals, isFetching } = useGetProposals(); const { data: currentVotingWeight } = useGetVotingWeight({}); const theme = useTheme(); const [activeProposals, setActiveProposals] = useState([]); @@ -44,7 +44,7 @@ export const Governance = () => { - + Upcoming & Active Proposals @@ -82,14 +82,14 @@ export const Governance = () => { - {activeProposals.length === 0 && !activeProposalsLoading ? ( + {(activeProposals.length === 0 && !activeProposalsLoading) || (proposals?.length === 0 && !isFetching) ? ( No Upcoming or Active Proposals ) : null} - + Past Proposals @@ -126,13 +126,15 @@ export const Governance = () => { - {pastProposals.length === 0 && !pastProposalsLoading ? ( + {(pastProposals.length === 0 && !pastProposalsLoading) || (proposals?.length === 0 && !isFetching) ? ( No Past Proposals ) : null} - + + +
); From 9158c993165795455ac9cff1a6b39b20893ad841 Mon Sep 17 00:00:00 2001 From: brightiron Date: Fri, 19 Apr 2024 12:09:31 -0500 Subject: [PATCH 11/12] cleanup --- src/views/Governance/hooks/useGetProposals.tsx | 2 -- src/views/MyBalances/index.tsx | 2 -- 2 files changed, 4 deletions(-) diff --git a/src/views/Governance/hooks/useGetProposals.tsx b/src/views/Governance/hooks/useGetProposals.tsx index 29dca80c9c..d526f0ba3d 100644 --- a/src/views/Governance/hooks/useGetProposals.tsx +++ b/src/views/Governance/hooks/useGetProposals.tsx @@ -17,8 +17,6 @@ export const useGetProposals = () => { Environment.getGovernanceStartBlock(), ); - console.log(proposalCreatedEvents); - const proposals = Promise.all( proposalCreatedEvents.map(async item => { const timestamp = (await archiveProvider.getBlock(item.blockNumber)).timestamp; diff --git a/src/views/MyBalances/index.tsx b/src/views/MyBalances/index.tsx index 62edd579a1..20638b05d7 100644 --- a/src/views/MyBalances/index.tsx +++ b/src/views/MyBalances/index.tsx @@ -141,8 +141,6 @@ export const MyBalances: FC = () => { }, ]; - console.log(totalWsohmBalance); - const walletTotalValueUSD = Object.values(tokenArray).reduce((totalValue, token) => totalValue + token.assetValue, 0); const myOhmBalancesTotalValueUSD = Object.values(myOhmBalances).reduce( (totalValue, token) => totalValue + token.assetValue, From 78b3cf8850a48204a44c5387f238f2aba93f6080 Mon Sep 17 00:00:00 2001 From: brightiron Date: Fri, 19 Apr 2024 12:23:05 -0500 Subject: [PATCH 12/12] archive provider --- src/helpers/environment/Environment/Environment.ts | 14 ++++++++++++++ src/helpers/providers/Providers/Providers.ts | 13 +++++++++++++ src/views/Governance/hooks/useGetCanceledTime.tsx | 2 +- src/views/Governance/hooks/useGetExecutedTime.tsx | 2 +- src/views/Governance/hooks/useGetProposals.tsx | 2 +- src/views/Governance/hooks/useGetQueuedTime.tsx | 2 +- src/views/Governance/hooks/useGetVetoedTime.tsx | 2 +- 7 files changed, 32 insertions(+), 5 deletions(-) diff --git a/src/helpers/environment/Environment/Environment.ts b/src/helpers/environment/Environment/Environment.ts index 47622ee086..0d2d8e13e5 100644 --- a/src/helpers/environment/Environment/Environment.ts +++ b/src/helpers/environment/Environment/Environment.ts @@ -137,4 +137,18 @@ export class Environment { }); } }; + public static getArchiveNodeUrls = (networkId: NetworkId) => { + switch (networkId) { + case NetworkId.MAINNET: + return this._get({ + key: `VITE_ETHEREUM_ARCHIVE_NODE_URL`, + fallback: "https://rpc.ankr.com/eth", + }); + default: + return this._get({ + key: `VITE_ETHEREUM_ARCHIVE_NODE_URL`, + fallback: "https://rpc.ankr.com/eth", + }); + } + }; } diff --git a/src/helpers/providers/Providers/Providers.ts b/src/helpers/providers/Providers/Providers.ts index 6255432923..5aa06c51b9 100644 --- a/src/helpers/providers/Providers/Providers.ts +++ b/src/helpers/providers/Providers/Providers.ts @@ -4,6 +4,7 @@ import { NetworkId } from "src/networkDetails"; export class Providers { private static _providerCache = {} as Record; + private static _archiveProviderCache = {} as Record; /** * Returns a provider url for a given network @@ -13,6 +14,11 @@ export class Providers { return url; } + public static getArchiveProviderUrl(networkId: NetworkId) { + const [url] = Environment.getArchiveNodeUrls(networkId); + + return url; + } /** * Returns a static provider for a given network @@ -23,4 +29,11 @@ export class Providers { return this._providerCache[networkId]; } + + public static getArchiveStaticProvider(networkId: NetworkId) { + if (!this._archiveProviderCache[networkId]) + this._archiveProviderCache[networkId] = new StaticJsonRpcProvider(this.getArchiveProviderUrl(networkId)); + + return this._archiveProviderCache[networkId]; + } } diff --git a/src/views/Governance/hooks/useGetCanceledTime.tsx b/src/views/Governance/hooks/useGetCanceledTime.tsx index fdbd6e60e5..4ec487e58b 100644 --- a/src/views/Governance/hooks/useGetCanceledTime.tsx +++ b/src/views/Governance/hooks/useGetCanceledTime.tsx @@ -6,7 +6,7 @@ import { NetworkId } from "src/networkDetails"; import { ProposalCanceledEventObject } from "src/typechain/OlympusGovernorBravo"; export const useGetCanceledTime = ({ proposalId, status }: { proposalId: number; status?: string }) => { - const archiveProvider = Providers.getStaticProvider(NetworkId.MAINNET); + const archiveProvider = Providers.getArchiveStaticProvider(NetworkId.MAINNET); const contract = GOVERNANCE_CONTRACT.getEthersContract(NetworkId.MAINNET); return useQuery( ["getCanceledTime", NetworkId.MAINNET, proposalId, status], diff --git a/src/views/Governance/hooks/useGetExecutedTime.tsx b/src/views/Governance/hooks/useGetExecutedTime.tsx index 245dbb5498..65f30e9be7 100644 --- a/src/views/Governance/hooks/useGetExecutedTime.tsx +++ b/src/views/Governance/hooks/useGetExecutedTime.tsx @@ -6,7 +6,7 @@ import { NetworkId } from "src/networkDetails"; import { ProposalExecutedEventObject } from "src/typechain/OlympusGovernorBravo"; export const useGetExecutedTime = ({ proposalId, status }: { proposalId: number; status?: string }) => { - const archiveProvider = Providers.getStaticProvider(NetworkId.MAINNET); + const archiveProvider = Providers.getArchiveStaticProvider(NetworkId.MAINNET); const contract = GOVERNANCE_CONTRACT.getEthersContract(NetworkId.MAINNET); return useQuery( ["getExecutedTime", NetworkId.MAINNET, proposalId, status], diff --git a/src/views/Governance/hooks/useGetProposals.tsx b/src/views/Governance/hooks/useGetProposals.tsx index d526f0ba3d..c9ac953e79 100644 --- a/src/views/Governance/hooks/useGetProposals.tsx +++ b/src/views/Governance/hooks/useGetProposals.tsx @@ -6,7 +6,7 @@ import { NetworkId } from "src/networkDetails"; import { ProposalCreatedEventObject } from "src/typechain/OlympusGovernorBravo"; export const useGetProposals = () => { - const archiveProvider = Providers.getStaticProvider(NetworkId.MAINNET); + const archiveProvider = Providers.getArchiveStaticProvider(NetworkId.MAINNET); const contract = GOVERNANCE_CONTRACT.getEthersContract(NetworkId.MAINNET); return useQuery( ["getProposals", NetworkId.MAINNET], diff --git a/src/views/Governance/hooks/useGetQueuedTime.tsx b/src/views/Governance/hooks/useGetQueuedTime.tsx index 4de8596b17..e8f222755e 100644 --- a/src/views/Governance/hooks/useGetQueuedTime.tsx +++ b/src/views/Governance/hooks/useGetQueuedTime.tsx @@ -6,7 +6,7 @@ import { ProposalQueuedEventObject } from "src/typechain/OlympusGovernorBravo"; import { useQuery } from "wagmi"; export const useGetQueuedTime = ({ proposalId }: { proposalId: number }) => { - const archiveProvider = Providers.getStaticProvider(NetworkId.MAINNET); + const archiveProvider = Providers.getArchiveStaticProvider(NetworkId.MAINNET); const contract = GOVERNANCE_CONTRACT.getEthersContract(NetworkId.MAINNET); return useQuery( ["getQueuedTime", NetworkId.MAINNET, proposalId], diff --git a/src/views/Governance/hooks/useGetVetoedTime.tsx b/src/views/Governance/hooks/useGetVetoedTime.tsx index 2c114dbb91..efd428fc7f 100644 --- a/src/views/Governance/hooks/useGetVetoedTime.tsx +++ b/src/views/Governance/hooks/useGetVetoedTime.tsx @@ -6,7 +6,7 @@ import { NetworkId } from "src/networkDetails"; import { ProposalVetoedEventObject } from "src/typechain/OlympusGovernorBravo"; export const useGetVetoedTime = ({ proposalId, status }: { proposalId: number; status?: string }) => { - const archiveProvider = Providers.getStaticProvider(NetworkId.MAINNET); + const archiveProvider = Providers.getArchiveStaticProvider(NetworkId.MAINNET); const contract = GOVERNANCE_CONTRACT.getEthersContract(NetworkId.MAINNET); return useQuery( ["getVetoedTime", NetworkId.MAINNET, proposalId, status],