Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b93fd230bd | ||
|
|
e9a11bb604 | ||
|
|
c5afbedb3e | ||
|
|
5b2c44522e | ||
|
|
7fd4005154 | ||
|
|
48eab0d0ac | ||
|
|
c9ee1b3b32 | ||
|
|
eb4c305eff | ||
|
|
d9825622f1 | ||
|
|
8975086a69 | ||
|
|
ddf88345a9 | ||
|
|
32ac25556b | ||
|
|
50a599c005 | ||
|
|
9c473270ee | ||
|
|
b650b17563 | ||
|
|
fc76177791 | ||
|
|
69655980db | ||
|
|
e835a34d0d | ||
|
|
33fdf28dec | ||
|
|
c45c293e82 | ||
|
|
9e140d1214 | ||
|
|
bf8efc9bf6 | ||
|
|
4f922a2824 | ||
|
|
b50cac0181 | ||
|
|
1ac16586ff | ||
|
|
e2686e0ed1 |
8
.github/workflows/lint.yml
vendored
8
.github/workflows/lint.yml
vendored
@@ -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
|
||||
|
||||
9
.github/workflows/release.yaml
vendored
9
.github/workflows/release.yaml
vendored
@@ -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 }}
|
||||
|
||||
4
.github/workflows/tests.yaml
vendored
4
.github/workflows/tests.yaml
vendored
@@ -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
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# Uniswap Interface
|
||||
|
||||
[](https://github.com/Uniswap/uniswap-interface/actions?query=workflow%3ALint)
|
||||
[](https://github.com/Uniswap/uniswap-interface/actions?query=workflow%3ATests)
|
||||
[](https://prettier.io/)
|
||||
|
||||
|
||||
@@ -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.18",
|
||||
"@uniswap/v2-core": "1.0.0",
|
||||
"@uniswap/v2-periphery": "^1.1.0-beta.0",
|
||||
"@web3-react/core": "^6.0.9",
|
||||
|
||||
@@ -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 |
@@ -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 {
|
||||
|
||||
12
src/components/Blocklist/index.tsx
Normal file
12
src/components/Blocklist/index.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import React, { ReactNode, useMemo } from 'react'
|
||||
import { BLOCKED_ADDRESSES } from '../../constants'
|
||||
import { useActiveWeb3React } from '../../hooks'
|
||||
|
||||
export default function Blocklist({ children }: { children: ReactNode }) {
|
||||
const { account } = useActiveWeb3React()
|
||||
const blocked: boolean = useMemo(() => Boolean(account && BLOCKED_ADDRESSES.indexOf(account) !== -1), [account])
|
||||
if (blocked) {
|
||||
return <div>Blocked address</div>
|
||||
}
|
||||
return <>{children}</>
|
||||
}
|
||||
@@ -159,6 +159,26 @@ export const ButtonPink = styled(Base)`
|
||||
}
|
||||
`
|
||||
|
||||
export const ButtonUNIGradient = styled(ButtonPrimary)`
|
||||
color: white;
|
||||
padding: 4px 8px;
|
||||
height: 36px;
|
||||
font-weight: 500;
|
||||
background-color: ${({ theme }) => theme.bg3};
|
||||
background: radial-gradient(174.47% 188.91% at 1.84% 0%, #ff007a 0%, #2172e5 100%), #edeef2;
|
||||
width: fit-content;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
white-space: no-wrap;
|
||||
:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
:active {
|
||||
opacity: 0.9;
|
||||
}
|
||||
`
|
||||
|
||||
export const ButtonOutlined = styled(Base)`
|
||||
border: 1px solid ${({ theme }) => theme.bg2};
|
||||
background-color: transparent;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -123,9 +123,6 @@ const AccountElement = styled.div<{ active: boolean }>`
|
||||
:focus {
|
||||
border: 1px solid blue;
|
||||
}
|
||||
/* :hover {
|
||||
background-color: ${({ theme, active }) => (!active ? theme.bg2 : theme.bg4)};
|
||||
} */
|
||||
`
|
||||
|
||||
const UNIAmount = styled(AccountElement)`
|
||||
@@ -264,7 +261,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 +289,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 +339,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 +382,3 @@ function Header({ history }: { history: any }) {
|
||||
</HeaderFrame>
|
||||
)
|
||||
}
|
||||
|
||||
export default withRouter(Header)
|
||||
|
||||
@@ -3,6 +3,7 @@ import React, { useCallback, useMemo } from 'react'
|
||||
import ReactGA from 'react-ga'
|
||||
import { useDispatch } from 'react-redux'
|
||||
import { Text } from 'rebass'
|
||||
import styled from 'styled-components'
|
||||
import { AppDispatch } from '../../state'
|
||||
import { useRemovePopup } from '../../state/application/hooks'
|
||||
import { acceptListUpdate } from '../../state/lists/actions'
|
||||
@@ -12,6 +13,11 @@ import { ButtonSecondary } from '../Button'
|
||||
import { AutoColumn } from '../Column'
|
||||
import { AutoRow } from '../Row'
|
||||
|
||||
export const ChangesList = styled.ul`
|
||||
max-height: 400px;
|
||||
overflow: auto;
|
||||
`
|
||||
|
||||
export default function ListUpdatePopup({
|
||||
popKey,
|
||||
listUrl,
|
||||
@@ -64,7 +70,7 @@ export default function ListUpdatePopup({
|
||||
An update is available for the token list "{oldList.name}" (
|
||||
{listVersionLabel(oldList.version)} to {listVersionLabel(newList.version)}).
|
||||
</Text>
|
||||
<ul>
|
||||
<ChangesList>
|
||||
{tokensAdded.length > 0 ? (
|
||||
<li>
|
||||
{tokensAdded.map((token, i) => (
|
||||
@@ -88,7 +94,7 @@ export default function ListUpdatePopup({
|
||||
</li>
|
||||
) : null}
|
||||
{numTokensChanged > 0 ? <li>{numTokensChanged} tokens updated</li> : null}
|
||||
</ul>
|
||||
</ChangesList>
|
||||
</div>
|
||||
<AutoRow>
|
||||
<div style={{ flexGrow: 1, marginRight: 12 }}>
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { JSBI, Pair, Percent } from '@uniswap/sdk'
|
||||
import { JSBI, Pair, Percent, TokenAmount } from '@uniswap/sdk'
|
||||
import { darken } from 'polished'
|
||||
import React, { useState } from 'react'
|
||||
import { ChevronDown, ChevronUp } from 'react-feather'
|
||||
@@ -9,10 +9,10 @@ import { useTotalSupply } from '../../data/TotalSupply'
|
||||
|
||||
import { useActiveWeb3React } from '../../hooks'
|
||||
import { useTokenBalance } from '../../state/wallet/hooks'
|
||||
import { ExternalLink, TYPE } from '../../theme'
|
||||
import { ExternalLink, TYPE, HideExtraSmall, ExtraSmallOnly } from '../../theme'
|
||||
import { currencyId } from '../../utils/currencyId'
|
||||
import { unwrappedToken } from '../../utils/wrappedCurrency'
|
||||
import { ButtonPrimary, ButtonSecondary, ButtonEmpty } from '../Button'
|
||||
import { ButtonPrimary, ButtonSecondary, ButtonEmpty, ButtonUNIGradient } from '../Button'
|
||||
import { transparentize } from 'polished'
|
||||
import { CardNoise } from '../earn/styled'
|
||||
|
||||
@@ -22,8 +22,9 @@ import Card, { GreyCard, LightCard } from '../Card'
|
||||
import { AutoColumn } from '../Column'
|
||||
import CurrencyLogo from '../CurrencyLogo'
|
||||
import DoubleCurrencyLogo from '../DoubleLogo'
|
||||
import { RowBetween, RowFixed } from '../Row'
|
||||
import { RowBetween, RowFixed, AutoRow } from '../Row'
|
||||
import { Dots } from '../swap/styleds'
|
||||
import { BIG_INT_ZERO } from '../../constants'
|
||||
|
||||
export const FixedHeightRow = styled(RowBetween)`
|
||||
height: 24px;
|
||||
@@ -47,6 +48,7 @@ interface PositionCardProps {
|
||||
pair: Pair
|
||||
showUnwrapped?: boolean
|
||||
border?: string
|
||||
stakedBalance?: TokenAmount // optional balance to indicate that liquidity is deposited in mining pool
|
||||
}
|
||||
|
||||
export function MinimalPositionCard({ pair, showUnwrapped = false, border }: PositionCardProps) {
|
||||
@@ -157,7 +159,7 @@ export function MinimalPositionCard({ pair, showUnwrapped = false, border }: Pos
|
||||
)
|
||||
}
|
||||
|
||||
export default function FullPositionCard({ pair, border }: PositionCardProps) {
|
||||
export default function FullPositionCard({ pair, border, stakedBalance }: PositionCardProps) {
|
||||
const { account } = useActiveWeb3React()
|
||||
|
||||
const currency0 = unwrappedToken(pair.token0)
|
||||
@@ -165,9 +167,12 @@ export default function FullPositionCard({ pair, border }: PositionCardProps) {
|
||||
|
||||
const [showMore, setShowMore] = useState(false)
|
||||
|
||||
const userPoolBalance = useTokenBalance(account ?? undefined, pair.liquidityToken)
|
||||
const userDefaultPoolBalance = useTokenBalance(account ?? undefined, pair.liquidityToken)
|
||||
const totalPoolTokens = useTotalSupply(pair.liquidityToken)
|
||||
|
||||
// if staked balance balance provided, add to standard liquidity amount
|
||||
const userPoolBalance = stakedBalance ? userDefaultPoolBalance?.add(stakedBalance) : userDefaultPoolBalance
|
||||
|
||||
const poolTokenPercentage =
|
||||
!!userPoolBalance && !!totalPoolTokens && JSBI.greaterThanOrEqual(totalPoolTokens.raw, userPoolBalance.raw)
|
||||
? new Percent(userPoolBalance.raw, totalPoolTokens.raw)
|
||||
@@ -192,12 +197,22 @@ export default function FullPositionCard({ pair, border }: PositionCardProps) {
|
||||
<CardNoise />
|
||||
<AutoColumn gap="12px">
|
||||
<FixedHeightRow>
|
||||
<RowFixed>
|
||||
<DoubleCurrencyLogo currency0={currency0} currency1={currency1} margin={true} size={20} />
|
||||
<AutoRow gap="8px">
|
||||
<DoubleCurrencyLogo currency0={currency0} currency1={currency1} size={20} />
|
||||
<Text fontWeight={500} fontSize={20}>
|
||||
{!currency0 || !currency1 ? <Dots>Loading</Dots> : `${currency0.symbol}/${currency1.symbol}`}
|
||||
</Text>
|
||||
</RowFixed>
|
||||
{!!stakedBalance && (
|
||||
<ButtonUNIGradient as={Link} to={`/uni/${currencyId(currency0)}/${currencyId(currency1)}`}>
|
||||
<HideExtraSmall>Earning UNI</HideExtraSmall>
|
||||
<ExtraSmallOnly>
|
||||
<span role="img" aria-label="bolt">
|
||||
⚡
|
||||
</span>
|
||||
</ExtraSmallOnly>
|
||||
</ButtonUNIGradient>
|
||||
)}
|
||||
</AutoRow>
|
||||
|
||||
<RowFixed gap="8px">
|
||||
<ButtonEmpty
|
||||
@@ -208,7 +223,6 @@ export default function FullPositionCard({ pair, border }: PositionCardProps) {
|
||||
>
|
||||
{showMore ? (
|
||||
<>
|
||||
{' '}
|
||||
Manage
|
||||
<ChevronUp size="20" style={{ marginLeft: '10px' }} />
|
||||
</>
|
||||
@@ -226,12 +240,22 @@ export default function FullPositionCard({ pair, border }: PositionCardProps) {
|
||||
<AutoColumn gap="8px">
|
||||
<FixedHeightRow>
|
||||
<Text fontSize={16} fontWeight={500}>
|
||||
Your pool tokens:
|
||||
Your total pool tokens:
|
||||
</Text>
|
||||
<Text fontSize={16} fontWeight={500}>
|
||||
{userPoolBalance ? userPoolBalance.toSignificant(4) : '-'}
|
||||
</Text>
|
||||
</FixedHeightRow>
|
||||
{stakedBalance && (
|
||||
<FixedHeightRow>
|
||||
<Text fontSize={16} fontWeight={500}>
|
||||
Pool tokens in rewards pool:
|
||||
</Text>
|
||||
<Text fontSize={16} fontWeight={500}>
|
||||
{stakedBalance.toSignificant(4)}
|
||||
</Text>
|
||||
</FixedHeightRow>
|
||||
)}
|
||||
<FixedHeightRow>
|
||||
<RowFixed>
|
||||
<Text fontSize={16} fontWeight={500}>
|
||||
@@ -273,7 +297,9 @@ export default function FullPositionCard({ pair, border }: PositionCardProps) {
|
||||
Your pool share:
|
||||
</Text>
|
||||
<Text fontSize={16} fontWeight={500}>
|
||||
{poolTokenPercentage ? poolTokenPercentage.toFixed(2) + '%' : '-'}
|
||||
{poolTokenPercentage
|
||||
? (poolTokenPercentage.toFixed(2) === '0.00' ? '<0.01' : poolTokenPercentage.toFixed(2)) + '%'
|
||||
: '-'}
|
||||
</Text>
|
||||
</FixedHeightRow>
|
||||
|
||||
@@ -285,26 +311,39 @@ export default function FullPositionCard({ pair, border }: PositionCardProps) {
|
||||
View accrued fees and analytics<span style={{ fontSize: '11px' }}>↗</span>
|
||||
</ExternalLink>
|
||||
</ButtonSecondary>
|
||||
<RowBetween marginTop="10px">
|
||||
{userDefaultPoolBalance && JSBI.greaterThan(userDefaultPoolBalance.raw, BIG_INT_ZERO) && (
|
||||
<RowBetween marginTop="10px">
|
||||
<ButtonPrimary
|
||||
padding="8px"
|
||||
borderRadius="8px"
|
||||
as={Link}
|
||||
to={`/add/${currencyId(currency0)}/${currencyId(currency1)}`}
|
||||
width="48%"
|
||||
>
|
||||
Add
|
||||
</ButtonPrimary>
|
||||
<ButtonPrimary
|
||||
padding="8px"
|
||||
borderRadius="8px"
|
||||
as={Link}
|
||||
width="48%"
|
||||
to={`/remove/${currencyId(currency0)}/${currencyId(currency1)}`}
|
||||
>
|
||||
Remove
|
||||
</ButtonPrimary>
|
||||
</RowBetween>
|
||||
)}
|
||||
{stakedBalance && JSBI.greaterThan(stakedBalance.raw, BIG_INT_ZERO) && (
|
||||
<ButtonPrimary
|
||||
padding="8px"
|
||||
borderRadius="8px"
|
||||
as={Link}
|
||||
to={`/add/${currencyId(currency0)}/${currencyId(currency1)}`}
|
||||
width="48%"
|
||||
to={`/uni/${currencyId(currency0)}/${currencyId(currency1)}`}
|
||||
width="100%"
|
||||
>
|
||||
Add
|
||||
Manage Liquidity in Rewards Pool
|
||||
</ButtonPrimary>
|
||||
<ButtonPrimary
|
||||
padding="8px"
|
||||
borderRadius="8px"
|
||||
as={Link}
|
||||
width="48%"
|
||||
to={`/remove/${currencyId(currency0)}/${currencyId(currency1)}`}
|
||||
>
|
||||
Remove
|
||||
</ButtonPrimary>
|
||||
</RowBetween>
|
||||
)}
|
||||
</AutoColumn>
|
||||
)}
|
||||
</AutoColumn>
|
||||
|
||||
@@ -14,6 +14,7 @@ import { unwrappedToken } from '../../utils/wrappedCurrency'
|
||||
import { useTotalSupply } from '../../data/TotalSupply'
|
||||
import { usePair } from '../../data/Reserves'
|
||||
import useUSDCPrice from '../../utils/useUSDCPrice'
|
||||
import { BIG_INT_SECONDS_IN_WEEK } from '../../constants'
|
||||
|
||||
const StatContainer = styled.div`
|
||||
display: flex;
|
||||
@@ -56,11 +57,6 @@ const TopSection = styled.div`
|
||||
`};
|
||||
`
|
||||
|
||||
// const APR = styled.div`
|
||||
// display: flex;
|
||||
// justify-content: flex-end;
|
||||
// `
|
||||
|
||||
const BottomSection = styled.div<{ showBackground: boolean }>`
|
||||
padding: 12px 16px;
|
||||
opacity: ${({ showBackground }) => (showBackground ? '1' : '0.4')};
|
||||
@@ -139,9 +135,15 @@ export default function PoolCard({ stakingInfo }: { stakingInfo: StakingInfo })
|
||||
</RowBetween>
|
||||
<RowBetween>
|
||||
<TYPE.white> Pool rate </TYPE.white>
|
||||
<TYPE.white>{`${stakingInfo.totalRewardRate
|
||||
?.multiply(`${60 * 60 * 24 * 7}`)
|
||||
?.toFixed(0, { groupSeparator: ',' })} UNI / week`}</TYPE.white>
|
||||
<TYPE.white>
|
||||
{stakingInfo
|
||||
? stakingInfo.active
|
||||
? `${stakingInfo.totalRewardRate
|
||||
?.multiply(BIG_INT_SECONDS_IN_WEEK)
|
||||
?.toFixed(0, { groupSeparator: ',' })} UNI / week`
|
||||
: '0 UNI / week'
|
||||
: '-'}
|
||||
</TYPE.white>
|
||||
</RowBetween>
|
||||
</StatContainer>
|
||||
|
||||
@@ -157,9 +159,13 @@ export default function PoolCard({ stakingInfo }: { stakingInfo: StakingInfo })
|
||||
<span role="img" aria-label="wizard-icon" style={{ marginRight: '0.5rem' }}>
|
||||
⚡
|
||||
</span>
|
||||
{`${stakingInfo.rewardRate
|
||||
?.multiply(`${60 * 60 * 24 * 7}`)
|
||||
?.toSignificant(4, { groupSeparator: ',' })} UNI / week`}
|
||||
{stakingInfo
|
||||
? stakingInfo.active
|
||||
? `${stakingInfo.rewardRate
|
||||
?.multiply(BIG_INT_SECONDS_IN_WEEK)
|
||||
?.toSignificant(4, { groupSeparator: ',' })} UNI / week`
|
||||
: '0 UNI / week'
|
||||
: '-'}
|
||||
</TYPE.black>
|
||||
</BottomSection>
|
||||
</>
|
||||
|
||||
@@ -18,13 +18,17 @@ export const USDT = new Token(ChainId.MAINNET, '0xdAC17F958D2ee523a2206206994597
|
||||
export const COMP = new Token(ChainId.MAINNET, '0xc00e94Cb662C3520282E6f5717214004A7f26888', 18, 'COMP', 'Compound')
|
||||
export const MKR = new Token(ChainId.MAINNET, '0x9f8F72aA9304c8B593d555F12eF6589cC3A579A2', 18, 'MKR', 'Maker')
|
||||
export const AMPL = new Token(ChainId.MAINNET, '0xD46bA6D942050d489DBd938a2C909A5d5039A161', 9, 'AMPL', 'Ampleforth')
|
||||
export const WBTC = new Token(ChainId.MAINNET, '0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599', 18, 'WBTC', 'Wrapped BTC')
|
||||
export const WBTC = new Token(ChainId.MAINNET, '0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599', 8, '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'
|
||||
@@ -169,6 +179,9 @@ export const INITIAL_ALLOWED_SLIPPAGE = 50
|
||||
// 20 minutes, denominated in seconds
|
||||
export const DEFAULT_DEADLINE_FROM_NOW = 60 * 20
|
||||
|
||||
// used for rewards deadlines
|
||||
export const BIG_INT_SECONDS_IN_WEEK = JSBI.BigInt(60 * 60 * 24 * 7)
|
||||
|
||||
export const BIG_INT_ZERO = JSBI.BigInt(0)
|
||||
|
||||
// one basis point
|
||||
@@ -186,3 +199,11 @@ export const BLOCKED_PRICE_IMPACT_NON_EXPERT: Percent = new Percent(JSBI.BigInt(
|
||||
// used to ensure the user doesn't send so much ETH so they end up with <.01
|
||||
export const MIN_ETH: JSBI = JSBI.exponentiate(JSBI.BigInt(10), JSBI.BigInt(16)) // .01 ETH
|
||||
export const BETTER_TRADE_LINK_THRESHOLD = new Percent(JSBI.BigInt(75), JSBI.BigInt(10000))
|
||||
|
||||
// SDN OFAC addresses
|
||||
export const BLOCKED_ADDRESSES: string[] = [
|
||||
'0x7F367cC41522cE07553e823bf3be79A889DEbe1B',
|
||||
'0xd882cFc20F52f2599D84b8e8D58C7FB62cfE344b',
|
||||
'0x901bb9583b24D97e995513C6778dc6888AB6870e',
|
||||
'0xA7e5d5A720f06526557c513402f2e6B5fA20b008'
|
||||
]
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -6,6 +6,7 @@ import ReactDOM from 'react-dom'
|
||||
import ReactGA from 'react-ga'
|
||||
import { Provider } from 'react-redux'
|
||||
import { HashRouter } from 'react-router-dom'
|
||||
import Blocklist from './components/Blocklist'
|
||||
import { NetworkContextName } from './constants'
|
||||
import './i18n'
|
||||
import App from './pages/App'
|
||||
@@ -58,15 +59,17 @@ ReactDOM.render(
|
||||
<FixedGlobalStyle />
|
||||
<Web3ReactProvider getLibrary={getLibrary}>
|
||||
<Web3ProviderNetwork getLibrary={getLibrary}>
|
||||
<Provider store={store}>
|
||||
<Updaters />
|
||||
<ThemeProvider>
|
||||
<ThemedGlobalStyle />
|
||||
<HashRouter>
|
||||
<App />
|
||||
</HashRouter>
|
||||
</ThemeProvider>
|
||||
</Provider>
|
||||
<Blocklist>
|
||||
<Provider store={store}>
|
||||
<Updaters />
|
||||
<ThemeProvider>
|
||||
<ThemedGlobalStyle />
|
||||
<HashRouter>
|
||||
<App />
|
||||
</HashRouter>
|
||||
</ThemeProvider>
|
||||
</Provider>
|
||||
</Blocklist>
|
||||
</Web3ProviderNetwork>
|
||||
</Web3ReactProvider>
|
||||
</StrictMode>,
|
||||
|
||||
@@ -28,7 +28,7 @@ import { useTotalSupply } from '../../data/TotalSupply'
|
||||
import { usePair } from '../../data/Reserves'
|
||||
import usePrevious from '../../hooks/usePrevious'
|
||||
import useUSDCPrice from '../../utils/useUSDCPrice'
|
||||
import { BIG_INT_ZERO } from '../../constants'
|
||||
import { BIG_INT_ZERO, BIG_INT_SECONDS_IN_WEEK } from '../../constants'
|
||||
|
||||
const PageWrapper = styled(AutoColumn)`
|
||||
max-width: 640px;
|
||||
@@ -177,9 +177,11 @@ export default function Manage({
|
||||
<AutoColumn gap="sm">
|
||||
<TYPE.body style={{ margin: 0 }}>Pool Rate</TYPE.body>
|
||||
<TYPE.body fontSize={24} fontWeight={500}>
|
||||
{stakingInfo?.totalRewardRate
|
||||
?.multiply((60 * 60 * 24 * 7).toString())
|
||||
?.toFixed(0, { groupSeparator: ',' }) ?? '-'}
|
||||
{stakingInfo?.active
|
||||
? stakingInfo?.totalRewardRate
|
||||
?.multiply(BIG_INT_SECONDS_IN_WEEK)
|
||||
?.toFixed(0, { groupSeparator: ',' }) ?? '-'
|
||||
: '0'}
|
||||
{' UNI / week'}
|
||||
</TYPE.body>
|
||||
</AutoColumn>
|
||||
@@ -293,9 +295,11 @@ export default function Manage({
|
||||
<span role="img" aria-label="wizard-icon" style={{ marginRight: '8px ' }}>
|
||||
⚡
|
||||
</span>
|
||||
{stakingInfo?.rewardRate
|
||||
?.multiply((60 * 60 * 24 * 7).toString())
|
||||
?.toSignificant(4, { groupSeparator: ',' }) ?? '-'}
|
||||
{stakingInfo?.active
|
||||
? stakingInfo?.rewardRate
|
||||
?.multiply(BIG_INT_SECONDS_IN_WEEK)
|
||||
?.toSignificant(4, { groupSeparator: ',' }) ?? '-'
|
||||
: '0'}
|
||||
{' UNI / week'}
|
||||
</TYPE.black>
|
||||
</RowBetween>
|
||||
@@ -311,9 +315,11 @@ export default function Manage({
|
||||
|
||||
{!showAddLiquidityButton && (
|
||||
<DataRow style={{ marginBottom: '1rem' }}>
|
||||
<ButtonPrimary padding="8px" borderRadius="8px" width="160px" onClick={handleDepositClick}>
|
||||
{stakingInfo?.stakedAmount?.greaterThan(JSBI.BigInt(0)) ? 'Deposit' : 'Deposit UNI-V2 LP Tokens'}
|
||||
</ButtonPrimary>
|
||||
{stakingInfo && stakingInfo.active && (
|
||||
<ButtonPrimary padding="8px" borderRadius="8px" width="160px" onClick={handleDepositClick}>
|
||||
{stakingInfo?.stakedAmount?.greaterThan(JSBI.BigInt(0)) ? 'Deposit' : 'Deposit UNI-V2 LP Tokens'}
|
||||
</ButtonPrimary>
|
||||
)}
|
||||
|
||||
{stakingInfo?.stakedAmount?.greaterThan(JSBI.BigInt(0)) && (
|
||||
<>
|
||||
@@ -329,7 +335,7 @@ export default function Manage({
|
||||
)}
|
||||
</DataRow>
|
||||
)}
|
||||
{!userLiquidityUnstaked ? null : userLiquidityUnstaked.equalTo('0') ? null : (
|
||||
{!userLiquidityUnstaked ? null : userLiquidityUnstaked.equalTo('0') ? null : !stakingInfo?.active ? null : (
|
||||
<TYPE.main>{userLiquidityUnstaked.toSignificant(6)} UNI-V2 LP tokens available</TYPE.main>
|
||||
)}
|
||||
</PositionInfo>
|
||||
|
||||
@@ -9,6 +9,9 @@ import { CardSection, DataCard, CardNoise, CardBGImage } from '../../components/
|
||||
import { Countdown } from './Countdown'
|
||||
import Loader from '../../components/Loader'
|
||||
import { useActiveWeb3React } from '../../hooks'
|
||||
import { JSBI } from '@uniswap/sdk'
|
||||
import { BIG_INT_ZERO } from '../../constants'
|
||||
import { OutlineCard } from '../../components/Card'
|
||||
|
||||
const PageWrapper = styled(AutoColumn)`
|
||||
max-width: 640px;
|
||||
@@ -29,16 +32,25 @@ const PoolSection = styled.div`
|
||||
justify-self: center;
|
||||
`
|
||||
|
||||
const DataRow = styled(RowBetween)`
|
||||
${({ theme }) => theme.mediaWidth.upToSmall`
|
||||
flex-direction: column;
|
||||
`};
|
||||
`
|
||||
|
||||
export default function Earn() {
|
||||
const { chainId } = useActiveWeb3React()
|
||||
|
||||
// staking info for connected account
|
||||
const stakingInfos = useStakingInfo()
|
||||
|
||||
const DataRow = styled(RowBetween)`
|
||||
${({ theme }) => theme.mediaWidth.upToSmall`
|
||||
flex-direction: column;
|
||||
`};
|
||||
`
|
||||
/**
|
||||
* only show staking cards with balance
|
||||
* @todo only account for this if rewards are inactive
|
||||
*/
|
||||
const stakingInfosWithBalance = stakingInfos?.filter(s => JSBI.greaterThan(s.stakedAmount.raw, BIG_INT_ZERO))
|
||||
|
||||
// toggle copy if rewards are inactive
|
||||
const stakingRewardsExist = Boolean(typeof chainId === 'number' && (STAKING_REWARDS_INFO[chainId]?.length ?? 0) > 0)
|
||||
|
||||
return (
|
||||
@@ -81,9 +93,11 @@ export default function Earn() {
|
||||
{stakingRewardsExist && stakingInfos?.length === 0 ? (
|
||||
<Loader style={{ margin: 'auto' }} />
|
||||
) : !stakingRewardsExist ? (
|
||||
'No active rewards'
|
||||
<OutlineCard>No active pools</OutlineCard>
|
||||
) : stakingInfos?.length !== 0 && stakingInfosWithBalance.length === 0 ? (
|
||||
<OutlineCard>No active pools</OutlineCard>
|
||||
) : (
|
||||
stakingInfos?.map(stakingInfo => {
|
||||
stakingInfosWithBalance?.map(stakingInfo => {
|
||||
// need to sort by added liquidity here
|
||||
return <PoolCard key={stakingInfo.stakingRewardAddress} stakingInfo={stakingInfo} />
|
||||
})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { useContext, useMemo } from 'react'
|
||||
import styled, { ThemeContext } from 'styled-components'
|
||||
import { Pair } from '@uniswap/sdk'
|
||||
import { Pair, JSBI } from '@uniswap/sdk'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { SwapPoolTabs } from '../../components/NavigationTabs'
|
||||
|
||||
@@ -19,6 +19,8 @@ import { usePairs } from '../../data/Reserves'
|
||||
import { toV2LiquidityToken, useTrackedTokenPairs } from '../../state/user/hooks'
|
||||
import { Dots } from '../../components/swap/styleds'
|
||||
import { CardSection, DataCard, CardNoise, CardBGImage } from '../../components/earn/styled'
|
||||
import { useStakingInfo } from '../../state/stake/hooks'
|
||||
import { BIG_INT_ZERO } from '../../constants'
|
||||
|
||||
const PageWrapper = styled(AutoColumn)`
|
||||
max-width: 640px;
|
||||
@@ -107,11 +109,24 @@ export default function Pool() {
|
||||
|
||||
const hasV1Liquidity = useUserHasLiquidityInAllTokens()
|
||||
|
||||
// show liquidity even if its deposited in rewards contract
|
||||
const stakingInfo = useStakingInfo()
|
||||
const stakingInfosWithBalance = stakingInfo?.filter(pool => JSBI.greaterThan(pool.stakedAmount.raw, BIG_INT_ZERO))
|
||||
const stakingPairs = usePairs(stakingInfosWithBalance?.map(stakingInfo => stakingInfo.tokens))
|
||||
|
||||
// remove any pairs that also are included in pairs with stake in mining pool
|
||||
const v2PairsWithoutStakedAmount = allV2PairsWithLiquidity.filter(v2Pair => {
|
||||
return (
|
||||
stakingPairs
|
||||
?.map(stakingPair => stakingPair[1])
|
||||
.filter(stakingPair => stakingPair?.liquidityToken.address === v2Pair.liquidityToken.address).length === 0
|
||||
)
|
||||
})
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageWrapper>
|
||||
<SwapPoolTabs active={'pool'} />
|
||||
|
||||
<VoteCard>
|
||||
<CardBGImage />
|
||||
<CardNoise />
|
||||
@@ -170,7 +185,7 @@ export default function Pool() {
|
||||
<Dots>Loading</Dots>
|
||||
</TYPE.body>
|
||||
</EmptyProposals>
|
||||
) : allV2PairsWithLiquidity?.length > 0 ? (
|
||||
) : allV2PairsWithLiquidity?.length > 0 || stakingPairs?.length > 0 ? (
|
||||
<>
|
||||
<ButtonSecondary>
|
||||
<RowBetween>
|
||||
@@ -180,10 +195,19 @@ export default function Pool() {
|
||||
<span> ↗</span>
|
||||
</RowBetween>
|
||||
</ButtonSecondary>
|
||||
|
||||
{allV2PairsWithLiquidity.map(v2Pair => (
|
||||
{v2PairsWithoutStakedAmount.map(v2Pair => (
|
||||
<FullPositionCard key={v2Pair.liquidityToken.address} pair={v2Pair} />
|
||||
))}
|
||||
{stakingPairs.map(
|
||||
(stakingPair, i) =>
|
||||
stakingPair[1] && ( // skip pairs that arent loaded
|
||||
<FullPositionCard
|
||||
key={stakingInfosWithBalance[i].stakingRewardAddress}
|
||||
pair={stakingPair[1]}
|
||||
stakedBalance={stakingInfosWithBalance[i].stakedAmount}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<EmptyProposals>
|
||||
|
||||
@@ -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%;
|
||||
@@ -94,12 +98,16 @@ const DetailText = styled.div`
|
||||
word-break: break-all;
|
||||
`
|
||||
|
||||
const ProposerAddressLink = styled(ExternalLink)`
|
||||
word-break: break-all;
|
||||
`
|
||||
|
||||
export default function VotePage({
|
||||
match: {
|
||||
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 +116,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 +137,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 +183,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 +218,7 @@ export default function VotePage({
|
||||
borderRadius="8px"
|
||||
onClick={() => {
|
||||
setSupport(false)
|
||||
setShowModal(true)
|
||||
toggleVoteModal()
|
||||
}}
|
||||
>
|
||||
Vote Against
|
||||
@@ -260,18 +280,18 @@ 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>
|
||||
</AutoColumn>
|
||||
<AutoColumn gap="md">
|
||||
<TYPE.mediumHeader fontWeight={600}>Proposer</TYPE.mediumHeader>
|
||||
<ExternalLink
|
||||
<ProposerAddressLink
|
||||
href={proposalData?.proposer && chainId ? getEtherscanLink(chainId, proposalData?.proposer, 'address') : ''}
|
||||
>
|
||||
<ReactMarkdown source={proposalData?.proposer} />
|
||||
</ExternalLink>
|
||||
</ProposerAddressLink>
|
||||
</AutoColumn>
|
||||
</ProposalInfo>
|
||||
</PageWrapper>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -124,7 +124,7 @@ export function useAllProposalData() {
|
||||
const formattedProposal: ProposalData = {
|
||||
id: allProposals[i]?.result?.id.toString(),
|
||||
title: formattedEvents[i].description?.split(/# |\n/g)[1] || 'Untitled',
|
||||
description: formattedEvents[i].description?.split(/# /)[1] || 'No description.',
|
||||
description: formattedEvents[i].description || 'No description.',
|
||||
proposer: allProposals[i]?.result?.proposer,
|
||||
status: enumerateProposalState(allProposalStates[i]?.result?.[0]) ?? 'Undetermined',
|
||||
forCount: parseFloat(ethers.utils.formatUnits(allProposals[i]?.result?.forVotes.toString(), 18)),
|
||||
@@ -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()
|
||||
|
||||
@@ -13,6 +13,9 @@ export default function Updater(): null {
|
||||
const { library } = useActiveWeb3React()
|
||||
const dispatch = useDispatch<AppDispatch>()
|
||||
const lists = useSelector<AppState, AppState['lists']['byUrl']>(state => state.lists.byUrl)
|
||||
const selectedListUrl = useSelector<AppState, AppState['lists']['selectedListUrl']>(
|
||||
state => state.lists.selectedListUrl
|
||||
)
|
||||
|
||||
const isWindowVisible = useIsWindowVisible()
|
||||
|
||||
@@ -54,19 +57,21 @@ export default function Updater(): null {
|
||||
// automatically update minor/patch as long as bump matches the min update
|
||||
if (bump >= min) {
|
||||
dispatch(acceptListUpdate(listUrl))
|
||||
dispatch(
|
||||
addPopup({
|
||||
key: listUrl,
|
||||
content: {
|
||||
listUpdate: {
|
||||
listUrl,
|
||||
oldList: list.current,
|
||||
newList: list.pendingUpdate,
|
||||
auto: true
|
||||
if (listUrl === selectedListUrl) {
|
||||
dispatch(
|
||||
addPopup({
|
||||
key: listUrl,
|
||||
content: {
|
||||
listUpdate: {
|
||||
listUrl,
|
||||
oldList: list.current,
|
||||
newList: list.pendingUpdate,
|
||||
auto: true
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
})
|
||||
)
|
||||
}
|
||||
} else {
|
||||
console.error(
|
||||
`List at url ${listUrl} could not automatically update because the version bump was only PATCH/MINOR while the update had breaking changes and should have been MAJOR`
|
||||
@@ -75,24 +80,26 @@ export default function Updater(): null {
|
||||
break
|
||||
|
||||
case VersionUpgrade.MAJOR:
|
||||
dispatch(
|
||||
addPopup({
|
||||
key: listUrl,
|
||||
content: {
|
||||
listUpdate: {
|
||||
listUrl,
|
||||
auto: false,
|
||||
oldList: list.current,
|
||||
newList: list.pendingUpdate
|
||||
}
|
||||
},
|
||||
removeAfterMs: null
|
||||
})
|
||||
)
|
||||
if (listUrl === selectedListUrl) {
|
||||
dispatch(
|
||||
addPopup({
|
||||
key: listUrl,
|
||||
content: {
|
||||
listUpdate: {
|
||||
listUrl,
|
||||
auto: false,
|
||||
oldList: list.current,
|
||||
newList: list.pendingUpdate
|
||||
}
|
||||
},
|
||||
removeAfterMs: null
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}, [dispatch, lists])
|
||||
}, [dispatch, lists, selectedListUrl])
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import { STAKING_REWARDS_INTERFACE } from '../../constants/abis/staking-rewards'
|
||||
import { useActiveWeb3React } from '../../hooks'
|
||||
import { NEVER_RELOAD, useMultipleContractSingleData } from '../multicall/hooks'
|
||||
import { tryParseAmount } from '../swap/hooks'
|
||||
import useCurrentBlockTimestamp from 'hooks/useCurrentBlockTimestamp'
|
||||
|
||||
export const STAKING_GENESIS = 1600387200
|
||||
|
||||
@@ -55,6 +56,8 @@ export interface StakingInfo {
|
||||
rewardRate: TokenAmount
|
||||
// when the period ends
|
||||
periodFinish: Date | undefined
|
||||
// if pool is active
|
||||
active: boolean
|
||||
// calculates a hypothetical amount of token distributed to the active account per second.
|
||||
getHypotheticalRewardRate: (
|
||||
stakedAmount: TokenAmount,
|
||||
@@ -67,6 +70,9 @@ export interface StakingInfo {
|
||||
export function useStakingInfo(pairToFilterBy?: Pair | null): StakingInfo[] {
|
||||
const { chainId, account } = useActiveWeb3React()
|
||||
|
||||
// detect if staking is ended
|
||||
const currentBlockTimestamp = useCurrentBlockTimestamp()
|
||||
|
||||
const info = useMemo(
|
||||
() =>
|
||||
chainId
|
||||
@@ -170,7 +176,12 @@ export function useStakingInfo(pairToFilterBy?: Pair | null): StakingInfo[] {
|
||||
|
||||
const individualRewardRate = getHypotheticalRewardRate(stakedAmount, totalStakedAmount, totalRewardRate)
|
||||
|
||||
const periodFinishMs = periodFinishState.result?.[0]?.mul(1000)?.toNumber()
|
||||
const periodFinishSeconds = periodFinishState.result?.[0]?.toNumber()
|
||||
const periodFinishMs = periodFinishSeconds * 1000
|
||||
|
||||
// compare period end timestamp vs current block timestamp (in seconds)
|
||||
const active =
|
||||
periodFinishSeconds && currentBlockTimestamp ? periodFinishSeconds > currentBlockTimestamp.toNumber() : true
|
||||
|
||||
memo.push({
|
||||
stakingRewardAddress: rewardsAddress,
|
||||
@@ -181,12 +192,24 @@ export function useStakingInfo(pairToFilterBy?: Pair | null): StakingInfo[] {
|
||||
totalRewardRate: totalRewardRate,
|
||||
stakedAmount: stakedAmount,
|
||||
totalStakedAmount: totalStakedAmount,
|
||||
getHypotheticalRewardRate
|
||||
getHypotheticalRewardRate,
|
||||
active
|
||||
})
|
||||
}
|
||||
return memo
|
||||
}, [])
|
||||
}, [balances, chainId, earnedAmounts, info, periodFinishes, rewardRates, rewardsAddresses, totalSupplies, uni])
|
||||
}, [
|
||||
balances,
|
||||
chainId,
|
||||
currentBlockTimestamp,
|
||||
earnedAmounts,
|
||||
info,
|
||||
periodFinishes,
|
||||
rewardRates,
|
||||
rewardsAddresses,
|
||||
totalSupplies,
|
||||
uni
|
||||
])
|
||||
}
|
||||
|
||||
export function useTotalUniEarned(): TokenAmount | undefined {
|
||||
|
||||
@@ -185,3 +185,16 @@ export const HideSmall = styled.span`
|
||||
display: none;
|
||||
`};
|
||||
`
|
||||
|
||||
export const HideExtraSmall = styled.span`
|
||||
${({ theme }) => theme.mediaWidth.upToExtraSmall`
|
||||
display: none;
|
||||
`};
|
||||
`
|
||||
|
||||
export const ExtraSmallOnly = styled.span`
|
||||
display: none;
|
||||
${({ theme }) => theme.mediaWidth.upToExtraSmall`
|
||||
display: block;
|
||||
`};
|
||||
`
|
||||
|
||||
@@ -192,6 +192,10 @@ body {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
a {
|
||||
color: ${colors(false).blue1};
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
36
src/utils/computeUniCirculation.test.ts
Normal file
36
src/utils/computeUniCirculation.test.ts
Normal 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)
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -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
|
||||
}
|
||||
@@ -10,5 +10,10 @@ describe('parseENSAddress', () => {
|
||||
expect(parseENSAddress('abso.lutely.eth')).toEqual({ ensName: 'abso.lutely.eth', ensPath: undefined })
|
||||
expect(parseENSAddress('eth')).toEqual(undefined)
|
||||
expect(parseENSAddress('eth/hello-world')).toEqual(undefined)
|
||||
expect(parseENSAddress('hello-world.eth')).toEqual({ ensName: 'hello-world.eth', ensPath: undefined })
|
||||
expect(parseENSAddress('-prefix-dash.eth')).toEqual(undefined)
|
||||
expect(parseENSAddress('suffix-dash-.eth')).toEqual(undefined)
|
||||
expect(parseENSAddress('it.eth')).toEqual({ ensName: 'it.eth', ensPath: undefined })
|
||||
expect(parseENSAddress('only-single--dash.eth')).toEqual(undefined)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const ENS_NAME_REGEX = /^(([a-zA-Z0-9]+\.)+)eth(\/.*)?$/
|
||||
const ENS_NAME_REGEX = /^(([a-zA-Z0-9]+(-[a-zA-Z0-9]+)*\.)+)eth(\/.*)?$/
|
||||
|
||||
export function parseENSAddress(ensAddress: string): { ensName: string; ensPath: string | undefined } | undefined {
|
||||
const match = ensAddress.match(ENS_NAME_REGEX)
|
||||
if (!match) return undefined
|
||||
return { ensName: `${match[1].toLowerCase()}eth`, ensPath: match[3] }
|
||||
return { ensName: `${match[1].toLowerCase()}eth`, ensPath: match[4] }
|
||||
}
|
||||
|
||||
@@ -22,7 +22,8 @@
|
||||
"jsx": "preserve",
|
||||
"downlevelIteration": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"types": ["react-spring", "jest"]
|
||||
"types": ["react-spring", "jest"],
|
||||
"baseUrl": "src"
|
||||
},
|
||||
"exclude": ["node_modules", "cypress"],
|
||||
"include": ["./src/**/*.ts", "./src/**/*.tsx", "src/components/Confetti/index.js"]
|
||||
|
||||
@@ -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.18":
|
||||
version "1.0.0-beta.18"
|
||||
resolved "https://registry.yarnpkg.com/@uniswap/token-lists/-/token-lists-1.0.0-beta.18.tgz#b6c70b67fa9ee142e6df088e40490ec3a545e7ba"
|
||||
integrity sha512-RuDY36JUc2+Id2Dlpnt+coFfUEf9WkfvR0a3LKvwVgzAfcrd5KvjZg+PhAr4bczFU1aq7rs3/QLgwpsnFYaiPw==
|
||||
|
||||
"@uniswap/v2-core@1.0.0":
|
||||
version "1.0.0"
|
||||
|
||||
Reference in New Issue
Block a user