Compare commits

...

15 Commits

Author SHA1 Message Date
Shane Fontaine
32ac25556b improve displayed proposal end time accuracy (#1173) 2020-10-19 10:14:56 -04:00
Ian Lapham
50a599c005 update vote fetching logic, delegate only on global page (#1165)
* update vote fetching logic, delegate only on global page

* Update index.ts

* add helper message on proposal page

Co-authored-by: Noah Zinsmeister <noahwz@gmail.com>
2020-10-12 17:13:27 -04:00
Ian Lapham
9c473270ee fix(governance): modal bugs, redable styles, show common names for contracts (#1164)
* fix modal bugs, reable styles, show common names for contracts

* use theme for blue
2020-10-12 15:45:20 -04:00
_XiaoTian
b650b17563 fix: Fix the style of account transaction record list. (#1104)
Co-authored-by: Moody Salem <moodysalem@users.noreply.github.com>
2020-10-09 20:09:33 -05:00
Nikita Kudryavtsev
fc76177791 fix style of proposals text block in vote page (#1149) 2020-10-09 18:12:49 -05:00
Jay Welsh
69655980db fix(ethereum-logo): centers Ethereum logo (#1157) 2020-10-09 10:52:06 -05:00
TM Lee
e835a34d0d fix(coingecko token list): Replace CoinGecko tokens list with all.json (#1147) 2020-10-01 22:45:51 -05:00
Jordan Frankfurt
33fdf28dec fix(notifications): update margin for EVEN SMALLER mobile devices (#1146) 2020-10-01 14:00:13 -05:00
Jordan Frankfurt
c45c293e82 fix(mobile popups): add right margin to mobile notifications (#1142) 2020-10-01 13:23:48 -05:00
Moody Salem
9e140d1214 chore(release): reenable automatic releases 2020-10-01 11:21:48 -05:00
Paul Razvan Berg
bf8efc9bf6 fix(ro translations): update ro translation (#1143) 2020-10-01 11:01:02 -05:00
Moody Salem
4f922a2824 fix(token lists): bump token list spec version (allowing lists up to 10k tokens) 2020-09-29 11:50:32 -05:00
Moody Salem
b50cac0181 fix(uni): fix uni in circulation calculation and make uni button always show up 2020-09-28 15:13:22 -05:00
Moody Salem
1ac16586ff remove npm token 2020-09-24 14:43:08 -05:00
Moody Salem
e2686e0ed1 pull request target instead 2020-09-24 12:18:40 -05:00
22 changed files with 301 additions and 181 deletions

View File

@@ -4,7 +4,9 @@ on:
push:
branches:
- master
pull_request:
pull_request_target:
branches:
- master
jobs:
run-linters:
@@ -36,11 +38,9 @@ jobs:
- name: Install dependencies
run: yarn install --frozen-lockfile
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Run linters
uses: wearerequired/lint-action@v1
uses: wearerequired/lint-action@77d70b9a07ecb93bc98dc46dc27d96c4f004d035
with:
github_token: ${{ secrets.github_token }}
eslint: true

View File

@@ -1,8 +1,7 @@
name: Release
on:
# every morning
# schedule:
# - cron: '0 12 * * 1-4'
schedule:
- cron: '0 12 * * 1-4' # every day 12:00 UTC Monday-Thursday
# manual trigger
workflow_dispatch:
@@ -42,8 +41,6 @@ jobs:
- name: Install dependencies
run: yarn install --frozen-lockfile
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Build the IPFS bundle
run: yarn build
@@ -104,4 +101,4 @@ jobs:
- https://ipfs.infura.io/ipfs/${{ steps.upload.outputs.hash }}/
- https://ipfs.io/ipfs/${{ steps.upload.outputs.hash }}/
${{ needs.bump_version.outputs.changelog }}
${{ needs.bump_version.outputs.changelog }}

View File

@@ -31,8 +31,6 @@ jobs:
restore-keys: |
${{ runner.os }}-yarn-
- run: yarn install --frozen-lockfile
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- run: yarn cypress install
- run: yarn build
env:
@@ -63,7 +61,5 @@ jobs:
restore-keys: |
${{ runner.os }}-yarn-
- run: yarn install --frozen-lockfile
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- run: yarn test

View File

@@ -1,5 +1,6 @@
# Uniswap Interface
[![Lint](https://github.com/Uniswap/uniswap-interface/workflows/Lint/badge.svg)](https://github.com/Uniswap/uniswap-interface/actions?query=workflow%3ALint)
[![Tests](https://github.com/Uniswap/uniswap-interface/workflows/Tests/badge.svg)](https://github.com/Uniswap/uniswap-interface/actions?query=workflow%3ATests)
[![Styled With Prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg)](https://prettier.io/)

View File

@@ -31,7 +31,7 @@
"@uniswap/liquidity-staker": "^1.0.2",
"@uniswap/merkle-distributor": "1.0.1",
"@uniswap/sdk": "3.0.3",
"@uniswap/token-lists": "^1.0.0-beta.16",
"@uniswap/token-lists": "^1.0.0-beta.17",
"@uniswap/v2-core": "1.0.0",
"@uniswap/v2-periphery": "^1.1.0-beta.0",
"@web3-react/core": "^6.0.9",

View File

@@ -5,71 +5,87 @@
"installWeb3MobileBrowser": "Incearcă să vizitezi această pagina folosind un browser precum Trust Wallet sau Coinbase Wallet.",
"installMetamask": "Vizitează această pagină din nou după ce instalezi MetaMask în Chrome sau Brave",
"disconnected": "Deconectat",
"swap": "Schimba",
"swap": "Schimbă",
"swapAnyway": "Schimbă Oricum",
"send": "Trimite",
"pool": "Extrage",
"sendAnyway": "Trimite Oricum",
"pool": "Depune Lichiditate",
"betaWarning": "Proiectul este încă în versiunea beta. Folosește-l cu grijă, riscul este al tău.",
"input": "Input",
"output": "Output",
"estimated": "estimat",
"balance": "Balanță: {{ balanceInput }}",
"unlock": "Deschide",
"unlock": "Deblochează",
"pending": "În Așteptare",
"selectToken": "Selectează un token",
"searchOrPaste": "Caută Token sau Setează Adresă",
"noExchange": "Niciun Exchange Găsit",
"exchangeRate": "Curs Valutar",
"selectToken": "Selectează un jeton",
"searchOrPaste": "Caută după Numele, Simbolul sau Adresa Jetonului",
"searchOrPasteMobile": "Nume, Simbol sau Adresă",
"noExchange": "Nicio Piață de Schimb Găsită",
"noToken": "Nicin Jeton Găsit",
"exchangeRate": "Curs de Schimb",
"unknownError": "Ups! A intervenit o eroare tehnică. Te rog reîncarcă pagina, sau acceseaz-o de pe alt navigator sau dispozitiv.",
"enterValueCont": "Setează o valoare pentru {{ missingCurrencyValue }} pentru a continua.",
"selectTokenCont": "Selectează un token pentru a continua.",
"noLiquidity": "Lichiditate Inexistentă.",
"unlockTokenCont": "Te rog deblochează tokenul pentru a continua.",
"transactionDetails": "Detalii Tranzacții",
"selectTokenCont": "Selectează un jeton pentru a continua.",
"noLiquidity": "Nu Există Lichiditate.",
"insufficientLiquidity": "Lichiditate Insuficientă.",
"unlockTokenCont": "Te rog deblochează jetonul pentru a continua.",
"transactionDetails": "Detalii Avansate",
"hideDetails": "Ascunde Detaili",
"youAreSelling": "Vinzi",
"slippageWarning": "Alertă Deviație de Preț",
"highSlippageWarning": "Alertă Deviație de Preț Mare",
"youAreSelling": "Tu vinzi",
"orTransFail": "sau tranzacția va eșua.",
"youWillReceive": "Vei primi cel puțin",
"youAreBuying": "Cumperi",
"youAreBuying": "Tu cumperi",
"itWillCost": "Va costa cel mult",
"forAtMost": "pentru maximum",
"insufficientBalance": "Balanță Insuficientă",
"inputNotValid": "Valoarea setată nu este validă",
"differentToken": "Trebuie să fie un token diferit.",
"differentToken": "Trebuie să fie un jeton diferit.",
"noRecipient": "Setează o adresă de portofel pentru destinatar.",
"invalidRecipient": "Te rog setează o adresă de portofel validă pentru destinatar.",
"recipientAddress": "Adresa Destinatarului",
"youAreSending": "Trimiți",
"youAreSending": "Tu trimiți",
"willReceive": "vei primi cel puțin",
"to": "spre",
"addLiquidity": "Crește Lichiditatea",
"addLiquidity": "Adaugă Lichiditate",
"deposit": "Depozitează",
"currentPoolSize": "Mărimea Actualului Fond Comun",
"yourPoolShare": "Partea Ta Din Fond Comun",
"currentPoolSize": "Volumul Actual al Fondului",
"yourPoolShare": "Partea Ta din Fond",
"noZero": "Cantitatea nu poate fi zero.",
"mustBeETH": "Una dintre valori trebuie să fie ETH.",
"enterCurrencyOrLabelCont": "Setează o valoare pentru {{ inputCurrency }} sau {{ label }} pentru a continua. ",
"youAreAdding": "Adaugi între",
"youAreAdding": "Tu adaugi",
"and": "și",
"intoPool": "în fondul comun de lichidatate.",
"outPool": "din fondul comun de lichiditate.",
"youWillMint": "Vei printa",
"liquidityTokens": "tokene disponibile.",
"totalSupplyIs": "Actuala ofertă totală de tokene disponibile este",
"youAreSettingExRate": "Setezi cursul valutar inițial la",
"totalSupplyIs0": "Actuala ofertă totală de tokene disponibile este 0.",
"tokenWorth": "La acest schimb valutar, fiecare token disponibil este estimată la",
"firstLiquidity": "Ești prima persoană care aduce lichiditate!",
"initialExchangeRate": "Schimbul valutar inițial va fi setat în funcție de depozitul tău. Te rog asigură-te că depoziturile ETH și {{ label }} pe care le-ai făcut au aceeași valoare în monezi naționale.",
"removeLiquidity": "Elimină Lichiditate",
"poolTokens": "Extrage Tokene",
"intoPool": "în fondul de lichidatate.",
"outPool": "din fondul de lichiditate.",
"youWillMint": "Tu vei tipări",
"liquidityTokens": "jetoane de lichiditate.",
"totalSupplyIs": "Cantitatea totală de jetoane de lichiditate este",
"youAreSettingExRate": "Setezi cursul de schimb inițial la",
"totalSupplyIs0": "Cantitatea totală de jetoane de lichiditate este 0.",
"tokenWorth": "La cursul de schimb actual, fiecare jeton de lichiditate este valorat la",
"firstLiquidity": "Ești prima persoană care depune lichiditate!",
"initialExchangeRate": "Cursul de schimb inițial va fi setat în funcție de depozitele tale. Te rog asigură-te că ETH-ul și {{ label }}-ul pe care le-ai depozitat au aceeași valoare în bani fiat.",
"removeLiquidity": "Retrage Lichiditat",
"poolTokens": "Depune Jetoane",
"enterLabelCont": "Setează o valoare pentru {{ label }} pentru a continua.",
"youAreRemoving": "Elimini între",
"youWillRemove": "Vei elimina",
"createExchange": "Creează Exchange",
"invalidTokenAddress": "Adresă de token nu este validă",
"exchangeExists": "{{ label }} Exchange există deja!",
"invalidSymbol": "Simbol invalid!",
"invalidDecimals": "Zecimale invalidate",
"tokenAddress": "Adresă Token",
"youAreRemoving": "Retragi între",
"youWillRemove": "Vei retrage",
"createExchange": "Creează Piață de Schimb",
"invalidTokenAddress": "Adresa de jeton nu este validă",
"exchangeExists": "{{ label }} Piața de schimb există deja!",
"invalidSymbol": "Simbol invalid",
"invalidDecimals": "Zecimale invalide",
"tokenAddress": "Adresă Jeton",
"label": "Denumire",
"name": "Nume",
"symbol": "Simbol",
"decimals": "Zecimale",
"enterTokenCont": "Setează o adresă de token pentru a continua"
"enterTokenCont": "Setează o adresă de jeton pentru a continua",
"priceChange": "Deviație de preț așteptată",
"forAtLeast": "pentru cel puțin ",
"brokenToken": "Jetonul selectat nu este compatibil cu Uniswap V1. Depunerea de lichiditate îți va bloca fondurile pe vecie.",
"toleranceExplanation": "Micșorând această limită, vei reduce riscul de frontrunning. În același timp, devine mai probabil că tranzacția va eșua din cauza variațiilor normale de preț.",
"tokenSearchPlaceholder": "Caută nume sau lipește adresă"
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@@ -99,7 +99,7 @@ const LowerSection = styled.div`
flex-grow: 1;
overflow: auto;
background-color: ${({ theme }) => theme.bg2};
border-bottom-left-radius: 25px;
border-bottom-left-radius: 20px;
border-bottom-right-radius: 20px;
h5 {

View File

@@ -6,6 +6,7 @@ import tokenLogo from '../../assets/images/token-logo.png'
import { UNI } from '../../constants'
import { useTotalSupply } from '../../data/TotalSupply'
import { useActiveWeb3React } from '../../hooks'
import { useMerkleDistributorContract } from '../../hooks/useContract'
import useCurrentBlockTimestamp from '../../hooks/useCurrentBlockTimestamp'
import { useTotalUniEarned } from '../../state/stake/hooks'
import { useAggregateUniBalance, useTokenBalance } from '../../state/wallet/hooks'
@@ -45,15 +46,18 @@ export default function UniBalanceContent({ setShowUniBalanceModal }: { setShowU
const total = useAggregateUniBalance()
const uniBalance: TokenAmount | undefined = useTokenBalance(account ?? undefined, uni)
const uniUnHarvested: TokenAmount | undefined = useTotalUniEarned()
const uniToClaim: TokenAmount | undefined = useTotalUniEarned()
const totalSupply: TokenAmount | undefined = useTotalSupply(uni)
const uniPrice = useUSDCPrice(uni)
const blockTimestamp = useCurrentBlockTimestamp()
const unclaimedUni = useTokenBalance(useMerkleDistributorContract()?.address, uni)
const circulation: TokenAmount | undefined = useMemo(
() =>
blockTimestamp && uni && chainId === ChainId.MAINNET ? computeUniCirculation(uni, blockTimestamp) : totalSupply,
[blockTimestamp, chainId, totalSupply, uni]
blockTimestamp && uni && chainId === ChainId.MAINNET
? computeUniCirculation(uni, blockTimestamp, unclaimedUni)
: totalSupply,
[blockTimestamp, chainId, totalSupply, unclaimedUni, uni]
)
return (
@@ -63,37 +67,41 @@ export default function UniBalanceContent({ setShowUniBalanceModal }: { setShowU
<CardNoise />
<CardSection gap="md">
<RowBetween>
<TYPE.white color="white">Your UNI Breakdown</TYPE.white>{' '}
<TYPE.white color="white">Your UNI Breakdown</TYPE.white>
<StyledClose stroke="white" onClick={() => setShowUniBalanceModal(false)} />
</RowBetween>
</CardSection>
<Break />
<CardSection gap="sm">
<AutoColumn gap="md" justify="center">
<UniTokenAnimated width="48px" src={tokenLogo} />{' '}
<TYPE.white fontSize={48} fontWeight={600} color="white">
{total?.toFixed(2, { groupSeparator: ',' })}
</TYPE.white>
</AutoColumn>
<AutoColumn gap="md">
<RowBetween>
<TYPE.white color="white">Balance:</TYPE.white>
<TYPE.white color="white">{uniBalance?.toFixed(2, { groupSeparator: ',' })}</TYPE.white>
</RowBetween>
<RowBetween>
<TYPE.white color="white">Unclaimed:</TYPE.white>
<TYPE.white color="white">
{uniUnHarvested?.toFixed(4, { groupSeparator: ',' })}{' '}
{uniUnHarvested && (
<StyledInternalLink onClick={() => setShowUniBalanceModal(false)} to="/uni">
(claim)
</StyledInternalLink>
)}
</TYPE.white>
</RowBetween>
</AutoColumn>
</CardSection>
<Break />
{account && (
<>
<CardSection gap="sm">
<AutoColumn gap="md" justify="center">
<UniTokenAnimated width="48px" src={tokenLogo} />{' '}
<TYPE.white fontSize={48} fontWeight={600} color="white">
{total?.toFixed(2, { groupSeparator: ',' })}
</TYPE.white>
</AutoColumn>
<AutoColumn gap="md">
<RowBetween>
<TYPE.white color="white">Balance:</TYPE.white>
<TYPE.white color="white">{uniBalance?.toFixed(2, { groupSeparator: ',' })}</TYPE.white>
</RowBetween>
<RowBetween>
<TYPE.white color="white">Unclaimed:</TYPE.white>
<TYPE.white color="white">
{uniToClaim?.toFixed(4, { groupSeparator: ',' })}{' '}
{uniToClaim && uniToClaim.greaterThan('0') && (
<StyledInternalLink onClick={() => setShowUniBalanceModal(false)} to="/uni">
(claim)
</StyledInternalLink>
)}
</TYPE.white>
</RowBetween>
</AutoColumn>
</CardSection>
<Break />
</>
)}
<CardSection gap="sm">
<AutoColumn gap="md">
<RowBetween>

View File

@@ -1,7 +1,7 @@
import { ChainId, TokenAmount, JSBI } from '@uniswap/sdk'
import { ChainId, TokenAmount } from '@uniswap/sdk'
import React, { useState } from 'react'
import { Text } from 'rebass'
import { NavLink, withRouter } from 'react-router-dom'
import { NavLink } from 'react-router-dom'
import { darken } from 'polished'
import { useTranslation } from 'react-i18next'
@@ -264,7 +264,7 @@ const NETWORK_LABELS: { [chainId in ChainId]?: string } = {
[ChainId.KOVAN]: 'Kovan'
}
function Header({ history }: { history: any }) {
export default function Header() {
const { account, chainId } = useActiveWeb3React()
const { t } = useTranslation()
@@ -292,34 +292,32 @@ function Header({ history }: { history: any }) {
<UniBalanceContent setShowUniBalanceModal={setShowUniBalanceModal} />
</Modal>
<HeaderRow>
<Title href="." style={{}}>
<Title href=".">
<UniIcon>
<img width={'24px'} src={isDark ? LogoDark : Logo} alt="logo" />
</UniIcon>
</Title>
<HeaderLinks>
<StyledNavLink id={`swap-nav-link`} to={'/swap'} isActive={() => history.location.pathname.includes('/swap')}>
<StyledNavLink id={`swap-nav-link`} to={'/swap'}>
{t('swap')}
</StyledNavLink>
<StyledNavLink
id={`pool-nav-link`}
to={'/pool'}
isActive={() =>
history.location.pathname.includes('/pool') ||
history.location.pathname.includes('/add') ||
history.location.pathname.includes('/remove')
isActive={(match, { pathname }) =>
Boolean(match) ||
pathname.startsWith('/add') ||
pathname.startsWith('/remove') ||
pathname.startsWith('/create') ||
pathname.startsWith('/find')
}
>
{t('pool')}
</StyledNavLink>
<StyledNavLink id={`stake-nav-link`} to={'/uni'} isActive={() => history.location.pathname.includes('/uni')}>
<StyledNavLink id={`stake-nav-link`} to={'/uni'}>
UNI
</StyledNavLink>
<StyledNavLink
id={`stake-nav-link`}
to={'/vote'}
isActive={() => history.location.pathname.includes('/vote')}
>
<StyledNavLink id={`stake-nav-link`} to={'/vote'}>
Vote
</StyledNavLink>
<StyledExternalLink id={`stake-nav-link`} href={'https://uniswap.info'}>
@@ -344,26 +342,28 @@ function Header({ history }: { history: any }) {
<CardNoise />
</UNIWrapper>
)}
{!availableClaim && aggregateBalance && JSBI.greaterThan(aggregateBalance.raw, JSBI.BigInt(0)) && (
{!availableClaim && aggregateBalance && (
<UNIWrapper onClick={() => setShowUniBalanceModal(true)}>
<UNIAmount active={!!account && !availableClaim} style={{ pointerEvents: 'auto' }}>
<HideSmall>
<TYPE.white
style={{
paddingRight: '.4rem'
}}
>
<CountUp
key={countUpValue}
isCounting
start={parseFloat(countUpValuePrevious)}
end={parseFloat(countUpValue)}
thousandsSeparator={','}
duration={1}
/>
</TYPE.white>
</HideSmall>
{'UNI'}
{account && (
<HideSmall>
<TYPE.white
style={{
paddingRight: '.4rem'
}}
>
<CountUp
key={countUpValue}
isCounting
start={parseFloat(countUpValuePrevious)}
end={parseFloat(countUpValue)}
thousandsSeparator={','}
duration={1}
/>
</TYPE.white>
</HideSmall>
)}
UNI
</UNIAmount>
<CardNoise />
</UNIWrapper>
@@ -385,5 +385,3 @@ function Header({ history }: { history: any }) {
</HeaderFrame>
)
}
export default withRouter(Header)

View File

@@ -30,6 +30,9 @@ export const Popup = styled.div`
${({ theme }) => theme.mediaWidth.upToSmall`
min-width: 290px;
&:not(:last-of-type) {
margin-right: 20px;
}
`}
`
const Fader = styled.div`

View File

@@ -20,11 +20,15 @@ export const MKR = new Token(ChainId.MAINNET, '0x9f8F72aA9304c8B593d555F12eF6589
export const AMPL = new Token(ChainId.MAINNET, '0xD46bA6D942050d489DBd938a2C909A5d5039A161', 9, 'AMPL', 'Ampleforth')
export const WBTC = new Token(ChainId.MAINNET, '0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599', 18, 'WBTC', 'Wrapped BTC')
// TODO this is only approximate, it's actually based on blocks
export const PROPOSAL_LENGTH_IN_DAYS = 7
// 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 = 14
export const PROPOSAL_LENGTH_IN_BLOCKS = 40_320
export const PROPOSAL_LENGTH_IN_SECS = AVERAGE_BLOCK_TIME_IN_SECS * PROPOSAL_LENGTH_IN_BLOCKS
export const GOVERNANCE_ADDRESS = '0x5e4be8Bc9637f0EAA1A755019e06A68ce081D58F'
export const TIMELOCK_ADDRESS = '0x1a9C8182C09F50C8318d769245beA52c32BE35BC'
const UNI_ADDRESS = '0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984'
export const UNI: { [chainId in ChainId]: Token } = {
[ChainId.MAINNET]: new Token(ChainId.MAINNET, UNI_ADDRESS, 18, 'UNI', 'Uniswap'),
@@ -34,6 +38,12 @@ export const UNI: { [chainId in ChainId]: Token } = {
[ChainId.KOVAN]: new Token(ChainId.KOVAN, UNI_ADDRESS, 18, 'UNI', 'Uniswap')
}
export const COMMON_CONTRACT_NAMES: { [address: string]: string } = {
[UNI_ADDRESS]: 'UNI',
[GOVERNANCE_ADDRESS]: 'Governance',
[TIMELOCK_ADDRESS]: 'Timelock'
}
// TODO: specify merkle distributor for mainnet
export const MERKLE_DISTRIBUTOR_ADDRESS: { [chainId in ChainId]?: string } = {
[ChainId.MAINNET]: '0x090D4613473dEE047c3f2706764f49E0821D256e'

View File

@@ -12,7 +12,7 @@ export const DEFAULT_LIST_OF_LISTS: string[] = [
'stablecoin.cmc.eth',
'tokenlist.zerion.eth',
'tokenlist.aave.eth',
'https://www.coingecko.com/tokens_list/uniswap/defi_100/v_0_0_0.json',
'https://tokens.coingecko.com/uniswap/all.json',
'https://app.tryroll.com/tokens.json',
'https://raw.githubusercontent.com/compound-finance/token-list/master/compound.tokenlist.json',
'https://defiprime.com/defiprime.tokenlist.json',

View File

@@ -9,16 +9,20 @@ import { CardSection, DataCard } from '../../components/earn/styled'
import { ArrowLeft } from 'react-feather'
import { ButtonPrimary } from '../../components/Button'
import { ProposalStatus } from './styled'
import { useProposalData, useUserVotes, useUserDelegatee, ProposalData } from '../../state/governance/hooks'
import { useProposalData, useUserVotesAsOfBlock, ProposalData, useUserDelegatee } from '../../state/governance/hooks'
import { useTimestampFromBlock } from '../../hooks/useTimestampFromBlock'
import { DateTime } from 'luxon'
import ReactMarkdown from 'react-markdown'
import VoteModal from '../../components/vote/VoteModal'
import { TokenAmount, JSBI } from '@uniswap/sdk'
import { useTokenBalance } from '../../state/wallet/hooks'
import { useActiveWeb3React } from '../../hooks'
import { UNI, ZERO_ADDRESS, PROPOSAL_LENGTH_IN_DAYS } from '../../constants'
import { PROPOSAL_LENGTH_IN_SECS, COMMON_CONTRACT_NAMES, UNI, ZERO_ADDRESS } from '../../constants'
import { isAddress, getEtherscanLink } from '../../utils'
import { ApplicationModal } from '../../state/application/actions'
import { useModalOpen, useToggleDelegateModal, useToggleVoteModal } from '../../state/application/hooks'
import DelegateModal from '../../components/vote/DelegateModal'
import { GreyCard } from '../../components/Card'
import { useTokenBalance } from '../../state/wallet/hooks'
const PageWrapper = styled(AutoColumn)`
width: 100%;
@@ -99,7 +103,7 @@ export default function VotePage({
params: { id }
}
}: RouteComponentProps<{ id: string }>) {
const { account, chainId } = useActiveWeb3React()
const { chainId, account } = useActiveWeb3React()
// get data for this specific proposal
const proposalData: ProposalData | undefined = useProposalData(id)
@@ -108,12 +112,17 @@ export default function VotePage({
const [support, setSupport] = useState<boolean>(true)
// modal for casting votes
const [showModal, setShowModal] = useState<boolean>(false)
const showVoteModal = useModalOpen(ApplicationModal.VOTE)
const toggleVoteModal = useToggleVoteModal()
// toggle for showing delegation modal
const showDelegateModal = useModalOpen(ApplicationModal.DELEGATE)
const toggelDelegateModal = useToggleDelegateModal()
// get and format date from data
const startTimestamp: number | undefined = useTimestampFromBlock(proposalData?.startBlock)
const endDate: DateTime | undefined = startTimestamp
? DateTime.fromSeconds(startTimestamp).plus({ days: PROPOSAL_LENGTH_IN_DAYS })
? DateTime.fromSeconds(startTimestamp).plus({ seconds: PROPOSAL_LENGTH_IN_SECS })
: undefined
const now: DateTime = DateTime.local()
@@ -124,30 +133,38 @@ export default function VotePage({
const againstPercentage: string =
proposalData && totalVotes ? ((proposalData.againstCount * 100) / totalVotes).toFixed(0) + '%' : '0%'
// show delegation option if they have have a balance, have not delegated
const availableVotes: TokenAmount | undefined = useUserVotes()
// only count available votes as of the proposal start block
const availableVotes: TokenAmount | 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.raw, JSBI.BigInt(0)) &&
proposalData &&
proposalData.status === 'active'
const uniBalance: TokenAmount | undefined = useTokenBalance(account ?? undefined, chainId ? UNI[chainId] : undefined)
const userDelegatee: string | undefined = useUserDelegatee()
const showUnlockVoting = Boolean(
// in blurb link to home page if they are able to unlock
const showLinkForUnlock = Boolean(
uniBalance && JSBI.notEqual(uniBalance.raw, 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) {
return <ExternalLink href={getEtherscanLink(chainId, content, 'address')}>{content}</ExternalLink>
const commonName = COMMON_CONTRACT_NAMES[content] ?? content
return <ExternalLink href={getEtherscanLink(chainId, content, 'address')}>{commonName}</ExternalLink>
}
return <span>{content}</span>
}
return (
<PageWrapper gap="lg" justify="center">
<VoteModal
isOpen={showModal}
onDismiss={() => setShowModal(false)}
proposalId={proposalData?.id}
support={support}
/>
<VoteModal isOpen={showVoteModal} onDismiss={toggleVoteModal} proposalId={proposalData?.id} support={support} />
<DelegateModal isOpen={showDelegateModal} onDismiss={toggelDelegateModal} title="Unlock Votes" />
<ProposalInfo gap="lg" justify="start">
<RowBetween style={{ width: '100%' }}>
<ArrowWrapper to="/vote">
@@ -162,33 +179,32 @@ export default function VotePage({
{endDate && endDate < now
? 'Voting ended ' + (endDate && endDate.toLocaleString(DateTime.DATETIME_FULL))
: proposalData
? 'Voting ends approximately' + (endDate && endDate.toLocaleString(DateTime.DATETIME_FULL))
? 'Voting ends approximately ' + (endDate && endDate.toLocaleString(DateTime.DATETIME_FULL))
: ''}
</TYPE.main>
{showUnlockVoting && endDate && endDate > now && (
<ButtonPrimary
style={{ width: 'fit-content' }}
padding="8px"
borderRadius="8px"
onClick={() => setShowModal(true)}
>
Unlock Voting
</ButtonPrimary>
)}
</RowBetween>
{proposalData && proposalData.status === 'active' && !showVotingButtons && (
<GreyCard>
<TYPE.black>
Only UNI votes that were self delegated or delegated to another address before block{' '}
{proposalData.startBlock} are eligible for voting.{' '}
{showLinkForUnlock && (
<span>
<StyledInternalLink to="/vote">Unlock voting</StyledInternalLink> to prepare for the next proposal.
</span>
)}
</TYPE.black>
</GreyCard>
)}
</AutoColumn>
{!showUnlockVoting &&
availableVotes &&
JSBI.greaterThan(availableVotes?.raw, JSBI.BigInt(0)) &&
endDate &&
endDate > now ? (
{showVotingButtons ? (
<RowFixed style={{ width: '100%', gap: '12px' }}>
<ButtonPrimary
padding="8px"
borderRadius="8px"
onClick={() => {
setSupport(true)
setShowModal(true)
toggleVoteModal()
}}
>
Vote For
@@ -198,7 +214,7 @@ export default function VotePage({
borderRadius="8px"
onClick={() => {
setSupport(false)
setShowModal(true)
toggleVoteModal()
}}
>
Vote Against
@@ -260,7 +276,7 @@ export default function VotePage({
})}
</AutoColumn>
<AutoColumn gap="md">
<TYPE.mediumHeader fontWeight={600}>Overview</TYPE.mediumHeader>
<TYPE.mediumHeader fontWeight={600}>Description</TYPE.mediumHeader>
<MarkDownWrapper>
<ReactMarkdown source={proposalData?.description} />
</MarkDownWrapper>

View File

@@ -1,4 +1,4 @@
import React, { useState } from 'react'
import React from 'react'
import { AutoColumn } from '../../components/Column'
import styled from 'styled-components'
import { TYPE, ExternalLink } from '../../theme'
@@ -19,6 +19,8 @@ import { JSBI, TokenAmount, ChainId } from '@uniswap/sdk'
import { shortenAddress, getEtherscanLink } 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'
const PageWrapper = styled(AutoColumn)``
@@ -102,7 +104,10 @@ const EmptyProposals = styled.div`
export default function Vote() {
const { account, chainId } = useActiveWeb3React()
const [showModal, setShowModal] = useState<boolean>(false)
// toggle for showing delegation modal
const showDelegateModal = useModalOpen(ApplicationModal.DELEGATE)
const toggelDelegateModal = useToggleDelegateModal()
// get data to list all proposals
const allProposals: ProposalData[] = useAllProposalData()
@@ -120,8 +125,8 @@ export default function Vote() {
return (
<PageWrapper gap="lg" justify="center">
<DelegateModal
isOpen={showModal}
onDismiss={() => setShowModal(false)}
isOpen={showDelegateModal}
onDismiss={toggelDelegateModal}
title={showUnlockVoting ? 'Unlock Votes' : 'Update Delegation'}
/>
<TopSection gap="md">
@@ -154,14 +159,14 @@ export default function Vote() {
</TopSection>
<TopSection gap="2px">
<WrapSmall>
<TYPE.mediumHeader style={{ margin: '0.5rem 0' }}>Proposals</TYPE.mediumHeader>
<TYPE.mediumHeader style={{ margin: '0.5rem 0.5rem 0.5rem 0', flexShrink: 0 }}>Proposals</TYPE.mediumHeader>
{(!allProposals || allProposals.length === 0) && !availableVotes && <Loader />}
{showUnlockVoting ? (
<ButtonPrimary
style={{ width: 'fit-content' }}
padding="8px"
borderRadius="8px"
onClick={() => setShowModal(true)}
onClick={toggelDelegateModal}
>
Unlock Voting
</ButtonPrimary>
@@ -195,7 +200,7 @@ export default function Vote() {
>
{userDelegatee === account ? 'Self' : shortenAddress(userDelegatee)}
</StyledExternalLink>
<TextButton onClick={() => setShowModal(true)} style={{ marginLeft: '4px' }}>
<TextButton onClick={toggelDelegateModal} style={{ marginLeft: '4px' }}>
(edit)
</TextButton>
</AddressButton>

View File

@@ -24,7 +24,9 @@ export enum ApplicationModal {
SELF_CLAIM,
ADDRESS_CLAIM,
CLAIM_POPUP,
MENU
MENU,
DELEGATE,
VOTE
}
export const updateBlockNumber = createAction<{ chainId: number; blockNumber: number }>('application/updateBlockNumber')

View File

@@ -51,6 +51,14 @@ export function useToggleSelfClaimModal(): () => void {
return useToggleModal(ApplicationModal.SELF_CLAIM)
}
export function useToggleDelegateModal(): () => void {
return useToggleModal(ApplicationModal.DELEGATE)
}
export function useToggleVoteModal(): () => void {
return useToggleModal(ApplicationModal.VOTE)
}
// returns a function that allows adding a popup
export function useAddPopup(): (content: PopupContent, key?: string) => void {
const dispatch = useDispatch()

View File

@@ -153,6 +153,7 @@ export function useUserDelegatee(): string {
return result?.[0] ?? undefined
}
// gets the users current votes
export function useUserVotes(): TokenAmount | undefined {
const { account, chainId } = useActiveWeb3React()
const uniContract = useUniContract()
@@ -163,6 +164,18 @@ export function useUserVotes(): TokenAmount | undefined {
return votes && uni ? new TokenAmount(uni, votes) : undefined
}
// fetch available votes as of block (usually proposal start block)
export function useUserVotesAsOfBlock(block: number | undefined): TokenAmount | 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 ? new TokenAmount(uni, votes) : undefined
}
export function useDelegateCallback(): (delegatee: string | undefined) => undefined | Promise<string> {
const { account, chainId, library } = useActiveWeb3React()
const addTransaction = useTransactionAdder()

View File

@@ -192,6 +192,10 @@ body {
padding: 0;
}
a {
color: ${colors(false).blue1};
}
* {
box-sizing: border-box;
}

View File

@@ -0,0 +1,36 @@
import { ChainId, JSBI, Token, TokenAmount } from '@uniswap/sdk'
import { BigNumber } from 'ethers'
import { ZERO_ADDRESS } from '../constants'
import { computeUniCirculation } from './computeUniCirculation'
describe('computeUniCirculation', () => {
const token = new Token(ChainId.RINKEBY, ZERO_ADDRESS, 18)
function expandTo18Decimals(num: JSBI | string | number) {
return JSBI.multiply(JSBI.BigInt(num), JSBI.exponentiate(JSBI.BigInt(10), JSBI.BigInt(18)))
}
function tokenAmount(num: JSBI | string | number) {
return new TokenAmount(token, expandTo18Decimals(num))
}
it('before staking', () => {
expect(computeUniCirculation(token, BigNumber.from(0), undefined)).toEqual(tokenAmount(150_000_000))
expect(computeUniCirculation(token, BigNumber.from(1600387200), undefined)).toEqual(tokenAmount(150_000_000))
})
it('mid staking', () => {
expect(computeUniCirculation(token, BigNumber.from(1600387200 + 15 * 24 * 60 * 60), undefined)).toEqual(
tokenAmount(155_000_000)
)
})
it('after staking and treasury vesting cliff', () => {
expect(computeUniCirculation(token, BigNumber.from(1600387200 + 60 * 24 * 60 * 60), undefined)).toEqual(
tokenAmount(224_575_341)
)
})
it('subtracts unclaimed uni', () => {
expect(computeUniCirculation(token, BigNumber.from(1600387200 + 15 * 24 * 60 * 60), tokenAmount(1000))).toEqual(
tokenAmount(154_999_000)
)
})
})

View File

@@ -38,21 +38,27 @@ function withVesting(before: JSBI, time: BigNumber, amount: number, start: numbe
if (time.gt(start)) {
if (time.gte(end)) {
return JSBI.add(before, JSBI.BigInt(amount))
} else if (cliff && time.gte(cliff)) {
return JSBI.add(
before,
JSBI.divide(
JSBI.multiply(JSBI.BigInt(amount), JSBI.BigInt(time.sub(start).toString())),
JSBI.subtract(JSBI.BigInt(end), JSBI.BigInt(start))
} else {
if ((typeof cliff === 'number' && time.gte(cliff)) || typeof cliff === 'undefined') {
return JSBI.add(
before,
JSBI.divide(
JSBI.multiply(JSBI.BigInt(amount), JSBI.BigInt(time.sub(start).toString())),
JSBI.subtract(JSBI.BigInt(end), JSBI.BigInt(start))
)
)
)
}
}
}
return before
}
export function computeUniCirculation(uni: Token, blockTimestamp: BigNumber): TokenAmount {
let wholeAmount = JSBI.BigInt(USERS_AMOUNT) // users, 15%
export function computeUniCirculation(
uni: Token,
blockTimestamp: BigNumber,
unclaimedUni: TokenAmount | undefined
): TokenAmount {
let wholeAmount = JSBI.BigInt(USERS_AMOUNT)
// staking rewards
wholeAmount = withVesting(wholeAmount, blockTimestamp, STAKING_REWARDS_AMOUNT, STAKING_GENESIS, STAKING_END)
@@ -101,5 +107,6 @@ export function computeUniCirculation(uni: Token, blockTimestamp: BigNumber): To
wholeAmount = withVesting(wholeAmount, blockTimestamp, TEAM_YEAR_3_AMOUNT, TREASURY_BEGIN_YEAR_3, TREASURY_END_YEAR_3)
wholeAmount = withVesting(wholeAmount, blockTimestamp, TEAM_YEAR_4_AMOUNT, TREASURY_BEGIN_YEAR_4, TREASURY_END_YEAR_4)
return new TokenAmount(uni, JSBI.multiply(wholeAmount, JSBI.exponentiate(JSBI.BigInt(10), JSBI.BigInt(18))))
const total = new TokenAmount(uni, JSBI.multiply(wholeAmount, JSBI.exponentiate(JSBI.BigInt(10), JSBI.BigInt(18))))
return unclaimedUni ? total.subtract(unclaimedUni) : total
}

View File

@@ -2726,10 +2726,10 @@
tiny-warning "^1.0.3"
toformat "^2.0.0"
"@uniswap/token-lists@^1.0.0-beta.16":
version "1.0.0-beta.16"
resolved "https://registry.yarnpkg.com/@uniswap/token-lists/-/token-lists-1.0.0-beta.16.tgz#17c5e781d490490b4f61a35a9c3106b2b1b6a57f"
integrity sha512-+Pu4yUQ8vUPsJW88d3pGEZTkA1T3bC7jI6wgBIcrtkQK90rfUOvgWiwj0mZBuKy/VGDm9AmSnyxYGUW2f0xn4A==
"@uniswap/token-lists@^1.0.0-beta.17":
version "1.0.0-beta.17"
resolved "https://registry.yarnpkg.com/@uniswap/token-lists/-/token-lists-1.0.0-beta.17.tgz#a861fe96a0f3de91b01eae05dec05a6a2018b38e"
integrity sha512-UVRmSsP/ghJ7Dg8BHYjAZmZL96jHlZKrFoIEuKD3g1E4FbahfwS2j2KAaf6iQuLx6RWo8PQmDZ99rfK6T2xi8w==
"@uniswap/v2-core@1.0.0":
version "1.0.0"