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
3d452b70
Commit
3d452b70
authored
Jul 29, 2019
by
ian-jh
Browse files
Options
Browse Files
Download
Plain Diff
resolve merge conflicts
parents
d265b120
3f3889b9
Changes
7
Show whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
1019 additions
and
1775 deletions
+1019
-1775
index.js
src/components/ContextualInfoNew/index.js
+2
-1
index.jsx
src/components/ExchangePage/index.jsx
+722
-0
index.js
src/components/Modal/index.js
+2
-3
index.js
src/components/TransactionDetails/index.js
+284
-318
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
-670
No files found.
src/components/ContextualInfoNew/index.js
View file @
3d452b70
...
...
@@ -32,7 +32,8 @@ const SummaryWrapperContainer = styled.div`
const
Details
=
styled
.
div
`
background-color:
${({
theme
})
=>
theme
.
concreteGray
}
;
/* padding: 1.25rem 1.25rem 1rem 1.25rem; */
padding: 1.5rem;
padding-bottom: 1rem;
border-radius: 1rem;
font-size: 0.75rem;
margin: 1rem 0.5rem 0 0.5rem;
...
...
src/components/ExchangePage/index.jsx
0 → 100644
View file @
3d452b70
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 @
3d452b70
...
...
@@ -5,9 +5,8 @@ import { DialogOverlay, DialogContent } from '@reach/dialog'
import
'
@reach/dialog/styles.css
'
const
AnimatedDialogOverlay
=
animated
(
DialogOverlay
)
const
WrappedDialogOverlay
=
({
suppressClassNameWarning
,
...
rest
})
=>
<
AnimatedDialogOverlay
{...
rest
}
/
>
const
StyledDialogOverlay
=
styled
(
WrappedDialogOverlay
).
attrs
({
suppressClassNameWarning
:
true
const
StyledDialogOverlay
=
styled
(
AnimatedDialogOverlay
).
attrs
({
suppressclassnamewarning
:
'
true
'
})
`
&[data-reach-dialog-overlay] {
z-index: 2;
...
...
src/components/TransactionDetails/index.js
View file @
3d452b70
import
React
,
{
useState
,
useEffect
,
useRef
}
from
'
react
'
import
React
,
{
useState
}
from
'
react
'
import
ReactGA
from
'
react-ga
'
import
{
useTranslation
}
from
'
react-i18next
'
import
styled
,
{
css
,
keyframes
}
from
'
styled-components
'
import
{
transparentize
,
darken
}
from
'
polished
'
import
{
amountFormatter
}
from
'
../../utils
'
import
{
useDebounce
}
from
'
../../hooks
'
import
question
from
'
../../assets/images/question.svg
'
import
styled
from
'
styled-components
'
import
{
isAddress
,
amountFormatter
}
from
'
../../utils
'
import
questionMark
from
'
../../assets/images/question-mark.svg
'
import
NewContextualInfo
from
'
../../components/ContextualInfoNew
'
const
WARNING_TYPE
=
Object
.
freeze
({
none
:
'
none
'
,
emptyInput
:
'
emptyInput
'
,
invalidEntryBound
:
'
invalidEntryBound
'
,
riskyEntryHigh
:
'
riskyEntryHigh
'
,
riskyEntryLow
:
'
riskyEntryLow
'
})
const
b
=
text
=>
<
Bold
>
{
text
}
<
/Bold
>
const
Flex
=
styled
.
div
`
display: flex;
justify-content: center;
button {
max-width: 20rem;
}
`
const
FlexBetween
=
styled
.
div
`
display: flex;
justify-content: space-between;
align-items: center;
height: 100%;
`
const
WrappedSlippageRow
=
({
wrap
,
...
rest
})
=>
<
Flex
{...
rest
}
/
>
const
SlippageRow
=
styled
(
WrappedSlippageRow
)
`
const
SlippageRow
=
styled
(
Flex
)
`
position: relative;
flex-wrap:
${({
wrap
})
=>
wrap
&&
'
wrap
'
}
;
width: 100%
;
flex-direction: row;
justify-content: flex-start;
align-items: center;
width: 100%
;
font-size: 0.8rem
;
padding: 0;
padding-top:
${({
wrap
})
=>
wrap
&&
'
0.25rem
'
}
;
height: 24px;
margin-bottom: 14px;
`
const
QuestionWrapper
=
styled
.
button
`
display: flex;
align-items: center;
justify-content: center;
margin: 0;
padding: 0;
const
QuestionWrapper
=
styled
.
div
`
margin-left: 0.4rem;
padding: 0.2rem;
border: none;
background: none;
outline: none;
cursor: default;
border-radius: 36px;
margin-top: 0.2rem;
:hover,
:focus {
opacity: 0.7;
}
`
const
HelpCircleStyled
=
styled
.
img
`
height: 18px;
width: 18px;
`
const
fadeIn
=
keyframes
`
from {
opacity : 0;
}
to {
opacity : 1;
&:hover {
cursor: pointer;
}
`
...
...
@@ -83,135 +41,100 @@ const Popup = styled(Flex)`
width: 228px;
left: -78px;
top: -124px;
flex-direction: column;
align-items: center;
padding: 0.6rem 1rem;
line-height: 150%;
background:
${({
theme
})
=>
theme
.
charcoalBlack
}
;
border-radius: 8px;
aligm-items: center;
padding: 1rem;
animation:
${
fadeIn
}
0.15s linear;
line-height: 183.52%;
background: #404040;
border-radius: 8px;
color: white;
font-style: italic;
${({
theme
})
=>
theme
.
mediaWidth
.
upToSmall
`
left: -20px;
`
}
`
const
FancyButton
=
styled
.
button
`
const
Option
=
styled
(
Flex
)
`
align-items: center;
min-width: 55px;
height: 2rem;
height: 24px;
margin-right: 4px;
border-radius: 36px;
border: 1px solid
${({
theme
})
=>
theme
.
mercuryGray
}
;
outline: none;
background:
${({
theme
})
=>
theme
.
white
}
;
border: 1px solid #f2f2f2;
:hover {
cursor: inherit;
border: 1px solid
${({
theme
})
=>
theme
.
royalBlue
}
;
box-shadow:
${({
theme
})
=>
transparentize
(
0.6
,
theme
.
royalBlue
)}
0px 0px 0px 2px;
}
:focus {
box-shadow:
${({
theme
})
=>
transparentize
(
0.6
,
theme
.
royalBlue
)}
0px 0px 0px 2px;
}
`
const
Option
=
styled
(
FancyButton
)
`
margin-right: 8px;
margin-top: 6px;
${({
active
,
theme
})
=>
${({
active
})
=>
active
&&
css
`
background-color:
${({
theme
})
=>
theme
.
royalBlue
}
;
color:
${({
theme
})
=>
theme
.
white
}
;
border: none
;
`
background-color: #2f80ed
;
color: white
;
border: 1px solid #2f80ed
;
`
}
`
const
OptionLarge
=
styled
(
Option
)
`
width: 120px;
&:hover {
cursor: pointer;
}
`
const
Input
=
styled
.
input
`
background:
${({
theme
})
=>
theme
.
white
}
;
flex-grow: 1
;
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;
}
cursor: inherit;
color:
${({
theme
})
=>
theme
.
doveGray
}
;
text-align: left;
${({
active
})
=>
active
&&
css
`
color: initial;
cursor: initial;
`
border: 1px solid #2f80ed;
text-align: right;
padding-right 1.5rem;
padding-left 0rem;
color : inherit;
`
}
${({
placeholder
})
=>
placeholder
!==
'
Custom
'
&&
css
`
text-align: right;
`
}
${({
color
})
=>
color
===
'
red
'
&&
css
`
color:
${({
theme
})
=>
theme
.
salmonRed
}
;
${({
warning
})
=>
warning
===
'
red
'
&&
`
color : #FF6871;
border: 1px solid #FF6871;
`
}
`
const
BottomError
=
styled
.
div
`
${({
show
})
=>
show
&&
css
`
padding-top: 12px;
`
}
color:
${({
theme
})
=>
theme
.
doveGray
}
;
${({
color
})
=>
color
===
'
red
'
&&
css
`
color:
${({
theme
})
=>
theme
.
salmonRed
}
;
`
}
`
const
OptionCustom
=
styled
(
FancyButton
)
`
height: 2rem;
position: relative;
width: 120px;
margin-top: 6px;
padding: 0 0.75rem;
${({
active
})
=>
active
&&
css
`
border: 1px solid
${({
theme
})
=>
theme
.
royalBlue
}
;
`
}
margin-top: 1rem;
color: #aeaeae;
${({
color
})
=>
color
===
'
red
'
&&
css
`
border: 1px solid
${({
theme
})
=>
theme
.
salmonRed
}
;
`
color : #FF6871
;
`
}
`
input {
const
Break
=
styled
.
div
`
border: 1px solid #f2f2f2;
width: 100%;
height: 100%;
border: 0px;
border-radius: 2rem;
}
margin-top: 1rem;
`
const
OptionLarge
=
styled
(
Option
)
`
width: 120px;
`
const
Bold
=
styled
.
span
`
...
...
@@ -223,55 +146,44 @@ const LastSummaryText = styled.div`
`
const
SlippageSelector
=
styled
.
div
`
background-color:
${({
theme
})
=>
darken
(
0.04
,
theme
.
concreteGray
)}
;
padding: 1rem 1.25rem 1rem 1.25rem;
border-radius: 12px;
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;
flex-grow: 0;
${({
color
,
theme
})
=>
${({
color
})
=>
(
color
===
'
faded
'
&&
css
`
color:
${
theme
.
doveGray
}
;
`
color : #AEAEAE
`
)
||
(
color
===
'
red
'
&&
css
`
color:
${
theme
.
salmonRed
}
;
`
)}
;
`
color : #FF6871
`
)}
`
const
Faded
=
styled
.
span
`
opacity: 0.7;
`
const
TransactionInfo
=
styled
.
div
`
padding: 1.25rem 1.25rem 1rem 1.25rem;
const
ErrorEmoji
=
styled
.
span
`
left: 30px;
top: 4px;
position: absolute;
`
export
default
function
TransactionDetails
(
props
)
{
const
{
t
}
=
useTranslation
()
const
[
activeIndex
,
setActiveIndex
]
=
useState
(
3
)
const
[
warningType
,
setWarningType
]
=
useState
(
WARNING_TYPE
.
none
)
const
inputRef
=
useRef
()
const
[
showPopup
,
setPopup
]
=
useState
(
false
)
const
[
userInput
,
setUserInput
]
=
useState
(
''
)
const
debouncedInput
=
useDebounce
(
userInput
,
150
)
useEffect
(()
=>
{
if
(
activeIndex
===
4
)
{
checkBounds
(
debouncedInput
)
}
})
function
renderSummary
()
{
let
contextualInfo
=
''
let
isError
=
false
...
...
@@ -283,6 +195,10 @@ export default function TransactionDetails(props) {
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
...
...
@@ -300,7 +216,13 @@ export default function TransactionDetails(props) {
closeDetailsText
=
{
t
(
'
hideDetails
'
)}
contextualInfo
=
{
contextualInfo
?
contextualInfo
:
slippageWarningText
}
allowExpand
=
{
!!
(
props
.
inputCurrency
&&
props
.
outputCurrency
&&
props
.
inputValueParsed
&&
props
.
outputValueParsed
)
!!
(
props
.
inputCurrency
&&
props
.
outputCurrency
&&
props
.
inputValueParsed
&&
props
.
outputValueParsed
&&
(
props
.
sending
?
props
.
recipientAddress
:
true
)
)
}
isError
=
{
isError
}
slippageWarning
=
{
props
.
slippageWarning
&&
!
contextualInfo
}
...
...
@@ -311,17 +233,23 @@ export default function TransactionDetails(props) {
)
}
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
add
i
tional
price
slippage
Limit
addtional
price
slippage
<
QuestionWrapper
onClick
=
{()
=>
{
setPopup
(
!
showPopup
)
}}
onMouseEnter
=
{()
=>
{
setPopup
(
true
)
}}
...
...
@@ -329,114 +257,105 @@ export default function TransactionDetails(props) {
setPopup
(
false
)
}}
>
<
HelpCircleStyled
src
=
{
question
}
alt
=
"
popup
"
/>
<
img
src
=
{
questionMark
}
alt
=
"
question mark
"
/>
<
/QuestionWrapper
>
{
showPopup
?
(
<
Popup
>
Lowering
this
limit
decreases
your
risk
of
frontrunning
.
However
,
this
makes
it
more
likely
that
your
transaction
will
fail
due
to
normal
price
movements
.
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
wrap
>
<
SlippageRow
>
<
Option
onClick
=
{()
=>
{
setFromFixed
(
1
,
0.1
)
updateSlippage
(
0.1
)
setWarningType
(
'
none
'
)
setActiveIndex
(
1
)
props
.
setcustomSlippageError
(
'
valid
'
)
setplaceHolder
(
'
Custom
'
)
}}
active
=
{
activeIndex
===
1
}
active
=
{
activeIndex
===
1
?
true
:
false
}
>
0.1
%
<
/Option
>
<
Option
onClick
=
{()
=>
{
setFromFixed
(
2
,
0.5
)
updateSlippage
(
1
)
setWarningType
(
'
none
'
)
setActiveIndex
(
2
)
props
.
setcustomSlippageError
(
'
valid
'
)
setplaceHolder
(
'
Custom
'
)
}}
active
=
{
activeIndex
===
2
}
active
=
{
activeIndex
===
2
?
true
:
false
}
>
0.5
%
1
%
<
/Option
>
<
OptionLarge
onClick
=
{()
=>
{
setFromFixed
(
3
,
1
)
updateSlippage
(
2
)
setWarningType
(
'
none
'
)
setActiveIndex
(
3
)
props
.
setcustomSlippageError
(
'
valid
'
)
setplaceHolder
(
'
Custom
'
)
}}
active
=
{
activeIndex
===
3
}
active
=
{
activeIndex
===
3
?
true
:
false
}
>
1
%
<
Faded
>
(
suggested
)
<
/Faded
>
2
%
<
Faded
>
(
suggested
)
<
/Faded
>
<
/OptionLarge
>
<
OptionCustom
active
=
{
activeIndex
===
4
}
color
=
{
warningType
===
WARNING_TYPE
.
emptyInput
?
''
:
warningType
!==
WARNING_TYPE
.
none
&&
warningType
!==
WARNING_TYPE
.
riskyEntryLow
?
'
red
'
:
''
}
onClick
=
{()
=>
{
setFromCustom
()
}}
>
<
FlexBetween
>
{
!
(
warningType
===
WARNING_TYPE
.
none
||
warningType
===
WARNING_TYPE
.
emptyInput
)
&&
(
<
span
role
=
"
img
"
aria
-
label
=
"
warning
"
>
⚠️
<
/span
>
)}
<
InputGroup
>
{
warningType
!==
'
none
'
?
<
ErrorEmoji
>
⚠️
<
/ErrorEmoji> : ''
}
<
Input
tabIndex
=
{
-
1
}
ref
=
{
inputRef
}
active
=
{
activeIndex
===
4
}
placeholder
=
{
activeIndex
===
4
?
!!
userInput
?
''
:
'
0
'
:
activeIndex
!==
4
&&
userInput
!==
''
?
userInput
:
'
Custom
'
}
value
=
{
activeIndex
===
4
?
userInput
:
''
}
placeholder
=
{
placeHolder
}
value
=
{
userInput
||
''
}
onChange
=
{
parseInput
}
color
=
{
warningType
===
WARNING_TYPE
.
emptyInput
onClick
=
{
e
=>
{
setActiveIndex
(
4
)
setplaceHolder
(
''
)
parseInput
(
e
)
}}
active
=
{
activeIndex
===
4
?
true
:
false
}
warning
=
{
warningType
===
'
emptyInput
'
?
''
:
warningType
!==
WARNING_TYPE
.
none
&&
warningType
!==
WARNING_TYPE
.
riskyEntryLow
:
warningType
!==
'
none
'
&&
warningType
!==
'
riskyEntryLow
'
?
'
red
'
:
''
}
/
>
<
Percent
color
=
{
activeIndex
!==
4
warningType
===
'
emptyInput
'
?
'
faded
'
:
warningType
===
WARNING_TYPE
.
riskyEntryHigh
||
warningType
===
WARNING_TYPE
.
invalidEntryBound
:
warningType
!==
'
none
'
&&
warningType
!==
'
riskyEntryLow
'
?
'
red
'
:
activeIndex
!==
4
?
'
faded
'
:
''
}
>
%
<
/Percent
>
<
/FlexBetween
>
<
/OptionCustom
>
<
/InputGroup
>
<
/SlippageRow
>
<
SlippageRow
>
<
BottomError
show
=
{
activeIndex
===
4
}
color
=
{
warningType
===
WARNING_TYPE
.
emptyInput
warningType
===
'
emptyInput
'
?
''
:
warningType
!==
WARNING_TYPE
.
none
&&
warningType
!==
WARNING_TYPE
.
riskyEntryLow
:
warningType
!==
'
none
'
&&
warningType
!==
'
riskyEntryLow
'
?
'
red
'
:
''
}
>
{
activeIndex
===
4
&&
warningType
.
toString
()
===
'
none
'
&&
'
Custom slippage value entered
'
}
{
warningType
===
WARNING_TYPE
.
emptyInput
&&
'
Enter a slippage percentage.
'
}
{
warningType
===
WARNING_TYPE
.
invalidEntryBound
&&
'
Please select value less than 50%
'
}
{
warningType
===
WARNING_TYPE
.
riskyEntryHigh
&&
'
Your transaction may be frontrun.
'
}
{
warningType
===
WARNING_TYPE
.
riskyEntryLow
&&
'
Your transaction may fail.
'
}
{
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
>
...
...
@@ -444,73 +363,94 @@ export default function TransactionDetails(props) {
)
}
const
setFromCustom
=
()
=>
{
setActiveIndex
(
4
)
inputRef
.
current
.
focus
()
// if there's a value, evaluate the bounds
checkBounds
(
userInput
)
}
const
[
userInput
,
setUserInput
]
=
useState
()
// used for slippage presets
const
setFromFixed
=
(
index
,
slippage
)
=>
{
// update slippage in parent, reset errors and input state
updateSlippage
(
slippage
)
setWarningType
(
WARNING_TYPE
.
none
)
setActiveIndex
(
index
)
props
.
setcustomSlippageError
(
'
valid`
'
)
}
const
checkBounds
=
slippageValue
=>
{
setWarningType
(
WARNING_TYPE
.
none
)
props
.
setcustomSlippageError
(
'
valid
'
)
if
(
slippageValue
===
''
)
{
const
parseInput
=
e
=>
{
let
input
=
e
.
target
.
value
if
(
input
===
''
)
{
setUserInput
(
input
)
props
.
setcustomSlippageError
(
'
invalid
'
)
return
setWarningType
(
WARNING_TYPE
.
emptyInput
)
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
'
)
}
}
// check bounds and set errors
if
(
slippageValue
<
0
||
slippageValue
>
50
)
{
const
checkAcceptablePercentValue
=
input
=>
{
setTimeout
(
function
()
{
setWarningType
(
'
none
'
)
props
.
setcustomSlippageError
(
'
valid
'
)
if
(
input
<
0
||
input
>
50
)
{
props
.
setcustomSlippageError
(
'
invalid
'
)
return
setWarningType
(
WARNING_TYPE
.
invalidEntryBound
)
return
setWarningType
(
'
invalidEntryBound
'
)
}
if
(
slippageValue
>=
0
&&
slippageValue
<
0.1
)
{
if
(
input
>=
0
&&
input
<
0.1
)
{
props
.
setcustomSlippageError
(
'
valid
'
)
setWarningType
(
WARNING_TYPE
.
riskyEntryLow
)
setWarningType
(
'
riskyEntryLow
'
)
}
if
(
slippageValue
>
5
)
{
if
(
input
>=
5
)
{
props
.
setcustomSlippageError
(
'
warning
'
)
setWarningType
(
WARNING_TYPE
.
riskyEntryHigh
)
}
//update the actual slippage value in parent
updateSlippage
(
slippageValue
)
}
// check that the theyve entered number and correct decimal
const
parseInput
=
e
=>
{
let
input
=
e
.
target
.
value
// restrict to 2 decimal places
let
acceptableValues
=
[
/^$/
,
/^
\d{1,2}
$/
,
/^
\d{0,2}\.\d{0,2}
$/
]
// if its within accepted decimal limit, update the input state
if
(
acceptableValues
.
some
(
a
=>
a
.
test
(
input
)))
{
setUserInput
(
input
)
setWarningType
(
'
riskyEntryHigh
'
)
}
updateSlippage
(
input
)
},
300
)
}
const
updateSlippage
=
newSlippage
=>
{
// round to 2 decimals to prevent ethers error
let
numParsed
=
parseFloat
((
newSlippage
*
100
).
toFixed
(
2
))
// set both slippage values in parents
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
(
<
TransactionInfo
>
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
(
...
...
@@ -533,11 +473,38 @@ export default function TransactionDetails(props) {
<
LastSummaryText
>
{
t
(
'
priceChange
'
)}
{
b
(
`
${
props
.
percentSlippageFormatted
}
%`
)}.
<
/LastSummaryText
>
<
/
TransactionInfo
>
<
/
div
>
)
}
else
{
return
(
<
TransactionInfo
>
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
(
...
...
@@ -558,12 +525,11 @@ export default function TransactionDetails(props) {
Math
.
min
(
4
,
props
.
dependentDecimals
)
)}
${
props
.
inputSymbol
}
`
)}{
'
'
}
{
t
(
'
orTransFail
'
)}
<
/LastSummaryText
>
<
LastSummaryText
>
{
t
(
'
priceChange
'
)}
{
b
(
`
${
props
.
percentSlippageFormatted
}
%`
)}.
<
/LastSummaryText
>
<
/
TransactionInfo
>
<
/
div
>
)
}
}
...
...
src/pages/App.js
View file @
3d452b70
...
...
@@ -46,7 +46,7 @@ export default function App() {
{
/* this Suspense is for route code-splitting */
}
<
Suspense
fallback
=
{
null
}
>
<
Switch
>
<
Route
exact
strict
path
=
"
/swap
"
component
=
{
Swap
}
/
>
<
Route
exact
strict
path
=
"
/swap
"
component
=
{
()
=>
<
Swap
/>
}
/
>
<
Route
exact
strict
...
...
@@ -59,7 +59,7 @@ export default function App() {
}
}}
/
>
<
Route
exact
strict
path
=
"
/send
"
component
=
{
Send
}
/
>
<
Route
exact
strict
path
=
"
/send
"
component
=
{
()
=>
<
Send
/>
}
/
>
<
Route
exact
strict
...
...
src/pages/Send/index.js
View file @
3d452b70
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
React
from
'
react
'
import
ExchangePage
from
'
../../components/ExchangePage
'
import
{
Button
}
from
'
../../theme
'
import
CurrencyInputPanel
from
'
../../components/CurrencyInputPanel
'
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
}
dropDownContent
=
{
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
${
inputSymbol
}
=
${
amountFormatter
(
exchangeRate
,
18
,
4
,
false
)}
${
outputSymbol
}
`
:
'
-
'
}
<
/span
>
)
:
(
<
span
>
{
exchangeRate
?
`1
${
outputSymbol
}
=
${
amountFormatter
(
exchangeRateInverted
,
18
,
4
,
false
)}
${
inputSymbol
}
`
:
'
-
'
}
<
/span
>
)}
<
/ExchangeRateWrapper
>
<
/OversizedPanel
>
{
renderSummary
()}
<
Flex
>
<
Button
disabled
=
{
!
isValid
}
onClick
=
{
onSwap
}
warning
=
{
highSlippageWarning
}
>
{
highSlippageWarning
?
t
(
'
sendAnyway
'
)
:
t
(
'
send
'
)}
<
/Button
>
<
/Flex
>
<
/
>
)
export
default
function
Send
({
initialCurrency
})
{
return
<
ExchangePage
initialCurrency
=
{
initialCurrency
}
sending
=
{
true
}
/
>
}
src/pages/Swap/index.js
View file @
3d452b70
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
'
../../components/CurrencyInputPanel
'
import
OversizedPanel
from
'
../../components/OversizedPanel
'
import
TransactionDetails
from
'
../../components/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
=
100
const
TOKEN_ALLOWED_SLIPPAGE_DEFAULT
=
100
// 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
{}
}
}
import
React
from
'
react
'
import
ExchangePage
from
'
../../components/ExchangePage
'
export
default
function
Swap
({
initialCurrency
})
{
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
// 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
=
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
:
'
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
)
})
}
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
/>
<
OversizedPanel
hideBottom
>
<
ExchangeRateWrapper
onClick
=
{()
=>
{
setInverted
(
inverted
=>
!
inverted
)
}}
>
<
ExchangeRate
>
{
t
(
'
exchangeRate
'
)}
<
/ExchangeRate
>
{
inverted
?
(
<
span
>
{
exchangeRate
?
`1
${
inputSymbol
}
=
${
amountFormatter
(
exchangeRate
,
18
,
4
,
false
)}
${
outputSymbol
}
`
:
'
-
'
}
<
/span
>
)
:
(
<
span
>
{
exchangeRate
?
`1
${
outputSymbol
}
=
${
amountFormatter
(
exchangeRateInverted
,
18
,
4
,
false
)}
${
inputSymbol
}
`
:
'
-
'
}
<
/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
}
/
>
<
Flex
>
<
Button
disabled
=
{
!
isValid
||
customSlippageError
===
'
invalid
'
}
onClick
=
{
onSwap
}
warning
=
{
highSlippageWarning
||
customSlippageError
===
'
warning
'
}
>
{
highSlippageWarning
||
customSlippageError
===
'
warning
'
?
t
(
'
swapAnyway
'
)
:
t
(
'
swap
'
)}
<
/Button
>
<
/Flex
>
<
/
>
)
return
<
ExchangePage
initialCurrency
=
{
initialCurrency
}
/
>
}
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