Add Custom url parameters (#423)
* query params for input and output currencies * add slippage option * add slippage cusytom param * updated for sender address * add field and amount support * update params for pool page * finish basic url support * update app format * update error checking to top level * update for all pages * fix build * param updates * fix slippage to basis points, update theme text, refactor to minimize lookups * fix code styles * update theme logic, remove extra setting, update rounding * remove eslint comment errors * remove logs, ignore lock * remove lock
This commit is contained in:
parent
61d6556a0d
commit
4f566ab0c2
2
.gitignore
vendored
2
.gitignore
vendored
@ -27,3 +27,5 @@ notes.txt
|
|||||||
.idea/
|
.idea/
|
||||||
|
|
||||||
.vscode/
|
.vscode/
|
||||||
|
|
||||||
|
package-lock.json
|
@ -10,6 +10,7 @@
|
|||||||
"@uniswap/sdk": "^1.0.0-beta.4",
|
"@uniswap/sdk": "^1.0.0-beta.4",
|
||||||
"copy-to-clipboard": "^3.2.0",
|
"copy-to-clipboard": "^3.2.0",
|
||||||
"escape-string-regexp": "^2.0.0",
|
"escape-string-regexp": "^2.0.0",
|
||||||
|
"history": "^4.9.0",
|
||||||
"ethers": "^4.0.36",
|
"ethers": "^4.0.36",
|
||||||
"i18next": "^15.0.9",
|
"i18next": "^15.0.9",
|
||||||
"i18next-browser-languagedetector": "^3.0.1",
|
"i18next-browser-languagedetector": "^3.0.1",
|
||||||
|
@ -75,7 +75,8 @@ export default function AddressInputPanel({ title, initialInput = '', onChange =
|
|||||||
|
|
||||||
const { library } = useWeb3Context()
|
const { library } = useWeb3Context()
|
||||||
|
|
||||||
const [input, setInput] = useState(initialInput)
|
const [input, setInput] = useState(initialInput.address ? initialInput.address : '')
|
||||||
|
|
||||||
const debouncedInput = useDebounce(input, 150)
|
const debouncedInput = useDebounce(input, 150)
|
||||||
|
|
||||||
const [data, setData] = useState({ address: undefined, name: undefined })
|
const [data, setData] = useState({ address: undefined, name: undefined })
|
||||||
|
@ -336,9 +336,9 @@ export default function CurrencyInputPanel({
|
|||||||
<Input
|
<Input
|
||||||
type="number"
|
type="number"
|
||||||
min="0"
|
min="0"
|
||||||
|
step="0.000000000000000001"
|
||||||
error={!!errorMessage}
|
error={!!errorMessage}
|
||||||
placeholder="0.0"
|
placeholder="0.0"
|
||||||
step="0.000000000000000001"
|
|
||||||
onChange={e => onValueChange(e.target.value)}
|
onChange={e => onValueChange(e.target.value)}
|
||||||
onKeyPress={e => {
|
onKeyPress={e => {
|
||||||
const charCode = e.which ? e.which : e.keyCode
|
const charCode = e.which ? e.which : e.keyCode
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import React, { useState, useReducer, useEffect } from 'react'
|
import React, { useState, useReducer, useEffect } from 'react'
|
||||||
import ReactGA from 'react-ga'
|
import ReactGA from 'react-ga'
|
||||||
|
import { createBrowserHistory } from 'history'
|
||||||
|
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useWeb3Context } from 'web3-react'
|
import { useWeb3Context } from 'web3-react'
|
||||||
@ -119,13 +120,17 @@ function calculateEtherTokenInputFromOutput(outputAmount, inputReserve, outputRe
|
|||||||
return numerator.div(denominator).add(ethers.constants.One)
|
return numerator.div(denominator).add(ethers.constants.One)
|
||||||
}
|
}
|
||||||
|
|
||||||
function getInitialSwapState(outputCurrency) {
|
function getInitialSwapState(state) {
|
||||||
return {
|
return {
|
||||||
independentValue: '', // this is a user input
|
independentValue: state.exactFieldURL && state.exactAmountURL ? state.exactAmountURL : '', // this is a user input
|
||||||
dependentValue: '', // this is a calculated number
|
dependentValue: '', // this is a calculated number
|
||||||
independentField: INPUT,
|
independentField: state.exactFieldURL === 'output' ? OUTPUT : INPUT,
|
||||||
inputCurrency: 'ETH',
|
inputCurrency: state.inputCurrencyURL ? state.inputCurrencyURL : 'ETH',
|
||||||
outputCurrency: outputCurrency ? outputCurrency : ''
|
outputCurrency: state.outputCurrencyURL
|
||||||
|
? state.outputCurrencyURL
|
||||||
|
: state.initialCurrency
|
||||||
|
? state.initialCurrency
|
||||||
|
: ''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -235,14 +240,32 @@ function getMarketRate(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function ExchangePage({ initialCurrency, sending }) {
|
export default function ExchangePage({ initialCurrency, sending = false, params }) {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { account } = useWeb3Context()
|
const { account } = useWeb3Context()
|
||||||
|
|
||||||
const addTransaction = useTransactionAdder()
|
const addTransaction = useTransactionAdder()
|
||||||
|
|
||||||
const [rawSlippage, setRawSlippage] = useState(ALLOWED_SLIPPAGE_DEFAULT)
|
// check if URL specifies valid slippage, if so use as default
|
||||||
const [rawTokenSlippage, setRawTokenSlippage] = useState(TOKEN_ALLOWED_SLIPPAGE_DEFAULT)
|
const initialSlippage = (token = false) => {
|
||||||
|
let slippage = Number.parseInt(params.slippage)
|
||||||
|
if (!isNaN(slippage) && (slippage === 0 || slippage >= 1)) {
|
||||||
|
return slippage // round to match custom input availability
|
||||||
|
}
|
||||||
|
// check for token <-> token slippage option
|
||||||
|
return token ? TOKEN_ALLOWED_SLIPPAGE_DEFAULT : ALLOWED_SLIPPAGE_DEFAULT
|
||||||
|
}
|
||||||
|
|
||||||
|
// check URL params for recipient, only on send page
|
||||||
|
const initialRecipient = () => {
|
||||||
|
if (sending && params.recipient) {
|
||||||
|
return params.recipient
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const [rawSlippage, setRawSlippage] = useState(() => initialSlippage())
|
||||||
|
const [rawTokenSlippage, setRawTokenSlippage] = useState(() => initialSlippage(true))
|
||||||
|
|
||||||
const allowedSlippageBig = ethers.utils.bigNumberify(rawSlippage)
|
const allowedSlippageBig = ethers.utils.bigNumberify(rawSlippage)
|
||||||
const tokenAllowedSlippageBig = ethers.utils.bigNumberify(rawTokenSlippage)
|
const tokenAllowedSlippageBig = ethers.utils.bigNumberify(rawTokenSlippage)
|
||||||
@ -253,11 +276,21 @@ export default function ExchangePage({ initialCurrency, sending }) {
|
|||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
// core swap state
|
// core swap state
|
||||||
const [swapState, dispatchSwapState] = useReducer(swapStateReducer, initialCurrency, getInitialSwapState)
|
const [swapState, dispatchSwapState] = useReducer(
|
||||||
|
swapStateReducer,
|
||||||
|
{
|
||||||
|
initialCurrency: initialCurrency,
|
||||||
|
inputCurrencyURL: params.inputCurrency,
|
||||||
|
outputCurrencyURL: params.outputCurrency,
|
||||||
|
exactFieldURL: params.exactField,
|
||||||
|
exactAmountURL: params.exactAmount
|
||||||
|
},
|
||||||
|
getInitialSwapState
|
||||||
|
)
|
||||||
|
|
||||||
const { independentValue, dependentValue, independentField, inputCurrency, outputCurrency } = swapState
|
const { independentValue, dependentValue, independentField, inputCurrency, outputCurrency } = swapState
|
||||||
|
|
||||||
const [recipient, setRecipient] = useState({ address: '', name: '' })
|
const [recipient, setRecipient] = useState({ address: initialRecipient(), name: '' })
|
||||||
const [recipientError, setRecipientError] = useState()
|
const [recipientError, setRecipientError] = useState()
|
||||||
|
|
||||||
// get swap type from the currency types
|
// get swap type from the currency types
|
||||||
@ -468,6 +501,11 @@ export default function ExchangePage({ initialCurrency, sending }) {
|
|||||||
t
|
t
|
||||||
])
|
])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const history = createBrowserHistory()
|
||||||
|
history.push(window.location.pathname + '')
|
||||||
|
}, [])
|
||||||
|
|
||||||
const [inverted, setInverted] = useState(false)
|
const [inverted, setInverted] = useState(false)
|
||||||
const exchangeRate = getExchangeRate(inputValueParsed, inputDecimals, outputValueParsed, outputDecimals)
|
const exchangeRate = getExchangeRate(inputValueParsed, inputDecimals, outputValueParsed, outputDecimals)
|
||||||
const exchangeRateInverted = getExchangeRate(inputValueParsed, inputDecimals, outputValueParsed, outputDecimals, true)
|
const exchangeRateInverted = getExchangeRate(inputValueParsed, inputDecimals, outputValueParsed, outputDecimals, true)
|
||||||
@ -655,7 +693,7 @@ export default function ExchangePage({ initialCurrency, sending }) {
|
|||||||
<DownArrow active={isValid} alt="arrow" />
|
<DownArrow active={isValid} alt="arrow" />
|
||||||
</DownArrowBackground>
|
</DownArrowBackground>
|
||||||
</OversizedPanel>
|
</OversizedPanel>
|
||||||
<AddressInputPanel onChange={setRecipient} onError={setRecipientError} />
|
<AddressInputPanel onChange={setRecipient} onError={setRecipientError} initialInput={recipient} />
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
''
|
''
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useState, useEffect, useRef } from 'react'
|
import React, { useState, useEffect, useRef, useCallback } from 'react'
|
||||||
import ReactGA from 'react-ga'
|
import ReactGA from 'react-ga'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import styled, { css, keyframes } from 'styled-components'
|
import styled, { css, keyframes } from 'styled-components'
|
||||||
@ -502,40 +502,94 @@ export default function TransactionDetails(props) {
|
|||||||
checkBounds(debouncedInput)
|
checkBounds(debouncedInput)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// destructure props for to limit effect callbacks
|
||||||
|
const setRawSlippage = props.setRawSlippage
|
||||||
|
const setRawTokenSlippage = props.setRawTokenSlippage
|
||||||
|
const setcustomSlippageError = props.setcustomSlippageError
|
||||||
|
|
||||||
|
const updateSlippage = useCallback(
|
||||||
|
newSlippage => {
|
||||||
|
// round to 2 decimals to prevent ethers error
|
||||||
|
let numParsed = parseInt(newSlippage * 100)
|
||||||
|
|
||||||
|
// set both slippage values in parents
|
||||||
|
setRawSlippage(numParsed)
|
||||||
|
setRawTokenSlippage(numParsed)
|
||||||
|
},
|
||||||
|
[setRawSlippage, setRawTokenSlippage]
|
||||||
|
)
|
||||||
|
|
||||||
// used for slippage presets
|
// used for slippage presets
|
||||||
const setFromFixed = (index, slippage) => {
|
const setFromFixed = useCallback(
|
||||||
|
(index, slippage) => {
|
||||||
// update slippage in parent, reset errors and input state
|
// update slippage in parent, reset errors and input state
|
||||||
updateSlippage(slippage)
|
updateSlippage(slippage)
|
||||||
setWarningType(WARNING_TYPE.none)
|
setWarningType(WARNING_TYPE.none)
|
||||||
setActiveIndex(index)
|
setActiveIndex(index)
|
||||||
props.setcustomSlippageError('valid`')
|
setcustomSlippageError('valid`')
|
||||||
}
|
},
|
||||||
|
[setcustomSlippageError, updateSlippage]
|
||||||
|
)
|
||||||
|
|
||||||
const checkBounds = slippageValue => {
|
/**
|
||||||
|
* @todo
|
||||||
|
* Breaks without useState here, able to
|
||||||
|
* break input parsing if typing is faster than
|
||||||
|
* debounce time
|
||||||
|
*/
|
||||||
|
|
||||||
|
const [initialSlippage] = useState(props.rawSlippage)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
switch (Number.parseInt(initialSlippage)) {
|
||||||
|
case 10:
|
||||||
|
setFromFixed(1, 0.1)
|
||||||
|
break
|
||||||
|
case 50:
|
||||||
|
setFromFixed(2, 0.5)
|
||||||
|
break
|
||||||
|
case 100:
|
||||||
|
setFromFixed(3, 1)
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
// restrict to 2 decimal places
|
||||||
|
let acceptableValues = [/^$/, /^\d{1,2}$/, /^\d{0,2}\.\d{0,2}$/]
|
||||||
|
// if its within accepted decimal limit, update the input state
|
||||||
|
if (acceptableValues.some(val => val.test(initialSlippage / 100))) {
|
||||||
|
setUserInput(initialSlippage / 100)
|
||||||
|
setActiveIndex(4)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [initialSlippage, setFromFixed])
|
||||||
|
|
||||||
|
const checkBounds = useCallback(
|
||||||
|
slippageValue => {
|
||||||
setWarningType(WARNING_TYPE.none)
|
setWarningType(WARNING_TYPE.none)
|
||||||
props.setcustomSlippageError('valid')
|
setcustomSlippageError('valid')
|
||||||
|
|
||||||
if (slippageValue === '' || slippageValue === '.') {
|
if (slippageValue === '' || slippageValue === '.') {
|
||||||
props.setcustomSlippageError('invalid')
|
setcustomSlippageError('invalid')
|
||||||
return setWarningType(WARNING_TYPE.emptyInput)
|
return setWarningType(WARNING_TYPE.emptyInput)
|
||||||
}
|
}
|
||||||
|
|
||||||
// check bounds and set errors
|
// check bounds and set errors
|
||||||
if (Number(slippageValue) < 0 || Number(slippageValue) > 50) {
|
if (Number(slippageValue) < 0 || Number(slippageValue) > 50) {
|
||||||
props.setcustomSlippageError('invalid')
|
setcustomSlippageError('invalid')
|
||||||
return setWarningType(WARNING_TYPE.invalidEntryBound)
|
return setWarningType(WARNING_TYPE.invalidEntryBound)
|
||||||
}
|
}
|
||||||
if (Number(slippageValue) >= 0 && Number(slippageValue) < 0.1) {
|
if (Number(slippageValue) >= 0 && Number(slippageValue) < 0.1) {
|
||||||
props.setcustomSlippageError('valid')
|
setcustomSlippageError('valid')
|
||||||
setWarningType(WARNING_TYPE.riskyEntryLow)
|
setWarningType(WARNING_TYPE.riskyEntryLow)
|
||||||
}
|
}
|
||||||
if (Number(slippageValue) > 5) {
|
if (Number(slippageValue) > 5) {
|
||||||
props.setcustomSlippageError('warning')
|
setcustomSlippageError('warning')
|
||||||
setWarningType(WARNING_TYPE.riskyEntryHigh)
|
setWarningType(WARNING_TYPE.riskyEntryHigh)
|
||||||
}
|
}
|
||||||
//update the actual slippage value in parent
|
//update the actual slippage value in parent
|
||||||
updateSlippage(Number(slippageValue))
|
updateSlippage(Number(slippageValue))
|
||||||
}
|
},
|
||||||
|
[setcustomSlippageError, updateSlippage]
|
||||||
|
)
|
||||||
|
|
||||||
// check that the theyve entered number and correct decimal
|
// check that the theyve entered number and correct decimal
|
||||||
const parseInput = e => {
|
const parseInput = e => {
|
||||||
@ -549,15 +603,6 @@ export default function TransactionDetails(props) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateSlippage = newSlippage => {
|
|
||||||
// round to 2 decimals to prevent ethers error
|
|
||||||
let numParsed = parseInt(newSlippage * 100)
|
|
||||||
|
|
||||||
// set both slippage values in parents
|
|
||||||
props.setRawSlippage(numParsed)
|
|
||||||
props.setRawTokenSlippage(numParsed)
|
|
||||||
}
|
|
||||||
|
|
||||||
const b = text => <Bold>{text}</Bold>
|
const b = text => <Bold>{text}</Bold>
|
||||||
|
|
||||||
const renderTransactionDetails = () => {
|
const renderTransactionDetails = () => {
|
||||||
|
@ -4,3 +4,8 @@ export const FACTORY_ADDRESSES = {
|
|||||||
4: '0xf5D915570BC477f9B8D6C0E980aA81757A3AaC36',
|
4: '0xf5D915570BC477f9B8D6C0E980aA81757A3AaC36',
|
||||||
42: '0xD3E51Ef092B2845f10401a0159B2B96e8B6c3D30'
|
42: '0xD3E51Ef092B2845f10401a0159B2B96e8B6c3D30'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const SUPPORTED_THEMES = {
|
||||||
|
DARK: 'DARK',
|
||||||
|
LIGHT: 'LIGHT'
|
||||||
|
}
|
||||||
|
@ -68,14 +68,12 @@ export function useFetchAllBalances() {
|
|||||||
Object.keys(allTokens).map(async k => {
|
Object.keys(allTokens).map(async k => {
|
||||||
let balance = null
|
let balance = null
|
||||||
let ethRate = null
|
let ethRate = null
|
||||||
|
|
||||||
if (isAddress(k) || k === 'ETH') {
|
if (isAddress(k) || k === 'ETH') {
|
||||||
if (k === 'ETH') {
|
if (k === 'ETH') {
|
||||||
balance = await getEtherBalance(account, library).catch(() => null)
|
balance = await getEtherBalance(account, library).catch(() => null)
|
||||||
ethRate = ONE
|
ethRate = ONE
|
||||||
} else {
|
} else {
|
||||||
balance = await getTokenBalance(k, account, library).catch(() => null)
|
balance = await getTokenBalance(k, account, library).catch(() => null)
|
||||||
|
|
||||||
// only get values for tokens with positive balances
|
// only get values for tokens with positive balances
|
||||||
if (!!balance && balance.gt(ZERO)) {
|
if (!!balance && balance.gt(ZERO)) {
|
||||||
const tokenReserves = await getTokenReserves(k, library).catch(() => null)
|
const tokenReserves = await getTokenReserves(k, library).catch(() => null)
|
||||||
|
@ -95,11 +95,14 @@ export function useBetaMessageManager() {
|
|||||||
export function useDarkModeManager() {
|
export function useDarkModeManager() {
|
||||||
const [state, { updateKey }] = useLocalStorageContext()
|
const [state, { updateKey }] = useLocalStorageContext()
|
||||||
|
|
||||||
const isDarkMode = state[DARK_MODE]
|
let isDarkMode = state[DARK_MODE]
|
||||||
|
|
||||||
const toggleDarkMode = useCallback(() => {
|
const toggleDarkMode = useCallback(
|
||||||
updateKey(DARK_MODE, !isDarkMode)
|
value => {
|
||||||
}, [updateKey, isDarkMode])
|
updateKey(DARK_MODE, value === false || value === true ? value : !isDarkMode)
|
||||||
|
},
|
||||||
|
[updateKey, isDarkMode]
|
||||||
|
)
|
||||||
|
|
||||||
return [state[DARK_MODE], toggleDarkMode]
|
return [state[DARK_MODE], toggleDarkMode]
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@ import Header from '../components/Header'
|
|||||||
import Footer from '../components/Footer'
|
import Footer from '../components/Footer'
|
||||||
|
|
||||||
import NavigationTabs from '../components/NavigationTabs'
|
import NavigationTabs from '../components/NavigationTabs'
|
||||||
import { isAddress } from '../utils'
|
import { isAddress, getAllQueryParams } from '../utils'
|
||||||
|
|
||||||
const Swap = lazy(() => import('./Swap'))
|
const Swap = lazy(() => import('./Swap'))
|
||||||
const Send = lazy(() => import('./Send'))
|
const Send = lazy(() => import('./Send'))
|
||||||
@ -48,6 +48,7 @@ const Body = styled.div`
|
|||||||
`
|
`
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
|
const params = getAllQueryParams()
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Suspense fallback={null}>
|
<Suspense fallback={null}>
|
||||||
@ -63,27 +64,33 @@ export default function App() {
|
|||||||
{/* this Suspense is for route code-splitting */}
|
{/* this Suspense is for route code-splitting */}
|
||||||
<Suspense fallback={null}>
|
<Suspense fallback={null}>
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route exact strict path="/swap" component={Swap} />
|
<Route exact strict path="/swap" component={() => <Swap params={params} />} />
|
||||||
<Route
|
<Route
|
||||||
exact
|
exact
|
||||||
strict
|
strict
|
||||||
path="/swap/:tokenAddress?"
|
path="/swap/:tokenAddress?"
|
||||||
render={({ match }) => {
|
render={({ match, location }) => {
|
||||||
if (isAddress(match.params.tokenAddress)) {
|
if (isAddress(match.params.tokenAddress)) {
|
||||||
return <Swap initialCurrency={isAddress(match.params.tokenAddress)} />
|
return (
|
||||||
|
<Swap
|
||||||
|
location={location}
|
||||||
|
initialCurrency={isAddress(match.params.tokenAddress)}
|
||||||
|
params={params}
|
||||||
|
/>
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
return <Redirect to={{ pathname: '/swap' }} />
|
return <Redirect to={{ pathname: '/swap' }} />
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Route exact strict path="/send" component={Send} />
|
<Route exact strict path="/send" component={() => <Send params={params} />} />
|
||||||
<Route
|
<Route
|
||||||
exact
|
exact
|
||||||
strict
|
strict
|
||||||
path="/send/:tokenAddress?"
|
path="/send/:tokenAddress?"
|
||||||
render={({ match }) => {
|
render={({ match, location }) => {
|
||||||
if (isAddress(match.params.tokenAddress)) {
|
if (isAddress(match.params.tokenAddress)) {
|
||||||
return <Send initialCurrency={isAddress(match.params.tokenAddress)} />
|
return <Send initialCurrency={isAddress(match.params.tokenAddress)} params={params} />
|
||||||
} else {
|
} else {
|
||||||
return <Redirect to={{ pathname: '/send' }} />
|
return <Redirect to={{ pathname: '/send' }} />
|
||||||
}
|
}
|
||||||
@ -96,7 +103,7 @@ export default function App() {
|
|||||||
'/create-exchange',
|
'/create-exchange',
|
||||||
'/create-exchange/:tokenAddress?'
|
'/create-exchange/:tokenAddress?'
|
||||||
]}
|
]}
|
||||||
component={Pool}
|
component={() => <Pool params={params} />}
|
||||||
/>
|
/>
|
||||||
<Redirect to="/swap" />
|
<Redirect to="/swap" />
|
||||||
</Switch>
|
</Switch>
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import React, { useReducer, useState, useCallback, useEffect, useMemo } from 'react'
|
import React, { useReducer, useState, useCallback, useEffect, useMemo } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useWeb3Context } from 'web3-react'
|
import { useWeb3Context } from 'web3-react'
|
||||||
|
import { createBrowserHistory } from 'history'
|
||||||
import { ethers } from 'ethers'
|
import { ethers } from 'ethers'
|
||||||
import ReactGA from 'react-ga'
|
import ReactGA from 'react-ga'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
@ -118,11 +119,13 @@ function calculateSlippageBounds(value) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialAddLiquidityState = {
|
function initialAddLiquidityState(state) {
|
||||||
inputValue: '',
|
return {
|
||||||
outputValue: '',
|
inputValue: state.ethAmountURL ? state.ethAmountURL : '',
|
||||||
lastEditedField: INPUT,
|
outputValue: state.tokenAmountURL && !state.ethAmountURL ? state.tokenAmountURL : '',
|
||||||
outputCurrency: ''
|
lastEditedField: state.tokenAmountURL && state.ethAmountURL === '' ? OUTPUT : INPUT,
|
||||||
|
outputCurrency: state.tokenURL ? state.tokenURL : ''
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function addLiquidityStateReducer(state, action) {
|
function addLiquidityStateReducer(state, action) {
|
||||||
@ -153,7 +156,7 @@ function addLiquidityStateReducer(state, action) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
return initialAddLiquidityState
|
return initialAddLiquidityState()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -189,11 +192,21 @@ function getMarketRate(reserveETH, reserveToken, decimals, invert = false) {
|
|||||||
return getExchangeRate(reserveETH, 18, reserveToken, decimals, invert)
|
return getExchangeRate(reserveETH, 18, reserveToken, decimals, invert)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function AddLiquidity() {
|
export default function AddLiquidity({ params }) {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { library, active, account } = useWeb3Context()
|
const { library, active, account } = useWeb3Context()
|
||||||
|
|
||||||
const [addLiquidityState, dispatchAddLiquidityState] = useReducer(addLiquidityStateReducer, initialAddLiquidityState)
|
// clear url of query
|
||||||
|
useEffect(() => {
|
||||||
|
const history = createBrowserHistory()
|
||||||
|
history.push(window.location.pathname + '')
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const [addLiquidityState, dispatchAddLiquidityState] = useReducer(
|
||||||
|
addLiquidityStateReducer,
|
||||||
|
{ ethAmountURL: params.ethAmount, tokenAmountURL: params.tokenAmount, tokenURL: params.token },
|
||||||
|
initialAddLiquidityState
|
||||||
|
)
|
||||||
const { inputValue, outputValue, lastEditedField, outputCurrency } = addLiquidityState
|
const { inputValue, outputValue, lastEditedField, outputCurrency } = addLiquidityState
|
||||||
const inputCurrency = 'ETH'
|
const inputCurrency = 'ETH'
|
||||||
|
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import React, { useState, useEffect } from 'react'
|
import React, { useState, useEffect } from 'react'
|
||||||
import { withRouter } from 'react-router'
|
import { withRouter } from 'react-router'
|
||||||
import { useWeb3Context } from 'web3-react'
|
import { useWeb3Context } from 'web3-react'
|
||||||
|
import { createBrowserHistory } from 'history'
|
||||||
import { ethers } from 'ethers'
|
import { ethers } from 'ethers'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import ReactGA from 'react-ga'
|
import ReactGA from 'react-ga'
|
||||||
|
|
||||||
import { Button } from '../../theme'
|
import { Button } from '../../theme'
|
||||||
import AddressInputPanel from '../../components/AddressInputPanel'
|
import AddressInputPanel from '../../components/AddressInputPanel'
|
||||||
import OversizedPanel from '../../components/OversizedPanel'
|
import OversizedPanel from '../../components/OversizedPanel'
|
||||||
@ -54,13 +54,13 @@ const Flex = styled.div`
|
|||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
function CreateExchange({ history, location }) {
|
function CreateExchange({ location, params }) {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { account } = useWeb3Context()
|
const { account } = useWeb3Context()
|
||||||
const factory = useFactoryContract()
|
const factory = useFactoryContract()
|
||||||
|
|
||||||
const [tokenAddress, setTokenAddress] = useState({
|
const [tokenAddress, setTokenAddress] = useState({
|
||||||
address: '',
|
address: params.tokenAddress ? params.tokenAddress : '',
|
||||||
name: ''
|
name: ''
|
||||||
})
|
})
|
||||||
const [tokenAddressError, setTokenAddressError] = useState()
|
const [tokenAddressError, setTokenAddressError] = useState()
|
||||||
@ -68,12 +68,11 @@ function CreateExchange({ history, location }) {
|
|||||||
const { name, symbol, decimals, exchangeAddress } = useTokenDetails(tokenAddress.address)
|
const { name, symbol, decimals, exchangeAddress } = useTokenDetails(tokenAddress.address)
|
||||||
const addTransaction = useTransactionAdder()
|
const addTransaction = useTransactionAdder()
|
||||||
|
|
||||||
// clear location state, if it exists
|
// clear url of query
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (location.state) {
|
const history = createBrowserHistory()
|
||||||
history.replace(location.pathname)
|
history.push(window.location.pathname + '')
|
||||||
}
|
}, [])
|
||||||
}, []) // eslint-disable-line react-hooks/exhaustive-deps
|
|
||||||
|
|
||||||
// validate everything
|
// validate everything
|
||||||
const [errorMessage, setErrorMessage] = useState(!account && t('noWallet'))
|
const [errorMessage, setErrorMessage] = useState(!account && t('noWallet'))
|
||||||
@ -118,7 +117,11 @@ function CreateExchange({ history, location }) {
|
|||||||
<>
|
<>
|
||||||
<AddressInputPanel
|
<AddressInputPanel
|
||||||
title={t('tokenAddress')}
|
title={t('tokenAddress')}
|
||||||
initialInput={(location.state && location.state.tokenAddress) || ''}
|
initialInput={
|
||||||
|
params.tokenAddress
|
||||||
|
? { address: params.tokenAddress }
|
||||||
|
: { address: (location.state && location.state.tokenAddress) || '' }
|
||||||
|
}
|
||||||
onChange={setTokenAddress}
|
onChange={setTokenAddress}
|
||||||
onError={setTokenAddressError}
|
onError={setTokenAddressError}
|
||||||
/>
|
/>
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import React, { useState, useEffect, useCallback } from 'react'
|
import React, { useState, useEffect, useCallback } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import ReactGA from 'react-ga'
|
import ReactGA from 'react-ga'
|
||||||
|
import { createBrowserHistory } from 'history'
|
||||||
import { useWeb3Context } from 'web3-react'
|
import { useWeb3Context } from 'web3-react'
|
||||||
import { ethers } from 'ethers'
|
import { ethers } from 'ethers'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
@ -141,14 +142,20 @@ function calculateSlippageBounds(value) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function RemoveLiquidity() {
|
export default function RemoveLiquidity({ params }) {
|
||||||
const { library, account, active } = useWeb3Context()
|
const { library, account, active } = useWeb3Context()
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
const addTransaction = useTransactionAdder()
|
const addTransaction = useTransactionAdder()
|
||||||
|
|
||||||
const [outputCurrency, setOutputCurrency] = useState('')
|
// clear url of query
|
||||||
const [value, setValue] = useState('')
|
useEffect(() => {
|
||||||
|
const history = createBrowserHistory()
|
||||||
|
history.push(window.location.pathname + '')
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const [outputCurrency, setOutputCurrency] = useState(params.poolTokenAddress)
|
||||||
|
const [value, setValue] = useState(params.poolTokenAmount ? params.poolTokenAmount : '')
|
||||||
const [inputError, setInputError] = useState()
|
const [inputError, setInputError] = useState()
|
||||||
const [valueParsed, setValueParsed] = useState()
|
const [valueParsed, setValueParsed] = useState()
|
||||||
// parse value
|
// parse value
|
||||||
|
@ -1,27 +1,32 @@
|
|||||||
import React, { Suspense, lazy, useEffect } from 'react'
|
import React, { Suspense, lazy, useEffect } from 'react'
|
||||||
import ReactGA from 'react-ga'
|
import ReactGA from 'react-ga'
|
||||||
import { Switch, Route, Redirect } from 'react-router-dom'
|
import { Switch, Route, Redirect } from 'react-router-dom'
|
||||||
|
|
||||||
import ModeSelector from './ModeSelector'
|
import ModeSelector from './ModeSelector'
|
||||||
|
|
||||||
const AddLiquidity = lazy(() => import('./AddLiquidity'))
|
const AddLiquidity = lazy(() => import('./AddLiquidity'))
|
||||||
const RemoveLiquidity = lazy(() => import('./RemoveLiquidity'))
|
const RemoveLiquidity = lazy(() => import('./RemoveLiquidity'))
|
||||||
const CreateExchange = lazy(() => import('./CreateExchange'))
|
const CreateExchange = lazy(() => import('./CreateExchange'))
|
||||||
|
|
||||||
export default function Pool() {
|
export default function Pool({ params }) {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
ReactGA.pageview(window.location.pathname + window.location.search)
|
ReactGA.pageview(window.location.pathname + window.location.search)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
const AddLiquidityParams = () => <AddLiquidity params={params} />
|
||||||
|
|
||||||
|
const RemoveLiquidityParams = () => <RemoveLiquidity params={params} />
|
||||||
|
|
||||||
|
const CreateExchangeParams = () => <CreateExchange params={params} />
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ModeSelector />
|
<ModeSelector />
|
||||||
{/* this Suspense is for route code-splitting */}
|
{/* this Suspense is for route code-splitting */}
|
||||||
<Suspense fallback={null}>
|
<Suspense fallback={null}>
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route exact strict path="/add-liquidity" component={AddLiquidity} />
|
<Route exact strict path="/add-liquidity" component={AddLiquidityParams} />
|
||||||
<Route exact strict path="/remove-liquidity" component={RemoveLiquidity} />
|
<Route exact strict path="/remove-liquidity" component={RemoveLiquidityParams} />
|
||||||
<Route exact strict path="/create-exchange" component={CreateExchange} />
|
<Route exact strict path="/create-exchange" component={CreateExchangeParams} />
|
||||||
<Route
|
<Route
|
||||||
path="/create-exchange/:tokenAddress"
|
path="/create-exchange/:tokenAddress"
|
||||||
render={({ match }) => {
|
render={({ match }) => {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import ExchangePage from '../../components/ExchangePage'
|
import ExchangePage from '../../components/ExchangePage'
|
||||||
|
|
||||||
export default function Send({ initialCurrency }) {
|
export default function Send({ initialCurrency, params }) {
|
||||||
return <ExchangePage initialCurrency={initialCurrency} sending={true} />
|
return <ExchangePage initialCurrency={initialCurrency} params={params} sending={true} />
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import ExchangePage from '../../components/ExchangePage'
|
import ExchangePage from '../../components/ExchangePage'
|
||||||
|
|
||||||
export default function Swap({ initialCurrency }) {
|
export default function Swap({ initialCurrency, params }) {
|
||||||
return <ExchangePage initialCurrency={initialCurrency} />
|
return <ExchangePage initialCurrency={initialCurrency} params={params} />
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import React from 'react'
|
import React, { useEffect } from 'react'
|
||||||
import { ThemeProvider as StyledComponentsThemeProvider, createGlobalStyle, css } from 'styled-components'
|
import { ThemeProvider as StyledComponentsThemeProvider, createGlobalStyle, css } from 'styled-components'
|
||||||
|
import { getQueryParam, checkSupportedTheme } from '../utils'
|
||||||
|
import { SUPPORTED_THEMES } from '../constants'
|
||||||
import { useDarkModeManager } from '../contexts/LocalStorage'
|
import { useDarkModeManager } from '../contexts/LocalStorage'
|
||||||
|
|
||||||
export * from './components'
|
export * from './components'
|
||||||
@ -32,6 +34,22 @@ const flexRowNoWrap = css`
|
|||||||
const white = '#FFFFFF'
|
const white = '#FFFFFF'
|
||||||
const black = '#000000'
|
const black = '#000000'
|
||||||
|
|
||||||
|
export default function ThemeProvider({ children }) {
|
||||||
|
const [darkMode, toggleDarkMode] = useDarkModeManager()
|
||||||
|
const themeURL = checkSupportedTheme(getQueryParam(window.location, 'theme'))
|
||||||
|
const themeToRender = themeURL
|
||||||
|
? themeURL.toUpperCase() === SUPPORTED_THEMES.DARK
|
||||||
|
? true
|
||||||
|
: themeURL.toUpperCase() === SUPPORTED_THEMES.LIGHT
|
||||||
|
? false
|
||||||
|
: darkMode
|
||||||
|
: darkMode
|
||||||
|
useEffect(() => {
|
||||||
|
toggleDarkMode(themeToRender)
|
||||||
|
}, [toggleDarkMode, themeToRender])
|
||||||
|
return <StyledComponentsThemeProvider theme={theme(themeToRender)}>{children}</StyledComponentsThemeProvider>
|
||||||
|
}
|
||||||
|
|
||||||
const theme = darkMode => ({
|
const theme = darkMode => ({
|
||||||
white,
|
white,
|
||||||
black,
|
black,
|
||||||
@ -84,12 +102,6 @@ const theme = darkMode => ({
|
|||||||
flexRowNoWrap
|
flexRowNoWrap
|
||||||
})
|
})
|
||||||
|
|
||||||
export default function ThemeProvider({ children }) {
|
|
||||||
const [darkMode] = useDarkModeManager()
|
|
||||||
|
|
||||||
return <StyledComponentsThemeProvider theme={theme(darkMode)}>{children}</StyledComponentsThemeProvider>
|
|
||||||
}
|
|
||||||
|
|
||||||
export const GlobalStyle = createGlobalStyle`
|
export const GlobalStyle = createGlobalStyle`
|
||||||
@import url('https://rsms.me/inter/inter.css');
|
@import url('https://rsms.me/inter/inter.css');
|
||||||
html { font-family: 'Inter', sans-serif; }
|
html { font-family: 'Inter', sans-serif; }
|
||||||
|
@ -4,7 +4,7 @@ import FACTORY_ABI from '../constants/abis/factory'
|
|||||||
import EXCHANGE_ABI from '../constants/abis/exchange'
|
import EXCHANGE_ABI from '../constants/abis/exchange'
|
||||||
import ERC20_ABI from '../constants/abis/erc20'
|
import ERC20_ABI from '../constants/abis/erc20'
|
||||||
import ERC20_BYTES32_ABI from '../constants/abis/erc20_bytes32'
|
import ERC20_BYTES32_ABI from '../constants/abis/erc20_bytes32'
|
||||||
import { FACTORY_ADDRESSES } from '../constants'
|
import { FACTORY_ADDRESSES, SUPPORTED_THEMES } from '../constants'
|
||||||
import { formatFixed } from '@uniswap/sdk'
|
import { formatFixed } from '@uniswap/sdk'
|
||||||
|
|
||||||
import UncheckedJsonRpcSigner from './signer'
|
import UncheckedJsonRpcSigner from './signer'
|
||||||
@ -33,6 +33,7 @@ const ETHERSCAN_PREFIXES = {
|
|||||||
5: 'goerli.',
|
5: 'goerli.',
|
||||||
42: 'kovan.'
|
42: 'kovan.'
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getEtherscanLink(networkId, data, type) {
|
export function getEtherscanLink(networkId, data, type) {
|
||||||
const prefix = `https://${ETHERSCAN_PREFIXES[networkId] || ETHERSCAN_PREFIXES[1]}etherscan.io`
|
const prefix = `https://${ETHERSCAN_PREFIXES[networkId] || ETHERSCAN_PREFIXES[1]}etherscan.io`
|
||||||
|
|
||||||
@ -47,6 +48,63 @@ export function getEtherscanLink(networkId, data, type) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getQueryParam(windowLocation, name) {
|
||||||
|
var q = windowLocation.search.match(new RegExp('[?&]' + name + '=([^&#?]*)'))
|
||||||
|
return q && q[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getAllQueryParams() {
|
||||||
|
let params = {}
|
||||||
|
params.theme = checkSupportedTheme(getQueryParam(window.location, 'theme'))
|
||||||
|
|
||||||
|
params.inputCurrency = isAddress(getQueryParam(window.location, 'inputCurrency'))
|
||||||
|
? getQueryParam(window.location, 'inputCurrency')
|
||||||
|
: ''
|
||||||
|
params.outputCurrency = isAddress(getQueryParam(window.location, 'outputCurrency'))
|
||||||
|
? getQueryParam(window.location, 'outputCurrency')
|
||||||
|
: ''
|
||||||
|
params.slippage = !isNaN(getQueryParam(window.location, 'slippage')) ? getQueryParam(window.location, 'slippage') : ''
|
||||||
|
params.exactField = getQueryParam(window.location, 'exactField')
|
||||||
|
params.exactAmount = !isNaN(getQueryParam(window.location, 'exactAmount'))
|
||||||
|
? getQueryParam(window.location, 'exactAmount')
|
||||||
|
: ''
|
||||||
|
params.theme = checkSupportedTheme(getQueryParam(window.location, 'theme'))
|
||||||
|
params.recipient = isAddress(getQueryParam(window.location, 'recipient'))
|
||||||
|
? getQueryParam(window.location, 'recipient')
|
||||||
|
: ''
|
||||||
|
|
||||||
|
// Add Liquidity params
|
||||||
|
params.ethAmount = !isNaN(getQueryParam(window.location, 'ethAmount'))
|
||||||
|
? getQueryParam(window.location, 'ethAmount')
|
||||||
|
: ''
|
||||||
|
params.tokenAmount = !isNaN(getQueryParam(window.location, 'tokenAmount'))
|
||||||
|
? getQueryParam(window.location, 'tokenAmount')
|
||||||
|
: ''
|
||||||
|
params.token = isAddress(getQueryParam(window.location, 'token')) ? getQueryParam(window.location, 'token') : ''
|
||||||
|
|
||||||
|
// Remove liquidity params
|
||||||
|
params.poolTokenAmount = !isNaN(getQueryParam(window.location, 'poolTokenAmount'))
|
||||||
|
? getQueryParam(window.location, 'poolTokenAmount')
|
||||||
|
: ''
|
||||||
|
params.poolTokenAddress = isAddress(getQueryParam(window.location, 'poolTokenAddress'))
|
||||||
|
? getQueryParam(window.location, 'poolTokenAddress')
|
||||||
|
: ''
|
||||||
|
|
||||||
|
// Create Exchange params
|
||||||
|
params.tokenAddress = isAddress(getQueryParam(window.location, 'tokenAddress'))
|
||||||
|
? getQueryParam(window.location, 'tokenAddress')
|
||||||
|
: ''
|
||||||
|
|
||||||
|
return params
|
||||||
|
}
|
||||||
|
|
||||||
|
export function checkSupportedTheme(themeName) {
|
||||||
|
if (themeName && themeName.toUpperCase() in SUPPORTED_THEMES) {
|
||||||
|
return themeName.toUpperCase()
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
export function getNetworkName(networkId) {
|
export function getNetworkName(networkId) {
|
||||||
switch (networkId) {
|
switch (networkId) {
|
||||||
case 1: {
|
case 1: {
|
||||||
|
Loading…
Reference in New Issue
Block a user