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
0f519911
Unverified
Commit
0f519911
authored
Feb 15, 2022
by
Zach Pomerantz
Committed by
GitHub
Feb 15, 2022
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: improved warning ux (#3310)
parent
da8884d8
Changes
8
Show whitespace changes
Inline
Side-by-side
Showing
8 changed files
with
57 additions
and
73 deletions
+57
-73
Input.tsx
src/lib/components/Swap/Input.tsx
+2
-2
Output.tsx
src/lib/components/Swap/Output.tsx
+17
-11
MaxSlippageSelect.tsx
src/lib/components/Swap/Settings/MaxSlippageSelect.tsx
+12
-30
Details.tsx
src/lib/components/Swap/Summary/Details.tsx
+6
-17
index.tsx
src/lib/components/Swap/Summary/index.tsx
+4
-8
useAllowedSlippage.ts
src/lib/hooks/useAllowedSlippage.ts
+10
-1
settings.ts
src/lib/state/settings.ts
+0
-4
prices.ts
src/utils/prices.ts
+6
-0
No files found.
src/lib/components/Swap/Input.tsx
View file @
0f519911
...
...
@@ -16,7 +16,7 @@ import Row from '../Row'
import
TokenImg
from
'
../TokenImg
'
import
TokenInput
from
'
./TokenInput
'
export
const
Loading
Span
=
styled
.
span
<
{
$loading
:
boolean
}
>
`
export
const
Loading
Row
=
styled
(
Row
)
<
{
$loading
:
boolean
}
>
`
${
loadingOpacityCss
}
;
`
...
...
@@ -85,7 +85,7 @@ export default function Input({ disabled, focused }: InputProps) {
>
<
ThemedText
.
Body2
color=
"secondary"
>
<
Row
>
<
Loading
Span
$loading=
{
isLoading
}
>
{
inputUSDC
?
`$${inputUSDC.toFixed(2)}`
:
'
-
'
}
</
LoadingSpan
>
<
Loading
Row
$loading=
{
isLoading
}
>
{
inputUSDC
?
`$${inputUSDC.toFixed(2)}`
:
'
-
'
}
</
LoadingRow
>
{
balance
&&
(
<
Balance
color=
{
inputCurrencyAmount
?.
greaterThan
(
balance
)
?
'
error
'
:
undefined
}
focused=
{
focused
}
>
Balance:
<
span
style=
{
{
userSelect
:
'
text
'
}
}
>
{
formatCurrencyAmount
(
balance
,
4
,
i18n
.
locale
)
}
</
span
>
...
...
src/lib/components/Swap/Output.tsx
View file @
0f519911
...
...
@@ -12,10 +12,11 @@ import { PropsWithChildren, useMemo } from 'react'
import
{
TradeState
}
from
'
state/routing/types
'
import
{
computeFiatValuePriceImpact
}
from
'
utils/computeFiatValuePriceImpact
'
import
{
formatCurrencyAmount
}
from
'
utils/formatCurrencyAmount
'
import
{
getPriceImpactWarning
}
from
'
utils/prices
'
import
Column
from
'
../Column
'
import
Row
from
'
../Row
'
import
{
Balance
,
InputProps
,
Loading
Span
}
from
'
./Input
'
import
{
Balance
,
InputProps
,
Loading
Row
}
from
'
./Input
'
import
TokenInput
from
'
./TokenInput
'
export
const
colorAtom
=
atom
<
string
|
undefined
>
(
undefined
)
...
...
@@ -63,16 +64,19 @@ export default function Output({ disabled, focused, children }: PropsWithChildre
const
outputUSDC
=
useUSDCValue
(
outputCurrencyAmount
)
const
priceImpact
=
useMemo
(()
=>
{
const
computedChange
=
computeFiatValuePriceImpact
(
inputUSDC
,
outputUSDC
)
return
computedChange
?
parseFloat
(
computedChange
.
multiply
(
-
1
)?.
toSignificant
(
3
))
:
undefined
},
[
inputUSDC
,
outputUSDC
])
const
fiatValuePriceImpact
=
computeFiatValuePriceImpact
(
inputUSDC
,
outputUSDC
)
if
(
!
fiatValuePriceImpact
)
return
null
const
usdc
=
useMemo
(()
=>
{
if
(
outputUSDC
)
{
return
`$
${
outputUSDC
.
toFixed
(
2
)}
(
${
priceImpact
&&
priceImpact
>
0
?
'
+
'
:
''
}${
priceImpact
}
%)`
}
return
''
},
[
priceImpact
,
outputUSDC
])
const
color
=
getPriceImpactWarning
(
fiatValuePriceImpact
)
const
sign
=
fiatValuePriceImpact
.
lessThan
(
0
)
?
'
+
'
:
''
const
displayedPriceImpact
=
parseFloat
(
fiatValuePriceImpact
.
multiply
(
-
1
)?.
toSignificant
(
3
))
return
(
<
ThemedText
.
Body2
color=
{
color
}
>
(
{
sign
}
{
displayedPriceImpact
}
%)
</
ThemedText
.
Body2
>
)
},
[
inputUSDC
,
outputUSDC
])
return
(
<
DynamicThemeProvider
color=
{
color
}
>
...
...
@@ -92,7 +96,9 @@ export default function Output({ disabled, focused, children }: PropsWithChildre
>
<
ThemedText
.
Body2
color=
"secondary"
>
<
Row
>
<
LoadingSpan
$loading=
{
isLoading
}
>
{
usdc
}
</
LoadingSpan
>
<
LoadingRow
gap=
{
0.5
}
$loading=
{
isLoading
}
>
{
outputUSDC
?.
toFixed
(
2
)
}
{
priceImpact
}
</
LoadingRow
>
{
balance
&&
(
<
Balance
focused=
{
focused
}
>
Balance:
<
span
style=
{
{
userSelect
:
'
text
'
}
}
>
{
formatCurrencyAmount
(
balance
,
4
,
i18n
.
locale
)
}
</
span
>
...
...
src/lib/components/Swap/Settings/MaxSlippageSelect.tsx
View file @
0f519911
import
{
Trans
}
from
'
@lingui/macro
'
import
{
Percent
}
from
'
@uniswap/sdk-core
'
import
{
useAtom
}
from
'
jotai
'
import
Popover
from
'
lib/components/Popover
'
import
{
useTooltip
}
from
'
lib/components/Tooltip
'
import
{
toPercent
}
from
'
lib/hooks/useAllowedSlippage
'
import
{
getSlippageWarning
,
toPercent
}
from
'
lib/hooks/useAllowedSlippage
'
import
{
AlertTriangle
,
Check
,
Icon
,
LargeIcon
,
XOctagon
}
from
'
lib/icons
'
import
{
autoSlippageAtom
,
MAX_VALID_SLIPPAGE
,
maxSlippageAtom
,
MIN_HIGH_SLIPPAGE
}
from
'
lib/state/settings
'
import
styled
,
{
Color
,
ThemedText
}
from
'
lib/theme
'
import
{
autoSlippageAtom
,
maxSlippageAtom
}
from
'
lib/state/settings
'
import
styled
,
{
ThemedText
}
from
'
lib/theme
'
import
{
forwardRef
,
memo
,
ReactNode
,
useCallback
,
useMemo
,
useRef
,
useState
}
from
'
react
'
import
{
BaseButton
,
TextButton
}
from
'
../../Button
'
...
...
@@ -56,35 +55,18 @@ const Option = forwardRef<HTMLButtonElement, OptionProps>(function Option(
)
})
enum
WarningState
{
INVALID_SLIPPAGE
=
1
,
HIGH_SLIPPAGE
,
}
function
toWarningState
(
percent
:
Percent
|
undefined
):
WarningState
|
undefined
{
if
(
percent
?.
greaterThan
(
MAX_VALID_SLIPPAGE
))
{
return
WarningState
.
INVALID_SLIPPAGE
}
else
if
(
percent
?.
greaterThan
(
MIN_HIGH_SLIPPAGE
))
{
return
WarningState
.
HIGH_SLIPPAGE
}
return
}
const
Warning
=
memo
(
function
Warning
({
state
,
showTooltip
}:
{
state
:
WarningState
;
showTooltip
:
boolean
})
{
let
icon
:
Icon
let
color
:
Color
const
Warning
=
memo
(
function
Warning
({
state
,
showTooltip
}:
{
state
?:
'
warning
'
|
'
error
'
;
showTooltip
:
boolean
})
{
let
icon
:
Icon
|
undefined
let
content
:
ReactNode
let
show
=
showTooltip
switch
(
state
)
{
case
WarningState
.
INVALID_SLIPPAGE
:
case
'
error
'
:
icon
=
XOctagon
color
=
'
error
'
content
=
invalidSlippage
show
=
true
break
case
WarningState
.
HIGH_SLIPPAGE
:
case
'
warning
'
:
icon
=
AlertTriangle
color
=
'
warning
'
content
=
highSlippage
break
}
...
...
@@ -97,7 +79,7 @@ const Warning = memo(function Warning({ state, showTooltip }: { state: WarningSt
offset=
{
16
}
contained
>
<
LargeIcon
icon=
{
icon
}
color=
{
color
}
size=
{
1.25
}
/>
<
LargeIcon
icon=
{
icon
}
color=
{
state
}
size=
{
1.25
}
/>
</
Popover
>
)
})
...
...
@@ -106,7 +88,7 @@ export default function MaxSlippageSelect() {
const
[
autoSlippage
,
setAutoSlippage
]
=
useAtom
(
autoSlippageAtom
)
const
[
maxSlippage
,
setMaxSlippage
]
=
useAtom
(
maxSlippageAtom
)
const
maxSlippageInput
=
useMemo
(()
=>
maxSlippage
?.
toString
()
||
''
,
[
maxSlippage
])
const
[
warning
,
setWarning
]
=
useState
<
WarningState
|
undefined
>
(
toWarningState
(
toPercent
(
maxSlippage
)))
const
[
warning
,
setWarning
]
=
useState
<
'
warning
'
|
'
error
'
|
undefined
>
(
getSlippageWarning
(
toPercent
(
maxSlippage
)))
const
option
=
useRef
<
HTMLButtonElement
>
(
null
)
const
showTooltip
=
useTooltip
(
option
.
current
)
...
...
@@ -117,10 +99,10 @@ export default function MaxSlippageSelect() {
const
processValue
=
useCallback
(
(
value
:
number
|
undefined
)
=>
{
const
percent
=
toPercent
(
value
)
const
warning
=
toWarningState
(
percent
)
const
warning
=
getSlippageWarning
(
percent
)
setWarning
(
warning
)
setMaxSlippage
(
value
)
setAutoSlippage
(
!
percent
||
warning
===
WarningState
.
INVALID_SLIPPAGE
)
setAutoSlippage
(
!
percent
||
warning
===
'
error
'
)
},
[
setAutoSlippage
,
setMaxSlippage
]
)
...
...
@@ -146,7 +128,7 @@ export default function MaxSlippageSelect() {
ref=
{
option
}
tabIndex=
{
-
1
}
>
<
Row
color=
{
warning
===
WarningState
.
INVALID_SLIPPAGE
?
'
error
'
:
undefined
}
>
<
Row
color=
{
warning
===
'
error
'
?
'
error
'
:
undefined
}
>
<
DecimalInput
size=
{
Math
.
max
(
maxSlippageInput
.
length
,
3
)
}
value=
{
maxSlippageInput
}
...
...
src/lib/components/Swap/Summary/Details.tsx
View file @
0f519911
...
...
@@ -2,15 +2,14 @@ import { t } from '@lingui/macro'
import
{
useLingui
}
from
'
@lingui/react
'
import
{
Trade
}
from
'
@uniswap/router-sdk
'
import
{
Currency
,
Percent
,
TradeType
}
from
'
@uniswap/sdk-core
'
import
{
ALLOWED_PRICE_IMPACT_HIGH
,
ALLOWED_PRICE_IMPACT_MEDIUM
}
from
'
constants/misc
'
import
{
useAtomValue
}
from
'
jotai/utils
'
import
{
MIN_HIGH_SLIPPAGE
}
from
'
lib/state/settings
'
import
{
getSlippageWarning
}
from
'
lib/hooks/useAllowedSlippage
'
import
{
feeOptionsAtom
}
from
'
lib/state/swap
'
import
styled
,
{
Color
,
ThemedText
}
from
'
lib/theme
'
import
{
useMemo
}
from
'
react
'
import
{
currencyId
}
from
'
utils/currencyId
'
import
{
formatCurrencyAmount
}
from
'
utils/formatCurrencyAmount
'
import
{
computeRealizedLPFeeAmount
,
computeRealizedPriceImpact
}
from
'
utils/prices
'
import
{
computeRealizedLPFeeAmount
,
computeRealizedPriceImpact
,
getPriceImpactWarning
}
from
'
utils/prices
'
import
Row
from
'
../../Row
'
...
...
@@ -52,7 +51,7 @@ export default function Details({ trade, allowedSlippage }: DetailsProps) {
const
{
i18n
}
=
useLingui
()
const
details
=
useMemo
(()
=>
{
const
rows
=
[]
const
rows
:
Array
<
[
string
,
string
]
|
[
string
,
string
,
Color
|
undefined
]
>
=
[]
// @TODO(ianlapham): Check that provider fee is even a valid list item
if
(
feeOptions
)
{
...
...
@@ -63,13 +62,7 @@ export default function Details({ trade, allowedSlippage }: DetailsProps) {
}
}
const
priceImpactRow
=
[
t
`Price impact`
,
`
${
priceImpact
.
toFixed
(
2
)}
%`
]
if
(
priceImpact
.
greaterThan
(
ALLOWED_PRICE_IMPACT_HIGH
))
{
priceImpactRow
.
push
(
'
error
'
)
}
else
if
(
priceImpact
.
greaterThan
(
ALLOWED_PRICE_IMPACT_MEDIUM
))
{
priceImpactRow
.
push
(
'
warning
'
)
}
rows
.
push
(
priceImpactRow
)
rows
.
push
([
t
`Price impact`
,
`
${
priceImpact
.
toFixed
(
2
)}
%`
,
getPriceImpactWarning
(
priceImpact
)])
if
(
lpFeeAmount
)
{
const
parsedLpFee
=
formatCurrencyAmount
(
lpFeeAmount
,
6
,
i18n
.
locale
)
...
...
@@ -86,11 +79,7 @@ export default function Details({ trade, allowedSlippage }: DetailsProps) {
rows
.
push
([
t
`Minimum received`
,
`
${
localizedMaxSent
}
${
outputCurrency
.
symbol
}
`
])
}
const
slippageToleranceRow
=
[
t
`Slippage tolerance`
,
`
${
allowedSlippage
.
toFixed
(
2
)}
%`
]
if
(
allowedSlippage
.
greaterThan
(
MIN_HIGH_SLIPPAGE
))
{
slippageToleranceRow
.
push
(
'
warning
'
)
}
rows
.
push
(
slippageToleranceRow
)
rows
.
push
([
t
`Slippage tolerance`
,
`
${
allowedSlippage
.
toFixed
(
2
)}
%`
,
getSlippageWarning
(
allowedSlippage
)])
return
rows
},
[
...
...
@@ -109,7 +98,7 @@ export default function Details({ trade, allowedSlippage }: DetailsProps) {
return
(
<>
{
details
.
map
(([
label
,
detail
,
color
])
=>
(
<
Detail
key=
{
label
}
label=
{
label
}
value=
{
detail
}
color=
{
color
as
Color
}
/>
<
Detail
key=
{
label
}
label=
{
label
}
value=
{
detail
}
color=
{
color
}
/>
))
}
</>
)
...
...
src/lib/components/Swap/Summary/index.tsx
View file @
0f519911
...
...
@@ -2,18 +2,17 @@ import { Trans } from '@lingui/macro'
import
{
useLingui
}
from
'
@lingui/react
'
import
{
Trade
}
from
'
@uniswap/router-sdk
'
import
{
Currency
,
Percent
,
TradeType
}
from
'
@uniswap/sdk-core
'
import
{
ALLOWED_PRICE_IMPACT_HIGH
,
ALLOWED_PRICE_IMPACT_MEDIUM
}
from
'
constants/misc
'
import
{
useAtomValue
}
from
'
jotai/utils
'
import
{
IconButton
}
from
'
lib/components/Button
'
import
{
getSlippageWarning
}
from
'
lib/hooks/useAllowedSlippage
'
import
useScrollbar
from
'
lib/hooks/useScrollbar
'
import
{
AlertTriangle
,
BarChart
,
Expando
,
Info
}
from
'
lib/icons
'
import
{
MIN_HIGH_SLIPPAGE
}
from
'
lib/state/settings
'
import
{
Field
,
independentFieldAtom
}
from
'
lib/state/swap
'
import
styled
,
{
ThemedText
}
from
'
lib/theme
'
import
formatLocaleNumber
from
'
lib/utils/formatLocaleNumber
'
import
{
useMemo
,
useState
}
from
'
react
'
import
{
formatCurrencyAmount
,
formatPrice
}
from
'
utils/formatCurrencyAmount
'
import
{
computeRealizedPriceImpact
}
from
'
utils/prices
'
import
{
computeRealizedPriceImpact
,
getPriceImpactWarning
}
from
'
utils/prices
'
import
{
tradeMeaningfullyDiffers
}
from
'
utils/tradeMeaningFullyDiffer
'
import
ActionButton
,
{
Action
}
from
'
../../ActionButton
'
...
...
@@ -98,10 +97,7 @@ export function SummaryDialog({ trade, allowedSlippage, onConfirm }: SummaryDial
const
scrollbar
=
useScrollbar
(
details
)
const
warning
=
useMemo
(()
=>
{
if
(
priceImpact
.
greaterThan
(
ALLOWED_PRICE_IMPACT_HIGH
))
return
'
error
'
if
(
priceImpact
.
greaterThan
(
ALLOWED_PRICE_IMPACT_MEDIUM
))
return
'
warning
'
if
(
allowedSlippage
.
greaterThan
(
MIN_HIGH_SLIPPAGE
))
return
'
warning
'
return
return
getPriceImpactWarning
(
priceImpact
)
||
getSlippageWarning
(
allowedSlippage
)
},
[
allowedSlippage
,
priceImpact
])
const
[
ackPriceImpact
,
setAckPriceImpact
]
=
useState
(
false
)
...
...
@@ -120,7 +116,7 @@ export function SummaryDialog({ trade, allowedSlippage, onConfirm }: SummaryDial
onClick
:
()
=>
setConfirmedTrade
(
trade
),
children
:
<
Trans
>
Accept
</
Trans
>,
}
}
else
if
(
priceImpact
.
greaterThan
(
ALLOWED_PRICE_IMPACT_HIGH
)
&&
!
ackPriceImpact
)
{
}
else
if
(
getPriceImpactWarning
(
priceImpact
)
===
'
error
'
&&
!
ackPriceImpact
)
{
return
{
message
:
<
Trans
>
High price impact
</
Trans
>,
onClick
:
()
=>
setAckPriceImpact
(
true
),
...
...
src/lib/hooks/useAllowedSlippage.ts
View file @
0f519911
...
...
@@ -11,8 +11,17 @@ export function toPercent(maxSlippage: number | undefined): Percent | undefined
}
/** Returns the user-inputted max slippage. */
export
default
function
use
Max
Slippage
(
trade
:
InterfaceTrade
<
Currency
,
Currency
,
TradeType
>
|
undefined
):
Percent
{
export
default
function
use
Allowed
Slippage
(
trade
:
InterfaceTrade
<
Currency
,
Currency
,
TradeType
>
|
undefined
):
Percent
{
const
autoSlippage
=
useAutoSlippageTolerance
(
trade
)
const
maxSlippage
=
toPercent
(
useAtomValue
(
maxSlippageAtom
))
return
useAtomValue
(
autoSlippageAtom
)
?
autoSlippage
:
maxSlippage
??
autoSlippage
}
export
const
MAX_VALID_SLIPPAGE
=
new
Percent
(
1
,
2
)
export
const
MIN_HIGH_SLIPPAGE
=
new
Percent
(
1
,
100
)
export
function
getSlippageWarning
(
slippage
?:
Percent
):
'
warning
'
|
'
error
'
|
undefined
{
if
(
slippage
?.
greaterThan
(
MAX_VALID_SLIPPAGE
))
return
'
error
'
if
(
slippage
?.
greaterThan
(
MIN_HIGH_SLIPPAGE
))
return
'
warning
'
return
}
src/lib/state/settings.ts
View file @
0f519911
import
{
Percent
}
from
'
@uniswap/sdk-core
'
import
{
atomWithReset
}
from
'
jotai/utils
'
import
{
pickAtom
,
setTogglable
}
from
'
./atoms
'
export
const
MAX_VALID_SLIPPAGE
=
new
Percent
(
1
,
2
)
export
const
MIN_HIGH_SLIPPAGE
=
new
Percent
(
1
,
100
)
interface
Settings
{
autoSlippage
:
boolean
// if true, slippage will use the default calculation
maxSlippage
:
number
|
undefined
// expressed as a percent
...
...
src/utils/prices.ts
View file @
0f519911
...
...
@@ -21,6 +21,12 @@ export function computeRealizedPriceImpact(trade: Trade<Currency, Currency, Trad
return
trade
.
priceImpact
.
subtract
(
realizedLpFeePercent
)
}
export
function
getPriceImpactWarning
(
priceImpact
?:
Percent
):
'
warning
'
|
'
error
'
|
undefined
{
if
(
priceImpact
?.
greaterThan
(
ALLOWED_PRICE_IMPACT_HIGH
))
return
'
error
'
if
(
priceImpact
?.
greaterThan
(
ALLOWED_PRICE_IMPACT_MEDIUM
))
return
'
warning
'
return
}
// computes realized lp fee as a percent
export
function
computeRealizedLPFeePercent
(
trade
:
Trade
<
Currency
,
Currency
,
TradeType
>
):
Percent
{
let
percent
:
Percent
...
...
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