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
e19e150f
Commit
e19e150f
authored
Jul 29, 2019
by
ian-jh
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
combine swap and send pages into one component
parent
38dcc1ba
Changes
6
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
806 additions
and
1492 deletions
+806
-1492
ExchangePage.jsx
src/components/ExchangePage/ExchangePage.jsx
+0
-6
index.jsx
src/components/ExchangePage/index.jsx
+722
-0
index.js
src/components/TransactionDetails/index.js
+74
-5
App.js
src/pages/App.js
+2
-2
index.js
src/pages/Send/index.js
+4
-781
index.js
src/pages/Swap/index.js
+4
-698
No files found.
src/components/ExchangePage/ExchangePage.jsx
deleted
100644 → 0
View file @
38dcc1ba
import
React
from
'
react
'
export
default
function
ExchangePage
({
sending
})
{
}
\ No newline at end of file
src/components/ExchangePage/index.jsx
0 → 100644
View file @
e19e150f
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/TransactionDetails/index.js
View file @
e19e150f
import
React
,
{
useState
}
from
'
react
'
import
ReactGA
from
'
react-ga
'
import
{
useTranslation
}
from
'
react-i18next
'
import
styled
from
'
styled-components
'
import
{
amountFormatter
}
from
'
../../utils
'
import
{
isAddress
,
amountFormatter
}
from
'
../../utils
'
import
questionMark
from
'
../../assets/images/question-mark.svg
'
import
NewContextualInfo
from
'
../../components/ContextualInfoNew
'
...
...
@@ -194,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
...
...
@@ -211,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
}
...
...
@@ -405,8 +416,40 @@ export default function TransactionDetails(props) {
const
b
=
text
=>
<
Bold
>
{
text
}
<
/Bold
>
const
renderTransactionDetails
=
()
=>
{
ReactGA
.
event
({
category
:
'
TransactionDetail
'
,
action
:
'
Open
'
})
if
(
props
.
independentField
===
props
.
INPUT
)
{
return
(
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
'
)}{
'
'
}
...
...
@@ -433,7 +476,34 @@ export default function TransactionDetails(props) {
<
/div
>
)
}
else
{
return
(
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
'
)}{
'
'
}
...
...
@@ -455,7 +525,6 @@ export default function TransactionDetails(props) {
Math
.
min
(
4
,
props
.
dependentDecimals
)
)}
${
props
.
inputSymbol
}
`
)}{
'
'
}
{
t
(
'
orTransFail
'
)}
<
/LastSummaryText
>
<
LastSummaryText
>
{
t
(
'
priceChange
'
)}
{
b
(
`
${
props
.
percentSlippageFormatted
}
%`
)}.
...
...
src/pages/App.js
View file @
e19e150f
...
...
@@ -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
sending
=
{
true
}
/>} /
>
<
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 @
e19e150f
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
}
renderTransactionDetails
=
{
renderTransactionDetails
}
/
>
)
}
async
function
onSwap
()
{
const
deadline
=
Math
.
ceil
(
Date
.
now
()
/
1000
)
+
DEADLINE_FROM_NOW
let
estimate
,
method
,
args
,
value
if
(
independentField
===
INPUT
)
{
ReactGA
.
event
({
category
:
`
${
swapType
}
`
,
action
:
'
TransferInput
'
})
if
(
swapType
===
ETH_TO_TOKEN
)
{
estimate
=
contract
.
estimate
.
ethToTokenTransferInput
method
=
contract
.
ethToTokenTransferInput
args
=
[
dependentValueMinumum
,
deadline
,
recipient
.
address
]
value
=
independentValueParsed
}
else
if
(
swapType
===
TOKEN_TO_ETH
)
{
estimate
=
contract
.
estimate
.
tokenToEthTransferInput
method
=
contract
.
tokenToEthTransferInput
args
=
[
independentValueParsed
,
dependentValueMinumum
,
deadline
,
recipient
.
address
]
value
=
ethers
.
constants
.
Zero
}
else
if
(
swapType
===
TOKEN_TO_TOKEN
)
{
estimate
=
contract
.
estimate
.
tokenToTokenTransferInput
method
=
contract
.
tokenToTokenTransferInput
args
=
[
independentValueParsed
,
dependentValueMinumum
,
ethers
.
constants
.
One
,
deadline
,
recipient
.
address
,
outputCurrency
]
value
=
ethers
.
constants
.
Zero
}
}
else
if
(
independentField
===
OUTPUT
)
{
ReactGA
.
event
({
category
:
`
${
swapType
}
`
,
action
:
'
TransferOutput
'
})
if
(
swapType
===
ETH_TO_TOKEN
)
{
estimate
=
contract
.
estimate
.
ethToTokenTransferOutput
method
=
contract
.
ethToTokenTransferOutput
args
=
[
independentValueParsed
,
deadline
,
recipient
.
address
]
value
=
dependentValueMaximum
}
else
if
(
swapType
===
TOKEN_TO_ETH
)
{
estimate
=
contract
.
estimate
.
tokenToEthTransferOutput
method
=
contract
.
tokenToEthTransferOutput
args
=
[
independentValueParsed
,
dependentValueMaximum
,
deadline
,
recipient
.
address
]
value
=
ethers
.
constants
.
Zero
}
else
if
(
swapType
===
TOKEN_TO_TOKEN
)
{
estimate
=
contract
.
estimate
.
tokenToTokenTransferOutput
method
=
contract
.
tokenToTokenTransferOutput
args
=
[
independentValueParsed
,
dependentValueMaximum
,
ethers
.
constants
.
MaxUint256
,
deadline
,
recipient
.
address
,
outputCurrency
]
value
=
ethers
.
constants
.
Zero
}
}
const
estimatedGasLimit
=
await
estimate
(...
args
,
{
value
})
method
(...
args
,
{
value
,
gasLimit
:
calculateGasMargin
(
estimatedGasLimit
,
GAS_MARGIN
)
}).
then
(
response
=>
{
addTransaction
(
response
)
})
}
return
(
<>
<
CurrencyInputPanel
title
=
{
t
(
'
input
'
)}
description
=
{
inputValueFormatted
&&
independentField
===
OUTPUT
?
estimatedText
:
''
}
extraText
=
{
inputBalanceFormatted
&&
formatBalance
(
inputBalanceFormatted
)}
extraTextClickHander
=
{()
=>
{
if
(
inputBalance
&&
inputDecimals
)
{
const
valueToSet
=
inputCurrency
===
'
ETH
'
?
inputBalance
.
sub
(
ethers
.
utils
.
parseEther
(
'
.1
'
))
:
inputBalance
if
(
valueToSet
.
gt
(
ethers
.
constants
.
Zero
))
{
dispatchSwapState
({
type
:
'
UPDATE_INDEPENDENT
'
,
payload
:
{
value
:
amountFormatter
(
valueToSet
,
inputDecimals
,
inputDecimals
,
false
),
field
:
INPUT
}
})
}
}
}}
onCurrencySelected
=
{
inputCurrency
=>
{
dispatchSwapState
({
type
:
'
SELECT_CURRENCY
'
,
payload
:
{
currency
:
inputCurrency
,
field
:
INPUT
}
})
}}
onValueChange
=
{
inputValue
=>
{
dispatchSwapState
({
type
:
'
UPDATE_INDEPENDENT
'
,
payload
:
{
value
:
inputValue
,
field
:
INPUT
}
})
}}
showUnlock
=
{
showUnlock
}
selectedTokens
=
{[
inputCurrency
,
outputCurrency
]}
selectedTokenAddress
=
{
inputCurrency
}
value
=
{
inputValueFormatted
}
errorMessage
=
{
inputError
?
inputError
:
independentField
===
INPUT
?
independentError
:
''
}
/
>
<
OversizedPanel
>
<
DownArrowBackground
>
<
DownArrow
onClick
=
{()
=>
{
dispatchSwapState
({
type
:
'
FLIP_INDEPENDENT
'
})
}}
clickable
alt
=
"
swap
"
src
=
{
isValid
?
ArrowDownBlue
:
ArrowDownGrey
}
/
>
<
/DownArrowBackground
>
<
/OversizedPanel
>
<
CurrencyInputPanel
title
=
{
t
(
'
output
'
)}
description
=
{
outputValueFormatted
&&
independentField
===
INPUT
?
estimatedText
:
''
}
extraText
=
{
outputBalanceFormatted
&&
formatBalance
(
outputBalanceFormatted
)}
onCurrencySelected
=
{
outputCurrency
=>
{
dispatchSwapState
({
type
:
'
SELECT_CURRENCY
'
,
payload
:
{
currency
:
outputCurrency
,
field
:
OUTPUT
}
})
}}
onValueChange
=
{
outputValue
=>
{
dispatchSwapState
({
type
:
'
UPDATE_INDEPENDENT
'
,
payload
:
{
value
:
outputValue
,
field
:
OUTPUT
}
})
}}
selectedTokens
=
{[
inputCurrency
,
outputCurrency
]}
selectedTokenAddress
=
{
outputCurrency
}
value
=
{
outputValueFormatted
}
errorMessage
=
{
independentField
===
OUTPUT
?
independentError
:
''
}
disableUnlock
/>
<
OversizedPanel
>
<
DownArrowBackground
>
<
DownArrow
src
=
{
isValid
?
ArrowDownBlue
:
ArrowDownGrey
}
alt
=
"
arrow
"
/>
<
/DownArrowBackground
>
<
/OversizedPanel
>
<
AddressInputPanel
onChange
=
{
setRecipient
}
onError
=
{
setRecipientError
}
/
>
<
OversizedPanel
hideBottom
>
<
ExchangeRateWrapper
onClick
=
{()
=>
{
setInverted
(
inverted
=>
!
inverted
)
}}
>
<
ExchangeRate
>
{
t
(
'
exchangeRate
'
)}
<
/ExchangeRate
>
{
inverted
?
(
<
span
>
{
exchangeRate
?
`1
${
outputSymbol
}
=
${
amountFormatter
(
exchangeRateInverted
,
18
,
4
,
false
)}
${
inputSymbol
}
`
:
'
-
'
}
<
/span
>
)
:
(
<
span
>
{
exchangeRate
?
`1
${
inputSymbol
}
=
${
amountFormatter
(
exchangeRate
,
18
,
4
,
false
)}
${
outputSymbol
}
`
:
'
-
'
}
<
/span
>
)}
<
/ExchangeRateWrapper
>
<
/OversizedPanel
>
{
renderSummary
()}
<
Flex
>
<
Button
disabled
=
{
!
isValid
}
onClick
=
{
onSwap
}
warning
=
{
highSlippageWarning
}
>
{
highSlippageWarning
?
t
(
'
sendAnyway
'
)
:
t
(
'
send
'
)}
<
/Button
>
<
/Flex
>
<
/
>
)
export
default
function
Send
({
initialCurrency
})
{
return
<
ExchangePage
initialCurrency
=
{
initialCurrency
}
sending
=
{
true
}
/
>
}
src/pages/Swap/index.js
View file @
e19e150f
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
AddressInputPanel
from
'
../../components/AddressInputPanel
'
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
=
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
BlueSpan
=
styled
.
span
`
color:
${({
theme
})
=>
theme
.
royalBlue
}
;
`
const
LastSummaryText
=
styled
.
div
`
margin-top: 1rem;
`
const
DownArrowBackground
=
styled
.
div
`
${({
theme
})
=>
theme
.
flexRowNoWrap
}
justify-content: center;
align-items: center;
`
const
DownArrow
=
styled
.
img
`
width: 0.625rem;
height: 0.625rem;
position: relative;
padding: 0.875rem;
cursor:
${({
clickable
})
=>
clickable
&&
'
pointer
'
}
;
`
const
ExchangeRateWrapper
=
styled
.
div
`
${({
theme
})
=>
theme
.
flexRowNoWrap
}
;
align-items: center;
color:
${({
theme
})
=>
theme
.
doveGray
}
;
font-size: 0.75rem;
padding: 0.5rem 1rem;
`
const
ExchangeRate
=
styled
.
span
`
flex: 1 1 auto;
width: 0;
color:
${({
theme
})
=>
theme
.
chaliceGray
}
;
`
const
Flex
=
styled
.
div
`
display: flex;
justify-content: center;
padding: 2rem;
button {
max-width: 20rem;
}
`
function
calculateSlippageBounds
(
value
,
token
=
false
,
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
Swap
({
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
:
'
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
/>
{
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
}
/
>
<
Flex
>
<
Button
disabled
=
{
!
isValid
||
customSlippageError
===
'
invalid
'
}
onClick
=
{
onSwap
}
warning
=
{
highSlippageWarning
||
customSlippageError
===
'
warning
'
}
>
{
highSlippageWarning
||
customSlippageError
===
'
warning
'
?
t
(
'
swapAnyway
'
)
:
t
(
'
swap
'
)}
<
/Button
>
<
/Flex
>
<
/
>
)
export
default
function
Swap
({
initialCurrency
})
{
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