From 06e3a400e4c9f18133203ee6593399f742b10bfe Mon Sep 17 00:00:00 2001 From: Ares-Solidity-Finance Date: Fri, 29 Mar 2024 11:26:21 -0400 Subject: [PATCH] removed Vote and Charts tabs and as much functionality as i could safely remove --- src/components/Header/index.tsx | 7 - src/components/vote/DelegateModal.tsx | 141 ------ src/components/vote/VoteModal.tsx | 167 -------- src/constants/governance.ts | 29 -- src/constants/proposals/index.ts | 1 - .../uniswap_grants_proposal_description.ts | 106 ----- src/pages/App.tsx | 8 +- .../CreateProposal/ProposalActionDetail.tsx | 83 ---- .../CreateProposal/ProposalActionSelector.tsx | 129 ------ src/pages/CreateProposal/ProposalEditor.tsx | 63 --- .../ProposalSubmissionModal.tsx | 57 --- src/pages/CreateProposal/index.tsx | 285 ------------- src/pages/Vote/VotePage.tsx | 360 ---------------- src/pages/Vote/index.tsx | 282 ------------ src/pages/Vote/styled.tsx | 32 -- src/state/governance/hooks.ts | 401 ------------------ 16 files changed, 1 insertion(+), 2150 deletions(-) delete mode 100644 src/components/vote/DelegateModal.tsx delete mode 100644 src/components/vote/VoteModal.tsx delete mode 100644 src/constants/governance.ts delete mode 100644 src/constants/proposals/index.ts delete mode 100644 src/constants/proposals/uniswap_grants_proposal_description.ts delete mode 100644 src/pages/CreateProposal/ProposalActionDetail.tsx delete mode 100644 src/pages/CreateProposal/ProposalActionSelector.tsx delete mode 100644 src/pages/CreateProposal/ProposalEditor.tsx delete mode 100644 src/pages/CreateProposal/ProposalSubmissionModal.tsx delete mode 100644 src/pages/CreateProposal/index.tsx delete mode 100644 src/pages/Vote/VotePage.tsx delete mode 100644 src/pages/Vote/index.tsx delete mode 100644 src/pages/Vote/styled.tsx delete mode 100644 src/state/governance/hooks.ts diff --git a/src/components/Header/index.tsx b/src/components/Header/index.tsx index 6eefc1eb1a4..50ec350f3a6 100644 --- a/src/components/Header/index.tsx +++ b/src/components/Header/index.tsx @@ -346,13 +346,6 @@ export default function Header() { > Pool - - Vote - - - Charts - - diff --git a/src/components/vote/DelegateModal.tsx b/src/components/vote/DelegateModal.tsx deleted file mode 100644 index 02f85517a41..00000000000 --- a/src/components/vote/DelegateModal.tsx +++ /dev/null @@ -1,141 +0,0 @@ -import React, { ReactNode, useState } from 'react' -import { UNI } from '../../constants/tokens' - -import Modal from '../Modal' -import { AutoColumn } from '../Column' -import styled from 'styled-components/macro' -import { RowBetween } from '../Row' -import { TYPE } from '../../theme' -import { X } from 'react-feather' -import { ButtonPrimary } from '../Button' -import { useActiveWeb3React } from '../../hooks/web3' -import AddressInputPanel from '../AddressInputPanel' -import { isAddress } from 'ethers/lib/utils' -import useENS from '../../hooks/useENS' -import { useDelegateCallback } from '../../state/governance/hooks' -import { useTokenBalance } from '../../state/wallet/hooks' -import { LoadingView, SubmittedView } from '../ModalViews' -import { formatCurrencyAmount } from 'utils/formatCurrencyAmount' -import { Trans } from '@lingui/macro' - -const ContentWrapper = styled(AutoColumn)` - width: 100%; - padding: 24px; -` - -const StyledClosed = styled(X)` - :hover { - cursor: pointer; - } -` - -const TextButton = styled.div` - :hover { - cursor: pointer; - } -` - -interface VoteModalProps { - isOpen: boolean - onDismiss: () => void - title: ReactNode -} - -export default function DelegateModal({ isOpen, onDismiss, title }: VoteModalProps) { - const { account, chainId } = useActiveWeb3React() - - // state for delegate input - const [usingDelegate, setUsingDelegate] = useState(false) - const [typed, setTyped] = useState('') - function handleRecipientType(val: string) { - setTyped(val) - } - - // monitor for self delegation or input for third part delegate - // default is self delegation - const activeDelegate = usingDelegate ? typed : account - const { address: parsedAddress } = useENS(activeDelegate) - - // get the number of votes available to delegate - const uniBalance = useTokenBalance(account ?? undefined, chainId ? UNI[chainId] : undefined) - - const delegateCallback = useDelegateCallback() - - // monitor call to help UI loading state - const [hash, setHash] = useState() - const [attempting, setAttempting] = useState(false) - - // wrapper to reset state on modal close - function wrappedOndismiss() { - setHash(undefined) - setAttempting(false) - onDismiss() - } - - async function onDelegate() { - setAttempting(true) - - // if callback not returned properly ignore - if (!delegateCallback) return - - // try delegation and store hash - const hash = await delegateCallback(parsedAddress ?? undefined)?.catch((error) => { - setAttempting(false) - console.log(error) - }) - - if (hash) { - setHash(hash) - } - } - - return ( - - {!attempting && !hash && ( - - - - {title} - - - - Earned UNI tokens represent voting shares in Uniswap governance. - - - You can either vote on each proposal yourself or delegate your votes to a third party. - - {usingDelegate && } - - - {usingDelegate ? Delegate Votes : Self Delegate} - - - setUsingDelegate(!usingDelegate)}> - {usingDelegate ? Remove Delegate : Add Delegate +} - - - - )} - {attempting && !hash && ( - - - - {usingDelegate ? Delegating votes : Unlocking Votes} - - {formatCurrencyAmount(uniBalance, 4)} - - - )} - {hash && ( - - - - Transaction Submitted - - {formatCurrencyAmount(uniBalance, 4)} - - - )} - - ) -} diff --git a/src/components/vote/VoteModal.tsx b/src/components/vote/VoteModal.tsx deleted file mode 100644 index f1f7ab55c70..00000000000 --- a/src/components/vote/VoteModal.tsx +++ /dev/null @@ -1,167 +0,0 @@ -import React, { useState, useContext } from 'react' -import { useActiveWeb3React } from '../../hooks/web3' -import { getExplorerLink, ExplorerDataType } from '../../utils/getExplorerLink' - -import Modal from '../Modal' -import { AutoColumn, ColumnCenter } from '../Column' -import styled, { ThemeContext } from 'styled-components' -import { RowBetween } from '../Row' -import { TYPE, CustomLightSpinner } from '../../theme' -import { X, ArrowUpCircle } from 'react-feather' -import { ButtonPrimary } from '../Button' -import Circle from '../../assets/images/blue-loader.svg' -import { useVoteCallback, useUserVotes } from '../../state/governance/hooks' -import { ExternalLink } from '../../theme/components' -import { formatCurrencyAmount } from 'utils/formatCurrencyAmount' -import { CurrencyAmount, Token } from '@uniswap/sdk-core' -import { Trans } from '@lingui/macro' - -const ContentWrapper = styled(AutoColumn)` - width: 100%; - padding: 24px; -` - -const StyledClosed = styled(X)` - :hover { - cursor: pointer; - } -` - -const ConfirmOrLoadingWrapper = styled.div` - width: 100%; - padding: 24px; -` - -const ConfirmedIcon = styled(ColumnCenter)` - padding: 60px 0; -` - -interface VoteModalProps { - isOpen: boolean - onDismiss: () => void - support: boolean // if user is for or against proposal - proposalId: string | undefined // id for the proposal to vote on -} - -export default function VoteModal({ isOpen, onDismiss, proposalId, support }: VoteModalProps) { - const { chainId } = useActiveWeb3React() - const { - voteCallback, - }: { - voteCallback: (proposalId: string | undefined, support: boolean) => Promise | undefined - } = useVoteCallback() - const availableVotes: CurrencyAmount | undefined = useUserVotes() - - // monitor call to help UI loading state - const [hash, setHash] = useState() - const [attempting, setAttempting] = useState(false) - - // get theme for colors - const theme = useContext(ThemeContext) - - // wrapper to reset state on modal close - function wrappedOndismiss() { - setHash(undefined) - setAttempting(false) - onDismiss() - } - - async function onVote() { - setAttempting(true) - - // if callback not returned properly ignore - if (!voteCallback) return - - // try delegation and store hash - const hash = await voteCallback(proposalId, support)?.catch((error) => { - setAttempting(false) - console.log(error) - }) - - if (hash) { - setHash(hash) - } - } - - return ( - - {!attempting && !hash && ( - - - - - {support ? ( - Vote for proposal {proposalId} - ) : ( - Vote against proposal {proposalId} - )} - - - - - {formatCurrencyAmount(availableVotes, 4)} Votes - - - - {support ? ( - Vote for proposal {proposalId} - ) : ( - Vote against proposal {proposalId} - )} - - - - - )} - {attempting && !hash && ( - - -
- - - - - - - - - Submitting Vote - - - - Confirm this transaction in your wallet - - - - )} - {hash && ( - - -
- - - - - - - - - Transaction Submitted - - - {chainId && ( - - - View transaction on Explorer - - - )} - - - )} - - ) -} diff --git a/src/constants/governance.ts b/src/constants/governance.ts deleted file mode 100644 index e7f3b806377..00000000000 --- a/src/constants/governance.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { GOVERNANCE_ADDRESSES, TIMELOCK_ADDRESS, UNI_ADDRESS } from './addresses' -import { SupportedChainId } from './chains' - -// returns { [address]: `Governance (V${n})`} for each address in GOVERNANCE_ADDRESSES except the current, which gets no version indicator -const governanceContracts = (): Record => - GOVERNANCE_ADDRESSES.reduce( - (acc, addressMap, i) => ({ - ...acc, - [addressMap[SupportedChainId.MAINNET]]: `Governance${ - i === 0 ? '' : ` (V${GOVERNANCE_ADDRESSES.length - 1 - i})` - }`, - }), - {} - ) - -export const COMMON_CONTRACT_NAMES: Record = { - [SupportedChainId.MAINNET]: { - [UNI_ADDRESS[SupportedChainId.MAINNET]]: 'UNI', - [TIMELOCK_ADDRESS[SupportedChainId.MAINNET]]: 'Timelock', - ...governanceContracts(), - }, -} - -export const DEFAULT_AVERAGE_BLOCK_TIME_IN_SECS = 13 - -// Block time here is slightly higher (~1s) than average in order to avoid ongoing proposals past the displayed time -export const AVERAGE_BLOCK_TIME_IN_SECS: { [chainId: number]: number } = { - [1]: DEFAULT_AVERAGE_BLOCK_TIME_IN_SECS, -} diff --git a/src/constants/proposals/index.ts b/src/constants/proposals/index.ts deleted file mode 100644 index 3a1ad3cafda..00000000000 --- a/src/constants/proposals/index.ts +++ /dev/null @@ -1 +0,0 @@ -export const UNISWAP_GRANTS_START_BLOCK = 11473815 diff --git a/src/constants/proposals/uniswap_grants_proposal_description.ts b/src/constants/proposals/uniswap_grants_proposal_description.ts deleted file mode 100644 index 51347d38fd0..00000000000 --- a/src/constants/proposals/uniswap_grants_proposal_description.ts +++ /dev/null @@ -1,106 +0,0 @@ -export const UNISWAP_GRANTS_PROPOSAL_DESCRIPTION = `# Uniswap Grants Program v0.1 - -*co-authored with [Ken Ng](https://twitter.com/nkennethk?lang=en)* - -## Summary: - -**This post outlines a framework for funding Uniswap ecosystem development with grants from the[ UNI Community Treasury](https://uniswap.org/blog/uni/).** The program starts small—sponsoring hackathons, [for example](https://gov.uniswap.org/c/proposal-discussion/5)—but could grow in significance over time (with renewals approved by governance) to fund core protocol development. Grants administration is a subjective process that cannot be easily automated, and thus we propose a nimble committee of 6 members —1 lead and 5 reviewers—to deliver an efficient, predictable process to applicants, such that funding can be administered without having to put each application to a vote. We propose the program start with an initial cap of $750K per quarter and a limit of 2 quarters before renewal—a sum that we feel is appropriate for an MVP relative to the size of the treasury that UNI token holders are entrusted with allocating. - -**Purpose:** - -**The mission of the UGP is to provide valuable resources to help grow the Uniswap ecosystem.** Through public discourse and inbound applications, the community will get first-hand exposure to identify and respond to the most pressing needs of the ecosystem, as well as the ability to support innovative projects expanding the capabilities of Uniswap. By rewarding talent early with developer incentives, bounties, and infrastructure support, UGP acts as a catalyst for growth and helps to maintain Uniswap as a nexus for DeFi on Ethereum. - -**Quarterly Budget:** - -* Max quarterly budget of up to $750k -* Budget and caps to be assessed every six months - -**Grant Allocation Committee:** - -* Of 6 committee members: 1 lead and 5 reviewers -* Each committee has a term of 2 quarters (6 months) after which the program needs to be renewed by UNI governance -* Committee functions as a 4 of 5 multi-sig - -**Committee Members** - -While the goals and priorities of the grant program will be thoroughly discussed and reviewed by the community through public discourse, **the decision to start UGP by operating as a small committee is to ensure that the application and decision process will be efficient and predictable, so applicants have clear objectives and timely decisions.** - -Starting with just six members enables the committee to efficiently fund projects with tight feedback loops and rapid iterations. The purpose of this committee would be to test the hypothesis that the Grants Program can successfully provide value for the UNI ecosystem before scaling the program. - -**We suggest the grants program is administered by a single lead. Here we propose Kenneth Ng, a co-author of this post**. Ken has helped grow the Ethereum Foundation Grants Program over the last two years in establishing high level priorities and adapting for the ecosystems needs. - -**The other 5 committee members should be thought of as “reviewers” — UNI community members who will keep the grants program functional by ensuring Ken is leading effectively and, of course, not absconding with funds.** Part of the reviewers job is to hold the program accountable for success (defined below) and/or return any excess funds to the UNI treasury. Reviewers are not compensated as part of this proposal as we expect their time commitment to be minimal. Compensation for the lead role is discussed below, as we expect this to be a labor intensive role. - -**Program Lead:** [Ken Ng](https://twitter.com/nkennethk?lang=en) (HL Creative Corp) -*Ecosystem Support Program at the Ethereum Foundation* - -1. Reviewer: [Jesse Walden](https://twitter.com/jessewldn) (o/b/o Unofficial LLC dba [Variant Fund](http://twitter.com/variantfund)) -*Founder and Investor at Variant Fund (holds UNI)* - -2. Reviewer: [Monet Supply](https://twitter.com/MonetSupply) -*Risk Analyst at MakerDAO* - -3. Reviewer: [Robert Leshner](https://twitter.com/rleshner) -*Founder and CEO of Compound Finance* - -4. Reviewer: [Kain Warwick](https://twitter.com/kaiynne) -*Founder of Synthetix* - -5. Reviewer: [Ashleigh Schap](https://twitter.com/ashleighschap) -*Growth Lead, Uniswap (Company)* - -## Methodology - -**1.1 Budget** - -This proposal recommends a max cap of $750K per quarter, with the ability to reevaluate biannually at each epoch (two fiscal quarters). While the UGP Grants Committee will be the decision makers around individual grants, respective budgets, roadmaps, and milestones, any top-level changes to UGP including epochs and max cap, will require full community quorum (4% approval). - -The UGP will be funded by the UNI treasury according to the[ release schedule](https://uniswap.org/blog/uni/) set out by the Uniswap team, whereby 43% of the UNI treasury is released over a four-year timeline. In Year 1 this will total to 172,000,000 UNI (~$344M as of writing). - -Taking into consideration the current landscape of ecosystem funding across Ethereum, the community would be hard-pressed to allocate even 5% of Year 1’s treasury. For context Gitcoin CLR Round 7 distributed $725k ($450k in matched) across 857 projects and YTD, Moloch has granted just under $200k but in contrast, the EF has committed to somewhere in the 8 figure range. - -**1.2 Committee Compensation** - -Operating a successful grants program takes considerable time and effort. Take, for instance, the EF Ecosystem Support Program, which has awarded grants since 2018, consists of an internal team at the Foundation as well as an ever increasing roster of community advisors in order to keep expanding and adapting to best serve the needs of the Ethereum ecosystem. While the structure of the allocation committee has six members, we propose that only the lead will be remunerated for their work in establishing the initial processes, vetting applications, and managing the program overall as this role is expected to be time consuming if the program is to be succesful. We propose that the other committee members be unpaid UNI ecosystem stakeholders who have an interest in seeing the protocol ecosystem continue to operate and grow. - -**We propose the lead be compensated 25 UNI/hr (approximately $100 USD at time of this writing) capped at 30 hours/week. This compensation, along with the quarterly budget, will be allocated to the UGP multisig from the UNI treasury**. In keeping with the committee’s commitment to the community, hours and duties will be logged publicly and transparently . - -**2.1 Priorities** - -Initially, the program aims to start narrow in scope, funding peripheral ecosystem initiatives, such as targeted bounties, hackathon sponsorships, and other low-stakes means of building out the Uniswap developer ecosystem. Over time, if the program proves effective, the grant allocations can grow in scope to include, for example, improved frontends, trading interfaces, and eventually, core protocol development. - -![|624x199](upload://vNP0APCOjiwQMurCmYI47cTLklc.png) - -With the initial priorities in mind, some effective measures for quick successes might look like: - -* Total number of projects funded -* Quarterly increase in applications -* Project engagement post-event/funding -* Overall community engagement/sentiment - -**2.2 Timeline** - -In keeping with the fast pace of the UNI ecosystem, we organize time in epochs, or two calendar quarters. Each epoch will see two funding rounds, one per quarter, after which the community can review and create proposals to improve or revamp the program as they deem fit. - -**![|624x245](upload://n56TSh5X3mt4TSqVVMFhbnZM0eM.png)** - -**Rolling Wave 1 & 2 Applications** - -* Starting in Q1 2021, UGP will start accepting applications for events looking for support in the form of bounties or prizes that in parallel can be proactively sourced. During these first two waves of funding projects, the allocation committee lead can begin laying out guardrails for continued funding - -* Considering the immediate feedback loops for the first epoch, we expect these allocation decisions to be discussed and reviewed by the larger community. Should this not be of value, the community can submit a formal governance proposal to change any piece of UGP that was not effective - -**Wave 3 & Beyond** - -* Beginning with Wave 3, there should have been enough time with impactful projects funded to be considered for follow-on funding, should it make sense. In the same vein, projects within scope will be expanded to also include those working on integrations and and other key components. - -* Beyond the second epoch, as the community helps to review and help adapt UGP to be most effective, the scope will continue to grow in order to accommodate the state of the ecosystem including that of core protocol improvements - -## Conclusion: - -**If this proposal is successfully approved, UGP will start accepting applications on a rolling basis beginning at the start of 2021.** With the phases and priorities laid out above, UGP will aim to make a significant impact by catalyzing growth and innovation in the UNI ecosystem. - -**This program is meant to be the simplest possible MVP of a Uniswap Ecosystem Grants initiative.** While the multi-sig committee comes with trust assumptions about the members, our hope is the community will approve this limited engagement to get the ball rolling in an efficient structure. **After the first epoch (2 fiscal quarters) the burden of proof will be on UGP to show empirical evidence that the program is worth continuing in its existing form and will submit to governance to renew treasury funding.** - -If this program proves successful, we hope it will inspire others to follow suit and create their own funding committees for allocating treasury capital—ideally with different specializations. -` diff --git a/src/pages/App.tsx b/src/pages/App.tsx index 526cb4b841f..07c5a072eed 100644 --- a/src/pages/App.tsx +++ b/src/pages/App.tsx @@ -23,13 +23,10 @@ import RemoveLiquidity from './RemoveLiquidity' import RemoveLiquidityV3 from './RemoveLiquidity/V3' import Swap from './Swap' import { OpenClaimAddressModalAndRedirectToSwap, RedirectPathToSwapOnly, RedirectToSwap } from './Swap/redirects' -import Vote from './Vote' -import VotePage from './Vote/VotePage' import { RedirectDuplicateTokenIdsV2 } from './AddLiquidityV2/redirects' import { PositionPage } from './Pool/PositionPage' import AddLiquidity from './AddLiquidity' import ApeModeQueryParamReader from 'hooks/useApeModeQueryParamReader' -import CreateProposal from './CreateProposal' const AppWrapper = styled.div` display: flex; @@ -86,8 +83,6 @@ export default function App() { - - @@ -121,8 +116,7 @@ export default function App() { - - + diff --git a/src/pages/CreateProposal/ProposalActionDetail.tsx b/src/pages/CreateProposal/ProposalActionDetail.tsx deleted file mode 100644 index 7262899afd6..00000000000 --- a/src/pages/CreateProposal/ProposalActionDetail.tsx +++ /dev/null @@ -1,83 +0,0 @@ -import React from 'react' -import AddressInputPanel from 'components/AddressInputPanel' -import CurrencyInputPanel from 'components/CurrencyInputPanel' -import styled from 'styled-components' -import { ProposalAction } from './ProposalActionSelector' -import { Currency } from '@uniswap/sdk-core' - -enum ProposalActionDetailField { - ADDRESS, - CURRENCY, -} - -const ProposalActionDetailContainer = styled.div` - margin-top: 10px; - display: grid; - grid-template-columns: repeat(1, 1fr); - grid-gap: 10px; -` - -export const ProposalActionDetail = ({ - className, - proposalAction, - currency, - amount, - toAddress, - onCurrencySelect, - onAmountInput, - onToAddressInput, -}: { - className?: string - proposalAction: ProposalAction - currency: Currency | undefined - amount: string - toAddress: string - onCurrencySelect: (currency: Currency) => void - onAmountInput: (amount: string) => void - onToAddressInput: (address: string) => void -}) => { - const proposalActionsData = { - [ProposalAction.TRANSFER_TOKEN]: [ - { - type: ProposalActionDetailField.ADDRESS, - label: 'To', - }, - { - type: ProposalActionDetailField.CURRENCY, - }, - ], - [ProposalAction.APPROVE_TOKEN]: [ - { - type: ProposalActionDetailField.ADDRESS, - label: 'To', - }, - { - type: ProposalActionDetailField.CURRENCY, - }, - ], - } - - return ( - - {proposalActionsData[proposalAction].map((field, i) => - field.type === ProposalActionDetailField.ADDRESS ? ( - - ) : field.type === ProposalActionDetailField.CURRENCY ? ( - onAmountInput(amount)} - onCurrencySelect={(currency: Currency) => onCurrencySelect(currency)} - showMaxButton={false} - showCommonBases={false} - showCurrencyAmount={false} - disableNonToken={true} - hideBalance={true} - id="currency-input" - /> - ) : null - )} - - ) -} diff --git a/src/pages/CreateProposal/ProposalActionSelector.tsx b/src/pages/CreateProposal/ProposalActionSelector.tsx deleted file mode 100644 index 2dba80e6ab8..00000000000 --- a/src/pages/CreateProposal/ProposalActionSelector.tsx +++ /dev/null @@ -1,129 +0,0 @@ -import React, { useCallback } from 'react' -import styled from 'styled-components' -import { Text } from 'rebass' -import { CloseIcon } from 'theme' -import { Trans } from '@lingui/macro' -import Column from 'components/Column' -import Modal from 'components/Modal' -import { RowBetween } from 'components/Row' -import { MenuItem, PaddedColumn, Separator } from 'components/SearchModal/styleds' -import { ButtonDropdown } from 'components/Button' - -export enum ProposalAction { - TRANSFER_TOKEN = 'Transfer Token', - APPROVE_TOKEN = 'Approve Token', -} - -interface ProposalActionSelectorModalProps { - isOpen: boolean - onDismiss: () => void - onProposalActionSelect: (proposalAction: ProposalAction) => void -} - -const ContentWrapper = styled(Column)` - width: 100%; - flex: 1 1; - position: relative; -` -const ActionSelectorHeader = styled.div` - font-size: 14px; - font-weight: 500; - color: ${({ theme }) => theme.text2}; -` - -const ActionDropdown = styled(ButtonDropdown)` - padding: 0px; - background-color: transparent; - color: ${({ theme }) => theme.text1} - font-size: 1.25rem; - - :hover, - :active, - :focus { - outline: 0px; - box-shadow: none; - background-color: transparent; - } -` - -const ProposalActionSelectorFlex = styled.div` - margin-top: 10px; - display: flex; - flex-flow: column nowrap; - border-radius: 20px; - border: 1px solid ${({ theme }) => theme.bg2}; - background-color: ${({ theme }) => theme.bg1}; -` - -const ProposalActionSelectorContainer = styled.div` - flex: 1; - padding: 1rem; - display: grid; - grid-auto-rows: auto; - grid-row-gap: 10px; -` - -export const ProposalActionSelector = ({ - className, - onClick, - proposalAction, -}: { - className?: string - onClick: () => void - proposalAction: ProposalAction -}) => { - return ( - - - - Proposed Action - - {proposalAction} - - - ) -} - -export function ProposalActionSelectorModal({ - isOpen, - onDismiss, - onProposalActionSelect, -}: ProposalActionSelectorModalProps) { - const handleProposalActionSelect = useCallback( - (proposalAction: ProposalAction) => { - onProposalActionSelect(proposalAction) - onDismiss() - }, - [onDismiss, onProposalActionSelect] - ) - - return ( - - - - - - Select an action - - - - - - handleProposalActionSelect(ProposalAction.TRANSFER_TOKEN)}> - - - Transfer Token - - - - handleProposalActionSelect(ProposalAction.APPROVE_TOKEN)}> - - - Approve Token - - - - - - ) -} diff --git a/src/pages/CreateProposal/ProposalEditor.tsx b/src/pages/CreateProposal/ProposalEditor.tsx deleted file mode 100644 index 71744dfff16..00000000000 --- a/src/pages/CreateProposal/ProposalEditor.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import React, { memo } from 'react' -import styled from 'styled-components' -import { Text } from 'rebass' -import { ResizingTextArea, TextInput } from 'components/TextInput' -import { t, Trans } from '@lingui/macro' - -const ProposalEditorHeader = styled(Text)` - font-size: 14px; - font-weight: 500; - color: ${({ theme }) => theme.text2}; -` - -const ProposalTitle = memo(styled(TextInput)` - margin-top: 10.5px; - margin-bottom: 7.5px; -`) - -const ProposalEditorContainer = styled.div` - margin-top: 10px; - padding: 0.75rem 1rem 0.75rem 1rem; - border-radius: 20px; - border: 1px solid ${({ theme }) => theme.bg2}; - background-color: ${({ theme }) => theme.bg1}; -` - -export const ProposalEditor = ({ - className, - title, - body, - onTitleInput, - onBodyInput, -}: { - className?: string - title: string - body: string - onTitleInput: (title: string) => void - onBodyInput: (body: string) => void -}) => { - const bodyPlaceholder = `## Summary - -Insert your summary here - -## Methodology - -Insert your methodology here - -## Conclusion - -Insert your conclusion here - - ` - - return ( - - - Proposal - - -
- -
- ) -} diff --git a/src/pages/CreateProposal/ProposalSubmissionModal.tsx b/src/pages/CreateProposal/ProposalSubmissionModal.tsx deleted file mode 100644 index b727234cedf..00000000000 --- a/src/pages/CreateProposal/ProposalSubmissionModal.tsx +++ /dev/null @@ -1,57 +0,0 @@ -import React, { useContext } from 'react' -import { ThemeContext } from 'styled-components' -import { Text } from 'rebass' -import { ExternalLink, TYPE } from 'theme' -import { ButtonPrimary } from 'components/Button' -import { AutoColumn } from 'components/Column' -import Modal from 'components/Modal' -import { LoadingView, SubmittedView } from 'components/ModalViews' -import { ExplorerDataType, getExplorerLink } from 'utils/getExplorerLink' -import { Link } from 'react-router-dom' -import { Trans } from '@lingui/macro' - -export const ProposalSubmissionModal = ({ - isOpen, - hash, - onDismiss, -}: { - isOpen: boolean - hash: string | undefined - onDismiss: () => void -}) => { - const theme = useContext(ThemeContext) - - return ( - - {!hash ? ( - - - - Submitting Proposal - - - - ) : ( - - - - Proposal Submitted - - {hash && ( - - - View on Etherscan - - - )} - - - Return - - - - - )} - - ) -} diff --git a/src/pages/CreateProposal/index.tsx b/src/pages/CreateProposal/index.tsx deleted file mode 100644 index 4591ab4e2ef..00000000000 --- a/src/pages/CreateProposal/index.tsx +++ /dev/null @@ -1,285 +0,0 @@ -import React, { useCallback, useMemo, useState } from 'react' -import JSBI from 'jsbi' -import styled from 'styled-components' -import { utils } from 'ethers' -import { ExternalLink, TYPE } from 'theme' -import { Currency, CurrencyAmount, Token } from '@uniswap/sdk-core' -import { UNI } from '../../constants/tokens' -import AppBody from '../AppBody' -import { CreateProposalTabs } from '../../components/NavigationTabs' -import { ButtonError } from 'components/Button' -import { AutoColumn } from 'components/Column' -import { BlueCard } from 'components/Card' -import { Wrapper } from 'pages/Pool/styleds' -import { ProposalAction, ProposalActionSelector, ProposalActionSelectorModal } from './ProposalActionSelector' -import { ProposalEditor } from './ProposalEditor' -import { ProposalActionDetail } from './ProposalActionDetail' -import { ProposalSubmissionModal } from './ProposalSubmissionModal' -import { useActiveWeb3React } from 'hooks/web3' -import { - CreateProposalData, - ProposalState, - useCreateProposalCallback, - useLatestProposalId, - useProposalData, - useProposalThreshold, - useUserVotes, -} from 'state/governance/hooks' -import { Trans } from '@lingui/macro' -import { tryParseAmount } from 'state/swap/hooks' -import { getAddress } from '@ethersproject/address' - -const CreateProposalButton = ({ - proposalThreshold, - hasActiveOrPendingProposal, - hasEnoughVote, - isFormInvalid, - handleCreateProposal, -}: { - proposalThreshold?: CurrencyAmount - hasActiveOrPendingProposal: boolean - hasEnoughVote: boolean - isFormInvalid: boolean - handleCreateProposal: () => void -}) => { - const formattedProposalThreshold = proposalThreshold - ? JSBI.divide( - proposalThreshold.quotient, - JSBI.exponentiate(JSBI.BigInt(10), JSBI.BigInt(proposalThreshold.currency.decimals)) - ).toLocaleString() - : undefined - - return ( - - {hasActiveOrPendingProposal ? ( - You already have an active or pending proposal - ) : !hasEnoughVote ? ( - <> - {formattedProposalThreshold ? ( - You must have {formattedProposalThreshold} votes to submit a proposal - ) : ( - You don't have enough votes to submit a proposal - )} - - ) : ( - Create Proposal - )} - - ) -} - -const CreateProposalWrapper = styled(Wrapper)` - display: flex; - flex-flow: column wrap; -` - -const AutonomousProposalCTA = styled.div` - text-align: center; - margin-top: 10px; -` - -export default function CreateProposal() { - const { account, chainId } = useActiveWeb3React() - - const latestProposalId = useLatestProposalId(account ?? '0x0000000000000000000000000000000000000000') ?? '0' - const latestProposalData = useProposalData(0, latestProposalId) - const availableVotes: CurrencyAmount | undefined = useUserVotes() - const proposalThreshold: CurrencyAmount | undefined = useProposalThreshold() - - const [modalOpen, setModalOpen] = useState(false) - const [hash, setHash] = useState() - const [attempting, setAttempting] = useState(false) - const [proposalAction, setProposalAction] = useState(ProposalAction.TRANSFER_TOKEN) - const [toAddressValue, setToAddressValue] = useState('') - const [currencyValue, setCurrencyValue] = useState(UNI[chainId ?? 1]) - const [amountValue, setAmountValue] = useState('') - const [titleValue, setTitleValue] = useState('') - const [bodyValue, setBodyValue] = useState('') - - const handleActionSelectorClick = useCallback(() => { - setModalOpen(true) - }, [setModalOpen]) - - const handleActionChange = useCallback( - (proposalAction: ProposalAction) => { - setProposalAction(proposalAction) - }, - [setProposalAction] - ) - - const handleDismissActionSelector = useCallback(() => { - setModalOpen(false) - }, [setModalOpen]) - - const handleDismissSubmissionModal = useCallback(() => { - setHash(undefined) - setAttempting(false) - }, [setHash, setAttempting]) - - const handleToAddressInput = useCallback( - (toAddress: string) => { - setToAddressValue(toAddress) - }, - [setToAddressValue] - ) - - const handleCurrencySelect = useCallback( - (currency: Currency) => { - setCurrencyValue(currency) - }, - [setCurrencyValue] - ) - - const handleAmountInput = useCallback( - (amount: string) => { - setAmountValue(amount) - }, - [setAmountValue] - ) - - const handleTitleInput = useCallback( - (title: string) => { - setTitleValue(title) - }, - [setTitleValue] - ) - - const handleBodyInput = useCallback( - (body: string) => { - setBodyValue(body) - }, - [setBodyValue] - ) - - const isFormInvalid = useMemo( - () => - Boolean( - !proposalAction || - !utils.isAddress(toAddressValue) || - !currencyValue?.isToken || - amountValue === '' || - titleValue === '' || - bodyValue === '' - ), - [proposalAction, toAddressValue, currencyValue, amountValue, titleValue, bodyValue] - ) - - const hasEnoughVote = Boolean( - availableVotes && proposalThreshold && JSBI.greaterThanOrEqual(availableVotes.quotient, proposalThreshold.quotient) - ) - - const createProposalCallback = useCreateProposalCallback() - - const handleCreateProposal = async () => { - setAttempting(true) - - const createProposalData: CreateProposalData = {} as CreateProposalData - - if (!createProposalCallback || !proposalAction || !currencyValue.isToken) return - - const tokenAmount = tryParseAmount(amountValue, currencyValue) - if (!tokenAmount) return - - createProposalData.targets = [currencyValue.address] - createProposalData.values = ['0'] - createProposalData.description = `# ${titleValue} - -${bodyValue} -` - - let types: string[][] - let values: string[][] - switch (proposalAction) { - case ProposalAction.TRANSFER_TOKEN: { - types = [['address', 'uint256']] - values = [[getAddress(toAddressValue), tokenAmount.quotient.toString()]] - createProposalData.signatures = [`transfer(${types[0].join(',')})`] - break - } - - case ProposalAction.APPROVE_TOKEN: { - types = [['address', 'uint256']] - values = [[getAddress(toAddressValue), tokenAmount.quotient.toString()]] - createProposalData.signatures = [`approve(${types[0].join(',')})`] - break - } - } - - createProposalData.calldatas = [] - for (let i = 0; i < createProposalData.signatures.length; i++) { - createProposalData.calldatas[i] = utils.defaultAbiCoder.encode(types[i], values[i]) - } - - const hash = await createProposalCallback(createProposalData ?? undefined)?.catch(() => { - setAttempting(false) - }) - - if (hash) setHash(hash) - } - - return ( - - - - - - - - Tip: Select an action and describe your proposal for the community. The proposal cannot - be modified after submission, so please verify all information before submitting. The voting period will - begin immediately and last for 7 days. To propose a custom action,{' '} - - read the docs - - . - - - - - - - - - - {!hasEnoughVote ? ( - - Don’t have 2.5M votes? Anyone can create an autonomous proposal using{' '} - fish.vote - - ) : null} - - handleActionChange(proposalAction)} - /> - - - ) -} diff --git a/src/pages/Vote/VotePage.tsx b/src/pages/Vote/VotePage.tsx deleted file mode 100644 index 20e76bee36b..00000000000 --- a/src/pages/Vote/VotePage.tsx +++ /dev/null @@ -1,360 +0,0 @@ -import { CurrencyAmount, Token } from '@uniswap/sdk-core' -import { BigNumber } from 'ethers' -import useCurrentBlockTimestamp from 'hooks/useCurrentBlockTimestamp' -import JSBI from 'jsbi' -import { DateTime } from 'luxon' -import React, { useState } from 'react' -import { ArrowLeft } from 'react-feather' -import ReactMarkdown from 'react-markdown' - -import { RouteComponentProps } from 'react-router-dom' -import styled from 'styled-components/macro' -import { ButtonPrimary } from '../../components/Button' -import { GreyCard } from '../../components/Card' -import { AutoColumn } from '../../components/Column' -import { CardSection, DataCard } from '../../components/earn/styled' -import { RowBetween, RowFixed } from '../../components/Row' -import { SwitchLocaleLink } from '../../components/SwitchLocaleLink' -import DelegateModal from '../../components/vote/DelegateModal' -import VoteModal from '../../components/vote/VoteModal' -import { - AVERAGE_BLOCK_TIME_IN_SECS, - COMMON_CONTRACT_NAMES, - DEFAULT_AVERAGE_BLOCK_TIME_IN_SECS, -} from '../../constants/governance' -import { ZERO_ADDRESS } from '../../constants/misc' -import { UNI } from '../../constants/tokens' -import { useActiveWeb3React } from '../../hooks/web3' -import { ApplicationModal } from '../../state/application/actions' -import { useBlockNumber, useModalOpen, useToggleDelegateModal, useToggleVoteModal } from '../../state/application/hooks' -import { - ProposalData, - ProposalState, - useProposalData, - useUserDelegatee, - useUserVotesAsOfBlock, -} from '../../state/governance/hooks' -import { useTokenBalance } from '../../state/wallet/hooks' -import { ExternalLink, StyledInternalLink, TYPE } from '../../theme' -import { isAddress } from '../../utils' -import { ExplorerDataType, getExplorerLink } from '../../utils/getExplorerLink' -import { ProposalStatus } from './styled' -import { t, Trans } from '@lingui/macro' - -const PageWrapper = styled(AutoColumn)` - width: 100%; -` - -const ProposalInfo = styled(AutoColumn)` - border: 1px solid ${({ theme }) => theme.bg4}; - border-radius: 12px; - padding: 1.5rem; - position: relative; - max-width: 640px; - width: 100%; -` -const ArrowWrapper = styled(StyledInternalLink)` - display: flex; - align-items: center; - gap: 8px; - height: 24px; - color: ${({ theme }) => theme.text1}; - - a { - color: ${({ theme }) => theme.text1}; - text-decoration: none; - } - :hover { - text-decoration: none; - } -` -const CardWrapper = styled.div` - display: grid; - grid-template-columns: 1fr 1fr; - gap: 12px; - width: 100%; -` - -const StyledDataCard = styled(DataCard)` - width: 100%; - background: none; - background-color: ${({ theme }) => theme.bg1}; - height: fit-content; - z-index: 2; -` - -const ProgressWrapper = styled.div` - width: 100%; - margin-top: 1rem; - height: 4px; - border-radius: 4px; - background-color: ${({ theme }) => theme.bg3}; - position: relative; -` - -const Progress = styled.div<{ status: 'for' | 'against'; percentageString?: string }>` - height: 4px; - border-radius: 4px; - background-color: ${({ theme, status }) => (status === 'for' ? theme.green1 : theme.red1)}; - width: ${({ percentageString }) => percentageString}; -` - -const MarkDownWrapper = styled.div` - max-width: 640px; - overflow: hidden; -` - -const WrapSmall = styled(RowBetween)` - ${({ theme }) => theme.mediaWidth.upToSmall` - align-items: flex-start; - flex-direction: column; - `}; -` - -const DetailText = styled.div` - word-break: break-all; -` - -const ProposerAddressLink = styled(ExternalLink)` - word-break: break-all; -` - -export default function VotePage({ - match: { - params: { governorIndex, id }, - }, -}: RouteComponentProps<{ governorIndex: string; id: string }>) { - const { chainId, account } = useActiveWeb3React() - - // get data for this specific proposal - const proposalData: ProposalData | undefined = useProposalData(Number.parseInt(governorIndex), id) - - // update support based on button interactions - const [support, setSupport] = useState(true) - - // modal for casting votes - const showVoteModal = useModalOpen(ApplicationModal.VOTE) - const toggleVoteModal = useToggleVoteModal() - - // toggle for showing delegation modal - const showDelegateModal = useModalOpen(ApplicationModal.DELEGATE) - const toggleDelegateModal = useToggleDelegateModal() - - // get and format date from data - const currentTimestamp = useCurrentBlockTimestamp() - const currentBlock = useBlockNumber() - const endDate: DateTime | undefined = - proposalData && currentTimestamp && currentBlock - ? DateTime.fromSeconds( - currentTimestamp - .add( - BigNumber.from( - (chainId && AVERAGE_BLOCK_TIME_IN_SECS[chainId]) ?? DEFAULT_AVERAGE_BLOCK_TIME_IN_SECS - ).mul(BigNumber.from(proposalData.endBlock - currentBlock)) - ) - .toNumber() - ) - : undefined - const now: DateTime = DateTime.local() - - // get total votes and format percentages for UI - const totalVotes: number | undefined = proposalData ? proposalData.forCount + proposalData.againstCount : undefined - const forPercentage: string = t`${ - proposalData && totalVotes ? ((proposalData.forCount * 100) / totalVotes).toFixed(0) : '0' - } %` - const againstPercentage: string = t`${ - proposalData && totalVotes ? ((proposalData.againstCount * 100) / totalVotes).toFixed(0) : '0' - } %` - - // only count available votes as of the proposal start block - const availableVotes: CurrencyAmount | undefined = useUserVotesAsOfBlock(proposalData?.startBlock ?? undefined) - - // only show voting if user has > 0 votes at proposal start block and proposal is active, - const showVotingButtons = - availableVotes && - JSBI.greaterThan(availableVotes.quotient, JSBI.BigInt(0)) && - proposalData && - proposalData.status === ProposalState.Active - - const uniBalance: CurrencyAmount | undefined = useTokenBalance( - account ?? undefined, - chainId ? UNI[chainId] : undefined - ) - const userDelegatee: string | undefined = useUserDelegatee() - - // in blurb link to home page if they are able to unlock - const showLinkForUnlock = Boolean( - uniBalance && JSBI.notEqual(uniBalance.quotient, JSBI.BigInt(0)) && userDelegatee === ZERO_ADDRESS - ) - - // show links in propsoal details if content is an address - // if content is contract with common name, replace address with common name - const linkIfAddress = (content: string) => { - if (isAddress(content) && chainId) { - const commonName = COMMON_CONTRACT_NAMES[chainId]?.[content] ?? content - return ( - {commonName} - ) - } - return {content} - } - - return ( - <> - - - Unlock Votes} /> - - - - - All Proposals - - - {proposalData && ( - {ProposalState[proposalData.status]} - )} - - - {proposalData?.title} - - - {endDate && endDate < now ? ( - Voting ended {endDate && endDate.toLocaleString(DateTime.DATETIME_FULL)} - ) : proposalData ? ( - Voting ends approximately {endDate && endDate.toLocaleString(DateTime.DATETIME_FULL)} - ) : ( - '' - )} - - - {proposalData && proposalData.status === ProposalState.Active && !showVotingButtons && ( - - - - Only UNI votes that were self delegated or delegated to another address before block{' '} - {proposalData.startBlock} are eligible for voting.{' '} - - {showLinkForUnlock && ( - - - Unlock voting to prepare for the next - proposal. - - - )} - - - )} - - {showVotingButtons ? ( - - { - setSupport(true) - toggleVoteModal() - }} - > - Vote For - - { - setSupport(false) - toggleVoteModal() - }} - > - Vote Against - - - ) : ( - '' - )} - - - - - - - For - - - {proposalData?.forCount?.toLocaleString(undefined, { maximumFractionDigits: 0 })} - - - - - - - - - - - - - - Against - - - {proposalData?.againstCount?.toLocaleString(undefined, { maximumFractionDigits: 0 })} - - - - - - - - - - - - Details - - {proposalData?.details?.map((d, i) => { - return ( - - {i + 1}: {linkIfAddress(d.target)}.{d.functionSig}( - {d.callData.split(',').map((content, i) => { - return ( - - {linkIfAddress(content)} - {d.callData.split(',').length - 1 === i ? '' : ','} - - ) - })} - ) - - ) - })} - - - - Description - - - - - - - - Proposer - - - - - - - - - - ) -} diff --git a/src/pages/Vote/index.tsx b/src/pages/Vote/index.tsx deleted file mode 100644 index 8d2a4251e35..00000000000 --- a/src/pages/Vote/index.tsx +++ /dev/null @@ -1,282 +0,0 @@ -import React from 'react' -import { AutoColumn } from '../../components/Column' -import styled from 'styled-components/macro' -import { SwitchLocaleLink } from '../../components/SwitchLocaleLink' -import { UNI } from '../../constants/tokens' -import { ExternalLink, TYPE } from '../../theme' -import { AutoRow, RowBetween, RowFixed } from '../../components/Row' -import { Link } from 'react-router-dom' -import { getExplorerLink, ExplorerDataType } from '../../utils/getExplorerLink' -import { ProposalStatus } from './styled' -import { ButtonPrimary } from '../../components/Button' -import { Button } from 'rebass/styled-components' -import { darken } from 'polished' -import { CardBGImage, CardNoise, CardSection, DataCard } from '../../components/earn/styled' -import { - ProposalData, - ProposalState, - useAllProposalData, - useUserDelegatee, - useUserVotes, -} from '../../state/governance/hooks' -import DelegateModal from '../../components/vote/DelegateModal' -import { useTokenBalance } from '../../state/wallet/hooks' -import { useActiveWeb3React } from '../../hooks/web3' -import { ZERO_ADDRESS } from '../../constants/misc' -import { Token, CurrencyAmount } from '@uniswap/sdk-core' -import JSBI from 'jsbi' -import { shortenAddress } from '../../utils' -import Loader from '../../components/Loader' -import FormattedCurrencyAmount from '../../components/FormattedCurrencyAmount' -import { useModalOpen, useToggleDelegateModal } from '../../state/application/hooks' -import { ApplicationModal } from '../../state/application/actions' -import { Trans } from '@lingui/macro' - -const PageWrapper = styled(AutoColumn)`` - -const TopSection = styled(AutoColumn)` - max-width: 640px; - width: 100%; -` - -const Proposal = styled(Button)` - padding: 0.75rem 1rem; - width: 100%; - margin-top: 1rem; - border-radius: 12px; - display: grid; - grid-template-columns: 48px 1fr 120px; - align-items: center; - text-align: left; - outline: none; - cursor: pointer; - color: ${({ theme }) => theme.text1}; - text-decoration: none; - background-color: ${({ theme }) => theme.bg1}; - &:focus { - background-color: ${({ theme }) => darken(0.05, theme.bg1)}; - } - &:hover { - background-color: ${({ theme }) => darken(0.05, theme.bg1)}; - } -` - -const ProposalNumber = styled.span` - opacity: 0.6; -` - -const ProposalTitle = styled.span` - font-weight: 600; -` - -const VoteCard = styled(DataCard)` - background: radial-gradient(76.02% 75.41% at 1.84% 0%, #27ae60 0%, #000000 100%); - overflow: hidden; -` - -const WrapSmall = styled(RowBetween)` - margin-bottom: 1rem; - ${({ theme }) => theme.mediaWidth.upToSmall` - flex-wrap: wrap; - `}; -` - -const TextButton = styled(TYPE.main)` - color: ${({ theme }) => theme.primary1}; - :hover { - cursor: pointer; - text-decoration: underline; - } -` - -const AddressButton = styled.div` - border: 1px solid ${({ theme }) => theme.bg3}; - padding: 2px 4px; - border-radius: 8px; - display: flex; - justify-content: center; - align-items: center; -` - -const StyledExternalLink = styled(ExternalLink)` - color: ${({ theme }) => theme.text1}; -` - -const EmptyProposals = styled.div` - border: 1px solid ${({ theme }) => theme.text4}; - padding: 16px 12px; - border-radius: 12px; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; -` - -export default function Vote() { - const { account, chainId } = useActiveWeb3React() - - // toggle for showing delegation modal - const showDelegateModal = useModalOpen(ApplicationModal.DELEGATE) - const toggleDelegateModal = useToggleDelegateModal() - - // get data to list all proposals - const allProposals: ProposalData[] = useAllProposalData() - - // user data - const availableVotes: CurrencyAmount | undefined = useUserVotes() - const uniBalance: CurrencyAmount | undefined = useTokenBalance( - account ?? undefined, - chainId ? UNI[chainId] : undefined - ) - const userDelegatee: string | undefined = useUserDelegatee() - - // show delegation option if they have have a balance, but have not delegated - const showUnlockVoting = Boolean( - uniBalance && JSBI.notEqual(uniBalance.quotient, JSBI.BigInt(0)) && userDelegatee === ZERO_ADDRESS - ) - - const maxGovernorIndex = allProposals.reduce((max, p) => Math.max(p.governorIndex, max), 0) - - return ( - <> - - Unlock Votes : Update Delegation} - /> - - - - - - - - - Uniswap Governance - - - - - - UNI tokens represent voting shares in Uniswap governance. You can vote on each proposal yourself - or delegate your votes to a third party. - - - - - - Read more about Uniswap governance - - - - - - - - - - - - Proposals - - - {(!allProposals || allProposals.length === 0) && !availableVotes && } - {showUnlockVoting ? ( - - Unlock Voting - - ) : availableVotes && JSBI.notEqual(JSBI.BigInt(0), availableVotes?.quotient) ? ( - - - Votes - - - ) : uniBalance && - userDelegatee && - userDelegatee !== ZERO_ADDRESS && - JSBI.notEqual(JSBI.BigInt(0), uniBalance?.quotient) ? ( - - - Votes - - - ) : ( - '' - )} - - Create Proposal - - - - {!showUnlockVoting && ( - -
- {userDelegatee && userDelegatee !== ZERO_ADDRESS ? ( - - - Delegated to: - - - - {userDelegatee === account ? Self : shortenAddress(userDelegatee)} - - - (edit) - - - - ) : ( - '' - )} - - )} - {allProposals?.length === 0 && ( - - - No proposals found. - - - - Proposals submitted by community members will appear here. - - - - )} - {allProposals?.reverse()?.map((p: ProposalData) => { - return ( - - - {maxGovernorIndex - p.governorIndex}.{p.id} - - {p.title} - {ProposalState[p.status]} - - ) - })} - - - A minimum threshold of 0.25% of the total UNI supply is required to submit proposals - - - - - ) -} diff --git a/src/pages/Vote/styled.tsx b/src/pages/Vote/styled.tsx deleted file mode 100644 index 4e8240f2742..00000000000 --- a/src/pages/Vote/styled.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import styled, { DefaultTheme } from 'styled-components' -import { ProposalState } from '../../state/governance/hooks' - -const handleColorType = (status: ProposalState, theme: DefaultTheme) => { - switch (status) { - case ProposalState.Pending: - case ProposalState.Active: - return theme.blue1 - case ProposalState.Succeeded: - case ProposalState.Executed: - return theme.green1 - case ProposalState.Defeated: - return theme.red1 - case ProposalState.Queued: - case ProposalState.Canceled: - case ProposalState.Expired: - default: - return theme.text3 - } -} - -export const ProposalStatus = styled.span<{ status: ProposalState }>` - font-size: 0.825rem; - font-weight: 600; - padding: 0.5rem; - border-radius: 8px; - color: ${({ status, theme }) => handleColorType(status, theme)}; - border: 1px solid ${({ status, theme }) => handleColorType(status, theme)}; - width: fit-content; - justify-self: flex-end; - text-transform: uppercase; -` diff --git a/src/state/governance/hooks.ts b/src/state/governance/hooks.ts deleted file mode 100644 index 621087c6f93..00000000000 --- a/src/state/governance/hooks.ts +++ /dev/null @@ -1,401 +0,0 @@ -import { TransactionResponse } from '@ethersproject/providers' -import { abi as GOV_ABI } from '@uniswap/governance/build/GovernorAlpha.json' -import { CurrencyAmount, Token } from '@uniswap/sdk-core' -import { GOVERNANCE_ADDRESSES } from 'constants/addresses' -import { SupportedChainId } from 'constants/chains' -import { UNISWAP_GRANTS_PROPOSAL_DESCRIPTION } from 'constants/proposals/uniswap_grants_proposal_description' -import { BigNumber, ethers, utils } from 'ethers' -import { isAddress } from 'ethers/lib/utils' -import { useGovernanceContracts, useUniContract } from 'hooks/useContract' -import { useActiveWeb3React } from 'hooks/web3' -import { useCallback, useEffect, useMemo, useState } from 'react' -import { calculateGasMargin } from 'utils/calculateGasMargin' -import { UNISWAP_GRANTS_START_BLOCK } from '../../constants/proposals' -import { UNI } from '../../constants/tokens' -import { useMultipleContractMultipleData, useSingleCallResult } from '../multicall/hooks' -import { useTransactionAdder } from '../transactions/hooks' -import { t } from '@lingui/macro' - -interface ProposalDetail { - target: string - functionSig: string - callData: string -} - -export interface ProposalData { - id: string - title: string - description: string - proposer: string - status: ProposalState - forCount: number - againstCount: number - startBlock: number - endBlock: number - details: ProposalDetail[] - governorIndex: number // index in the governance address array for which this proposal pertains -} - -export interface CreateProposalData { - targets: string[] - values: string[] - signatures: string[] - calldatas: string[] - description: string -} - -export enum ProposalState { - Undetermined = -1, - Pending, - Active, - Canceled, - Defeated, - Succeeded, - Queued, - Expired, - Executed, -} - -const GovernanceInterface = new ethers.utils.Interface(GOV_ABI) -// get count of all proposals made in the latest governor contract -function useLatestProposalCount(): number | undefined { - const govContracts = useGovernanceContracts() - - const res = useSingleCallResult(govContracts[0], 'proposalCount') - - if (res?.result?.[0]) { - return (res.result[0] as BigNumber).toNumber() - } - - return undefined -} - -/** - * Need proposal events to get description data emitted from - * new proposal event. - */ -function useDataFromEventLogs(): - | { - description: string - details: { target: string; functionSig: string; callData: string }[] - }[][] - | undefined { - const { library, chainId } = useActiveWeb3React() - const [formattedEvents, setFormattedEvents] = - useState<{ description: string; details: { target: string; functionSig: string; callData: string }[] }[][]>() - - const govContracts = useGovernanceContracts() - - // create filters for ProposalCreated events - const filters = useMemo( - () => - govContracts?.filter((govContract) => !!govContract)?.length > 0 - ? govContracts - .filter((govContract): govContract is ethers.Contract => !!govContract) - .map((contract) => ({ - ...contract.filters.ProposalCreated(), - fromBlock: 10861678, // TODO could optimize this on a per-contract basis, this is the safe value - toBlock: 'latest', - })) - : undefined, - [govContracts] - ) - - // clear logs on chainId change - useEffect(() => { - return () => { - setFormattedEvents(undefined) - } - }, [chainId]) - - useEffect(() => { - if (!filters || !library) return - let stale = false - - if (!formattedEvents) { - Promise.all(filters.map((filter) => library.getLogs(filter))) - .then((governanceContractsProposalEvents) => { - if (stale) return - - const formattedEventData = governanceContractsProposalEvents.map((proposalEvents) => { - return proposalEvents.map((event) => { - const eventParsed = GovernanceInterface.parseLog(event).args - return { - description: eventParsed.description, - details: eventParsed.targets.map((target: string, i: number) => { - const signature = eventParsed.signatures[i] - const [name, types] = signature.substr(0, signature.length - 1).split('(') - const calldata = eventParsed.calldatas[i] - const decoded = utils.defaultAbiCoder.decode(types.split(','), calldata) - return { - target, - functionSig: name, - callData: decoded.join(', '), - } - }), - } - }) - }) - - setFormattedEvents(formattedEventData) - }) - .catch((error) => { - if (stale) return - - console.error('Failed to fetch proposals', error) - setFormattedEvents(undefined) - }) - - return () => { - stale = true - } - } - - return - }, [filters, library, formattedEvents, chainId]) - - return formattedEvents -} - -// get data for all past and active proposals -export function useAllProposalData(): ProposalData[] { - const { chainId } = useActiveWeb3React() - const proposalCount = useLatestProposalCount() - - const addresses = useMemo(() => { - return chainId === SupportedChainId.MAINNET ? GOVERNANCE_ADDRESSES.map((addressMap) => addressMap[chainId]) : [] - }, [chainId]) - - const proposalIndexes = useMemo(() => { - return chainId === SupportedChainId.MAINNET - ? [ - typeof proposalCount === 'number' ? new Array(proposalCount).fill(0).map((_, i) => [i + 1]) : [], // dynamic for current governor alpha - [[1], [2], [3], [4]], // hardcoded for governor alpha V0 - ] - : [] - }, [chainId, proposalCount]) - - // get all proposal entities - const allProposalsCallData = useMultipleContractMultipleData( - addresses, - GovernanceInterface, - 'proposals', - proposalIndexes - ) - - // get all proposal states - const allProposalStatesCallData = useMultipleContractMultipleData( - addresses, - GovernanceInterface, - 'state', - proposalIndexes - ) - - // get metadata from past events - const allFormattedEvents = useDataFromEventLogs() - - // early return until events are fetched - if (!allFormattedEvents) return [] - - const results: ProposalData[][] = [] - - for ( - let governanceContractIndex = 0; - governanceContractIndex < allProposalsCallData.length; - governanceContractIndex++ - ) { - const proposalsCallData = allProposalsCallData[governanceContractIndex] - const proposalStatesCallData = allProposalStatesCallData[governanceContractIndex] - const formattedEvents = allFormattedEvents[governanceContractIndex] - - if ( - !proposalsCallData?.every((p) => Boolean(p.result)) || - !proposalStatesCallData?.every((p) => Boolean(p.result)) || - !formattedEvents?.every((p) => Boolean(p)) - ) { - results.push([]) - continue - } - - results.push( - proposalsCallData.map((proposal, i) => { - let description = formattedEvents[i].description - const startBlock = parseInt(proposal?.result?.startBlock?.toString()) - if (startBlock === UNISWAP_GRANTS_START_BLOCK) { - description = UNISWAP_GRANTS_PROPOSAL_DESCRIPTION - } - return { - id: proposal?.result?.id.toString(), - title: description?.split(/# |\n/g)[1] ?? 'Untitled', - description: description ?? 'No description.', - proposer: proposal?.result?.proposer, - status: proposalStatesCallData[i]?.result?.[0] ?? ProposalState.Undetermined, - forCount: parseFloat(ethers.utils.formatUnits(proposal?.result?.forVotes.toString(), 18)), - againstCount: parseFloat(ethers.utils.formatUnits(proposal?.result?.againstVotes.toString(), 18)), - startBlock, - endBlock: parseInt(proposal?.result?.endBlock?.toString()), - details: formattedEvents[i].details, - governorIndex: governanceContractIndex, - } - }) - ) - } - - return results.reverse().flat() -} - -export function useProposalData(governorIndex: number, id: string): ProposalData | undefined { - const allProposalData = useAllProposalData() - return allProposalData?.filter((p) => p.governorIndex === governorIndex)?.find((p) => p.id === id) -} - -// get the users delegatee if it exists -export function useUserDelegatee(): string { - const { account } = useActiveWeb3React() - const uniContract = useUniContract() - const { result } = useSingleCallResult(uniContract, 'delegates', [account ?? undefined]) - return result?.[0] ?? undefined -} - -// gets the users current votes -export function useUserVotes(): CurrencyAmount | undefined { - const { account, chainId } = useActiveWeb3React() - const uniContract = useUniContract() - - // check for available votes - const uni = chainId ? UNI[chainId] : undefined - const votes = useSingleCallResult(uniContract, 'getCurrentVotes', [account ?? undefined])?.result?.[0] - return votes && uni ? CurrencyAmount.fromRawAmount(uni, votes) : undefined -} - -// fetch available votes as of block (usually proposal start block) -export function useUserVotesAsOfBlock(block: number | undefined): CurrencyAmount | undefined { - const { account, chainId } = useActiveWeb3React() - const uniContract = useUniContract() - - // check for available votes - const uni = chainId ? UNI[chainId] : undefined - const votes = useSingleCallResult(uniContract, 'getPriorVotes', [account ?? undefined, block ?? undefined]) - ?.result?.[0] - return votes && uni ? CurrencyAmount.fromRawAmount(uni, votes) : undefined -} - -export function useDelegateCallback(): (delegatee: string | undefined) => undefined | Promise { - const { account, chainId, library } = useActiveWeb3React() - const addTransaction = useTransactionAdder() - - const uniContract = useUniContract() - - return useCallback( - (delegatee: string | undefined) => { - if (!library || !chainId || !account || !isAddress(delegatee ?? '')) return undefined - const args = [delegatee] - if (!uniContract) throw new Error('No UNI Contract!') - return uniContract.estimateGas.delegate(...args, {}).then((estimatedGasLimit) => { - return uniContract - .delegate(...args, { value: null, gasLimit: calculateGasMargin(estimatedGasLimit) }) - .then((response: TransactionResponse) => { - addTransaction(response, { - summary: `Delegated votes`, - }) - return response.hash - }) - }) - }, - [account, addTransaction, chainId, library, uniContract] - ) -} - -export function useVoteCallback(): { - voteCallback: (proposalId: string | undefined, support: boolean) => undefined | Promise -} { - const { account } = useActiveWeb3React() - - const govContracts = useGovernanceContracts() - const latestGovernanceContract = govContracts ? govContracts[0] : null - const addTransaction = useTransactionAdder() - - const voteCallback = useCallback( - (proposalId: string | undefined, support: boolean) => { - if (!account || !latestGovernanceContract || !proposalId) return - const args = [proposalId, support] - return latestGovernanceContract.estimateGas.castVote(...args, {}).then((estimatedGasLimit) => { - return latestGovernanceContract - .castVote(...args, { value: null, gasLimit: calculateGasMargin(estimatedGasLimit) }) - .then((response: TransactionResponse) => { - addTransaction(response, { - summary: `Voted ${support ? 'for ' : 'against'} proposal ${proposalId}`, - }) - return response.hash - }) - }) - }, - [account, addTransaction, latestGovernanceContract] - ) - return { voteCallback } -} - -export function useCreateProposalCallback(): ( - createProposalData: CreateProposalData | undefined -) => undefined | Promise { - const { account } = useActiveWeb3React() - - const govContracts = useGovernanceContracts() - const latestGovernanceContract = govContracts ? govContracts[0] : null - const addTransaction = useTransactionAdder() - - const createProposalCallback = useCallback( - (createProposalData: CreateProposalData | undefined) => { - if (!account || !latestGovernanceContract || !createProposalData) return undefined - - const args = [ - createProposalData.targets, - createProposalData.values, - createProposalData.signatures, - createProposalData.calldatas, - createProposalData.description, - ] - - return latestGovernanceContract.estimateGas.propose(...args).then((estimatedGasLimit) => { - return latestGovernanceContract - .propose(...args, { gasLimit: calculateGasMargin(estimatedGasLimit) }) - .then((response: TransactionResponse) => { - addTransaction(response, { - summary: t`Submitted new proposal`, - }) - return response.hash - }) - }) - }, - [account, addTransaction, latestGovernanceContract] - ) - - return createProposalCallback -} - -export function useLatestProposalId(address: string): string | undefined { - const govContracts = useGovernanceContracts() - const latestGovernanceContract = govContracts ? govContracts[0] : null - const res = useSingleCallResult(latestGovernanceContract, 'latestProposalIds', [address]) - - if (res?.result?.[0]) { - return (res.result[0] as BigNumber).toString() - } - - return undefined -} - -export function useProposalThreshold(): CurrencyAmount | undefined { - const { chainId } = useActiveWeb3React() - - const govContracts = useGovernanceContracts() - const latestGovernanceContract = govContracts ? govContracts[0] : null - const res = useSingleCallResult(latestGovernanceContract, 'proposalThreshold') - const uni = chainId ? UNI[chainId] : undefined - - if (res?.result?.[0] && uni) { - return CurrencyAmount.fromRawAmount(uni, res.result[0]) - } - - return undefined -}