Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
I
interface
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
LuckySwap
interface
Commits
3f3889b9
Unverified
Commit
3f3889b9
authored
Jul 29, 2019
by
Ian Lapham
Committed by
GitHub
Jul 29, 2019
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #1 from ianlapham/combine-swap-send
Combine swap send into one component
parents
870b3f7e
e19e150f
Changes
10
Hide whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
1283 additions
and
1539 deletions
+1283
-1539
en.json
public/locales/en.json
+2
-1
question-mark.svg
src/assets/images/question-mark.svg
+4
-0
question.svg
src/assets/images/question.svg
+4
-0
index.js
src/components/ContextualInfoNew/index.js
+4
-3
index.jsx
src/components/ExchangePage/index.jsx
+722
-0
index.js
src/components/Modal/index.js
+1
-1
index.js
src/components/TransactionDetails/index.js
+537
-0
App.js
src/pages/App.js
+2
-2
index.js
src/pages/Send/index.js
+4
-781
index.js
src/pages/Swap/index.js
+3
-751
No files found.
public/locales/en.json
View file @
3f3889b9
...
@@ -80,5 +80,6 @@
...
@@ -80,5 +80,6 @@
"symbol"
:
"Symbol"
,
"symbol"
:
"Symbol"
,
"decimals"
:
"Decimals"
,
"decimals"
:
"Decimals"
,
"enterTokenCont"
:
"Enter a token address to continue"
,
"enterTokenCont"
:
"Enter a token address to continue"
,
"priceChange"
:
"This trade will cause the price to change by"
"priceChange"
:
"Expected price slippage"
,
"forAtLeast"
:
"for at least "
}
}
src/assets/images/question-mark.svg
0 → 100644
View file @
3f3889b9
<svg
width=
"18"
height=
"18"
viewBox=
"0 0 18 18"
fill=
"none"
xmlns=
"http://www.w3.org/2000/svg"
>
<circle
cx=
"9"
cy=
"9"
r=
"9"
fill=
"#E1E1E1"
/>
<path
d=
"M8.06493 10.8317H9.15706V10.7592C9.17233 9.88089 9.42436 9.48757 10.0735 9.08662C10.7571 8.67421 11.1771 8.09378 11.1771 7.23459C11.1771 5.99354 10.2377 5.15344 8.83629 5.15344C7.54942 5.15344 6.51839 5.90571 6.46875 7.28041H7.62961C7.67543 6.47086 8.25204 6.11573 8.83629 6.11573C9.48546 6.11573 10.0124 6.54724 10.0124 7.22313C10.0124 7.79211 9.65729 8.19306 9.20288 8.47564C8.49262 8.91096 8.07257 9.34246 8.06493 10.7592V10.8317ZM8.64154 13.1534C9.05777 13.1534 9.40527 12.8136 9.40527 12.3897C9.40527 11.9735 9.05777 11.6298 8.64154 11.6298C8.22149 11.6298 7.87782 11.9735 7.87782 12.3897C7.87782 12.8136 8.22149 13.1534 8.64154 13.1534Z"
fill=
"#737373"
/>
</svg>
src/assets/images/question.svg
0 → 100644
View file @
3f3889b9
<svg
width=
"16"
height=
"16"
viewBox=
"0 0 16 16"
fill=
"none"
xmlns=
"http://www.w3.org/2000/svg"
>
<circle
cx=
"8"
cy=
"8"
r=
"8"
fill=
"#E1E1E1"
/>
<path
d=
"M7.09618 9.67828H8.18831V9.60573C8.20358 8.72745 8.45561 8.33413 9.10477 7.93317C9.78831 7.52076 10.2084 6.94033 10.2084 6.08115C10.2084 4.8401 9.26897 4 7.86754 4C6.58067 4 5.54964 4.75227 5.5 6.12697H6.66086C6.70668 5.31742 7.28329 4.96229 7.86754 4.96229C8.51671 4.96229 9.04368 5.39379 9.04368 6.06969C9.04368 6.63866 8.68854 7.03962 8.23413 7.3222C7.52387 7.75752 7.10382 8.18902 7.09618 9.60573V9.67828ZM7.67279 12C8.08902 12 8.43652 11.6601 8.43652 11.2363C8.43652 10.82 8.08902 10.4764 7.67279 10.4764C7.25274 10.4764 6.90907 10.82 6.90907 11.2363C6.90907 11.6601 7.25274 12 7.67279 12Z"
fill=
"#737373"
/>
</svg>
src/components/ContextualInfoNew/index.js
View file @
3f3889b9
...
@@ -33,6 +33,7 @@ const SummaryWrapperContainer = styled.div`
...
@@ -33,6 +33,7 @@ const SummaryWrapperContainer = styled.div`
const
Details
=
styled
.
div
`
const
Details
=
styled
.
div
`
background-color:
${({
theme
})
=>
theme
.
concreteGray
}
;
background-color:
${({
theme
})
=>
theme
.
concreteGray
}
;
padding: 1.5rem;
padding: 1.5rem;
padding-bottom: 1rem;
border-radius: 1rem;
border-radius: 1rem;
font-size: 0.75rem;
font-size: 0.75rem;
margin-top: 1rem;
margin-top: 1rem;
...
@@ -92,10 +93,10 @@ export default function ContextualInfo({
...
@@ -92,10 +93,10 @@ export default function ContextualInfo({
renderTransactionDetails
=
()
=>
{},
renderTransactionDetails
=
()
=>
{},
isError
=
false
,
isError
=
false
,
slippageWarning
,
slippageWarning
,
highSlippageWarning
highSlippageWarning
,
dropDownContent
})
{
})
{
const
[
showDetails
,
setShowDetails
]
=
useState
(
false
)
const
[
showDetails
,
setShowDetails
]
=
useState
(
false
)
return
!
allowExpand
?
(
return
!
allowExpand
?
(
<
SummaryWrapper
>
{
contextualInfo
}
<
/SummaryWrapper
>
<
SummaryWrapper
>
{
contextualInfo
}
<
/SummaryWrapper
>
)
:
(
)
:
(
...
@@ -117,7 +118,7 @@ export default function ContextualInfo({
...
@@ -117,7 +118,7 @@ export default function ContextualInfo({
)}
)}
<
/
>
<
/
>
<
/SummaryWrapperContainer
>
<
/SummaryWrapperContainer
>
{
showDetails
&&
<
Details
>
{
renderTransactionDetails
()}
<
/Details>
}
{
showDetails
&&
<
Details
>
{
dropDownContent
()}
<
/Details>
}
<
/
>
<
/
>
)
)
}
}
src/components/ExchangePage/index.jsx
0 → 100644
View file @
3f3889b9
import
React
,
{
useState
,
useReducer
,
useEffect
}
from
'
react
'
import
ReactGA
from
'
react-ga
'
import
{
useTranslation
}
from
'
react-i18next
'
import
{
useWeb3Context
}
from
'
web3-react
'
import
{
ethers
}
from
'
ethers
'
import
styled
from
'
styled-components
'
import
{
Button
}
from
'
../../theme
'
import
CurrencyInputPanel
from
'
../CurrencyInputPanel
'
import
AddressInputPanel
from
'
../AddressInputPanel
'
import
OversizedPanel
from
'
../OversizedPanel
'
import
TransactionDetails
from
'
../TransactionDetails
'
import
ArrowDownBlue
from
'
../../assets/images/arrow-down-blue.svg
'
import
ArrowDownGrey
from
'
../../assets/images/arrow-down-grey.svg
'
import
{
amountFormatter
,
calculateGasMargin
}
from
'
../../utils
'
import
{
useExchangeContract
}
from
'
../../hooks
'
import
{
useTokenDetails
}
from
'
../../contexts/Tokens
'
import
{
useTransactionAdder
}
from
'
../../contexts/Transactions
'
import
{
useAddressBalance
,
useExchangeReserves
}
from
'
../../contexts/Balances
'
import
{
useAddressAllowance
}
from
'
../../contexts/Allowances
'
const
INPUT
=
0
const
OUTPUT
=
1
const
ETH_TO_TOKEN
=
0
const
TOKEN_TO_ETH
=
1
const
TOKEN_TO_TOKEN
=
2
// denominated in bips
const
ALLOWED_SLIPPAGE_DEFAULT
=
150
const
TOKEN_ALLOWED_SLIPPAGE_DEFAULT
=
200
// denominated in seconds
const
DEADLINE_FROM_NOW
=
60
*
15
// denominated in bips
const
GAS_MARGIN
=
ethers
.
utils
.
bigNumberify
(
1000
)
const
DownArrowBackground
=
styled
.
div
`
${({
theme
})
=>
theme
.
flexRowNoWrap
}
justify-content: center;
align-items: center;
`
const
DownArrow
=
styled
.
img
`
width: 0.625rem;
height: 0.625rem;
position: relative;
padding: 0.875rem;
cursor:
${({
clickable
})
=>
clickable
&&
'
pointer
'
}
;
`
const
ExchangeRateWrapper
=
styled
.
div
`
${({
theme
})
=>
theme
.
flexRowNoWrap
}
;
align-items: center;
color:
${({
theme
})
=>
theme
.
doveGray
}
;
font-size: 0.75rem;
padding: 0.5rem 1rem;
`
const
ExchangeRate
=
styled
.
span
`
flex: 1 1 auto;
width: 0;
color:
${({
theme
})
=>
theme
.
chaliceGray
}
;
`
const
Flex
=
styled
.
div
`
display: flex;
justify-content: center;
padding: 2rem;
button {
max-width: 20rem;
}
`
function
calculateSlippageBounds
(
value
,
token
=
false
,
tokenAllowedSlippage
,
allowedSlippage
)
{
if
(
value
)
{
const
offset
=
value
.
mul
(
token
?
tokenAllowedSlippage
:
allowedSlippage
).
div
(
ethers
.
utils
.
bigNumberify
(
10000
))
const
minimum
=
value
.
sub
(
offset
)
const
maximum
=
value
.
add
(
offset
)
return
{
minimum
:
minimum
.
lt
(
ethers
.
constants
.
Zero
)
?
ethers
.
constants
.
Zero
:
minimum
,
maximum
:
maximum
.
gt
(
ethers
.
constants
.
MaxUint256
)
?
ethers
.
constants
.
MaxUint256
:
maximum
}
}
else
{
return
{}
}
}
function
getSwapType
(
inputCurrency
,
outputCurrency
)
{
if
(
!
inputCurrency
||
!
outputCurrency
)
{
return
null
}
else
if
(
inputCurrency
===
'
ETH
'
)
{
return
ETH_TO_TOKEN
}
else
if
(
outputCurrency
===
'
ETH
'
)
{
return
TOKEN_TO_ETH
}
else
{
return
TOKEN_TO_TOKEN
}
}
// this mocks the getInputPrice function, and calculates the required output
function
calculateEtherTokenOutputFromInput
(
inputAmount
,
inputReserve
,
outputReserve
)
{
const
inputAmountWithFee
=
inputAmount
.
mul
(
ethers
.
utils
.
bigNumberify
(
997
))
const
numerator
=
inputAmountWithFee
.
mul
(
outputReserve
)
const
denominator
=
inputReserve
.
mul
(
ethers
.
utils
.
bigNumberify
(
1000
)).
add
(
inputAmountWithFee
)
return
numerator
.
div
(
denominator
)
}
// this mocks the getOutputPrice function, and calculates the required input
function
calculateEtherTokenInputFromOutput
(
outputAmount
,
inputReserve
,
outputReserve
)
{
const
numerator
=
inputReserve
.
mul
(
outputAmount
).
mul
(
ethers
.
utils
.
bigNumberify
(
1000
))
const
denominator
=
outputReserve
.
sub
(
outputAmount
).
mul
(
ethers
.
utils
.
bigNumberify
(
997
))
return
numerator
.
div
(
denominator
).
add
(
ethers
.
constants
.
One
)
}
function
getInitialSwapState
(
outputCurrency
)
{
return
{
independentValue
:
''
,
// this is a user input
dependentValue
:
''
,
// this is a calculated number
independentField
:
INPUT
,
inputCurrency
:
'
ETH
'
,
outputCurrency
:
outputCurrency
?
outputCurrency
:
''
}
}
function
swapStateReducer
(
state
,
action
)
{
switch
(
action
.
type
)
{
case
'
FLIP_INDEPENDENT
'
:
{
const
{
independentField
,
inputCurrency
,
outputCurrency
}
=
state
return
{
...
state
,
dependentValue
:
''
,
independentField
:
independentField
===
INPUT
?
OUTPUT
:
INPUT
,
inputCurrency
:
outputCurrency
,
outputCurrency
:
inputCurrency
}
}
case
'
SELECT_CURRENCY
'
:
{
const
{
inputCurrency
,
outputCurrency
}
=
state
const
{
field
,
currency
}
=
action
.
payload
const
newInputCurrency
=
field
===
INPUT
?
currency
:
inputCurrency
const
newOutputCurrency
=
field
===
OUTPUT
?
currency
:
outputCurrency
if
(
newInputCurrency
===
newOutputCurrency
)
{
return
{
...
state
,
inputCurrency
:
field
===
INPUT
?
currency
:
''
,
outputCurrency
:
field
===
OUTPUT
?
currency
:
''
}
}
else
{
return
{
...
state
,
inputCurrency
:
newInputCurrency
,
outputCurrency
:
newOutputCurrency
}
}
}
case
'
UPDATE_INDEPENDENT
'
:
{
const
{
field
,
value
}
=
action
.
payload
const
{
dependentValue
,
independentValue
}
=
state
return
{
...
state
,
independentValue
:
value
,
dependentValue
:
value
===
independentValue
?
dependentValue
:
''
,
independentField
:
field
}
}
case
'
UPDATE_DEPENDENT
'
:
{
return
{
...
state
,
dependentValue
:
action
.
payload
}
}
default
:
{
return
getInitialSwapState
()
}
}
}
function
getExchangeRate
(
inputValue
,
inputDecimals
,
outputValue
,
outputDecimals
,
invert
=
false
)
{
try
{
if
(
inputValue
&&
(
inputDecimals
||
inputDecimals
===
0
)
&&
outputValue
&&
(
outputDecimals
||
outputDecimals
===
0
)
)
{
const
factor
=
ethers
.
utils
.
bigNumberify
(
10
).
pow
(
ethers
.
utils
.
bigNumberify
(
18
))
if
(
invert
)
{
return
inputValue
.
mul
(
factor
)
.
div
(
outputValue
)
.
mul
(
ethers
.
utils
.
bigNumberify
(
10
).
pow
(
ethers
.
utils
.
bigNumberify
(
outputDecimals
)))
.
div
(
ethers
.
utils
.
bigNumberify
(
10
).
pow
(
ethers
.
utils
.
bigNumberify
(
inputDecimals
)))
}
else
{
return
outputValue
.
mul
(
factor
)
.
div
(
inputValue
)
.
mul
(
ethers
.
utils
.
bigNumberify
(
10
).
pow
(
ethers
.
utils
.
bigNumberify
(
inputDecimals
)))
.
div
(
ethers
.
utils
.
bigNumberify
(
10
).
pow
(
ethers
.
utils
.
bigNumberify
(
outputDecimals
)))
}
}
}
catch
{}
}
function
getMarketRate
(
swapType
,
inputReserveETH
,
inputReserveToken
,
inputDecimals
,
outputReserveETH
,
outputReserveToken
,
outputDecimals
,
invert
=
false
)
{
if
(
swapType
===
ETH_TO_TOKEN
)
{
return
getExchangeRate
(
outputReserveETH
,
18
,
outputReserveToken
,
outputDecimals
,
invert
)
}
else
if
(
swapType
===
TOKEN_TO_ETH
)
{
return
getExchangeRate
(
inputReserveToken
,
inputDecimals
,
inputReserveETH
,
18
,
invert
)
}
else
if
(
swapType
===
TOKEN_TO_TOKEN
)
{
const
factor
=
ethers
.
utils
.
bigNumberify
(
10
).
pow
(
ethers
.
utils
.
bigNumberify
(
18
))
const
firstRate
=
getExchangeRate
(
inputReserveToken
,
inputDecimals
,
inputReserveETH
,
18
)
const
secondRate
=
getExchangeRate
(
outputReserveETH
,
18
,
outputReserveToken
,
outputDecimals
)
try
{
return
!!
(
firstRate
&&
secondRate
)
?
firstRate
.
mul
(
secondRate
).
div
(
factor
)
:
undefined
}
catch
{}
}
}
export
default
function
ExchangePage
({
initialCurrency
,
sending
})
{
const
{
t
}
=
useTranslation
()
const
{
account
}
=
useWeb3Context
()
const
addTransaction
=
useTransactionAdder
()
const
[
rawSlippage
,
setRawSlippage
]
=
useState
(
ALLOWED_SLIPPAGE_DEFAULT
)
const
[
rawTokenSlippage
,
setRawTokenSlippage
]
=
useState
(
TOKEN_ALLOWED_SLIPPAGE_DEFAULT
)
let
allowedSlippageBig
=
ethers
.
utils
.
bigNumberify
(
rawSlippage
)
let
tokenAllowedSlippageBig
=
ethers
.
utils
.
bigNumberify
(
rawTokenSlippage
)
// analytics
useEffect
(()
=>
{
ReactGA
.
pageview
(
window
.
location
.
pathname
+
window
.
location
.
search
)
},
[])
// core swap state-
const
[
swapState
,
dispatchSwapState
]
=
useReducer
(
swapStateReducer
,
initialCurrency
,
getInitialSwapState
)
const
{
independentValue
,
dependentValue
,
independentField
,
inputCurrency
,
outputCurrency
}
=
swapState
const
[
recipient
,
setRecipient
]
=
useState
({
address
:
''
,
name
:
''
})
const
[
recipientError
,
setRecipientError
]
=
useState
()
// get swap type from the currency types
const
swapType
=
getSwapType
(
inputCurrency
,
outputCurrency
)
// get decimals and exchange addressfor each of the currency types
const
{
symbol
:
inputSymbol
,
decimals
:
inputDecimals
,
exchangeAddress
:
inputExchangeAddress
}
=
useTokenDetails
(
inputCurrency
)
const
{
symbol
:
outputSymbol
,
decimals
:
outputDecimals
,
exchangeAddress
:
outputExchangeAddress
}
=
useTokenDetails
(
outputCurrency
)
const
inputExchangeContract
=
useExchangeContract
(
inputExchangeAddress
)
const
outputExchangeContract
=
useExchangeContract
(
outputExchangeAddress
)
const
contract
=
swapType
===
ETH_TO_TOKEN
?
outputExchangeContract
:
inputExchangeContract
// get input allowance
const
inputAllowance
=
useAddressAllowance
(
account
,
inputCurrency
,
inputExchangeAddress
)
// fetch reserves for each of the currency types
const
{
reserveETH
:
inputReserveETH
,
reserveToken
:
inputReserveToken
}
=
useExchangeReserves
(
inputCurrency
)
const
{
reserveETH
:
outputReserveETH
,
reserveToken
:
outputReserveToken
}
=
useExchangeReserves
(
outputCurrency
)
// get balances for each of the currency types
const
inputBalance
=
useAddressBalance
(
account
,
inputCurrency
)
const
outputBalance
=
useAddressBalance
(
account
,
outputCurrency
)
const
inputBalanceFormatted
=
!!
(
inputBalance
&&
Number
.
isInteger
(
inputDecimals
))
?
amountFormatter
(
inputBalance
,
inputDecimals
,
Math
.
min
(
4
,
inputDecimals
))
:
''
const
outputBalanceFormatted
=
!!
(
outputBalance
&&
Number
.
isInteger
(
outputDecimals
))
?
amountFormatter
(
outputBalance
,
outputDecimals
,
Math
.
min
(
4
,
outputDecimals
))
:
''
// compute useful transforms of the data above
const
independentDecimals
=
independentField
===
INPUT
?
inputDecimals
:
outputDecimals
const
dependentDecimals
=
independentField
===
OUTPUT
?
inputDecimals
:
outputDecimals
// declare/get parsed and formatted versions of input/output values
const
[
independentValueParsed
,
setIndependentValueParsed
]
=
useState
()
const
dependentValueFormatted
=
!!
(
dependentValue
&&
(
dependentDecimals
||
dependentDecimals
===
0
))
?
amountFormatter
(
dependentValue
,
dependentDecimals
,
Math
.
min
(
4
,
dependentDecimals
),
false
)
:
''
const
inputValueParsed
=
independentField
===
INPUT
?
independentValueParsed
:
dependentValue
const
inputValueFormatted
=
independentField
===
INPUT
?
independentValue
:
dependentValueFormatted
const
outputValueParsed
=
independentField
===
OUTPUT
?
independentValueParsed
:
dependentValue
const
outputValueFormatted
=
independentField
===
OUTPUT
?
independentValue
:
dependentValueFormatted
// validate + parse independent value
const
[
independentError
,
setIndependentError
]
=
useState
()
useEffect
(()
=>
{
if
(
independentValue
&&
(
independentDecimals
||
independentDecimals
===
0
))
{
try
{
const
parsedValue
=
ethers
.
utils
.
parseUnits
(
independentValue
,
independentDecimals
)
if
(
parsedValue
.
lte
(
ethers
.
constants
.
Zero
)
||
parsedValue
.
gte
(
ethers
.
constants
.
MaxUint256
))
{
throw
Error
()
}
else
{
setIndependentValueParsed
(
parsedValue
)
setIndependentError
(
null
)
}
}
catch
{
setIndependentError
(
t
(
'
inputNotValid
'
))
}
return
()
=>
{
setIndependentValueParsed
()
setIndependentError
()
}
}
},
[
independentValue
,
independentDecimals
,
t
])
// calculate slippage from target rate
const
{
minimum
:
dependentValueMinumum
,
maximum
:
dependentValueMaximum
}
=
calculateSlippageBounds
(
dependentValue
,
swapType
===
TOKEN_TO_TOKEN
,
tokenAllowedSlippageBig
,
allowedSlippageBig
)
// validate input allowance + balance
const
[
inputError
,
setInputError
]
=
useState
()
const
[
showUnlock
,
setShowUnlock
]
=
useState
(
false
)
useEffect
(()
=>
{
const
inputValueCalculation
=
independentField
===
INPUT
?
independentValueParsed
:
dependentValueMaximum
if
(
inputBalance
&&
(
inputAllowance
||
inputCurrency
===
'
ETH
'
)
&&
inputValueCalculation
)
{
if
(
inputBalance
.
lt
(
inputValueCalculation
))
{
setInputError
(
t
(
'
insufficientBalance
'
))
}
else
if
(
inputCurrency
!==
'
ETH
'
&&
inputAllowance
.
lt
(
inputValueCalculation
))
{
setInputError
(
t
(
'
unlockTokenCont
'
))
setShowUnlock
(
true
)
}
else
{
setInputError
(
null
)
setShowUnlock
(
false
)
}
return
()
=>
{
setInputError
()
setShowUnlock
(
false
)
}
}
},
[
independentField
,
independentValueParsed
,
dependentValueMaximum
,
inputBalance
,
inputCurrency
,
inputAllowance
,
t
])
// calculate dependent value
useEffect
(()
=>
{
const
amount
=
independentValueParsed
if
(
swapType
===
ETH_TO_TOKEN
)
{
const
reserveETH
=
outputReserveETH
const
reserveToken
=
outputReserveToken
if
(
amount
&&
reserveETH
&&
reserveToken
)
{
try
{
const
calculatedDependentValue
=
independentField
===
INPUT
?
calculateEtherTokenOutputFromInput
(
amount
,
reserveETH
,
reserveToken
)
:
calculateEtherTokenInputFromOutput
(
amount
,
reserveETH
,
reserveToken
)
if
(
calculatedDependentValue
.
lte
(
ethers
.
constants
.
Zero
))
{
throw
Error
()
}
dispatchSwapState
({
type
:
'
UPDATE_DEPENDENT
'
,
payload
:
calculatedDependentValue
})
}
catch
{
setIndependentError
(
t
(
'
insufficientLiquidity
'
))
}
return
()
=>
{
dispatchSwapState
({
type
:
'
UPDATE_DEPENDENT
'
,
payload
:
''
})
}
}
}
else
if
(
swapType
===
TOKEN_TO_ETH
)
{
const
reserveETH
=
inputReserveETH
const
reserveToken
=
inputReserveToken
if
(
amount
&&
reserveETH
&&
reserveToken
)
{
try
{
const
calculatedDependentValue
=
independentField
===
INPUT
?
calculateEtherTokenOutputFromInput
(
amount
,
reserveToken
,
reserveETH
)
:
calculateEtherTokenInputFromOutput
(
amount
,
reserveToken
,
reserveETH
)
if
(
calculatedDependentValue
.
lte
(
ethers
.
constants
.
Zero
))
{
throw
Error
()
}
dispatchSwapState
({
type
:
'
UPDATE_DEPENDENT
'
,
payload
:
calculatedDependentValue
})
}
catch
{
setIndependentError
(
t
(
'
insufficientLiquidity
'
))
}
return
()
=>
{
dispatchSwapState
({
type
:
'
UPDATE_DEPENDENT
'
,
payload
:
''
})
}
}
}
else
if
(
swapType
===
TOKEN_TO_TOKEN
)
{
const
reserveETHFirst
=
inputReserveETH
const
reserveTokenFirst
=
inputReserveToken
const
reserveETHSecond
=
outputReserveETH
const
reserveTokenSecond
=
outputReserveToken
if
(
amount
&&
reserveETHFirst
&&
reserveTokenFirst
&&
reserveETHSecond
&&
reserveTokenSecond
)
{
try
{
if
(
independentField
===
INPUT
)
{
const
intermediateValue
=
calculateEtherTokenOutputFromInput
(
amount
,
reserveTokenFirst
,
reserveETHFirst
)
if
(
intermediateValue
.
lte
(
ethers
.
constants
.
Zero
))
{
throw
Error
()
}
const
calculatedDependentValue
=
calculateEtherTokenOutputFromInput
(
intermediateValue
,
reserveETHSecond
,
reserveTokenSecond
)
if
(
calculatedDependentValue
.
lte
(
ethers
.
constants
.
Zero
))
{
throw
Error
()
}
dispatchSwapState
({
type
:
'
UPDATE_DEPENDENT
'
,
payload
:
calculatedDependentValue
})
}
else
{
const
intermediateValue
=
calculateEtherTokenInputFromOutput
(
amount
,
reserveETHSecond
,
reserveTokenSecond
)
if
(
intermediateValue
.
lte
(
ethers
.
constants
.
Zero
))
{
throw
Error
()
}
const
calculatedDependentValue
=
calculateEtherTokenInputFromOutput
(
intermediateValue
,
reserveTokenFirst
,
reserveETHFirst
)
if
(
calculatedDependentValue
.
lte
(
ethers
.
constants
.
Zero
))
{
throw
Error
()
}
dispatchSwapState
({
type
:
'
UPDATE_DEPENDENT
'
,
payload
:
calculatedDependentValue
})
}
}
catch
{
setIndependentError
(
t
(
'
insufficientLiquidity
'
))
}
return
()
=>
{
dispatchSwapState
({
type
:
'
UPDATE_DEPENDENT
'
,
payload
:
''
})
}
}
}
},
[
independentValueParsed
,
swapType
,
outputReserveETH
,
outputReserveToken
,
inputReserveETH
,
inputReserveToken
,
independentField
,
t
])
const
[
inverted
,
setInverted
]
=
useState
(
false
)
const
exchangeRate
=
getExchangeRate
(
inputValueParsed
,
inputDecimals
,
outputValueParsed
,
outputDecimals
)
const
exchangeRateInverted
=
getExchangeRate
(
inputValueParsed
,
inputDecimals
,
outputValueParsed
,
outputDecimals
,
true
)
const
marketRate
=
getMarketRate
(
swapType
,
inputReserveETH
,
inputReserveToken
,
inputDecimals
,
outputReserveETH
,
outputReserveToken
,
outputDecimals
)
const
percentSlippage
=
exchangeRate
&&
marketRate
?
exchangeRate
.
sub
(
marketRate
)
.
abs
()
.
mul
(
ethers
.
utils
.
bigNumberify
(
10
).
pow
(
ethers
.
utils
.
bigNumberify
(
18
)))
.
div
(
marketRate
)
.
sub
(
ethers
.
utils
.
bigNumberify
(
3
).
mul
(
ethers
.
utils
.
bigNumberify
(
10
).
pow
(
ethers
.
utils
.
bigNumberify
(
15
))))
:
undefined
const
percentSlippageFormatted
=
percentSlippage
&&
amountFormatter
(
percentSlippage
,
16
,
2
)
const
slippageWarning
=
percentSlippage
&&
percentSlippage
.
gte
(
ethers
.
utils
.
parseEther
(
'
.05
'
))
&&
percentSlippage
.
lt
(
ethers
.
utils
.
parseEther
(
'
.2
'
))
// [5% - 20%)
const
highSlippageWarning
=
percentSlippage
&&
percentSlippage
.
gte
(
ethers
.
utils
.
parseEther
(
'
.2
'
))
// [20+%
const
isValid
=
sending
?
exchangeRate
&&
inputError
===
null
&&
independentError
===
null
&&
recipientError
===
null
:
exchangeRate
&&
inputError
===
null
&&
independentError
===
null
const
estimatedText
=
`(
${
t
(
'
estimated
'
)}
)`
function
formatBalance
(
value
)
{
return
`Balance:
${
value
}
`
}
async
function
onSwap
()
{
const
deadline
=
Math
.
ceil
(
Date
.
now
()
/
1000
)
+
DEADLINE_FROM_NOW
let
estimate
,
method
,
args
,
value
if
(
independentField
===
INPUT
)
{
ReactGA
.
event
({
category
:
`
${
swapType
}
`
,
action
:
sending
?
'
TransferInput
'
:
'
SwapInput
'
})
if
(
swapType
===
ETH_TO_TOKEN
)
{
estimate
=
sending
?
contract
.
estimate
.
ethToTokenTransferInput
:
contract
.
estimate
.
ethToTokenSwapInput
method
=
sending
?
contract
.
ethToTokenTransferInput
:
contract
.
ethToTokenSwapInput
args
=
sending
?
[
dependentValueMinumum
,
deadline
,
recipient
.
address
]
:
[
dependentValueMinumum
,
deadline
]
value
=
independentValueParsed
}
else
if
(
swapType
===
TOKEN_TO_ETH
)
{
estimate
=
sending
?
contract
.
estimate
.
tokenToEthTransferInput
:
contract
.
estimate
.
tokenToEthSwapInput
method
=
sending
?
contract
.
tokenToEthTransferInput
:
contract
.
tokenToEthSwapInput
args
=
sending
?
[
independentValueParsed
,
dependentValueMinumum
,
deadline
,
recipient
.
address
]
:
[
independentValueParsed
,
dependentValueMinumum
,
deadline
]
value
=
ethers
.
constants
.
Zero
}
else
if
(
swapType
===
TOKEN_TO_TOKEN
)
{
estimate
=
sending
?
contract
.
estimate
.
tokenToTokenTransferInput
:
contract
.
estimate
.
tokenToTokenSwapInput
method
=
sending
?
contract
.
tokenToTokenTransferInput
:
contract
.
tokenToTokenSwapInput
args
=
sending
?
[
independentValueParsed
,
dependentValueMinumum
,
ethers
.
constants
.
One
,
deadline
,
recipient
.
address
,
outputCurrency
]
:
[
independentValueParsed
,
dependentValueMinumum
,
ethers
.
constants
.
One
,
deadline
,
outputCurrency
]
value
=
ethers
.
constants
.
Zero
}
}
else
if
(
independentField
===
OUTPUT
)
{
ReactGA
.
event
({
category
:
`
${
swapType
}
`
,
action
:
sending
?
'
TransferOutput
'
:
'
SwapOutput
'
})
if
(
swapType
===
ETH_TO_TOKEN
)
{
estimate
=
sending
?
contract
.
estimate
.
ethToTokenTransferOutput
:
contract
.
estimate
.
ethToTokenSwapOutput
method
=
sending
?
contract
.
ethToTokenTransferOutput
:
contract
.
ethToTokenSwapOutput
args
=
sending
?
[
independentValueParsed
,
deadline
,
recipient
.
address
]
:
[
independentValueParsed
,
deadline
]
value
=
dependentValueMaximum
}
else
if
(
swapType
===
TOKEN_TO_ETH
)
{
estimate
=
sending
?
contract
.
estimate
.
tokenToEthTransferOutput
:
contract
.
estimate
.
tokenToEthSwapOutput
method
=
sending
?
contract
.
tokenToEthTransferOutput
:
contract
.
tokenToEthSwapOutput
args
=
sending
?
[
independentValueParsed
,
dependentValueMaximum
,
deadline
,
recipient
.
address
]
:
[
independentValueParsed
,
dependentValueMaximum
,
deadline
]
value
=
ethers
.
constants
.
Zero
}
else
if
(
swapType
===
TOKEN_TO_TOKEN
)
{
estimate
=
sending
?
contract
.
estimate
.
tokenToTokenTransferOutput
:
contract
.
estimate
.
tokenToTokenSwapOutput
method
=
sending
?
contract
.
tokenToTokenTransferOutput
:
contract
.
tokenToTokenSwapOutput
args
=
sending
?
[
independentValueParsed
,
dependentValueMaximum
,
ethers
.
constants
.
MaxUint256
,
deadline
,
recipient
.
address
,
outputCurrency
]
:
[
independentValueParsed
,
dependentValueMaximum
,
ethers
.
constants
.
MaxUint256
,
deadline
,
outputCurrency
]
value
=
ethers
.
constants
.
Zero
}
}
const
estimatedGasLimit
=
await
estimate
(...
args
,
{
value
})
method
(...
args
,
{
value
,
gasLimit
:
calculateGasMargin
(
estimatedGasLimit
,
GAS_MARGIN
)
}).
then
(
response
=>
{
addTransaction
(
response
)
})
}
const
[
customSlippageError
,
setcustomSlippageError
]
=
useState
(
''
)
return
(
<>
<
CurrencyInputPanel
title=
{
t
(
'
input
'
)
}
description=
{
inputValueFormatted
&&
independentField
===
OUTPUT
?
estimatedText
:
''
}
extraText=
{
inputBalanceFormatted
&&
formatBalance
(
inputBalanceFormatted
)
}
extraTextClickHander=
{
()
=>
{
if
(
inputBalance
&&
inputDecimals
)
{
const
valueToSet
=
inputCurrency
===
'
ETH
'
?
inputBalance
.
sub
(
ethers
.
utils
.
parseEther
(
'
.1
'
))
:
inputBalance
if
(
valueToSet
.
gt
(
ethers
.
constants
.
Zero
))
{
dispatchSwapState
({
type
:
'
UPDATE_INDEPENDENT
'
,
payload
:
{
value
:
amountFormatter
(
valueToSet
,
inputDecimals
,
inputDecimals
,
false
),
field
:
INPUT
}
})
}
}
}
}
onCurrencySelected=
{
inputCurrency
=>
{
dispatchSwapState
({
type
:
'
SELECT_CURRENCY
'
,
payload
:
{
currency
:
inputCurrency
,
field
:
INPUT
}
})
}
}
onValueChange=
{
inputValue
=>
{
dispatchSwapState
({
type
:
'
UPDATE_INDEPENDENT
'
,
payload
:
{
value
:
inputValue
,
field
:
INPUT
}
})
}
}
showUnlock=
{
showUnlock
}
selectedTokens=
{
[
inputCurrency
,
outputCurrency
]
}
selectedTokenAddress=
{
inputCurrency
}
value=
{
inputValueFormatted
}
errorMessage=
{
inputError
?
inputError
:
independentField
===
INPUT
?
independentError
:
''
}
/>
<
OversizedPanel
>
<
DownArrowBackground
>
<
DownArrow
onClick=
{
()
=>
{
dispatchSwapState
({
type
:
'
FLIP_INDEPENDENT
'
})
}
}
clickable
alt=
"swap"
src=
{
isValid
?
ArrowDownBlue
:
ArrowDownGrey
}
/>
</
DownArrowBackground
>
</
OversizedPanel
>
<
CurrencyInputPanel
title=
{
t
(
'
output
'
)
}
description=
{
outputValueFormatted
&&
independentField
===
INPUT
?
estimatedText
:
''
}
extraText=
{
outputBalanceFormatted
&&
formatBalance
(
outputBalanceFormatted
)
}
onCurrencySelected=
{
outputCurrency
=>
{
dispatchSwapState
({
type
:
'
SELECT_CURRENCY
'
,
payload
:
{
currency
:
outputCurrency
,
field
:
OUTPUT
}
})
}
}
onValueChange=
{
outputValue
=>
{
dispatchSwapState
({
type
:
'
UPDATE_INDEPENDENT
'
,
payload
:
{
value
:
outputValue
,
field
:
OUTPUT
}
})
}
}
selectedTokens=
{
[
inputCurrency
,
outputCurrency
]
}
selectedTokenAddress=
{
outputCurrency
}
value=
{
outputValueFormatted
}
errorMessage=
{
independentField
===
OUTPUT
?
independentError
:
''
}
disableUnlock
/>
{
sending
?
(
<>
<
OversizedPanel
>
<
DownArrowBackground
>
<
DownArrow
src=
{
isValid
?
ArrowDownBlue
:
ArrowDownGrey
}
alt=
"arrow"
/>
</
DownArrowBackground
>
</
OversizedPanel
>
<
AddressInputPanel
onChange=
{
setRecipient
}
onError=
{
setRecipientError
}
/>
</>
)
:
(
''
)
}
<
OversizedPanel
hideBottom
>
<
ExchangeRateWrapper
onClick=
{
()
=>
{
setInverted
(
inverted
=>
!
inverted
)
}
}
>
<
ExchangeRate
>
{
t
(
'
exchangeRate
'
)
}
</
ExchangeRate
>
{
inverted
?
(
<
span
>
{
exchangeRate
?
`1 ${outputSymbol} = ${amountFormatter(exchangeRateInverted, 18, 4, false)} ${inputSymbol}`
:
'
-
'
}
</
span
>
)
:
(
<
span
>
{
exchangeRate
?
`1 ${inputSymbol} = ${amountFormatter(exchangeRate, 18, 4, false)} ${outputSymbol}`
:
'
-
'
}
</
span
>
)
}
</
ExchangeRateWrapper
>
</
OversizedPanel
>
<
TransactionDetails
account=
{
account
}
setRawSlippage=
{
setRawSlippage
}
setRawTokenSlippage=
{
setRawTokenSlippage
}
rawSlippage=
{
rawSlippage
}
slippageWarning=
{
slippageWarning
}
highSlippageWarning=
{
highSlippageWarning
}
inputError=
{
inputError
}
independentError=
{
independentError
}
inputCurrency=
{
inputCurrency
}
outputCurrency=
{
outputCurrency
}
independentValue=
{
independentValue
}
independentValueParsed=
{
independentValueParsed
}
independentField=
{
independentField
}
INPUT=
{
INPUT
}
inputValueParsed=
{
inputValueParsed
}
outputValueParsed=
{
outputValueParsed
}
inputSymbol=
{
inputSymbol
}
outputSymbol=
{
outputSymbol
}
dependentValueMinumum=
{
dependentValueMinumum
}
dependentValueMaximum=
{
dependentValueMaximum
}
dependentDecimals=
{
dependentDecimals
}
independentDecimals=
{
independentDecimals
}
percentSlippageFormatted=
{
percentSlippageFormatted
}
setcustomSlippageError=
{
setcustomSlippageError
}
recipientAddress=
{
recipient
.
address
}
sending=
{
sending
}
/>
<
Flex
>
<
Button
disabled=
{
!
isValid
||
customSlippageError
===
'
invalid
'
}
onClick=
{
onSwap
}
warning=
{
highSlippageWarning
||
customSlippageError
===
'
warning
'
}
>
{
sending
?
highSlippageWarning
||
customSlippageError
===
'
warning
'
?
t
(
'
sendAnyway
'
)
:
t
(
'
send
'
)
:
highSlippageWarning
||
customSlippageError
===
'
warning
'
?
t
(
'
swapAnyway
'
)
:
t
(
'
swap
'
)
}
</
Button
>
</
Flex
>
</>
)
}
src/components/Modal/index.js
View file @
3f3889b9
...
@@ -6,7 +6,7 @@ import '@reach/dialog/styles.css'
...
@@ -6,7 +6,7 @@ import '@reach/dialog/styles.css'
const
AnimatedDialogOverlay
=
animated
(
DialogOverlay
)
const
AnimatedDialogOverlay
=
animated
(
DialogOverlay
)
const
StyledDialogOverlay
=
styled
(
AnimatedDialogOverlay
).
attrs
({
const
StyledDialogOverlay
=
styled
(
AnimatedDialogOverlay
).
attrs
({
suppress
ClassNameWarning
:
true
suppress
classnamewarning
:
'
true
'
})
`
})
`
&[data-reach-dialog-overlay] {
&[data-reach-dialog-overlay] {
z-index: 2;
z-index: 2;
...
...
src/components/TransactionDetails/index.js
0 → 100644
View file @
3f3889b9
import
React
,
{
useState
}
from
'
react
'
import
ReactGA
from
'
react-ga
'
import
{
useTranslation
}
from
'
react-i18next
'
import
styled
from
'
styled-components
'
import
{
isAddress
,
amountFormatter
}
from
'
../../utils
'
import
questionMark
from
'
../../assets/images/question-mark.svg
'
import
NewContextualInfo
from
'
../../components/ContextualInfoNew
'
const
Flex
=
styled
.
div
`
display: flex;
justify-content: center;
button {
max-width: 20rem;
}
`
const
SlippageRow
=
styled
(
Flex
)
`
position: relative;
width: 100%;
flex-direction: row;
justify-content: flex-start;
align-items: center;
font-size: 0.8rem;
padding: 0;
height: 24px;
margin-bottom: 14px;
`
const
QuestionWrapper
=
styled
.
div
`
margin-left: 0.4rem;
margin-top: 0.2rem;
&:hover {
cursor: pointer;
}
`
const
Popup
=
styled
(
Flex
)
`
position: absolute;
width: 228px;
left: -78px;
top: -124px;
flex-direction: column;
aligm-items: center;
padding: 1rem;
line-height: 183.52%;
background: #404040;
border-radius: 8px;
color: white;
font-style: italic;
`
const
Option
=
styled
(
Flex
)
`
align-items: center;
min-width: 55px;
height: 24px;
margin-right: 4px;
border-radius: 36px;
border: 1px solid #f2f2f2;
${({
active
})
=>
active
&&
`
background-color: #2f80ed;
color: white;
border: 1px solid #2f80ed;
`
}
&:hover {
cursor: pointer;
}
`
const
Input
=
styled
.
input
`
width: 123.27px;
background: #ffffff;
height: 2rem;
outline: none;
margin-left: 20px;
border: 1px solid #f2f2f2;
box-sizing: border-box;
border-radius: 36px;
color: #aeaeae;
&:focus {
}
text-align: left;
padding-left: 0.9rem;
&::-webkit-outer-spin-button,
&::-webkit-inner-spin-button {
-webkit-appearance: none;
}
${({
active
})
=>
active
&&
`
border: 1px solid #2f80ed;
text-align: right;
padding-right 1.5rem;
padding-left 0rem;
color : inherit;
`
}
${({
warning
})
=>
warning
===
'
red
'
&&
`
color : #FF6871;
border: 1px solid #FF6871;
`
}
`
const
BottomError
=
styled
.
div
`
margin-top: 1rem;
color: #aeaeae;
${({
color
})
=>
color
===
'
red
'
&&
`
color : #FF6871;
`
}
`
const
Break
=
styled
.
div
`
border: 1px solid #f2f2f2;
width: 100%;
margin-top: 1rem;
`
const
OptionLarge
=
styled
(
Option
)
`
width: 120px;
`
const
Bold
=
styled
.
span
`
font-weight: 500;
`
const
LastSummaryText
=
styled
.
div
`
margin-top: 0.6rem;
`
const
SlippageSelector
=
styled
.
div
`
margin-top: 1rem;
`
const
InputGroup
=
styled
.
div
`
position: relative;
`
const
Percent
=
styled
.
div
`
right: 14px;
top: 9px;
position: absolute;
color: inherit;
font-size: 0, 8rem;
${({
color
})
=>
(
color
===
'
faded
'
&&
`
color : #AEAEAE
`
)
||
(
color
===
'
red
'
&&
`
color : #FF6871
`
)}
`
const
Faded
=
styled
.
span
`
opacity: 0.7;
`
const
ErrorEmoji
=
styled
.
span
`
left: 30px;
top: 4px;
position: absolute;
`
export
default
function
TransactionDetails
(
props
)
{
const
{
t
}
=
useTranslation
()
function
renderSummary
()
{
let
contextualInfo
=
''
let
isError
=
false
if
(
props
.
inputError
||
props
.
independentError
)
{
contextualInfo
=
props
.
inputError
||
props
.
independentError
isError
=
true
}
else
if
(
!
props
.
inputCurrency
||
!
props
.
outputCurrency
)
{
contextualInfo
=
t
(
'
selectTokenCont
'
)
}
else
if
(
!
props
.
independentValue
)
{
contextualInfo
=
t
(
'
enterValueCont
'
)
}
else
if
(
props
.
sending
&&
!
props
.
recipientAddress
)
{
contextualInfo
=
t
(
'
noRecipient
'
)
}
else
if
(
props
.
sending
&&
!
isAddress
(
props
.
recipientAddress
))
{
contextualInfo
=
t
(
'
invalidRecipient
'
)
}
else
if
(
!
props
.
account
)
{
contextualInfo
=
t
(
'
noWallet
'
)
isError
=
true
}
const
slippageWarningText
=
props
.
highSlippageWarning
?
t
(
'
highSlippageWarning
'
)
:
props
.
slippageWarning
?
t
(
'
slippageWarning
'
)
:
''
return
(
<
NewContextualInfo
openDetailsText
=
{
t
(
'
transactionDetails
'
)}
closeDetailsText
=
{
t
(
'
hideDetails
'
)}
contextualInfo
=
{
contextualInfo
?
contextualInfo
:
slippageWarningText
}
allowExpand
=
{
!!
(
props
.
inputCurrency
&&
props
.
outputCurrency
&&
props
.
inputValueParsed
&&
props
.
outputValueParsed
&&
(
props
.
sending
?
props
.
recipientAddress
:
true
)
)
}
isError
=
{
isError
}
slippageWarning
=
{
props
.
slippageWarning
&&
!
contextualInfo
}
highSlippageWarning
=
{
props
.
highSlippageWarning
&&
!
contextualInfo
}
renderTransactionDetails
=
{
renderTransactionDetails
}
dropDownContent
=
{
dropDownContent
}
/
>
)
}
const
[
activeIndex
,
setActiveIndex
]
=
useState
(
3
)
const
[
placeHolder
,
setplaceHolder
]
=
useState
(
'
Custom
'
)
const
[
warningType
,
setWarningType
]
=
useState
(
'
none
'
)
const
[
showPopup
,
setPopup
]
=
useState
(
false
)
const
dropDownContent
=
()
=>
{
return
(
<>
{
renderTransactionDetails
()}
<
Break
/>
<
SlippageSelector
>
<
SlippageRow
>
Limit
addtional
price
slippage
<
QuestionWrapper
onMouseEnter
=
{()
=>
{
setPopup
(
true
)
}}
onMouseLeave
=
{()
=>
{
setPopup
(
false
)
}}
>
<
img
src
=
{
questionMark
}
alt
=
"
question mark
"
/>
<
/QuestionWrapper
>
{
showPopup
?
(
<
Popup
>
Lowering
this
limit
decreases
your
risk
of
frontrunning
.
This
makes
it
more
likely
that
your
transaction
will
fail
due
to
normal
price
movements
.
<
/Popup
>
)
:
(
''
)}
<
/SlippageRow
>
<
SlippageRow
>
<
Option
onClick
=
{()
=>
{
updateSlippage
(
0.1
)
setWarningType
(
'
none
'
)
setActiveIndex
(
1
)
props
.
setcustomSlippageError
(
'
valid
'
)
setplaceHolder
(
'
Custom
'
)
}}
active
=
{
activeIndex
===
1
?
true
:
false
}
>
0.1
%
<
/Option
>
<
Option
onClick
=
{()
=>
{
updateSlippage
(
1
)
setWarningType
(
'
none
'
)
setActiveIndex
(
2
)
props
.
setcustomSlippageError
(
'
valid
'
)
setplaceHolder
(
'
Custom
'
)
}}
active
=
{
activeIndex
===
2
?
true
:
false
}
>
1
%
<
/Option
>
<
OptionLarge
onClick
=
{()
=>
{
updateSlippage
(
2
)
setWarningType
(
'
none
'
)
setActiveIndex
(
3
)
props
.
setcustomSlippageError
(
'
valid
'
)
setplaceHolder
(
'
Custom
'
)
}}
active
=
{
activeIndex
===
3
?
true
:
false
}
>
2
%
<
Faded
>
(
suggested
)
<
/Faded
>
<
/OptionLarge
>
<
InputGroup
>
{
warningType
!==
'
none
'
?
<
ErrorEmoji
>
⚠️
<
/ErrorEmoji> : ''
}
<
Input
placeholder
=
{
placeHolder
}
value
=
{
userInput
||
''
}
onChange
=
{
parseInput
}
onClick
=
{
e
=>
{
setActiveIndex
(
4
)
setplaceHolder
(
''
)
parseInput
(
e
)
}}
active
=
{
activeIndex
===
4
?
true
:
false
}
warning
=
{
warningType
===
'
emptyInput
'
?
''
:
warningType
!==
'
none
'
&&
warningType
!==
'
riskyEntryLow
'
?
'
red
'
:
''
}
/
>
<
Percent
color
=
{
warningType
===
'
emptyInput
'
?
'
faded
'
:
warningType
!==
'
none
'
&&
warningType
!==
'
riskyEntryLow
'
?
'
red
'
:
activeIndex
!==
4
?
'
faded
'
:
''
}
>
%
<
/Percent
>
<
/InputGroup
>
<
/SlippageRow
>
<
SlippageRow
>
<
BottomError
color
=
{
warningType
===
'
emptyInput
'
?
''
:
warningType
!==
'
none
'
&&
warningType
!==
'
riskyEntryLow
'
?
'
red
'
:
''
}
>
{
warningType
===
'
emptyInput
'
?
'
Enter a slippage percentage.
'
:
''
}
{
warningType
===
'
invalidEntry
'
?
'
Please input a valid percentage.
'
:
''
}
{
warningType
===
'
invalidEntryBound
'
?
'
Pleae select value less than 50%
'
:
''
}
{
warningType
===
'
riskyEntryHigh
'
?
'
Your transaction may be frontrun.
'
:
''
}
{
warningType
===
'
riskyEntryLow
'
?
'
Your transaction may fail.
'
:
''
}
<
/BottomError
>
<
/SlippageRow
>
<
/SlippageSelector
>
<
/
>
)
}
const
[
userInput
,
setUserInput
]
=
useState
()
const
parseInput
=
e
=>
{
let
input
=
e
.
target
.
value
if
(
input
===
''
)
{
setUserInput
(
input
)
props
.
setcustomSlippageError
(
'
invalid
'
)
return
setWarningType
(
'
emptyInput
'
)
}
//check for decimal
let
isValid
=
/^
[
+
]?\d
*
\.?\d{1,2}
$/
.
test
(
input
)
||
/^
[
+
]?\d
*
\.
$/
.
test
(
input
)
let
decimalLimit
=
/^
\d
+
\.?\d{0,2}
$/
.
test
(
input
)
||
input
===
''
if
(
decimalLimit
)
{
setUserInput
(
input
)
}
else
{
return
}
if
(
isValid
)
{
checkAcceptablePercentValue
(
input
)
}
else
{
setWarningType
(
'
invalidEntry
'
)
}
}
const
checkAcceptablePercentValue
=
input
=>
{
setTimeout
(
function
()
{
setWarningType
(
'
none
'
)
props
.
setcustomSlippageError
(
'
valid
'
)
if
(
input
<
0
||
input
>
50
)
{
props
.
setcustomSlippageError
(
'
invalid
'
)
return
setWarningType
(
'
invalidEntryBound
'
)
}
if
(
input
>=
0
&&
input
<
0.1
)
{
props
.
setcustomSlippageError
(
'
valid
'
)
setWarningType
(
'
riskyEntryLow
'
)
}
if
(
input
>=
5
)
{
props
.
setcustomSlippageError
(
'
warning
'
)
setWarningType
(
'
riskyEntryHigh
'
)
}
updateSlippage
(
input
)
},
300
)
}
const
updateSlippage
=
newSlippage
=>
{
let
numParsed
=
parseFloat
((
newSlippage
*
100
).
toFixed
(
2
))
props
.
setRawSlippage
(
numParsed
)
props
.
setRawTokenSlippage
(
numParsed
)
}
const
b
=
text
=>
<
Bold
>
{
text
}
<
/Bold
>
const
renderTransactionDetails
=
()
=>
{
ReactGA
.
event
({
category
:
'
TransactionDetail
'
,
action
:
'
Open
'
})
if
(
props
.
independentField
===
props
.
INPUT
)
{
return
props
.
sending
?
(
<
div
>
<
div
>
{
t
(
'
youAreSelling
'
)}{
'
'
}
{
b
(
`
${
amountFormatter
(
props
.
independentValueParsed
,
props
.
independentDecimals
,
Math
.
min
(
4
,
props
.
independentDecimals
)
)}
${
props
.
inputSymbol
}
`
)}
.
<
/div
>
<
LastSummaryText
>
{
b
(
props
.
recipientAddress
)}
{
t
(
'
willReceive
'
)}{
'
'
}
{
b
(
`
${
amountFormatter
(
props
.
dependentValueMinumum
,
props
.
dependentDecimals
,
Math
.
min
(
4
,
props
.
dependentDecimals
)
)}
${
props
.
outputSymbol
}
`
)}{
'
'
}
<
/LastSummaryText
>
<
LastSummaryText
>
{
t
(
'
priceChange
'
)}
{
b
(
`
${
props
.
percentSlippageFormatted
}
%`
)}.
<
/LastSummaryText
>
<
/div
>
)
:
(
<
div
>
<
div
>
{
t
(
'
youAreSelling
'
)}{
'
'
}
{
b
(
`
${
amountFormatter
(
props
.
independentValueParsed
,
props
.
independentDecimals
,
Math
.
min
(
4
,
props
.
independentDecimals
)
)}
${
props
.
inputSymbol
}
`
)}{
'
'
}
{
t
(
'
forAtLeast
'
)}
{
b
(
`
${
amountFormatter
(
props
.
dependentValueMinumum
,
props
.
dependentDecimals
,
Math
.
min
(
4
,
props
.
dependentDecimals
)
)}
${
props
.
outputSymbol
}
`
)}
.
<
/div
>
<
LastSummaryText
>
{
t
(
'
priceChange
'
)}
{
b
(
`
${
props
.
percentSlippageFormatted
}
%`
)}.
<
/LastSummaryText
>
<
/div
>
)
}
else
{
return
props
.
sending
?
(
<
div
>
<
div
>
{
t
(
'
youAreSending
'
)}{
'
'
}
{
b
(
`
${
amountFormatter
(
props
.
independentValueParsed
,
props
.
independentDecimals
,
Math
.
min
(
4
,
props
.
independentDecimals
)
)}
${
props
.
outputSymbol
}
`
)}{
'
'
}
{
t
(
'
to
'
)}
{
b
(
props
.
recipientAddress
)}.
<
/div
>
<
LastSummaryText
>
{
t
(
'
itWillCost
'
)}{
'
'
}
{
b
(
`
${
amountFormatter
(
props
.
dependentValueMaximum
,
props
.
dependentDecimals
,
Math
.
min
(
4
,
props
.
dependentDecimals
)
)}
${
props
.
inputSymbol
}
`
)}{
'
'
}
<
/LastSummaryText
>
<
LastSummaryText
>
{
t
(
'
priceChange
'
)}
{
b
(
`
${
props
.
percentSlippageFormatted
}
%`
)}.
<
/LastSummaryText
>
<
/div
>
)
:
(
<
div
>
<
div
>
{
t
(
'
youAreBuying
'
)}{
'
'
}
{
b
(
`
${
amountFormatter
(
props
.
independentValueParsed
,
props
.
independentDecimals
,
Math
.
min
(
4
,
props
.
independentDecimals
)
)}
${
props
.
outputSymbol
}
`
)}
.
<
/div
>
<
LastSummaryText
>
{
t
(
'
itWillCost
'
)}{
'
'
}
{
b
(
`
${
amountFormatter
(
props
.
dependentValueMaximum
,
props
.
dependentDecimals
,
Math
.
min
(
4
,
props
.
dependentDecimals
)
)}
${
props
.
inputSymbol
}
`
)}{
'
'
}
<
/LastSummaryText
>
<
LastSummaryText
>
{
t
(
'
priceChange
'
)}
{
b
(
`
${
props
.
percentSlippageFormatted
}
%`
)}.
<
/LastSummaryText
>
<
/div
>
)
}
}
return
<>
{
renderSummary
()}
<
/
>
}
src/pages/App.js
View file @
3f3889b9
...
@@ -46,7 +46,7 @@ export default function App() {
...
@@ -46,7 +46,7 @@ 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
/>
}
/
>
<
Route
<
Route
exact
exact
strict
strict
...
@@ -59,7 +59,7 @@ export default function App() {
...
@@ -59,7 +59,7 @@ export default function App() {
}
}
}}
}}
/
>
/
>
<
Route
exact
strict
path
=
"
/send
"
component
=
{
Send
}
/
>
<
Route
exact
strict
path
=
"
/send
"
component
=
{
()
=>
<
Send
/>
}
/
>
<
Route
<
Route
exact
exact
strict
strict
...
...
src/pages/Send/index.js
View file @
3f3889b9
import
React
,
{
useState
,
useReducer
,
useEffect
}
from
'
react
'
import
React
from
'
react
'
import
ReactGA
from
'
react-ga
'
import
ExchangePage
from
'
../../components/ExchangePage
'
import
{
useTranslation
}
from
'
react-i18next
'
import
{
useWeb3Context
}
from
'
web3-react
'
import
{
ethers
}
from
'
ethers
'
import
styled
from
'
styled-components
'
import
{
Button
}
from
'
../../theme
'
export
default
function
Send
({
initialCurrency
})
{
import
CurrencyInputPanel
from
'
../../components/CurrencyInputPanel
'
return
<
ExchangePage
initialCurrency
=
{
initialCurrency
}
sending
=
{
true
}
/
>
import
NewContextualInfo
from
'
../../components/ContextualInfoNew
'
import
OversizedPanel
from
'
../../components/OversizedPanel
'
import
AddressInputPanel
from
'
../../components/AddressInputPanel
'
import
ArrowDownBlue
from
'
../../assets/images/arrow-down-blue.svg
'
import
ArrowDownGrey
from
'
../../assets/images/arrow-down-grey.svg
'
import
{
isAddress
,
amountFormatter
,
calculateGasMargin
}
from
'
../../utils
'
import
{
useExchangeContract
}
from
'
../../hooks
'
import
{
useTokenDetails
}
from
'
../../contexts/Tokens
'
import
{
useTransactionAdder
}
from
'
../../contexts/Transactions
'
import
{
useAddressBalance
,
useExchangeReserves
}
from
'
../../contexts/Balances
'
import
{
useAddressAllowance
}
from
'
../../contexts/Allowances
'
const
INPUT
=
0
const
OUTPUT
=
1
const
ETH_TO_TOKEN
=
0
const
TOKEN_TO_ETH
=
1
const
TOKEN_TO_TOKEN
=
2
// denominated in bips
const
ALLOWED_SLIPPAGE
=
ethers
.
utils
.
bigNumberify
(
200
)
const
TOKEN_ALLOWED_SLIPPAGE
=
ethers
.
utils
.
bigNumberify
(
400
)
// denominated in seconds
const
DEADLINE_FROM_NOW
=
60
*
15
// denominated in bips
const
GAS_MARGIN
=
ethers
.
utils
.
bigNumberify
(
1000
)
const
BlueSpan
=
styled
.
span
`
color:
${({
theme
})
=>
theme
.
royalBlue
}
;
`
const
DownArrowBackground
=
styled
.
div
`
${({
theme
})
=>
theme
.
flexRowNoWrap
}
justify-content: center;
align-items: center;
`
const
DownArrow
=
styled
.
img
`
width: 0.625rem;
height: 0.625rem;
position: relative;
padding: 0.875rem;
cursor:
${({
clickable
})
=>
clickable
&&
'
pointer
'
}
;
`
const
LastSummaryText
=
styled
.
div
`
margin-top: 1rem;
`
const
ExchangeRateWrapper
=
styled
.
div
`
${({
theme
})
=>
theme
.
flexRowNoWrap
}
;
align-items: center;
color:
${({
theme
})
=>
theme
.
doveGray
}
;
font-size: 0.75rem;
padding: 0.5rem 1rem;
`
const
ExchangeRate
=
styled
.
span
`
flex: 1 1 auto;
width: 0;
color:
${({
theme
})
=>
theme
.
chaliceGray
}
;
`
const
Flex
=
styled
.
div
`
display: flex;
justify-content: center;
padding: 2rem;
button {
max-width: 20rem;
}
`
function
calculateSlippageBounds
(
value
,
token
=
false
)
{
if
(
value
)
{
const
offset
=
value
.
mul
(
token
?
TOKEN_ALLOWED_SLIPPAGE
:
ALLOWED_SLIPPAGE
).
div
(
ethers
.
utils
.
bigNumberify
(
10000
))
const
minimum
=
value
.
sub
(
offset
)
const
maximum
=
value
.
add
(
offset
)
return
{
minimum
:
minimum
.
lt
(
ethers
.
constants
.
Zero
)
?
ethers
.
constants
.
Zero
:
minimum
,
maximum
:
maximum
.
gt
(
ethers
.
constants
.
MaxUint256
)
?
ethers
.
constants
.
MaxUint256
:
maximum
}
}
else
{
return
{}
}
}
function
getSwapType
(
inputCurrency
,
outputCurrency
)
{
if
(
!
inputCurrency
||
!
outputCurrency
)
{
return
null
}
else
if
(
inputCurrency
===
'
ETH
'
)
{
return
ETH_TO_TOKEN
}
else
if
(
outputCurrency
===
'
ETH
'
)
{
return
TOKEN_TO_ETH
}
else
{
return
TOKEN_TO_TOKEN
}
}
// this mocks the getInputPrice function, and calculates the required output
function
calculateEtherTokenOutputFromInput
(
inputAmount
,
inputReserve
,
outputReserve
)
{
const
inputAmountWithFee
=
inputAmount
.
mul
(
ethers
.
utils
.
bigNumberify
(
997
))
const
numerator
=
inputAmountWithFee
.
mul
(
outputReserve
)
const
denominator
=
inputReserve
.
mul
(
ethers
.
utils
.
bigNumberify
(
1000
)).
add
(
inputAmountWithFee
)
return
numerator
.
div
(
denominator
)
}
// this mocks the getOutputPrice function, and calculates the required input
function
calculateEtherTokenInputFromOutput
(
outputAmount
,
inputReserve
,
outputReserve
)
{
const
numerator
=
inputReserve
.
mul
(
outputAmount
).
mul
(
ethers
.
utils
.
bigNumberify
(
1000
))
const
denominator
=
outputReserve
.
sub
(
outputAmount
).
mul
(
ethers
.
utils
.
bigNumberify
(
997
))
return
numerator
.
div
(
denominator
).
add
(
ethers
.
constants
.
One
)
}
function
getInitialSwapState
(
outputCurrency
)
{
return
{
independentValue
:
''
,
// this is a user input
dependentValue
:
''
,
// this is a calculated number
independentField
:
INPUT
,
inputCurrency
:
'
ETH
'
,
outputCurrency
:
outputCurrency
?
outputCurrency
:
''
}
}
function
swapStateReducer
(
state
,
action
)
{
switch
(
action
.
type
)
{
case
'
FLIP_INDEPENDENT
'
:
{
const
{
independentField
,
inputCurrency
,
outputCurrency
}
=
state
return
{
...
state
,
dependentValue
:
''
,
independentField
:
independentField
===
INPUT
?
OUTPUT
:
INPUT
,
inputCurrency
:
outputCurrency
,
outputCurrency
:
inputCurrency
}
}
case
'
SELECT_CURRENCY
'
:
{
const
{
inputCurrency
,
outputCurrency
}
=
state
const
{
field
,
currency
}
=
action
.
payload
const
newInputCurrency
=
field
===
INPUT
?
currency
:
inputCurrency
const
newOutputCurrency
=
field
===
OUTPUT
?
currency
:
outputCurrency
if
(
newInputCurrency
===
newOutputCurrency
)
{
return
{
...
state
,
inputCurrency
:
field
===
INPUT
?
currency
:
''
,
outputCurrency
:
field
===
OUTPUT
?
currency
:
''
}
}
else
{
return
{
...
state
,
inputCurrency
:
newInputCurrency
,
outputCurrency
:
newOutputCurrency
}
}
}
case
'
UPDATE_INDEPENDENT
'
:
{
const
{
field
,
value
}
=
action
.
payload
return
{
...
state
,
independentValue
:
value
,
dependentValue
:
''
,
independentField
:
field
}
}
case
'
UPDATE_DEPENDENT
'
:
{
return
{
...
state
,
dependentValue
:
action
.
payload
}
}
default
:
{
return
getInitialSwapState
()
}
}
}
function
getExchangeRate
(
inputValue
,
inputDecimals
,
outputValue
,
outputDecimals
,
invert
=
false
)
{
try
{
if
(
inputValue
&&
(
inputDecimals
||
inputDecimals
===
0
)
&&
outputValue
&&
(
outputDecimals
||
outputDecimals
===
0
)
)
{
const
factor
=
ethers
.
utils
.
bigNumberify
(
10
).
pow
(
ethers
.
utils
.
bigNumberify
(
18
))
if
(
invert
)
{
return
inputValue
.
mul
(
factor
)
.
div
(
outputValue
)
.
mul
(
ethers
.
utils
.
bigNumberify
(
10
).
pow
(
ethers
.
utils
.
bigNumberify
(
outputDecimals
)))
.
div
(
ethers
.
utils
.
bigNumberify
(
10
).
pow
(
ethers
.
utils
.
bigNumberify
(
inputDecimals
)))
}
else
{
return
outputValue
.
mul
(
factor
)
.
div
(
inputValue
)
.
mul
(
ethers
.
utils
.
bigNumberify
(
10
).
pow
(
ethers
.
utils
.
bigNumberify
(
inputDecimals
)))
.
div
(
ethers
.
utils
.
bigNumberify
(
10
).
pow
(
ethers
.
utils
.
bigNumberify
(
outputDecimals
)))
}
}
}
catch
{}
}
function
getMarketRate
(
swapType
,
inputReserveETH
,
inputReserveToken
,
inputDecimals
,
outputReserveETH
,
outputReserveToken
,
outputDecimals
,
invert
=
false
)
{
if
(
swapType
===
ETH_TO_TOKEN
)
{
return
getExchangeRate
(
outputReserveETH
,
18
,
outputReserveToken
,
outputDecimals
,
invert
)
}
else
if
(
swapType
===
TOKEN_TO_ETH
)
{
return
getExchangeRate
(
inputReserveToken
,
inputDecimals
,
inputReserveETH
,
18
,
invert
)
}
else
if
(
swapType
===
TOKEN_TO_TOKEN
)
{
const
factor
=
ethers
.
utils
.
bigNumberify
(
10
).
pow
(
ethers
.
utils
.
bigNumberify
(
18
))
const
firstRate
=
getExchangeRate
(
inputReserveToken
,
inputDecimals
,
inputReserveETH
,
18
)
const
secondRate
=
getExchangeRate
(
outputReserveETH
,
18
,
outputReserveToken
,
outputDecimals
)
try
{
return
!!
(
firstRate
&&
secondRate
)
?
firstRate
.
mul
(
secondRate
).
div
(
factor
)
:
undefined
}
catch
{}
}
}
export
default
function
Swap
({
initialCurrency
})
{
const
{
t
}
=
useTranslation
()
const
{
account
}
=
useWeb3Context
()
const
addTransaction
=
useTransactionAdder
()
// analytics
useEffect
(()
=>
{
ReactGA
.
pageview
(
window
.
location
.
pathname
+
window
.
location
.
search
)
},
[])
// core swap state
const
[
swapState
,
dispatchSwapState
]
=
useReducer
(
swapStateReducer
,
initialCurrency
,
getInitialSwapState
)
const
{
independentValue
,
dependentValue
,
independentField
,
inputCurrency
,
outputCurrency
}
=
swapState
const
[
recipient
,
setRecipient
]
=
useState
({
address
:
''
,
name
:
''
})
const
[
recipientError
,
setRecipientError
]
=
useState
()
// get swap type from the currency types
const
swapType
=
getSwapType
(
inputCurrency
,
outputCurrency
)
// get decimals and exchange addressfor each of the currency types
const
{
symbol
:
inputSymbol
,
decimals
:
inputDecimals
,
exchangeAddress
:
inputExchangeAddress
}
=
useTokenDetails
(
inputCurrency
)
const
{
symbol
:
outputSymbol
,
decimals
:
outputDecimals
,
exchangeAddress
:
outputExchangeAddress
}
=
useTokenDetails
(
outputCurrency
)
const
inputExchangeContract
=
useExchangeContract
(
inputExchangeAddress
)
const
outputExchangeContract
=
useExchangeContract
(
outputExchangeAddress
)
const
contract
=
swapType
===
ETH_TO_TOKEN
?
outputExchangeContract
:
inputExchangeContract
// get input allowance
const
inputAllowance
=
useAddressAllowance
(
account
,
inputCurrency
,
inputExchangeAddress
)
// fetch reserves for each of the currency types
const
{
reserveETH
:
inputReserveETH
,
reserveToken
:
inputReserveToken
}
=
useExchangeReserves
(
inputCurrency
)
const
{
reserveETH
:
outputReserveETH
,
reserveToken
:
outputReserveToken
}
=
useExchangeReserves
(
outputCurrency
)
// get balances for each of the currency types
const
inputBalance
=
useAddressBalance
(
account
,
inputCurrency
)
const
outputBalance
=
useAddressBalance
(
account
,
outputCurrency
)
const
inputBalanceFormatted
=
!!
(
inputBalance
&&
Number
.
isInteger
(
inputDecimals
))
?
amountFormatter
(
inputBalance
,
inputDecimals
,
Math
.
min
(
4
,
inputDecimals
))
:
''
const
outputBalanceFormatted
=
!!
(
outputBalance
&&
Number
.
isInteger
(
outputDecimals
))
?
amountFormatter
(
outputBalance
,
outputDecimals
,
Math
.
min
(
4
,
outputDecimals
))
:
''
// compute useful transforms of the data above
const
independentDecimals
=
independentField
===
INPUT
?
inputDecimals
:
outputDecimals
const
dependentDecimals
=
independentField
===
OUTPUT
?
inputDecimals
:
outputDecimals
// declare/get parsed and formatted versions of input/output values
const
[
independentValueParsed
,
setIndependentValueParsed
]
=
useState
()
const
dependentValueFormatted
=
!!
(
dependentValue
&&
(
dependentDecimals
||
dependentDecimals
===
0
))
?
amountFormatter
(
dependentValue
,
dependentDecimals
,
Math
.
min
(
4
,
dependentDecimals
),
false
)
:
''
const
inputValueParsed
=
independentField
===
INPUT
?
independentValueParsed
:
dependentValue
const
inputValueFormatted
=
independentField
===
INPUT
?
independentValue
:
dependentValueFormatted
const
outputValueParsed
=
independentField
===
OUTPUT
?
independentValueParsed
:
dependentValue
const
outputValueFormatted
=
independentField
===
OUTPUT
?
independentValue
:
dependentValueFormatted
// validate + parse independent value
const
[
independentError
,
setIndependentError
]
=
useState
()
useEffect
(()
=>
{
if
(
independentValue
&&
(
independentDecimals
||
independentDecimals
===
0
))
{
try
{
const
parsedValue
=
ethers
.
utils
.
parseUnits
(
independentValue
,
independentDecimals
)
if
(
parsedValue
.
lte
(
ethers
.
constants
.
Zero
)
||
parsedValue
.
gte
(
ethers
.
constants
.
MaxUint256
))
{
throw
Error
()
}
else
{
setIndependentValueParsed
(
parsedValue
)
setIndependentError
(
null
)
}
}
catch
{
setIndependentError
(
t
(
'
inputNotValid
'
))
}
return
()
=>
{
setIndependentValueParsed
()
setIndependentError
()
}
}
},
[
independentValue
,
independentDecimals
,
t
])
// calculate slippage from target rate
const
{
minimum
:
dependentValueMinumum
,
maximum
:
dependentValueMaximum
}
=
calculateSlippageBounds
(
dependentValue
,
swapType
===
TOKEN_TO_TOKEN
)
// validate input allowance + balance
const
[
inputError
,
setInputError
]
=
useState
()
const
[
showUnlock
,
setShowUnlock
]
=
useState
(
false
)
useEffect
(()
=>
{
const
inputValueCalculation
=
independentField
===
INPUT
?
independentValueParsed
:
dependentValueMaximum
if
(
inputBalance
&&
(
inputAllowance
||
inputCurrency
===
'
ETH
'
)
&&
inputValueCalculation
)
{
if
(
inputBalance
.
lt
(
inputValueCalculation
))
{
setInputError
(
t
(
'
insufficientBalance
'
))
}
else
if
(
inputCurrency
!==
'
ETH
'
&&
inputAllowance
.
lt
(
inputValueCalculation
))
{
setInputError
(
t
(
'
unlockTokenCont
'
))
setShowUnlock
(
true
)
}
else
{
setInputError
(
null
)
setShowUnlock
(
false
)
}
return
()
=>
{
setInputError
()
setShowUnlock
(
false
)
}
}
},
[
independentField
,
independentValueParsed
,
dependentValueMaximum
,
inputBalance
,
inputCurrency
,
inputAllowance
,
t
])
// calculate dependent value
useEffect
(()
=>
{
const
amount
=
independentValueParsed
if
(
swapType
===
ETH_TO_TOKEN
)
{
const
reserveETH
=
outputReserveETH
const
reserveToken
=
outputReserveToken
if
(
amount
&&
reserveETH
&&
reserveToken
)
{
try
{
const
calculatedDependentValue
=
independentField
===
INPUT
?
calculateEtherTokenOutputFromInput
(
amount
,
reserveETH
,
reserveToken
)
:
calculateEtherTokenInputFromOutput
(
amount
,
reserveETH
,
reserveToken
)
if
(
calculatedDependentValue
.
lte
(
ethers
.
constants
.
Zero
))
{
throw
Error
()
}
dispatchSwapState
({
type
:
'
UPDATE_DEPENDENT
'
,
payload
:
calculatedDependentValue
})
}
catch
{
setIndependentError
(
t
(
'
insufficientLiquidity
'
))
}
return
()
=>
{
dispatchSwapState
({
type
:
'
UPDATE_DEPENDENT
'
,
payload
:
''
})
}
}
}
else
if
(
swapType
===
TOKEN_TO_ETH
)
{
const
reserveETH
=
inputReserveETH
const
reserveToken
=
inputReserveToken
if
(
amount
&&
reserveETH
&&
reserveToken
)
{
try
{
const
calculatedDependentValue
=
independentField
===
INPUT
?
calculateEtherTokenOutputFromInput
(
amount
,
reserveToken
,
reserveETH
)
:
calculateEtherTokenInputFromOutput
(
amount
,
reserveToken
,
reserveETH
)
if
(
calculatedDependentValue
.
lte
(
ethers
.
constants
.
Zero
))
{
throw
Error
()
}
dispatchSwapState
({
type
:
'
UPDATE_DEPENDENT
'
,
payload
:
calculatedDependentValue
})
}
catch
{
setIndependentError
(
t
(
'
insufficientLiquidity
'
))
}
return
()
=>
{
dispatchSwapState
({
type
:
'
UPDATE_DEPENDENT
'
,
payload
:
''
})
}
}
}
else
if
(
swapType
===
TOKEN_TO_TOKEN
)
{
const
reserveETHFirst
=
inputReserveETH
const
reserveTokenFirst
=
inputReserveToken
const
reserveETHSecond
=
outputReserveETH
const
reserveTokenSecond
=
outputReserveToken
if
(
amount
&&
reserveETHFirst
&&
reserveTokenFirst
&&
reserveETHSecond
&&
reserveTokenSecond
)
{
try
{
if
(
independentField
===
INPUT
)
{
const
intermediateValue
=
calculateEtherTokenOutputFromInput
(
amount
,
reserveTokenFirst
,
reserveETHFirst
)
if
(
intermediateValue
.
lte
(
ethers
.
constants
.
Zero
))
{
throw
Error
()
}
const
calculatedDependentValue
=
calculateEtherTokenOutputFromInput
(
intermediateValue
,
reserveETHSecond
,
reserveTokenSecond
)
if
(
calculatedDependentValue
.
lte
(
ethers
.
constants
.
Zero
))
{
throw
Error
()
}
dispatchSwapState
({
type
:
'
UPDATE_DEPENDENT
'
,
payload
:
calculatedDependentValue
})
}
else
{
const
intermediateValue
=
calculateEtherTokenInputFromOutput
(
amount
,
reserveETHSecond
,
reserveTokenSecond
)
if
(
intermediateValue
.
lte
(
ethers
.
constants
.
Zero
))
{
throw
Error
()
}
// console.log('hi!', amountFormatter(intermediateValue, ))
const
calculatedDependentValue
=
calculateEtherTokenInputFromOutput
(
intermediateValue
,
reserveTokenFirst
,
reserveETHFirst
)
if
(
calculatedDependentValue
.
lte
(
ethers
.
constants
.
Zero
))
{
throw
Error
()
}
dispatchSwapState
({
type
:
'
UPDATE_DEPENDENT
'
,
payload
:
calculatedDependentValue
})
}
}
catch
{
setIndependentError
(
t
(
'
insufficientLiquidity
'
))
}
return
()
=>
{
dispatchSwapState
({
type
:
'
UPDATE_DEPENDENT
'
,
payload
:
''
})
}
}
}
},
[
independentValueParsed
,
swapType
,
outputReserveETH
,
outputReserveToken
,
inputReserveETH
,
inputReserveToken
,
independentField
,
t
])
const
[
inverted
,
setInverted
]
=
useState
(
false
)
const
exchangeRate
=
getExchangeRate
(
inputValueParsed
,
inputDecimals
,
outputValueParsed
,
outputDecimals
)
const
exchangeRateInverted
=
getExchangeRate
(
inputValueParsed
,
inputDecimals
,
outputValueParsed
,
outputDecimals
,
true
)
const
marketRate
=
getMarketRate
(
swapType
,
inputReserveETH
,
inputReserveToken
,
inputDecimals
,
outputReserveETH
,
outputReserveToken
,
outputDecimals
)
const
percentSlippage
=
exchangeRate
&&
marketRate
?
exchangeRate
.
sub
(
marketRate
)
.
abs
()
.
mul
(
ethers
.
utils
.
bigNumberify
(
10
).
pow
(
ethers
.
utils
.
bigNumberify
(
18
)))
.
div
(
marketRate
)
.
sub
(
ethers
.
utils
.
bigNumberify
(
3
).
mul
(
ethers
.
utils
.
bigNumberify
(
10
).
pow
(
ethers
.
utils
.
bigNumberify
(
15
))))
:
undefined
const
percentSlippageFormatted
=
percentSlippage
&&
amountFormatter
(
percentSlippage
,
16
,
2
)
const
slippageWarning
=
percentSlippage
&&
percentSlippage
.
gte
(
ethers
.
utils
.
parseEther
(
'
.05
'
))
&&
percentSlippage
.
lt
(
ethers
.
utils
.
parseEther
(
'
.2
'
))
// [5% - 20%)
const
highSlippageWarning
=
percentSlippage
&&
percentSlippage
.
gte
(
ethers
.
utils
.
parseEther
(
'
.2
'
))
// [20+%
const
isValid
=
exchangeRate
&&
inputError
===
null
&&
independentError
===
null
&&
recipientError
===
null
const
estimatedText
=
`(
${
t
(
'
estimated
'
)}
)`
function
formatBalance
(
value
)
{
return
`Balance:
${
value
}
`
}
function
renderTransactionDetails
()
{
ReactGA
.
event
({
category
:
'
TransactionDetail
'
,
action
:
'
Open
'
})
const
b
=
text
=>
<
BlueSpan
>
{
text
}
<
/BlueSpan
>
if
(
independentField
===
INPUT
)
{
return
(
<
div
>
<
div
>
{
t
(
'
youAreSelling
'
)}{
'
'
}
{
b
(
`
${
amountFormatter
(
independentValueParsed
,
independentDecimals
,
Math
.
min
(
4
,
independentDecimals
)
)}
${
inputSymbol
}
`
)}
.
<
/div
>
<
LastSummaryText
>
{
b
(
recipient
.
address
)}
{
t
(
'
willReceive
'
)}{
'
'
}
{
b
(
`
${
amountFormatter
(
dependentValueMinumum
,
dependentDecimals
,
Math
.
min
(
4
,
dependentDecimals
)
)}
${
outputSymbol
}
`
)}{
'
'
}
{
t
(
'
orTransFail
'
)}
<
/LastSummaryText
>
<
LastSummaryText
>
{(
slippageWarning
||
highSlippageWarning
)
&&
(
<
span
role
=
"
img
"
aria
-
label
=
"
warning
"
>
⚠️
<
/span
>
)}
{
t
(
'
priceChange
'
)}
{
b
(
`
${
percentSlippageFormatted
}
%`
)}.
<
/LastSummaryText
>
<
/div
>
)
}
else
{
return
(
<
div
>
<
div
>
{
t
(
'
youAreSending
'
)}{
'
'
}
{
b
(
`
${
amountFormatter
(
independentValueParsed
,
independentDecimals
,
Math
.
min
(
4
,
independentDecimals
)
)}
${
outputSymbol
}
`
)}{
'
'
}
{
t
(
'
to
'
)}
{
b
(
recipient
.
address
)}.
<
/div
>
<
LastSummaryText
>
{
t
(
'
itWillCost
'
)}{
'
'
}
{
b
(
`
${
amountFormatter
(
dependentValueMaximum
,
dependentDecimals
,
Math
.
min
(
4
,
dependentDecimals
)
)}
${
inputSymbol
}
`
)}{
'
'
}
{
t
(
'
orTransFail
'
)}
<
/LastSummaryText
>
<
LastSummaryText
>
{
t
(
'
priceChange
'
)}
{
b
(
`
${
percentSlippageFormatted
}
%`
)}.
<
/LastSummaryText
>
<
/div
>
)
}
}
function
renderSummary
()
{
let
contextualInfo
=
''
let
isError
=
false
if
(
inputError
||
independentError
)
{
contextualInfo
=
inputError
||
independentError
isError
=
true
}
else
if
(
!
inputCurrency
||
!
outputCurrency
)
{
contextualInfo
=
t
(
'
selectTokenCont
'
)
}
else
if
(
!
independentValue
)
{
contextualInfo
=
t
(
'
enterValueCont
'
)
}
else
if
(
!
recipient
.
address
)
{
contextualInfo
=
t
(
'
noRecipient
'
)
}
else
if
(
!
isAddress
(
recipient
.
address
))
{
contextualInfo
=
t
(
'
invalidRecipient
'
)
}
else
if
(
!
account
)
{
contextualInfo
=
t
(
'
noWallet
'
)
isError
=
true
}
const
slippageWarningText
=
highSlippageWarning
?
t
(
'
highSlippageWarning
'
)
:
slippageWarning
?
t
(
'
slippageWarning
'
)
:
''
return
(
<
NewContextualInfo
openDetailsText
=
{
t
(
'
transactionDetails
'
)}
closeDetailsText
=
{
t
(
'
hideDetails
'
)}
contextualInfo
=
{
contextualInfo
?
contextualInfo
:
slippageWarningText
}
allowExpand
=
{
!!
(
inputCurrency
&&
outputCurrency
&&
inputValueParsed
&&
outputValueParsed
&&
recipient
.
address
)}
isError
=
{
isError
}
slippageWarning
=
{
slippageWarning
&&
slippageWarningText
}
highSlippageWarning
=
{
highSlippageWarning
&&
slippageWarningText
}
renderTransactionDetails
=
{
renderTransactionDetails
}
/
>
)
}
async
function
onSwap
()
{
const
deadline
=
Math
.
ceil
(
Date
.
now
()
/
1000
)
+
DEADLINE_FROM_NOW
let
estimate
,
method
,
args
,
value
if
(
independentField
===
INPUT
)
{
ReactGA
.
event
({
category
:
`
${
swapType
}
`
,
action
:
'
TransferInput
'
})
if
(
swapType
===
ETH_TO_TOKEN
)
{
estimate
=
contract
.
estimate
.
ethToTokenTransferInput
method
=
contract
.
ethToTokenTransferInput
args
=
[
dependentValueMinumum
,
deadline
,
recipient
.
address
]
value
=
independentValueParsed
}
else
if
(
swapType
===
TOKEN_TO_ETH
)
{
estimate
=
contract
.
estimate
.
tokenToEthTransferInput
method
=
contract
.
tokenToEthTransferInput
args
=
[
independentValueParsed
,
dependentValueMinumum
,
deadline
,
recipient
.
address
]
value
=
ethers
.
constants
.
Zero
}
else
if
(
swapType
===
TOKEN_TO_TOKEN
)
{
estimate
=
contract
.
estimate
.
tokenToTokenTransferInput
method
=
contract
.
tokenToTokenTransferInput
args
=
[
independentValueParsed
,
dependentValueMinumum
,
ethers
.
constants
.
One
,
deadline
,
recipient
.
address
,
outputCurrency
]
value
=
ethers
.
constants
.
Zero
}
}
else
if
(
independentField
===
OUTPUT
)
{
ReactGA
.
event
({
category
:
`
${
swapType
}
`
,
action
:
'
TransferOutput
'
})
if
(
swapType
===
ETH_TO_TOKEN
)
{
estimate
=
contract
.
estimate
.
ethToTokenTransferOutput
method
=
contract
.
ethToTokenTransferOutput
args
=
[
independentValueParsed
,
deadline
,
recipient
.
address
]
value
=
dependentValueMaximum
}
else
if
(
swapType
===
TOKEN_TO_ETH
)
{
estimate
=
contract
.
estimate
.
tokenToEthTransferOutput
method
=
contract
.
tokenToEthTransferOutput
args
=
[
independentValueParsed
,
dependentValueMaximum
,
deadline
,
recipient
.
address
]
value
=
ethers
.
constants
.
Zero
}
else
if
(
swapType
===
TOKEN_TO_TOKEN
)
{
estimate
=
contract
.
estimate
.
tokenToTokenTransferOutput
method
=
contract
.
tokenToTokenTransferOutput
args
=
[
independentValueParsed
,
dependentValueMaximum
,
ethers
.
constants
.
MaxUint256
,
deadline
,
recipient
.
address
,
outputCurrency
]
value
=
ethers
.
constants
.
Zero
}
}
const
estimatedGasLimit
=
await
estimate
(...
args
,
{
value
})
method
(...
args
,
{
value
,
gasLimit
:
calculateGasMargin
(
estimatedGasLimit
,
GAS_MARGIN
)
}).
then
(
response
=>
{
addTransaction
(
response
)
})
}
return
(
<>
<
CurrencyInputPanel
title
=
{
t
(
'
input
'
)}
description
=
{
inputValueFormatted
&&
independentField
===
OUTPUT
?
estimatedText
:
''
}
extraText
=
{
inputBalanceFormatted
&&
formatBalance
(
inputBalanceFormatted
)}
extraTextClickHander
=
{()
=>
{
if
(
inputBalance
&&
inputDecimals
)
{
const
valueToSet
=
inputCurrency
===
'
ETH
'
?
inputBalance
.
sub
(
ethers
.
utils
.
parseEther
(
'
.1
'
))
:
inputBalance
if
(
valueToSet
.
gt
(
ethers
.
constants
.
Zero
))
{
dispatchSwapState
({
type
:
'
UPDATE_INDEPENDENT
'
,
payload
:
{
value
:
amountFormatter
(
valueToSet
,
inputDecimals
,
inputDecimals
,
false
),
field
:
INPUT
}
})
}
}
}}
onCurrencySelected
=
{
inputCurrency
=>
{
dispatchSwapState
({
type
:
'
SELECT_CURRENCY
'
,
payload
:
{
currency
:
inputCurrency
,
field
:
INPUT
}
})
}}
onValueChange
=
{
inputValue
=>
{
dispatchSwapState
({
type
:
'
UPDATE_INDEPENDENT
'
,
payload
:
{
value
:
inputValue
,
field
:
INPUT
}
})
}}
showUnlock
=
{
showUnlock
}
selectedTokens
=
{[
inputCurrency
,
outputCurrency
]}
selectedTokenAddress
=
{
inputCurrency
}
value
=
{
inputValueFormatted
}
errorMessage
=
{
inputError
?
inputError
:
independentField
===
INPUT
?
independentError
:
''
}
/
>
<
OversizedPanel
>
<
DownArrowBackground
>
<
DownArrow
onClick
=
{()
=>
{
dispatchSwapState
({
type
:
'
FLIP_INDEPENDENT
'
})
}}
clickable
alt
=
"
swap
"
src
=
{
isValid
?
ArrowDownBlue
:
ArrowDownGrey
}
/
>
<
/DownArrowBackground
>
<
/OversizedPanel
>
<
CurrencyInputPanel
title
=
{
t
(
'
output
'
)}
description
=
{
outputValueFormatted
&&
independentField
===
INPUT
?
estimatedText
:
''
}
extraText
=
{
outputBalanceFormatted
&&
formatBalance
(
outputBalanceFormatted
)}
onCurrencySelected
=
{
outputCurrency
=>
{
dispatchSwapState
({
type
:
'
SELECT_CURRENCY
'
,
payload
:
{
currency
:
outputCurrency
,
field
:
OUTPUT
}
})
}}
onValueChange
=
{
outputValue
=>
{
dispatchSwapState
({
type
:
'
UPDATE_INDEPENDENT
'
,
payload
:
{
value
:
outputValue
,
field
:
OUTPUT
}
})
}}
selectedTokens
=
{[
inputCurrency
,
outputCurrency
]}
selectedTokenAddress
=
{
outputCurrency
}
value
=
{
outputValueFormatted
}
errorMessage
=
{
independentField
===
OUTPUT
?
independentError
:
''
}
disableUnlock
/>
<
OversizedPanel
>
<
DownArrowBackground
>
<
DownArrow
src
=
{
isValid
?
ArrowDownBlue
:
ArrowDownGrey
}
alt
=
"
arrow
"
/>
<
/DownArrowBackground
>
<
/OversizedPanel
>
<
AddressInputPanel
onChange
=
{
setRecipient
}
onError
=
{
setRecipientError
}
/
>
<
OversizedPanel
hideBottom
>
<
ExchangeRateWrapper
onClick
=
{()
=>
{
setInverted
(
inverted
=>
!
inverted
)
}}
>
<
ExchangeRate
>
{
t
(
'
exchangeRate
'
)}
<
/ExchangeRate
>
{
inverted
?
(
<
span
>
{
exchangeRate
?
`1
${
outputSymbol
}
=
${
amountFormatter
(
exchangeRateInverted
,
18
,
4
,
false
)}
${
inputSymbol
}
`
:
'
-
'
}
<
/span
>
)
:
(
<
span
>
{
exchangeRate
?
`1
${
inputSymbol
}
=
${
amountFormatter
(
exchangeRate
,
18
,
4
,
false
)}
${
outputSymbol
}
`
:
'
-
'
}
<
/span
>
)}
<
/ExchangeRateWrapper
>
<
/OversizedPanel
>
{
renderSummary
()}
<
Flex
>
<
Button
disabled
=
{
!
isValid
}
onClick
=
{
onSwap
}
warning
=
{
highSlippageWarning
}
>
{
highSlippageWarning
?
t
(
'
sendAnyway
'
)
:
t
(
'
send
'
)}
<
/Button
>
<
/Flex
>
<
/
>
)
}
}
src/pages/Swap/index.js
View file @
3f3889b9
import
React
,
{
useState
,
useReducer
,
useEffect
}
from
'
react
'
import
React
from
'
react
'
import
ReactGA
from
'
react-ga
'
import
ExchangePage
from
'
../../components/ExchangePage
'
import
{
useTranslation
}
from
'
react-i18next
'
import
{
useWeb3Context
}
from
'
web3-react
'
import
{
ethers
}
from
'
ethers
'
import
styled
from
'
styled-components
'
import
{
Button
}
from
'
../../theme
'
import
CurrencyInputPanel
from
'
../../components/CurrencyInputPanel
'
import
NewContextualInfo
from
'
../../components/ContextualInfoNew
'
import
OversizedPanel
from
'
../../components/OversizedPanel
'
import
ArrowDownBlue
from
'
../../assets/images/arrow-down-blue.svg
'
import
ArrowDownGrey
from
'
../../assets/images/arrow-down-grey.svg
'
import
{
amountFormatter
,
calculateGasMargin
}
from
'
../../utils
'
import
{
useExchangeContract
}
from
'
../../hooks
'
import
{
useTokenDetails
}
from
'
../../contexts/Tokens
'
import
{
useTransactionAdder
}
from
'
../../contexts/Transactions
'
import
{
useAddressBalance
,
useExchangeReserves
}
from
'
../../contexts/Balances
'
import
{
useAddressAllowance
}
from
'
../../contexts/Allowances
'
const
INPUT
=
0
const
OUTPUT
=
1
const
ETH_TO_TOKEN
=
0
const
TOKEN_TO_ETH
=
1
const
TOKEN_TO_TOKEN
=
2
// denominated in bips
const
ALLOWED_SLIPPAGE
=
ethers
.
utils
.
bigNumberify
(
200
)
const
TOKEN_ALLOWED_SLIPPAGE
=
ethers
.
utils
.
bigNumberify
(
400
)
// denominated in seconds
const
DEADLINE_FROM_NOW
=
60
*
15
// denominated in bips
const
GAS_MARGIN
=
ethers
.
utils
.
bigNumberify
(
1000
)
const
BlueSpan
=
styled
.
span
`
color:
${({
theme
})
=>
theme
.
royalBlue
}
;
`
const
LastSummaryText
=
styled
.
div
`
margin-top: 1rem;
`
const
DownArrowBackground
=
styled
.
div
`
${({
theme
})
=>
theme
.
flexRowNoWrap
}
justify-content: center;
align-items: center;
`
const
DownArrow
=
styled
.
img
`
width: 0.625rem;
height: 0.625rem;
position: relative;
padding: 0.875rem;
cursor:
${({
clickable
})
=>
clickable
&&
'
pointer
'
}
;
`
const
ExchangeRateWrapper
=
styled
.
div
`
${({
theme
})
=>
theme
.
flexRowNoWrap
}
;
align-items: center;
color:
${({
theme
})
=>
theme
.
doveGray
}
;
font-size: 0.75rem;
padding: 0.5rem 1rem;
`
const
ExchangeRate
=
styled
.
span
`
flex: 1 1 auto;
width: 0;
color:
${({
theme
})
=>
theme
.
chaliceGray
}
;
`
const
Flex
=
styled
.
div
`
display: flex;
justify-content: center;
padding: 2rem;
button {
max-width: 20rem;
}
`
function
calculateSlippageBounds
(
value
,
token
=
false
)
{
if
(
value
)
{
const
offset
=
value
.
mul
(
token
?
TOKEN_ALLOWED_SLIPPAGE
:
ALLOWED_SLIPPAGE
).
div
(
ethers
.
utils
.
bigNumberify
(
10000
))
const
minimum
=
value
.
sub
(
offset
)
const
maximum
=
value
.
add
(
offset
)
return
{
minimum
:
minimum
.
lt
(
ethers
.
constants
.
Zero
)
?
ethers
.
constants
.
Zero
:
minimum
,
maximum
:
maximum
.
gt
(
ethers
.
constants
.
MaxUint256
)
?
ethers
.
constants
.
MaxUint256
:
maximum
}
}
else
{
return
{}
}
}
function
getSwapType
(
inputCurrency
,
outputCurrency
)
{
if
(
!
inputCurrency
||
!
outputCurrency
)
{
return
null
}
else
if
(
inputCurrency
===
'
ETH
'
)
{
return
ETH_TO_TOKEN
}
else
if
(
outputCurrency
===
'
ETH
'
)
{
return
TOKEN_TO_ETH
}
else
{
return
TOKEN_TO_TOKEN
}
}
// this mocks the getInputPrice function, and calculates the required output
function
calculateEtherTokenOutputFromInput
(
inputAmount
,
inputReserve
,
outputReserve
)
{
const
inputAmountWithFee
=
inputAmount
.
mul
(
ethers
.
utils
.
bigNumberify
(
997
))
const
numerator
=
inputAmountWithFee
.
mul
(
outputReserve
)
const
denominator
=
inputReserve
.
mul
(
ethers
.
utils
.
bigNumberify
(
1000
)).
add
(
inputAmountWithFee
)
return
numerator
.
div
(
denominator
)
}
// this mocks the getOutputPrice function, and calculates the required input
function
calculateEtherTokenInputFromOutput
(
outputAmount
,
inputReserve
,
outputReserve
)
{
const
numerator
=
inputReserve
.
mul
(
outputAmount
).
mul
(
ethers
.
utils
.
bigNumberify
(
1000
))
const
denominator
=
outputReserve
.
sub
(
outputAmount
).
mul
(
ethers
.
utils
.
bigNumberify
(
997
))
return
numerator
.
div
(
denominator
).
add
(
ethers
.
constants
.
One
)
}
function
getInitialSwapState
(
outputCurrency
)
{
return
{
independentValue
:
''
,
// this is a user input
dependentValue
:
''
,
// this is a calculated number
independentField
:
INPUT
,
inputCurrency
:
'
ETH
'
,
outputCurrency
:
outputCurrency
?
outputCurrency
:
''
}
}
function
swapStateReducer
(
state
,
action
)
{
switch
(
action
.
type
)
{
case
'
FLIP_INDEPENDENT
'
:
{
const
{
independentField
,
inputCurrency
,
outputCurrency
}
=
state
return
{
...
state
,
dependentValue
:
''
,
independentField
:
independentField
===
INPUT
?
OUTPUT
:
INPUT
,
inputCurrency
:
outputCurrency
,
outputCurrency
:
inputCurrency
}
}
case
'
SELECT_CURRENCY
'
:
{
const
{
inputCurrency
,
outputCurrency
}
=
state
const
{
field
,
currency
}
=
action
.
payload
const
newInputCurrency
=
field
===
INPUT
?
currency
:
inputCurrency
const
newOutputCurrency
=
field
===
OUTPUT
?
currency
:
outputCurrency
if
(
newInputCurrency
===
newOutputCurrency
)
{
return
{
...
state
,
inputCurrency
:
field
===
INPUT
?
currency
:
''
,
outputCurrency
:
field
===
OUTPUT
?
currency
:
''
}
}
else
{
return
{
...
state
,
inputCurrency
:
newInputCurrency
,
outputCurrency
:
newOutputCurrency
}
}
}
case
'
UPDATE_INDEPENDENT
'
:
{
const
{
field
,
value
}
=
action
.
payload
const
{
dependentValue
,
independentValue
}
=
state
return
{
...
state
,
independentValue
:
value
,
dependentValue
:
value
===
independentValue
?
dependentValue
:
''
,
independentField
:
field
}
}
case
'
UPDATE_DEPENDENT
'
:
{
return
{
...
state
,
dependentValue
:
action
.
payload
}
}
default
:
{
return
getInitialSwapState
()
}
}
}
function
getExchangeRate
(
inputValue
,
inputDecimals
,
outputValue
,
outputDecimals
,
invert
=
false
)
{
try
{
if
(
inputValue
&&
(
inputDecimals
||
inputDecimals
===
0
)
&&
outputValue
&&
(
outputDecimals
||
outputDecimals
===
0
)
)
{
const
factor
=
ethers
.
utils
.
bigNumberify
(
10
).
pow
(
ethers
.
utils
.
bigNumberify
(
18
))
if
(
invert
)
{
return
inputValue
.
mul
(
factor
)
.
div
(
outputValue
)
.
mul
(
ethers
.
utils
.
bigNumberify
(
10
).
pow
(
ethers
.
utils
.
bigNumberify
(
outputDecimals
)))
.
div
(
ethers
.
utils
.
bigNumberify
(
10
).
pow
(
ethers
.
utils
.
bigNumberify
(
inputDecimals
)))
}
else
{
return
outputValue
.
mul
(
factor
)
.
div
(
inputValue
)
.
mul
(
ethers
.
utils
.
bigNumberify
(
10
).
pow
(
ethers
.
utils
.
bigNumberify
(
inputDecimals
)))
.
div
(
ethers
.
utils
.
bigNumberify
(
10
).
pow
(
ethers
.
utils
.
bigNumberify
(
outputDecimals
)))
}
}
}
catch
{}
}
function
getMarketRate
(
swapType
,
inputReserveETH
,
inputReserveToken
,
inputDecimals
,
outputReserveETH
,
outputReserveToken
,
outputDecimals
,
invert
=
false
)
{
if
(
swapType
===
ETH_TO_TOKEN
)
{
return
getExchangeRate
(
outputReserveETH
,
18
,
outputReserveToken
,
outputDecimals
,
invert
)
}
else
if
(
swapType
===
TOKEN_TO_ETH
)
{
return
getExchangeRate
(
inputReserveToken
,
inputDecimals
,
inputReserveETH
,
18
,
invert
)
}
else
if
(
swapType
===
TOKEN_TO_TOKEN
)
{
const
factor
=
ethers
.
utils
.
bigNumberify
(
10
).
pow
(
ethers
.
utils
.
bigNumberify
(
18
))
const
firstRate
=
getExchangeRate
(
inputReserveToken
,
inputDecimals
,
inputReserveETH
,
18
)
const
secondRate
=
getExchangeRate
(
outputReserveETH
,
18
,
outputReserveToken
,
outputDecimals
)
try
{
return
!!
(
firstRate
&&
secondRate
)
?
firstRate
.
mul
(
secondRate
).
div
(
factor
)
:
undefined
}
catch
{}
}
}
export
default
function
Swap
({
initialCurrency
})
{
export
default
function
Swap
({
initialCurrency
})
{
const
{
t
}
=
useTranslation
()
return
<
ExchangePage
initialCurrency
=
{
initialCurrency
}
/
>
const
{
account
}
=
useWeb3Context
()
const
addTransaction
=
useTransactionAdder
()
// analytics
useEffect
(()
=>
{
ReactGA
.
pageview
(
window
.
location
.
pathname
+
window
.
location
.
search
)
},
[])
// core swap state
const
[
swapState
,
dispatchSwapState
]
=
useReducer
(
swapStateReducer
,
initialCurrency
,
getInitialSwapState
)
const
{
independentValue
,
dependentValue
,
independentField
,
inputCurrency
,
outputCurrency
}
=
swapState
// get swap type from the currency types
const
swapType
=
getSwapType
(
inputCurrency
,
outputCurrency
)
// get decimals and exchange addressfor each of the currency types
const
{
symbol
:
inputSymbol
,
decimals
:
inputDecimals
,
exchangeAddress
:
inputExchangeAddress
}
=
useTokenDetails
(
inputCurrency
)
const
{
symbol
:
outputSymbol
,
decimals
:
outputDecimals
,
exchangeAddress
:
outputExchangeAddress
}
=
useTokenDetails
(
outputCurrency
)
const
inputExchangeContract
=
useExchangeContract
(
inputExchangeAddress
)
const
outputExchangeContract
=
useExchangeContract
(
outputExchangeAddress
)
const
contract
=
swapType
===
ETH_TO_TOKEN
?
outputExchangeContract
:
inputExchangeContract
// get input allowance
const
inputAllowance
=
useAddressAllowance
(
account
,
inputCurrency
,
inputExchangeAddress
)
// fetch reserves for each of the currency types
const
{
reserveETH
:
inputReserveETH
,
reserveToken
:
inputReserveToken
}
=
useExchangeReserves
(
inputCurrency
)
const
{
reserveETH
:
outputReserveETH
,
reserveToken
:
outputReserveToken
}
=
useExchangeReserves
(
outputCurrency
)
// get balances for each of the currency types
const
inputBalance
=
useAddressBalance
(
account
,
inputCurrency
)
const
outputBalance
=
useAddressBalance
(
account
,
outputCurrency
)
const
inputBalanceFormatted
=
!!
(
inputBalance
&&
Number
.
isInteger
(
inputDecimals
))
?
amountFormatter
(
inputBalance
,
inputDecimals
,
Math
.
min
(
4
,
inputDecimals
))
:
''
const
outputBalanceFormatted
=
!!
(
outputBalance
&&
Number
.
isInteger
(
outputDecimals
))
?
amountFormatter
(
outputBalance
,
outputDecimals
,
Math
.
min
(
4
,
outputDecimals
))
:
''
// compute useful transforms of the data above
const
independentDecimals
=
independentField
===
INPUT
?
inputDecimals
:
outputDecimals
const
dependentDecimals
=
independentField
===
OUTPUT
?
inputDecimals
:
outputDecimals
// declare/get parsed and formatted versions of input/output values
const
[
independentValueParsed
,
setIndependentValueParsed
]
=
useState
()
const
dependentValueFormatted
=
!!
(
dependentValue
&&
(
dependentDecimals
||
dependentDecimals
===
0
))
?
amountFormatter
(
dependentValue
,
dependentDecimals
,
Math
.
min
(
4
,
dependentDecimals
),
false
)
:
''
const
inputValueParsed
=
independentField
===
INPUT
?
independentValueParsed
:
dependentValue
const
inputValueFormatted
=
independentField
===
INPUT
?
independentValue
:
dependentValueFormatted
const
outputValueParsed
=
independentField
===
OUTPUT
?
independentValueParsed
:
dependentValue
const
outputValueFormatted
=
independentField
===
OUTPUT
?
independentValue
:
dependentValueFormatted
// validate + parse independent value
const
[
independentError
,
setIndependentError
]
=
useState
()
useEffect
(()
=>
{
if
(
independentValue
&&
(
independentDecimals
||
independentDecimals
===
0
))
{
try
{
const
parsedValue
=
ethers
.
utils
.
parseUnits
(
independentValue
,
independentDecimals
)
if
(
parsedValue
.
lte
(
ethers
.
constants
.
Zero
)
||
parsedValue
.
gte
(
ethers
.
constants
.
MaxUint256
))
{
throw
Error
()
}
else
{
setIndependentValueParsed
(
parsedValue
)
setIndependentError
(
null
)
}
}
catch
{
setIndependentError
(
t
(
'
inputNotValid
'
))
}
return
()
=>
{
setIndependentValueParsed
()
setIndependentError
()
}
}
},
[
independentValue
,
independentDecimals
,
t
])
// calculate slippage from target rate
const
{
minimum
:
dependentValueMinumum
,
maximum
:
dependentValueMaximum
}
=
calculateSlippageBounds
(
dependentValue
,
swapType
===
TOKEN_TO_TOKEN
)
// validate input allowance + balance
const
[
inputError
,
setInputError
]
=
useState
()
const
[
showUnlock
,
setShowUnlock
]
=
useState
(
false
)
useEffect
(()
=>
{
const
inputValueCalculation
=
independentField
===
INPUT
?
independentValueParsed
:
dependentValueMaximum
if
(
inputBalance
&&
(
inputAllowance
||
inputCurrency
===
'
ETH
'
)
&&
inputValueCalculation
)
{
if
(
inputBalance
.
lt
(
inputValueCalculation
))
{
setInputError
(
t
(
'
insufficientBalance
'
))
}
else
if
(
inputCurrency
!==
'
ETH
'
&&
inputAllowance
.
lt
(
inputValueCalculation
))
{
setInputError
(
t
(
'
unlockTokenCont
'
))
setShowUnlock
(
true
)
}
else
{
setInputError
(
null
)
setShowUnlock
(
false
)
}
return
()
=>
{
setInputError
()
setShowUnlock
(
false
)
}
}
},
[
independentField
,
independentValueParsed
,
dependentValueMaximum
,
inputBalance
,
inputCurrency
,
inputAllowance
,
t
])
// calculate dependent value
useEffect
(()
=>
{
const
amount
=
independentValueParsed
if
(
swapType
===
ETH_TO_TOKEN
)
{
const
reserveETH
=
outputReserveETH
const
reserveToken
=
outputReserveToken
if
(
amount
&&
reserveETH
&&
reserveToken
)
{
try
{
const
calculatedDependentValue
=
independentField
===
INPUT
?
calculateEtherTokenOutputFromInput
(
amount
,
reserveETH
,
reserveToken
)
:
calculateEtherTokenInputFromOutput
(
amount
,
reserveETH
,
reserveToken
)
if
(
calculatedDependentValue
.
lte
(
ethers
.
constants
.
Zero
))
{
throw
Error
()
}
dispatchSwapState
({
type
:
'
UPDATE_DEPENDENT
'
,
payload
:
calculatedDependentValue
})
}
catch
{
setIndependentError
(
t
(
'
insufficientLiquidity
'
))
}
return
()
=>
{
dispatchSwapState
({
type
:
'
UPDATE_DEPENDENT
'
,
payload
:
''
})
}
}
}
else
if
(
swapType
===
TOKEN_TO_ETH
)
{
const
reserveETH
=
inputReserveETH
const
reserveToken
=
inputReserveToken
if
(
amount
&&
reserveETH
&&
reserveToken
)
{
try
{
const
calculatedDependentValue
=
independentField
===
INPUT
?
calculateEtherTokenOutputFromInput
(
amount
,
reserveToken
,
reserveETH
)
:
calculateEtherTokenInputFromOutput
(
amount
,
reserveToken
,
reserveETH
)
if
(
calculatedDependentValue
.
lte
(
ethers
.
constants
.
Zero
))
{
throw
Error
()
}
dispatchSwapState
({
type
:
'
UPDATE_DEPENDENT
'
,
payload
:
calculatedDependentValue
})
}
catch
{
setIndependentError
(
t
(
'
insufficientLiquidity
'
))
}
return
()
=>
{
dispatchSwapState
({
type
:
'
UPDATE_DEPENDENT
'
,
payload
:
''
})
}
}
}
else
if
(
swapType
===
TOKEN_TO_TOKEN
)
{
const
reserveETHFirst
=
inputReserveETH
const
reserveTokenFirst
=
inputReserveToken
const
reserveETHSecond
=
outputReserveETH
const
reserveTokenSecond
=
outputReserveToken
if
(
amount
&&
reserveETHFirst
&&
reserveTokenFirst
&&
reserveETHSecond
&&
reserveTokenSecond
)
{
try
{
if
(
independentField
===
INPUT
)
{
const
intermediateValue
=
calculateEtherTokenOutputFromInput
(
amount
,
reserveTokenFirst
,
reserveETHFirst
)
if
(
intermediateValue
.
lte
(
ethers
.
constants
.
Zero
))
{
throw
Error
()
}
const
calculatedDependentValue
=
calculateEtherTokenOutputFromInput
(
intermediateValue
,
reserveETHSecond
,
reserveTokenSecond
)
if
(
calculatedDependentValue
.
lte
(
ethers
.
constants
.
Zero
))
{
throw
Error
()
}
dispatchSwapState
({
type
:
'
UPDATE_DEPENDENT
'
,
payload
:
calculatedDependentValue
})
}
else
{
const
intermediateValue
=
calculateEtherTokenInputFromOutput
(
amount
,
reserveETHSecond
,
reserveTokenSecond
)
if
(
intermediateValue
.
lte
(
ethers
.
constants
.
Zero
))
{
throw
Error
()
}
const
calculatedDependentValue
=
calculateEtherTokenInputFromOutput
(
intermediateValue
,
reserveTokenFirst
,
reserveETHFirst
)
if
(
calculatedDependentValue
.
lte
(
ethers
.
constants
.
Zero
))
{
throw
Error
()
}
dispatchSwapState
({
type
:
'
UPDATE_DEPENDENT
'
,
payload
:
calculatedDependentValue
})
}
}
catch
{
setIndependentError
(
t
(
'
insufficientLiquidity
'
))
}
return
()
=>
{
dispatchSwapState
({
type
:
'
UPDATE_DEPENDENT
'
,
payload
:
''
})
}
}
}
},
[
independentValueParsed
,
swapType
,
outputReserveETH
,
outputReserveToken
,
inputReserveETH
,
inputReserveToken
,
independentField
,
t
])
const
[
inverted
,
setInverted
]
=
useState
(
false
)
const
exchangeRate
=
getExchangeRate
(
inputValueParsed
,
inputDecimals
,
outputValueParsed
,
outputDecimals
)
const
exchangeRateInverted
=
getExchangeRate
(
inputValueParsed
,
inputDecimals
,
outputValueParsed
,
outputDecimals
,
true
)
const
marketRate
=
getMarketRate
(
swapType
,
inputReserveETH
,
inputReserveToken
,
inputDecimals
,
outputReserveETH
,
outputReserveToken
,
outputDecimals
)
const
percentSlippage
=
exchangeRate
&&
marketRate
?
exchangeRate
.
sub
(
marketRate
)
.
abs
()
.
mul
(
ethers
.
utils
.
bigNumberify
(
10
).
pow
(
ethers
.
utils
.
bigNumberify
(
18
)))
.
div
(
marketRate
)
.
sub
(
ethers
.
utils
.
bigNumberify
(
3
).
mul
(
ethers
.
utils
.
bigNumberify
(
10
).
pow
(
ethers
.
utils
.
bigNumberify
(
15
))))
:
undefined
const
percentSlippageFormatted
=
percentSlippage
&&
amountFormatter
(
percentSlippage
,
16
,
2
)
const
slippageWarning
=
percentSlippage
&&
percentSlippage
.
gte
(
ethers
.
utils
.
parseEther
(
'
.05
'
))
&&
percentSlippage
.
lt
(
ethers
.
utils
.
parseEther
(
'
.2
'
))
// [5% - 20%)
const
highSlippageWarning
=
percentSlippage
&&
percentSlippage
.
gte
(
ethers
.
utils
.
parseEther
(
'
.2
'
))
// [20+%
const
isValid
=
exchangeRate
&&
inputError
===
null
&&
independentError
===
null
const
estimatedText
=
`(
${
t
(
'
estimated
'
)}
)`
function
formatBalance
(
value
)
{
return
`Balance:
${
value
}
`
}
function
renderTransactionDetails
()
{
ReactGA
.
event
({
category
:
'
TransactionDetail
'
,
action
:
'
Open
'
})
const
b
=
text
=>
<
BlueSpan
>
{
text
}
<
/BlueSpan
>
if
(
independentField
===
INPUT
)
{
return
(
<
div
>
<
div
>
{
t
(
'
youAreSelling
'
)}{
'
'
}
{
b
(
`
${
amountFormatter
(
independentValueParsed
,
independentDecimals
,
Math
.
min
(
4
,
independentDecimals
)
)}
${
inputSymbol
}
`
)}
.
<
/div
>
<
LastSummaryText
>
{
t
(
'
youWillReceive
'
)}{
'
'
}
{
b
(
`
${
amountFormatter
(
dependentValueMinumum
,
dependentDecimals
,
Math
.
min
(
4
,
dependentDecimals
)
)}
${
outputSymbol
}
`
)}{
'
'
}
{
t
(
'
orTransFail
'
)}
<
/LastSummaryText
>
<
LastSummaryText
>
{(
slippageWarning
||
highSlippageWarning
)
&&
(
<
span
role
=
"
img
"
aria
-
label
=
"
warning
"
>
⚠️
<
/span
>
)}
{
t
(
'
priceChange
'
)}
{
b
(
`
${
percentSlippageFormatted
}
%`
)}.
<
/LastSummaryText
>
<
/div
>
)
}
else
{
return
(
<
div
>
<
div
>
{
t
(
'
youAreBuying
'
)}{
'
'
}
{
b
(
`
${
amountFormatter
(
independentValueParsed
,
independentDecimals
,
Math
.
min
(
4
,
independentDecimals
)
)}
${
outputSymbol
}
`
)}
.
<
/div
>
<
LastSummaryText
>
{
t
(
'
itWillCost
'
)}{
'
'
}
{
b
(
`
${
amountFormatter
(
dependentValueMaximum
,
dependentDecimals
,
Math
.
min
(
4
,
dependentDecimals
)
)}
${
inputSymbol
}
`
)}{
'
'
}
{
t
(
'
orTransFail
'
)}
<
/LastSummaryText
>
<
LastSummaryText
>
{
t
(
'
priceChange
'
)}
{
b
(
`
${
percentSlippageFormatted
}
%`
)}.
<
/LastSummaryText
>
<
/div
>
)
}
}
function
renderSummary
()
{
let
contextualInfo
=
''
let
isError
=
false
if
(
inputError
||
independentError
)
{
contextualInfo
=
inputError
||
independentError
isError
=
true
}
else
if
(
!
inputCurrency
||
!
outputCurrency
)
{
contextualInfo
=
t
(
'
selectTokenCont
'
)
}
else
if
(
!
independentValue
)
{
contextualInfo
=
t
(
'
enterValueCont
'
)
}
else
if
(
!
account
)
{
contextualInfo
=
t
(
'
noWallet
'
)
isError
=
true
}
const
slippageWarningText
=
highSlippageWarning
?
t
(
'
highSlippageWarning
'
)
:
slippageWarning
?
t
(
'
slippageWarning
'
)
:
''
return
(
<
NewContextualInfo
openDetailsText
=
{
t
(
'
transactionDetails
'
)}
closeDetailsText
=
{
t
(
'
hideDetails
'
)}
contextualInfo
=
{
contextualInfo
?
contextualInfo
:
slippageWarningText
}
allowExpand
=
{
!!
(
inputCurrency
&&
outputCurrency
&&
inputValueParsed
&&
outputValueParsed
)}
isError
=
{
isError
}
slippageWarning
=
{
slippageWarning
&&
!
contextualInfo
}
highSlippageWarning
=
{
highSlippageWarning
&&
!
contextualInfo
}
renderTransactionDetails
=
{
renderTransactionDetails
}
/
>
)
}
async
function
onSwap
()
{
const
deadline
=
Math
.
ceil
(
Date
.
now
()
/
1000
)
+
DEADLINE_FROM_NOW
let
estimate
,
method
,
args
,
value
if
(
independentField
===
INPUT
)
{
ReactGA
.
event
({
category
:
`
${
swapType
}
`
,
action
:
'
SwapInput
'
})
if
(
swapType
===
ETH_TO_TOKEN
)
{
estimate
=
contract
.
estimate
.
ethToTokenSwapInput
method
=
contract
.
ethToTokenSwapInput
args
=
[
dependentValueMinumum
,
deadline
]
value
=
independentValueParsed
}
else
if
(
swapType
===
TOKEN_TO_ETH
)
{
estimate
=
contract
.
estimate
.
tokenToEthSwapInput
method
=
contract
.
tokenToEthSwapInput
args
=
[
independentValueParsed
,
dependentValueMinumum
,
deadline
]
value
=
ethers
.
constants
.
Zero
}
else
if
(
swapType
===
TOKEN_TO_TOKEN
)
{
estimate
=
contract
.
estimate
.
tokenToTokenSwapInput
method
=
contract
.
tokenToTokenSwapInput
args
=
[
independentValueParsed
,
dependentValueMinumum
,
ethers
.
constants
.
One
,
deadline
,
outputCurrency
]
value
=
ethers
.
constants
.
Zero
}
}
else
if
(
independentField
===
OUTPUT
)
{
ReactGA
.
event
({
category
:
`
${
swapType
}
`
,
action
:
'
SwapOutput
'
})
if
(
swapType
===
ETH_TO_TOKEN
)
{
estimate
=
contract
.
estimate
.
ethToTokenSwapOutput
method
=
contract
.
ethToTokenSwapOutput
args
=
[
independentValueParsed
,
deadline
]
value
=
dependentValueMaximum
}
else
if
(
swapType
===
TOKEN_TO_ETH
)
{
estimate
=
contract
.
estimate
.
tokenToEthSwapOutput
method
=
contract
.
tokenToEthSwapOutput
args
=
[
independentValueParsed
,
dependentValueMaximum
,
deadline
]
value
=
ethers
.
constants
.
Zero
}
else
if
(
swapType
===
TOKEN_TO_TOKEN
)
{
estimate
=
contract
.
estimate
.
tokenToTokenSwapOutput
method
=
contract
.
tokenToTokenSwapOutput
args
=
[
independentValueParsed
,
dependentValueMaximum
,
ethers
.
constants
.
MaxUint256
,
deadline
,
outputCurrency
]
value
=
ethers
.
constants
.
Zero
}
}
const
estimatedGasLimit
=
await
estimate
(...
args
,
{
value
})
method
(...
args
,
{
value
,
gasLimit
:
calculateGasMargin
(
estimatedGasLimit
,
GAS_MARGIN
)
}).
then
(
response
=>
{
addTransaction
(
response
)
})
}
return
(
<>
<
CurrencyInputPanel
title
=
{
t
(
'
input
'
)}
description
=
{
inputValueFormatted
&&
independentField
===
OUTPUT
?
estimatedText
:
''
}
extraText
=
{
inputBalanceFormatted
&&
formatBalance
(
inputBalanceFormatted
)}
extraTextClickHander
=
{()
=>
{
if
(
inputBalance
&&
inputDecimals
)
{
const
valueToSet
=
inputCurrency
===
'
ETH
'
?
inputBalance
.
sub
(
ethers
.
utils
.
parseEther
(
'
.1
'
))
:
inputBalance
if
(
valueToSet
.
gt
(
ethers
.
constants
.
Zero
))
{
dispatchSwapState
({
type
:
'
UPDATE_INDEPENDENT
'
,
payload
:
{
value
:
amountFormatter
(
valueToSet
,
inputDecimals
,
inputDecimals
,
false
),
field
:
INPUT
}
})
}
}
}}
onCurrencySelected
=
{
inputCurrency
=>
{
dispatchSwapState
({
type
:
'
SELECT_CURRENCY
'
,
payload
:
{
currency
:
inputCurrency
,
field
:
INPUT
}
})
}}
onValueChange
=
{
inputValue
=>
{
dispatchSwapState
({
type
:
'
UPDATE_INDEPENDENT
'
,
payload
:
{
value
:
inputValue
,
field
:
INPUT
}
})
}}
showUnlock
=
{
showUnlock
}
selectedTokens
=
{[
inputCurrency
,
outputCurrency
]}
selectedTokenAddress
=
{
inputCurrency
}
value
=
{
inputValueFormatted
}
errorMessage
=
{
inputError
?
inputError
:
independentField
===
INPUT
?
independentError
:
''
}
/
>
<
OversizedPanel
>
<
DownArrowBackground
>
<
DownArrow
onClick
=
{()
=>
{
dispatchSwapState
({
type
:
'
FLIP_INDEPENDENT
'
})
}}
clickable
alt
=
"
swap
"
src
=
{
isValid
?
ArrowDownBlue
:
ArrowDownGrey
}
/
>
<
/DownArrowBackground
>
<
/OversizedPanel
>
<
CurrencyInputPanel
title
=
{
t
(
'
output
'
)}
description
=
{
outputValueFormatted
&&
independentField
===
INPUT
?
estimatedText
:
''
}
extraText
=
{
outputBalanceFormatted
&&
formatBalance
(
outputBalanceFormatted
)}
onCurrencySelected
=
{
outputCurrency
=>
{
dispatchSwapState
({
type
:
'
SELECT_CURRENCY
'
,
payload
:
{
currency
:
outputCurrency
,
field
:
OUTPUT
}
})
}}
onValueChange
=
{
outputValue
=>
{
dispatchSwapState
({
type
:
'
UPDATE_INDEPENDENT
'
,
payload
:
{
value
:
outputValue
,
field
:
OUTPUT
}
})
}}
selectedTokens
=
{[
inputCurrency
,
outputCurrency
]}
selectedTokenAddress
=
{
outputCurrency
}
value
=
{
outputValueFormatted
}
errorMessage
=
{
independentField
===
OUTPUT
?
independentError
:
''
}
disableUnlock
/>
<
OversizedPanel
hideBottom
>
<
ExchangeRateWrapper
onClick
=
{()
=>
{
setInverted
(
inverted
=>
!
inverted
)
}}
>
<
ExchangeRate
>
{
t
(
'
exchangeRate
'
)}
<
/ExchangeRate
>
{
inverted
?
(
<
span
>
{
exchangeRate
?
`1
${
outputSymbol
}
=
${
amountFormatter
(
exchangeRateInverted
,
18
,
4
,
false
)}
${
inputSymbol
}
`
:
'
-
'
}
<
/span
>
)
:
(
<
span
>
{
exchangeRate
?
`1
${
inputSymbol
}
=
${
amountFormatter
(
exchangeRate
,
18
,
4
,
false
)}
${
outputSymbol
}
`
:
'
-
'
}
<
/span
>
)}
<
/ExchangeRateWrapper
>
<
/OversizedPanel
>
{
renderSummary
()}
<
Flex
>
<
Button
disabled
=
{
!
isValid
}
onClick
=
{
onSwap
}
warning
=
{
highSlippageWarning
}
>
{
highSlippageWarning
?
t
(
'
swapAnyway
'
)
:
t
(
'
swap
'
)}
<
/Button
>
<
/Flex
>
<
/
>
)
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment