Compare commits

...

19 Commits

Author SHA1 Message Date
Antonio Savage
8a845ee0e9 fix(discord invite link): working discord invite link (#929) 2020-07-06 22:59:14 -04:00
Moody Salem
f5229ca838 linter error 2020-07-06 21:31:08 -04:00
Moody Salem
875203f0ef fix(responsiveness): small tweaks for mobile 2020-07-06 21:26:38 -04:00
Moody Salem
91a8202737 fix(send page): support swap + send query parameters on send page (#921)
* support swap + send query parameters on send page

* revert the unfinished portis logic

Co-authored-by: ianlapham <ianlapham@gmail.com>
2020-07-05 22:32:54 -04:00
Moody Salem
0b4819d165 fix(#899): Add PieDAO USD++ 2020-07-05 22:29:49 -04:00
Jonathan Diep
e7d3289754 improvement(token warning card): link to the token page on etherscan instead of the address page (#914) 2020-07-02 08:27:19 -04:00
Moody Salem
0698e0f82a Update README.md 2020-07-01 13:09:37 -04:00
Micah Zoltu
0350cc4701 fix(REP token): renames REP to REPv1 (#915)
https://www.augur.net/blog/v2-launch/

TL;DR: Augur v2 launch is coming up and will introduce a new REP token.  FF has requested all exchanges rename REP to REPv1 to avoid confusion.

Going forward, REP tokens will contain versioning in their name/symbol on chain so this should be a one-time "fix" for Augur v1 REP.
2020-07-01 10:19:28 -04:00
Moody Salem
997052869d fix(lint): linter error 2020-06-30 16:50:19 -04:00
Moody Salem
9ec16c2ba8 actually add the inter-ui dependency 2020-06-30 16:47:16 -04:00
Moody Salem
e2cf8f1642 fix(font): do not load font from remote 2020-06-30 16:43:21 -04:00
Moody Salem
ed6952d1f7 readme cleanup 2020-06-30 14:13:27 -04:00
Moody Salem
3277d70e93 fix all tests 2020-06-30 14:02:09 -04:00
Moody Salem
d1a31fe763 old link 2020-06-30 13:53:18 -04:00
Moody Salem
f88af029ae chore(tests): fix integration tests 2020-06-30 13:51:20 -04:00
Moody Salem
9f3e49b4d8 chore(ipfs migration): point at master branch instead of v2 branch 2020-06-30 13:49:38 -04:00
Moody Salem
d4911d1054 chore(ipfs migration): changes for ipfs url migration
- remove netlify stuff
- update rename to uniswap-interface
- always use hash router
2020-06-30 13:41:51 -04:00
Moody Salem
90df9c4ced improvement(layout): move header version switch, drop footer for mobile (#910)
* version switch tweaks

* Mobile layout and toggle tweaks

* Remove the entire footer

Co-authored-by: Callil Capuozzo <callil.capuozzo@gmail.com>
2020-06-29 16:55:33 -04:00
Callil Capuozzo
14f15d1fd6 fix(i18n): Fix return characters and remove uneeded file (#912) 2020-06-29 14:15:45 -04:00
27 changed files with 150 additions and 252 deletions

View File

@@ -7,7 +7,7 @@ on:
# releases are triggered on changes to this file
push:
branches:
- v2
- master
paths:
- '.github/workflows/release.yaml'
- '.env.production'
@@ -47,7 +47,7 @@ jobs:
run: yarn install --ignore-scripts --frozen-lockfile
- name: Build the IPFS bundle
run: yarn ipfs-build
run: yarn build
- name: Pin to IPFS
id: upload

View File

@@ -2,10 +2,10 @@ name: Tests
on:
push:
branches:
- v2
- master
pull_request:
branches:
- v2
- master
jobs:
integration-tests:
name: Integration tests

1
.yarnrc Normal file
View File

@@ -0,0 +1 @@
ignore-scripts true

View File

@@ -1,11 +1,12 @@
# Uniswap Frontend
# Uniswap Interface
[![Tests](https://github.com/Uniswap/uniswap-frontend/workflows/Tests/badge.svg)](https://github.com/Uniswap/uniswap-frontend/actions?query=workflow%3ATests)
[![Tests](https://github.com/Uniswap/uniswap-interface/workflows/Tests/badge.svg)](https://github.com/Uniswap/uniswap-interface/actions?query=workflow%3ATests)
[![Styled With Prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg)](https://prettier.io/)
An open source interface for Uniswap -- a protocol for decentralized exchange of Ethereum tokens.
- Website: [uniswap.org](https://uniswap.org/)
- Interface: [app.uniswap.org](https://app.uniswap.org)
- Docs: [uniswap.org/docs/](https://uniswap.org/docs/)
- Twitter: [@UniswapProtocol](https://twitter.com/UniswapProtocol)
- Reddit: [/r/Uniswap](https://www.reddit.com/r/Uniswap/)
@@ -13,11 +14,11 @@ An open source interface for Uniswap -- a protocol for decentralized exchange of
- Discord: [Uniswap](https://discord.gg/Y7TF6QA)
- Whitepaper: [Link](https://hackmd.io/C-DvwDSfSxuh-Gd4WKE_ig)
## Accessing the frontend
## Accessing the Uniswap Interface
To access the front end, use an IPFS gateway link from the
[latest release](https://github.com/Uniswap/uniswap-frontend/releases/latest)
or visit [uniswap.exchange](https://uniswap.exchange).
To access the Uniswap Interface, use an IPFS gateway link from the
[latest release](https://github.com/Uniswap/uniswap-interface/releases/latest),
or visit [app.uniswap.org](https://app.uniswap.org).
## Development
@@ -27,31 +28,32 @@ or visit [uniswap.exchange](https://uniswap.exchange).
yarn
```
### Configure Environment (optional)
Copy `.env` to `.env.local` and change the appropriate variables.
### Run
```bash
yarn start
```
To have the frontend default to a different network, make a copy of `.env` named `.env.local`,
change `REACT_APP_NETWORK_ID` to `"{yourNetworkId}"`, and change `REACT_APP_NETWORK_URL` to e.g.
`"https://{yourNetwork}.infura.io/v3/{yourKey}"`.
### Configuring the environment (optional)
Note that the front end only works properly on testnets where both
To have the interface default to a different network when a wallet is not connected:
1. Make a copy of `.env` named `.env.local`
2. Change `REACT_APP_NETWORK_ID` to `"{YOUR_NETWORK_ID}"`
3. Change `REACT_APP_NETWORK_URL` to e.g. `"https://{YOUR_NETWORK_ID}.infura.io/v3/{YOUR_INFURA_KEY}"`
Note that the interface only works on testnets where both
[Uniswap V2](https://uniswap.org/docs/v2/smart-contracts/factory/) and
[multicall](https://github.com/makerdao/multicall) are deployed.
The frontend will not work on other networks.
The interface will not work on other networks.
## Contributions
**Please open all pull requests against the `v2` branch.**
CI checks will run against all PRs.
**Please open all pull requests against the `master` branch.**
CI checks will run against all PRs.
## Accessing Uniswap V1 interface
## Accessing Uniswap Interface V1
The Uniswap V1 interface for mainnet and testnets is accessible via IPFS gateways linked
from the [v1.0.0 release](https://github.com/Uniswap/uniswap-frontend/releases/tag/v1.0.0).
The Uniswap Interface supports swapping against, and migrating or removing liquidity from Uniswap V1. However,
if you would like to use Uniswap V1, the Uniswap V1 interface for mainnet and testnets is accessible via IPFS gateways
linked from the [v1.0.0 release](https://github.com/Uniswap/uniswap-interface/releases/tag/v1.0.0).

View File

@@ -69,13 +69,13 @@ class CustomizedBridge extends _Eip1193Bridge {
// sets up the injected provider to be a mock ethereum provider with the given mnemonic/index
Cypress.Commands.overwrite('visit', (original, url, options) => {
return original(url, {
return original(url.startsWith('/') && url.length > 2 && !url.startsWith('/#') ? `/#${url}` : url, {
...options,
onBeforeLoad(win) {
options && options.onBeforeLoad && options.onBeforeLoad(win)
const provider = new JsonRpcProvider('https://rinkeby.infura.io/v3/acb7e55995d04c49bfb52b7141599467', 4)
const signer = new Wallet(PRIVATE_KEY_TEST_NEVER_USE, provider)
win.ethereum = new CustomizedBridge(signer, provider)
}
},
})
})

View File

@@ -1,27 +0,0 @@
# block some countries
[[redirects]]
from = "/*"
to = "/451.html"
status = 451
force = true
conditions = {Country=["BY","CU","IR","IQ","CI","LR","KP","SD","SY","ZW"]}
headers = {Link="<https://uniswap.exchange>"}
# forward migrate
[[redirects]]
from = "https://migrate.uniswap.exchange/*"
to = "https://uniswap.exchange/migrate/v1"
status = 301
force = true
# forward v2 subdomain to apex
[[redirects]]
from = "https://v2.uniswap.exchange/*"
to = "https://uniswap.exchange/:splat"
status = 301
# support SPA setup
[[redirects]]
from = "/*"
to = "/index.html"
status = 200

View File

@@ -1,7 +1,7 @@
{
"name": "@uniswap/interface",
"description": "Uniswap Interface",
"homepage": "https://uniswap.exchange",
"homepage": ".",
"private": true,
"devDependencies": {
"@ethersproject/address": "^5.0.0-beta.134",
@@ -13,7 +13,7 @@
"@ethersproject/strings": "^5.0.0-beta.136",
"@ethersproject/units": "^5.0.0-beta.132",
"@ethersproject/wallet": "^5.0.0-beta.141",
"@popperjs/core": "^2.4.0",
"@popperjs/core": "^2.4.4",
"@reach/dialog": "^0.10.3",
"@reach/portal": "^0.10.3",
"@reduxjs/toolkit": "^1.3.5",
@@ -51,6 +51,7 @@
"i18next": "^15.0.9",
"i18next-browser-languagedetector": "^3.0.1",
"i18next-xhr-backend": "^2.0.1",
"inter-ui": "^3.13.1",
"jazzicon": "^1.5.0",
"lodash.flatmap": "^4.5.0",
"polished": "^3.3.2",
@@ -81,7 +82,6 @@
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"ipfs-build": "cross-env PUBLIC_URL=\".\" react-scripts build",
"test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject",
"lint": "eslint 'src/**/*.{js,jsx,ts,tsx}'",

View File

@@ -1,75 +0,0 @@
{
"noWallet": "לא נמצא ארנק",
"wrongNetwork": "נבחרה רשת לא נכונה",
"switchNetwork": "{{ correctNetwork }} יש צורך לשנות את הרשת ל",
"installWeb3MobileBrowser": "יש צורך בארנק ווב3.0, תתקין מטאמאסק או ארנק דומה",
"installMetamask": " Metamask יש צורך להתקין תוסף מטאמאסק לדפדפן, חפשו בגוגל ",
"disconnected": "מנותק",
"swap": "המרה",
"send": "שליחה",
"pool": "להפקיד",
"betaWarning": "הפרויקט נמצא בשלב בטא, השתמשו באחריות",
"input": "מוכר",
"output": "אקבל",
"estimated": "הערכה",
"balance": "בארנק שלי {{ balanceInput }}",
"unlock": "שחרור נעילת ארנק",
"pending": "ממתין לאישור",
"selectToken": "בחרו את הטוקן להמרה",
"searchOrPaste": "הכניסו שם או כתובת של טוקן לחיפוש",
"noExchange": "לא מתאפשרת המרה",
"exchangeRate": "שער המרה",
"enterValueCont": "כדי להמשיך {{ missingCurrencyValue }} הזינו ",
"selectTokenCont": "בחרו טוקן כדי להמשיך",
"noLiquidity": "אין נזילות",
"unlockTokenCont": "יש צורך לאשר את הטוקן למסחר",
"transactionDetails": "פרטי הטרנזקציה",
"hideDetails": "הסתר פרטים נוספים",
"youAreSelling": "למכירה",
"orTransFail": "או שהטרנזקציה תיכשל",
"youWillReceive": "תוצר המרה מינימלי",
"youAreBuying": "קונה",
"itWillCost": "זה יעלה",
"insufficientBalance": "אין בחשבון מספיק מטבעות",
"inputNotValid": "קלט לא תקין",
"differentToken": "יש צורך בטוקנים שונים",
"noRecipient": "לא הוכנסה כתובת ארנק יעד",
"invalidRecipient": "לא הוכנסה כתובת תקינה",
"recipientAddress": "כתובת יעד",
"youAreSending": "כמות לשליחה",
"willReceive": "יתקבל לכל הפחות",
"to": "אל",
"addLiquidity": "להוספת נזילות למאגר",
"deposit": "הפקדה",
"currentPoolSize": "גודל מאגר הנזילות הכולל",
"yourPoolShare": "חלקך במאגר הנזילות",
"noZero": "אפס אינו ערך תקין",
"mustBeETH": "ETH חייב להופיע באחד מהצדדים",
"enterCurrencyOrLabelCont": "כדי להמשיך {{ inputCurrency }} או {{ label }} הכנס",
"youAreAdding": "מתווספים למאגר",
"and": "וגם",
"intoPool": "לתוך הנזילות",
"outPool": "מתוך",
"youWillMint": "יונפקו לכם",
"liquidityTokens": "טוקנים של נזילות",
"totalSupplyIs": "חלקך במאגר הנזילות",
"youAreSettingExRate": "שער ההמרה יקבע על ידך",
"totalSupplyIs0": "אין לך טוקנים של נזילות",
"tokenWorth": "שווי כל טוקן נזילות הינו",
"firstLiquidity": "את\ה הראשון\ה שמזרים נזילות למאגר",
"initialExchangeRate": "ושל האית'ר הינן בערך שווה {{ label }} תוודאו שההפקדה של הטוקן",
"removeLiquidity": "הוצאה של נזילות",
"poolTokens": "טוקנים של מאגר הנזילות",
"enterLabelCont": "כדי להמשיך {{ label }} הכנס ",
"youAreRemoving": "יוסרו",
"youWillRemove": "יוסרו",
"createExchange": "ליצירת זוג מסחר",
"invalidTokenAddress": "כתובת טוקן לא נכונה",
"exchangeExists": "{{ label }} כבר קיים זוג המרה עבור",
"invalidSymbol": "תו שגוי",
"invalidDecimals": "ספרות עשרוניות שגויות",
"tokenAddress": "כתובת הטוקן",
"label": "שם",
"decimals": "ספרות עשרויות",
"enterTokenCont": "הכניסו כתובת טוקן כדי להמשיך"
}

View File

@@ -56,7 +56,7 @@
"youAreSettingExRate": "שער ההמרה יקבע על ידך",
"totalSupplyIs0": "אין לך טוקנים של נזילות",
"tokenWorth": "שווי כל טוקן נזילות הינו",
"firstLiquidity": "את\ה הראשון\ה שמזרים נזילות למאגר",
"firstLiquidity": "אתה הראשוןה שמזרים נזילות למאגר",
"initialExchangeRate": "ושל האית'ר הינן בערך שווה {{ label }} תוודאו שההפקדה של הטוקן",
"removeLiquidity": "הוצאה של נזילות",
"poolTokens": "טוקנים של מאגר הנזילות",

View File

@@ -1,35 +0,0 @@
import React from 'react'
import styled from 'styled-components'
import { Send, Sun, Moon } from 'react-feather'
import { useDarkModeManager } from '../../state/user/hooks'
import { ButtonSecondary } from '../Button'
const FooterFrame = styled.div`
display: flex;
align-items: center;
justify-content: flex-end;
position: fixed;
right: 1rem;
bottom: 1rem;
${({ theme }) => theme.mediaWidth.upToExtraSmall`
display: none;
`};
`
export default function Footer() {
const [darkMode, toggleDarkMode] = useDarkModeManager()
return (
<FooterFrame>
<form action="https://forms.gle/DaLuqvJsVhVaAM3J9" target="_blank">
<ButtonSecondary p="8px 12px">
<Send size={16} style={{ marginRight: '8px' }} /> Feedback
</ButtonSecondary>
</form>
<ButtonSecondary onClick={toggleDarkMode} p="8px 12px" ml="0.5rem" width="min-content">
{darkMode ? <Sun size={16} /> : <Moon size={16} />}
</ButtonSecondary>
</FooterFrame>
)
}

View File

@@ -6,16 +6,16 @@ import useParsedQueryString from '../../hooks/useParsedQueryString'
import useToggledVersion, { Version } from '../../hooks/useToggledVersion'
const VersionLabel = styled.span<{ enabled: boolean }>`
padding: ${({ enabled }) => (enabled ? '0.15rem 0.5rem 0.16rem 0.45rem' : '0.15rem 0.5rem 0.16rem 0.35rem')};
border-radius: 14px;
padding: 0.35rem 0.6rem;
border-radius: 12px;
background: ${({ theme, enabled }) => (enabled ? theme.primary1 : 'none')};
color: ${({ theme, enabled }) => (enabled ? theme.white : theme.primary1)};
font-size: 0.825rem;
font-weight: 400;
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.primary3)};
color: ${({ theme, enabled }) => (enabled ? theme.white : theme.text1)};
}
`
@@ -25,21 +25,21 @@ interface VersionToggleProps extends React.ComponentProps<typeof Link> {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const VersionToggle = styled(({ enabled, ...rest }: VersionToggleProps) => <Link {...rest} />)<VersionToggleProps>`
border-radius: 16px;
border-radius: 12px;
opacity: ${({ enabled }) => (enabled ? 1 : 0.5)};
cursor: ${({ enabled }) => (enabled ? 'pointer' : 'default')};
background: ${({ theme }) => theme.primary5};
border: 1px solid ${({ theme }) => theme.primary4};
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 function VersionSwitch() {
export default function VersionSwitch() {
const version = useToggledVersion()
const location = useLocation()
const query = useParsedQueryString()

View File

@@ -22,7 +22,7 @@ import Menu from '../Menu'
import Row, { RowBetween } from '../Row'
import Web3Status from '../Web3Status'
import { VersionSwitch } from './VersionSwitch'
import VersionSwitch from './VersionSwitch'
const HeaderFrame = styled.div`
display: flex;
@@ -45,6 +45,15 @@ const HeaderElement = styled.div`
align-items: center;
`
const HeaderElementWrap = styled.div`
display: flex;
align-items: center;
${({ theme }) => theme.mediaWidth.upToExtraSmall`
margin-top: 0.5rem;
`};
`
const Title = styled.div`
display: flex;
align-items: center;
@@ -70,6 +79,7 @@ const AccountElement = styled.div<{ active: boolean }>`
background-color: ${({ theme, active }) => (!active ? theme.bg1 : theme.bg3)};
border-radius: 12px;
white-space: nowrap;
width: 100%;
:focus {
border: 1px solid blue;
@@ -80,10 +90,7 @@ const TestnetWrapper = styled.div`
white-space: nowrap;
width: fit-content;
margin-left: 10px;
${({ theme }) => theme.mediaWidth.upToSmall`
display: none;
`};
pointer-events: auto;
`
const NetworkCard = styled(YellowCard)`
@@ -120,6 +127,16 @@ const MigrateBanner = styled(AutoColumn)`
`};
`
const HeaderControls = styled.div`
display: flex;
flex-direction: row;
align-items: flex-end;
${({ theme }) => theme.mediaWidth.upToSmall`
flex-direction: column;
`};
`
const NETWORK_LABELS: { [chainId in ChainId]: string | null } = {
[ChainId.MAINNET]: null,
[ChainId.RINKEBY]: 'Rinkeby',
@@ -128,12 +145,6 @@ const NETWORK_LABELS: { [chainId in ChainId]: string | null } = {
[ChainId.KOVAN]: 'Kovan'
}
const BalanceWrapper = styled.div`
${({ theme }) => theme.mediaWidth.upToExtraSmall`
display: none;
`};
`
export default function Header() {
const { account, chainId } = useActiveWeb3React()
@@ -153,7 +164,7 @@ export default function Header() {
</StyledInternalLink>
.
</MigrateBanner>
<RowBetween padding="1rem">
<RowBetween style={{ alignItems: 'flex-start' }} padding="1rem 1rem 0 1rem">
<HeaderElement>
<Title>
<UniIcon id="link" to="/">
@@ -171,25 +182,27 @@ export default function Header() {
</TitleText>
)}
</Title>
<TestnetWrapper style={{ pointerEvents: 'auto' }}>{!isMobile && <VersionSwitch />}</TestnetWrapper>
</HeaderElement>
<HeaderElement>
<TestnetWrapper>
{!isMobile && NETWORK_LABELS[chainId] && <NetworkCard>{NETWORK_LABELS[chainId]}</NetworkCard>}
</TestnetWrapper>
<AccountElement active={!!account} style={{ pointerEvents: 'auto' }}>
<BalanceWrapper>
<HeaderControls>
<HeaderElement>
<TestnetWrapper>
{!isMobile && NETWORK_LABELS[chainId] && <NetworkCard>{NETWORK_LABELS[chainId]}</NetworkCard>}
</TestnetWrapper>
<AccountElement active={!!account} style={{ pointerEvents: 'auto' }}>
{account && userEthBalance ? (
<Text style={{ flexShrink: 0 }} pl="0.75rem" pr="0.5rem" fontWeight={500}>
{userEthBalance?.toSignificant(4)} ETH
</Text>
) : null}
</BalanceWrapper>
<Web3Status />
</AccountElement>
<Settings />
<Menu />
</HeaderElement>
<Web3Status />
</AccountElement>
</HeaderElement>
<HeaderElementWrap>
<VersionSwitch />
<Settings />
<Menu />
</HeaderElementWrap>
</HeaderControls>
</RowBetween>
</HeaderFrame>
)

View File

@@ -77,7 +77,7 @@ const MenuItem = styled(ExternalLink)`
}
`
const CODE_LINK = 'https://github.com/Uniswap/uniswap-frontend'
const CODE_LINK = 'https://github.com/Uniswap/uniswap-interface'
export default function Menu() {
const node = useRef<HTMLDivElement>()
@@ -121,7 +121,7 @@ export default function Menu() {
<Code size={14} />
Code
</MenuItem>
<MenuItem id="link" href="https://discord.gg/vXCdddD">
<MenuItem id="link" href="https://discord.gg/EwFs3Pp">
<MessageCircle size={14} />
Discord
</MenuItem>

View File

@@ -7,6 +7,7 @@ import { ThemeContext } from 'styled-components'
import Card from '../../components/Card'
import { useActiveWeb3React } from '../../hooks'
import { useAllTokens, useToken } from '../../hooks/Tokens'
import useInterval from '../../hooks/useInterval'
import { useAllTokenBalancesTreatingWETHasETH, useTokenBalanceTreatingWETHasETH } from '../../state/wallet/hooks'
import { CloseIcon, LinkStyledButton } from '../../theme/components'
import { isAddress } from '../../utils'
@@ -110,10 +111,17 @@ export default function TokenSearchModal({
const openTooltip = useCallback(() => {
setTooltipOpen(true)
inputRef.current?.focus()
}, [setTooltipOpen])
const closeTooltip = useCallback(() => setTooltipOpen(false), [setTooltipOpen])
useInterval(
() => {
setTooltipOpen(false)
},
tooltipOpen ? 4000 : null,
false
)
return (
<Modal
isOpen={isOpen}
@@ -146,6 +154,7 @@ export default function TokenSearchModal({
value={searchQuery}
ref={inputRef}
onChange={handleInput}
onFocus={closeTooltip}
onBlur={closeTooltip}
/>
</Tooltip>

View File

@@ -49,8 +49,8 @@ const Option = styled(FancyButton)<{ active: boolean }>`
const Input = styled.input`
background: ${({ theme }) => theme.bg1};
flex-grow: 1;
font-size: 12px;
min-width: 20px;
font-size: 16px;
min-width: 60px;
outline: none;
&::-webkit-outer-spin-button,
&::-webkit-inner-spin-button {

View File

@@ -109,7 +109,7 @@ export default function TokenWarningCard({ token, ...rest }: TokenWarningCardPro
? `${token.name} (${token.symbol})`
: token.name || token.symbol}
</div>
<ExternalLink style={{ fontWeight: 400 }} href={getEtherscanLink(chainId, token.address, 'address')}>
<ExternalLink style={{ fontWeight: 400 }} href={getEtherscanLink(chainId, token.address, 'token')}>
(View on Etherscan)
</ExternalLink>
</Row>

View File

@@ -91,7 +91,7 @@ export default [
new Token(ChainId.MAINNET, '0x459086F2376525BdCebA5bDDA135e4E9d3FeF5bf', 8, 'renBCH', 'renBCH'),
new Token(ChainId.MAINNET, '0xEB4C2781e4ebA804CE9a9803C67d0893436bB27D', 8, 'renBTC', 'renBTC'),
new Token(ChainId.MAINNET, '0x1C5db575E2Ff833E46a2E9864C22F4B22E0B37C2', 8, 'renZEC', 'renZEC'),
new Token(ChainId.MAINNET, '0x1985365e9f78359a9B6AD760e32412f4a445E862', 18, 'REP', 'Reputation'),
new Token(ChainId.MAINNET, '0x1985365e9f78359a9B6AD760e32412f4a445E862', 18, 'REPv1', 'Augur v1 Reputation'),
new Token(ChainId.MAINNET, '0x9469D013805bFfB7D3DEBe5E7839237e535ec483', 18, 'RING', 'Darwinia Network Native Token'),
new Token(ChainId.MAINNET, '0x607F4C5BB672230e8672085532f7e901544a7375', 9, 'RLC', 'iEx.ec Network Token'),
new Token(ChainId.MAINNET, '0xB4EFd85c19999D84251304bDA99E90B92300Bd93', 18, 'RPL', 'Rocket Pool'),
@@ -123,6 +123,7 @@ export default [
new Token(ChainId.MAINNET, '0xA4Bdb11dc0a2bEC88d24A3aa1E6Bb17201112eBe', 6, 'USDS', 'StableUSD'),
USDT,
new Token(ChainId.MAINNET, '0xeb269732ab75A6fD61Ea60b06fE994cD32a83549', 18, 'USDx', 'dForce'),
new Token(ChainId.MAINNET, '0x9A48BD0EC040ea4f1D3147C025cd4076A2e71e3e', 18, 'USD++', 'PieDAO USD++'),
new Token(ChainId.MAINNET, '0x8f3470A7388c05eE4e7AF3d01D8C722b0FF52374', 18, 'VERI', 'Veritaseum'),
new Token(ChainId.MAINNET, '0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599', 8, 'WBTC', 'Wrapped BTC'),
new Token(ChainId.MAINNET, '0x09fE5f0236F0Ea5D930197DCE254d77B04128075', 18, 'WCK', 'Wrapped CryptoKitties'),

View File

@@ -1,6 +1,6 @@
import { useEffect, useRef } from 'react'
export default function useInterval(callback: () => void, delay: null | number) {
export default function useInterval(callback: () => void, delay: null | number, leading = true) {
const savedCallback = useRef<() => void>()
// Remember the latest callback.
@@ -16,10 +16,10 @@ export default function useInterval(callback: () => void, delay: null | number)
}
if (delay !== null) {
tick()
if (leading) tick()
const id = setInterval(tick, delay)
return () => clearInterval(id)
}
return
}, [delay])
}, [delay, leading])
}

View File

@@ -3,15 +3,13 @@ import { initReactI18next } from 'react-i18next'
import XHR from 'i18next-xhr-backend'
import LanguageDetector from 'i18next-browser-languagedetector'
const LOAD_PATH: string = process.env.PUBLIC_URL === '.' ? `./locales/{{lng}}.json` : '/locales/{{lng}}.json'
i18next
.use(XHR)
.use(LanguageDetector)
.use(initReactI18next)
.init({
backend: {
loadPath: LOAD_PATH
loadPath: `./locales/{{lng}}.json`
},
react: {
useSuspense: true

View File

@@ -6,6 +6,7 @@ import ReactDOM from 'react-dom'
import ReactGA from 'react-ga'
import { Provider } from 'react-redux'
import { NetworkContextName } from './constants'
import 'inter-ui'
import './i18n'
import App from './pages/App'
import store from './state'

View File

@@ -1,8 +1,7 @@
import React, { Suspense } from 'react'
import { BrowserRouter, HashRouter, Route, Switch } from 'react-router-dom'
import { HashRouter, Route, Switch } from 'react-router-dom'
import styled from 'styled-components'
import GoogleAnalyticsReporter from '../components/analytics/GoogleAnalyticsReporter'
import Footer from '../components/Footer'
import Header from '../components/Header'
import Popups from '../components/Popups'
import Web3ReactManager from '../components/Web3ReactManager'
@@ -54,17 +53,10 @@ const Marginer = styled.div`
margin-top: 5rem;
`
let Router: React.ComponentType
if (process.env.PUBLIC_URL === '.') {
Router = HashRouter
} else {
Router = BrowserRouter
}
export default function App() {
return (
<Suspense fallback={null}>
<Router>
<HashRouter>
<Route component={GoogleAnalyticsReporter} />
<Route component={DarkModeQueryParamReader} />
<AppWrapper>
@@ -90,10 +82,9 @@ export default function App() {
</Switch>
</Web3ReactManager>
<Marginer />
<Footer />
</BodyWrapper>
</AppWrapper>
</Router>
</HashRouter>
</Suspense>
)
}

View File

@@ -43,7 +43,8 @@ import { useUserSlippageTolerance, useUserDeadline, useExpertModeManager } from
import { ClickableText } from '../Pool/styleds'
export default function Send() {
useDefaultsFromURLSearch()
// override auto ETH populate to allow for single inputs or swap and send
useDefaultsFromURLSearch(true)
// text translation
// const { t } = useTranslation()
@@ -64,7 +65,19 @@ export default function Send() {
const [recipientError, setRecipientError] = useState<string | null>('Enter a Recipient')
// trade details, check query params for initial state
const { independentField, typedValue } = useSwapState()
const {
independentField,
typedValue,
[Field.OUTPUT]: { address: output }
} = useSwapState()
// if output is valid set to sending view (will reset to undefined on remove swap)
useEffect(() => {
if (output) {
setSendingWithSwap(true)
}
}, [output])
const {
parsedAmount,
bestTrade: bestTradeV2,
@@ -134,13 +147,6 @@ export default function Send() {
const { onSwitchTokens, onTokenSelection, onUserInput } = useSwapActionHandlers()
// reset field if sending with with swap is cancled
useEffect(() => {
if (!sendingWithSwap) {
onTokenSelection(Field.OUTPUT, null)
}
}, [onTokenSelection, sendingWithSwap])
const maxAmountInput: TokenAmount =
!!tokenBalances[Field.INPUT] &&
!!tokens[Field.INPUT] &&
@@ -419,7 +425,10 @@ export default function Send() {
<ArrowDown size="16" color={theme.text2} onClick={onSwitchTokens} />
</ArrowWrapper>
<ButtonSecondary
onClick={() => setSendingWithSwap(false)}
onClick={() => {
setSendingWithSwap(false)
onTokenSelection(Field.OUTPUT, null)
}}
style={{ marginRight: '0px', width: 'auto', fontSize: '14px' }}
padding={'4px 6px'}
>

View File

@@ -172,15 +172,14 @@ export function useDerivedSwapInfo(): {
}
}
function parseCurrencyFromURLParameter(urlParam: any, chainId: number): string {
function parseCurrencyFromURLParameter(urlParam: any, chainId: number, overrideWETH: boolean): string {
if (typeof urlParam === 'string') {
const valid = isAddress(urlParam)
if (valid) return valid
if (urlParam.toLowerCase() === 'eth') return WETH[chainId as ChainId]?.address ?? ''
if (valid === false) return WETH[chainId as ChainId]?.address ?? ''
}
return WETH[chainId as ChainId]?.address
return overrideWETH ? '' : WETH[chainId as ChainId]?.address ?? ''
}
function parseTokenAmountURLParameter(urlParam: any): string {
@@ -191,9 +190,9 @@ function parseIndependentFieldURLParameter(urlParam: any): Field {
return typeof urlParam === 'string' && urlParam.toLowerCase() === 'output' ? Field.OUTPUT : Field.INPUT
}
export function queryParametersToSwapState(parsedQs: ParsedQs, chainId: ChainId): SwapState {
let inputCurrency = parseCurrencyFromURLParameter(parsedQs.inputCurrency, chainId)
let outputCurrency = parseCurrencyFromURLParameter(parsedQs.outputCurrency, chainId)
export function queryParametersToSwapState(parsedQs: ParsedQs, chainId: ChainId, overrideETH: boolean): SwapState {
let inputCurrency = parseCurrencyFromURLParameter(parsedQs.inputCurrency, chainId, overrideETH)
let outputCurrency = parseCurrencyFromURLParameter(parsedQs.outputCurrency, chainId, overrideETH)
if (inputCurrency === outputCurrency) {
if (typeof parsedQs.outputCurrency === 'string') {
inputCurrency = ''
@@ -215,14 +214,16 @@ export function queryParametersToSwapState(parsedQs: ParsedQs, chainId: ChainId)
}
// updates the swap state to use the defaults for a given network
export function useDefaultsFromURLSearch() {
// set overrideETH to true if dont want to autopopulate ETH
export function useDefaultsFromURLSearch(overrideWETH = false) {
const { chainId } = useActiveWeb3React()
const dispatch = useDispatch<AppDispatch>()
const parsedQs = useParsedQueryString()
useEffect(() => {
if (!chainId) return
const parsed = queryParametersToSwapState(parsedQs, chainId)
const parsed = queryParametersToSwapState(parsedQs, chainId, overrideWETH)
dispatch(
replaceSwapState({
typedValue: parsed.typedValue,

View File

@@ -168,8 +168,6 @@ export const TYPE = {
}
export const FixedGlobalStyle = createGlobalStyle`
@import url('https://rsms.me/inter/inter.css');
html, input, textarea, button {
font-family: 'Inter', sans-serif;
letter-spacing: -0.018em;

View File

@@ -16,6 +16,9 @@ describe('utils', () => {
it('correct for tx', () => {
expect(getEtherscanLink(1, 'abc', 'transaction')).toEqual('https://etherscan.io/tx/abc')
})
it('correct for token', () => {
expect(getEtherscanLink(1, 'abc', 'token')).toEqual('https://etherscan.io/token/abc')
})
it('correct for address', () => {
expect(getEtherscanLink(1, 'abc', 'address')).toEqual('https://etherscan.io/address/abc')
})

View File

@@ -25,13 +25,16 @@ const ETHERSCAN_PREFIXES: { [chainId in ChainId]: string } = {
42: 'kovan.'
}
export function getEtherscanLink(chainId: ChainId, data: string, type: 'transaction' | 'address'): string {
export function getEtherscanLink(chainId: ChainId, data: string, type: 'transaction' | 'token' | 'address'): string {
const prefix = `https://${ETHERSCAN_PREFIXES[chainId] || ETHERSCAN_PREFIXES[1]}etherscan.io`
switch (type) {
case 'transaction': {
return `${prefix}/tx/${data}`
}
case 'token': {
return `${prefix}/token/${data}`
}
case 'address':
default: {
return `${prefix}/address/${data}`

View File

@@ -2046,10 +2046,10 @@
resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz#2b5a3ab3f918cca48a8c754c08168e3f03eba61b"
integrity sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==
"@popperjs/core@^2.4.0":
version "2.4.0"
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.4.0.tgz#0e1bdf8d021e7ea58affade33d9d607e11365915"
integrity sha512-NMrDy6EWh9TPdSRiHmHH2ye1v5U0gBD7pRYwSwJvomx7Bm4GG04vu63dYiVzebLOx2obPpJugew06xVP0Nk7hA==
"@popperjs/core@^2.4.4":
version "2.4.4"
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.4.4.tgz#11d5db19bd178936ec89cd84519c4de439574398"
integrity sha512-1oO6+dN5kdIA3sKPZhRGJTfGVP4SWV6KqlMOwry4J3HfyD68sl/3KmG7DeYUzvN+RbhXDnv/D8vNNB8168tAMg==
"@portis/eth-json-rpc-middleware@^4.1.2":
version "4.1.2"
@@ -8419,6 +8419,11 @@ inquirer@^7.0.0:
strip-ansi "^6.0.0"
through "^2.3.6"
inter-ui@^3.13.1:
version "3.13.1"
resolved "https://registry.yarnpkg.com/inter-ui/-/inter-ui-3.13.1.tgz#3b3841c1ab425035d0146b38c7ee7a640d3da3d8"
integrity sha512-A+gHBm9WXZZmIYHdQci9ZoIrsPkzwYvWqG2+DyrwOuxjZVnRyz3b73ridPUWI/JvZ1nGf2j0VdJ+vxh0/bKBwg==
internal-ip@^4.3.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/internal-ip/-/internal-ip-4.3.0.tgz#845452baad9d2ca3b69c635a137acb9a0dad0907"