Commit 03913d9c authored by Moody Salem's avatar Moody Salem

UNI

parent 23c7f4ce
name: Lint
on:
push:
branches:
- master
pull_request:
jobs:
run-linters:
name: Run linters
runs-on: ubuntu-latest
steps:
- name: Check out Git repository
uses: actions/checkout@v2
- name: Set up node
uses: actions/setup-node@v1
with:
node-version: 12
always-auth: true
registry-url: https://registry.npmjs.org
- name: Set output of cache
id: yarn-cache
run: echo "::set-output name=dir::$(yarn cache dir)"
- name: Node dependency cache
uses: actions/cache@v1
with:
path: ${{ steps.yarn-cache.outputs.dir }}
key: yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
yarn-
- name: Install dependencies
run: yarn --frozen-lockfile
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Run linters
uses: wearerequired/lint-action@v1
with:
github_token: ${{ secrets.github_token }}
eslint: true
eslint_extensions: js,jsx,ts,tsx,json
auto_fix: true
...@@ -37,9 +37,13 @@ jobs: ...@@ -37,9 +37,13 @@ jobs:
- uses: actions/setup-node@v1 - uses: actions/setup-node@v1
with: with:
node-version: '12' node-version: '12'
always-auth: true
registry-url: https://registry.npmjs.org
- name: Install dependencies - name: Install dependencies
run: yarn install --frozen-lockfile run: yarn install --frozen-lockfile
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Build the IPFS bundle - name: Build the IPFS bundle
run: yarn build run: yarn build
......
...@@ -6,6 +6,7 @@ on: ...@@ -6,6 +6,7 @@ on:
pull_request: pull_request:
branches: branches:
- master - master
jobs: jobs:
integration-tests: integration-tests:
name: Integration tests name: Integration tests
...@@ -16,6 +17,9 @@ jobs: ...@@ -16,6 +17,9 @@ jobs:
- uses: actions/setup-node@v1 - uses: actions/setup-node@v1
with: with:
node-version: '12' node-version: '12'
always-auth: true
registry-url: https://registry.npmjs.org
- name: Get yarn cache directory path - name: Get yarn cache directory path
id: yarn-cache-dir-path id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)" run: echo "::set-output name=dir::$(yarn cache dir)"
...@@ -27,9 +31,12 @@ jobs: ...@@ -27,9 +31,12 @@ jobs:
restore-keys: | restore-keys: |
${{ runner.os }}-yarn- ${{ runner.os }}-yarn-
- run: yarn install --frozen-lockfile - run: yarn install --frozen-lockfile
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- run: yarn cypress install - run: yarn cypress install
- run: yarn build - run: yarn build
env: env:
CI: false
REACT_APP_NETWORK_URL: "https://mainnet.infura.io/v3/4bf032f2d38a4ed6bb975b80d6340847" REACT_APP_NETWORK_URL: "https://mainnet.infura.io/v3/4bf032f2d38a4ed6bb975b80d6340847"
- run: yarn integration-test - run: yarn integration-test
...@@ -42,28 +49,9 @@ jobs: ...@@ -42,28 +49,9 @@ jobs:
- uses: actions/setup-node@v1 - uses: actions/setup-node@v1
with: with:
node-version: '12' node-version: '12'
- name: Get yarn cache directory path always-auth: true
id: yarn-cache-dir-path registry-url: https://registry.npmjs.org
run: echo "::set-output name=dir::$(yarn cache dir)"
- uses: actions/cache@v1
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- run: yarn install --frozen-lockfile
- run: yarn test
lint:
name: Lint
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v1
- uses: actions/setup-node@v1
with:
node-version: '12'
- name: Get yarn cache directory path - name: Get yarn cache directory path
id: yarn-cache-dir-path id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)" run: echo "::set-output name=dir::$(yarn cache dir)"
...@@ -75,5 +63,7 @@ jobs: ...@@ -75,5 +63,7 @@ jobs:
restore-keys: | restore-keys: |
${{ runner.os }}-yarn- ${{ runner.os }}-yarn-
- run: yarn install --frozen-lockfile - run: yarn install --frozen-lockfile
- run: yarn lint env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- run: yarn test
describe('Swap', () => { describe('Lists', () => {
beforeEach(() => { beforeEach(() => {
cy.visit('/swap') cy.visit('/swap')
}) })
it('list selection persists', () => { it('defaults to uniswap list', () => {
cy.get('#swap-currency-output .open-currency-select-button').click() cy.get('#swap-currency-output .open-currency-select-button').click()
cy.get('#list-introduction-choose-a-list').click() cy.get('#currency-search-selected-list-name').should('contain', 'Uniswap')
cy.get('#list-row-tokens-uniswap-eth .select-button').click()
cy.reload()
cy.get('#swap-currency-output .open-currency-select-button').click()
cy.get('#list-introduction-choose-a-list').should('not.exist')
}) })
it('change list', () => { it('change list', () => {
cy.get('#swap-currency-output .open-currency-select-button').click() cy.get('#swap-currency-output .open-currency-select-button').click()
cy.get('#list-introduction-choose-a-list').click()
cy.get('#list-row-tokens-uniswap-eth .select-button').click()
cy.get('#currency-search-selected-list-name').should('contain', 'Uniswap')
cy.get('#currency-search-change-list-button').click() cy.get('#currency-search-change-list-button').click()
cy.get('#list-row-tokens-1inch-eth .select-button').click() cy.get('#list-row-tokens-1inch-eth .select-button').click()
cy.get('#currency-search-selected-list-name').should('contain', '1inch') cy.get('#currency-search-selected-list-name').should('contain', '1inch')
cy.get('#currency-search-change-list-button').click()
cy.get('#list-row-tokens-uniswap-eth .select-button').click()
cy.get('#currency-search-selected-list-name').should('contain', 'Uniswap')
}) })
}) })
...@@ -34,8 +34,6 @@ describe('Swap', () => { ...@@ -34,8 +34,6 @@ describe('Swap', () => {
it('can swap ETH for DAI', () => { it('can swap ETH for DAI', () => {
cy.get('#swap-currency-output .open-currency-select-button').click() cy.get('#swap-currency-output .open-currency-select-button').click()
cy.get('#list-introduction-choose-a-list').click()
cy.get('#list-row-tokens-uniswap-eth .select-button').click()
cy.get('.token-item-0xc7AD46e0b8a400Bb3C915120d284AafbA8fc4735').should('be.visible') cy.get('.token-item-0xc7AD46e0b8a400Bb3C915120d284AafbA8fc4735').should('be.visible')
cy.get('.token-item-0xc7AD46e0b8a400Bb3C915120d284AafbA8fc4735').click({ force: true }) cy.get('.token-item-0xc7AD46e0b8a400Bb3C915120d284AafbA8fc4735').click({ force: true })
cy.get('#swap-currency-input .token-amount-input').should('be.visible') cy.get('#swap-currency-input .token-amount-input').should('be.visible')
......
...@@ -3,15 +3,8 @@ ...@@ -3,15 +3,8 @@
"strict": true, "strict": true,
"baseUrl": "../node_modules", "baseUrl": "../node_modules",
"target": "es5", "target": "es5",
"lib": [ "lib": ["es5", "dom"],
"es5", "types": ["cypress"]
"dom"
],
"types": [
"cypress"
]
}, },
"include": [ "include": ["**/*.ts"]
"**/*.ts"
]
} }
...@@ -11,6 +11,7 @@ ...@@ -11,6 +11,7 @@
"@reduxjs/toolkit": "^1.3.5", "@reduxjs/toolkit": "^1.3.5",
"@types/jest": "^25.2.1", "@types/jest": "^25.2.1",
"@types/lodash.flatmap": "^4.5.6", "@types/lodash.flatmap": "^4.5.6",
"@types/luxon": "^1.24.4",
"@types/multicodec": "^1.0.0", "@types/multicodec": "^1.0.0",
"@types/node": "^13.13.5", "@types/node": "^13.13.5",
"@types/qs": "^6.9.2", "@types/qs": "^6.9.2",
...@@ -23,10 +24,13 @@ ...@@ -23,10 +24,13 @@
"@types/rebass": "^4.0.5", "@types/rebass": "^4.0.5",
"@types/styled-components": "^5.1.0", "@types/styled-components": "^5.1.0",
"@types/testing-library__cypress": "^5.0.5", "@types/testing-library__cypress": "^5.0.5",
"@types/wcag-contrast": "^3.0.0",
"@typescript-eslint/eslint-plugin": "^2.31.0", "@typescript-eslint/eslint-plugin": "^2.31.0",
"@typescript-eslint/parser": "^2.31.0", "@typescript-eslint/parser": "^2.31.0",
"@uniswap/default-token-list": "^1.3.1", "@uniswap/governance": "^1.0.2",
"@uniswap/sdk": "3.0.3-beta.1", "@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.16",
"@uniswap/v2-core": "1.0.0", "@uniswap/v2-core": "1.0.0",
"@uniswap/v2-periphery": "^1.1.0-beta.0", "@uniswap/v2-periphery": "^1.1.0-beta.0",
...@@ -53,17 +57,21 @@ ...@@ -53,17 +57,21 @@
"inter-ui": "^3.13.1", "inter-ui": "^3.13.1",
"jazzicon": "^1.5.0", "jazzicon": "^1.5.0",
"lodash.flatmap": "^4.5.0", "lodash.flatmap": "^4.5.0",
"luxon": "^1.25.0",
"multicodec": "^2.0.0", "multicodec": "^2.0.0",
"multihashes": "^3.0.1", "multihashes": "^3.0.1",
"node-vibrant": "^3.1.5",
"polished": "^3.3.2", "polished": "^3.3.2",
"prettier": "^1.17.0", "prettier": "^1.17.0",
"qs": "^6.9.4", "qs": "^6.9.4",
"react": "^16.13.1", "react": "^16.13.1",
"react-confetti": "^6.0.0",
"react-device-detect": "^1.6.2", "react-device-detect": "^1.6.2",
"react-dom": "^16.13.1", "react-dom": "^16.13.1",
"react-feather": "^2.0.8", "react-feather": "^2.0.8",
"react-ga": "^2.5.7", "react-ga": "^2.5.7",
"react-i18next": "^10.7.0", "react-i18next": "^10.7.0",
"react-markdown": "^4.3.1",
"react-popper": "^2.2.3", "react-popper": "^2.2.3",
"react-redux": "^7.2.0", "react-redux": "^7.2.0",
"react-router-dom": "^5.0.0", "react-router-dom": "^5.0.0",
...@@ -77,7 +85,9 @@ ...@@ -77,7 +85,9 @@
"serve": "^11.3.0", "serve": "^11.3.0",
"start-server-and-test": "^1.11.0", "start-server-and-test": "^1.11.0",
"styled-components": "^4.2.0", "styled-components": "^4.2.0",
"typescript": "^3.8.3" "typescript": "^3.8.3",
"use-count-up": "^2.2.5",
"wcag-contrast": "^3.0.0"
}, },
"resolutions": { "resolutions": {
"@walletconnect/web3-provider": "1.1.1-alpha.0" "@walletconnect/web3-provider": "1.1.1-alpha.0"
...@@ -87,11 +97,13 @@ ...@@ -87,11 +97,13 @@
"build": "react-scripts build", "build": "react-scripts build",
"test": "react-scripts test --env=jsdom", "test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject", "eject": "react-scripts eject",
"lint": "eslint 'src/**/*.{js,jsx,ts,tsx}'",
"integration-test": "start-server-and-test 'serve build -l 3000' http://localhost:3000 'cypress run'" "integration-test": "start-server-and-test 'serve build -l 3000' http://localhost:3000 'cypress run'"
}, },
"eslintConfig": { "eslintConfig": {
"extends": "react-app" "extends": "react-app",
"ignorePatterns": [
"node_modules"
]
}, },
"browserslist": { "browserslist": {
"production": [ "production": [
......
public/favicon.png

6.91 KB | W: | H:

public/favicon.png

2.77 KB | W: | H:

public/favicon.png
public/favicon.png
public/favicon.png
public/favicon.png
  • 2-up
  • Swipe
  • Onion skin
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
...@@ -16,7 +16,7 @@ const Base = styled(RebassButton)<{ ...@@ -16,7 +16,7 @@ const Base = styled(RebassButton)<{
width: ${({ width }) => (width ? width : '100%')}; width: ${({ width }) => (width ? width : '100%')};
font-weight: 500; font-weight: 500;
text-align: center; text-align: center;
border-radius: 20px; border-radius: 12px;
border-radius: ${({ borderRadius }) => borderRadius && borderRadius}; border-radius: ${({ borderRadius }) => borderRadius && borderRadius};
outline: none; outline: none;
border: 1px solid transparent; border: 1px solid transparent;
...@@ -110,28 +110,31 @@ export const ButtonGray = styled(Base)` ...@@ -110,28 +110,31 @@ export const ButtonGray = styled(Base)`
` `
export const ButtonSecondary = styled(Base)` export const ButtonSecondary = styled(Base)`
background-color: ${({ theme }) => theme.primary5}; border: 1px solid ${({ theme }) => theme.primary4};
color: ${({ theme }) => theme.primaryText1}; color: ${({ theme }) => theme.primary1};
background-color: transparent;
font-size: 16px; font-size: 16px;
border-radius: 8px; border-radius: 12px;
padding: ${({ padding }) => (padding ? padding : '10px')}; padding: ${({ padding }) => (padding ? padding : '10px')};
&:focus { &:focus {
box-shadow: 0 0 0 1pt ${({ theme }) => theme.primary4}; box-shadow: 0 0 0 1pt ${({ theme }) => theme.primary4};
background-color: ${({ theme }) => theme.primary4}; border: 1px solid ${({ theme }) => theme.primary3};
} }
&:hover { &:hover {
background-color: ${({ theme }) => theme.primary4}; border: 1px solid ${({ theme }) => theme.primary3};
} }
&:active { &:active {
box-shadow: 0 0 0 1pt ${({ theme }) => theme.primary4}; box-shadow: 0 0 0 1pt ${({ theme }) => theme.primary4};
background-color: ${({ theme }) => theme.primary4}; border: 1px solid ${({ theme }) => theme.primary3};
} }
&:disabled { &:disabled {
background-color: ${({ theme }) => theme.primary5};
opacity: 50%; opacity: 50%;
cursor: auto; cursor: auto;
} }
a:hover {
text-decoration: none;
}
` `
export const ButtonPink = styled(Base)` export const ButtonPink = styled(Base)`
...@@ -184,13 +187,13 @@ export const ButtonEmpty = styled(Base)` ...@@ -184,13 +187,13 @@ export const ButtonEmpty = styled(Base)`
align-items: center; align-items: center;
&:focus { &:focus {
background-color: ${({ theme }) => theme.advancedBG}; text-decoration: underline;
} }
&:hover { &:hover {
background-color: ${({ theme }) => theme.advancedBG}; text-decoration: underline;
} }
&:active { &:active {
background-color: ${({ theme }) => theme.advancedBG}; text-decoration: underline;
} }
&:disabled { &:disabled {
opacity: 50%; opacity: 50%;
......
import React from 'react'
import ReactConfetti from 'react-confetti'
import { useWindowSize } from '../../hooks/useWindowSize'
// eslint-disable-next-line react/prop-types
export default function Confetti({ start, variant }: { start: boolean; variant?: string }) {
const { width, height } = useWindowSize()
const _variant = variant ? variant : height && width && height > 1.5 * width ? 'bottom' : variant
return start && width && height ? (
<ReactConfetti
style={{ zIndex: 1401 }}
numberOfPieces={400}
recycle={false}
run={true}
width={width}
height={height}
confettiSource={{
h: height,
w: width,
x: 0,
y: _variant === 'top' ? height * 0.25 : _variant === 'bottom' ? height * 0.75 : height * 0.5
}}
initialVelocityX={15}
initialVelocityY={30}
gravity={0.45}
tweenDuration={100}
wind={0.05}
/>
) : null
}
...@@ -129,6 +129,7 @@ interface CurrencyInputPanelProps { ...@@ -129,6 +129,7 @@ interface CurrencyInputPanelProps {
otherCurrency?: Currency | null otherCurrency?: Currency | null
id: string id: string
showCommonBases?: boolean showCommonBases?: boolean
customBalanceText?: string
} }
export default function CurrencyInputPanel({ export default function CurrencyInputPanel({
...@@ -145,7 +146,8 @@ export default function CurrencyInputPanel({ ...@@ -145,7 +146,8 @@ export default function CurrencyInputPanel({
hideInput = false, hideInput = false,
otherCurrency, otherCurrency,
id, id,
showCommonBases showCommonBases,
customBalanceText
}: CurrencyInputPanelProps) { }: CurrencyInputPanelProps) {
const { t } = useTranslation() const { t } = useTranslation()
...@@ -176,7 +178,7 @@ export default function CurrencyInputPanel({ ...@@ -176,7 +178,7 @@ export default function CurrencyInputPanel({
style={{ display: 'inline', cursor: 'pointer' }} style={{ display: 'inline', cursor: 'pointer' }}
> >
{!hideBalance && !!currency && selectedCurrencyBalance {!hideBalance && !!currency && selectedCurrencyBalance
? 'Balance: ' + selectedCurrencyBalance?.toSignificant(6) ? (customBalanceText ?? 'Balance: ') + selectedCurrencyBalance?.toSignificant(6)
: ' -'} : ' -'}
</TYPE.body> </TYPE.body>
)} )}
......
...@@ -20,6 +20,8 @@ const StyledEthereumLogo = styled.img<{ size: string }>` ...@@ -20,6 +20,8 @@ const StyledEthereumLogo = styled.img<{ size: string }>`
const StyledLogo = styled(Logo)<{ size: string }>` const StyledLogo = styled(Logo)<{ size: string }>`
width: ${({ size }) => size}; width: ${({ size }) => size};
height: ${({ size }) => size}; height: ${({ size }) => size};
border-radius: ${({ size }) => size};
box-shadow: 0px 6px 10px rgba(0, 0, 0, 0.075);
` `
export default function CurrencyLogo({ export default function CurrencyLogo({
......
...@@ -22,7 +22,7 @@ const HigherLogo = styled(CurrencyLogo)` ...@@ -22,7 +22,7 @@ const HigherLogo = styled(CurrencyLogo)`
` `
const CoveredLogo = styled(CurrencyLogo)<{ sizeraw: number }>` const CoveredLogo = styled(CurrencyLogo)<{ sizeraw: number }>`
position: absolute; position: absolute;
left: ${({ sizeraw }) => (sizeraw / 2).toString() + 'px'}; left: ${({ sizeraw }) => '-' + (sizeraw / 2).toString() + 'px'} !important;
` `
export default function DoubleCurrencyLogo({ export default function DoubleCurrencyLogo({
......
import React from 'react'
import { CurrencyAmount, Fraction, JSBI } from '@uniswap/sdk'
const CURRENCY_AMOUNT_MIN = new Fraction(JSBI.BigInt(1), JSBI.BigInt(1000000))
export default function FormattedCurrencyAmount({
currencyAmount,
significantDigits = 4
}: {
currencyAmount: CurrencyAmount
significantDigits?: number
}) {
return (
<>
{currencyAmount.equalTo(JSBI.BigInt(0))
? '0'
: currencyAmount.greaterThan(CURRENCY_AMOUNT_MIN)
? currencyAmount.toSignificant(significantDigits)
: `<${CURRENCY_AMOUNT_MIN.toSignificant(1)}`}
</>
)
}
import React, { useState, useEffect } from 'react'
import styled, { keyframes } from 'styled-components'
import { TYPE, ExternalLink } from '../../theme'
import { useBlockNumber } from '../../state/application/hooks'
import { getEtherscanLink } from '../../utils'
import { useActiveWeb3React } from '../../hooks'
const StyledPolling = styled.div`
position: fixed;
display: flex;
right: 0;
bottom: 0;
padding: 1rem;
color: white;
transition: opacity 0.25s ease;
color: ${({ theme }) => theme.green1};
:hover {
opacity: 1;
}
${({ theme }) => theme.mediaWidth.upToMedium`
display: none;
`}
`
const StyledPollingDot = styled.div`
width: 8px;
height: 8px;
min-height: 8px;
min-width: 8px;
margin-left: 0.5rem;
margin-top: 3px;
border-radius: 50%;
position: relative;
background-color: ${({ theme }) => theme.green1};
`
const rotate360 = keyframes`
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
`
const Spinner = styled.div`
animation: ${rotate360} 1s cubic-bezier(0.83, 0, 0.17, 1) infinite;
transform: translateZ(0);
border-top: 1px solid transparent;
border-right: 1px solid transparent;
border-bottom: 1px solid transparent;
border-left: 2px solid ${({ theme }) => theme.green1};
background: transparent;
width: 14px;
height: 14px;
border-radius: 50%;
position: relative;
left: -3px;
top: -3px;
`
export default function Polling() {
const { chainId } = useActiveWeb3React()
const blockNumber = useBlockNumber()
const [isMounted, setIsMounted] = useState(true)
useEffect(
() => {
const timer1 = setTimeout(() => setIsMounted(true), 1000)
// this will clear Timeout when component unmount like in willComponentUnmount
return () => {
setIsMounted(false)
clearTimeout(timer1)
}
},
[blockNumber] //useEffect will run only one time
//if you pass a value to array, like this [data] than clearTimeout will run every time this value changes (useEffect re-run)
)
return (
<ExternalLink href={chainId && blockNumber ? getEtherscanLink(chainId, blockNumber.toString(), 'block') : ''}>
<StyledPolling>
<TYPE.small style={{ opacity: isMounted ? '0.2' : '0.6' }}>{blockNumber}</TYPE.small>
<StyledPollingDot>{!isMounted && <Spinner />}</StyledPollingDot>
</StyledPolling>
</ExternalLink>
)
}
import React from 'react'
import styled from 'styled-components'
import { AlertTriangle, X } from 'react-feather'
import { useURLWarningToggle, useURLWarningVisible } from '../../state/user/hooks'
import { isMobile } from 'react-device-detect'
const PhishAlert = styled.div<{ isActive: any }>`
width: 100%;
padding: 6px 6px;
background-color: ${({ theme }) => theme.blue1};
color: white;
font-size: 11px;
justify-content: space-between;
align-items: center;
display: ${({ isActive }) => (isActive ? 'flex' : 'none')};
`
export const StyledClose = styled(X)`
:hover {
cursor: pointer;
}
`
export default function URLWarning() {
const toggleURLWarning = useURLWarningToggle()
const showURLWarning = useURLWarningVisible()
return isMobile ? (
<PhishAlert isActive={showURLWarning}>
<div style={{ display: 'flex' }}>
<AlertTriangle style={{ marginRight: 6 }} size={12} /> Make sure the URL is
<code style={{ padding: '0 4px', display: 'inline', fontWeight: 'bold' }}>app.uniswap.org</code>
</div>
<StyledClose size={12} onClick={toggleURLWarning} />
</PhishAlert>
) : window.location.hostname === 'app.uniswap.org' ? (
<PhishAlert isActive={showURLWarning}>
<div style={{ display: 'flex' }}>
<AlertTriangle style={{ marginRight: 6 }} size={12} /> Always make sure the URL is
<code style={{ padding: '0 4px', display: 'inline', fontWeight: 'bold' }}>app.uniswap.org</code> - bookmark it
to be safe.
</div>
<StyledClose size={12} onClick={toggleURLWarning} />
</PhishAlert>
) : null
}
import { ChainId, TokenAmount } from '@uniswap/sdk'
import React, { useMemo } from 'react'
import { X } from 'react-feather'
import styled from 'styled-components'
import tokenLogo from '../../assets/images/token-logo.png'
import { UNI } from '../../constants'
import { useTotalSupply } from '../../data/TotalSupply'
import { useActiveWeb3React } from '../../hooks'
import useCurrentBlockTimestamp from '../../hooks/useCurrentBlockTimestamp'
import { useTotalUniEarned } from '../../state/stake/hooks'
import { useAggregateUniBalance, useTokenBalance } from '../../state/wallet/hooks'
import { ExternalLink, StyledInternalLink, TYPE, UniTokenAnimated } from '../../theme'
import { computeUniCirculation } from '../../utils/computeUniCirculation'
import useUSDCPrice from '../../utils/useUSDCPrice'
import { AutoColumn } from '../Column'
import { RowBetween } from '../Row'
import { Break, CardBGImage, CardNoise, CardSection, DataCard } from '../earn/styled'
const ContentWrapper = styled(AutoColumn)`
width: 100%;
`
const ModalUpper = styled(DataCard)`
box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.1);
background: radial-gradient(76.02% 75.41% at 1.84% 0%, #ff007a 0%, #021d43 100%);
padding: 0.5rem;
`
const StyledClose = styled(X)`
position: absolute;
right: 16px;
top: 16px;
:hover {
cursor: pointer;
}
`
/**
* Content for balance stats modal
*/
export default function UniBalanceContent({ setShowUniBalanceModal }: { setShowUniBalanceModal: any }) {
const { account, chainId } = useActiveWeb3React()
const uni = chainId ? UNI[chainId] : undefined
const total = useAggregateUniBalance()
const uniBalance: TokenAmount | undefined = useTokenBalance(account ?? undefined, uni)
const uniUnHarvested: TokenAmount | undefined = useTotalUniEarned()
const totalSupply: TokenAmount | undefined = useTotalSupply(uni)
const uniPrice = useUSDCPrice(uni)
const blockTimestamp = useCurrentBlockTimestamp()
const circulation: TokenAmount | undefined = useMemo(
() =>
blockTimestamp && uni && chainId === ChainId.MAINNET ? computeUniCirculation(uni, blockTimestamp) : totalSupply,
[blockTimestamp, chainId, totalSupply, uni]
)
return (
<ContentWrapper gap="lg">
<ModalUpper>
<CardBGImage />
<CardNoise />
<CardSection gap="md">
<RowBetween>
<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 />
<CardSection gap="sm">
<AutoColumn gap="md">
<RowBetween>
<TYPE.white color="white">UNI price:</TYPE.white>
<TYPE.white color="white">${uniPrice?.toFixed(2) ?? '-'}</TYPE.white>
</RowBetween>
<RowBetween>
<TYPE.white color="white">UNI in circulation:</TYPE.white>
<TYPE.white color="white">{circulation?.toFixed(0, { groupSeparator: ',' })}</TYPE.white>
</RowBetween>
<RowBetween>
<TYPE.white color="white">Total Supply</TYPE.white>
<TYPE.white color="white">{totalSupply?.toFixed(0, { groupSeparator: ',' })}</TYPE.white>
</RowBetween>
{uni && uni.chainId === ChainId.MAINNET ? (
<ExternalLink href={`https://uniswap.info/token/${uni.address}`}>View UNI Analytics</ExternalLink>
) : null}
</AutoColumn>
</CardSection>
</ModalUpper>
</ContentWrapper>
)
}
import { stringify } from 'qs'
import React, { useCallback, useMemo } from 'react'
import { Link, useLocation } from 'react-router-dom'
import styled from 'styled-components'
import useParsedQueryString from '../../hooks/useParsedQueryString'
import useToggledVersion, { Version } from '../../hooks/useToggledVersion'
import { MouseoverTooltip } from '../Tooltip'
const VersionLabel = styled.span<{ enabled: boolean }>`
padding: 0.35rem 0.6rem;
border-radius: 12px;
background: ${({ theme, enabled }) => (enabled ? theme.primary1 : 'none')};
color: ${({ theme, enabled }) => (enabled ? theme.white : theme.text1)};
font-size: 1rem;
font-weight: ${({ enabled }) => (enabled ? '500' : '400')};
:hover {
user-select: ${({ enabled }) => (enabled ? 'none' : 'initial')};
background: ${({ theme, enabled }) => (enabled ? theme.primary1 : 'none')};
color: ${({ theme, enabled }) => (enabled ? theme.white : theme.text1)};
}
`
interface VersionToggleProps extends React.ComponentProps<typeof Link> {
enabled: boolean
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const VersionToggle = styled(({ enabled, ...rest }: VersionToggleProps) => <Link {...rest} />)<VersionToggleProps>`
border-radius: 12px;
opacity: ${({ enabled }) => (enabled ? 1 : 0.5)};
cursor: ${({ enabled }) => (enabled ? 'pointer' : 'default')};
background: ${({ theme }) => theme.bg3};
color: ${({ theme }) => theme.primary1};
display: flex;
width: fit-content;
margin-left: 0.5rem;
text-decoration: none;
:hover {
text-decoration: none;
}
`
export default function VersionSwitch() {
const version = useToggledVersion()
const location = useLocation()
const query = useParsedQueryString()
const versionSwitchAvailable = location.pathname === '/swap' || location.pathname === '/send'
const toggleDest = useMemo(() => {
return versionSwitchAvailable
? {
...location,
search: `?${stringify({ ...query, use: version === Version.v1 ? undefined : Version.v1 })}`
}
: location
}, [location, query, version, versionSwitchAvailable])
const handleClick = useCallback(
e => {
if (!versionSwitchAvailable) e.preventDefault()
},
[versionSwitchAvailable]
)
const toggle = (
<VersionToggle enabled={versionSwitchAvailable} to={toggleDest} onClick={handleClick}>
<VersionLabel enabled={version === Version.v2 || !versionSwitchAvailable}>V2</VersionLabel>
<VersionLabel enabled={version === Version.v1 && versionSwitchAvailable}>V1</VersionLabel>
</VersionToggle>
)
return versionSwitchAvailable ? (
toggle
) : (
<MouseoverTooltip text="This page is only compatible with Uniswap V2.">{toggle}</MouseoverTooltip>
)
}
This diff is collapsed.
...@@ -24,7 +24,15 @@ const StyledSVG = styled.svg<{ size: string; stroke?: string }>` ...@@ -24,7 +24,15 @@ const StyledSVG = styled.svg<{ size: string; stroke?: string }>`
* Takes in custom size and stroke for circle color, default to primary color as fill, * Takes in custom size and stroke for circle color, default to primary color as fill,
* need ...rest for layered styles on top * need ...rest for layered styles on top
*/ */
export default function Loader({ size = '16px', stroke, ...rest }: { size?: string; stroke?: string }) { export default function Loader({
size = '16px',
stroke,
...rest
}: {
size?: string
stroke?: string
[k: string]: any
}) {
return ( return (
<StyledSVG viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" size={size} stroke={stroke} {...rest}> <StyledSVG viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" size={size} stroke={stroke} {...rest}>
<path <path
......
import React, { useRef } from 'react' import React, { useRef } from 'react'
import { Info, BookOpen, Code, PieChart, MessageCircle } from 'react-feather' import { BookOpen, Code, Info, MessageCircle, PieChart } from 'react-feather'
import styled from 'styled-components' import styled from 'styled-components'
import { ReactComponent as MenuIcon } from '../../assets/images/menu.svg' import { ReactComponent as MenuIcon } from '../../assets/images/menu.svg'
import { useActiveWeb3React } from '../../hooks'
import { useOnClickOutside } from '../../hooks/useOnClickOutside' import { useOnClickOutside } from '../../hooks/useOnClickOutside'
import useToggle from '../../hooks/useToggle' import { ApplicationModal } from '../../state/application/actions'
import { useModalOpen, useToggleModal } from '../../state/application/hooks'
import { ExternalLink } from '../../theme' import { ExternalLink } from '../../theme'
import { ButtonPrimary } from '../Button'
const StyledMenuIcon = styled(MenuIcon)` const StyledMenuIcon = styled(MenuIcon)`
path { path {
...@@ -53,15 +56,19 @@ const MenuFlyout = styled.span` ...@@ -53,15 +56,19 @@ const MenuFlyout = styled.span`
background-color: ${({ theme }) => theme.bg3}; background-color: ${({ theme }) => theme.bg3};
box-shadow: 0px 0px 1px rgba(0, 0, 0, 0.01), 0px 4px 8px rgba(0, 0, 0, 0.04), 0px 16px 24px rgba(0, 0, 0, 0.04), box-shadow: 0px 0px 1px rgba(0, 0, 0, 0.01), 0px 4px 8px rgba(0, 0, 0, 0.04), 0px 16px 24px rgba(0, 0, 0, 0.04),
0px 24px 32px rgba(0, 0, 0, 0.01); 0px 24px 32px rgba(0, 0, 0, 0.01);
border-radius: 0.5rem; border-radius: 12px;
padding: 0.5rem; padding: 0.5rem;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
font-size: 1rem; font-size: 1rem;
position: absolute; position: absolute;
top: 3rem; top: 4rem;
right: 0rem; right: 0rem;
z-index: 100; z-index: 100;
${({ theme }) => theme.mediaWidth.upToMedium`
top: -17.25rem;
`};
` `
const MenuItem = styled(ExternalLink)` const MenuItem = styled(ExternalLink)`
...@@ -81,10 +88,13 @@ const MenuItem = styled(ExternalLink)` ...@@ -81,10 +88,13 @@ const MenuItem = styled(ExternalLink)`
const CODE_LINK = 'https://github.com/Uniswap/uniswap-interface' const CODE_LINK = 'https://github.com/Uniswap/uniswap-interface'
export default function Menu() { export default function Menu() {
const node = useRef<HTMLDivElement>() const { account } = useActiveWeb3React()
const [open, toggle] = useToggle(false)
const node = useRef<HTMLDivElement>()
const open = useModalOpen(ApplicationModal.MENU)
const toggle = useToggleModal(ApplicationModal.MENU)
useOnClickOutside(node, open ? toggle : undefined) useOnClickOutside(node, open ? toggle : undefined)
const openClaimModal = useToggleModal(ApplicationModal.ADDRESS_CLAIM)
return ( return (
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/30451 // https://github.com/DefinitelyTyped/DefinitelyTyped/issues/30451
...@@ -92,6 +102,7 @@ export default function Menu() { ...@@ -92,6 +102,7 @@ export default function Menu() {
<StyledMenuButton onClick={toggle}> <StyledMenuButton onClick={toggle}>
<StyledMenuIcon /> <StyledMenuIcon />
</StyledMenuButton> </StyledMenuButton>
{open && ( {open && (
<MenuFlyout> <MenuFlyout>
<MenuItem id="link" href="https://uniswap.org/"> <MenuItem id="link" href="https://uniswap.org/">
...@@ -114,6 +125,11 @@ export default function Menu() { ...@@ -114,6 +125,11 @@ export default function Menu() {
<PieChart size={14} /> <PieChart size={14} />
Analytics Analytics
</MenuItem> </MenuItem>
{account && (
<ButtonPrimary onClick={openClaimModal} padding="8px 16px" width="100%" borderRadius="12px" mt="0.5rem">
Claim UNI
</ButtonPrimary>
)}
</MenuFlyout> </MenuFlyout>
)} )}
</StyledMenu> </StyledMenu>
......
...@@ -31,14 +31,16 @@ const StyledDialogContent = styled(({ minHeight, maxHeight, mobile, isOpen, ...r ...@@ -31,14 +31,16 @@ const StyledDialogContent = styled(({ minHeight, maxHeight, mobile, isOpen, ...r
)).attrs({ )).attrs({
'aria-label': 'dialog' 'aria-label': 'dialog'
})` })`
overflow-y: ${({ mobile }) => (mobile ? 'scroll' : 'hidden')};
&[data-reach-dialog-content] { &[data-reach-dialog-content] {
margin: 0 0 2rem 0; margin: 0 0 2rem 0;
border: 1px solid ${({ theme }) => theme.bg1};
background-color: ${({ theme }) => theme.bg1}; background-color: ${({ theme }) => theme.bg1};
box-shadow: 0 4px 8px 0 ${({ theme }) => transparentize(0.95, theme.shadow1)}; box-shadow: 0 4px 8px 0 ${({ theme }) => transparentize(0.95, theme.shadow1)};
padding: 0px; padding: 0px;
width: 50vw; width: 50vw;
overflow: hidden; overflow-y: ${({ mobile }) => (mobile ? 'scroll' : 'hidden')};
overflow-x: hidden;
align-self: ${({ mobile }) => (mobile ? 'flex-end' : 'center')}; align-self: ${({ mobile }) => (mobile ? 'flex-end' : 'center')};
...@@ -85,7 +87,7 @@ export default function Modal({ ...@@ -85,7 +87,7 @@ export default function Modal({
isOpen, isOpen,
onDismiss, onDismiss,
minHeight = false, minHeight = false,
maxHeight = 50, maxHeight = 90,
initialFocusRef, initialFocusRef,
children children
}: ModalProps) { }: ModalProps) {
......
import React, { useContext } from 'react'
import { useActiveWeb3React } from '../../hooks'
import { AutoColumn, ColumnCenter } from '../Column'
import styled, { ThemeContext } from 'styled-components'
import { RowBetween } from '../Row'
import { TYPE, CloseIcon, CustomLightSpinner } from '../../theme'
import { ArrowUpCircle } from 'react-feather'
import Circle from '../../assets/images/blue-loader.svg'
import { getEtherscanLink } from '../../utils'
import { ExternalLink } from '../../theme/components'
const ConfirmOrLoadingWrapper = styled.div`
width: 100%;
padding: 24px;
`
const ConfirmedIcon = styled(ColumnCenter)`
padding: 60px 0;
`
export function LoadingView({ children, onDismiss }: { children: any; onDismiss: () => void }) {
return (
<ConfirmOrLoadingWrapper>
<RowBetween>
<div />
<CloseIcon onClick={onDismiss} />
</RowBetween>
<ConfirmedIcon>
<CustomLightSpinner src={Circle} alt="loader" size={'90px'} />
</ConfirmedIcon>
<AutoColumn gap="100px" justify={'center'}>
{children}
<TYPE.subHeader>Confirm this transaction in your wallet</TYPE.subHeader>
</AutoColumn>
</ConfirmOrLoadingWrapper>
)
}
export function SubmittedView({
children,
onDismiss,
hash
}: {
children: any
onDismiss: () => void
hash: string | undefined
}) {
const theme = useContext(ThemeContext)
const { chainId } = useActiveWeb3React()
return (
<ConfirmOrLoadingWrapper>
<RowBetween>
<div />
<CloseIcon onClick={onDismiss} />
</RowBetween>
<ConfirmedIcon>
<ArrowUpCircle strokeWidth={0.5} size={90} color={theme.primary1} />
</ConfirmedIcon>
<AutoColumn gap="100px" justify={'center'}>
{children}
{chainId && hash && (
<ExternalLink href={getEtherscanLink(chainId, hash, 'transaction')} style={{ marginLeft: '4px' }}>
<TYPE.subHeader>View transaction on Etherscan</TYPE.subHeader>
</ExternalLink>
)}
</AutoColumn>
</ConfirmOrLoadingWrapper>
)
}
...@@ -55,7 +55,7 @@ const StyledArrowLeft = styled(ArrowLeft)` ...@@ -55,7 +55,7 @@ const StyledArrowLeft = styled(ArrowLeft)`
export function SwapPoolTabs({ active }: { active: 'swap' | 'pool' }) { export function SwapPoolTabs({ active }: { active: 'swap' | 'pool' }) {
const { t } = useTranslation() const { t } = useTranslation()
return ( return (
<Tabs style={{ marginBottom: '20px' }}> <Tabs style={{ marginBottom: '20px', display: 'none' }}>
<StyledNavLink id={`swap-nav-link`} to={'/swap'} isActive={() => active === 'swap'}> <StyledNavLink id={`swap-nav-link`} to={'/swap'} isActive={() => active === 'swap'}>
{t('swap')} {t('swap')}
</StyledNavLink> </StyledNavLink>
...@@ -80,14 +80,14 @@ export function FindPoolTabs() { ...@@ -80,14 +80,14 @@ export function FindPoolTabs() {
) )
} }
export function AddRemoveTabs({ adding }: { adding: boolean }) { export function AddRemoveTabs({ adding, creating }: { adding: boolean; creating: boolean }) {
return ( return (
<Tabs> <Tabs>
<RowBetween style={{ padding: '1rem' }}> <RowBetween style={{ padding: '1rem' }}>
<HistoryLink to="/pool"> <HistoryLink to="/pool">
<StyledArrowLeft /> <StyledArrowLeft />
</HistoryLink> </HistoryLink>
<ActiveText>{adding ? 'Add' : 'Remove'} Liquidity</ActiveText> <ActiveText>{creating ? 'Create a pair' : adding ? 'Add Liquidity' : 'Remove Liquidity'}</ActiveText>
<QuestionHelper <QuestionHelper
text={ text={
adding adding
......
import { TokenAmount } from '@uniswap/sdk'
import React, { useEffect } from 'react'
import { X } from 'react-feather'
import styled, { keyframes } from 'styled-components'
import tokenLogo from '../../assets/images/token-logo.png'
import { ButtonPrimary } from '../../components/Button'
import { useActiveWeb3React } from '../../hooks'
import { ApplicationModal } from '../../state/application/actions'
import {
useModalOpen,
useShowClaimPopup,
useToggleSelfClaimModal,
useToggleShowClaimPopup
} from '../../state/application/hooks'
import { useUserHasAvailableClaim, useUserUnclaimedAmount } from '../../state/claim/hooks'
import { TYPE } from '../../theme'
import { AutoColumn } from '../Column'
import { CardBGImage, CardNoise } from '../earn/styled'
const ClaimPopup = styled(AutoColumn)`
background: radial-gradient(76.02% 75.41% at 1.84% 0%, #ff007a 0%, #021d43 100%);
border-radius: 20px;
padding: 1.5rem;
overflow: hidden;
position: relative;
max-width: 360px;
box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.1);
`
const StyledClose = styled(X)`
position: absolute;
right: 10px;
top: 10px;
:hover {
cursor: pointer;
}
`
const rotate = keyframes`
0% {
transform: perspective(1000px) rotateY(0deg);
}
100% {
transform: perspective(1000px) rotateY(360deg);
}
`
const UniToken = styled.img`
animation: ${rotate} 5s cubic-bezier(0.83, 0, 0.17, 1) infinite;
`
export default function ClaimPopup() {
const { account } = useActiveWeb3React()
// dont store these in persisted state yet
const showClaimPopup: boolean = useShowClaimPopup()
const toggleShowClaimPopup = useToggleShowClaimPopup()
// toggle for showing this modal
const showClaimModal = useModalOpen(ApplicationModal.SELF_CLAIM)
const toggleSelfClaimModal = useToggleSelfClaimModal()
// const userHasAvailableclaim = useUserHasAvailableClaim()
const userHasAvailableclaim: boolean = useUserHasAvailableClaim(account)
const unclaimedAmount: TokenAmount | undefined = useUserUnclaimedAmount(account)
// listen for available claim and show popup if needed
useEffect(() => {
if (userHasAvailableclaim) {
toggleShowClaimPopup()
}
// the toggleShowClaimPopup function changes every time the popup changes, so this will cause an infinite loop.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [userHasAvailableclaim])
return (
<>
{showClaimPopup && !showClaimModal && (
<ClaimPopup gap="md">
<CardBGImage />
<CardNoise />
<StyledClose stroke="white" onClick={toggleShowClaimPopup} />
<AutoColumn style={{ padding: '2rem 0', zIndex: 10 }} justify="center">
<UniToken width="48px" src={tokenLogo} />{' '}
<TYPE.white style={{ marginTop: '1rem' }} fontSize={36} fontWeight={600}>
{unclaimedAmount?.toFixed(0, { groupSeparator: ',' } ?? '-')} UNI
</TYPE.white>
<TYPE.white style={{ paddingTop: '1.25rem', textAlign: 'center' }} fontWeight={600} color="white">
<span role="img" aria-label="party">
🎉
</span>{' '}
UNI has arrived{' '}
<span role="img" aria-label="party">
🎉
</span>
</TYPE.white>
<TYPE.subHeader style={{ paddingTop: '0.5rem', textAlign: 'center' }} color="white">
{`Thanks for being part of the Uniswap community <3`}
</TYPE.subHeader>
</AutoColumn>
<AutoColumn style={{ zIndex: 10 }} justify="center">
<ButtonPrimary padding="8px" borderRadius="8px" width={'fit-content'} onClick={toggleSelfClaimModal}>
Claim your UNI tokens
</ButtonPrimary>
</AutoColumn>
</ClaimPopup>
)}
</>
)
}
...@@ -3,6 +3,8 @@ import styled from 'styled-components' ...@@ -3,6 +3,8 @@ import styled from 'styled-components'
import { useActivePopups } from '../../state/application/hooks' import { useActivePopups } from '../../state/application/hooks'
import { AutoColumn } from '../Column' import { AutoColumn } from '../Column'
import PopupItem from './PopupItem' import PopupItem from './PopupItem'
import ClaimPopup from './ClaimPopup'
import { useURLWarningVisible } from '../../state/user/hooks'
const MobilePopupWrapper = styled.div<{ height: string | number }>` const MobilePopupWrapper = styled.div<{ height: string | number }>`
position: relative; position: relative;
...@@ -29,13 +31,13 @@ const MobilePopupInner = styled.div` ...@@ -29,13 +31,13 @@ const MobilePopupInner = styled.div`
} }
` `
const FixedPopupColumn = styled(AutoColumn)` const FixedPopupColumn = styled(AutoColumn)<{ extraPadding: boolean }>`
position: fixed; position: fixed;
top: 64px; top: ${({ extraPadding }) => (extraPadding ? '108px' : '88px')};
right: 1rem; right: 1rem;
max-width: 355px !important; max-width: 355px !important;
width: 100%; width: 100%;
z-index: 2; z-index: 3;
${({ theme }) => theme.mediaWidth.upToSmall` ${({ theme }) => theme.mediaWidth.upToSmall`
display: none; display: none;
...@@ -46,9 +48,12 @@ export default function Popups() { ...@@ -46,9 +48,12 @@ export default function Popups() {
// get all popups // get all popups
const activePopups = useActivePopups() const activePopups = useActivePopups()
const urlWarningActive = useURLWarningVisible()
return ( return (
<> <>
<FixedPopupColumn gap="20px"> <FixedPopupColumn gap="20px" extraPadding={urlWarningActive}>
<ClaimPopup />
{activePopups.map(item => ( {activePopups.map(item => (
<PopupItem key={item.key} content={item.content} popKey={item.key} removeAfterMs={item.removeAfterMs} /> <PopupItem key={item.key} content={item.content} popKey={item.key} removeAfterMs={item.removeAfterMs} />
))} ))}
......
...@@ -9,16 +9,20 @@ import { useTotalSupply } from '../../data/TotalSupply' ...@@ -9,16 +9,20 @@ import { useTotalSupply } from '../../data/TotalSupply'
import { useActiveWeb3React } from '../../hooks' import { useActiveWeb3React } from '../../hooks'
import { useTokenBalance } from '../../state/wallet/hooks' import { useTokenBalance } from '../../state/wallet/hooks'
import { ExternalLink } from '../../theme' import { ExternalLink, TYPE } from '../../theme'
import { currencyId } from '../../utils/currencyId' import { currencyId } from '../../utils/currencyId'
import { unwrappedToken } from '../../utils/wrappedCurrency' import { unwrappedToken } from '../../utils/wrappedCurrency'
import { ButtonSecondary } from '../Button' import { ButtonPrimary, ButtonSecondary, ButtonEmpty } from '../Button'
import { transparentize } from 'polished'
import { CardNoise } from '../earn/styled'
import Card, { GreyCard } from '../Card' import { useColor } from '../../hooks/useColor'
import Card, { GreyCard, LightCard } from '../Card'
import { AutoColumn } from '../Column' import { AutoColumn } from '../Column'
import CurrencyLogo from '../CurrencyLogo' import CurrencyLogo from '../CurrencyLogo'
import DoubleCurrencyLogo from '../DoubleLogo' import DoubleCurrencyLogo from '../DoubleLogo'
import { AutoRow, RowBetween, RowFixed } from '../Row' import { RowBetween, RowFixed } from '../Row'
import { Dots } from '../swap/styleds' import { Dots } from '../swap/styleds'
export const FixedHeightRow = styled(RowBetween)` export const FixedHeightRow = styled(RowBetween)`
...@@ -26,11 +30,18 @@ export const FixedHeightRow = styled(RowBetween)` ...@@ -26,11 +30,18 @@ export const FixedHeightRow = styled(RowBetween)`
` `
export const HoverCard = styled(Card)` export const HoverCard = styled(Card)`
border: 1px solid ${({ theme }) => theme.bg2}; border: 1px solid transparent;
:hover { :hover {
border: 1px solid ${({ theme }) => darken(0.06, theme.bg2)}; border: 1px solid ${({ theme }) => darken(0.06, theme.bg2)};
} }
` `
const StyledPositionCard = styled(LightCard)<{ bgColor: any }>`
border: none;
background: ${({ theme, bgColor }) =>
`radial-gradient(91.85% 100% at 1.84% 0%, ${transparentize(0.8, bgColor)} 0%, ${theme.bg3} 100%) `};
position: relative;
overflow: hidden;
`
interface PositionCardProps { interface PositionCardProps {
pair: Pair pair: Pair
...@@ -49,6 +60,11 @@ export function MinimalPositionCard({ pair, showUnwrapped = false, border }: Pos ...@@ -49,6 +60,11 @@ export function MinimalPositionCard({ pair, showUnwrapped = false, border }: Pos
const userPoolBalance = useTokenBalance(account ?? undefined, pair.liquidityToken) const userPoolBalance = useTokenBalance(account ?? undefined, pair.liquidityToken)
const totalPoolTokens = useTotalSupply(pair.liquidityToken) const totalPoolTokens = useTotalSupply(pair.liquidityToken)
const poolTokenPercentage =
!!userPoolBalance && !!totalPoolTokens && JSBI.greaterThanOrEqual(totalPoolTokens.raw, userPoolBalance.raw)
? new Percent(userPoolBalance.raw, totalPoolTokens.raw)
: undefined
const [token0Deposited, token1Deposited] = const [token0Deposited, token1Deposited] =
!!pair && !!pair &&
!!totalPoolTokens && !!totalPoolTokens &&
...@@ -63,7 +79,7 @@ export function MinimalPositionCard({ pair, showUnwrapped = false, border }: Pos ...@@ -63,7 +79,7 @@ export function MinimalPositionCard({ pair, showUnwrapped = false, border }: Pos
return ( return (
<> <>
{userPoolBalance && ( {userPoolBalance && JSBI.greaterThan(userPoolBalance.raw, JSBI.BigInt(0)) ? (
<GreyCard border={border}> <GreyCard border={border}>
<AutoColumn gap="12px"> <AutoColumn gap="12px">
<FixedHeightRow> <FixedHeightRow>
...@@ -88,12 +104,20 @@ export function MinimalPositionCard({ pair, showUnwrapped = false, border }: Pos ...@@ -88,12 +104,20 @@ export function MinimalPositionCard({ pair, showUnwrapped = false, border }: Pos
</FixedHeightRow> </FixedHeightRow>
<AutoColumn gap="4px"> <AutoColumn gap="4px">
<FixedHeightRow> <FixedHeightRow>
<Text color="#888D9B" fontSize={16} fontWeight={500}> <Text fontSize={16} fontWeight={500}>
Your pool share:
</Text>
<Text fontSize={16} fontWeight={500}>
{poolTokenPercentage ? poolTokenPercentage.toFixed(6) + '%' : '-'}
</Text>
</FixedHeightRow>
<FixedHeightRow>
<Text fontSize={16} fontWeight={500}>
{currency0.symbol}: {currency0.symbol}:
</Text> </Text>
{token0Deposited ? ( {token0Deposited ? (
<RowFixed> <RowFixed>
<Text color="#888D9B" fontSize={16} fontWeight={500} marginLeft={'6px'}> <Text fontSize={16} fontWeight={500} marginLeft={'6px'}>
{token0Deposited?.toSignificant(6)} {token0Deposited?.toSignificant(6)}
</Text> </Text>
</RowFixed> </RowFixed>
...@@ -102,12 +126,12 @@ export function MinimalPositionCard({ pair, showUnwrapped = false, border }: Pos ...@@ -102,12 +126,12 @@ export function MinimalPositionCard({ pair, showUnwrapped = false, border }: Pos
)} )}
</FixedHeightRow> </FixedHeightRow>
<FixedHeightRow> <FixedHeightRow>
<Text color="#888D9B" fontSize={16} fontWeight={500}> <Text fontSize={16} fontWeight={500}>
{currency1.symbol}: {currency1.symbol}:
</Text> </Text>
{token1Deposited ? ( {token1Deposited ? (
<RowFixed> <RowFixed>
<Text color="#888D9B" fontSize={16} fontWeight={500} marginLeft={'6px'}> <Text fontSize={16} fontWeight={500} marginLeft={'6px'}>
{token1Deposited?.toSignificant(6)} {token1Deposited?.toSignificant(6)}
</Text> </Text>
</RowFixed> </RowFixed>
...@@ -118,6 +142,16 @@ export function MinimalPositionCard({ pair, showUnwrapped = false, border }: Pos ...@@ -118,6 +142,16 @@ export function MinimalPositionCard({ pair, showUnwrapped = false, border }: Pos
</AutoColumn> </AutoColumn>
</AutoColumn> </AutoColumn>
</GreyCard> </GreyCard>
) : (
<LightCard>
<TYPE.subHeader style={{ textAlign: 'center' }}>
<span role="img" aria-label="wizard-icon">
⭐️
</span>{' '}
By adding liquidity you&apos;ll earn 0.3% of all trades on this pair proportional to your share of the pool.
Fees are added to the pool, accrue in real time and can be claimed by withdrawing your liquidity.
</TYPE.subHeader>
</LightCard>
)} )}
</> </>
) )
...@@ -151,26 +185,53 @@ export default function FullPositionCard({ pair, border }: PositionCardProps) { ...@@ -151,26 +185,53 @@ export default function FullPositionCard({ pair, border }: PositionCardProps) {
] ]
: [undefined, undefined] : [undefined, undefined]
const backgroundColor = useColor(pair?.token0)
return ( return (
<HoverCard border={border}> <StyledPositionCard border={border} bgColor={backgroundColor}>
<CardNoise />
<AutoColumn gap="12px"> <AutoColumn gap="12px">
<FixedHeightRow onClick={() => setShowMore(!showMore)} style={{ cursor: 'pointer' }}> <FixedHeightRow>
<RowFixed> <RowFixed>
<DoubleCurrencyLogo currency0={currency0} currency1={currency1} margin={true} size={20} /> <DoubleCurrencyLogo currency0={currency0} currency1={currency1} margin={true} size={20} />
<Text fontWeight={500} fontSize={20}> <Text fontWeight={500} fontSize={20}>
{!currency0 || !currency1 ? <Dots>Loading</Dots> : `${currency0.symbol}/${currency1.symbol}`} {!currency0 || !currency1 ? <Dots>Loading</Dots> : `${currency0.symbol}/${currency1.symbol}`}
</Text> </Text>
</RowFixed> </RowFixed>
<RowFixed>
<RowFixed gap="8px">
<ButtonEmpty
padding="6px 8px"
borderRadius="12px"
width="fit-content"
onClick={() => setShowMore(!showMore)}
>
{showMore ? ( {showMore ? (
<>
{' '}
Manage
<ChevronUp size="20" style={{ marginLeft: '10px' }} /> <ChevronUp size="20" style={{ marginLeft: '10px' }} />
</>
) : ( ) : (
<>
Manage
<ChevronDown size="20" style={{ marginLeft: '10px' }} /> <ChevronDown size="20" style={{ marginLeft: '10px' }} />
</>
)} )}
</ButtonEmpty>
</RowFixed> </RowFixed>
</FixedHeightRow> </FixedHeightRow>
{showMore && ( {showMore && (
<AutoColumn gap="8px"> <AutoColumn gap="8px">
<FixedHeightRow>
<Text fontSize={16} fontWeight={500}>
Your pool tokens:
</Text>
<Text fontSize={16} fontWeight={500}>
{userPoolBalance ? userPoolBalance.toSignificant(4) : '-'}
</Text>
</FixedHeightRow>
<FixedHeightRow> <FixedHeightRow>
<RowFixed> <RowFixed>
<Text fontSize={16} fontWeight={500}> <Text fontSize={16} fontWeight={500}>
...@@ -206,14 +267,7 @@ export default function FullPositionCard({ pair, border }: PositionCardProps) { ...@@ -206,14 +267,7 @@ export default function FullPositionCard({ pair, border }: PositionCardProps) {
'-' '-'
)} )}
</FixedHeightRow> </FixedHeightRow>
<FixedHeightRow>
<Text fontSize={16} fontWeight={500}>
Your pool tokens:
</Text>
<Text fontSize={16} fontWeight={500}>
{userPoolBalance ? userPoolBalance.toSignificant(4) : '-'}
</Text>
</FixedHeightRow>
<FixedHeightRow> <FixedHeightRow>
<Text fontSize={16} fontWeight={500}> <Text fontSize={16} fontWeight={500}>
Your pool share: Your pool share:
...@@ -223,22 +277,37 @@ export default function FullPositionCard({ pair, border }: PositionCardProps) { ...@@ -223,22 +277,37 @@ export default function FullPositionCard({ pair, border }: PositionCardProps) {
</Text> </Text>
</FixedHeightRow> </FixedHeightRow>
<AutoRow justify="center" marginTop={'10px'}> <ButtonSecondary padding="8px" borderRadius="8px">
<ExternalLink href={`https://uniswap.info/pair/${pair.liquidityToken.address}`}> <ExternalLink
View pool information ↗ style={{ width: '100%', textAlign: 'center' }}
href={`https://uniswap.info/account/${account}`}
>
View accrued fees and analytics<span style={{ fontSize: '11px' }}></span>
</ExternalLink> </ExternalLink>
</AutoRow> </ButtonSecondary>
<RowBetween marginTop="10px"> <RowBetween marginTop="10px">
<ButtonSecondary as={Link} to={`/add/${currencyId(currency0)}/${currencyId(currency1)}`} width="48%"> <ButtonPrimary
padding="8px"
borderRadius="8px"
as={Link}
to={`/add/${currencyId(currency0)}/${currencyId(currency1)}`}
width="48%"
>
Add Add
</ButtonSecondary> </ButtonPrimary>
<ButtonSecondary as={Link} width="48%" to={`/remove/${currencyId(currency0)}/${currencyId(currency1)}`}> <ButtonPrimary
padding="8px"
borderRadius="8px"
as={Link}
width="48%"
to={`/remove/${currencyId(currency0)}/${currencyId(currency1)}`}
>
Remove Remove
</ButtonSecondary> </ButtonPrimary>
</RowBetween> </RowBetween>
</AutoColumn> </AutoColumn>
)} )}
</AutoColumn> </AutoColumn>
</HoverCard> </StyledPositionCard>
) )
} }
...@@ -4,9 +4,7 @@ import { RowBetween } from '../Row' ...@@ -4,9 +4,7 @@ import { RowBetween } from '../Row'
import { AutoColumn } from '../Column' import { AutoColumn } from '../Column'
import { transparentize } from 'polished' import { transparentize } from 'polished'
const Wrapper = styled(AutoColumn)` const Wrapper = styled(AutoColumn)``
margin-top: 1.25rem;
`
const Grouping = styled(RowBetween)` const Grouping = styled(RowBetween)`
width: 50%; width: 50%;
...@@ -32,20 +30,23 @@ const CircleRow = styled.div` ...@@ -32,20 +30,23 @@ const CircleRow = styled.div`
align-items: center; align-items: center;
` `
const Connector = styled.div<{ prevConfirmed?: boolean }>` const Connector = styled.div<{ prevConfirmed?: boolean; disabled?: boolean }>`
width: 100%; width: 100%;
height: 2px; height: 2px;
background-color: ; background-color: ;
background: linear-gradient( background: linear-gradient(
90deg, 90deg,
${({ theme, prevConfirmed }) => transparentize(0.5, prevConfirmed ? theme.green1 : theme.primary1)} 0%, ${({ theme, prevConfirmed, disabled }) =>
${({ theme, prevConfirmed }) => (prevConfirmed ? theme.primary1 : theme.bg4)} 80% disabled ? theme.bg4 : transparentize(0.5, prevConfirmed ? theme.green1 : theme.primary1)}
0%,
${({ theme, prevConfirmed, disabled }) => (disabled ? theme.bg4 : prevConfirmed ? theme.primary1 : theme.bg4)} 80%
); );
opacity: 0.6; opacity: 0.6;
` `
interface ProgressCirclesProps { interface ProgressCirclesProps {
steps: boolean[] steps: boolean[]
disabled?: boolean
} }
/** /**
...@@ -58,21 +59,21 @@ interface ProgressCirclesProps { ...@@ -58,21 +59,21 @@ interface ProgressCirclesProps {
* *
* @param steps array of booleans where true means step is complete * @param steps array of booleans where true means step is complete
*/ */
export default function ProgressCircles({ steps }: ProgressCirclesProps) { export default function ProgressCircles({ steps, disabled = false, ...rest }: ProgressCirclesProps) {
return ( return (
<Wrapper justify={'center'}> <Wrapper justify={'center'} {...rest}>
<Grouping> <Grouping>
{steps.map((step, i) => { {steps.map((step, i) => {
return ( return (
<CircleRow key={i}> <CircleRow key={i}>
<Circle confirmed={step} disabled={!steps[i - 1] && i !== 0}> <Circle confirmed={step} disabled={disabled || (!steps[i - 1] && i !== 0)}>
{step ? '' : i + 1} {step ? '' : i + 1}
</Circle> </Circle>
<Connector prevConfirmed={step} /> <Connector prevConfirmed={step} disabled={disabled} />
</CircleRow> </CircleRow>
) )
})} })}
<Circle disabled={!steps[steps.length - 1]}>{steps.length + 1}</Circle> <Circle disabled={disabled || !steps[steps.length - 1]}>{steps.length + 1}</Circle>
</Grouping> </Grouping>
</Wrapper> </Wrapper>
) )
......
...@@ -22,6 +22,31 @@ const QuestionWrapper = styled.div` ...@@ -22,6 +22,31 @@ const QuestionWrapper = styled.div`
} }
` `
const LightQuestionWrapper = styled.div`
display: flex;
align-items: center;
justify-content: center;
padding: 0.2rem;
border: none;
background: none;
outline: none;
cursor: default;
border-radius: 36px;
width: 24px;
height: 24px;
background-color: rgba(255, 255, 255, 0.1);
color: ${({ theme }) => theme.white};
:hover,
:focus {
opacity: 0.7;
}
`
const QuestionMark = styled.span`
font-size: 1rem;
`
export default function QuestionHelper({ text }: { text: string }) { export default function QuestionHelper({ text }: { text: string }) {
const [show, setShow] = useState<boolean>(false) const [show, setShow] = useState<boolean>(false)
...@@ -38,3 +63,20 @@ export default function QuestionHelper({ text }: { text: string }) { ...@@ -38,3 +63,20 @@ export default function QuestionHelper({ text }: { text: string }) {
</span> </span>
) )
} }
export function LightQuestionHelper({ text }: { text: string }) {
const [show, setShow] = useState<boolean>(false)
const open = useCallback(() => setShow(true), [setShow])
const close = useCallback(() => setShow(false), [setShow])
return (
<span style={{ marginLeft: 4 }}>
<Tooltip text={text} show={show}>
<LightQuestionWrapper onClick={open} onMouseEnter={open} onMouseLeave={close}>
<QuestionMark>?</QuestionMark>
</LightQuestionWrapper>
</Tooltip>
</span>
)
}
...@@ -2,10 +2,8 @@ import { Currency } from '@uniswap/sdk' ...@@ -2,10 +2,8 @@ import { Currency } from '@uniswap/sdk'
import React, { useCallback, useEffect, useState } from 'react' import React, { useCallback, useEffect, useState } from 'react'
import ReactGA from 'react-ga' import ReactGA from 'react-ga'
import useLast from '../../hooks/useLast' import useLast from '../../hooks/useLast'
import { useSelectedListUrl } from '../../state/lists/hooks'
import Modal from '../Modal' import Modal from '../Modal'
import { CurrencySearch } from './CurrencySearch' import { CurrencySearch } from './CurrencySearch'
import ListIntroduction from './ListIntroduction'
import { ListSelect } from './ListSelect' import { ListSelect } from './ListSelect'
interface CurrencySearchModalProps { interface CurrencySearchModalProps {
...@@ -56,19 +54,11 @@ export default function CurrencySearchModal({ ...@@ -56,19 +54,11 @@ export default function CurrencySearchModal({
}) })
setListView(false) setListView(false)
}, []) }, [])
const handleSelectListIntroduction = useCallback(() => {
setListView(true)
}, [])
const selectedListUrl = useSelectedListUrl()
const noListSelected = !selectedListUrl
return ( return (
<Modal isOpen={isOpen} onDismiss={onDismiss} maxHeight={90} minHeight={listView ? 40 : noListSelected ? 0 : 80}> <Modal isOpen={isOpen} onDismiss={onDismiss} maxHeight={80} minHeight={listView ? 40 : 80}>
{listView ? ( {listView ? (
<ListSelect onDismiss={onDismiss} onBack={handleClickBack} /> <ListSelect onDismiss={onDismiss} onBack={handleClickBack} />
) : noListSelected ? (
<ListIntroduction onSelectList={handleSelectListIntroduction} />
) : ( ) : (
<CurrencySearch <CurrencySearch
isOpen={isOpen} isOpen={isOpen}
......
import React from 'react'
import { Text } from 'rebass'
import { ExternalLink } from '../../theme'
import { ButtonPrimary } from '../Button'
import { OutlineCard } from '../Card'
import Column, { AutoColumn } from '../Column'
import { PaddedColumn } from './styleds'
import { useDarkModeManager } from '../../state/user/hooks'
import listLight from '../../assets/images/token-list/lists-light.png'
import listDark from '../../assets/images/token-list/lists-dark.png'
export default function ListIntroduction({ onSelectList }: { onSelectList: () => void }) {
const [isDark] = useDarkModeManager()
return (
<Column style={{ width: '100%', flex: '1 1' }}>
<PaddedColumn>
<AutoColumn gap="14px">
<img
style={{ width: '120px', margin: '0 auto' }}
src={isDark ? listDark : listLight}
alt="token-list-preview"
/>
<img
style={{ width: '100%', borderRadius: '12px' }}
src="https://cloudflare-ipfs.com/ipfs/QmRf1rAJcZjV3pwKTHfPdJh4RxR8yvRHkdLjZCsmp7T6hA"
alt="token-list-preview"
/>
<Text style={{ marginBottom: '8px', textAlign: 'center' }}>
Uniswap now supports token lists. You can add your own custom lists via IPFS, HTTPS and ENS.{' '}
</Text>
<ButtonPrimary onClick={onSelectList} id="list-introduction-choose-a-list">
Choose a list
</ButtonPrimary>
<OutlineCard style={{ marginBottom: '8px', padding: '1rem' }}>
<Text fontWeight={400} fontSize={14} style={{ textAlign: 'center' }}>
Token lists are an{' '}
<ExternalLink href="https://github.com/uniswap/token-lists">open specification</ExternalLink>. Check out{' '}
<ExternalLink href="https://tokenlists.org">tokenlists.org</ExternalLink> to learn more.
</Text>
</OutlineCard>
</AutoColumn>
</PaddedColumn>
</Column>
)
}
...@@ -236,7 +236,6 @@ const ListRow = memo(function ListRow({ listUrl, onBack }: { listUrl: string; on ...@@ -236,7 +236,6 @@ const ListRow = memo(function ListRow({ listUrl, onBack }: { listUrl: string; on
}) })
const AddListButton = styled(ButtonSecondary)` const AddListButton = styled(ButtonSecondary)`
/* height: 1.8rem; */
max-width: 4rem; max-width: 4rem;
margin-left: 1rem; margin-left: 1rem;
border-radius: 12px; border-radius: 12px;
......
import React, { useRef, useContext, useState } from 'react' import React, { useContext, useRef, useState } from 'react'
import { Settings, X } from 'react-feather' import { Settings, X } from 'react-feather'
import styled from 'styled-components' import { Text } from 'rebass'
import styled, { ThemeContext } from 'styled-components'
import { useOnClickOutside } from '../../hooks/useOnClickOutside' import { useOnClickOutside } from '../../hooks/useOnClickOutside'
import { ApplicationModal } from '../../state/application/actions'
import { useModalOpen, useToggleSettingsMenu } from '../../state/application/hooks'
import { import {
useUserSlippageTolerance, useDarkModeManager,
useExpertModeManager, useExpertModeManager,
useUserDeadline, useUserTransactionTTL,
useDarkModeManager useUserSlippageTolerance
} from '../../state/user/hooks' } from '../../state/user/hooks'
import TransactionSettings from '../TransactionSettings'
import { RowFixed, RowBetween } from '../Row'
import { TYPE } from '../../theme' import { TYPE } from '../../theme'
import QuestionHelper from '../QuestionHelper'
import Toggle from '../Toggle'
import { ThemeContext } from 'styled-components'
import { AutoColumn } from '../Column'
import { ButtonError } from '../Button' import { ButtonError } from '../Button'
import { useSettingsMenuOpen, useToggleSettingsMenu } from '../../state/application/hooks' import { AutoColumn } from '../Column'
import { Text } from 'rebass'
import Modal from '../Modal' import Modal from '../Modal'
import QuestionHelper from '../QuestionHelper'
import { RowBetween, RowFixed } from '../Row'
import Toggle from '../Toggle'
import TransactionSettings from '../TransactionSettings'
const StyledMenuIcon = styled(Settings)` const StyledMenuIcon = styled(Settings)`
height: 20px; height: 20px;
...@@ -85,18 +85,15 @@ const StyledMenu = styled.div` ...@@ -85,18 +85,15 @@ const StyledMenu = styled.div`
const MenuFlyout = styled.span` const MenuFlyout = styled.span`
min-width: 20.125rem; min-width: 20.125rem;
background-color: ${({ theme }) => theme.bg1}; background-color: ${({ theme }) => theme.bg2};
box-shadow: 0px 0px 1px rgba(0, 0, 0, 0.01), 0px 4px 8px rgba(0, 0, 0, 0.04), 0px 16px 24px rgba(0, 0, 0, 0.04), box-shadow: 0px 0px 1px rgba(0, 0, 0, 0.01), 0px 4px 8px rgba(0, 0, 0, 0.04), 0px 16px 24px rgba(0, 0, 0, 0.04),
0px 24px 32px rgba(0, 0, 0, 0.01); 0px 24px 32px rgba(0, 0, 0, 0.01);
border-radius: 12px;
border: 1px solid ${({ theme }) => theme.bg3};
border-radius: 0.5rem;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
font-size: 1rem; font-size: 1rem;
position: absolute; position: absolute;
top: 3rem; top: 4rem;
right: 0rem; right: 0rem;
z-index: 100; z-index: 100;
...@@ -104,6 +101,11 @@ const MenuFlyout = styled.span` ...@@ -104,6 +101,11 @@ const MenuFlyout = styled.span`
min-width: 18.125rem; min-width: 18.125rem;
right: -46px; right: -46px;
`}; `};
${({ theme }) => theme.mediaWidth.upToMedium`
min-width: 18.125rem;
top: -22rem;
`};
` `
const Break = styled.div` const Break = styled.div`
...@@ -123,13 +125,13 @@ const ModalContentWrapper = styled.div` ...@@ -123,13 +125,13 @@ const ModalContentWrapper = styled.div`
export default function SettingsTab() { export default function SettingsTab() {
const node = useRef<HTMLDivElement>() const node = useRef<HTMLDivElement>()
const open = useSettingsMenuOpen() const open = useModalOpen(ApplicationModal.SETTINGS)
const toggle = useToggleSettingsMenu() const toggle = useToggleSettingsMenu()
const theme = useContext(ThemeContext) const theme = useContext(ThemeContext)
const [userSlippageTolerance, setUserslippageTolerance] = useUserSlippageTolerance() const [userSlippageTolerance, setUserslippageTolerance] = useUserSlippageTolerance()
const [deadline, setDeadline] = useUserDeadline() const [ttl, setTtl] = useUserTransactionTTL()
const [expertMode, toggleExpertMode] = useExpertModeManager() const [expertMode, toggleExpertMode] = useExpertModeManager()
...@@ -182,13 +184,13 @@ export default function SettingsTab() { ...@@ -182,13 +184,13 @@ export default function SettingsTab() {
</Modal> </Modal>
<StyledMenuButton onClick={toggle} id="open-settings-dialog-button"> <StyledMenuButton onClick={toggle} id="open-settings-dialog-button">
<StyledMenuIcon /> <StyledMenuIcon />
{expertMode && ( {expertMode ? (
<EmojiWrapper> <EmojiWrapper>
<span role="img" aria-label="wizard-icon"> <span role="img" aria-label="wizard-icon">
🧙 🧙
</span> </span>
</EmojiWrapper> </EmojiWrapper>
)} ) : null}
</StyledMenuButton> </StyledMenuButton>
{open && ( {open && (
<MenuFlyout> <MenuFlyout>
...@@ -199,8 +201,8 @@ export default function SettingsTab() { ...@@ -199,8 +201,8 @@ export default function SettingsTab() {
<TransactionSettings <TransactionSettings
rawSlippage={userSlippageTolerance} rawSlippage={userSlippageTolerance}
setRawSlippage={setUserslippageTolerance} setRawSlippage={setUserslippageTolerance}
deadline={deadline} deadline={ttl}
setDeadline={setDeadline} setDeadline={setTtl}
/> />
<Text fontWeight={600} fontSize={14}> <Text fontWeight={600} fontSize={14}>
Interface Settings Interface Settings
......
...@@ -6,19 +6,34 @@ const ToggleElement = styled.span<{ isActive?: boolean; isOnSwitch?: boolean }>` ...@@ -6,19 +6,34 @@ const ToggleElement = styled.span<{ isActive?: boolean; isOnSwitch?: boolean }>`
border-radius: 14px; border-radius: 14px;
background: ${({ theme, isActive, isOnSwitch }) => (isActive ? (isOnSwitch ? theme.primary1 : theme.text4) : 'none')}; background: ${({ theme, isActive, isOnSwitch }) => (isActive ? (isOnSwitch ? theme.primary1 : theme.text4) : 'none')};
color: ${({ theme, isActive, isOnSwitch }) => (isActive ? (isOnSwitch ? theme.white : theme.text2) : theme.text3)}; color: ${({ theme, isActive, isOnSwitch }) => (isActive ? (isOnSwitch ? theme.white : theme.text2) : theme.text3)};
font-size: 0.825rem; font-size: 1rem;
font-weight: 400; font-weight: 400;
padding: 0.35rem 0.6rem;
border-radius: 12px;
background: ${({ theme, isActive, isOnSwitch }) => (isActive ? (isOnSwitch ? theme.primary1 : theme.text4) : 'none')};
color: ${({ theme, isActive, isOnSwitch }) => (isActive ? (isOnSwitch ? theme.white : theme.text2) : theme.text2)};
font-size: 1rem;
font-weight: ${({ isOnSwitch }) => (isOnSwitch ? '500' : '400')};
:hover {
user-select: ${({ isOnSwitch }) => (isOnSwitch ? 'none' : 'initial')};
background: ${({ theme, isActive, isOnSwitch }) =>
isActive ? (isOnSwitch ? theme.primary1 : theme.text3) : 'none'};
color: ${({ theme, isActive, isOnSwitch }) => (isActive ? (isOnSwitch ? theme.white : theme.text2) : theme.text3)};
}
` `
const StyledToggle = styled.button<{ isActive?: boolean; activeElement?: boolean }>` const StyledToggle = styled.button<{ isActive?: boolean; activeElement?: boolean }>`
border-radius: 16px; border-radius: 12px;
border: 1px solid ${({ theme, isActive }) => (isActive ? theme.primary5 : theme.text4)}; border: none;
/* border: 1px solid ${({ theme, isActive }) => (isActive ? theme.primary5 : theme.text4)}; */
background: ${({ theme }) => theme.bg3};
display: flex; display: flex;
width: fit-content; width: fit-content;
cursor: pointer; cursor: pointer;
outline: none; outline: none;
padding: 0; padding: 0;
background-color: transparent; /* background-color: transparent; */
` `
export interface ToggleProps { export interface ToggleProps {
......
...@@ -4,7 +4,7 @@ import styled, { ThemeContext } from 'styled-components' ...@@ -4,7 +4,7 @@ import styled, { ThemeContext } from 'styled-components'
import Modal from '../Modal' import Modal from '../Modal'
import { ExternalLink } from '../../theme' import { ExternalLink } from '../../theme'
import { Text } from 'rebass' import { Text } from 'rebass'
import { CloseIcon, Spinner } from '../../theme/components' import { CloseIcon, CustomLightSpinner } from '../../theme/components'
import { RowBetween } from '../Row' import { RowBetween } from '../Row'
import { AlertTriangle, ArrowUpCircle } from 'react-feather' import { AlertTriangle, ArrowUpCircle } from 'react-feather'
import { ButtonPrimary } from '../Button' import { ButtonPrimary } from '../Button'
...@@ -31,11 +31,6 @@ const ConfirmedIcon = styled(ColumnCenter)` ...@@ -31,11 +31,6 @@ const ConfirmedIcon = styled(ColumnCenter)`
padding: 60px 0; padding: 60px 0;
` `
const CustomLightSpinner = styled(Spinner)<{ size: string }>`
height: ${({ size }) => size};
width: ${({ size }) => size};
`
function ConfirmationPendingContent({ onDismiss, pendingText }: { onDismiss: () => void; pendingText: string }) { function ConfirmationPendingContent({ onDismiss, pendingText }: { onDismiss: () => void; pendingText: string }) {
return ( return (
<Wrapper> <Wrapper>
...@@ -90,7 +85,6 @@ function TransactionSubmittedContent({ ...@@ -90,7 +85,6 @@ function TransactionSubmittedContent({
<Text fontWeight={500} fontSize={20}> <Text fontWeight={500} fontSize={20}>
Transaction Submitted Transaction Submitted
</Text> </Text>
{chainId && hash && ( {chainId && hash && (
<ExternalLink href={getEtherscanLink(chainId, hash, 'transaction')}> <ExternalLink href={getEtherscanLink(chainId, hash, 'transaction')}>
<Text fontWeight={500} fontSize={14} color={theme.primary1}> <Text fontWeight={500} fontSize={14} color={theme.primary1}>
......
...@@ -23,9 +23,9 @@ const FancyButton = styled.button` ...@@ -23,9 +23,9 @@ const FancyButton = styled.button`
align-items: center; align-items: center;
height: 2rem; height: 2rem;
border-radius: 36px; border-radius: 36px;
font-size: 12px; font-size: 1rem;
width: auto; width: auto;
min-width: 3rem; min-width: 3.5rem;
border: 1px solid ${({ theme }) => theme.bg3}; border: 1px solid ${({ theme }) => theme.bg3};
outline: none; outline: none;
background: ${({ theme }) => theme.bg1}; background: ${({ theme }) => theme.bg1};
......
import React, { useState, useEffect } from 'react' import { AbstractConnector } from '@web3-react/abstract-connector'
import { UnsupportedChainIdError, useWeb3React } from '@web3-react/core'
import { WalletConnectConnector } from '@web3-react/walletconnect-connector'
import React, { useEffect, useState } from 'react'
import { isMobile } from 'react-device-detect'
import ReactGA from 'react-ga' import ReactGA from 'react-ga'
import styled from 'styled-components' import styled from 'styled-components'
import { isMobile } from 'react-device-detect' import MetamaskIcon from '../../assets/images/metamask.png'
import { UnsupportedChainIdError, useWeb3React } from '@web3-react/core' import { ReactComponent as Close } from '../../assets/images/x.svg'
import { fortmatic, injected, portis } from '../../connectors'
import { OVERLAY_READY } from '../../connectors/Fortmatic'
import { SUPPORTED_WALLETS } from '../../constants'
import usePrevious from '../../hooks/usePrevious' import usePrevious from '../../hooks/usePrevious'
import { useWalletModalOpen, useWalletModalToggle } from '../../state/application/hooks' import { ApplicationModal } from '../../state/application/actions'
import { useModalOpen, useWalletModalToggle } from '../../state/application/hooks'
import { ExternalLink } from '../../theme'
import AccountDetails from '../AccountDetails'
import Modal from '../Modal' import Modal from '../Modal'
import AccountDetails from '../AccountDetails'
import PendingView from './PendingView'
import Option from './Option' import Option from './Option'
import { SUPPORTED_WALLETS } from '../../constants' import PendingView from './PendingView'
import { ExternalLink } from '../../theme'
import MetamaskIcon from '../../assets/images/metamask.png'
import { ReactComponent as Close } from '../../assets/images/x.svg'
import { injected, fortmatic, portis } from '../../connectors'
import { OVERLAY_READY } from '../../connectors/Fortmatic'
import { WalletConnectConnector } from '@web3-react/walletconnect-connector'
import { AbstractConnector } from '@web3-react/abstract-connector'
const CloseIcon = styled.div` const CloseIcon = styled.div`
position: absolute; position: absolute;
...@@ -133,7 +134,7 @@ export default function WalletModal({ ...@@ -133,7 +134,7 @@ export default function WalletModal({
const [pendingError, setPendingError] = useState<boolean>() const [pendingError, setPendingError] = useState<boolean>()
const walletModalOpen = useWalletModalOpen() const walletModalOpen = useModalOpen(ApplicationModal.WALLET)
const toggleWalletModal = useWalletModalToggle() const toggleWalletModal = useWalletModalToggle()
const previousAccount = usePrevious(account) const previousAccount = usePrevious(account)
......
import React, { useState } from 'react'
import Modal from '../Modal'
import { AutoColumn, ColumnCenter } from '../Column'
import styled from 'styled-components'
import { DataCard, CardSection, Break } from '../earn/styled'
import { RowBetween } from '../Row'
import { TYPE, ExternalLink, CloseIcon, CustomLightSpinner, UniTokenAnimated } from '../../theme'
import { ButtonPrimary } from '../Button'
import { useClaimCallback, useUserUnclaimedAmount, useUserHasAvailableClaim } from '../../state/claim/hooks'
import tokenLogo from '../../assets/images/token-logo.png'
import Circle from '../../assets/images/blue-loader.svg'
import { Text } from 'rebass'
import AddressInputPanel from '../AddressInputPanel'
import useENS from '../../hooks/useENS'
import { useActiveWeb3React } from '../../hooks'
import { isAddress } from 'ethers/lib/utils'
import Confetti from '../Confetti'
import { CardNoise, CardBGImage, CardBGImageSmaller } from '../earn/styled'
import { useIsTransactionPending } from '../../state/transactions/hooks'
import { TokenAmount } from '@uniswap/sdk'
import { getEtherscanLink, shortenAddress } from '../../utils'
const ContentWrapper = styled(AutoColumn)`
width: 100%;
`
const ModalUpper = styled(DataCard)`
box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.1);
background: radial-gradient(76.02% 75.41% at 1.84% 0%, #ff007a 0%, #021d43 100%);
`
const ConfirmOrLoadingWrapper = styled.div<{ activeBG: boolean }>`
width: 100%;
padding: 24px;
position: relative;
background: ${({ activeBG }) =>
activeBG &&
'radial-gradient(76.02% 75.41% at 1.84% 0%, rgba(255, 0, 122, 0.2) 0%, rgba(33, 114, 229, 0.2) 100%), #FFFFFF;'};
`
const ConfirmedIcon = styled(ColumnCenter)`
padding: 60px 0;
`
export default function AddressClaimModal({ isOpen, onDismiss }: { isOpen: boolean; onDismiss: () => void }) {
const { chainId } = useActiveWeb3React()
// state for smart contract input
const [typed, setTyped] = useState('')
function handleRecipientType(val: string) {
setTyped(val)
}
// monitor for third party recipient of claim
const { address: parsedAddress } = useENS(typed)
// used for UI loading states
const [attempting, setAttempting] = useState<boolean>(false)
// monitor the status of the claim from contracts and txns
const { claimCallback } = useClaimCallback(parsedAddress)
const unclaimedAmount: TokenAmount | undefined = useUserUnclaimedAmount(parsedAddress)
// check if the user has something available
const hasAvailableClaim = useUserHasAvailableClaim(parsedAddress)
const [hash, setHash] = useState<string | undefined>()
// monitor the status of the claim from contracts and txns
const claimPending = useIsTransactionPending(hash ?? '')
const claimConfirmed = hash && !claimPending
// use the hash to monitor this txn
function onClaim() {
setAttempting(true)
claimCallback()
.then(hash => {
setHash(hash)
})
// reset modal and log error
.catch(error => {
setAttempting(false)
console.log(error)
})
}
function wrappedOnDismiss() {
setAttempting(false)
setHash(undefined)
setTyped('')
onDismiss()
}
return (
<Modal isOpen={isOpen} onDismiss={wrappedOnDismiss} maxHeight={90}>
<Confetti start={Boolean(isOpen && claimConfirmed && attempting)} />
{!attempting && (
<ContentWrapper gap="lg">
<ModalUpper>
<CardBGImage />
<CardNoise />
<CardSection gap="md">
<RowBetween>
<TYPE.white fontWeight={500}>Claim UNI Token</TYPE.white>
<CloseIcon onClick={wrappedOnDismiss} style={{ zIndex: 99 }} stroke="white" />
</RowBetween>
<TYPE.white fontWeight={700} fontSize={36}>
{unclaimedAmount?.toFixed(0, { groupSeparator: ',' } ?? '-')} UNI
</TYPE.white>
</CardSection>
<Break />
</ModalUpper>
<AutoColumn gap="md" style={{ padding: '1rem', paddingTop: '0' }} justify="center">
<TYPE.subHeader fontWeight={500}>
Enter an address to trigger a UNI claim. If the address has any claimable UNI it will be sent to them on
submission.
</TYPE.subHeader>
<AddressInputPanel value={typed} onChange={handleRecipientType} />
{parsedAddress && !hasAvailableClaim && (
<TYPE.error error={true}>Address has no available claim</TYPE.error>
)}
<ButtonPrimary
disabled={!isAddress(parsedAddress ?? '') || !hasAvailableClaim}
padding="16px 16px"
width="100%"
borderRadius="12px"
mt="1rem"
onClick={onClaim}
>
Claim UNI
</ButtonPrimary>
</AutoColumn>
</ContentWrapper>
)}
{(attempting || claimConfirmed) && (
<ConfirmOrLoadingWrapper activeBG={true}>
<CardNoise />
<CardBGImageSmaller desaturate />
<RowBetween>
<div />
<CloseIcon onClick={wrappedOnDismiss} style={{ zIndex: 99 }} stroke="black" />
</RowBetween>
<ConfirmedIcon>
{!claimConfirmed ? (
<CustomLightSpinner src={Circle} alt="loader" size={'90px'} />
) : (
<UniTokenAnimated width="72px" src={tokenLogo} />
)}
</ConfirmedIcon>
<AutoColumn gap="100px" justify={'center'}>
<AutoColumn gap="12px" justify={'center'}>
<TYPE.largeHeader fontWeight={600} color="black">
{claimConfirmed ? 'Claimed' : 'Claiming'}
</TYPE.largeHeader>
{!claimConfirmed && (
<Text fontSize={36} color={'#ff007a'} fontWeight={800}>
{unclaimedAmount?.toFixed(0, { groupSeparator: ',' } ?? '-')} UNI
</Text>
)}
{parsedAddress && (
<TYPE.largeHeader fontWeight={600} color="black">
for {shortenAddress(parsedAddress)}
</TYPE.largeHeader>
)}
</AutoColumn>
{claimConfirmed && (
<>
<TYPE.subHeader fontWeight={500} color="black">
<span role="img" aria-label="party-hat">
🎉{' '}
</span>
Welcome to team Unicorn :){' '}
<span role="img" aria-label="party-hat">
🎉
</span>
</TYPE.subHeader>
</>
)}
{attempting && !hash && (
<TYPE.subHeader color="black">Confirm this transaction in your wallet</TYPE.subHeader>
)}
{attempting && hash && !claimConfirmed && chainId && hash && (
<ExternalLink href={getEtherscanLink(chainId, hash, 'transaction')} style={{ zIndex: 99 }}>
View transaction on Etherscan
</ExternalLink>
)}
</AutoColumn>
</ConfirmOrLoadingWrapper>
)}
</Modal>
)
}
import { JSBI, TokenAmount } from '@uniswap/sdk'
import { isAddress } from 'ethers/lib/utils'
import React, { useEffect, useState } from 'react'
import { Text } from 'rebass'
import styled from 'styled-components'
import Circle from '../../assets/images/blue-loader.svg'
import tokenLogo from '../../assets/images/token-logo.png'
import { useActiveWeb3React } from '../../hooks'
import { ApplicationModal } from '../../state/application/actions'
import { useModalOpen, useToggleSelfClaimModal } from '../../state/application/hooks'
import { useClaimCallback, useUserClaimData, useUserUnclaimedAmount } from '../../state/claim/hooks'
import { useUserHasSubmittedClaim } from '../../state/transactions/hooks'
import { CloseIcon, CustomLightSpinner, ExternalLink, TYPE, UniTokenAnimated } from '../../theme'
import { getEtherscanLink } from '../../utils'
import { ButtonPrimary } from '../Button'
import { AutoColumn, ColumnCenter } from '../Column'
import Confetti from '../Confetti'
import { Break, CardBGImage, CardBGImageSmaller, CardNoise, CardSection, DataCard } from '../earn/styled'
import Modal from '../Modal'
import { RowBetween } from '../Row'
const ContentWrapper = styled(AutoColumn)`
width: 100%;
`
const ModalUpper = styled(DataCard)`
box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.1);
background: radial-gradient(76.02% 75.41% at 1.84% 0%, #ff007a 0%, #021d43 100%);
`
const ConfirmOrLoadingWrapper = styled.div<{ activeBG: boolean }>`
width: 100%;
padding: 24px;
position: relative;
background: ${({ activeBG }) =>
activeBG &&
'radial-gradient(76.02% 75.41% at 1.84% 0%, rgba(255, 0, 122, 0.2) 0%, rgba(33, 114, 229, 0.2) 100%), #FFFFFF;'};
`
const ConfirmedIcon = styled(ColumnCenter)`
padding: 60px 0;
`
const SOCKS_AMOUNT = 1000
const USER_AMOUNT = 400
export default function ClaimModal() {
const isOpen = useModalOpen(ApplicationModal.SELF_CLAIM)
const toggleClaimModal = useToggleSelfClaimModal()
const { account, chainId } = useActiveWeb3React()
// used for UI loading states
const [attempting, setAttempting] = useState<boolean>(false)
// get user claim data
const userClaimData = useUserClaimData(account)
// monitor the status of the claim from contracts and txns
const { claimCallback } = useClaimCallback(account)
const unclaimedAmount: TokenAmount | undefined = useUserUnclaimedAmount(account)
const { claimSubmitted, claimTxn } = useUserHasSubmittedClaim(account ?? undefined)
const claimConfirmed = Boolean(claimTxn?.receipt)
function onClaim() {
setAttempting(true)
claimCallback()
// reset modal and log error
.catch(error => {
setAttempting(false)
console.log(error)
})
}
// once confirmed txn is found, if modal is closed open, mark as not attempting regradless
useEffect(() => {
if (claimConfirmed && claimSubmitted && attempting) {
setAttempting(false)
if (!isOpen) {
toggleClaimModal()
}
}
}, [attempting, claimConfirmed, claimSubmitted, isOpen, toggleClaimModal])
const nonLPAmount = JSBI.multiply(
JSBI.BigInt((userClaimData?.flags?.isSOCKS ? SOCKS_AMOUNT : 0) + (userClaimData?.flags?.isUser ? USER_AMOUNT : 0)),
JSBI.exponentiate(JSBI.BigInt(10), JSBI.BigInt(18))
)
return (
<Modal isOpen={isOpen} onDismiss={toggleClaimModal} maxHeight={90}>
<Confetti start={Boolean(isOpen && claimConfirmed)} />
{!attempting && !claimConfirmed && (
<ContentWrapper gap="lg">
<ModalUpper>
<CardBGImage />
<CardNoise />
<CardSection gap="md">
<RowBetween>
<TYPE.white fontWeight={500}>Claim UNI</TYPE.white>
<CloseIcon onClick={toggleClaimModal} style={{ zIndex: 99 }} color="white" />
</RowBetween>
<TYPE.white fontWeight={700} fontSize={36}>
{unclaimedAmount?.toFixed(0, { groupSeparator: ',' } ?? '-')} UNI
</TYPE.white>
</CardSection>
<Break />
<CardSection gap="sm">
{userClaimData?.flags?.isSOCKS && (
<RowBetween>
<TYPE.subHeader color="white">SOCKS</TYPE.subHeader>
<TYPE.subHeader color="white">{SOCKS_AMOUNT} UNI</TYPE.subHeader>
</RowBetween>
)}
{userClaimData?.flags?.isLP &&
unclaimedAmount &&
JSBI.greaterThanOrEqual(unclaimedAmount.raw, nonLPAmount) && (
<RowBetween>
<TYPE.subHeader color="white">Liquidity</TYPE.subHeader>
<TYPE.subHeader color="white">
{unclaimedAmount
.subtract(new TokenAmount(unclaimedAmount.token, nonLPAmount))
.toFixed(0, { groupSeparator: ',' })}{' '}
UNI
</TYPE.subHeader>
</RowBetween>
)}
{userClaimData?.flags?.isUser && (
<RowBetween>
<TYPE.subHeader color="white">User</TYPE.subHeader>
<TYPE.subHeader color="white">{USER_AMOUNT} UNI</TYPE.subHeader>
</RowBetween>
)}
</CardSection>
</ModalUpper>
<AutoColumn gap="md" style={{ padding: '1rem', paddingTop: '0' }} justify="center">
<TYPE.subHeader fontWeight={500}>
As a member of the Uniswap community you may claim UNI to be used for voting and governance. <br /> <br />
<ExternalLink href="https://uniswap.org/blog/uni">Read more about UNI</ExternalLink>
</TYPE.subHeader>
<ButtonPrimary
disabled={!isAddress(account ?? '')}
padding="16px 16px"
width="100%"
borderRadius="12px"
mt="1rem"
onClick={onClaim}
>
Claim UNI
</ButtonPrimary>
</AutoColumn>
</ContentWrapper>
)}
{(attempting || claimConfirmed) && (
<ConfirmOrLoadingWrapper activeBG={true}>
<CardNoise />
<CardBGImageSmaller desaturate />
<RowBetween>
<div />
<CloseIcon onClick={toggleClaimModal} style={{ zIndex: 99 }} stroke="black" />
</RowBetween>
<ConfirmedIcon>
{!claimConfirmed ? (
<CustomLightSpinner src={Circle} alt="loader" size={'90px'} />
) : (
<UniTokenAnimated width="72px" src={tokenLogo} />
)}
</ConfirmedIcon>
<AutoColumn gap="100px" justify={'center'}>
<AutoColumn gap="12px" justify={'center'}>
<TYPE.largeHeader fontWeight={600} color="black">
{claimConfirmed ? 'Claimed!' : 'Claiming'}
</TYPE.largeHeader>
{!claimConfirmed && (
<Text fontSize={36} color={'#ff007a'} fontWeight={800}>
{unclaimedAmount?.toFixed(0, { groupSeparator: ',' } ?? '-')} UNI
</Text>
)}
</AutoColumn>
{claimConfirmed && (
<>
<TYPE.subHeader fontWeight={500} color="black">
<span role="img" aria-label="party-hat">
🎉{' '}
</span>
Welcome to team Unicorn :){' '}
<span role="img" aria-label="party-hat">
🎉
</span>
</TYPE.subHeader>
</>
)}
{attempting && !claimSubmitted && (
<TYPE.subHeader color="black">Confirm this transaction in your wallet</TYPE.subHeader>
)}
{attempting && claimSubmitted && !claimConfirmed && chainId && claimTxn?.hash && (
<ExternalLink href={getEtherscanLink(chainId, claimTxn?.hash, 'transaction')} style={{ zIndex: 99 }}>
View transaction on Etherscan
</ExternalLink>
)}
</AutoColumn>
</ConfirmOrLoadingWrapper>
)}
</Modal>
)
}
import React, { useState } from 'react'
import Modal from '../Modal'
import { AutoColumn } from '../Column'
import styled from 'styled-components'
import { RowBetween } from '../Row'
import { TYPE, CloseIcon } from '../../theme'
import { ButtonError } from '../Button'
import { StakingInfo } from '../../state/stake/hooks'
import { useStakingContract } from '../../hooks/useContract'
import { SubmittedView, LoadingView } from '../ModalViews'
import { TransactionResponse } from '@ethersproject/providers'
import { useTransactionAdder } from '../../state/transactions/hooks'
import { useActiveWeb3React } from '../../hooks'
const ContentWrapper = styled(AutoColumn)`
width: 100%;
padding: 1rem;
`
interface StakingModalProps {
isOpen: boolean
onDismiss: () => void
stakingInfo: StakingInfo
}
export default function ClaimRewardModal({ isOpen, onDismiss, stakingInfo }: StakingModalProps) {
const { account } = useActiveWeb3React()
// monitor call to help UI loading state
const addTransaction = useTransactionAdder()
const [hash, setHash] = useState<string | undefined>()
const [attempting, setAttempting] = useState(false)
function wrappedOnDismiss() {
setHash(undefined)
setAttempting(false)
onDismiss()
}
const stakingContract = useStakingContract(stakingInfo.stakingRewardAddress)
async function onClaimReward() {
if (stakingContract && stakingInfo?.stakedAmount) {
setAttempting(true)
await stakingContract
.getReward({ gasLimit: 350000 })
.then((response: TransactionResponse) => {
addTransaction(response, {
summary: `Claim accumulated UNI rewards`
})
setHash(response.hash)
})
.catch((error: any) => {
setAttempting(false)
console.log(error)
})
}
}
let error: string | undefined
if (!account) {
error = 'Connect Wallet'
}
if (!stakingInfo?.stakedAmount) {
error = error ?? 'Enter an amount'
}
return (
<Modal isOpen={isOpen} onDismiss={wrappedOnDismiss} maxHeight={90}>
{!attempting && !hash && (
<ContentWrapper gap="lg">
<RowBetween>
<TYPE.mediumHeader>Claim</TYPE.mediumHeader>
<CloseIcon onClick={wrappedOnDismiss} />
</RowBetween>
{stakingInfo?.earnedAmount && (
<AutoColumn justify="center" gap="md">
<TYPE.body fontWeight={600} fontSize={36}>
{stakingInfo?.earnedAmount?.toSignificant(6)}
</TYPE.body>
<TYPE.body>Unclaimed UNI</TYPE.body>
</AutoColumn>
)}
<TYPE.subHeader style={{ textAlign: 'center' }}>
When you claim without withdrawing your liquidity remains in the mining pool.
</TYPE.subHeader>
<ButtonError disabled={!!error} error={!!error && !!stakingInfo?.stakedAmount} onClick={onClaimReward}>
{error ?? 'Claim'}
</ButtonError>
</ContentWrapper>
)}
{attempting && !hash && (
<LoadingView onDismiss={wrappedOnDismiss}>
<AutoColumn gap="12px" justify={'center'}>
<TYPE.body fontSize={20}>Claiming {stakingInfo?.earnedAmount?.toSignificant(6)} UNI</TYPE.body>
</AutoColumn>
</LoadingView>
)}
{hash && (
<SubmittedView onDismiss={wrappedOnDismiss} hash={hash}>
<AutoColumn gap="12px" justify={'center'}>
<TYPE.largeHeader>Transaction Submitted</TYPE.largeHeader>
<TYPE.body fontSize={20}>Claimed UNI!</TYPE.body>
</AutoColumn>
</SubmittedView>
)}
</Modal>
)
}
import React from 'react'
import { AutoColumn } from '../Column'
import { RowBetween } from '../Row'
import styled from 'styled-components'
import { TYPE, StyledInternalLink } from '../../theme'
import DoubleCurrencyLogo from '../DoubleLogo'
import { ETHER, JSBI, TokenAmount } from '@uniswap/sdk'
import { ButtonPrimary } from '../Button'
import { StakingInfo } from '../../state/stake/hooks'
import { useColor } from '../../hooks/useColor'
import { currencyId } from '../../utils/currencyId'
import { Break, CardNoise, CardBGImage } from './styled'
import { unwrappedToken } from '../../utils/wrappedCurrency'
import { useTotalSupply } from '../../data/TotalSupply'
import { usePair } from '../../data/Reserves'
import useUSDCPrice from '../../utils/useUSDCPrice'
const StatContainer = styled.div`
display: flex;
justify-content: space-between;
flex-direction: column;
gap: 12px;
margin-bottom: 1rem;
margin-right: 1rem;
margin-left: 1rem;
${({ theme }) => theme.mediaWidth.upToSmall`
display: none;
`};
`
const Wrapper = styled(AutoColumn)<{ showBackground: boolean; bgColor: any }>`
border-radius: 12px;
width: 100%;
overflow: hidden;
position: relative;
opacity: ${({ showBackground }) => (showBackground ? '1' : '1')};
background: ${({ theme, bgColor, showBackground }) =>
`radial-gradient(91.85% 100% at 1.84% 0%, ${bgColor} 0%, ${showBackground ? theme.black : theme.bg5} 100%) `};
color: ${({ theme, showBackground }) => (showBackground ? theme.white : theme.text1)} !important;
${({ showBackground }) =>
showBackground &&
` box-shadow: 0px 0px 1px rgba(0, 0, 0, 0.01), 0px 4px 8px rgba(0, 0, 0, 0.04), 0px 16px 24px rgba(0, 0, 0, 0.04),
0px 24px 32px rgba(0, 0, 0, 0.01);`}
`
const TopSection = styled.div`
display: grid;
grid-template-columns: 48px 1fr 120px;
grid-gap: 0px;
align-items: center;
padding: 1rem;
z-index: 1;
${({ theme }) => theme.mediaWidth.upToSmall`
grid-template-columns: 48px 1fr 96px;
`};
`
// 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')};
border-radius: 0 0 12px 12px;
display: flex;
flex-direction: row;
align-items: baseline;
justify-content: space-between;
z-index: 1;
`
export default function PoolCard({ stakingInfo }: { stakingInfo: StakingInfo }) {
const token0 = stakingInfo.tokens[0]
const token1 = stakingInfo.tokens[1]
const currency0 = unwrappedToken(token0)
const currency1 = unwrappedToken(token1)
const isStaking = Boolean(stakingInfo.stakedAmount.greaterThan('0'))
// get the color of the token
const token = currency0 === ETHER ? token1 : token0
const WETH = currency0 === ETHER ? token0 : token1
const backgroundColor = useColor(token)
const totalSupplyOfStakingToken = useTotalSupply(stakingInfo.stakedAmount.token)
const [, stakingTokenPair] = usePair(...stakingInfo.tokens)
// let returnOverMonth: Percent = new Percent('0')
let valueOfTotalStakedAmountInWETH: TokenAmount | undefined
if (totalSupplyOfStakingToken && stakingTokenPair) {
// take the total amount of LP tokens staked, multiply by ETH value of all LP tokens, divide by all LP tokens
valueOfTotalStakedAmountInWETH = new TokenAmount(
WETH,
JSBI.divide(
JSBI.multiply(
JSBI.multiply(stakingInfo.totalStakedAmount.raw, stakingTokenPair.reserveOf(WETH).raw),
JSBI.BigInt(2) // this is b/c the value of LP shares are ~double the value of the WETH they entitle owner to
),
totalSupplyOfStakingToken.raw
)
)
}
// get the USD value of staked WETH
const USDPrice = useUSDCPrice(WETH)
const valueOfTotalStakedAmountInUSDC =
valueOfTotalStakedAmountInWETH && USDPrice?.quote(valueOfTotalStakedAmountInWETH)
return (
<Wrapper showBackground={isStaking} bgColor={backgroundColor}>
<CardBGImage desaturate />
<CardNoise />
<TopSection>
<DoubleCurrencyLogo currency0={currency0} currency1={currency1} size={24} />
<TYPE.white fontWeight={600} fontSize={24} style={{ marginLeft: '8px' }}>
{currency0.symbol}-{currency1.symbol}
</TYPE.white>
<StyledInternalLink to={`/uni/${currencyId(currency0)}/${currencyId(currency1)}`} style={{ width: '100%' }}>
<ButtonPrimary padding="8px" borderRadius="8px">
{isStaking ? 'Manage' : 'Deposit'}
</ButtonPrimary>
</StyledInternalLink>
</TopSection>
<StatContainer>
<RowBetween>
<TYPE.white> Total deposited</TYPE.white>
<TYPE.white>
{valueOfTotalStakedAmountInUSDC
? `$${valueOfTotalStakedAmountInUSDC.toFixed(0, { groupSeparator: ',' })}`
: `${valueOfTotalStakedAmountInWETH?.toSignificant(4, { groupSeparator: ',' }) ?? '-'} ETH`}
</TYPE.white>
</RowBetween>
<RowBetween>
<TYPE.white> Pool rate </TYPE.white>
<TYPE.white>{`${stakingInfo.totalRewardRate
?.multiply(`${60 * 60 * 24 * 7}`)
?.toFixed(0, { groupSeparator: ',' })} UNI / week`}</TYPE.white>
</RowBetween>
</StatContainer>
{isStaking && (
<>
<Break />
<BottomSection showBackground={true}>
<TYPE.black color={'white'} fontWeight={500}>
<span>Your rate</span>
</TYPE.black>
<TYPE.black style={{ textAlign: 'right' }} color={'white'} fontWeight={500}>
<span role="img" aria-label="wizard-icon" style={{ marginRight: '0.5rem' }}>
</span>
{`${stakingInfo.rewardRate
?.multiply(`${60 * 60 * 24 * 7}`)
?.toSignificant(4, { groupSeparator: ',' })} UNI / week`}
</TYPE.black>
</BottomSection>
</>
)}
</Wrapper>
)
}
import React, { useState, useCallback } from 'react'
import useIsArgentWallet from '../../hooks/useIsArgentWallet'
import useTransactionDeadline from '../../hooks/useTransactionDeadline'
import Modal from '../Modal'
import { AutoColumn } from '../Column'
import styled from 'styled-components'
import { RowBetween } from '../Row'
import { TYPE, CloseIcon } from '../../theme'
import { ButtonConfirmed, ButtonError } from '../Button'
import ProgressCircles from '../ProgressSteps'
import CurrencyInputPanel from '../CurrencyInputPanel'
import { TokenAmount, Pair } from '@uniswap/sdk'
import { useActiveWeb3React } from '../../hooks'
import { maxAmountSpend } from '../../utils/maxAmountSpend'
import { usePairContract, useStakingContract } from '../../hooks/useContract'
import { useApproveCallback, ApprovalState } from '../../hooks/useApproveCallback'
import { splitSignature } from 'ethers/lib/utils'
import { StakingInfo, useDerivedStakeInfo } from '../../state/stake/hooks'
import { wrappedCurrencyAmount } from '../../utils/wrappedCurrency'
import { TransactionResponse } from '@ethersproject/providers'
import { useTransactionAdder } from '../../state/transactions/hooks'
import { LoadingView, SubmittedView } from '../ModalViews'
const HypotheticalRewardRate = styled.div<{ dim: boolean }>`
display: flex;
justify-content: space-between;
padding-right: 20px;
padding-left: 20px;
opacity: ${({ dim }) => (dim ? 0.5 : 1)};
`
const ContentWrapper = styled(AutoColumn)`
width: 100%;
padding: 1rem;
`
interface StakingModalProps {
isOpen: boolean
onDismiss: () => void
stakingInfo: StakingInfo
userLiquidityUnstaked: TokenAmount | undefined
}
export default function StakingModal({ isOpen, onDismiss, stakingInfo, userLiquidityUnstaked }: StakingModalProps) {
const { account, chainId, library } = useActiveWeb3React()
// track and parse user input
const [typedValue, setTypedValue] = useState('')
const { parsedAmount, error } = useDerivedStakeInfo(typedValue, stakingInfo.stakedAmount.token, userLiquidityUnstaked)
const parsedAmountWrapped = wrappedCurrencyAmount(parsedAmount, chainId)
let hypotheticalRewardRate: TokenAmount = new TokenAmount(stakingInfo.rewardRate.token, '0')
if (parsedAmountWrapped?.greaterThan('0')) {
hypotheticalRewardRate = stakingInfo.getHypotheticalRewardRate(
stakingInfo.stakedAmount.add(parsedAmountWrapped),
stakingInfo.totalStakedAmount.add(parsedAmountWrapped),
stakingInfo.totalRewardRate
)
}
// state for pending and submitted txn views
const addTransaction = useTransactionAdder()
const [attempting, setAttempting] = useState<boolean>(false)
const [hash, setHash] = useState<string | undefined>()
const wrappedOnDismiss = useCallback(() => {
setHash(undefined)
setAttempting(false)
onDismiss()
}, [onDismiss])
// pair contract for this token to be staked
const dummyPair = new Pair(new TokenAmount(stakingInfo.tokens[0], '0'), new TokenAmount(stakingInfo.tokens[1], '0'))
const pairContract = usePairContract(dummyPair.liquidityToken.address)
// approval data for stake
const deadline = useTransactionDeadline()
const [signatureData, setSignatureData] = useState<{ v: number; r: string; s: string; deadline: number } | null>(null)
const [approval, approveCallback] = useApproveCallback(parsedAmount, stakingInfo.stakingRewardAddress)
const isArgentWallet = useIsArgentWallet()
const stakingContract = useStakingContract(stakingInfo.stakingRewardAddress)
async function onStake() {
setAttempting(true)
if (stakingContract && parsedAmount && deadline) {
if (approval === ApprovalState.APPROVED) {
await stakingContract.stake(`0x${parsedAmount.raw.toString(16)}`, { gasLimit: 350000 })
} else if (signatureData) {
stakingContract
.stakeWithPermit(
`0x${parsedAmount.raw.toString(16)}`,
signatureData.deadline,
signatureData.v,
signatureData.r,
signatureData.s,
{ gasLimit: 350000 }
)
.then((response: TransactionResponse) => {
addTransaction(response, {
summary: `Deposit liquidity`
})
setHash(response.hash)
})
.catch((error: any) => {
setAttempting(false)
console.log(error)
})
} else {
setAttempting(false)
throw new Error('Attempting to stake without approval or a signature. Please contact support.')
}
}
}
// wrapped onUserInput to clear signatures
const onUserInput = useCallback((typedValue: string) => {
setSignatureData(null)
setTypedValue(typedValue)
}, [])
// used for max input button
const maxAmountInput = maxAmountSpend(userLiquidityUnstaked)
const atMaxAmount = Boolean(maxAmountInput && parsedAmount?.equalTo(maxAmountInput))
const handleMax = useCallback(() => {
maxAmountInput && onUserInput(maxAmountInput.toExact())
}, [maxAmountInput, onUserInput])
async function onAttemptToApprove() {
if (!pairContract || !library || !deadline) throw new Error('missing dependencies')
const liquidityAmount = parsedAmount
if (!liquidityAmount) throw new Error('missing liquidity amount')
if (isArgentWallet) {
return approveCallback()
}
// try to gather a signature for permission
const nonce = await pairContract.nonces(account)
const EIP712Domain = [
{ name: 'name', type: 'string' },
{ name: 'version', type: 'string' },
{ name: 'chainId', type: 'uint256' },
{ name: 'verifyingContract', type: 'address' }
]
const domain = {
name: 'Uniswap V2',
version: '1',
chainId: chainId,
verifyingContract: pairContract.address
}
const Permit = [
{ name: 'owner', type: 'address' },
{ name: 'spender', type: 'address' },
{ name: 'value', type: 'uint256' },
{ name: 'nonce', type: 'uint256' },
{ name: 'deadline', type: 'uint256' }
]
const message = {
owner: account,
spender: stakingInfo.stakingRewardAddress,
value: liquidityAmount.raw.toString(),
nonce: nonce.toHexString(),
deadline: deadline.toNumber()
}
const data = JSON.stringify({
types: {
EIP712Domain,
Permit
},
domain,
primaryType: 'Permit',
message
})
library
.send('eth_signTypedData_v4', [account, data])
.then(splitSignature)
.then(signature => {
setSignatureData({
v: signature.v,
r: signature.r,
s: signature.s,
deadline: deadline.toNumber()
})
})
.catch(error => {
// for all errors other than 4001 (EIP-1193 user rejected request), fall back to manual approve
if (error?.code !== 4001) {
approveCallback()
}
})
}
return (
<Modal isOpen={isOpen} onDismiss={wrappedOnDismiss} maxHeight={90}>
{!attempting && !hash && (
<ContentWrapper gap="lg">
<RowBetween>
<TYPE.mediumHeader>Deposit</TYPE.mediumHeader>
<CloseIcon onClick={wrappedOnDismiss} />
</RowBetween>
<CurrencyInputPanel
value={typedValue}
onUserInput={onUserInput}
onMax={handleMax}
showMaxButton={!atMaxAmount}
currency={stakingInfo.stakedAmount.token}
pair={dummyPair}
label={''}
disableCurrencySelect={true}
customBalanceText={'Available to deposit: '}
id="stake-liquidity-token"
/>
<HypotheticalRewardRate dim={!hypotheticalRewardRate.greaterThan('0')}>
<div>
<TYPE.black fontWeight={600}>Weekly Rewards</TYPE.black>
</div>
<TYPE.black>
{hypotheticalRewardRate.multiply((60 * 60 * 24 * 7).toString()).toSignificant(4, { groupSeparator: ',' })}{' '}
UNI / week
</TYPE.black>
</HypotheticalRewardRate>
<RowBetween>
<ButtonConfirmed
mr="0.5rem"
onClick={onAttemptToApprove}
confirmed={approval === ApprovalState.APPROVED || signatureData !== null}
disabled={approval !== ApprovalState.NOT_APPROVED || signatureData !== null}
>
Approve
</ButtonConfirmed>
<ButtonError
disabled={!!error || (signatureData === null && approval !== ApprovalState.APPROVED)}
error={!!error && !!parsedAmount}
onClick={onStake}
>
{error ?? 'Deposit'}
</ButtonError>
</RowBetween>
<ProgressCircles steps={[approval === ApprovalState.APPROVED || signatureData !== null]} disabled={true} />
</ContentWrapper>
)}
{attempting && !hash && (
<LoadingView onDismiss={wrappedOnDismiss}>
<AutoColumn gap="12px" justify={'center'}>
<TYPE.largeHeader>Depositing Liquidity</TYPE.largeHeader>
<TYPE.body fontSize={20}>{parsedAmount?.toSignificant(4)} UNI-V2</TYPE.body>
</AutoColumn>
</LoadingView>
)}
{attempting && hash && (
<SubmittedView onDismiss={wrappedOnDismiss} hash={hash}>
<AutoColumn gap="12px" justify={'center'}>
<TYPE.largeHeader>Transaction Submitted</TYPE.largeHeader>
<TYPE.body fontSize={20}>Deposited {parsedAmount?.toSignificant(4)} UNI-V2</TYPE.body>
</AutoColumn>
</SubmittedView>
)}
</Modal>
)
}
import React, { useState } from 'react'
import Modal from '../Modal'
import { AutoColumn } from '../Column'
import styled from 'styled-components'
import { RowBetween } from '../Row'
import { TYPE, CloseIcon } from '../../theme'
import { ButtonError } from '../Button'
import { StakingInfo } from '../../state/stake/hooks'
import { useStakingContract } from '../../hooks/useContract'
import { SubmittedView, LoadingView } from '../ModalViews'
import { TransactionResponse } from '@ethersproject/providers'
import { useTransactionAdder } from '../../state/transactions/hooks'
import FormattedCurrencyAmount from '../FormattedCurrencyAmount'
import { useActiveWeb3React } from '../../hooks'
const ContentWrapper = styled(AutoColumn)`
width: 100%;
padding: 1rem;
`
interface StakingModalProps {
isOpen: boolean
onDismiss: () => void
stakingInfo: StakingInfo
}
export default function UnstakingModal({ isOpen, onDismiss, stakingInfo }: StakingModalProps) {
const { account } = useActiveWeb3React()
// monitor call to help UI loading state
const addTransaction = useTransactionAdder()
const [hash, setHash] = useState<string | undefined>()
const [attempting, setAttempting] = useState(false)
function wrappedOndismiss() {
setHash(undefined)
setAttempting(false)
onDismiss()
}
const stakingContract = useStakingContract(stakingInfo.stakingRewardAddress)
async function onWithdraw() {
if (stakingContract && stakingInfo?.stakedAmount) {
setAttempting(true)
await stakingContract
.exit({ gasLimit: 300000 })
.then((response: TransactionResponse) => {
addTransaction(response, {
summary: `Withdraw deposited liquidity`
})
setHash(response.hash)
})
.catch((error: any) => {
setAttempting(false)
console.log(error)
})
}
}
let error: string | undefined
if (!account) {
error = 'Connect Wallet'
}
if (!stakingInfo?.stakedAmount) {
error = error ?? 'Enter an amount'
}
return (
<Modal isOpen={isOpen} onDismiss={wrappedOndismiss} maxHeight={90}>
{!attempting && !hash && (
<ContentWrapper gap="lg">
<RowBetween>
<TYPE.mediumHeader>Withdraw</TYPE.mediumHeader>
<CloseIcon onClick={wrappedOndismiss} />
</RowBetween>
{stakingInfo?.stakedAmount && (
<AutoColumn justify="center" gap="md">
<TYPE.body fontWeight={600} fontSize={36}>
{<FormattedCurrencyAmount currencyAmount={stakingInfo.stakedAmount} />}
</TYPE.body>
<TYPE.body>Deposited liquidity:</TYPE.body>
</AutoColumn>
)}
{stakingInfo?.earnedAmount && (
<AutoColumn justify="center" gap="md">
<TYPE.body fontWeight={600} fontSize={36}>
{<FormattedCurrencyAmount currencyAmount={stakingInfo?.earnedAmount} />}
</TYPE.body>
<TYPE.body>Unclaimed UNI</TYPE.body>
</AutoColumn>
)}
<TYPE.subHeader style={{ textAlign: 'center' }}>
When you withdraw, your UNI is claimed and your liquidity is removed from the mining pool.
</TYPE.subHeader>
<ButtonError disabled={!!error} error={!!error && !!stakingInfo?.stakedAmount} onClick={onWithdraw}>
{error ?? 'Withdraw & Claim'}
</ButtonError>
</ContentWrapper>
)}
{attempting && !hash && (
<LoadingView onDismiss={wrappedOndismiss}>
<AutoColumn gap="12px" justify={'center'}>
<TYPE.body fontSize={20}>Withdrawing {stakingInfo?.stakedAmount?.toSignificant(4)} UNI-V2</TYPE.body>
<TYPE.body fontSize={20}>Claiming {stakingInfo?.earnedAmount?.toSignificant(4)} UNI</TYPE.body>
</AutoColumn>
</LoadingView>
)}
{hash && (
<SubmittedView onDismiss={wrappedOndismiss} hash={hash}>
<AutoColumn gap="12px" justify={'center'}>
<TYPE.largeHeader>Transaction Submitted</TYPE.largeHeader>
<TYPE.body fontSize={20}>Withdrew UNI-V2!</TYPE.body>
<TYPE.body fontSize={20}>Claimed UNI!</TYPE.body>
</AutoColumn>
</SubmittedView>
)}
</Modal>
)
}
import styled from 'styled-components'
import { AutoColumn } from '../Column'
import uImage from '../../assets/images/big_unicorn.png'
import xlUnicorn from '../../assets/images/xl_uni.png'
import noise from '../../assets/images/noise.png'
export const TextBox = styled.div`
display: flex;
align-items: center;
justify-content: center;
padding: 4px 12px;
border: 1px solid rgba(255, 255, 255, 0.4);
border-radius: 20px;
width: fit-content;
justify-self: flex-end;
`
export const DataCard = styled(AutoColumn)<{ disabled?: boolean }>`
background: radial-gradient(76.02% 75.41% at 1.84% 0%, #ff007a 0%, #2172e5 100%);
border-radius: 12px;
width: 100%;
position: relative;
overflow: hidden;
`
export const CardBGImage = styled.span<{ desaturate?: boolean }>`
background: url(${uImage});
width: 1000px;
height: 600px;
position: absolute;
border-radius: 12px;
opacity: 0.4;
top: -100px;
left: -100px;
transform: rotate(-15deg);
user-select: none;
${({ desaturate }) => desaturate && `filter: saturate(0)`}
`
export const CardBGImageSmaller = styled.span<{ desaturate?: boolean }>`
background: url(${xlUnicorn});
width: 1200px;
height: 1200px;
position: absolute;
border-radius: 12px;
top: -300px;
left: -300px;
opacity: 0.4;
user-select: none;
${({ desaturate }) => desaturate && `filter: saturate(0)`}
`
export const CardNoise = styled.span`
background: url(${noise});
background-size: cover;
mix-blend-mode: overlay;
border-radius: 12px;
width: 100%;
height: 100%;
opacity: 0.15;
position: absolute;
top: 0;
left: 0;
user-select: none;
`
export const CardSection = styled(AutoColumn)<{ disabled?: boolean }>`
padding: 1rem;
z-index: 1;
opacity: ${({ disabled }) => disabled && '0.4'};
`
export const Break = styled.div`
width: 100%;
background-color: rgba(255, 255, 255, 0.2);
height: 1px;
`
import { Trade, TradeType } from '@uniswap/sdk' import { Trade, TradeType } from '@uniswap/sdk'
import React, { useContext } from 'react' import React, { useContext } from 'react'
import { ThemeContext } from 'styled-components' import styled, { ThemeContext } from 'styled-components'
import { Field } from '../../state/swap/actions' import { Field } from '../../state/swap/actions'
import { useUserSlippageTolerance } from '../../state/user/hooks' import { useUserSlippageTolerance } from '../../state/user/hooks'
import { TYPE } from '../../theme' import { TYPE, ExternalLink } from '../../theme'
import { computeSlippageAdjustedAmounts, computeTradePriceBreakdown } from '../../utils/prices' import { computeSlippageAdjustedAmounts, computeTradePriceBreakdown } from '../../utils/prices'
import { AutoColumn } from '../Column' import { AutoColumn } from '../Column'
import QuestionHelper from '../QuestionHelper' import QuestionHelper from '../QuestionHelper'
...@@ -12,6 +12,16 @@ import FormattedPriceImpact from './FormattedPriceImpact' ...@@ -12,6 +12,16 @@ import FormattedPriceImpact from './FormattedPriceImpact'
import { SectionBreak } from './styleds' import { SectionBreak } from './styleds'
import SwapRoute from './SwapRoute' import SwapRoute from './SwapRoute'
const InfoLink = styled(ExternalLink)`
width: 100%;
border: 1px solid ${({ theme }) => theme.bg3};
padding: 6px 6px;
border-radius: 8px;
text-align: center;
font-size: 14px;
color: ${({ theme }) => theme.text1};
`
function TradeSummary({ trade, allowedSlippage }: { trade: Trade; allowedSlippage: number }) { function TradeSummary({ trade, allowedSlippage }: { trade: Trade; allowedSlippage: number }) {
const theme = useContext(ThemeContext) const theme = useContext(ThemeContext)
const { priceImpactWithoutFee, realizedLPFee } = computeTradePriceBreakdown(trade) const { priceImpactWithoutFee, realizedLPFee } = computeTradePriceBreakdown(trade)
...@@ -94,6 +104,11 @@ export function AdvancedSwapDetails({ trade }: AdvancedSwapDetailsProps) { ...@@ -94,6 +104,11 @@ export function AdvancedSwapDetails({ trade }: AdvancedSwapDetailsProps) {
</AutoColumn> </AutoColumn>
</> </>
)} )}
<AutoColumn style={{ padding: '0 24px' }}>
<InfoLink href={'https://uniswap.info/pair/' + trade.route.pairs[0].liquidityToken.address} target="_blank">
View pair analytics ↗
</InfoLink>
</AutoColumn>
</> </>
)} )}
</AutoColumn> </AutoColumn>
......
...@@ -4,14 +4,27 @@ import { useLocation } from 'react-router' ...@@ -4,14 +4,27 @@ import { useLocation } from 'react-router'
import { Text } from 'rebass' import { Text } from 'rebass'
import { ThemeContext } from 'styled-components' import { ThemeContext } from 'styled-components'
import useParsedQueryString from '../../hooks/useParsedQueryString' import useParsedQueryString from '../../hooks/useParsedQueryString'
import { DEFAULT_VERSION, Version } from '../../hooks/useToggledVersion' import useToggledVersion, { DEFAULT_VERSION, Version } from '../../hooks/useToggledVersion'
import { StyledInternalLink } from '../../theme' import { StyledInternalLink } from '../../theme'
import { YellowCard } from '../Card' import { YellowCard } from '../Card'
import { AutoColumn } from '../Column' import { AutoColumn } from '../Column'
export default function BetterTradeLink({ version }: { version: Version }) { function VersionLinkContainer({ children }: { children: React.ReactNode }) {
const theme = useContext(ThemeContext) const theme = useContext(ThemeContext)
return (
<YellowCard style={{ marginTop: '12px', padding: '0.5rem 0.5rem' }}>
<AutoColumn gap="sm" justify="center" style={{ alignItems: 'center', textAlign: 'center' }}>
<Text lineHeight="145.23%;" fontSize={14} fontWeight={400} color={theme.text1}>
{children}
</Text>
</AutoColumn>
</YellowCard>
)
}
export default function BetterTradeLink({ version }: { version: Version }) {
const location = useLocation() const location = useLocation()
const search = useParsedQueryString() const search = useParsedQueryString()
...@@ -26,15 +39,36 @@ export default function BetterTradeLink({ version }: { version: Version }) { ...@@ -26,15 +39,36 @@ export default function BetterTradeLink({ version }: { version: Version }) {
}, [location, search, version]) }, [location, search, version])
return ( return (
<YellowCard style={{ marginTop: '12px', padding: '8px 4px' }}> <VersionLinkContainer>
<AutoColumn gap="sm" justify="center" style={{ alignItems: 'center', textAlign: 'center' }}>
<Text lineHeight="145.23%;" fontSize={14} fontWeight={400} color={theme.text1}>
There is a better price for this trade on{' '} There is a better price for this trade on{' '}
<StyledInternalLink to={linkDestination}> <StyledInternalLink to={linkDestination}>
<b>Uniswap {version.toUpperCase()}</b> <b>Uniswap {version.toUpperCase()}</b>
</StyledInternalLink> </StyledInternalLink>
</Text> </VersionLinkContainer>
</AutoColumn> )
</YellowCard> }
export function DefaultVersionLink() {
const location = useLocation()
const search = useParsedQueryString()
const version = useToggledVersion()
const linkDestination = useMemo(() => {
return {
...location,
search: `?${stringify({
...search,
use: DEFAULT_VERSION
})}`
}
}, [location, search])
return (
<VersionLinkContainer>
Showing {version.toUpperCase()} price.{' '}
<StyledInternalLink to={linkDestination}>
<b>Switch to Uniswap {DEFAULT_VERSION.toUpperCase()}</b>
</StyledInternalLink>
</VersionLinkContainer>
) )
} }
import React, { useState } from 'react'
import Modal from '../Modal'
import { AutoColumn } from '../Column'
import styled from 'styled-components'
import { RowBetween } from '../Row'
import { TYPE } from '../../theme'
import { X } from 'react-feather'
import { ButtonPrimary } from '../Button'
import { useActiveWeb3React } from '../../hooks'
import AddressInputPanel from '../AddressInputPanel'
import { isAddress } from 'ethers/lib/utils'
import useENS from '../../hooks/useENS'
import { useDelegateCallback } from '../../state/governance/hooks'
import { useTokenBalance } from '../../state/wallet/hooks'
import { UNI } from '../../constants'
import { LoadingView, SubmittedView } from '../ModalViews'
const ContentWrapper = styled(AutoColumn)`
width: 100%;
padding: 24px;
`
const StyledClosed = styled(X)`
:hover {
cursor: pointer;
}
`
const TextButton = styled.div`
:hover {
cursor: pointer;
}
`
interface VoteModalProps {
isOpen: boolean
onDismiss: () => void
title: string
}
export default function DelegateModal({ isOpen, onDismiss, title }: VoteModalProps) {
const { account, chainId } = useActiveWeb3React()
// state for delegate input
const [usingDelegate, setUsingDelegate] = useState(false)
const [typed, setTyped] = useState('')
function handleRecipientType(val: string) {
setTyped(val)
}
// monitor for self delegation or input for third part delegate
// default is self delegation
const activeDelegate = usingDelegate ? typed : account
const { address: parsedAddress } = useENS(activeDelegate)
// get the number of votes available to delegate
const uniBalance = useTokenBalance(account ?? undefined, chainId ? UNI[chainId] : undefined)
const delegateCallback = useDelegateCallback()
// monitor call to help UI loading state
const [hash, setHash] = useState<string | undefined>()
const [attempting, setAttempting] = useState(false)
// wrapper to reset state on modal close
function wrappedOndismiss() {
setHash(undefined)
setAttempting(false)
onDismiss()
}
async function onDelegate() {
setAttempting(true)
// if callback not returned properly ignore
if (!delegateCallback) return
// try delegation and store hash
const hash = await delegateCallback(parsedAddress ?? undefined)?.catch(error => {
setAttempting(false)
console.log(error)
})
if (hash) {
setHash(hash)
}
}
return (
<Modal isOpen={isOpen} onDismiss={wrappedOndismiss} maxHeight={90}>
{!attempting && !hash && (
<ContentWrapper gap="lg">
<AutoColumn gap="lg" justify="center">
<RowBetween>
<TYPE.mediumHeader fontWeight={500}>{title}</TYPE.mediumHeader>
<StyledClosed stroke="black" onClick={wrappedOndismiss} />
</RowBetween>
<TYPE.body>Earned UNI tokens represent voting shares in Uniswap governance.</TYPE.body>
<TYPE.body>
You can either vote on each proposal yourself or delegate your votes to a third party.
</TYPE.body>
{usingDelegate && <AddressInputPanel value={typed} onChange={handleRecipientType} />}
<ButtonPrimary disabled={!isAddress(parsedAddress ?? '')} onClick={onDelegate}>
<TYPE.mediumHeader color="white">{usingDelegate ? 'Delegate Votes' : 'Self Delegate'}</TYPE.mediumHeader>
</ButtonPrimary>
<TextButton onClick={() => setUsingDelegate(!usingDelegate)}>
<TYPE.blue>
{usingDelegate ? 'Remove' : 'Add'} Delegate {!usingDelegate && '+'}
</TYPE.blue>
</TextButton>
</AutoColumn>
</ContentWrapper>
)}
{attempting && !hash && (
<LoadingView onDismiss={wrappedOndismiss}>
<AutoColumn gap="12px" justify={'center'}>
<TYPE.largeHeader>{usingDelegate ? 'Delegating votes' : 'Unlocking Votes'}</TYPE.largeHeader>
<TYPE.main fontSize={36}>{uniBalance?.toSignificant(4)}</TYPE.main>
</AutoColumn>
</LoadingView>
)}
{hash && (
<SubmittedView onDismiss={wrappedOndismiss} hash={hash}>
<AutoColumn gap="12px" justify={'center'}>
<TYPE.largeHeader>Transaction Submitted</TYPE.largeHeader>
<TYPE.main fontSize={36}>{uniBalance?.toSignificant(4)}</TYPE.main>
</AutoColumn>
</SubmittedView>
)}
</Modal>
)
}
import React, { useState, useContext } from 'react'
import { useActiveWeb3React } from '../../hooks'
import Modal from '../Modal'
import { AutoColumn, ColumnCenter } from '../Column'
import styled, { ThemeContext } from 'styled-components'
import { RowBetween } from '../Row'
import { TYPE, CustomLightSpinner } from '../../theme'
import { X, ArrowUpCircle } from 'react-feather'
import { ButtonPrimary } from '../Button'
import Circle from '../../assets/images/blue-loader.svg'
import { useVoteCallback, useUserVotes } from '../../state/governance/hooks'
import { getEtherscanLink } from '../../utils'
import { ExternalLink } from '../../theme/components'
import { TokenAmount } from '@uniswap/sdk'
const ContentWrapper = styled(AutoColumn)`
width: 100%;
padding: 24px;
`
const StyledClosed = styled(X)`
:hover {
cursor: pointer;
}
`
const ConfirmOrLoadingWrapper = styled.div`
width: 100%;
padding: 24px;
`
const ConfirmedIcon = styled(ColumnCenter)`
padding: 60px 0;
`
interface VoteModalProps {
isOpen: boolean
onDismiss: () => void
support: boolean // if user is for or against proposal
proposalId: string | undefined // id for the proposal to vote on
}
export default function VoteModal({ isOpen, onDismiss, proposalId, support }: VoteModalProps) {
const { chainId } = useActiveWeb3React()
const {
voteCallback
}: {
voteCallback: (proposalId: string | undefined, support: boolean) => Promise<string> | undefined
} = useVoteCallback()
const availableVotes: TokenAmount | undefined = useUserVotes()
// monitor call to help UI loading state
const [hash, setHash] = useState<string | undefined>()
const [attempting, setAttempting] = useState<boolean>(false)
// get theme for colors
const theme = useContext(ThemeContext)
// wrapper to reset state on modal close
function wrappedOndismiss() {
setHash(undefined)
setAttempting(false)
onDismiss()
}
async function onVote() {
setAttempting(true)
// if callback not returned properly ignore
if (!voteCallback) return
// try delegation and store hash
const hash = await voteCallback(proposalId, support)?.catch(error => {
setAttempting(false)
console.log(error)
})
if (hash) {
setHash(hash)
}
}
return (
<Modal isOpen={isOpen} onDismiss={wrappedOndismiss} maxHeight={90}>
{!attempting && !hash && (
<ContentWrapper gap="lg">
<AutoColumn gap="lg" justify="center">
<RowBetween>
<TYPE.mediumHeader fontWeight={500}>{`Vote ${
support ? 'for ' : 'against'
} proposal ${proposalId}`}</TYPE.mediumHeader>
<StyledClosed stroke="black" onClick={wrappedOndismiss} />
</RowBetween>
<TYPE.largeHeader>{availableVotes?.toSignificant(4)} Votes</TYPE.largeHeader>
<ButtonPrimary onClick={onVote}>
<TYPE.mediumHeader color="white">{`Vote ${
support ? 'for ' : 'against'
} proposal ${proposalId}`}</TYPE.mediumHeader>
</ButtonPrimary>
</AutoColumn>
</ContentWrapper>
)}
{attempting && !hash && (
<ConfirmOrLoadingWrapper>
<RowBetween>
<div />
<StyledClosed onClick={wrappedOndismiss} />
</RowBetween>
<ConfirmedIcon>
<CustomLightSpinner src={Circle} alt="loader" size={'90px'} />
</ConfirmedIcon>
<AutoColumn gap="100px" justify={'center'}>
<AutoColumn gap="12px" justify={'center'}>
<TYPE.largeHeader>Submitting Vote</TYPE.largeHeader>
</AutoColumn>
<TYPE.subHeader>Confirm this transaction in your wallet</TYPE.subHeader>
</AutoColumn>
</ConfirmOrLoadingWrapper>
)}
{hash && (
<ConfirmOrLoadingWrapper>
<RowBetween>
<div />
<StyledClosed onClick={wrappedOndismiss} />
</RowBetween>
<ConfirmedIcon>
<ArrowUpCircle strokeWidth={0.5} size={90} color={theme.primary1} />
</ConfirmedIcon>
<AutoColumn gap="100px" justify={'center'}>
<AutoColumn gap="12px" justify={'center'}>
<TYPE.largeHeader>Transaction Submitted</TYPE.largeHeader>
</AutoColumn>
{chainId && (
<ExternalLink href={getEtherscanLink(chainId, hash, 'transaction')} style={{ marginLeft: '4px' }}>
<TYPE.subHeader>View transaction on Etherscan</TYPE.subHeader>
</ExternalLink>
)}
</AutoColumn>
</ConfirmOrLoadingWrapper>
)}
</Modal>
)
}
[
{
"inputs": [
{ "internalType": "bytes32[]", "name": "_codes", "type": "bytes32[]" },
{ "internalType": "address[]", "name": "_implementations", "type": "address[]" }
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"anonymous": false,
"inputs": [{ "indexed": true, "internalType": "bytes32", "name": "code", "type": "bytes32" }],
"name": "CodeAdded",
"type": "event"
},
{
"anonymous": false,
"inputs": [{ "indexed": true, "internalType": "address", "name": "implementation", "type": "address" }],
"name": "ImplementationAdded",
"type": "event"
},
{
"anonymous": false,
"inputs": [{ "indexed": true, "internalType": "address", "name": "_newOwner", "type": "address" }],
"name": "OwnerChanged",
"type": "event"
},
{
"inputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }],
"name": "acceptedCodes",
"outputs": [
{ "internalType": "bool", "name": "exists", "type": "bool" },
{ "internalType": "uint128", "name": "index", "type": "uint128" }
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [{ "internalType": "address", "name": "", "type": "address" }],
"name": "acceptedImplementations",
"outputs": [
{ "internalType": "bool", "name": "exists", "type": "bool" },
{ "internalType": "uint128", "name": "index", "type": "uint128" }
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [{ "internalType": "bytes32", "name": "_code", "type": "bytes32" }],
"name": "addCode",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [{ "internalType": "address", "name": "_argentWallet", "type": "address" }],
"name": "addCodeAndImplementationFromWallet",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [{ "internalType": "address", "name": "_impl", "type": "address" }],
"name": "addImplementation",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [{ "internalType": "address", "name": "_newOwner", "type": "address" }],
"name": "changeOwner",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "getCodes",
"outputs": [{ "internalType": "bytes32[]", "name": "", "type": "bytes32[]" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "getImplementations",
"outputs": [{ "internalType": "address[]", "name": "", "type": "address[]" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [{ "internalType": "address", "name": "_wallet", "type": "address" }],
"name": "isArgentWallet",
"outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "owner",
"outputs": [{ "internalType": "address", "name": "", "type": "address" }],
"stateMutability": "view",
"type": "function"
}
]
import ARGENT_WALLET_DETECTOR_ABI from './argent-wallet-detector.json'
const ARGENT_WALLET_DETECTOR_MAINNET_ADDRESS = '0xeca4B0bDBf7c55E9b7925919d03CbF8Dc82537E8'
export { ARGENT_WALLET_DETECTOR_ABI, ARGENT_WALLET_DETECTOR_MAINNET_ADDRESS }
...@@ -10,7 +10,10 @@ ...@@ -10,7 +10,10 @@
}, },
{ {
"constant": false, "constant": false,
"inputs": [{ "name": "_spender", "type": "address" }, { "name": "_value", "type": "uint256" }], "inputs": [
{ "name": "_spender", "type": "address" },
{ "name": "_value", "type": "uint256" }
],
"name": "approve", "name": "approve",
"outputs": [{ "name": "", "type": "bool" }], "outputs": [{ "name": "", "type": "bool" }],
"payable": false, "payable": false,
...@@ -68,7 +71,10 @@ ...@@ -68,7 +71,10 @@
}, },
{ {
"constant": false, "constant": false,
"inputs": [{ "name": "_to", "type": "address" }, { "name": "_value", "type": "uint256" }], "inputs": [
{ "name": "_to", "type": "address" },
{ "name": "_value", "type": "uint256" }
],
"name": "transfer", "name": "transfer",
"outputs": [{ "name": "", "type": "bool" }], "outputs": [{ "name": "", "type": "bool" }],
"payable": false, "payable": false,
...@@ -77,7 +83,10 @@ ...@@ -77,7 +83,10 @@
}, },
{ {
"constant": true, "constant": true,
"inputs": [{ "name": "_owner", "type": "address" }, { "name": "_spender", "type": "address" }], "inputs": [
{ "name": "_owner", "type": "address" },
{ "name": "_spender", "type": "address" }
],
"name": "allowance", "name": "allowance",
"outputs": [{ "name": "", "type": "uint256" }], "outputs": [{ "name": "", "type": "uint256" }],
"payable": false, "payable": false,
......
import { Interface } from '@ethersproject/abi'
import { abi as STAKING_REWARDS_ABI } from '@uniswap/liquidity-staker/build/StakingRewards.json'
import { abi as STAKING_REWARDS_FACTORY_ABI } from '@uniswap/liquidity-staker/build/StakingRewardsFactory.json'
const STAKING_REWARDS_INTERFACE = new Interface(STAKING_REWARDS_ABI)
const STAKING_REWARDS_FACTORY_INTERFACE = new Interface(STAKING_REWARDS_FACTORY_ABI)
export { STAKING_REWARDS_FACTORY_INTERFACE, STAKING_REWARDS_INTERFACE }
...@@ -5,6 +5,8 @@ import { fortmatic, injected, portis, walletconnect, walletlink } from '../conne ...@@ -5,6 +5,8 @@ import { fortmatic, injected, portis, walletconnect, walletlink } from '../conne
export const ROUTER_ADDRESS = '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D' export const ROUTER_ADDRESS = '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D'
export const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'
// a list of tokens by chain // a list of tokens by chain
type ChainTokenList = { type ChainTokenList = {
readonly [chainId in ChainId]: Token[] readonly [chainId in ChainId]: Token[]
...@@ -16,6 +18,26 @@ export const USDT = new Token(ChainId.MAINNET, '0xdAC17F958D2ee523a2206206994597 ...@@ -16,6 +18,26 @@ export const USDT = new Token(ChainId.MAINNET, '0xdAC17F958D2ee523a2206206994597
export const COMP = new Token(ChainId.MAINNET, '0xc00e94Cb662C3520282E6f5717214004A7f26888', 18, 'COMP', 'Compound') export const COMP = new Token(ChainId.MAINNET, '0xc00e94Cb662C3520282E6f5717214004A7f26888', 18, 'COMP', 'Compound')
export const MKR = new Token(ChainId.MAINNET, '0x9f8F72aA9304c8B593d555F12eF6589cC3A579A2', 18, 'MKR', 'Maker') export const MKR = new Token(ChainId.MAINNET, '0x9f8F72aA9304c8B593d555F12eF6589cC3A579A2', 18, 'MKR', 'Maker')
export const AMPL = new Token(ChainId.MAINNET, '0xD46bA6D942050d489DBd938a2C909A5d5039A161', 9, 'AMPL', 'Ampleforth') 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
export const GOVERNANCE_ADDRESS = '0x5e4be8Bc9637f0EAA1A755019e06A68ce081D58F'
const UNI_ADDRESS = '0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984'
export const UNI: { [chainId in ChainId]: Token } = {
[ChainId.MAINNET]: new Token(ChainId.MAINNET, UNI_ADDRESS, 18, 'UNI', 'Uniswap'),
[ChainId.RINKEBY]: new Token(ChainId.RINKEBY, UNI_ADDRESS, 18, 'UNI', 'Uniswap'),
[ChainId.ROPSTEN]: new Token(ChainId.ROPSTEN, UNI_ADDRESS, 18, 'UNI', 'Uniswap'),
[ChainId.GÖRLI]: new Token(ChainId.GÖRLI, UNI_ADDRESS, 18, 'UNI', 'Uniswap'),
[ChainId.KOVAN]: new Token(ChainId.KOVAN, UNI_ADDRESS, 18, 'UNI', 'Uniswap')
}
// TODO: specify merkle distributor for mainnet
export const MERKLE_DISTRIBUTOR_ADDRESS: { [chainId in ChainId]?: string } = {
[ChainId.MAINNET]: '0x090D4613473dEE047c3f2706764f49E0821D256e'
}
const WETH_ONLY: ChainTokenList = { const WETH_ONLY: ChainTokenList = {
[ChainId.MAINNET]: [WETH[ChainId.MAINNET]], [ChainId.MAINNET]: [WETH[ChainId.MAINNET]],
...@@ -147,6 +169,8 @@ export const INITIAL_ALLOWED_SLIPPAGE = 50 ...@@ -147,6 +169,8 @@ export const INITIAL_ALLOWED_SLIPPAGE = 50
// 20 minutes, denominated in seconds // 20 minutes, denominated in seconds
export const DEFAULT_DEADLINE_FROM_NOW = 60 * 20 export const DEFAULT_DEADLINE_FROM_NOW = 60 * 20
export const BIG_INT_ZERO = JSBI.BigInt(0)
// one basis point // one basis point
export const ONE_BIPS = new Percent(JSBI.BigInt(1), JSBI.BigInt(10000)) export const ONE_BIPS = new Percent(JSBI.BigInt(1), JSBI.BigInt(10000))
export const BIPS_BASE = JSBI.BigInt(10000) export const BIPS_BASE = JSBI.BigInt(10000)
......
This source diff could not be displayed because it is too large. You can view the blob instead.
import { useState, useLayoutEffect } from 'react'
import { shade } from 'polished'
import Vibrant from 'node-vibrant'
import { hex } from 'wcag-contrast'
import { Token, ChainId } from '@uniswap/sdk'
async function getColorFromToken(token: Token): Promise<string | null> {
if (token.chainId === ChainId.RINKEBY && token.address === '0xc7AD46e0b8a400Bb3C915120d284AafbA8fc4735') {
return Promise.resolve('#FAAB14')
}
const path = `https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/${token.address}/logo.png`
return Vibrant.from(path)
.getPalette()
.then(palette => {
if (palette?.Vibrant) {
let detectedHex = palette.Vibrant.hex
let AAscore = hex(detectedHex, '#FFF')
while (AAscore < 3) {
detectedHex = shade(0.005, detectedHex)
AAscore = hex(detectedHex, '#FFF')
}
return detectedHex
}
return null
})
.catch(() => null)
}
export function useColor(token?: Token) {
const [color, setColor] = useState('#2172E5')
useLayoutEffect(() => {
let stale = false
if (token) {
getColorFromToken(token).then(tokenColor => {
if (!stale && tokenColor !== null) {
setColor(tokenColor)
}
})
}
return () => {
stale = true
setColor('#2172E5')
}
}, [token])
return color
}
import { Contract } from '@ethersproject/contracts' import { Contract } from '@ethersproject/contracts'
import { abi as GOVERNANCE_ABI } from '@uniswap/governance/build/GovernorAlpha.json'
import { abi as UNI_ABI } from '@uniswap/governance/build/Uni.json'
import { abi as STAKING_REWARDS_ABI } from '@uniswap/liquidity-staker/build/StakingRewards.json'
import { abi as MERKLE_DISTRIBUTOR_ABI } from '@uniswap/merkle-distributor/build/MerkleDistributor.json'
import { ChainId, WETH } from '@uniswap/sdk' import { ChainId, WETH } from '@uniswap/sdk'
import { abi as IUniswapV2PairABI } from '@uniswap/v2-core/build/IUniswapV2Pair.json' import { abi as IUniswapV2PairABI } from '@uniswap/v2-core/build/IUniswapV2Pair.json'
import { useMemo } from 'react' import { useMemo } from 'react'
import ENS_ABI from '../constants/abis/ens-registrar.json' import { GOVERNANCE_ADDRESS, MERKLE_DISTRIBUTOR_ADDRESS, UNI } from '../constants'
import {
ARGENT_WALLET_DETECTOR_ABI,
ARGENT_WALLET_DETECTOR_MAINNET_ADDRESS
} from '../constants/abis/argent-wallet-detector'
import ENS_PUBLIC_RESOLVER_ABI from '../constants/abis/ens-public-resolver.json' import ENS_PUBLIC_RESOLVER_ABI from '../constants/abis/ens-public-resolver.json'
import ENS_ABI from '../constants/abis/ens-registrar.json'
import { ERC20_BYTES32_ABI } from '../constants/abis/erc20' import { ERC20_BYTES32_ABI } from '../constants/abis/erc20'
import ERC20_ABI from '../constants/abis/erc20.json' import ERC20_ABI from '../constants/abis/erc20.json'
import { MIGRATOR_ABI, MIGRATOR_ADDRESS } from '../constants/abis/migrator' import { MIGRATOR_ABI, MIGRATOR_ADDRESS } from '../constants/abis/migrator'
...@@ -51,6 +60,15 @@ export function useWETHContract(withSignerIfPossible?: boolean): Contract | null ...@@ -51,6 +60,15 @@ export function useWETHContract(withSignerIfPossible?: boolean): Contract | null
return useContract(chainId ? WETH[chainId].address : undefined, WETH_ABI, withSignerIfPossible) return useContract(chainId ? WETH[chainId].address : undefined, WETH_ABI, withSignerIfPossible)
} }
export function useArgentWalletDetectorContract(): Contract | null {
const { chainId } = useActiveWeb3React()
return useContract(
chainId === ChainId.MAINNET ? ARGENT_WALLET_DETECTOR_MAINNET_ADDRESS : undefined,
ARGENT_WALLET_DETECTOR_ABI,
false
)
}
export function useENSRegistrarContract(withSignerIfPossible?: boolean): Contract | null { export function useENSRegistrarContract(withSignerIfPossible?: boolean): Contract | null {
const { chainId } = useActiveWeb3React() const { chainId } = useActiveWeb3React()
let address: string | undefined let address: string | undefined
...@@ -84,6 +102,24 @@ export function useMulticallContract(): Contract | null { ...@@ -84,6 +102,24 @@ export function useMulticallContract(): Contract | null {
return useContract(chainId && MULTICALL_NETWORKS[chainId], MULTICALL_ABI, false) return useContract(chainId && MULTICALL_NETWORKS[chainId], MULTICALL_ABI, false)
} }
export function useMerkleDistributorContract(): Contract | null {
const { chainId } = useActiveWeb3React()
return useContract(chainId ? MERKLE_DISTRIBUTOR_ADDRESS[chainId] : undefined, MERKLE_DISTRIBUTOR_ABI, true)
}
export function useGovernanceContract(): Contract | null {
return useContract(GOVERNANCE_ADDRESS, GOVERNANCE_ABI, true)
}
export function useUniContract(): Contract | null {
const { chainId } = useActiveWeb3React()
return useContract(chainId ? UNI[chainId].address : undefined, UNI_ABI, true)
}
export function useStakingContract(stakingAddress?: string, withSignerIfPossible?: boolean): Contract | null {
return useContract(stakingAddress, STAKING_REWARDS_ABI, withSignerIfPossible)
}
export function useSocksController(): Contract | null { export function useSocksController(): Contract | null {
const { chainId } = useActiveWeb3React() const { chainId } = useActiveWeb3React()
return useContract( return useContract(
......
import { BigNumber } from 'ethers'
import { useSingleCallResult } from '../state/multicall/hooks'
import { useMulticallContract } from './useContract'
// gets the current timestamp from the blockchain
export default function useCurrentBlockTimestamp(): BigNumber | undefined {
const multicall = useMulticallContract()
return useSingleCallResult(multicall, 'getCurrentBlockTimestamp')?.result?.[0]
}
import { NEVER_RELOAD, useSingleCallResult } from '../state/multicall/hooks'
import { useActiveWeb3React } from './index'
import { useArgentWalletDetectorContract } from './useContract'
export default function useIsArgentWallet(): boolean {
const { account } = useActiveWeb3React()
const argentWalletDetector = useArgentWalletDetectorContract()
const call = useSingleCallResult(argentWalletDetector, 'isArgentWallet', [account ?? undefined], NEVER_RELOAD)
return call?.result?.[0] ?? false
}
...@@ -2,7 +2,7 @@ import { BigNumber } from '@ethersproject/bignumber' ...@@ -2,7 +2,7 @@ import { BigNumber } from '@ethersproject/bignumber'
import { Contract } from '@ethersproject/contracts' import { Contract } from '@ethersproject/contracts'
import { JSBI, Percent, Router, SwapParameters, Trade, TradeType } from '@uniswap/sdk' import { JSBI, Percent, Router, SwapParameters, Trade, TradeType } from '@uniswap/sdk'
import { useMemo } from 'react' import { useMemo } from 'react'
import { BIPS_BASE, DEFAULT_DEADLINE_FROM_NOW, INITIAL_ALLOWED_SLIPPAGE } from '../constants' import { BIPS_BASE, INITIAL_ALLOWED_SLIPPAGE } from '../constants'
import { getTradeVersion, useV1TradeExchangeAddress } from '../data/V1' import { getTradeVersion, useV1TradeExchangeAddress } from '../data/V1'
import { useTransactionAdder } from '../state/transactions/hooks' import { useTransactionAdder } from '../state/transactions/hooks'
import { calculateGasMargin, getRouterContract, isAddress, shortenAddress } from '../utils' import { calculateGasMargin, getRouterContract, isAddress, shortenAddress } from '../utils'
...@@ -10,6 +10,7 @@ import isZero from '../utils/isZero' ...@@ -10,6 +10,7 @@ import isZero from '../utils/isZero'
import v1SwapArguments from '../utils/v1SwapArguments' import v1SwapArguments from '../utils/v1SwapArguments'
import { useActiveWeb3React } from './index' import { useActiveWeb3React } from './index'
import { useV1ExchangeContract } from './useContract' import { useV1ExchangeContract } from './useContract'
import useTransactionDeadline from './useTransactionDeadline'
import useENS from './useENS' import useENS from './useENS'
import { Version } from './useToggledVersion' import { Version } from './useToggledVersion'
...@@ -40,25 +41,24 @@ type EstimatedSwapCall = SuccessfulCall | FailedCall ...@@ -40,25 +41,24 @@ type EstimatedSwapCall = SuccessfulCall | FailedCall
* Returns the swap calls that can be used to make the trade * Returns the swap calls that can be used to make the trade
* @param trade trade to execute * @param trade trade to execute
* @param allowedSlippage user allowed slippage * @param allowedSlippage user allowed slippage
* @param deadline the deadline for the trade
* @param recipientAddressOrName * @param recipientAddressOrName
*/ */
function useSwapCallArguments( function useSwapCallArguments(
trade: Trade | undefined, // trade to execute, required trade: Trade | undefined, // trade to execute, required
allowedSlippage: number = INITIAL_ALLOWED_SLIPPAGE, // in bips allowedSlippage: number = INITIAL_ALLOWED_SLIPPAGE, // in bips
deadline: number = DEFAULT_DEADLINE_FROM_NOW, // in seconds from now
recipientAddressOrName: string | null // the ENS name or address of the recipient of the trade, or null if swap should be returned to sender recipientAddressOrName: string | null // the ENS name or address of the recipient of the trade, or null if swap should be returned to sender
): SwapCall[] { ): SwapCall[] {
const { account, chainId, library } = useActiveWeb3React() const { account, chainId, library } = useActiveWeb3React()
const { address: recipientAddress } = useENS(recipientAddressOrName) const { address: recipientAddress } = useENS(recipientAddressOrName)
const recipient = recipientAddressOrName === null ? account : recipientAddress const recipient = recipientAddressOrName === null ? account : recipientAddress
const deadline = useTransactionDeadline()
const v1Exchange = useV1ExchangeContract(useV1TradeExchangeAddress(trade), true) const v1Exchange = useV1ExchangeContract(useV1TradeExchangeAddress(trade), true)
return useMemo(() => { return useMemo(() => {
const tradeVersion = getTradeVersion(trade) const tradeVersion = getTradeVersion(trade)
if (!trade || !recipient || !library || !account || !tradeVersion || !chainId) return [] if (!trade || !recipient || !library || !account || !tradeVersion || !chainId || !deadline) return []
const contract: Contract | null = const contract: Contract | null =
tradeVersion === Version.v2 ? getRouterContract(chainId, library, account) : v1Exchange tradeVersion === Version.v2 ? getRouterContract(chainId, library, account) : v1Exchange
...@@ -75,7 +75,7 @@ function useSwapCallArguments( ...@@ -75,7 +75,7 @@ function useSwapCallArguments(
feeOnTransfer: false, feeOnTransfer: false,
allowedSlippage: new Percent(JSBI.BigInt(allowedSlippage), BIPS_BASE), allowedSlippage: new Percent(JSBI.BigInt(allowedSlippage), BIPS_BASE),
recipient, recipient,
ttl: deadline deadline: deadline.toNumber()
}) })
) )
...@@ -85,7 +85,7 @@ function useSwapCallArguments( ...@@ -85,7 +85,7 @@ function useSwapCallArguments(
feeOnTransfer: true, feeOnTransfer: true,
allowedSlippage: new Percent(JSBI.BigInt(allowedSlippage), BIPS_BASE), allowedSlippage: new Percent(JSBI.BigInt(allowedSlippage), BIPS_BASE),
recipient, recipient,
ttl: deadline deadline: deadline.toNumber()
}) })
) )
} }
...@@ -95,7 +95,7 @@ function useSwapCallArguments( ...@@ -95,7 +95,7 @@ function useSwapCallArguments(
v1SwapArguments(trade, { v1SwapArguments(trade, {
allowedSlippage: new Percent(JSBI.BigInt(allowedSlippage), BIPS_BASE), allowedSlippage: new Percent(JSBI.BigInt(allowedSlippage), BIPS_BASE),
recipient, recipient,
ttl: deadline deadline: deadline.toNumber()
}) })
) )
break break
...@@ -109,12 +109,11 @@ function useSwapCallArguments( ...@@ -109,12 +109,11 @@ function useSwapCallArguments(
export function useSwapCallback( export function useSwapCallback(
trade: Trade | undefined, // trade to execute, required trade: Trade | undefined, // trade to execute, required
allowedSlippage: number = INITIAL_ALLOWED_SLIPPAGE, // in bips allowedSlippage: number = INITIAL_ALLOWED_SLIPPAGE, // in bips
deadline: number = DEFAULT_DEADLINE_FROM_NOW, // in seconds from now
recipientAddressOrName: string | null // the ENS name or address of the recipient of the trade, or null if swap should be returned to sender recipientAddressOrName: string | null // the ENS name or address of the recipient of the trade, or null if swap should be returned to sender
): { state: SwapCallbackState; callback: null | (() => Promise<string>); error: string | null } { ): { state: SwapCallbackState; callback: null | (() => Promise<string>); error: string | null } {
const { account, chainId, library } = useActiveWeb3React() const { account, chainId, library } = useActiveWeb3React()
const swapCalls = useSwapCallArguments(trade, allowedSlippage, deadline, recipientAddressOrName) const swapCalls = useSwapCallArguments(trade, allowedSlippage, recipientAddressOrName)
const addTransaction = useTransactionAdder() const addTransaction = useTransactionAdder()
......
import { useActiveWeb3React } from '.'
import { useState, useEffect } from 'react'
export function useTimestampFromBlock(block: number | undefined): number | undefined {
const { library } = useActiveWeb3React()
const [timestamp, setTimestamp] = useState<number>()
useEffect(() => {
async function fetchTimestamp() {
if (block) {
const blockData = await library?.getBlock(block)
blockData && setTimestamp(blockData.timestamp)
}
}
if (!timestamp) {
fetchTimestamp()
}
}, [block, library, timestamp])
return timestamp
}
import { BigNumber } from 'ethers'
import { useMemo } from 'react'
import { useSelector } from 'react-redux'
import { AppState } from '../state'
import useCurrentBlockTimestamp from './useCurrentBlockTimestamp'
// combines the block timestamp with the user setting to give the deadline that should be used for any submitted transaction
export default function useTransactionDeadline(): BigNumber | undefined {
const ttl = useSelector<AppState, number>(state => state.user.userDeadline)
const blockTimestamp = useCurrentBlockTimestamp()
return useMemo(() => {
if (blockTimestamp && ttl) return blockTimestamp.add(ttl)
return undefined
}, [blockTimestamp, ttl])
}
import { useEffect, useState } from 'react'
const isClient = typeof window === 'object'
function getSize() {
return {
width: isClient ? window.innerWidth : undefined,
height: isClient ? window.innerHeight : undefined
}
}
// https://usehooks.com/useWindowSize/
export function useWindowSize() {
const [windowSize, setWindowSize] = useState(getSize)
useEffect(() => {
function handleResize() {
setWindowSize(getSize())
}
if (isClient) {
window.addEventListener('resize', handleResize)
return () => {
window.removeEventListener('resize', handleResize)
}
}
return undefined
}, [])
return windowSize
}
...@@ -5,6 +5,7 @@ import { isMobile } from 'react-device-detect' ...@@ -5,6 +5,7 @@ import { isMobile } from 'react-device-detect'
import ReactDOM from 'react-dom' import ReactDOM from 'react-dom'
import ReactGA from 'react-ga' import ReactGA from 'react-ga'
import { Provider } from 'react-redux' import { Provider } from 'react-redux'
import { HashRouter } from 'react-router-dom'
import { NetworkContextName } from './constants' import { NetworkContextName } from './constants'
import './i18n' import './i18n'
import App from './pages/App' import App from './pages/App'
...@@ -61,7 +62,9 @@ ReactDOM.render( ...@@ -61,7 +62,9 @@ ReactDOM.render(
<Updaters /> <Updaters />
<ThemeProvider> <ThemeProvider>
<ThemedGlobalStyle /> <ThemedGlobalStyle />
<HashRouter>
<App /> <App />
</HashRouter>
</ThemeProvider> </ThemeProvider>
</Provider> </Provider>
</Web3ProviderNetwork> </Web3ProviderNetwork>
......
...@@ -8,7 +8,7 @@ import { RouteComponentProps } from 'react-router-dom' ...@@ -8,7 +8,7 @@ import { RouteComponentProps } from 'react-router-dom'
import { Text } from 'rebass' import { Text } from 'rebass'
import { ThemeContext } from 'styled-components' import { ThemeContext } from 'styled-components'
import { ButtonError, ButtonLight, ButtonPrimary } from '../../components/Button' import { ButtonError, ButtonLight, ButtonPrimary } from '../../components/Button'
import { BlueCard, GreyCard, LightCard } from '../../components/Card' import { BlueCard, LightCard } from '../../components/Card'
import { AutoColumn, ColumnCenter } from '../../components/Column' import { AutoColumn, ColumnCenter } from '../../components/Column'
import TransactionConfirmationModal, { ConfirmationModalContent } from '../../components/TransactionConfirmationModal' import TransactionConfirmationModal, { ConfirmationModalContent } from '../../components/TransactionConfirmationModal'
import CurrencyInputPanel from '../../components/CurrencyInputPanel' import CurrencyInputPanel from '../../components/CurrencyInputPanel'
...@@ -22,12 +22,13 @@ import { PairState } from '../../data/Reserves' ...@@ -22,12 +22,13 @@ import { PairState } from '../../data/Reserves'
import { useActiveWeb3React } from '../../hooks' import { useActiveWeb3React } from '../../hooks'
import { useCurrency } from '../../hooks/Tokens' import { useCurrency } from '../../hooks/Tokens'
import { ApprovalState, useApproveCallback } from '../../hooks/useApproveCallback' import { ApprovalState, useApproveCallback } from '../../hooks/useApproveCallback'
import useTransactionDeadline from '../../hooks/useTransactionDeadline'
import { useWalletModalToggle } from '../../state/application/hooks' import { useWalletModalToggle } from '../../state/application/hooks'
import { Field } from '../../state/mint/actions' import { Field } from '../../state/mint/actions'
import { useDerivedMintInfo, useMintActionHandlers, useMintState } from '../../state/mint/hooks' import { useDerivedMintInfo, useMintActionHandlers, useMintState } from '../../state/mint/hooks'
import { useTransactionAdder } from '../../state/transactions/hooks' import { useTransactionAdder } from '../../state/transactions/hooks'
import { useIsExpertMode, useUserDeadline, useUserSlippageTolerance } from '../../state/user/hooks' import { useIsExpertMode, useUserSlippageTolerance } from '../../state/user/hooks'
import { TYPE } from '../../theme' import { TYPE } from '../../theme'
import { calculateGasMargin, calculateSlippageAmount, getRouterContract } from '../../utils' import { calculateGasMargin, calculateSlippageAmount, getRouterContract } from '../../utils'
import { maxAmountSpend } from '../../utils/maxAmountSpend' import { maxAmountSpend } from '../../utils/maxAmountSpend'
...@@ -84,7 +85,7 @@ export default function AddLiquidity({ ...@@ -84,7 +85,7 @@ export default function AddLiquidity({
const [attemptingTxn, setAttemptingTxn] = useState<boolean>(false) // clicked confirm const [attemptingTxn, setAttemptingTxn] = useState<boolean>(false) // clicked confirm
// txn values // txn values
const [deadline] = useUserDeadline() // custom from users settings const deadline = useTransactionDeadline() // custom from users settings
const [allowedSlippage] = useUserSlippageTolerance() // custom from users const [allowedSlippage] = useUserSlippageTolerance() // custom from users
const [txHash, setTxHash] = useState<string>('') const [txHash, setTxHash] = useState<string>('')
...@@ -126,7 +127,7 @@ export default function AddLiquidity({ ...@@ -126,7 +127,7 @@ export default function AddLiquidity({
const router = getRouterContract(chainId, library, account) const router = getRouterContract(chainId, library, account)
const { [Field.CURRENCY_A]: parsedAmountA, [Field.CURRENCY_B]: parsedAmountB } = parsedAmounts const { [Field.CURRENCY_A]: parsedAmountA, [Field.CURRENCY_B]: parsedAmountB } = parsedAmounts
if (!parsedAmountA || !parsedAmountB || !currencyA || !currencyB) { if (!parsedAmountA || !parsedAmountB || !currencyA || !currencyB || !deadline) {
return return
} }
...@@ -135,8 +136,6 @@ export default function AddLiquidity({ ...@@ -135,8 +136,6 @@ export default function AddLiquidity({
[Field.CURRENCY_B]: calculateSlippageAmount(parsedAmountB, noLiquidity ? 0 : allowedSlippage)[0] [Field.CURRENCY_B]: calculateSlippageAmount(parsedAmountB, noLiquidity ? 0 : allowedSlippage)[0]
} }
const deadlineFromNow = Math.ceil(Date.now() / 1000) + deadline
let estimate, let estimate,
method: (...args: any) => Promise<TransactionResponse>, method: (...args: any) => Promise<TransactionResponse>,
args: Array<string | string[] | number>, args: Array<string | string[] | number>,
...@@ -151,7 +150,7 @@ export default function AddLiquidity({ ...@@ -151,7 +150,7 @@ export default function AddLiquidity({
amountsMin[tokenBIsETH ? Field.CURRENCY_A : Field.CURRENCY_B].toString(), // token min amountsMin[tokenBIsETH ? Field.CURRENCY_A : Field.CURRENCY_B].toString(), // token min
amountsMin[tokenBIsETH ? Field.CURRENCY_B : Field.CURRENCY_A].toString(), // eth min amountsMin[tokenBIsETH ? Field.CURRENCY_B : Field.CURRENCY_A].toString(), // eth min
account, account,
deadlineFromNow deadline.toHexString()
] ]
value = BigNumber.from((tokenBIsETH ? parsedAmountB : parsedAmountA).raw.toString()) value = BigNumber.from((tokenBIsETH ? parsedAmountB : parsedAmountA).raw.toString())
} else { } else {
...@@ -165,7 +164,7 @@ export default function AddLiquidity({ ...@@ -165,7 +164,7 @@ export default function AddLiquidity({
amountsMin[Field.CURRENCY_A].toString(), amountsMin[Field.CURRENCY_A].toString(),
amountsMin[Field.CURRENCY_B].toString(), amountsMin[Field.CURRENCY_B].toString(),
account, account,
deadlineFromNow deadline.toHexString()
] ]
value = null value = null
} }
...@@ -303,10 +302,12 @@ export default function AddLiquidity({ ...@@ -303,10 +302,12 @@ export default function AddLiquidity({
setTxHash('') setTxHash('')
}, [onFieldAInput, txHash]) }, [onFieldAInput, txHash])
const isCreate = history.location.pathname.includes('/create')
return ( return (
<> <>
<AppBody> <AppBody>
<AddRemoveTabs adding={true} /> <AddRemoveTabs creating={isCreate} adding={true} />
<Wrapper> <Wrapper>
<TransactionConfirmationModal <TransactionConfirmationModal
isOpen={showConfirm} isOpen={showConfirm}
...@@ -324,7 +325,8 @@ export default function AddLiquidity({ ...@@ -324,7 +325,8 @@ export default function AddLiquidity({
pendingText={pendingText} pendingText={pendingText}
/> />
<AutoColumn gap="20px"> <AutoColumn gap="20px">
{noLiquidity && ( {noLiquidity ||
(isCreate && (
<ColumnCenter> <ColumnCenter>
<BlueCard> <BlueCard>
<AutoColumn gap="10px"> <AutoColumn gap="10px">
...@@ -340,7 +342,7 @@ export default function AddLiquidity({ ...@@ -340,7 +342,7 @@ export default function AddLiquidity({
</AutoColumn> </AutoColumn>
</BlueCard> </BlueCard>
</ColumnCenter> </ColumnCenter>
)} ))}
<CurrencyInputPanel <CurrencyInputPanel
value={formattedAmounts[Field.CURRENCY_A]} value={formattedAmounts[Field.CURRENCY_A]}
onUserInput={onFieldAInput} onUserInput={onFieldAInput}
...@@ -370,7 +372,7 @@ export default function AddLiquidity({ ...@@ -370,7 +372,7 @@ export default function AddLiquidity({
/> />
{currencies[Field.CURRENCY_A] && currencies[Field.CURRENCY_B] && pairState !== PairState.INVALID && ( {currencies[Field.CURRENCY_A] && currencies[Field.CURRENCY_B] && pairState !== PairState.INVALID && (
<> <>
<GreyCard padding="0px" borderRadius={'20px'}> <LightCard padding="0px" borderRadius={'20px'}>
<RowBetween padding="1rem"> <RowBetween padding="1rem">
<TYPE.subHeader fontWeight={500} fontSize={14}> <TYPE.subHeader fontWeight={500} fontSize={14}>
{noLiquidity ? 'Initial prices' : 'Prices'} and pool share {noLiquidity ? 'Initial prices' : 'Prices'} and pool share
...@@ -384,7 +386,7 @@ export default function AddLiquidity({ ...@@ -384,7 +386,7 @@ export default function AddLiquidity({
price={price} price={price}
/> />
</LightCard> </LightCard>
</GreyCard> </LightCard>
</> </>
)} )}
...@@ -444,7 +446,7 @@ export default function AddLiquidity({ ...@@ -444,7 +446,7 @@ export default function AddLiquidity({
</AppBody> </AppBody>
{pair && !noLiquidity && pairState !== PairState.INVALID ? ( {pair && !noLiquidity && pairState !== PairState.INVALID ? (
<AutoColumn style={{ minWidth: '20rem', marginTop: '1rem' }}> <AutoColumn style={{ minWidth: '20rem', width: '100%', maxWidth: '400px', marginTop: '1rem' }}>
<MinimalPositionCard showUnwrapped={oneCurrencyIsWETH} pair={pair} /> <MinimalPositionCard showUnwrapped={oneCurrencyIsWETH} pair={pair} />
</AutoColumn> </AutoColumn>
) : null} ) : null}
......
import React, { Suspense } from 'react' import React, { Suspense } from 'react'
import { HashRouter, Route, Switch } from 'react-router-dom' import { Route, Switch } from 'react-router-dom'
import styled from 'styled-components' import styled from 'styled-components'
import GoogleAnalyticsReporter from '../components/analytics/GoogleAnalyticsReporter' import GoogleAnalyticsReporter from '../components/analytics/GoogleAnalyticsReporter'
import AddressClaimModal from '../components/claim/AddressClaimModal'
import Header from '../components/Header' import Header from '../components/Header'
import Polling from '../components/Header/Polling'
import URLWarning from '../components/Header/URLWarning'
import Popups from '../components/Popups' import Popups from '../components/Popups'
import Web3ReactManager from '../components/Web3ReactManager' import Web3ReactManager from '../components/Web3ReactManager'
import { ApplicationModal } from '../state/application/actions'
import { useModalOpen, useToggleModal } from '../state/application/hooks'
import DarkModeQueryParamReader from '../theme/DarkModeQueryParamReader' import DarkModeQueryParamReader from '../theme/DarkModeQueryParamReader'
import AddLiquidity from './AddLiquidity' import AddLiquidity from './AddLiquidity'
import { import {
...@@ -12,6 +17,8 @@ import { ...@@ -12,6 +17,8 @@ import {
RedirectOldAddLiquidityPathStructure, RedirectOldAddLiquidityPathStructure,
RedirectToAddLiquidity RedirectToAddLiquidity
} from './AddLiquidity/redirects' } from './AddLiquidity/redirects'
import Earn from './Earn'
import Manage from './Earn/Manage'
import MigrateV1 from './MigrateV1' import MigrateV1 from './MigrateV1'
import MigrateV1Exchange from './MigrateV1/MigrateV1Exchange' import MigrateV1Exchange from './MigrateV1/MigrateV1Exchange'
import RemoveV1Exchange from './MigrateV1/RemoveV1Exchange' import RemoveV1Exchange from './MigrateV1/RemoveV1Exchange'
...@@ -20,7 +27,10 @@ import PoolFinder from './PoolFinder' ...@@ -20,7 +27,10 @@ import PoolFinder from './PoolFinder'
import RemoveLiquidity from './RemoveLiquidity' import RemoveLiquidity from './RemoveLiquidity'
import { RedirectOldRemoveLiquidityPathStructure } from './RemoveLiquidity/redirects' import { RedirectOldRemoveLiquidityPathStructure } from './RemoveLiquidity/redirects'
import Swap from './Swap' import Swap from './Swap'
import { RedirectPathToSwapOnly, RedirectToSwap } from './Swap/redirects' import { OpenClaimAddressModalAndRedirectToSwap, RedirectPathToSwapOnly, RedirectToSwap } from './Swap/redirects'
import Vote from './Vote'
import VotePage from './Vote/VotePage'
const AppWrapper = styled.div` const AppWrapper = styled.div`
display: flex; display: flex;
...@@ -39,15 +49,16 @@ const BodyWrapper = styled.div` ...@@ -39,15 +49,16 @@ const BodyWrapper = styled.div`
display: flex; display: flex;
flex-direction: column; flex-direction: column;
width: 100%; width: 100%;
padding-top: 160px; padding-top: 100px;
align-items: center; align-items: center;
flex: 1; flex: 1;
overflow-y: auto; overflow-y: auto;
overflow-x: hidden; overflow-x: hidden;
z-index: 10; z-index: 10;
${({ theme }) => theme.mediaWidth.upToExtraSmall` ${({ theme }) => theme.mediaWidth.upToSmall`
padding: 16px; padding: 16px;
padding-top: 2rem;
`}; `};
z-index: 1; z-index: 1;
...@@ -57,41 +68,56 @@ const Marginer = styled.div` ...@@ -57,41 +68,56 @@ const Marginer = styled.div`
margin-top: 5rem; margin-top: 5rem;
` `
function TopLevelModals() {
const open = useModalOpen(ApplicationModal.ADDRESS_CLAIM)
const toggle = useToggleModal(ApplicationModal.ADDRESS_CLAIM)
return <AddressClaimModal isOpen={open} onDismiss={toggle} />
}
export default function App() { export default function App() {
return ( return (
<Suspense fallback={null}> <Suspense fallback={null}>
<HashRouter>
<Route component={GoogleAnalyticsReporter} /> <Route component={GoogleAnalyticsReporter} />
<Route component={DarkModeQueryParamReader} /> <Route component={DarkModeQueryParamReader} />
<AppWrapper> <AppWrapper>
<URLWarning />
<HeaderWrapper> <HeaderWrapper>
<Header /> <Header />
</HeaderWrapper> </HeaderWrapper>
<BodyWrapper> <BodyWrapper>
<Popups /> <Popups />
<Polling />
<TopLevelModals />
<Web3ReactManager> <Web3ReactManager>
<Switch> <Switch>
<Route exact strict path="/swap" component={Swap} /> <Route exact strict path="/swap" component={Swap} />
<Route exact strict path="/claim" component={OpenClaimAddressModalAndRedirectToSwap} />
<Route exact strict path="/swap/:outputCurrency" component={RedirectToSwap} /> <Route exact strict path="/swap/:outputCurrency" component={RedirectToSwap} />
<Route exact strict path="/send" component={RedirectPathToSwapOnly} /> <Route exact strict path="/send" component={RedirectPathToSwapOnly} />
<Route exact strict path="/find" component={PoolFinder} /> <Route exact strict path="/find" component={PoolFinder} />
<Route exact strict path="/pool" component={Pool} /> <Route exact strict path="/pool" component={Pool} />
<Route exact strict path="/uni" component={Earn} />
<Route exact strict path="/vote" component={Vote} />
<Route exact strict path="/create" component={RedirectToAddLiquidity} /> <Route exact strict path="/create" component={RedirectToAddLiquidity} />
<Route exact path="/add" component={AddLiquidity} /> <Route exact path="/add" component={AddLiquidity} />
<Route exact path="/add/:currencyIdA" component={RedirectOldAddLiquidityPathStructure} /> <Route exact path="/add/:currencyIdA" component={RedirectOldAddLiquidityPathStructure} />
<Route exact path="/add/:currencyIdA/:currencyIdB" component={RedirectDuplicateTokenIds} /> <Route exact path="/add/:currencyIdA/:currencyIdB" component={RedirectDuplicateTokenIds} />
<Route exact path="/create" component={AddLiquidity} />
<Route exact path="/create/:currencyIdA" component={RedirectOldAddLiquidityPathStructure} />
<Route exact path="/create/:currencyIdA/:currencyIdB" component={RedirectDuplicateTokenIds} />
<Route exact strict path="/remove/v1/:address" component={RemoveV1Exchange} /> <Route exact strict path="/remove/v1/:address" component={RemoveV1Exchange} />
<Route exact strict path="/remove/:tokens" component={RedirectOldRemoveLiquidityPathStructure} /> <Route exact strict path="/remove/:tokens" component={RedirectOldRemoveLiquidityPathStructure} />
<Route exact strict path="/remove/:currencyIdA/:currencyIdB" component={RemoveLiquidity} /> <Route exact strict path="/remove/:currencyIdA/:currencyIdB" component={RemoveLiquidity} />
<Route exact strict path="/migrate/v1" component={MigrateV1} /> <Route exact strict path="/migrate/v1" component={MigrateV1} />
<Route exact strict path="/migrate/v1/:address" component={MigrateV1Exchange} /> <Route exact strict path="/migrate/v1/:address" component={MigrateV1Exchange} />
<Route exact strict path="/uni/:currencyIdA/:currencyIdB" component={Manage} />
<Route exact strict path="/vote/:id" component={VotePage} />
<Route component={RedirectPathToSwapOnly} /> <Route component={RedirectPathToSwapOnly} />
</Switch> </Switch>
</Web3ReactManager> </Web3ReactManager>
<Marginer /> <Marginer />
</BodyWrapper> </BodyWrapper>
</AppWrapper> </AppWrapper>
</HashRouter>
</Suspense> </Suspense>
) )
} }
import React, { useEffect, useMemo, useState } from 'react'
import { STAKING_GENESIS, REWARDS_DURATION_DAYS } from '../../state/stake/hooks'
import { TYPE } from '../../theme'
const MINUTE = 60
const HOUR = MINUTE * 60
const DAY = HOUR * 24
const REWARDS_DURATION = DAY * REWARDS_DURATION_DAYS
export function Countdown({ exactEnd }: { exactEnd?: Date }) {
// get end/beginning times
const end = useMemo(() => (exactEnd ? Math.floor(exactEnd.getTime() / 1000) : STAKING_GENESIS + REWARDS_DURATION), [
exactEnd
])
const begin = useMemo(() => end - REWARDS_DURATION, [end])
// get current time
const [time, setTime] = useState(() => Math.floor(Date.now() / 1000))
useEffect((): (() => void) | void => {
// we only need to tick if rewards haven't ended yet
if (time <= end) {
const timeout = setTimeout(() => setTime(Math.floor(Date.now() / 1000)), 1000)
return () => {
clearTimeout(timeout)
}
}
}, [time, end])
const timeUntilGenesis = begin - time
const timeUntilEnd = end - time
let timeRemaining: number
let message: string
if (timeUntilGenesis >= 0) {
message = 'Rewards begin in'
timeRemaining = timeUntilGenesis
} else {
const ongoing = timeUntilEnd >= 0
if (ongoing) {
message = 'Rewards end in'
timeRemaining = timeUntilEnd
} else {
message = 'Rewards have ended!'
timeRemaining = Infinity
}
}
const days = (timeRemaining - (timeRemaining % DAY)) / DAY
timeRemaining -= days * DAY
const hours = (timeRemaining - (timeRemaining % HOUR)) / HOUR
timeRemaining -= hours * HOUR
const minutes = (timeRemaining - (timeRemaining % MINUTE)) / MINUTE
timeRemaining -= minutes * MINUTE
const seconds = timeRemaining
return (
<TYPE.black fontWeight={400}>
{message}{' '}
{Number.isFinite(timeRemaining) && (
<code>
{`${days}:${hours.toString().padStart(2, '0')}:${minutes
.toString()
.padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`}
</code>
)}
</TYPE.black>
)
}
This diff is collapsed.
This diff is collapsed.
...@@ -9,6 +9,7 @@ import { ButtonConfirmed } from '../../components/Button' ...@@ -9,6 +9,7 @@ import { ButtonConfirmed } from '../../components/Button'
import { LightCard, PinkCard, YellowCard } from '../../components/Card' import { LightCard, PinkCard, YellowCard } from '../../components/Card'
import { AutoColumn } from '../../components/Column' import { AutoColumn } from '../../components/Column'
import CurrencyLogo from '../../components/CurrencyLogo' import CurrencyLogo from '../../components/CurrencyLogo'
import FormattedCurrencyAmount from '../../components/FormattedCurrencyAmount'
import QuestionHelper from '../../components/QuestionHelper' import QuestionHelper from '../../components/QuestionHelper'
import { AutoRow, RowBetween, RowFixed } from '../../components/Row' import { AutoRow, RowBetween, RowFixed } from '../../components/Row'
import { Dots } from '../../components/swap/styleds' import { Dots } from '../../components/swap/styleds'
...@@ -28,25 +29,12 @@ import { getEtherscanLink, isAddress } from '../../utils' ...@@ -28,25 +29,12 @@ import { getEtherscanLink, isAddress } from '../../utils'
import { BodyWrapper } from '../AppBody' import { BodyWrapper } from '../AppBody'
import { EmptyState } from './EmptyState' import { EmptyState } from './EmptyState'
const POOL_CURRENCY_AMOUNT_MIN = new Fraction(JSBI.BigInt(1), JSBI.BigInt(1000000))
const WEI_DENOM = JSBI.exponentiate(JSBI.BigInt(10), JSBI.BigInt(18)) const WEI_DENOM = JSBI.exponentiate(JSBI.BigInt(10), JSBI.BigInt(18))
const ZERO = JSBI.BigInt(0) const ZERO = JSBI.BigInt(0)
const ONE = JSBI.BigInt(1) const ONE = JSBI.BigInt(1)
const ZERO_FRACTION = new Fraction(ZERO, ONE) const ZERO_FRACTION = new Fraction(ZERO, ONE)
const ALLOWED_OUTPUT_MIN_PERCENT = new Percent(JSBI.BigInt(10000 - INITIAL_ALLOWED_SLIPPAGE), JSBI.BigInt(10000)) const ALLOWED_OUTPUT_MIN_PERCENT = new Percent(JSBI.BigInt(10000 - INITIAL_ALLOWED_SLIPPAGE), JSBI.BigInt(10000))
function FormattedPoolCurrencyAmount({ currencyAmount }: { currencyAmount: CurrencyAmount }) {
return (
<>
{currencyAmount.equalTo(JSBI.BigInt(0))
? '0'
: currencyAmount.greaterThan(POOL_CURRENCY_AMOUNT_MIN)
? currencyAmount.toSignificant(4)
: `<${POOL_CURRENCY_AMOUNT_MIN.toSignificant(1)}`}
</>
)
}
export function V1LiquidityInfo({ export function V1LiquidityInfo({
token, token,
liquidityTokenAmount, liquidityTokenAmount,
...@@ -66,7 +54,7 @@ export function V1LiquidityInfo({ ...@@ -66,7 +54,7 @@ export function V1LiquidityInfo({
<CurrencyLogo size="24px" currency={token} /> <CurrencyLogo size="24px" currency={token} />
<div style={{ marginLeft: '.75rem' }}> <div style={{ marginLeft: '.75rem' }}>
<TYPE.mediumHeader> <TYPE.mediumHeader>
{<FormattedPoolCurrencyAmount currencyAmount={liquidityTokenAmount} />}{' '} {<FormattedCurrencyAmount currencyAmount={liquidityTokenAmount} />}{' '}
{chainId && token.equals(WETH[chainId]) ? 'WETH' : token.symbol}/ETH {chainId && token.equals(WETH[chainId]) ? 'WETH' : token.symbol}/ETH
</TYPE.mediumHeader> </TYPE.mediumHeader>
</div> </div>
...@@ -89,7 +77,7 @@ export function V1LiquidityInfo({ ...@@ -89,7 +77,7 @@ export function V1LiquidityInfo({
</Text> </Text>
<RowFixed> <RowFixed>
<Text fontSize={16} fontWeight={500} marginLeft={'6px'}> <Text fontSize={16} fontWeight={500} marginLeft={'6px'}>
<FormattedPoolCurrencyAmount currencyAmount={ethWorth} /> <FormattedCurrencyAmount currencyAmount={ethWorth} />
</Text> </Text>
<CurrencyLogo size="20px" style={{ marginLeft: '8px' }} currency={Currency.ETHER} /> <CurrencyLogo size="20px" style={{ marginLeft: '8px' }} currency={Currency.ETHER} />
</RowFixed> </RowFixed>
......
This diff is collapsed.
...@@ -131,6 +131,9 @@ export default function PoolFinder() { ...@@ -131,6 +131,9 @@ export default function PoolFinder() {
<Text textAlign="center" fontWeight={500}> <Text textAlign="center" fontWeight={500}>
Pool Found! Pool Found!
</Text> </Text>
<StyledInternalLink to={`/pool`}>
<Text textAlign="center">Manage this pool.</Text>
</StyledInternalLink>
</ColumnCenter> </ColumnCenter>
)} )}
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment