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
e79630c6
Commit
e79630c6
authored
May 16, 2024
by
Uniswap Labs Service Account
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
ci(release): publish latest release
parent
2c7635f6
Changes
11
Hide whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
77 additions
and
82 deletions
+77
-82
RELEASE
RELEASE
+8
-7
VERSION
VERSION
+1
-1
CurrencySearchModal.tsx
apps/web/src/components/SearchModal/CurrencySearchModal.tsx
+1
-1
TokenSafetyModal.tsx
apps/web/src/components/TokenSafety/TokenSafetyModal.tsx
+4
-4
index.tsx
apps/web/src/components/TokenSafety/index.tsx
+17
-27
index.tsx
apps/web/src/components/Tokens/TokenDetails/index.tsx
+1
-1
useSyncChainQuery.ts
apps/web/src/hooks/useSyncChainQuery.ts
+3
-3
SwapForm.tsx
apps/web/src/pages/Swap/SwapForm.tsx
+9
-20
index.tsx
apps/web/src/pages/Swap/index.tsx
+5
-16
hooks.tsx
apps/web/src/state/swap/hooks.tsx
+27
-2
types.ts
apps/web/src/state/swap/types.ts
+1
-0
No files found.
RELEASE
View file @
e79630c6
IPFS hash of the deployment:
- CIDv0: `Qm
ba9yfwsUsVcbQA6CAkQCxqFCoLJapvjV7RQsp26zJvBh
`
- CIDv1: `bafybeig
etwjqz26or3og65klvf2oefze44ct4oukkncojpcb5wocbc6x7a
`
- CIDv0: `Qm
ccrxuuPdXnhgCm2377DmcpBZtS36JK2kHNhGcutyYCfC
`
- CIDv1: `bafybeig
uflcfu2p2lxehliz5wutxlmqtjfeg7qk4pw2ifw24xbbhjhcf5m
`
The latest release is always mirrored at [app.uniswap.org](https://app.uniswap.org).
...
...
@@ -10,15 +10,16 @@ You can also access the Uniswap Interface from an IPFS gateway.
Your Uniswap settings are never remembered across different URLs.
IPFS gateways:
- https://bafybeig
etwjqz26or3og65klvf2oefze44ct4oukkncojpcb5wocbc6x7a
.ipfs.dweb.link/
- https://bafybeig
etwjqz26or3og65klvf2oefze44ct4oukkncojpcb5wocbc6x7a
.ipfs.cf-ipfs.com/
- [ipfs://Qm
ba9yfwsUsVcbQA6CAkQCxqFCoLJapvjV7RQsp26zJvBh/](ipfs://Qmba9yfwsUsVcbQA6CAkQCxqFCoLJapvjV7RQsp26zJvBh
/)
- https://bafybeig
uflcfu2p2lxehliz5wutxlmqtjfeg7qk4pw2ifw24xbbhjhcf5m
.ipfs.dweb.link/
- https://bafybeig
uflcfu2p2lxehliz5wutxlmqtjfeg7qk4pw2ifw24xbbhjhcf5m
.ipfs.cf-ipfs.com/
- [ipfs://Qm
ccrxuuPdXnhgCm2377DmcpBZtS36JK2kHNhGcutyYCfC/](ipfs://QmccrxuuPdXnhgCm2377DmcpBZtS36JK2kHNhGcutyYCfC
/)
### 5.27.
6
(2024-05-16)
### 5.27.
7
(2024-05-16)
### Bug Fixes
* **web:** Allow viewing pool mgmt pages when disconnected - prod (#8248) f7cce8f
* **web:** support multichain input params 272661d
* **web:** support multichain input params 8620c6c
VERSION
View file @
e79630c6
web/5.27.6
\ No newline at end of file
web/5.27.7
\ No newline at end of file
apps/web/src/components/SearchModal/CurrencySearchModal.tsx
View file @
e79630c6
...
...
@@ -89,7 +89,7 @@ export default memo(function CurrencySearchModal({
if
(
warningToken
)
{
content
=
(
<
TokenSafety
token
Address=
{
warningToken
.
address
}
token
0=
{
warningToken
}
onContinue=
{
()
=>
handleCurrencySelect
(
warningToken
)
}
onCancel=
{
()
=>
setModalView
(
CurrencyModalView
.
search
)
}
showCancel=
{
true
}
...
...
apps/web/src/components/TokenSafety/TokenSafetyModal.tsx
View file @
e79630c6
...
...
@@ -7,8 +7,8 @@ interface TokenSafetyModalProps extends TokenSafetyProps {
export
default
function
TokenSafetyModal
({
isOpen
,
token
Address
,
secondTokenAddress
,
token
0
,
token1
,
onContinue
,
onCancel
,
onBlocked
,
...
...
@@ -17,8 +17,8 @@ export default function TokenSafetyModal({
return
(
<
Modal
isOpen=
{
isOpen
}
onDismiss=
{
onCancel
}
>
<
TokenSafety
token
Address=
{
tokenAddress
}
secondTokenAddress=
{
secondTokenAddress
}
token
0=
{
token0
}
token1=
{
token1
}
onContinue=
{
onContinue
}
onBlocked=
{
onBlocked
}
onCancel=
{
onCancel
}
...
...
apps/web/src/components/TokenSafety/index.tsx
View file @
e79630c6
...
...
@@ -11,7 +11,6 @@ import {
useTokenWarning
,
Warning
,
}
from
'
constants/tokenSafety
'
import
{
useToken
}
from
'
hooks/Tokens
'
import
{
Trans
}
from
'
i18n
'
import
{
ExternalLink
as
LinkIconFeather
}
from
'
react-feather
'
import
{
Text
}
from
'
rebass
'
...
...
@@ -196,59 +195,50 @@ const StyledExternalLink = styled(ExternalLink)`
`
export
interface
TokenSafetyProps
{
token
Address
?:
string
secondTokenAddress
?:
string
token
0
?:
Token
token1
?:
Token
onContinue
:
()
=>
void
onCancel
:
()
=>
void
onBlocked
?:
()
=>
void
showCancel
?:
boolean
}
export
default
function
TokenSafety
({
tokenAddress
,
secondTokenAddress
,
onContinue
,
onCancel
,
onBlocked
,
showCancel
,
}:
TokenSafetyProps
)
{
export
default
function
TokenSafety
({
token0
,
token1
,
onContinue
,
onCancel
,
onBlocked
,
showCancel
}:
TokenSafetyProps
)
{
const
logos
=
[]
const
urls
=
[]
const
token1
=
useToken
(
tokenAddress
)
const
token1Warning
=
useTokenWarning
(
tokenAddress
,
token1
?.
chainId
)
const
token2
=
useToken
(
secondTokenAddress
)
const
token2Warning
=
useTokenWarning
(
secondTokenAddress
,
token2
?.
chainId
)
const
token0Warning
=
useTokenWarning
(
token0
?.
address
,
token0
?.
chainId
)
const
token1Warning
=
useTokenWarning
(
token1
?.
address
,
token1
?.
chainId
)
const
token0Unsupported
=
!
token0Warning
?.
canProceed
const
token1Unsupported
=
!
token1Warning
?.
canProceed
const
token2Unsupported
=
!
token2Warning
?.
canProceed
// Logic for only showing the 'unsupported' warning if one is supported and other isn't
if
(
token1
&&
token1Warning
&&
(
token1Unsupported
||
!
(
token2Warning
&&
token2Unsupported
)))
{
if
(
token0
&&
token0Warning
&&
(
token0Unsupported
||
!
(
token1Warning
&&
token1Unsupported
)))
{
logos
.
push
(<
CurrencyLogo
key=
{
token0
.
address
}
currency=
{
token0
}
size=
{
48
}
/>)
urls
.
push
(<
ExplorerView
token=
{
token0
}
/>)
}
if
(
token1
&&
token1Warning
&&
(
token1Unsupported
||
!
(
token0Warning
&&
token0Unsupported
)))
{
logos
.
push
(<
CurrencyLogo
key=
{
token1
.
address
}
currency=
{
token1
}
size=
{
48
}
/>)
urls
.
push
(<
ExplorerView
token=
{
token1
}
/>)
}
if
(
token2
&&
token2Warning
&&
(
token2Unsupported
||
!
(
token1Warning
&&
token1Unsupported
)))
{
logos
.
push
(<
CurrencyLogo
key=
{
token2
.
address
}
currency=
{
token2
}
size=
{
48
}
/>)
urls
.
push
(<
ExplorerView
token=
{
token2
}
/>)
}
const
plural
=
logos
.
length
>
1
// Show higher level warning if two are present
let
displayWarning
=
token
1
Warning
if
(
!
token
1Warning
||
(
token2Warning
&&
token2Unsupported
&&
!
token1
Unsupported
))
{
displayWarning
=
token
2
Warning
let
displayWarning
=
token
0
Warning
if
(
!
token
0Warning
||
(
token1Warning
&&
token1Unsupported
&&
!
token0
Unsupported
))
{
displayWarning
=
token
1
Warning
}
// If a warning is acknowledged, import these tokens
const
addToken
=
useAddUserToken
()
const
acknowledge
=
()
=>
{
if
(
token0
)
{
addToken
(
token0
)
}
if
(
token1
)
{
addToken
(
token1
)
}
if
(
token2
)
{
addToken
(
token2
)
}
onContinue
()
}
...
...
apps/web/src/components/Tokens/TokenDetails/index.tsx
View file @
e79630c6
...
...
@@ -148,7 +148,7 @@ function TDPSwapComponent() {
{
warning
&&
<
TokenSafetyMessage
tokenAddress=
{
address
}
warning=
{
warning
}
/>
}
<
TokenSafetyModal
isOpen=
{
openTokenSafetyModal
||
!!
continueSwap
}
token
Address=
{
address
}
token
0=
{
currency
.
isToken
?
currency
:
undefined
}
onContinue=
{
()
=>
onResolveSwap
(
true
)
}
onBlocked=
{
()
=>
{
setOpenTokenSafetyModal
(
false
)
...
...
apps/web/src/hooks/useSyncChainQuery.ts
View file @
e79630c6
...
...
@@ -14,7 +14,7 @@ function getChainIdFromName(name: string) {
return
chainId
?
parseInt
(
chainId
)
:
undefined
}
function
getParsedChainId
(
parsedQs
?:
ParsedQs
)
{
export
function
getParsedChainId
(
parsedQs
?:
ParsedQs
)
{
const
chain
=
parsedQs
?.
chain
if
(
!
chain
||
typeof
chain
!==
'
string
'
)
return
...
...
@@ -44,8 +44,8 @@ export default function useSyncChainQuery() {
const
[
searchParams
,
setSearchParams
]
=
useSearchParams
()
useEffect
(()
=>
{
// Change a
user's chain on pageload if the connected
chainId does not match the query param chain
if
(
isConnected
&&
urlChainId
&&
chainIdRef
.
current
===
chainId
&&
chainId
!==
urlChainId
)
{
// Change a
page chain on pageload if the app
chainId does not match the query param chain
if
(
urlChainId
&&
chainIdRef
.
current
===
chainId
&&
chainId
!==
urlChainId
)
{
selectChain
(
urlChainId
)
}
// If a user has a connected wallet and has manually changed their chain, update the query parameter if it's supported
...
...
apps/web/src/pages/Swap/SwapForm.tsx
View file @
e79630c6
...
...
@@ -25,11 +25,9 @@ import { ArrowContainer, ArrowWrapper, OutputSwapSection, SwapSection } from 'co
import
{
CHAIN_INFO
,
useIsSupportedChainId
}
from
'
constants/chains
'
import
{
useIsSwapUnsupported
}
from
'
hooks/useIsSwapUnsupported
'
import
{
useMaxAmountIn
}
from
'
hooks/useMaxAmountIn
'
import
useParsedQueryString
from
'
hooks/useParsedQueryString
'
import
usePermit2Allowance
,
{
AllowanceState
}
from
'
hooks/usePermit2Allowance
'
import
usePrevious
from
'
hooks/usePrevious
'
import
{
SwapResult
,
useSwapCallback
}
from
'
hooks/useSwapCallback
'
import
{
useSwitchChain
}
from
'
hooks/useSwitchChain
'
import
{
useUSDPrice
}
from
'
hooks/useUSDPrice
'
import
useWrapCallback
,
{
WrapErrorText
,
WrapType
}
from
'
hooks/useWrapCallback
'
import
{
Trans
}
from
'
i18n
'
...
...
@@ -42,12 +40,7 @@ import { Text } from 'rebass'
import
{
useAppSelector
}
from
'
state/hooks
'
import
{
InterfaceTrade
,
RouterPreference
,
TradeState
}
from
'
state/routing/types
'
import
{
isClassicTrade
}
from
'
state/routing/utils
'
import
{
queryParametersToCurrencyState
,
useSwapActionHandlers
,
useSwapAndLimitContext
,
useSwapContext
,
}
from
'
state/swap/hooks
'
import
{
useSwapActionHandlers
,
useSwapAndLimitContext
,
useSwapContext
}
from
'
state/swap/hooks
'
import
{
useTheme
}
from
'
styled-components
'
import
{
ExternalLink
,
ThemedText
}
from
'
theme/components
'
import
{
maybeLogFirstSwapAction
}
from
'
tracing/swapFlowLoggers
'
...
...
@@ -61,6 +54,7 @@ import { didUserReject } from 'utils/swapErrorToUserReadableMessage'
import
Error
from
'
components/Icons/Error
'
import
Row
from
'
components/Row
'
import
{
useCurrencyInfo
}
from
'
hooks/Tokens
'
import
useSelectChain
from
'
hooks/useSelectChain
'
import
{
CurrencyState
}
from
'
state/swap/types
'
import
{
SafetyLevel
}
from
'
uniswap/src/data/graphql/uniswap-data-api/__generated__/types-and-hooks
'
import
{
CurrencyInfo
}
from
'
uniswap/src/features/dataApi/types
'
...
...
@@ -88,14 +82,8 @@ export function SwapForm({ disableTokenInputs = false, onCurrencyChange }: SwapF
const
{
typedValue
,
independentField
}
=
swapState
// token warning stuff
const
parsedQs
=
useParsedQueryString
()
const
prefilledCurrencies
=
useMemo
(()
=>
{
return
queryParametersToCurrencyState
(
parsedQs
)
},
[
parsedQs
])
const
prefilledInputCurrencyInfo
=
useCurrencyInfo
(
prefilledCurrencies
?.
inputCurrencyId
,
chainId
)
const
prefilledOutputCurrencyInfo
=
useCurrencyInfo
(
prefilledCurrencies
?.
outputCurrencyId
,
chainId
)
const
prefilledInputCurrencyInfo
=
useCurrencyInfo
(
prefilledState
.
inputCurrency
)
const
prefilledOutputCurrencyInfo
=
useCurrencyInfo
(
prefilledState
.
outputCurrency
)
const
[
dismissTokenWarning
,
setDismissTokenWarning
]
=
useState
<
boolean
>
(
false
)
const
[
showPriceImpactModal
,
setShowPriceImpactModal
]
=
useState
<
boolean
>
(
false
)
...
...
@@ -463,7 +451,8 @@ export function SwapForm({ disableTokenInputs = false, onCurrencyChange }: SwapF
)
const
inputCurrency
=
currencies
[
Field
.
INPUT
]
??
undefined
const
switchChain
=
useSwitchChain
()
const
selectChain
=
useSelectChain
()
const
switchingChain
=
useAppSelector
((
state
)
=>
state
.
wallets
.
switchingChain
)
const
targetChain
=
switchingChain
?
switchingChain
:
undefined
const
switchingChainIsSupported
=
useIsSupportedChainId
(
targetChain
)
...
...
@@ -474,8 +463,8 @@ export function SwapForm({ disableTokenInputs = false, onCurrencyChange }: SwapF
<>
<
TokenSafetyModal
isOpen=
{
urlTokensNotInDefault
.
length
>
0
&&
!
dismissTokenWarning
}
token
Address=
{
urlTokensNotInDefault
[
0
]?.
address
}
secondTokenAddress=
{
urlTokensNotInDefault
[
1
]?.
address
}
token
0=
{
urlTokensNotInDefault
[
0
]
}
token1=
{
urlTokensNotInDefault
[
1
]
}
onContinue=
{
handleConfirmTokenWarning
}
onCancel=
{
handleDismissTokenWarning
}
showCancel=
{
true
}
...
...
@@ -624,7 +613,7 @@ export function SwapForm({ disableTokenInputs = false, onCurrencyChange }: SwapF
$borderRadius=
"16px"
onClick=
{
async
()
=>
{
try
{
await
s
witch
Chain
(
chainId
)
await
s
elect
Chain
(
chainId
)
}
catch
(
error
)
{
if
(
didUserReject
(
error
))
{
// Ignore error, which keeps the user on the previous chain.
...
...
apps/web/src/pages/Swap/index.tsx
View file @
e79630c6
...
...
@@ -7,19 +7,16 @@ import SwapHeader from 'components/swap/SwapHeader'
import
{
SwapTab
}
from
'
components/swap/constants
'
import
{
PageWrapper
,
SwapWrapper
}
from
'
components/swap/styled
'
import
{
useSupportedChainId
}
from
'
constants/chains
'
import
{
useCurrency
}
from
'
hooks/Tokens
'
import
useParsedQueryString
from
'
hooks/useParsedQueryString
'
import
{
useScreenSize
}
from
'
hooks/useScreenSize
'
import
{
SendForm
}
from
'
pages/Swap/Send/SendForm
'
import
{
ReactNode
,
useMemo
}
from
'
react
'
import
{
ReactNode
}
from
'
react
'
import
{
useLocation
}
from
'
react-router-dom
'
import
{
InterfaceTrade
,
TradeState
}
from
'
state/routing/types
'
import
{
isPreviewTrade
}
from
'
state/routing/utils
'
import
{
SwapAndLimitContextProvider
,
SwapContextProvider
}
from
'
state/swap/SwapContext
'
import
{
queryParametersTo
CurrencyState
}
from
'
state/swap/hooks
'
import
{
useInitial
CurrencyState
}
from
'
state/swap/hooks
'
import
{
CurrencyState
,
SwapAndLimitContext
}
from
'
state/swap/types
'
import
{
useChainId
}
from
'
wagmi
'
import
{
useIsDarkMode
}
from
'
../../theme/components/ThemeToggle
'
import
{
LimitFormWrapper
}
from
'
./Limit/LimitForm
'
import
{
SwapForm
}
from
'
./SwapForm
'
...
...
@@ -39,16 +36,8 @@ export function getIsReviewableQuote(
export
default
function
SwapPage
({
className
}:
{
className
?:
string
})
{
const
location
=
useLocation
()
const
supportedChainId
=
useSupportedChainId
(
useChainId
())
const
chainId
=
supportedChainId
||
ChainId
.
MAINNET
const
parsedQs
=
useParsedQueryString
()
const
parsedCurrencyState
=
useMemo
(()
=>
{
return
queryParametersToCurrencyState
(
parsedQs
)
},
[
parsedQs
])
const
initialInputCurrency
=
useCurrency
(
parsedCurrencyState
.
inputCurrencyId
,
chainId
)
const
initialOutputCurrency
=
useCurrency
(
parsedCurrencyState
.
outputCurrencyId
,
chainId
)
const
{
initialInputCurrency
,
initialOutputCurrency
,
chainId
}
=
useInitialCurrencyState
()
const
shouldDisableTokenInputs
=
useSupportedChainId
(
useChainId
())
===
undefined
return
(
<
Trace
page=
{
InterfacePageName
.
SWAP_PAGE
}
shouldLogImpression
>
...
...
@@ -56,7 +45,7 @@ export default function SwapPage({ className }: { className?: string }) {
<
Swap
className=
{
className
}
chainId=
{
chainId
}
disableTokenInputs=
{
s
upportedChainId
===
undefined
}
disableTokenInputs=
{
s
houldDisableTokenInputs
}
initialInputCurrency=
{
initialInputCurrency
}
initialOutputCurrency=
{
initialOutputCurrency
}
syncTabToUrl=
{
true
}
...
...
apps/web/src/state/swap/hooks.tsx
View file @
e79630c6
import
{
Currency
,
CurrencyAmount
,
TradeType
}
from
'
@uniswap/sdk-core
'
import
{
C
hainId
,
C
urrency
,
CurrencyAmount
,
TradeType
}
from
'
@uniswap/sdk-core
'
import
{
useWeb3React
}
from
'
@web3-react/core
'
import
{
Field
}
from
'
components/swap/constants
'
import
useAutoSlippageTolerance
from
'
hooks/useAutoSlippageTolerance
'
...
...
@@ -13,9 +13,13 @@ import { isClassicTrade, isSubmittableTrade, isUniswapXTrade } from 'state/routi
import
{
useUserSlippageToleranceWithDefault
}
from
'
state/user/hooks
'
import
{
isAddress
}
from
'
utilities/src/addresses
'
import
{
useSupportedChainId
}
from
'
constants/chains
'
import
{
useCurrency
}
from
'
hooks/Tokens
'
import
useParsedQueryString
from
'
hooks/useParsedQueryString
'
import
{
getParsedChainId
}
from
'
hooks/useSyncChainQuery
'
import
useNativeCurrency
from
'
lib/hooks/useNativeCurrency
'
import
{
InterfaceTrade
,
RouterPreference
,
TradeState
}
from
'
state/routing/types
'
import
{
useAccount
}
from
'
wagmi
'
import
{
useAccount
,
useChainId
}
from
'
wagmi
'
import
{
useCurrencyBalance
,
useCurrencyBalances
}
from
'
../connection/hooks
'
import
{
CurrencyState
,
...
...
@@ -280,6 +284,7 @@ export function queryParametersToCurrencyState(parsedQs: ParsedQs): SerializedCu
let
inputCurrency
=
parseCurrencyFromURLParameter
(
parsedQs
.
inputCurrency
??
parsedQs
.
inputcurrency
)
let
outputCurrency
=
parseCurrencyFromURLParameter
(
parsedQs
.
outputCurrency
??
parsedQs
.
outputcurrency
)
const
independentField
=
parseIndependentFieldURLParameter
(
parsedQs
.
exactField
)
const
chainId
=
getParsedChainId
(
parsedQs
)
if
(
inputCurrency
===
''
&&
outputCurrency
===
''
&&
independentField
===
Field
.
INPUT
)
{
// Defaults to having the native currency selected
...
...
@@ -292,5 +297,25 @@ export function queryParametersToCurrencyState(parsedQs: ParsedQs): SerializedCu
return
{
inputCurrencyId
:
inputCurrency
===
''
?
undefined
:
inputCurrency
??
undefined
,
outputCurrencyId
:
outputCurrency
===
''
?
undefined
:
outputCurrency
??
undefined
,
chainId
,
}
}
export
function
useInitialCurrencyState
():
{
initialInputCurrency
?:
Currency
initialOutputCurrency
?:
Currency
chainId
:
ChainId
}
{
const
parsedQs
=
useParsedQueryString
()
const
parsedCurrencyState
=
useMemo
(()
=>
{
return
queryParametersToCurrencyState
(
parsedQs
)
},
[
parsedQs
])
const
connectedChainId
=
useChainId
()
const
chainId
=
useSupportedChainId
(
parsedCurrencyState
.
chainId
??
connectedChainId
)
??
ChainId
.
MAINNET
const
initialInputCurrency
=
useCurrency
(
parsedCurrencyState
.
inputCurrencyId
,
chainId
)
const
initialOutputCurrency
=
useCurrency
(
parsedCurrencyState
.
outputCurrencyId
,
chainId
)
return
{
initialInputCurrency
,
initialOutputCurrency
,
chainId
}
}
apps/web/src/state/swap/types.ts
View file @
e79630c6
...
...
@@ -100,6 +100,7 @@ export const SwapAndLimitContext = createContext<SwapAndLimitContextType>({
export
interface
SerializedCurrencyState
{
inputCurrencyId
?:
string
outputCurrencyId
?:
string
chainId
?:
number
}
// shared state between Swap and Limit
...
...
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