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:
- uses: actions/setup-node@v1
with:
node-version: '12'
always-auth: true
registry-url: https://registry.npmjs.org
- name: Install dependencies
run: yarn install --frozen-lockfile
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Build the IPFS bundle
run: yarn build
......
......@@ -6,6 +6,7 @@ on:
pull_request:
branches:
- master
jobs:
integration-tests:
name: Integration tests
......@@ -16,6 +17,9 @@ jobs:
- uses: actions/setup-node@v1
with:
node-version: '12'
always-auth: true
registry-url: https://registry.npmjs.org
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
......@@ -27,9 +31,12 @@ jobs:
restore-keys: |
${{ runner.os }}-yarn-
- run: yarn install --frozen-lockfile
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- run: yarn cypress install
- run: yarn build
env:
CI: false
REACT_APP_NETWORK_URL: "https://mainnet.infura.io/v3/4bf032f2d38a4ed6bb975b80d6340847"
- run: yarn integration-test
......@@ -42,28 +49,9 @@ jobs:
- uses: actions/setup-node@v1
with:
node-version: '12'
- name: Get yarn cache directory path
id: yarn-cache-dir-path
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
always-auth: true
registry-url: https://registry.npmjs.org
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
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
......@@ -75,5 +63,7 @@ jobs:
restore-keys: |
${{ runner.os }}-yarn-
- run: yarn install --frozen-lockfile
- run: yarn lint
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- run: yarn test
describe('Swap', () => {
describe('Lists', () => {
beforeEach(() => {
cy.visit('/swap')
})
it('list selection persists', () => {
it('defaults to uniswap list', () => {
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.reload()
cy.get('#swap-currency-output .open-currency-select-button').click()
cy.get('#list-introduction-choose-a-list').should('not.exist')
cy.get('#currency-search-selected-list-name').should('contain', 'Uniswap')
})
it('change list', () => {
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('#list-row-tokens-1inch-eth .select-button').click()
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', () => {
it('can swap ETH for DAI', () => {
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').click({ force: true })
cy.get('#swap-currency-input .token-amount-input').should('be.visible')
......
......@@ -3,15 +3,8 @@
"strict": true,
"baseUrl": "../node_modules",
"target": "es5",
"lib": [
"es5",
"dom"
],
"types": [
"cypress"
]
"lib": ["es5", "dom"],
"types": ["cypress"]
},
"include": [
"**/*.ts"
]
}
\ No newline at end of file
"include": ["**/*.ts"]
}
......@@ -11,6 +11,7 @@
"@reduxjs/toolkit": "^1.3.5",
"@types/jest": "^25.2.1",
"@types/lodash.flatmap": "^4.5.6",
"@types/luxon": "^1.24.4",
"@types/multicodec": "^1.0.0",
"@types/node": "^13.13.5",
"@types/qs": "^6.9.2",
......@@ -23,10 +24,13 @@
"@types/rebass": "^4.0.5",
"@types/styled-components": "^5.1.0",
"@types/testing-library__cypress": "^5.0.5",
"@types/wcag-contrast": "^3.0.0",
"@typescript-eslint/eslint-plugin": "^2.31.0",
"@typescript-eslint/parser": "^2.31.0",
"@uniswap/default-token-list": "^1.3.1",
"@uniswap/sdk": "3.0.3-beta.1",
"@uniswap/governance": "^1.0.2",
"@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/v2-core": "1.0.0",
"@uniswap/v2-periphery": "^1.1.0-beta.0",
......@@ -53,17 +57,21 @@
"inter-ui": "^3.13.1",
"jazzicon": "^1.5.0",
"lodash.flatmap": "^4.5.0",
"luxon": "^1.25.0",
"multicodec": "^2.0.0",
"multihashes": "^3.0.1",
"node-vibrant": "^3.1.5",
"polished": "^3.3.2",
"prettier": "^1.17.0",
"qs": "^6.9.4",
"react": "^16.13.1",
"react-confetti": "^6.0.0",
"react-device-detect": "^1.6.2",
"react-dom": "^16.13.1",
"react-feather": "^2.0.8",
"react-ga": "^2.5.7",
"react-i18next": "^10.7.0",
"react-markdown": "^4.3.1",
"react-popper": "^2.2.3",
"react-redux": "^7.2.0",
"react-router-dom": "^5.0.0",
......@@ -77,7 +85,9 @@
"serve": "^11.3.0",
"start-server-and-test": "^1.11.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": {
"@walletconnect/web3-provider": "1.1.1-alpha.0"
......@@ -87,11 +97,13 @@
"build": "react-scripts build",
"test": "react-scripts test --env=jsdom",
"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'"
},
"eslintConfig": {
"extends": "react-app"
"extends": "react-app",
"ignorePatterns": [
"node_modules"
]
},
"browserslist": {
"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
{
"noWallet": "No se encontró billetera de Ethereum",
"wrongNetwork": "Te encontrás en la red equivocada",
"switchNetwork": "Por favor cambia a {{ correctNetwork }}",
"installWeb3MobileBrowser": "Por favor ingresá desde un navegador móvil con web3 habilitado como Trust Wallet o Coinbase Wallet.",
"installMetamask": "Por favor visítanos nuevamente luego de instalar Metamask en Chrome o Brave.",
"disconnected": "Desconectado",
"swap": "Intercambiar",
"send": "Enviar",
"pool": "Pool",
"betaWarning": "Este proyecto se encuentra en beta. Usalo bajo tu propio riesgo.",
"input": "Entrada",
"output": "Salida",
"estimated": "estimado",
"balance": "Saldo: {{ balanceInput }}",
"unlock": "Desbloquear",
"pending": "Pendiente",
"selectToken": "Seleccioná un token",
"searchOrPaste": "Buscar Token o Pegar Dirección",
"noExchange": "No se encontró la divisa",
"exchangeRate": "Tasa de Cambio",
"enterValueCont": "Ingresá un valor en {{ missingCurrencyValue }} para continuar.",
"selectTokenCont": "Seleccioná un token para continuar.",
"noLiquidity": "Sin liquidez.",
"unlockTokenCont": "Por favor desbloqueá un token para continuar.",
"transactionDetails": "Detalles de la transacción",
"hideDetails": "Ocultar detalles",
"youAreSelling": "Estás vendiendo",
"orTransFail": "o la transacción fallará.",
"youWillReceive": "Vas a recibir al menos",
"youAreBuying": "Estás comprando",
"itWillCost": "Costará a lo sumo",
"insufficientBalance": "Saldo insuficiente",
"inputNotValid": "No es un valor de entrada válido",
"differentToken": "Debe ser un token distinto.",
"noRecipient": "Ingresá una dirección de billetera para enviar.",
"invalidRecipient": "Por favor ingrese una billetera de destino válida.",
"recipientAddress": "Dirección del recipiente",
"youAreSending": "Estás enviando",
"willReceive": "recibirá al menos",
"to": "a",
"addLiquidity": "Agregar liquidez",
"deposit": "Depositar",
"currentPoolSize": "Tamaño del Pool Actual",
"yourPoolShare": "Tu parte del Pool",
"noZero": "El monto no puede ser cero.",
"mustBeETH": "Una de las entradas debe ser ETH.",
"enterCurrencyOrLabelCont": "Ingresá un valor de {{ inputCurrency }} o de {{ label }} para continuar.",
"youAreAdding": "Estás agregando entre",
"and": "y",
"intoPool": "en el pool de liquidez.",
"outPool": "en el pool de liquidez.",
"youWillMint": "Vas a acuñar",
"liquidityTokens": "tokens de liquidez.",
"totalSupplyIs": "El actual suministro total de tokens de liquidez es",
"youAreSettingExRate": "Está configurando el tipo de cambio inicial a",
"totalSupplyIs0": "El actual suministro total de tokens de liquidez es 0.",
"tokenWorth": "Al tipo de cambio actual, cada token del pool vale",
"firstLiquidity": "Sos la primer persona en agregar liquidez!",
"initialExchangeRate": "El tipo de cambio inicial se establecerá en función de tus depósitos. Por favor, asegúrate de que tus depósitos en ETH y {{ label }} tengan el mismo valor fíat.",
"removeLiquidity": "Remover Liquidez",
"poolTokens": "Pool de Tokens",
"enterLabelCont": "Ingresá un valor de {{ label }} para continuar.",
"youAreRemoving": "Estás quitando entre",
"youWillRemove": "Vas a remover",
"createExchange": "Crear divisa",
"invalidTokenAddress": "No es una dirección de token válida",
"exchangeExists": "La divisa {{ label }} ya existe!",
"invalidSymbol": "Símbolo inválido",
"invalidDecimals": "Decimales inválidos",
"tokenAddress": "Dirección de Token",
"label": "Etiqueta",
"decimals": "Decimales",
"enterTokenCont": "Ingresá una dirección de token para continuar"
"noWallet": "No se encontró billetera de Ethereum",
"wrongNetwork": "Te encontrás en la red equivocada",
"switchNetwork": "Por favor cambia a {{ correctNetwork }}",
"installWeb3MobileBrowser": "Por favor ingresá desde un navegador móvil con web3 habilitado como Trust Wallet o Coinbase Wallet.",
"installMetamask": "Por favor visítanos nuevamente luego de instalar Metamask en Chrome o Brave.",
"disconnected": "Desconectado",
"swap": "Intercambiar",
"send": "Enviar",
"pool": "Pool",
"betaWarning": "Este proyecto se encuentra en beta. Usalo bajo tu propio riesgo.",
"input": "Entrada",
"output": "Salida",
"estimated": "estimado",
"balance": "Saldo: {{ balanceInput }}",
"unlock": "Desbloquear",
"pending": "Pendiente",
"selectToken": "Seleccioná un token",
"searchOrPaste": "Buscar Token o Pegar Dirección",
"noExchange": "No se encontró la divisa",
"exchangeRate": "Tasa de Cambio",
"enterValueCont": "Ingresá un valor en {{ missingCurrencyValue }} para continuar.",
"selectTokenCont": "Seleccioná un token para continuar.",
"noLiquidity": "Sin liquidez.",
"unlockTokenCont": "Por favor desbloqueá un token para continuar.",
"transactionDetails": "Detalles de la transacción",
"hideDetails": "Ocultar detalles",
"youAreSelling": "Estás vendiendo",
"orTransFail": "o la transacción fallará.",
"youWillReceive": "Vas a recibir al menos",
"youAreBuying": "Estás comprando",
"itWillCost": "Costará a lo sumo",
"insufficientBalance": "Saldo insuficiente",
"inputNotValid": "No es un valor de entrada válido",
"differentToken": "Debe ser un token distinto.",
"noRecipient": "Ingresá una dirección de billetera para enviar.",
"invalidRecipient": "Por favor ingrese una billetera de destino válida.",
"recipientAddress": "Dirección del recipiente",
"youAreSending": "Estás enviando",
"willReceive": "recibirá al menos",
"to": "a",
"addLiquidity": "Agregar liquidez",
"deposit": "Depositar",
"currentPoolSize": "Tamaño del Pool Actual",
"yourPoolShare": "Tu parte del Pool",
"noZero": "El monto no puede ser cero.",
"mustBeETH": "Una de las entradas debe ser ETH.",
"enterCurrencyOrLabelCont": "Ingresá un valor de {{ inputCurrency }} o de {{ label }} para continuar.",
"youAreAdding": "Estás agregando entre",
"and": "y",
"intoPool": "en el pool de liquidez.",
"outPool": "en el pool de liquidez.",
"youWillMint": "Vas a acuñar",
"liquidityTokens": "tokens de liquidez.",
"totalSupplyIs": "El actual suministro total de tokens de liquidez es",
"youAreSettingExRate": "Está configurando el tipo de cambio inicial a",
"totalSupplyIs0": "El actual suministro total de tokens de liquidez es 0.",
"tokenWorth": "Al tipo de cambio actual, cada token del pool vale",
"firstLiquidity": "Sos la primer persona en agregar liquidez!",
"initialExchangeRate": "El tipo de cambio inicial se establecerá en función de tus depósitos. Por favor, asegúrate de que tus depósitos en ETH y {{ label }} tengan el mismo valor fíat.",
"removeLiquidity": "Remover Liquidez",
"poolTokens": "Pool de Tokens",
"enterLabelCont": "Ingresá un valor de {{ label }} para continuar.",
"youAreRemoving": "Estás quitando entre",
"youWillRemove": "Vas a remover",
"createExchange": "Crear divisa",
"invalidTokenAddress": "No es una dirección de token válida",
"exchangeExists": "La divisa {{ label }} ya existe!",
"invalidSymbol": "Símbolo inválido",
"invalidDecimals": "Decimales inválidos",
"tokenAddress": "Dirección de Token",
"label": "Etiqueta",
"decimals": "Decimales",
"enterTokenCont": "Ingresá una dirección de token para continuar"
}
{
"noWallet": "No se ha encontrado billetera de Ethereum",
"wrongNetwork": "Se encuentra en la red equivocada",
"switchNetwork": "Por favor cambie a {{ correctNetwork }}",
"installWeb3MobileBrowser": "Por favor ingrese desde un navegador móvil con web3 habilitado como Trust Wallet o Coinbase Wallet.",
"installMetamask": "Por favor visítenos nuevamente luego de instalar Metamask en Chrome o Brave.",
"disconnected": "Desconectado",
"swap": "Intercambiar",
"send": "Enviar",
"pool": "Pool",
"betaWarning": "Este proyecto se encuentra en beta. Úselo bajo tu propio riesgo.",
"input": "Entrada",
"output": "Salida",
"estimated": "estimado",
"balance": "Saldo: {{ balanceInput }}",
"unlock": "Desbloquear",
"pending": "Pendiente",
"selectToken": "Seleccione un token",
"searchOrPaste": "Buscar Token o Pegar Dirección",
"noExchange": "No se ha encontrado la divisa",
"exchangeRate": "Tasa de Cambio",
"enterValueCont": "Ingrese un valor en {{ missingCurrencyValue }} para continuar.",
"selectTokenCont": "Seleccione un token para continuar.",
"noLiquidity": "Sin liquidez.",
"unlockTokenCont": "Por favor desbloquea un token para continuar.",
"transactionDetails": "Detalles de la transacción",
"hideDetails": "Ocultar detalles",
"youAreSelling": "Está vendiendo",
"orTransFail": "o la transacción fallará.",
"youWillReceive": "Va a recibir al menos",
"youAreBuying": "Está comprando",
"itWillCost": "Costará a lo sumo",
"insufficientBalance": "Saldo insuficiente",
"inputNotValid": "No es un valor de entrada válido",
"differentToken": "Debe ser un token distinto.",
"noRecipient": "Ingrese una dirección de billetera para enviar.",
"invalidRecipient": "Por favor ingrese una billetera de destino válida.",
"recipientAddress": "Dirección del recipiente",
"youAreSending": "Está enviando",
"willReceive": "recibirá al menos",
"to": "a",
"addLiquidity": "Agregar liquidez",
"deposit": "Depositar",
"currentPoolSize": "Tamaño del Pool Actual",
"yourPoolShare": "Su parte del Pool",
"noZero": "El monto no puede ser cero.",
"mustBeETH": "Una de las entradas debe ser ETH.",
"enterCurrencyOrLabelCont": "Ingrese un valor de {{ inputCurrency }} o de {{ label }} para continuar.",
"youAreAdding": "Está agregando entre",
"and": "y",
"intoPool": "en el pool de liquidez.",
"outPool": "en el pool de liquidez.",
"youWillMint": "Va a acuñar",
"liquidityTokens": "tokens de liquidez.",
"totalSupplyIs": "El actual suministro total de tokens de liquidez es",
"youAreSettingExRate": "Está configurando el tipo de cambio inicial a",
"totalSupplyIs0": "El actual suministro total de tokens de liquidez es 0.",
"tokenWorth": "Al tipo de cambio actual, cada token del pool vale",
"firstLiquidity": "Es la primer persona en agregar liquidez!",
"initialExchangeRate": "El tipo de cambio inicial se establecerá en función de sus depósitos. Por favor, asegúrese de que sus depósitos en ETH y {{ label }} tengan el mismo valor fíat.",
"removeLiquidity": "Remover Liquidez",
"poolTokens": "Pool de Tokens",
"enterLabelCont": "Ingresa un valor de {{ label }} para continuar.",
"youAreRemoving": "Está quitando entre",
"youWillRemove": "Va a remover",
"createExchange": "Crear tipo de cambio",
"invalidTokenAddress": "No es una dirección de token válida",
"exchangeExists": "El tipo de cambio {{ label }} ya existe!",
"invalidSymbol": "Símbolo inválido",
"invalidDecimals": "Decimales inválidos",
"tokenAddress": "Dirección de Token",
"label": "Etiqueta",
"decimals": "Decimales",
"enterTokenCont": "Ingrese una dirección de token para continuar"
"noWallet": "No se ha encontrado billetera de Ethereum",
"wrongNetwork": "Se encuentra en la red equivocada",
"switchNetwork": "Por favor cambie a {{ correctNetwork }}",
"installWeb3MobileBrowser": "Por favor ingrese desde un navegador móvil con web3 habilitado como Trust Wallet o Coinbase Wallet.",
"installMetamask": "Por favor visítenos nuevamente luego de instalar Metamask en Chrome o Brave.",
"disconnected": "Desconectado",
"swap": "Intercambiar",
"send": "Enviar",
"pool": "Pool",
"betaWarning": "Este proyecto se encuentra en beta. Úselo bajo tu propio riesgo.",
"input": "Entrada",
"output": "Salida",
"estimated": "estimado",
"balance": "Saldo: {{ balanceInput }}",
"unlock": "Desbloquear",
"pending": "Pendiente",
"selectToken": "Seleccione un token",
"searchOrPaste": "Buscar Token o Pegar Dirección",
"noExchange": "No se ha encontrado la divisa",
"exchangeRate": "Tasa de Cambio",
"enterValueCont": "Ingrese un valor en {{ missingCurrencyValue }} para continuar.",
"selectTokenCont": "Seleccione un token para continuar.",
"noLiquidity": "Sin liquidez.",
"unlockTokenCont": "Por favor desbloquea un token para continuar.",
"transactionDetails": "Detalles de la transacción",
"hideDetails": "Ocultar detalles",
"youAreSelling": "Está vendiendo",
"orTransFail": "o la transacción fallará.",
"youWillReceive": "Va a recibir al menos",
"youAreBuying": "Está comprando",
"itWillCost": "Costará a lo sumo",
"insufficientBalance": "Saldo insuficiente",
"inputNotValid": "No es un valor de entrada válido",
"differentToken": "Debe ser un token distinto.",
"noRecipient": "Ingrese una dirección de billetera para enviar.",
"invalidRecipient": "Por favor ingrese una billetera de destino válida.",
"recipientAddress": "Dirección del recipiente",
"youAreSending": "Está enviando",
"willReceive": "recibirá al menos",
"to": "a",
"addLiquidity": "Agregar liquidez",
"deposit": "Depositar",
"currentPoolSize": "Tamaño del Pool Actual",
"yourPoolShare": "Su parte del Pool",
"noZero": "El monto no puede ser cero.",
"mustBeETH": "Una de las entradas debe ser ETH.",
"enterCurrencyOrLabelCont": "Ingrese un valor de {{ inputCurrency }} o de {{ label }} para continuar.",
"youAreAdding": "Está agregando entre",
"and": "y",
"intoPool": "en el pool de liquidez.",
"outPool": "en el pool de liquidez.",
"youWillMint": "Va a acuñar",
"liquidityTokens": "tokens de liquidez.",
"totalSupplyIs": "El actual suministro total de tokens de liquidez es",
"youAreSettingExRate": "Está configurando el tipo de cambio inicial a",
"totalSupplyIs0": "El actual suministro total de tokens de liquidez es 0.",
"tokenWorth": "Al tipo de cambio actual, cada token del pool vale",
"firstLiquidity": "Es la primer persona en agregar liquidez!",
"initialExchangeRate": "El tipo de cambio inicial se establecerá en función de sus depósitos. Por favor, asegúrese de que sus depósitos en ETH y {{ label }} tengan el mismo valor fíat.",
"removeLiquidity": "Remover Liquidez",
"poolTokens": "Pool de Tokens",
"enterLabelCont": "Ingresa un valor de {{ label }} para continuar.",
"youAreRemoving": "Está quitando entre",
"youWillRemove": "Va a remover",
"createExchange": "Crear tipo de cambio",
"invalidTokenAddress": "No es una dirección de token válida",
"exchangeExists": "El tipo de cambio {{ label }} ya existe!",
"invalidSymbol": "Símbolo inválido",
"invalidDecimals": "Decimales inválidos",
"tokenAddress": "Dirección de Token",
"label": "Etiqueta",
"decimals": "Decimales",
"enterTokenCont": "Ingrese una dirección de token para continuar"
}
{
"noWallet": "Không tìm thấy ví tiền Ethereum",
"wrongNetwork": "Kết nối mạng không đúng",
"switchNetwork": "Vui lòng chuyển sang {{ correctNetwork }}",
"installWeb3MobileBrowser": "Vui lòng truy cập từ trình duyệt di động hỗ trợ web3 như là Ví Trust hoặc Ví Coinbase",
"installMetamask": "Vui lòng truy cập sau khi cài đặt Metamask trên Chrome hoặc Brave.",
"disconnected": "Ngắt kết nối rồi",
"swap": "Hoán đổi",
"swapAnyway": "Tiếp tục hoán đổi?",
"send": "Gửi",
"sendAnyway": "Tiếp tục gửi?",
"pool": "Chung vốn",
"betaWarning": "Dự án này đang trong giai đoạn thử nghiệm. Sử dụng có rủi ro của riêng bạn",
"input": "Đầu vào",
"output": "Đầu ra",
"estimated": "ước lượng",
"balance": "Số dư: {{ balanceInput }}",
"unlock": "Mở khóa",
"pending": "Đang chờ xử lý",
"selectToken": "Chọn một đồng tiền ảo",
"searchOrPaste": "Tìm kiếm tên, biểu tượng, hoặc địa chỉ của đồng tiền ảo",
"searchOrPasteMobile": "Tên, Biểu tượng, hoặc Địa chỉ",
"noExchange": "Không tìm thấy giao dịch",
"exchangeRate": "Tỷ giá",
"unknownError": "Rất tiếc! Xảy ra lỗi không xác định. Vui lòng làm mới trang, hoặc truy cập từ trình duyệt hay thiết bị khác.",
"enterValueCont": "Nhập một giá trị {{ missingCurrencyValue }} để tiếp tục.",
"selectTokenCont": "Chọn một đồng tiền ảo để tiếp tục.",
"noLiquidity": "Không có tính thanh khoản.",
"insufficientLiquidity": "Không đủ tính thanh khoản.",
"unlockTokenCont": "Vui lòng mở khoá đồng tiền ảo để tiếp tục",
"transactionDetails": "Chi tiết nâng cao",
"hideDetails": "Ẩn chi tiết",
"slippageWarning": "Cảnh báo trượt giá",
"highSlippageWarning": "Cảnh báo trượt giá cao",
"youAreSelling": "Bạn đang bán",
"orTransFail": "hoặc giao dịch sẽ thất bại.",
"youWillReceive": "Bạn sẽ nhận dược ít nhất là",
"youAreBuying": "Bạn đang mua",
"itWillCost": "Nó sẽ có giá cao nhất",
"forAtMost": "nhiều nhất",
"insufficientBalance": "Số dư không đủ",
"inputNotValid": "Giá trị nhập vào không hợp lệ",
"differentToken": "Đồng tiền ảo phải khác nhau.",
"noRecipient": "Nhập địa chỉ ví để gửi đến.",
"invalidRecipient": "Vui lòng nhập một người nhận địa chỉ ví hợp lệ.",
"recipientAddress": "Địa chỉ người nhận",
"youAreSending": "Bạn đang gửi",
"willReceive": "sẽ nhận dược ít nhất là",
"to": "đến",
"addLiquidity": "Thêm tiền thanh khoản",
"deposit": "Gửi tiền",
"currentPoolSize": "Quy mô hiện tại của quỹ",
"yourPoolShare": "Phần hùn của bạn trong quỹ",
"noZero": "Số tiền không thể bằng không.",
"mustBeETH": "Một trong những đầu vào phải là ETH.",
"enterCurrencyOrLabelCont": "Nhập giá trị {{ inputCurrency }} hoặc {{ label }} để tiếp tục.",
"youAreAdding": "Bạn đang thêm",
"and": "và",
"intoPool": "vào nhóm thanh khoản.",
"outPool": "từ nhóm thanh khoản.",
"youWillMint": "Bạn sẽ đúc tiền",
"liquidityTokens": "đồng thanh khoản.",
"totalSupplyIs": "Tổng cung hiện tại của đồng thanh khoản là",
"youAreSettingExRate": "Bạn đang đặt tỷ giá hối đoái ban đầu thành",
"totalSupplyIs0": "Tổng cung hiện tại của đồng thanh khoản là 0.",
"tokenWorth": "Tại tỷ giá hối đoái hiện tại, giá trị đồng token của quỹ là",
"firstLiquidity": "Bạn là người đầu tiên thêm thanh khoản!",
"initialExchangeRate": "Tỷ giá hối đoái ban đầu sẽ được thiết lập dựa trên tiền gửi của bạn. Vui lòng đảm bảo rằng tiền gửi ETH và {{ label }} của bạn có cùng giá trị tiền định danh.",
"removeLiquidity": "Loại bỏ thanh khoản",
"poolTokens": "Đồng tiền ảo của quỹ",
"enterLabelCont": "Nhập giá trị {{ label }} để tiếp tục",
"youAreRemoving": "Bạn đang loại bỏ giữa",
"youWillRemove": "Bạn sẽ loại bỏ",
"createExchange": "Tạo giao dịch",
"invalidTokenAddress": "Địa chỉ đồng tiền điện tử không hợp lệ",
"exchangeExists": "{{ label }} Giao dịch đã tồn tại!",
"invalidSymbol": "Biểu tượng không hợp lệ",
"invalidDecimals": "Số thập phân không hợp lệ",
"tokenAddress": "Địa chỉ đồng tiền điện tử",
"label": "Nhãn",
"name": "Tên",
"symbol": "Biểu tượng",
"decimals": "Số thập phân",
"enterTokenCont": "Nhập địa chỉ đồng tiền ảo để tiếp tục",
"priceChange": "Trượt giá dự kiến",
"forAtLeast": "cho ít nhất "
"noWallet": "Không tìm thấy ví tiền Ethereum",
"wrongNetwork": "Kết nối mạng không đúng",
"switchNetwork": "Vui lòng chuyển sang {{ correctNetwork }}",
"installWeb3MobileBrowser": "Vui lòng truy cập từ trình duyệt di động hỗ trợ web3 như là Ví Trust hoặc Ví Coinbase",
"installMetamask": "Vui lòng truy cập sau khi cài đặt Metamask trên Chrome hoặc Brave.",
"disconnected": "Ngắt kết nối rồi",
"swap": "Hoán đổi",
"swapAnyway": "Tiếp tục hoán đổi?",
"send": "Gửi",
"sendAnyway": "Tiếp tục gửi?",
"pool": "Chung vốn",
"betaWarning": "Dự án này đang trong giai đoạn thử nghiệm. Sử dụng có rủi ro của riêng bạn",
"input": "Đầu vào",
"output": "Đầu ra",
"estimated": "ước lượng",
"balance": "Số dư: {{ balanceInput }}",
"unlock": "Mở khóa",
"pending": "Đang chờ xử lý",
"selectToken": "Chọn một đồng tiền ảo",
"searchOrPaste": "Tìm kiếm tên, biểu tượng, hoặc địa chỉ của đồng tiền ảo",
"searchOrPasteMobile": "Tên, Biểu tượng, hoặc Địa chỉ",
"noExchange": "Không tìm thấy giao dịch",
"exchangeRate": "Tỷ giá",
"unknownError": "Rất tiếc! Xảy ra lỗi không xác định. Vui lòng làm mới trang, hoặc truy cập từ trình duyệt hay thiết bị khác.",
"enterValueCont": "Nhập một giá trị {{ missingCurrencyValue }} để tiếp tục.",
"selectTokenCont": "Chọn một đồng tiền ảo để tiếp tục.",
"noLiquidity": "Không có tính thanh khoản.",
"insufficientLiquidity": "Không đủ tính thanh khoản.",
"unlockTokenCont": "Vui lòng mở khoá đồng tiền ảo để tiếp tục",
"transactionDetails": "Chi tiết nâng cao",
"hideDetails": "Ẩn chi tiết",
"slippageWarning": "Cảnh báo trượt giá",
"highSlippageWarning": "Cảnh báo trượt giá cao",
"youAreSelling": "Bạn đang bán",
"orTransFail": "hoặc giao dịch sẽ thất bại.",
"youWillReceive": "Bạn sẽ nhận dược ít nhất là",
"youAreBuying": "Bạn đang mua",
"itWillCost": "Nó sẽ có giá cao nhất",
"forAtMost": "nhiều nhất",
"insufficientBalance": "Số dư không đủ",
"inputNotValid": "Giá trị nhập vào không hợp lệ",
"differentToken": "Đồng tiền ảo phải khác nhau.",
"noRecipient": "Nhập địa chỉ ví để gửi đến.",
"invalidRecipient": "Vui lòng nhập một người nhận địa chỉ ví hợp lệ.",
"recipientAddress": "Địa chỉ người nhận",
"youAreSending": "Bạn đang gửi",
"willReceive": "sẽ nhận dược ít nhất là",
"to": "đến",
"addLiquidity": "Thêm tiền thanh khoản",
"deposit": "Gửi tiền",
"currentPoolSize": "Quy mô hiện tại của quỹ",
"yourPoolShare": "Phần hùn của bạn trong quỹ",
"noZero": "Số tiền không thể bằng không.",
"mustBeETH": "Một trong những đầu vào phải là ETH.",
"enterCurrencyOrLabelCont": "Nhập giá trị {{ inputCurrency }} hoặc {{ label }} để tiếp tục.",
"youAreAdding": "Bạn đang thêm",
"and": "và",
"intoPool": "vào nhóm thanh khoản.",
"outPool": "từ nhóm thanh khoản.",
"youWillMint": "Bạn sẽ đúc tiền",
"liquidityTokens": "đồng thanh khoản.",
"totalSupplyIs": "Tổng cung hiện tại của đồng thanh khoản là",
"youAreSettingExRate": "Bạn đang đặt tỷ giá hối đoái ban đầu thành",
"totalSupplyIs0": "Tổng cung hiện tại của đồng thanh khoản là 0.",
"tokenWorth": "Tại tỷ giá hối đoái hiện tại, giá trị đồng token của quỹ là",
"firstLiquidity": "Bạn là người đầu tiên thêm thanh khoản!",
"initialExchangeRate": "Tỷ giá hối đoái ban đầu sẽ được thiết lập dựa trên tiền gửi của bạn. Vui lòng đảm bảo rằng tiền gửi ETH và {{ label }} của bạn có cùng giá trị tiền định danh.",
"removeLiquidity": "Loại bỏ thanh khoản",
"poolTokens": "Đồng tiền ảo của quỹ",
"enterLabelCont": "Nhập giá trị {{ label }} để tiếp tục",
"youAreRemoving": "Bạn đang loại bỏ giữa",
"youWillRemove": "Bạn sẽ loại bỏ",
"createExchange": "Tạo giao dịch",
"invalidTokenAddress": "Địa chỉ đồng tiền điện tử không hợp lệ",
"exchangeExists": "{{ label }} Giao dịch đã tồn tại!",
"invalidSymbol": "Biểu tượng không hợp lệ",
"invalidDecimals": "Số thập phân không hợp lệ",
"tokenAddress": "Địa chỉ đồng tiền điện tử",
"label": "Nhãn",
"name": "Tên",
"symbol": "Biểu tượng",
"decimals": "Số thập phân",
"enterTokenCont": "Nhập địa chỉ đồng tiền ảo để tiếp tục",
"priceChange": "Trượt giá dự kiến",
"forAtLeast": "cho ít nhất "
}
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)<{
width: ${({ width }) => (width ? width : '100%')};
font-weight: 500;
text-align: center;
border-radius: 20px;
border-radius: 12px;
border-radius: ${({ borderRadius }) => borderRadius && borderRadius};
outline: none;
border: 1px solid transparent;
......@@ -110,28 +110,31 @@ export const ButtonGray = styled(Base)`
`
export const ButtonSecondary = styled(Base)`
background-color: ${({ theme }) => theme.primary5};
color: ${({ theme }) => theme.primaryText1};
border: 1px solid ${({ theme }) => theme.primary4};
color: ${({ theme }) => theme.primary1};
background-color: transparent;
font-size: 16px;
border-radius: 8px;
border-radius: 12px;
padding: ${({ padding }) => (padding ? padding : '10px')};
&:focus {
box-shadow: 0 0 0 1pt ${({ theme }) => theme.primary4};
background-color: ${({ theme }) => theme.primary4};
border: 1px solid ${({ theme }) => theme.primary3};
}
&:hover {
background-color: ${({ theme }) => theme.primary4};
border: 1px solid ${({ theme }) => theme.primary3};
}
&:active {
box-shadow: 0 0 0 1pt ${({ theme }) => theme.primary4};
background-color: ${({ theme }) => theme.primary4};
border: 1px solid ${({ theme }) => theme.primary3};
}
&:disabled {
background-color: ${({ theme }) => theme.primary5};
opacity: 50%;
cursor: auto;
}
a:hover {
text-decoration: none;
}
`
export const ButtonPink = styled(Base)`
......@@ -184,13 +187,13 @@ export const ButtonEmpty = styled(Base)`
align-items: center;
&:focus {
background-color: ${({ theme }) => theme.advancedBG};
text-decoration: underline;
}
&:hover {
background-color: ${({ theme }) => theme.advancedBG};
text-decoration: underline;
}
&:active {
background-color: ${({ theme }) => theme.advancedBG};
text-decoration: underline;
}
&:disabled {
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 {
otherCurrency?: Currency | null
id: string
showCommonBases?: boolean
customBalanceText?: string
}
export default function CurrencyInputPanel({
......@@ -145,7 +146,8 @@ export default function CurrencyInputPanel({
hideInput = false,
otherCurrency,
id,
showCommonBases
showCommonBases,
customBalanceText
}: CurrencyInputPanelProps) {
const { t } = useTranslation()
......@@ -176,7 +178,7 @@ export default function CurrencyInputPanel({
style={{ display: 'inline', cursor: 'pointer' }}
>
{!hideBalance && !!currency && selectedCurrencyBalance
? 'Balance: ' + selectedCurrencyBalance?.toSignificant(6)
? (customBalanceText ?? 'Balance: ') + selectedCurrencyBalance?.toSignificant(6)
: ' -'}
</TYPE.body>
)}
......
......@@ -20,6 +20,8 @@ const StyledEthereumLogo = styled.img<{ size: string }>`
const StyledLogo = styled(Logo)<{ size: string }>`
width: ${({ size }) => size};
height: ${({ size }) => size};
border-radius: ${({ size }) => size};
box-shadow: 0px 6px 10px rgba(0, 0, 0, 0.075);
`
export default function CurrencyLogo({
......
......@@ -22,7 +22,7 @@ const HigherLogo = styled(CurrencyLogo)`
`
const CoveredLogo = styled(CurrencyLogo)<{ sizeraw: number }>`
position: absolute;
left: ${({ sizeraw }) => (sizeraw / 2).toString() + 'px'};
left: ${({ sizeraw }) => '-' + (sizeraw / 2).toString() + 'px'} !important;
`
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 }>`
* Takes in custom size and stroke for circle color, default to primary color as fill,
* 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 (
<StyledSVG viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" size={size} stroke={stroke} {...rest}>
<path
......
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 { ReactComponent as MenuIcon } from '../../assets/images/menu.svg'
import { useActiveWeb3React } from '../../hooks'
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 { ButtonPrimary } from '../Button'
const StyledMenuIcon = styled(MenuIcon)`
path {
......@@ -53,15 +56,19 @@ const MenuFlyout = styled.span`
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),
0px 24px 32px rgba(0, 0, 0, 0.01);
border-radius: 0.5rem;
border-radius: 12px;
padding: 0.5rem;
display: flex;
flex-direction: column;
font-size: 1rem;
position: absolute;
top: 3rem;
top: 4rem;
right: 0rem;
z-index: 100;
${({ theme }) => theme.mediaWidth.upToMedium`
top: -17.25rem;
`};
`
const MenuItem = styled(ExternalLink)`
......@@ -81,10 +88,13 @@ const MenuItem = styled(ExternalLink)`
const CODE_LINK = 'https://github.com/Uniswap/uniswap-interface'
export default function Menu() {
const node = useRef<HTMLDivElement>()
const [open, toggle] = useToggle(false)
const { account } = useActiveWeb3React()
const node = useRef<HTMLDivElement>()
const open = useModalOpen(ApplicationModal.MENU)
const toggle = useToggleModal(ApplicationModal.MENU)
useOnClickOutside(node, open ? toggle : undefined)
const openClaimModal = useToggleModal(ApplicationModal.ADDRESS_CLAIM)
return (
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/30451
......@@ -92,6 +102,7 @@ export default function Menu() {
<StyledMenuButton onClick={toggle}>
<StyledMenuIcon />
</StyledMenuButton>
{open && (
<MenuFlyout>
<MenuItem id="link" href="https://uniswap.org/">
......@@ -114,6 +125,11 @@ export default function Menu() {
<PieChart size={14} />
Analytics
</MenuItem>
{account && (
<ButtonPrimary onClick={openClaimModal} padding="8px 16px" width="100%" borderRadius="12px" mt="0.5rem">
Claim UNI
</ButtonPrimary>
)}
</MenuFlyout>
)}
</StyledMenu>
......
......@@ -31,14 +31,16 @@ const StyledDialogContent = styled(({ minHeight, maxHeight, mobile, isOpen, ...r
)).attrs({
'aria-label': 'dialog'
})`
overflow-y: ${({ mobile }) => (mobile ? 'scroll' : 'hidden')};
&[data-reach-dialog-content] {
margin: 0 0 2rem 0;
border: 1px solid ${({ theme }) => theme.bg1};
background-color: ${({ theme }) => theme.bg1};
box-shadow: 0 4px 8px 0 ${({ theme }) => transparentize(0.95, theme.shadow1)};
padding: 0px;
width: 50vw;
overflow: hidden;
overflow-y: ${({ mobile }) => (mobile ? 'scroll' : 'hidden')};
overflow-x: hidden;
align-self: ${({ mobile }) => (mobile ? 'flex-end' : 'center')};
......@@ -85,7 +87,7 @@ export default function Modal({
isOpen,
onDismiss,
minHeight = false,
maxHeight = 50,
maxHeight = 90,
initialFocusRef,
children
}: 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)`
export function SwapPoolTabs({ active }: { active: 'swap' | 'pool' }) {
const { t } = useTranslation()
return (
<Tabs style={{ marginBottom: '20px' }}>
<Tabs style={{ marginBottom: '20px', display: 'none' }}>
<StyledNavLink id={`swap-nav-link`} to={'/swap'} isActive={() => active === 'swap'}>
{t('swap')}
</StyledNavLink>
......@@ -80,14 +80,14 @@ export function FindPoolTabs() {
)
}
export function AddRemoveTabs({ adding }: { adding: boolean }) {
export function AddRemoveTabs({ adding, creating }: { adding: boolean; creating: boolean }) {
return (
<Tabs>
<RowBetween style={{ padding: '1rem' }}>
<HistoryLink to="/pool">
<StyledArrowLeft />
</HistoryLink>
<ActiveText>{adding ? 'Add' : 'Remove'} Liquidity</ActiveText>
<ActiveText>{creating ? 'Create a pair' : adding ? 'Add Liquidity' : 'Remove Liquidity'}</ActiveText>
<QuestionHelper
text={
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'
import { useActivePopups } from '../../state/application/hooks'
import { AutoColumn } from '../Column'
import PopupItem from './PopupItem'
import ClaimPopup from './ClaimPopup'
import { useURLWarningVisible } from '../../state/user/hooks'
const MobilePopupWrapper = styled.div<{ height: string | number }>`
position: relative;
......@@ -29,13 +31,13 @@ const MobilePopupInner = styled.div`
}
`
const FixedPopupColumn = styled(AutoColumn)`
const FixedPopupColumn = styled(AutoColumn)<{ extraPadding: boolean }>`
position: fixed;
top: 64px;
top: ${({ extraPadding }) => (extraPadding ? '108px' : '88px')};
right: 1rem;
max-width: 355px !important;
width: 100%;
z-index: 2;
z-index: 3;
${({ theme }) => theme.mediaWidth.upToSmall`
display: none;
......@@ -46,9 +48,12 @@ export default function Popups() {
// get all popups
const activePopups = useActivePopups()
const urlWarningActive = useURLWarningVisible()
return (
<>
<FixedPopupColumn gap="20px">
<FixedPopupColumn gap="20px" extraPadding={urlWarningActive}>
<ClaimPopup />
{activePopups.map(item => (
<PopupItem key={item.key} content={item.content} popKey={item.key} removeAfterMs={item.removeAfterMs} />
))}
......
......@@ -9,16 +9,20 @@ import { useTotalSupply } from '../../data/TotalSupply'
import { useActiveWeb3React } from '../../hooks'
import { useTokenBalance } from '../../state/wallet/hooks'
import { ExternalLink } from '../../theme'
import { ExternalLink, TYPE } from '../../theme'
import { currencyId } from '../../utils/currencyId'
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 CurrencyLogo from '../CurrencyLogo'
import DoubleCurrencyLogo from '../DoubleLogo'
import { AutoRow, RowBetween, RowFixed } from '../Row'
import { RowBetween, RowFixed } from '../Row'
import { Dots } from '../swap/styleds'
export const FixedHeightRow = styled(RowBetween)`
......@@ -26,11 +30,18 @@ export const FixedHeightRow = styled(RowBetween)`
`
export const HoverCard = styled(Card)`
border: 1px solid ${({ theme }) => theme.bg2};
border: 1px solid transparent;
:hover {
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 {
pair: Pair
......@@ -49,6 +60,11 @@ export function MinimalPositionCard({ pair, showUnwrapped = false, border }: Pos
const userPoolBalance = useTokenBalance(account ?? undefined, 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] =
!!pair &&
!!totalPoolTokens &&
......@@ -63,7 +79,7 @@ export function MinimalPositionCard({ pair, showUnwrapped = false, border }: Pos
return (
<>
{userPoolBalance && (
{userPoolBalance && JSBI.greaterThan(userPoolBalance.raw, JSBI.BigInt(0)) ? (
<GreyCard border={border}>
<AutoColumn gap="12px">
<FixedHeightRow>
......@@ -88,12 +104,20 @@ export function MinimalPositionCard({ pair, showUnwrapped = false, border }: Pos
</FixedHeightRow>
<AutoColumn gap="4px">
<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}:
</Text>
{token0Deposited ? (
<RowFixed>
<Text color="#888D9B" fontSize={16} fontWeight={500} marginLeft={'6px'}>
<Text fontSize={16} fontWeight={500} marginLeft={'6px'}>
{token0Deposited?.toSignificant(6)}
</Text>
</RowFixed>
......@@ -102,12 +126,12 @@ export function MinimalPositionCard({ pair, showUnwrapped = false, border }: Pos
)}
</FixedHeightRow>
<FixedHeightRow>
<Text color="#888D9B" fontSize={16} fontWeight={500}>
<Text fontSize={16} fontWeight={500}>
{currency1.symbol}:
</Text>
{token1Deposited ? (
<RowFixed>
<Text color="#888D9B" fontSize={16} fontWeight={500} marginLeft={'6px'}>
<Text fontSize={16} fontWeight={500} marginLeft={'6px'}>
{token1Deposited?.toSignificant(6)}
</Text>
</RowFixed>
......@@ -118,6 +142,16 @@ export function MinimalPositionCard({ pair, showUnwrapped = false, border }: Pos
</AutoColumn>
</AutoColumn>
</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) {
]
: [undefined, undefined]
const backgroundColor = useColor(pair?.token0)
return (
<HoverCard border={border}>
<StyledPositionCard border={border} bgColor={backgroundColor}>
<CardNoise />
<AutoColumn gap="12px">
<FixedHeightRow onClick={() => setShowMore(!showMore)} style={{ cursor: 'pointer' }}>
<FixedHeightRow>
<RowFixed>
<DoubleCurrencyLogo currency0={currency0} currency1={currency1} margin={true} size={20} />
<Text fontWeight={500} fontSize={20}>
{!currency0 || !currency1 ? <Dots>Loading</Dots> : `${currency0.symbol}/${currency1.symbol}`}
</Text>
</RowFixed>
<RowFixed>
{showMore ? (
<ChevronUp size="20" style={{ marginLeft: '10px' }} />
) : (
<ChevronDown size="20" style={{ marginLeft: '10px' }} />
)}
<RowFixed gap="8px">
<ButtonEmpty
padding="6px 8px"
borderRadius="12px"
width="fit-content"
onClick={() => setShowMore(!showMore)}
>
{showMore ? (
<>
{' '}
Manage
<ChevronUp size="20" style={{ marginLeft: '10px' }} />
</>
) : (
<>
Manage
<ChevronDown size="20" style={{ marginLeft: '10px' }} />
</>
)}
</ButtonEmpty>
</RowFixed>
</FixedHeightRow>
{showMore && (
<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>
<RowFixed>
<Text fontSize={16} fontWeight={500}>
......@@ -206,14 +267,7 @@ export default function FullPositionCard({ pair, border }: PositionCardProps) {
'-'
)}
</FixedHeightRow>
<FixedHeightRow>
<Text fontSize={16} fontWeight={500}>
Your pool tokens:
</Text>
<Text fontSize={16} fontWeight={500}>
{userPoolBalance ? userPoolBalance.toSignificant(4) : '-'}
</Text>
</FixedHeightRow>
<FixedHeightRow>
<Text fontSize={16} fontWeight={500}>
Your pool share:
......@@ -223,22 +277,37 @@ export default function FullPositionCard({ pair, border }: PositionCardProps) {
</Text>
</FixedHeightRow>
<AutoRow justify="center" marginTop={'10px'}>
<ExternalLink href={`https://uniswap.info/pair/${pair.liquidityToken.address}`}>
View pool information ↗
<ButtonSecondary padding="8px" borderRadius="8px">
<ExternalLink
style={{ width: '100%', textAlign: 'center' }}
href={`https://uniswap.info/account/${account}`}
>
View accrued fees and analytics<span style={{ fontSize: '11px' }}></span>
</ExternalLink>
</AutoRow>
</ButtonSecondary>
<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
</ButtonSecondary>
<ButtonSecondary as={Link} width="48%" to={`/remove/${currencyId(currency0)}/${currencyId(currency1)}`}>
</ButtonPrimary>
<ButtonPrimary
padding="8px"
borderRadius="8px"
as={Link}
width="48%"
to={`/remove/${currencyId(currency0)}/${currencyId(currency1)}`}
>
Remove
</ButtonSecondary>
</ButtonPrimary>
</RowBetween>
</AutoColumn>
)}
</AutoColumn>
</HoverCard>
</StyledPositionCard>
)
}
......@@ -4,9 +4,7 @@ import { RowBetween } from '../Row'
import { AutoColumn } from '../Column'
import { transparentize } from 'polished'
const Wrapper = styled(AutoColumn)`
margin-top: 1.25rem;
`
const Wrapper = styled(AutoColumn)``
const Grouping = styled(RowBetween)`
width: 50%;
......@@ -32,20 +30,23 @@ const CircleRow = styled.div`
align-items: center;
`
const Connector = styled.div<{ prevConfirmed?: boolean }>`
const Connector = styled.div<{ prevConfirmed?: boolean; disabled?: boolean }>`
width: 100%;
height: 2px;
background-color: ;
background: linear-gradient(
90deg,
${({ theme, prevConfirmed }) => transparentize(0.5, prevConfirmed ? theme.green1 : theme.primary1)} 0%,
${({ theme, prevConfirmed }) => (prevConfirmed ? theme.primary1 : theme.bg4)} 80%
${({ theme, prevConfirmed, disabled }) =>
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;
`
interface ProgressCirclesProps {
steps: boolean[]
disabled?: boolean
}
/**
......@@ -58,21 +59,21 @@ interface ProgressCirclesProps {
*
* @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 (
<Wrapper justify={'center'}>
<Wrapper justify={'center'} {...rest}>
<Grouping>
{steps.map((step, i) => {
return (
<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}
</Circle>
<Connector prevConfirmed={step} />
<Connector prevConfirmed={step} disabled={disabled} />
</CircleRow>
)
})}
<Circle disabled={!steps[steps.length - 1]}>{steps.length + 1}</Circle>
<Circle disabled={disabled || !steps[steps.length - 1]}>{steps.length + 1}</Circle>
</Grouping>
</Wrapper>
)
......
......@@ -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 }) {
const [show, setShow] = useState<boolean>(false)
......@@ -38,3 +63,20 @@ export default function QuestionHelper({ text }: { text: string }) {
</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'
import React, { useCallback, useEffect, useState } from 'react'
import ReactGA from 'react-ga'
import useLast from '../../hooks/useLast'
import { useSelectedListUrl } from '../../state/lists/hooks'
import Modal from '../Modal'
import { CurrencySearch } from './CurrencySearch'
import ListIntroduction from './ListIntroduction'
import { ListSelect } from './ListSelect'
interface CurrencySearchModalProps {
......@@ -56,19 +54,11 @@ export default function CurrencySearchModal({
})
setListView(false)
}, [])
const handleSelectListIntroduction = useCallback(() => {
setListView(true)
}, [])
const selectedListUrl = useSelectedListUrl()
const noListSelected = !selectedListUrl
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 ? (
<ListSelect onDismiss={onDismiss} onBack={handleClickBack} />
) : noListSelected ? (
<ListIntroduction onSelectList={handleSelectListIntroduction} />
) : (
<CurrencySearch
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
})
const AddListButton = styled(ButtonSecondary)`
/* height: 1.8rem; */
max-width: 4rem;
margin-left: 1rem;
border-radius: 12px;
......
import React, { useRef, useContext, useState } from 'react'
import React, { useContext, useRef, useState } from 'react'
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 { ApplicationModal } from '../../state/application/actions'
import { useModalOpen, useToggleSettingsMenu } from '../../state/application/hooks'
import {
useUserSlippageTolerance,
useDarkModeManager,
useExpertModeManager,
useUserDeadline,
useDarkModeManager
useUserTransactionTTL,
useUserSlippageTolerance
} from '../../state/user/hooks'
import TransactionSettings from '../TransactionSettings'
import { RowFixed, RowBetween } from '../Row'
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 { useSettingsMenuOpen, useToggleSettingsMenu } from '../../state/application/hooks'
import { Text } from 'rebass'
import { AutoColumn } from '../Column'
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)`
height: 20px;
......@@ -85,18 +85,15 @@ const StyledMenu = styled.div`
const MenuFlyout = styled.span`
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),
0px 24px 32px rgba(0, 0, 0, 0.01);
border: 1px solid ${({ theme }) => theme.bg3};
border-radius: 0.5rem;
border-radius: 12px;
display: flex;
flex-direction: column;
font-size: 1rem;
position: absolute;
top: 3rem;
top: 4rem;
right: 0rem;
z-index: 100;
......@@ -104,6 +101,11 @@ const MenuFlyout = styled.span`
min-width: 18.125rem;
right: -46px;
`};
${({ theme }) => theme.mediaWidth.upToMedium`
min-width: 18.125rem;
top: -22rem;
`};
`
const Break = styled.div`
......@@ -123,13 +125,13 @@ const ModalContentWrapper = styled.div`
export default function SettingsTab() {
const node = useRef<HTMLDivElement>()
const open = useSettingsMenuOpen()
const open = useModalOpen(ApplicationModal.SETTINGS)
const toggle = useToggleSettingsMenu()
const theme = useContext(ThemeContext)
const [userSlippageTolerance, setUserslippageTolerance] = useUserSlippageTolerance()
const [deadline, setDeadline] = useUserDeadline()
const [ttl, setTtl] = useUserTransactionTTL()
const [expertMode, toggleExpertMode] = useExpertModeManager()
......@@ -182,13 +184,13 @@ export default function SettingsTab() {
</Modal>
<StyledMenuButton onClick={toggle} id="open-settings-dialog-button">
<StyledMenuIcon />
{expertMode && (
{expertMode ? (
<EmojiWrapper>
<span role="img" aria-label="wizard-icon">
🧙
</span>
</EmojiWrapper>
)}
) : null}
</StyledMenuButton>
{open && (
<MenuFlyout>
......@@ -199,8 +201,8 @@ export default function SettingsTab() {
<TransactionSettings
rawSlippage={userSlippageTolerance}
setRawSlippage={setUserslippageTolerance}
deadline={deadline}
setDeadline={setDeadline}
deadline={ttl}
setDeadline={setTtl}
/>
<Text fontWeight={600} fontSize={14}>
Interface Settings
......
......@@ -6,19 +6,34 @@ const ToggleElement = styled.span<{ isActive?: boolean; isOnSwitch?: boolean }>`
border-radius: 14px;
background: ${({ theme, isActive, isOnSwitch }) => (isActive ? (isOnSwitch ? theme.primary1 : theme.text4) : 'none')};
color: ${({ theme, isActive, isOnSwitch }) => (isActive ? (isOnSwitch ? theme.white : theme.text2) : theme.text3)};
font-size: 0.825rem;
font-size: 1rem;
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 }>`
border-radius: 16px;
border: 1px solid ${({ theme, isActive }) => (isActive ? theme.primary5 : theme.text4)};
border-radius: 12px;
border: none;
/* border: 1px solid ${({ theme, isActive }) => (isActive ? theme.primary5 : theme.text4)}; */
background: ${({ theme }) => theme.bg3};
display: flex;
width: fit-content;
cursor: pointer;
outline: none;
padding: 0;
background-color: transparent;
/* background-color: transparent; */
`
export interface ToggleProps {
......
......@@ -4,7 +4,7 @@ import styled, { ThemeContext } from 'styled-components'
import Modal from '../Modal'
import { ExternalLink } from '../../theme'
import { Text } from 'rebass'
import { CloseIcon, Spinner } from '../../theme/components'
import { CloseIcon, CustomLightSpinner } from '../../theme/components'
import { RowBetween } from '../Row'
import { AlertTriangle, ArrowUpCircle } from 'react-feather'
import { ButtonPrimary } from '../Button'
......@@ -31,11 +31,6 @@ const ConfirmedIcon = styled(ColumnCenter)`
padding: 60px 0;
`
const CustomLightSpinner = styled(Spinner)<{ size: string }>`
height: ${({ size }) => size};
width: ${({ size }) => size};
`
function ConfirmationPendingContent({ onDismiss, pendingText }: { onDismiss: () => void; pendingText: string }) {
return (
<Wrapper>
......@@ -90,7 +85,6 @@ function TransactionSubmittedContent({
<Text fontWeight={500} fontSize={20}>
Transaction Submitted
</Text>
{chainId && hash && (
<ExternalLink href={getEtherscanLink(chainId, hash, 'transaction')}>
<Text fontWeight={500} fontSize={14} color={theme.primary1}>
......
......@@ -23,9 +23,9 @@ const FancyButton = styled.button`
align-items: center;
height: 2rem;
border-radius: 36px;
font-size: 12px;
font-size: 1rem;
width: auto;
min-width: 3rem;
min-width: 3.5rem;
border: 1px solid ${({ theme }) => theme.bg3};
outline: none;
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 styled from 'styled-components'
import { isMobile } from 'react-device-detect'
import { UnsupportedChainIdError, useWeb3React } from '@web3-react/core'
import MetamaskIcon from '../../assets/images/metamask.png'
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 { 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 AccountDetails from '../AccountDetails'
import PendingView from './PendingView'
import Option from './Option'
import { SUPPORTED_WALLETS } from '../../constants'
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'
import PendingView from './PendingView'
const CloseIcon = styled.div`
position: absolute;
......@@ -133,7 +134,7 @@ export default function WalletModal({
const [pendingError, setPendingError] = useState<boolean>()
const walletModalOpen = useWalletModalOpen()
const walletModalOpen = useModalOpen(ApplicationModal.WALLET)
const toggleWalletModal = useWalletModalToggle()
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 React, { useContext } from 'react'
import { ThemeContext } from 'styled-components'
import styled, { ThemeContext } from 'styled-components'
import { Field } from '../../state/swap/actions'
import { useUserSlippageTolerance } from '../../state/user/hooks'
import { TYPE } from '../../theme'
import { TYPE, ExternalLink } from '../../theme'
import { computeSlippageAdjustedAmounts, computeTradePriceBreakdown } from '../../utils/prices'
import { AutoColumn } from '../Column'
import QuestionHelper from '../QuestionHelper'
......@@ -12,6 +12,16 @@ import FormattedPriceImpact from './FormattedPriceImpact'
import { SectionBreak } from './styleds'
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 }) {
const theme = useContext(ThemeContext)
const { priceImpactWithoutFee, realizedLPFee } = computeTradePriceBreakdown(trade)
......@@ -94,6 +104,11 @@ export function AdvancedSwapDetails({ trade }: AdvancedSwapDetailsProps) {
</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>
......
......@@ -4,14 +4,27 @@ import { useLocation } from 'react-router'
import { Text } from 'rebass'
import { ThemeContext } from 'styled-components'
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 { YellowCard } from '../Card'
import { AutoColumn } from '../Column'
export default function BetterTradeLink({ version }: { version: Version }) {
function VersionLinkContainer({ children }: { children: React.ReactNode }) {
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 search = useParsedQueryString()
......@@ -26,15 +39,36 @@ export default function BetterTradeLink({ version }: { version: Version }) {
}, [location, search, version])
return (
<YellowCard style={{ marginTop: '12px', padding: '8px 4px' }}>
<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{' '}
<StyledInternalLink to={linkDestination}>
<b>Uniswap {version.toUpperCase()}</b>
</StyledInternalLink>
</Text>
</AutoColumn>
</YellowCard>
<VersionLinkContainer>
There is a better price for this trade on{' '}
<StyledInternalLink to={linkDestination}>
<b>Uniswap {version.toUpperCase()}</b>
</StyledInternalLink>
</VersionLinkContainer>
)
}
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 }
......@@ -813,4 +813,4 @@
"stateMutability": "view",
"type": "function"
}
]
\ No newline at end of file
]
......@@ -419,4 +419,4 @@
"stateMutability": "view",
"type": "function"
}
]
\ No newline at end of file
]
......@@ -10,7 +10,10 @@
},
{
"constant": false,
"inputs": [{ "name": "_spender", "type": "address" }, { "name": "_value", "type": "uint256" }],
"inputs": [
{ "name": "_spender", "type": "address" },
{ "name": "_value", "type": "uint256" }
],
"name": "approve",
"outputs": [{ "name": "", "type": "bool" }],
"payable": false,
......@@ -68,7 +71,10 @@
},
{
"constant": false,
"inputs": [{ "name": "_to", "type": "address" }, { "name": "_value", "type": "uint256" }],
"inputs": [
{ "name": "_to", "type": "address" },
{ "name": "_value", "type": "uint256" }
],
"name": "transfer",
"outputs": [{ "name": "", "type": "bool" }],
"payable": false,
......@@ -77,7 +83,10 @@
},
{
"constant": true,
"inputs": [{ "name": "_owner", "type": "address" }, { "name": "_spender", "type": "address" }],
"inputs": [
{ "name": "_owner", "type": "address" },
{ "name": "_spender", "type": "address" }
],
"name": "allowance",
"outputs": [{ "name": "", "type": "uint256" }],
"payable": false,
......
......@@ -52,4 +52,4 @@
"stateMutability": "payable",
"type": "receive"
}
]
\ No newline at end of file
]
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 }
......@@ -468,4 +468,4 @@
"type": "function",
"gas": 1246
}
]
\ No newline at end of file
]
......@@ -276,4 +276,4 @@
"name": "Withdrawal",
"type": "event"
}
]
\ No newline at end of file
]
......@@ -5,6 +5,8 @@ import { fortmatic, injected, portis, walletconnect, walletlink } from '../conne
export const ROUTER_ADDRESS = '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D'
export const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'
// a list of tokens by chain
type ChainTokenList = {
readonly [chainId in ChainId]: Token[]
......@@ -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 MKR = new Token(ChainId.MAINNET, '0x9f8F72aA9304c8B593d555F12eF6589cC3A579A2', 18, 'MKR', 'Maker')
export const AMPL = new Token(ChainId.MAINNET, '0xD46bA6D942050d489DBd938a2C909A5d5039A161', 9, 'AMPL', 'Ampleforth')
export const WBTC = new Token(ChainId.MAINNET, '0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599', 18, 'WBTC', 'Wrapped BTC')
// 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 = {
[ChainId.MAINNET]: [WETH[ChainId.MAINNET]],
......@@ -147,6 +169,8 @@ export const INITIAL_ALLOWED_SLIPPAGE = 50
// 20 minutes, denominated in seconds
export const DEFAULT_DEADLINE_FROM_NOW = 60 * 20
export const BIG_INT_ZERO = JSBI.BigInt(0)
// one basis point
export const ONE_BIPS = new Percent(JSBI.BigInt(1), JSBI.BigInt(10000))
export const BIPS_BASE = JSBI.BigInt(10000)
......
......@@ -140,4 +140,4 @@
"stateMutability": "view",
"type": "function"
}
]
\ No newline at end of file
]
......@@ -968,4 +968,4 @@
"payable": false,
"type": "function"
}
]
\ No newline at end of file
]
This diff is collapsed.
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 { 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 { abi as IUniswapV2PairABI } from '@uniswap/v2-core/build/IUniswapV2Pair.json'
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_ABI from '../constants/abis/ens-registrar.json'
import { ERC20_BYTES32_ABI } from '../constants/abis/erc20'
import ERC20_ABI from '../constants/abis/erc20.json'
import { MIGRATOR_ABI, MIGRATOR_ADDRESS } from '../constants/abis/migrator'
......@@ -51,6 +60,15 @@ export function useWETHContract(withSignerIfPossible?: boolean): Contract | null
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 {
const { chainId } = useActiveWeb3React()
let address: string | undefined
......@@ -84,6 +102,24 @@ export function useMulticallContract(): Contract | null {
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 {
const { chainId } = useActiveWeb3React()
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'
import { Contract } from '@ethersproject/contracts'
import { JSBI, Percent, Router, SwapParameters, Trade, TradeType } from '@uniswap/sdk'
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 { useTransactionAdder } from '../state/transactions/hooks'
import { calculateGasMargin, getRouterContract, isAddress, shortenAddress } from '../utils'
......@@ -10,6 +10,7 @@ import isZero from '../utils/isZero'
import v1SwapArguments from '../utils/v1SwapArguments'
import { useActiveWeb3React } from './index'
import { useV1ExchangeContract } from './useContract'
import useTransactionDeadline from './useTransactionDeadline'
import useENS from './useENS'
import { Version } from './useToggledVersion'
......@@ -40,25 +41,24 @@ type EstimatedSwapCall = SuccessfulCall | FailedCall
* Returns the swap calls that can be used to make the trade
* @param trade trade to execute
* @param allowedSlippage user allowed slippage
* @param deadline the deadline for the trade
* @param recipientAddressOrName
*/
function useSwapCallArguments(
trade: Trade | undefined, // trade to execute, required
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
): SwapCall[] {
const { account, chainId, library } = useActiveWeb3React()
const { address: recipientAddress } = useENS(recipientAddressOrName)
const recipient = recipientAddressOrName === null ? account : recipientAddress
const deadline = useTransactionDeadline()
const v1Exchange = useV1ExchangeContract(useV1TradeExchangeAddress(trade), true)
return useMemo(() => {
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 =
tradeVersion === Version.v2 ? getRouterContract(chainId, library, account) : v1Exchange
......@@ -75,7 +75,7 @@ function useSwapCallArguments(
feeOnTransfer: false,
allowedSlippage: new Percent(JSBI.BigInt(allowedSlippage), BIPS_BASE),
recipient,
ttl: deadline
deadline: deadline.toNumber()
})
)
......@@ -85,7 +85,7 @@ function useSwapCallArguments(
feeOnTransfer: true,
allowedSlippage: new Percent(JSBI.BigInt(allowedSlippage), BIPS_BASE),
recipient,
ttl: deadline
deadline: deadline.toNumber()
})
)
}
......@@ -95,7 +95,7 @@ function useSwapCallArguments(
v1SwapArguments(trade, {
allowedSlippage: new Percent(JSBI.BigInt(allowedSlippage), BIPS_BASE),
recipient,
ttl: deadline
deadline: deadline.toNumber()
})
)
break
......@@ -109,12 +109,11 @@ function useSwapCallArguments(
export function useSwapCallback(
trade: Trade | undefined, // trade to execute, required
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
): { state: SwapCallbackState; callback: null | (() => Promise<string>); error: string | null } {
const { account, chainId, library } = useActiveWeb3React()
const swapCalls = useSwapCallArguments(trade, allowedSlippage, deadline, recipientAddressOrName)
const swapCalls = useSwapCallArguments(trade, allowedSlippage, recipientAddressOrName)
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'
import ReactDOM from 'react-dom'
import ReactGA from 'react-ga'
import { Provider } from 'react-redux'
import { HashRouter } from 'react-router-dom'
import { NetworkContextName } from './constants'
import './i18n'
import App from './pages/App'
......@@ -61,7 +62,9 @@ ReactDOM.render(
<Updaters />
<ThemeProvider>
<ThemedGlobalStyle />
<App />
<HashRouter>
<App />
</HashRouter>
</ThemeProvider>
</Provider>
</Web3ProviderNetwork>
......
......@@ -8,7 +8,7 @@ import { RouteComponentProps } from 'react-router-dom'
import { Text } from 'rebass'
import { ThemeContext } from 'styled-components'
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 TransactionConfirmationModal, { ConfirmationModalContent } from '../../components/TransactionConfirmationModal'
import CurrencyInputPanel from '../../components/CurrencyInputPanel'
......@@ -22,12 +22,13 @@ import { PairState } from '../../data/Reserves'
import { useActiveWeb3React } from '../../hooks'
import { useCurrency } from '../../hooks/Tokens'
import { ApprovalState, useApproveCallback } from '../../hooks/useApproveCallback'
import useTransactionDeadline from '../../hooks/useTransactionDeadline'
import { useWalletModalToggle } from '../../state/application/hooks'
import { Field } from '../../state/mint/actions'
import { useDerivedMintInfo, useMintActionHandlers, useMintState } from '../../state/mint/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 { calculateGasMargin, calculateSlippageAmount, getRouterContract } from '../../utils'
import { maxAmountSpend } from '../../utils/maxAmountSpend'
......@@ -84,7 +85,7 @@ export default function AddLiquidity({
const [attemptingTxn, setAttemptingTxn] = useState<boolean>(false) // clicked confirm
// txn values
const [deadline] = useUserDeadline() // custom from users settings
const deadline = useTransactionDeadline() // custom from users settings
const [allowedSlippage] = useUserSlippageTolerance() // custom from users
const [txHash, setTxHash] = useState<string>('')
......@@ -126,7 +127,7 @@ export default function AddLiquidity({
const router = getRouterContract(chainId, library, account)
const { [Field.CURRENCY_A]: parsedAmountA, [Field.CURRENCY_B]: parsedAmountB } = parsedAmounts
if (!parsedAmountA || !parsedAmountB || !currencyA || !currencyB) {
if (!parsedAmountA || !parsedAmountB || !currencyA || !currencyB || !deadline) {
return
}
......@@ -135,8 +136,6 @@ export default function AddLiquidity({
[Field.CURRENCY_B]: calculateSlippageAmount(parsedAmountB, noLiquidity ? 0 : allowedSlippage)[0]
}
const deadlineFromNow = Math.ceil(Date.now() / 1000) + deadline
let estimate,
method: (...args: any) => Promise<TransactionResponse>,
args: Array<string | string[] | number>,
......@@ -151,7 +150,7 @@ export default function AddLiquidity({
amountsMin[tokenBIsETH ? Field.CURRENCY_A : Field.CURRENCY_B].toString(), // token min
amountsMin[tokenBIsETH ? Field.CURRENCY_B : Field.CURRENCY_A].toString(), // eth min
account,
deadlineFromNow
deadline.toHexString()
]
value = BigNumber.from((tokenBIsETH ? parsedAmountB : parsedAmountA).raw.toString())
} else {
......@@ -165,7 +164,7 @@ export default function AddLiquidity({
amountsMin[Field.CURRENCY_A].toString(),
amountsMin[Field.CURRENCY_B].toString(),
account,
deadlineFromNow
deadline.toHexString()
]
value = null
}
......@@ -303,10 +302,12 @@ export default function AddLiquidity({
setTxHash('')
}, [onFieldAInput, txHash])
const isCreate = history.location.pathname.includes('/create')
return (
<>
<AppBody>
<AddRemoveTabs adding={true} />
<AddRemoveTabs creating={isCreate} adding={true} />
<Wrapper>
<TransactionConfirmationModal
isOpen={showConfirm}
......@@ -324,23 +325,24 @@ export default function AddLiquidity({
pendingText={pendingText}
/>
<AutoColumn gap="20px">
{noLiquidity && (
<ColumnCenter>
<BlueCard>
<AutoColumn gap="10px">
<TYPE.link fontWeight={600} color={'primaryText1'}>
You are the first liquidity provider.
</TYPE.link>
<TYPE.link fontWeight={400} color={'primaryText1'}>
The ratio of tokens you add will set the price of this pool.
</TYPE.link>
<TYPE.link fontWeight={400} color={'primaryText1'}>
Once you are happy with the rate click supply to review.
</TYPE.link>
</AutoColumn>
</BlueCard>
</ColumnCenter>
)}
{noLiquidity ||
(isCreate && (
<ColumnCenter>
<BlueCard>
<AutoColumn gap="10px">
<TYPE.link fontWeight={600} color={'primaryText1'}>
You are the first liquidity provider.
</TYPE.link>
<TYPE.link fontWeight={400} color={'primaryText1'}>
The ratio of tokens you add will set the price of this pool.
</TYPE.link>
<TYPE.link fontWeight={400} color={'primaryText1'}>
Once you are happy with the rate click supply to review.
</TYPE.link>
</AutoColumn>
</BlueCard>
</ColumnCenter>
))}
<CurrencyInputPanel
value={formattedAmounts[Field.CURRENCY_A]}
onUserInput={onFieldAInput}
......@@ -370,7 +372,7 @@ export default function AddLiquidity({
/>
{currencies[Field.CURRENCY_A] && currencies[Field.CURRENCY_B] && pairState !== PairState.INVALID && (
<>
<GreyCard padding="0px" borderRadius={'20px'}>
<LightCard padding="0px" borderRadius={'20px'}>
<RowBetween padding="1rem">
<TYPE.subHeader fontWeight={500} fontSize={14}>
{noLiquidity ? 'Initial prices' : 'Prices'} and pool share
......@@ -384,7 +386,7 @@ export default function AddLiquidity({
price={price}
/>
</LightCard>
</GreyCard>
</LightCard>
</>
)}
......@@ -444,7 +446,7 @@ export default function AddLiquidity({
</AppBody>
{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} />
</AutoColumn>
) : null}
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
......@@ -131,6 +131,9 @@ export default function PoolFinder() {
<Text textAlign="center" fontWeight={500}>
Pool Found!
</Text>
<StyledInternalLink to={`/pool`}>
<Text textAlign="center">Manage this pool.</Text>
</StyledInternalLink>
</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