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
c8f8d838
Unverified
Commit
c8f8d838
authored
Jul 30, 2019
by
Ian Lapham
Committed by
GitHub
Jul 30, 2019
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #376 from ianlapham/combine-swap-send
Combine Swap and Send Into 1 Component
parents
767ce864
3a23d84d
Changes
4
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
825 additions
and
1471 deletions
+825
-1471
index.jsx
src/components/ExchangePage/index.jsx
+721
-0
index.js
src/components/TransactionDetails/index.js
+97
-20
index.js
src/pages/Send/index.js
+4
-781
index.js
src/pages/Swap/index.js
+3
-670
No files found.
src/components/ExchangePage/index.jsx
0 → 100644
View file @
c8f8d838
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
=
100
const
TOKEN_ALLOWED_SLIPPAGE_DEFAULT
=
100
// 15 minutes, denominated in seconds
const
DEADLINE_FROM_NOW
=
60
*
15
// % above the calculated gas cost that we actually send, 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
)
const
allowedSlippageBig
=
ethers
.
utils
.
bigNumberify
(
rawSlippage
)
const
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 address for 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 ${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
}
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 @
c8f8d838
import
React
,
{
useState
,
useEffect
,
useRef
}
from
'
react
'
import
ReactGA
from
'
react-ga
'
import
{
useTranslation
}
from
'
react-i18next
'
import
styled
,
{
css
,
keyframes
}
from
'
styled-components
'
import
{
darken
,
lighten
}
from
'
polished
'
import
{
amountFormatter
}
from
'
../../utils
'
import
{
isAddress
,
amountFormatter
}
from
'
../../utils
'
import
{
useDebounce
}
from
'
../../hooks
'
import
question
from
'
../../assets/images/question.svg
'
...
...
@@ -17,8 +18,6 @@ const WARNING_TYPE = Object.freeze({
riskyEntryLow
:
'
riskyEntryLow
'
})
const
b
=
text
=>
<
Bold
>
{
text
}
<
/Bold
>
const
Flex
=
styled
.
div
`
display: flex;
justify-content: center;
...
...
@@ -321,6 +320,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
...
...
@@ -338,7 +341,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
}
...
...
@@ -471,10 +480,10 @@ export default function TransactionDetails(props) {
}
>
{
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
===
WARNING_TYPE
.
emptyInput
&&
'
Enter a slippage percentage
'
}
{
warningType
===
WARNING_TYPE
.
invalidEntryBound
&&
'
Please select
a value no greater
than 50%
'
}
{
warningType
===
WARNING_TYPE
.
riskyEntryHigh
&&
'
Your transaction may be frontrun
'
}
{
warningType
===
WARNING_TYPE
.
riskyEntryLow
&&
'
Your transaction may fail
'
}
<
/BottomError
>
<
/SlippageRow
>
<
/SlippageSelector
>
...
...
@@ -486,7 +495,7 @@ export default function TransactionDetails(props) {
setActiveIndex
(
4
)
inputRef
.
current
.
focus
()
// if there's a value, evaluate the bounds
checkBounds
(
user
Input
)
checkBounds
(
debounced
Input
)
}
// used for slippage presets
...
...
@@ -508,20 +517,20 @@ export default function TransactionDetails(props) {
}
// check bounds and set errors
if
(
slippageValue
<
0
||
slippageValue
>
50
)
{
if
(
Number
(
slippageValue
)
<
0
||
Number
(
slippageValue
)
>
50
)
{
props
.
setcustomSlippageError
(
'
invalid
'
)
return
setWarningType
(
WARNING_TYPE
.
invalidEntryBound
)
}
if
(
slippageValue
>=
0
&&
slippageValue
<
0.1
)
{
if
(
Number
(
slippageValue
)
>=
0
&&
Number
(
slippageValue
)
<
0.1
)
{
props
.
setcustomSlippageError
(
'
valid
'
)
setWarningType
(
WARNING_TYPE
.
riskyEntryLow
)
}
if
(
slippageValue
>
5
)
{
if
(
Number
(
slippageValue
)
>
5
)
{
props
.
setcustomSlippageError
(
'
warning
'
)
setWarningType
(
WARNING_TYPE
.
riskyEntryHigh
)
}
//update the actual slippage value in parent
updateSlippage
(
slippageValue
)
updateSlippage
(
Number
(
slippageValue
)
)
}
// check that the theyve entered number and correct decimal
...
...
@@ -538,16 +547,54 @@ export default function TransactionDetails(props) {
const
updateSlippage
=
newSlippage
=>
{
// round to 2 decimals to prevent ethers error
let
numParsed
=
parse
Float
((
newSlippage
*
100
).
toFixed
(
2
)
)
let
numParsed
=
parse
Int
(
newSlippage
*
100
)
// 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
(
return
props
.
sending
?
(
<
TransactionInfo
>
<
div
>
{
t
(
'
youAreSelling
'
)}{
'
'
}
<
ValueWrapper
>
{
b
(
`
${
amountFormatter
(
props
.
independentValueParsed
,
props
.
independentDecimals
,
Math
.
min
(
4
,
props
.
independentDecimals
)
)}
${
props
.
inputSymbol
}
`
)}
<
/ValueWrapper
>
.
<
/div
>
<
LastSummaryText
>
{
b
(
props
.
recipientAddress
)}
{
t
(
'
willReceive
'
)}{
'
'
}
<
ValueWrapper
>
{
b
(
`
${
amountFormatter
(
props
.
dependentValueMinumum
,
props
.
dependentDecimals
,
Math
.
min
(
4
,
props
.
dependentDecimals
)
)}
${
props
.
outputSymbol
}
`
)}
<
/ValueWrapper>{' '
}
<
/LastSummaryText
>
<
LastSummaryText
>
{
t
(
'
priceChange
'
)}
<
ValueWrapper
>
{
b
(
`
${
props
.
percentSlippageFormatted
}
%`
)}
<
/ValueWrapper>
.
<
/LastSummaryText
>
<
/TransactionInfo
>
)
:
(
<
TransactionInfo
>
<
div
>
{
t
(
'
youAreSelling
'
)}{
'
'
}
...
...
@@ -560,7 +607,7 @@ export default function TransactionDetails(props) {
)}
${
props
.
inputSymbol
}
`
)}
<
/ValueWrapper>{' '
}
{
t
(
'
forAtLeast
'
)}
{
'
'
}
{
t
(
'
forAtLeast
'
)}
<
ValueWrapper
>
{
b
(
`
${
amountFormatter
(
...
...
@@ -573,12 +620,43 @@ export default function TransactionDetails(props) {
.
<
/div
>
<
LastSummaryText
>
{
t
(
'
priceChange
'
)}
<
ValueWrapper
>
{
b
(
`
${
props
.
percentSlippageFormatted
}
%`
)}
<
/ValueWrapper
>
{
t
(
'
priceChange
'
)}
<
ValueWrapper
>
{
b
(
`
${
props
.
percentSlippageFormatted
}
%`
)}
<
/ValueWrapper>
.
<
/LastSummaryText
>
<
/TransactionInfo
>
)
}
else
{
return
(
return
props
.
sending
?
(
<
TransactionInfo
>
<
div
>
{
t
(
'
youAreSending
'
)}{
'
'
}
<
ValueWrapper
>
{
b
(
`
${
amountFormatter
(
props
.
independentValueParsed
,
props
.
independentDecimals
,
Math
.
min
(
4
,
props
.
independentDecimals
)
)}
${
props
.
outputSymbol
}
`
)}
<
/ValueWrapper>{' '
}
{
t
(
'
to
'
)}
{
b
(
props
.
recipientAddress
)}.
<
/div
>
<
LastSummaryText
>
{
t
(
'
itWillCost
'
)}{
'
'
}
<
ValueWrapper
>
{
b
(
`
${
amountFormatter
(
props
.
dependentValueMaximum
,
props
.
dependentDecimals
,
Math
.
min
(
4
,
props
.
dependentDecimals
)
)}
${
props
.
inputSymbol
}
`
)}
<
/ValueWrapper>{' '
}
<
/LastSummaryText
>
<
LastSummaryText
>
{
t
(
'
priceChange
'
)}
<
ValueWrapper
>
{
b
(
`
${
props
.
percentSlippageFormatted
}
%`
)}
<
/ValueWrapper>
.
<
/LastSummaryText
>
<
/TransactionInfo
>
)
:
(
<
TransactionInfo
>
<
div
>
{
t
(
'
youAreBuying
'
)}{
'
'
}
...
...
@@ -604,10 +682,9 @@ export default function TransactionDetails(props) {
)}
${
props
.
inputSymbol
}
`
)}
<
/ValueWrapper>{' '
}
{
t
(
'
orTransFail
'
)}
<
/LastSummaryText
>
<
LastSummaryText
>
{
t
(
'
priceChange
'
)}
<
ValueWrapper
>
{
b
(
`
${
props
.
percentSlippageFormatted
}
%`
)}
<
/ValueWrapper
>
{
t
(
'
priceChange
'
)}
<
ValueWrapper
>
{
b
(
`
${
props
.
percentSlippageFormatted
}
%`
)}
<
/ValueWrapper>
.
<
/LastSummaryText
>
<
/TransactionInfo
>
)
...
...
src/pages/Send/index.js
View file @
c8f8d838
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 @
c8f8d838
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