Commit 0f91af1d authored by Moody Salem's avatar Moody Salem Committed by GitHub

improvement(swap): Better swap errors for FoT (#1015)

* move the gas estimation stuff into its own hook and report errors from the gas estimation

* fix linter errors

* show the swap callback error separately

* rename some variables

* use a manually specified key for gas estimates

* flip price... thought i did this already

* only show swap callback error if approval state is approved

* some clean up to the swap components

* stop proactively looking for gas estimates

* improve some retry stuff, show errors inline

* add another retry test

* latest ethers

* fix integration tests

* simplify modal and fix jitter on open in mobile

* refactor confirmation modal into pieces before creating the error content

* finish refactoring of transaction confirmation modal

* show error state in the transaction confirmation modal

* fix lint errors

* error not always relevant

* fix lint errors, remove action item

* move a lot of code into ConfirmSwapModal.tsx

* show accept changes flow, not styled

* Adjust styles for slippage error states

* Add styles for updated price prompt

* Add input/output highlighting

* lint errors

* fix link to wallets in modal

* use total supply instead of reserves for `noLiquidity` (fixes #701)

* bump the walletconnect version to the fixed alpha
Co-authored-by: default avatarCallil Capuozzo <callil.capuozzo@gmail.com>
parent 10ef0451
...@@ -4,17 +4,7 @@ ...@@ -4,17 +4,7 @@
"homepage": ".", "homepage": ".",
"private": true, "private": true,
"devDependencies": { "devDependencies": {
"@ethersproject/address": "5.0.0-beta.134", "@ethersproject/experimental": "^5.0.1",
"@ethersproject/bignumber": "5.0.0-beta.138",
"@ethersproject/constants": "5.0.0-beta.133",
"@ethersproject/contracts": "5.0.0-beta.151",
"@ethersproject/experimental": "5.0.0-beta.141",
"@ethersproject/networks": "5.0.0-beta.136",
"@ethersproject/providers": "5.0.0-beta.162",
"@ethersproject/solidity": "5.0.2",
"@ethersproject/strings": "5.0.0-beta.136",
"@ethersproject/units": "5.0.0-beta.132",
"@ethersproject/wallet": "5.0.0-beta.141",
"@popperjs/core": "^2.4.4", "@popperjs/core": "^2.4.4",
"@reach/dialog": "^0.10.3", "@reach/dialog": "^0.10.3",
"@reach/portal": "^0.10.3", "@reach/portal": "^0.10.3",
...@@ -52,6 +42,7 @@ ...@@ -52,6 +42,7 @@
"eslint-plugin-prettier": "^3.1.3", "eslint-plugin-prettier": "^3.1.3",
"eslint-plugin-react": "^7.19.0", "eslint-plugin-react": "^7.19.0",
"eslint-plugin-react-hooks": "^4.0.0", "eslint-plugin-react-hooks": "^4.0.0",
"ethers": "^5.0.7",
"i18next": "^15.0.9", "i18next": "^15.0.9",
"i18next-browser-languagedetector": "^3.0.1", "i18next-browser-languagedetector": "^3.0.1",
"i18next-xhr-backend": "^2.0.1", "i18next-xhr-backend": "^2.0.1",
...@@ -60,7 +51,6 @@ ...@@ -60,7 +51,6 @@
"lodash.flatmap": "^4.5.0", "lodash.flatmap": "^4.5.0",
"polished": "^3.3.2", "polished": "^3.3.2",
"prettier": "^1.17.0", "prettier": "^1.17.0",
"qrcode.react": "^0.9.3",
"qs": "^6.9.4", "qs": "^6.9.4",
"react": "^16.13.1", "react": "^16.13.1",
"react-device-detect": "^1.6.2", "react-device-detect": "^1.6.2",
...@@ -80,8 +70,10 @@ ...@@ -80,8 +70,10 @@
"serve": "^11.3.0", "serve": "^11.3.0",
"start-server-and-test": "^1.11.0", "start-server-and-test": "^1.11.0",
"styled-components": "^4.2.0", "styled-components": "^4.2.0",
"typescript": "^3.8.3", "typescript": "^3.8.3"
"use-media": "^1.4.0" },
"resolutions": {
"@walletconnect/web3-provider": "1.1.1-alpha.0"
}, },
"scripts": { "scripts": {
"start": "react-scripts start", "start": "react-scripts start",
......
...@@ -251,26 +251,26 @@ export default function AccountDetails({ ...@@ -251,26 +251,26 @@ export default function AccountDetails({
} else if (connector === walletconnect) { } else if (connector === walletconnect) {
return ( return (
<IconWrapper size={16}> <IconWrapper size={16}>
<img src={WalletConnectIcon} alt={''} /> <img src={WalletConnectIcon} alt={'wallet connect logo'} />
</IconWrapper> </IconWrapper>
) )
} else if (connector === walletlink) { } else if (connector === walletlink) {
return ( return (
<IconWrapper size={16}> <IconWrapper size={16}>
<img src={CoinbaseWalletIcon} alt={''} /> <img src={CoinbaseWalletIcon} alt={'coinbase wallet logo'} />
</IconWrapper> </IconWrapper>
) )
} else if (connector === fortmatic) { } else if (connector === fortmatic) {
return ( return (
<IconWrapper size={16}> <IconWrapper size={16}>
<img src={FortmaticIcon} alt={''} /> <img src={FortmaticIcon} alt={'fortmatic logo'} />
</IconWrapper> </IconWrapper>
) )
} else if (connector === portis) { } else if (connector === portis) {
return ( return (
<> <>
<IconWrapper size={16}> <IconWrapper size={16}>
<img src={PortisIcon} alt={''} /> <img src={PortisIcon} alt={'portis logo'} />
<MainWalletAction <MainWalletAction
onClick={() => { onClick={() => {
portis.portis.showPortis() portis.portis.showPortis()
...@@ -382,7 +382,6 @@ export default function AccountDetails({ ...@@ -382,7 +382,6 @@ export default function AccountDetails({
</AccountControl> </AccountControl>
</> </>
)} )}
{/* {formatConnectorName()} */}
</AccountGroupingRow> </AccountGroupingRow>
</InfoCard> </InfoCard>
</YourAccount> </YourAccount>
......
...@@ -27,6 +27,8 @@ const Base = styled(RebassButton)<{ ...@@ -27,6 +27,8 @@ const Base = styled(RebassButton)<{
flex-wrap: nowrap; flex-wrap: nowrap;
align-items: center; align-items: center;
cursor: pointer; cursor: pointer;
position: relative;
z-index: 1;
&:disabled { &:disabled {
cursor: auto; cursor: auto;
} }
......
import React, { useContext } from 'react'
import styled, { ThemeContext } from 'styled-components'
import Modal from '../Modal'
import { ExternalLink } from '../../theme'
import { Text } from 'rebass'
import { CloseIcon, Spinner } from '../../theme/components'
import { RowBetween } from '../Row'
import { ArrowUpCircle } from 'react-feather'
import { ButtonPrimary } from '../Button'
import { AutoColumn, ColumnCenter } from '../Column'
import Circle from '../../assets/images/blue-loader.svg'
import { getEtherscanLink } from '../../utils'
import { useActiveWeb3React } from '../../hooks'
const Wrapper = styled.div`
width: 100%;
`
const Section = styled(AutoColumn)`
padding: 24px;
`
const BottomSection = styled(Section)`
background-color: ${({ theme }) => theme.bg2};
border-bottom-left-radius: 20px;
border-bottom-right-radius: 20px;
`
const ConfirmedIcon = styled(ColumnCenter)`
padding: 60px 0;
`
const CustomLightSpinner = styled(Spinner)<{ size: string }>`
height: ${({ size }) => size};
width: ${({ size }) => size};
`
interface ConfirmationModalProps {
isOpen: boolean
onDismiss: () => void
hash: string
topContent: () => React.ReactChild
bottomContent: () => React.ReactChild
attemptingTxn: boolean
pendingText: string
title?: string
}
export default function ConfirmationModal({
isOpen,
onDismiss,
topContent,
bottomContent,
attemptingTxn,
hash,
pendingText,
title = ''
}: ConfirmationModalProps) {
const { chainId } = useActiveWeb3React()
const theme = useContext(ThemeContext)
const transactionBroadcast = !!hash
// waiting for user to confirm/reject tx _or_ showing info on a tx that has been broadcast
if (attemptingTxn || transactionBroadcast) {
return (
<Modal isOpen={isOpen} onDismiss={onDismiss} maxHeight={90}>
<Wrapper>
<Section>
<RowBetween>
<div />
<CloseIcon onClick={onDismiss} />
</RowBetween>
<ConfirmedIcon>
{transactionBroadcast ? (
<ArrowUpCircle strokeWidth={0.5} size={90} color={theme.primary1} />
) : (
<CustomLightSpinner src={Circle} alt="loader" size={'90px'} />
)}
</ConfirmedIcon>
<AutoColumn gap="12px" justify={'center'}>
<Text fontWeight={500} fontSize={20}>
{transactionBroadcast ? 'Transaction Submitted' : 'Waiting For Confirmation'}
</Text>
<AutoColumn gap="12px" justify={'center'}>
<Text fontWeight={600} fontSize={14} color="" textAlign="center">
{pendingText}
</Text>
</AutoColumn>
{transactionBroadcast ? (
<>
<ExternalLink href={getEtherscanLink(chainId, hash, 'transaction')}>
<Text fontWeight={500} fontSize={14} color={theme.primary1}>
View on Etherscan
</Text>
</ExternalLink>
<ButtonPrimary onClick={onDismiss} style={{ margin: '20px 0 0 0' }}>
<Text fontWeight={500} fontSize={20}>
Close
</Text>
</ButtonPrimary>
</>
) : (
<Text fontSize={12} color="#565A69" textAlign="center">
Confirm this transaction in your wallet
</Text>
)}
</AutoColumn>
</Section>
</Wrapper>
</Modal>
)
}
// confirmation screen
return (
<Modal isOpen={isOpen} onDismiss={onDismiss} maxHeight={90}>
<Wrapper>
<Section>
<RowBetween>
<Text fontWeight={500} fontSize={20}>
{title}
</Text>
<CloseIcon onClick={onDismiss} />
</RowBetween>
{topContent()}
</Section>
<BottomSection gap="12px">{bottomContent()}</BottomSection>
</Wrapper>
</Modal>
)
}
import React from 'react' import React from 'react'
import styled, { css } from 'styled-components' import styled, { css } from 'styled-components'
import { animated, useTransition, useSpring } from 'react-spring' import { animated, useTransition, useSpring } from 'react-spring'
import { Spring } from 'react-spring/renderprops'
import { DialogOverlay, DialogContent } from '@reach/dialog' import { DialogOverlay, DialogContent } from '@reach/dialog'
import { isMobile } from 'react-device-detect' import { isMobile } from 'react-device-detect'
import '@reach/dialog/styles.css' import '@reach/dialog/styles.css'
...@@ -11,39 +9,25 @@ import { useGesture } from 'react-use-gesture' ...@@ -11,39 +9,25 @@ import { useGesture } from 'react-use-gesture'
const AnimatedDialogOverlay = animated(DialogOverlay) const AnimatedDialogOverlay = animated(DialogOverlay)
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
const StyledDialogOverlay = styled(({ mobile, ...rest }) => <AnimatedDialogOverlay {...rest} />)<{ mobile: boolean }>` const StyledDialogOverlay = styled(AnimatedDialogOverlay)`
&[data-reach-dialog-overlay] { &[data-reach-dialog-overlay] {
z-index: 2; z-index: 2;
display: flex;
align-items: center;
justify-content: center;
background-color: transparent; background-color: transparent;
overflow: hidden; overflow: hidden;
${({ mobile }) => display: flex;
mobile && align-items: center;
css` justify-content: center;
align-items: flex-end;
`}
&::after {
content: '';
background-color: ${({ theme }) => theme.modalBG}; background-color: ${({ theme }) => theme.modalBG};
opacity: 0.5;
top: 0;
left: 0;
bottom: 0;
right: 0;
position: fixed;
z-index: -1;
}
} }
` `
const AnimatedDialogContent = animated(DialogContent)
// destructure to not pass custom props to Dialog DOM element // destructure to not pass custom props to Dialog DOM element
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
const StyledDialogContent = styled(({ minHeight, maxHeight, mobile, isOpen, ...rest }) => ( const StyledDialogContent = styled(({ minHeight, maxHeight, mobile, isOpen, ...rest }) => (
<DialogContent {...rest} /> <AnimatedDialogContent {...rest} />
)).attrs({ )).attrs({
'aria-label': 'dialog' 'aria-label': 'dialog'
})` })`
...@@ -55,6 +39,8 @@ const StyledDialogContent = styled(({ minHeight, maxHeight, mobile, isOpen, ...r ...@@ -55,6 +39,8 @@ const StyledDialogContent = styled(({ minHeight, maxHeight, mobile, isOpen, ...r
padding: 0px; padding: 0px;
width: 50vw; width: 50vw;
align-self: ${({ mobile }) => (mobile ? 'flex-end' : 'center')};
max-width: 420px; max-width: 420px;
${({ maxHeight }) => ${({ maxHeight }) =>
maxHeight && maxHeight &&
...@@ -102,7 +88,7 @@ export default function Modal({ ...@@ -102,7 +88,7 @@ export default function Modal({
initialFocusRef = null, initialFocusRef = null,
children children
}: ModalProps) { }: ModalProps) {
const transitions = useTransition(isOpen, null, { const fadeTransition = useTransition(isOpen, null, {
config: { duration: 200 }, config: { duration: 200 },
from: { opacity: 0 }, from: { opacity: 0 },
enter: { opacity: 1 }, enter: { opacity: 1 },
...@@ -115,74 +101,32 @@ export default function Modal({ ...@@ -115,74 +101,32 @@ export default function Modal({
set({ set({
y: state.down ? state.movement[1] : 0 y: state.down ? state.movement[1] : 0
}) })
if (state.velocity > 3 && state.direction[1] > 0) { if (state.movement[1] > 300 || (state.velocity > 3 && state.direction[1] > 0)) {
onDismiss() onDismiss()
} }
} }
}) })
if (isMobile) {
return ( return (
<> <>
{transitions.map( {fadeTransition.map(
({ item, key, props }) =>
item && (
<StyledDialogOverlay
key={key}
style={props}
onDismiss={onDismiss}
initialFocusRef={initialFocusRef}
mobile={true}
>
{/* prevents the automatic focusing of inputs on mobile by the reach dialog */}
{initialFocusRef ? null : <div tabIndex={1} />}
<Spring // animation for entrance and exit
from={{
transform: isOpen ? 'translateY(200px)' : 'translateY(100px)'
}}
to={{
transform: isOpen ? 'translateY(0px)' : 'translateY(200px)'
}}
>
{props => (
<animated.div
{...bind()}
style={{
transform: y.interpolate(y => `translateY(${y > 0 ? y : 0}px)`)
}}
>
<StyledDialogContent
aria-label="dialog content"
style={props}
hidden={true}
minHeight={minHeight}
maxHeight={maxHeight}
mobile={isMobile}
>
{children}
</StyledDialogContent>
</animated.div>
)}
</Spring>
</StyledDialogOverlay>
)
)}
</>
)
} else {
return (
<>
{transitions.map(
({ item, key, props }) => ({ item, key, props }) =>
item && ( item && (
<StyledDialogOverlay key={key} style={props} onDismiss={onDismiss} initialFocusRef={initialFocusRef}> <StyledDialogOverlay key={key} style={props} onDismiss={onDismiss} initialFocusRef={initialFocusRef}>
<StyledDialogContent <StyledDialogContent
{...(isMobile
? {
...bind(),
style: { transform: y.interpolate(y => `translateY(${y > 0 ? y : 0}px)`) }
}
: {})}
aria-label="dialog content" aria-label="dialog content"
hidden={true}
minHeight={minHeight} minHeight={minHeight}
maxHeight={maxHeight} maxHeight={maxHeight}
isOpen={isOpen} mobile={isMobile}
> >
{/* prevents the automatic focusing of inputs on mobile by the reach dialog */}
{!initialFocusRef && isMobile ? <div tabIndex={1} /> : null}
{children} {children}
</StyledDialogContent> </StyledDialogContent>
</StyledDialogOverlay> </StyledDialogOverlay>
...@@ -190,5 +134,4 @@ export default function Modal({ ...@@ -190,5 +134,4 @@ export default function Modal({
)} )}
</> </>
) )
}
} }
...@@ -5,7 +5,7 @@ import useInterval from '../../hooks/useInterval' ...@@ -5,7 +5,7 @@ import useInterval from '../../hooks/useInterval'
import { PopupContent } from '../../state/application/actions' import { PopupContent } from '../../state/application/actions'
import { useRemovePopup } from '../../state/application/hooks' import { useRemovePopup } from '../../state/application/hooks'
import ListUpdatePopup from './ListUpdatePopup' import ListUpdatePopup from './ListUpdatePopup'
import TxnPopup from './TxnPopup' import TransactionPopup from './TransactionPopup'
export const StyledClose = styled(X)` export const StyledClose = styled(X)`
position: absolute; position: absolute;
...@@ -68,7 +68,7 @@ export default function PopupItem({ content, popKey }: { content: PopupContent; ...@@ -68,7 +68,7 @@ export default function PopupItem({ content, popKey }: { content: PopupContent;
const { const {
txn: { hash, success, summary } txn: { hash, success, summary }
} = content } = content
popupContent = <TxnPopup hash={hash} success={success} summary={summary} /> popupContent = <TransactionPopup hash={hash} success={success} summary={summary} />
} else if ('listUpdate' in content) { } else if ('listUpdate' in content) {
const { const {
listUpdate: { listUrl, oldList, newList, auto } listUpdate: { listUrl, oldList, newList, auto }
......
import React, { useContext } from 'react' import React, { useContext } from 'react'
import { AlertCircle, CheckCircle } from 'react-feather' import { AlertCircle, CheckCircle } from 'react-feather'
import { ThemeContext } from 'styled-components' import styled, { ThemeContext } from 'styled-components'
import { useActiveWeb3React } from '../../hooks' import { useActiveWeb3React } from '../../hooks'
import { TYPE } from '../../theme' import { TYPE } from '../../theme'
import { ExternalLink } from '../../theme/components' import { ExternalLink } from '../../theme/components'
...@@ -8,13 +8,25 @@ import { getEtherscanLink } from '../../utils' ...@@ -8,13 +8,25 @@ import { getEtherscanLink } from '../../utils'
import { AutoColumn } from '../Column' import { AutoColumn } from '../Column'
import { AutoRow } from '../Row' import { AutoRow } from '../Row'
export default function TxnPopup({ hash, success, summary }: { hash: string; success?: boolean; summary?: string }) { const RowNoFlex = styled(AutoRow)`
flex-wrap: nowrap;
`
export default function TransactionPopup({
hash,
success,
summary
}: {
hash: string
success?: boolean
summary?: string
}) {
const { chainId } = useActiveWeb3React() const { chainId } = useActiveWeb3React()
const theme = useContext(ThemeContext) const theme = useContext(ThemeContext)
return ( return (
<AutoRow> <RowNoFlex>
<div style={{ paddingRight: 16 }}> <div style={{ paddingRight: 16 }}>
{success ? <CheckCircle color={theme.green1} size={24} /> : <AlertCircle color={theme.red1} size={24} />} {success ? <CheckCircle color={theme.green1} size={24} /> : <AlertCircle color={theme.red1} size={24} />}
</div> </div>
...@@ -22,6 +34,6 @@ export default function TxnPopup({ hash, success, summary }: { hash: string; suc ...@@ -22,6 +34,6 @@ export default function TxnPopup({ hash, success, summary }: { hash: string; suc
<TYPE.body fontWeight={500}>{summary ?? 'Hash: ' + hash.slice(0, 8) + '...' + hash.slice(58, 65)}</TYPE.body> <TYPE.body fontWeight={500}>{summary ?? 'Hash: ' + hash.slice(0, 8) + '...' + hash.slice(58, 65)}</TYPE.body>
<ExternalLink href={getEtherscanLink(chainId, hash, 'transaction')}>View on Etherscan</ExternalLink> <ExternalLink href={getEtherscanLink(chainId, hash, 'transaction')}>View on Etherscan</ExternalLink>
</AutoColumn> </AutoColumn>
</AutoRow> </RowNoFlex>
) )
} }
import React from 'react' import React from 'react'
import styled from 'styled-components' import styled from 'styled-components'
import { useMediaLayout } from 'use-media'
import { useActivePopups } from '../../state/application/hooks' import { useActivePopups } from '../../state/application/hooks'
import { AutoColumn } from '../Column' import { AutoColumn } from '../Column'
import PopupItem from './PopupItem' import PopupItem from './PopupItem'
...@@ -11,6 +10,11 @@ const MobilePopupWrapper = styled.div<{ height: string | number }>` ...@@ -11,6 +10,11 @@ const MobilePopupWrapper = styled.div<{ height: string | number }>`
height: ${({ height }) => height}; height: ${({ height }) => height};
margin: ${({ height }) => (height ? '0 auto;' : 0)}; margin: ${({ height }) => (height ? '0 auto;' : 0)};
margin-bottom: ${({ height }) => (height ? '20px' : 0)}}; margin-bottom: ${({ height }) => (height ? '20px' : 0)}};
display: none;
${({ theme }) => theme.mediaWidth.upToSmall`
display: block;
`};
` `
const MobilePopupInner = styled.div` const MobilePopupInner = styled.div`
...@@ -26,8 +30,8 @@ const MobilePopupInner = styled.div` ...@@ -26,8 +30,8 @@ const MobilePopupInner = styled.div`
` `
const FixedPopupColumn = styled(AutoColumn)` const FixedPopupColumn = styled(AutoColumn)`
position: absolute; position: fixed;
top: 112px; top: 64px;
right: 1rem; right: 1rem;
max-width: 355px !important; max-width: 355px !important;
width: 100%; width: 100%;
...@@ -41,21 +45,13 @@ export default function Popups() { ...@@ -41,21 +45,13 @@ export default function Popups() {
// get all popups // get all popups
const activePopups = useActivePopups() const activePopups = useActivePopups()
// switch view settings on mobile
const isMobile = useMediaLayout({ maxWidth: '600px' })
if (!isMobile) {
return ( return (
<>
<FixedPopupColumn gap="20px"> <FixedPopupColumn gap="20px">
{activePopups.map(item => ( {activePopups.map(item => (
<PopupItem key={item.key} content={item.content} popKey={item.key} /> <PopupItem key={item.key} content={item.content} popKey={item.key} />
))} ))}
</FixedPopupColumn> </FixedPopupColumn>
)
}
//mobile
else
return (
<MobilePopupWrapper height={activePopups?.length > 0 ? 'fit-content' : 0}> <MobilePopupWrapper height={activePopups?.length > 0 ? 'fit-content' : 0}>
<MobilePopupInner> <MobilePopupInner>
{activePopups // reverse so new items up front {activePopups // reverse so new items up front
...@@ -66,5 +62,6 @@ export default function Popups() { ...@@ -66,5 +62,6 @@ export default function Popups() {
))} ))}
</MobilePopupInner> </MobilePopupInner>
</MobilePopupWrapper> </MobilePopupWrapper>
</>
) )
} }
import { ChainId } from '@uniswap/sdk'
import React, { useContext } from 'react'
import styled, { ThemeContext } from 'styled-components'
import Modal from '../Modal'
import { ExternalLink } from '../../theme'
import { Text } from 'rebass'
import { CloseIcon, Spinner } from '../../theme/components'
import { RowBetween } from '../Row'
import { AlertTriangle, ArrowUpCircle } from 'react-feather'
import { ButtonPrimary } from '../Button'
import { AutoColumn, ColumnCenter } from '../Column'
import Circle from '../../assets/images/blue-loader.svg'
import { getEtherscanLink } from '../../utils'
import { useActiveWeb3React } from '../../hooks'
const Wrapper = styled.div`
width: 100%;
`
const Section = styled(AutoColumn)`
padding: 24px;
`
const BottomSection = styled(Section)`
background-color: ${({ theme }) => theme.bg2};
border-bottom-left-radius: 20px;
border-bottom-right-radius: 20px;
`
const ConfirmedIcon = styled(ColumnCenter)`
padding: 60px 0;
`
const CustomLightSpinner = styled(Spinner)<{ size: string }>`
height: ${({ size }) => size};
width: ${({ size }) => size};
`
function ConfirmationPendingContent({ onDismiss, pendingText }: { onDismiss: () => void; pendingText: string }) {
return (
<Wrapper>
<Section>
<RowBetween>
<div />
<CloseIcon onClick={onDismiss} />
</RowBetween>
<ConfirmedIcon>
<CustomLightSpinner src={Circle} alt="loader" size={'90px'} />
</ConfirmedIcon>
<AutoColumn gap="12px" justify={'center'}>
<Text fontWeight={500} fontSize={20}>
Waiting For Confirmation
</Text>
<AutoColumn gap="12px" justify={'center'}>
<Text fontWeight={600} fontSize={14} color="" textAlign="center">
{pendingText}
</Text>
</AutoColumn>
<Text fontSize={12} color="#565A69" textAlign="center">
Confirm this transaction in your wallet
</Text>
</AutoColumn>
</Section>
</Wrapper>
)
}
function TransactionSubmittedContent({
onDismiss,
chainId,
hash
}: {
onDismiss: () => void
hash: string | undefined
chainId: ChainId
}) {
const theme = useContext(ThemeContext)
return (
<Wrapper>
<Section>
<RowBetween>
<div />
<CloseIcon onClick={onDismiss} />
</RowBetween>
<ConfirmedIcon>
<ArrowUpCircle strokeWidth={0.5} size={90} color={theme.primary1} />
</ConfirmedIcon>
<AutoColumn gap="12px" justify={'center'}>
<Text fontWeight={500} fontSize={20}>
Transaction Submitted
</Text>
<ExternalLink href={getEtherscanLink(chainId, hash, 'transaction')}>
<Text fontWeight={500} fontSize={14} color={theme.primary1}>
View on Etherscan
</Text>
</ExternalLink>
<ButtonPrimary onClick={onDismiss} style={{ margin: '20px 0 0 0' }}>
<Text fontWeight={500} fontSize={20}>
Close
</Text>
</ButtonPrimary>
</AutoColumn>
</Section>
</Wrapper>
)
}
export function ConfirmationModalContent({
title,
bottomContent,
onDismiss,
topContent
}: {
title: string
onDismiss: () => void
topContent: () => React.ReactNode
bottomContent: () => React.ReactNode
}) {
return (
<Wrapper>
<Section>
<RowBetween>
<Text fontWeight={500} fontSize={20}>
{title}
</Text>
<CloseIcon onClick={onDismiss} />
</RowBetween>
{topContent()}
</Section>
<BottomSection gap="12px">{bottomContent()}</BottomSection>
</Wrapper>
)
}
export function TransactionErrorContent({ message, onDismiss }: { message: string; onDismiss: () => void }) {
const theme = useContext(ThemeContext)
return (
<Wrapper>
<Section>
<RowBetween>
<Text fontWeight={500} fontSize={20}>
Error
</Text>
<CloseIcon onClick={onDismiss} />
</RowBetween>
<AutoColumn style={{ marginTop: 20, padding: '2rem 0' }} gap="24px" justify="center">
<AlertTriangle color={theme.red1} style={{ strokeWidth: 1.5 }} size={64} />
<Text fontWeight={500} fontSize={16} color={theme.red1} style={{ textAlign: 'center', width: '85%' }}>
{message}
</Text>
</AutoColumn>
</Section>
<BottomSection gap="12px">
<ButtonPrimary onClick={onDismiss}>Dismiss</ButtonPrimary>
</BottomSection>
</Wrapper>
)
}
interface ConfirmationModalProps {
isOpen: boolean
onDismiss: () => void
hash: string | undefined
content: () => React.ReactNode
attemptingTxn: boolean
pendingText: string
}
export default function TransactionConfirmationModal({
isOpen,
onDismiss,
attemptingTxn,
hash,
pendingText,
content
}: ConfirmationModalProps) {
const { chainId } = useActiveWeb3React()
if (!chainId) return null
// confirmation screen
return (
<Modal isOpen={isOpen} onDismiss={onDismiss} maxHeight={90}>
{attemptingTxn ? (
<ConfirmationPendingContent onDismiss={onDismiss} pendingText={pendingText} />
) : hash ? (
<TransactionSubmittedContent chainId={chainId} hash={hash} onDismiss={onDismiss} />
) : (
content()
)}
</Modal>
)
}
{
"extends": "../../../tsconfig.strict.json",
"include": ["**/*"]
}
\ No newline at end of file
...@@ -349,9 +349,7 @@ export default function WalletModal({ ...@@ -349,9 +349,7 @@ export default function WalletModal({
{walletView !== WALLET_VIEWS.PENDING && ( {walletView !== WALLET_VIEWS.PENDING && (
<Blurb> <Blurb>
<span>New to Ethereum? &nbsp;</span>{' '} <span>New to Ethereum? &nbsp;</span>{' '}
<ExternalLink href="https://ethereum.org/use/#3-what-is-a-wallet-and-which-one-should-i-use"> <ExternalLink href="https://ethereum.org/wallets/">Learn more about wallets</ExternalLink>
Learn more about wallets
</ExternalLink>
</Blurb> </Blurb>
)} )}
</ContentWrapper> </ContentWrapper>
......
...@@ -73,11 +73,13 @@ export function AdvancedSwapDetails({ trade }: AdvancedSwapDetailsProps) { ...@@ -73,11 +73,13 @@ export function AdvancedSwapDetails({ trade }: AdvancedSwapDetailsProps) {
const [allowedSlippage] = useUserSlippageTolerance() const [allowedSlippage] = useUserSlippageTolerance()
const showRoute = trade?.route?.path?.length > 2 const showRoute = Boolean(trade && trade.route.path.length > 2)
return ( return (
<AutoColumn gap="md"> <AutoColumn gap="md">
{trade && <TradeSummary trade={trade} allowedSlippage={allowedSlippage} />} {trade && (
<>
<TradeSummary trade={trade} allowedSlippage={allowedSlippage} />
{showRoute && ( {showRoute && (
<> <>
<SectionBreak /> <SectionBreak />
...@@ -92,6 +94,8 @@ export function AdvancedSwapDetails({ trade }: AdvancedSwapDetailsProps) { ...@@ -92,6 +94,8 @@ export function AdvancedSwapDetails({ trade }: AdvancedSwapDetailsProps) {
</AutoColumn> </AutoColumn>
</> </>
)} )}
</>
)}
</AutoColumn> </AutoColumn>
) )
} }
import React from 'react' import React from 'react'
import styled from 'styled-components' import styled from 'styled-components'
import useLast from '../../hooks/useLast' import { useLastTruthy } from '../../hooks/useLast'
import { AdvancedSwapDetails, AdvancedSwapDetailsProps } from './AdvancedSwapDetails' import { AdvancedSwapDetails, AdvancedSwapDetailsProps } from './AdvancedSwapDetails'
const AdvancedDetailsFooter = styled.div<{ show: boolean }>` const AdvancedDetailsFooter = styled.div<{ show: boolean }>`
...@@ -20,11 +20,11 @@ const AdvancedDetailsFooter = styled.div<{ show: boolean }>` ...@@ -20,11 +20,11 @@ const AdvancedDetailsFooter = styled.div<{ show: boolean }>`
` `
export default function AdvancedSwapDetailsDropdown({ trade, ...rest }: AdvancedSwapDetailsProps) { export default function AdvancedSwapDetailsDropdown({ trade, ...rest }: AdvancedSwapDetailsProps) {
const lastTrade = useLast(trade) const lastTrade = useLastTruthy(trade)
return ( return (
<AdvancedDetailsFooter show={Boolean(trade)}> <AdvancedDetailsFooter show={Boolean(trade)}>
<AdvancedSwapDetails {...rest} trade={trade ?? lastTrade} /> <AdvancedSwapDetails {...rest} trade={trade ?? lastTrade ?? undefined} />
</AdvancedDetailsFooter> </AdvancedDetailsFooter>
) )
} }
import { currencyEquals, Trade } from '@uniswap/sdk'
import React, { useCallback, useMemo } from 'react'
import TransactionConfirmationModal, {
ConfirmationModalContent,
TransactionErrorContent
} from '../TransactionConfirmationModal'
import SwapModalFooter from './SwapModalFooter'
import SwapModalHeader from './SwapModalHeader'
/**
* Returns true if the trade requires a confirmation of details before we can submit it
* @param tradeA trade A
* @param tradeB trade B
*/
function tradeMeaningfullyDiffers(tradeA: Trade, tradeB: Trade): boolean {
return (
tradeA.tradeType !== tradeB.tradeType ||
!currencyEquals(tradeA.inputAmount.currency, tradeB.inputAmount.currency) ||
!tradeA.inputAmount.equalTo(tradeB.inputAmount) ||
!currencyEquals(tradeA.outputAmount.currency, tradeB.outputAmount.currency) ||
!tradeA.outputAmount.equalTo(tradeB.outputAmount)
)
}
export default function ConfirmSwapModal({
trade,
originalTrade,
onAcceptChanges,
allowedSlippage,
onConfirm,
onDismiss,
recipient,
swapErrorMessage,
isOpen,
attemptingTxn,
txHash
}: {
isOpen: boolean
trade: Trade | undefined
originalTrade: Trade | undefined
attemptingTxn: boolean
txHash: string | undefined
recipient: string | null
allowedSlippage: number
onAcceptChanges: () => void
onConfirm: () => void
swapErrorMessage: string | undefined
onDismiss: () => void
}) {
const showAcceptChanges = useMemo(
() => Boolean(trade && originalTrade && tradeMeaningfullyDiffers(trade, originalTrade)),
[originalTrade, trade]
)
const modalHeader = useCallback(() => {
return trade ? (
<SwapModalHeader
trade={trade}
allowedSlippage={allowedSlippage}
recipient={recipient}
showAcceptChanges={showAcceptChanges}
onAcceptChanges={onAcceptChanges}
/>
) : null
}, [allowedSlippage, onAcceptChanges, recipient, showAcceptChanges, trade])
const modalBottom = useCallback(() => {
return trade ? (
<SwapModalFooter
onConfirm={onConfirm}
trade={trade}
disabledConfirm={showAcceptChanges}
swapErrorMessage={swapErrorMessage}
allowedSlippage={allowedSlippage}
/>
) : null
}, [allowedSlippage, onConfirm, showAcceptChanges, swapErrorMessage, trade])
// text to show while loading
const pendingText = `Swapping ${trade?.inputAmount?.toSignificant(6)} ${
trade?.inputAmount?.currency?.symbol
} for ${trade?.outputAmount?.toSignificant(6)} ${trade?.outputAmount?.currency?.symbol}`
const confirmationContent = useCallback(
() =>
swapErrorMessage ? (
<TransactionErrorContent onDismiss={onDismiss} message={swapErrorMessage} />
) : (
<ConfirmationModalContent
title="Confirm Swap"
onDismiss={onDismiss}
topContent={modalHeader}
bottomContent={modalBottom}
/>
),
[onDismiss, modalBottom, modalHeader, swapErrorMessage]
)
return (
<TransactionConfirmationModal
isOpen={isOpen}
onDismiss={onDismiss}
attemptingTxn={attemptingTxn}
hash={txHash}
content={confirmationContent}
pendingText={pendingText}
/>
)
}
...@@ -4,10 +4,13 @@ import { ONE_BIPS } from '../../constants' ...@@ -4,10 +4,13 @@ import { ONE_BIPS } from '../../constants'
import { warningSeverity } from '../../utils/prices' import { warningSeverity } from '../../utils/prices'
import { ErrorText } from './styleds' import { ErrorText } from './styleds'
/**
* Formatted version of price impact text with warning colors
*/
export default function FormattedPriceImpact({ priceImpact }: { priceImpact?: Percent }) { export default function FormattedPriceImpact({ priceImpact }: { priceImpact?: Percent }) {
return ( return (
<ErrorText fontWeight={500} fontSize={14} severity={warningSeverity(priceImpact)}> <ErrorText fontWeight={500} fontSize={14} severity={warningSeverity(priceImpact)}>
{priceImpact?.lessThan(ONE_BIPS) ? '<0.01%' : `${priceImpact?.toFixed(2)}%` ?? '-'} {priceImpact ? (priceImpact.lessThan(ONE_BIPS) ? '<0.01%' : `${priceImpact.toFixed(2)}%`) : '-'}
</ErrorText> </ErrorText>
) )
} }
import { CurrencyAmount, Percent, Trade, TradeType } from '@uniswap/sdk' import { Trade, TradeType } from '@uniswap/sdk'
import React, { useContext } from 'react' import React, { useContext, useMemo, useState } from 'react'
import { Repeat } from 'react-feather' import { Repeat } from 'react-feather'
import { Text } from 'rebass' import { Text } from 'rebass'
import { ThemeContext } from 'styled-components' import { ThemeContext } from 'styled-components'
import { Field } from '../../state/swap/actions' import { Field } from '../../state/swap/actions'
import { TYPE } from '../../theme' import { TYPE } from '../../theme'
import { formatExecutionPrice } from '../../utils/prices' import {
computeSlippageAdjustedAmounts,
computeTradePriceBreakdown,
formatExecutionPrice,
warningSeverity
} from '../../utils/prices'
import { ButtonError } from '../Button' import { ButtonError } from '../Button'
import { AutoColumn } from '../Column' import { AutoColumn } from '../Column'
import QuestionHelper from '../QuestionHelper' import QuestionHelper from '../QuestionHelper'
import { AutoRow, RowBetween, RowFixed } from '../Row' import { AutoRow, RowBetween, RowFixed } from '../Row'
import FormattedPriceImpact from './FormattedPriceImpact' import FormattedPriceImpact from './FormattedPriceImpact'
import { StyledBalanceMaxMini } from './styleds' import { StyledBalanceMaxMini, SwapCallbackError } from './styleds'
export default function SwapModalFooter({ export default function SwapModalFooter({
trade, trade,
showInverted, onConfirm,
setShowInverted, allowedSlippage,
severity, swapErrorMessage,
slippageAdjustedAmounts, disabledConfirm
onSwap,
parsedAmounts,
realizedLPFee,
priceImpactWithoutFee,
confirmText
}: { }: {
trade?: Trade trade: Trade
showInverted: boolean allowedSlippage: number
setShowInverted: (inverted: boolean) => void onConfirm: () => void
severity: number swapErrorMessage: string | undefined
slippageAdjustedAmounts?: { [field in Field]?: CurrencyAmount } disabledConfirm: boolean
onSwap: () => any
parsedAmounts?: { [field in Field]?: CurrencyAmount }
realizedLPFee?: CurrencyAmount
priceImpactWithoutFee?: Percent
confirmText: string
}) { }) {
const [showInverted, setShowInverted] = useState<boolean>(false)
const theme = useContext(ThemeContext) const theme = useContext(ThemeContext)
const slippageAdjustedAmounts = useMemo(() => computeSlippageAdjustedAmounts(trade, allowedSlippage), [
if (!trade) { allowedSlippage,
return null trade
} ])
const { priceImpactWithoutFee, realizedLPFee } = useMemo(() => computeTradePriceBreakdown(trade), [trade])
const severity = warningSeverity(priceImpactWithoutFee)
return ( return (
<> <>
...@@ -71,23 +69,21 @@ export default function SwapModalFooter({ ...@@ -71,23 +69,21 @@ export default function SwapModalFooter({
<RowBetween> <RowBetween>
<RowFixed> <RowFixed>
<TYPE.black fontSize={14} fontWeight={400} color={theme.text2}> <TYPE.black fontSize={14} fontWeight={400} color={theme.text2}>
{trade?.tradeType === TradeType.EXACT_INPUT ? 'Minimum received' : 'Maximum sold'} {trade.tradeType === TradeType.EXACT_INPUT ? 'Minimum received' : 'Maximum sold'}
</TYPE.black> </TYPE.black>
<QuestionHelper text="Your transaction will revert if there is a large, unfavorable price movement before it is confirmed." /> <QuestionHelper text="Your transaction will revert if there is a large, unfavorable price movement before it is confirmed." />
</RowFixed> </RowFixed>
<RowFixed> <RowFixed>
<TYPE.black fontSize={14}> <TYPE.black fontSize={14}>
{trade?.tradeType === TradeType.EXACT_INPUT {trade.tradeType === TradeType.EXACT_INPUT
? slippageAdjustedAmounts[Field.OUTPUT]?.toSignificant(4) ?? '-' ? slippageAdjustedAmounts[Field.OUTPUT]?.toSignificant(4) ?? '-'
: slippageAdjustedAmounts[Field.INPUT]?.toSignificant(4) ?? '-'} : slippageAdjustedAmounts[Field.INPUT]?.toSignificant(4) ?? '-'}
</TYPE.black> </TYPE.black>
{parsedAmounts[Field.OUTPUT] && parsedAmounts[Field.INPUT] && (
<TYPE.black fontSize={14} marginLeft={'4px'}> <TYPE.black fontSize={14} marginLeft={'4px'}>
{trade?.tradeType === TradeType.EXACT_INPUT {trade.tradeType === TradeType.EXACT_INPUT
? parsedAmounts[Field.OUTPUT]?.currency?.symbol ? trade.outputAmount.currency.symbol
: parsedAmounts[Field.INPUT]?.currency?.symbol} : trade.inputAmount.currency.symbol}
</TYPE.black> </TYPE.black>
)}
</RowFixed> </RowFixed>
</RowBetween> </RowBetween>
<RowBetween> <RowBetween>
...@@ -107,17 +103,25 @@ export default function SwapModalFooter({ ...@@ -107,17 +103,25 @@ export default function SwapModalFooter({
<QuestionHelper text="A portion of each trade (0.30%) goes to liquidity providers as a protocol incentive." /> <QuestionHelper text="A portion of each trade (0.30%) goes to liquidity providers as a protocol incentive." />
</RowFixed> </RowFixed>
<TYPE.black fontSize={14}> <TYPE.black fontSize={14}>
{realizedLPFee ? realizedLPFee?.toSignificant(6) + ' ' + trade?.inputAmount?.currency?.symbol : '-'} {realizedLPFee ? realizedLPFee?.toSignificant(6) + ' ' + trade.inputAmount.currency.symbol : '-'}
</TYPE.black> </TYPE.black>
</RowBetween> </RowBetween>
</AutoColumn> </AutoColumn>
<AutoRow> <AutoRow>
<ButtonError onClick={onSwap} error={severity > 2} style={{ margin: '10px 0 0 0' }} id="confirm-swap-or-send"> <ButtonError
onClick={onConfirm}
disabled={disabledConfirm}
error={severity > 2}
style={{ margin: '10px 0 0 0' }}
id="confirm-swap-or-send"
>
<Text fontSize={20} fontWeight={500}> <Text fontSize={20} fontWeight={500}>
{confirmText} {severity > 2 ? 'Swap Anyway' : 'Confirm Swap'}
</Text> </Text>
</ButtonError> </ButtonError>
{swapErrorMessage ? <SwapCallbackError error={swapErrorMessage} /> : null}
</AutoRow> </AutoRow>
</> </>
) )
......
import { Currency, CurrencyAmount } from '@uniswap/sdk' import { Trade, TradeType } from '@uniswap/sdk'
import React, { useContext } from 'react' import React, { useContext, useMemo } from 'react'
import { ArrowDown } from 'react-feather' import { ArrowDown, AlertTriangle } from 'react-feather'
import { Text } from 'rebass' import { Text } from 'rebass'
import { ThemeContext } from 'styled-components' import { ThemeContext } from 'styled-components'
import { Field } from '../../state/swap/actions' import { Field } from '../../state/swap/actions'
import { TYPE } from '../../theme' import { TYPE } from '../../theme'
import { ButtonPrimary } from '../Button'
import { isAddress, shortenAddress } from '../../utils' import { isAddress, shortenAddress } from '../../utils'
import { computeSlippageAdjustedAmounts, computeTradePriceBreakdown, warningSeverity } from '../../utils/prices'
import { AutoColumn } from '../Column' import { AutoColumn } from '../Column'
import { RowBetween, RowFixed } from '../Row'
import CurrencyLogo from '../CurrencyLogo' import CurrencyLogo from '../CurrencyLogo'
import { TruncatedText } from './styleds' import { RowBetween, RowFixed } from '../Row'
import { TruncatedText, SwapShowAcceptChanges } from './styleds'
export default function SwapModalHeader({ export default function SwapModalHeader({
currencies, trade,
formattedAmounts, allowedSlippage,
slippageAdjustedAmounts, recipient,
priceImpactSeverity, showAcceptChanges,
independentField, onAcceptChanges
recipient
}: { }: {
currencies: { [field in Field]?: Currency } trade: Trade
formattedAmounts: { [field in Field]?: string } allowedSlippage: number
slippageAdjustedAmounts: { [field in Field]?: CurrencyAmount }
priceImpactSeverity: number
independentField: Field
recipient: string | null recipient: string | null
showAcceptChanges: boolean
onAcceptChanges: () => void
}) { }) {
const slippageAdjustedAmounts = useMemo(() => computeSlippageAdjustedAmounts(trade, allowedSlippage), [
trade,
allowedSlippage
])
const { priceImpactWithoutFee } = useMemo(() => computeTradePriceBreakdown(trade), [trade])
const priceImpactSeverity = warningSeverity(priceImpactWithoutFee)
const theme = useContext(ThemeContext) const theme = useContext(ThemeContext)
return ( return (
<AutoColumn gap={'md'} style={{ marginTop: '20px' }}> <AutoColumn gap={'md'} style={{ marginTop: '20px' }}>
<RowBetween align="flex-end"> <RowBetween align="flex-end">
<TruncatedText fontSize={24} fontWeight={500}> <RowFixed gap={'0px'}>
{formattedAmounts[Field.INPUT]} <CurrencyLogo currency={trade.inputAmount.currency} size={'24px'} style={{ marginRight: '12px' }} />
<TruncatedText
fontSize={24}
fontWeight={500}
color={showAcceptChanges && trade.tradeType === TradeType.EXACT_OUTPUT ? theme.primary1 : ''}
>
{trade.inputAmount.toSignificant(6)}
</TruncatedText> </TruncatedText>
<RowFixed gap="4px"> </RowFixed>
<CurrencyLogo currency={currencies[Field.INPUT]} size={'24px'} /> <RowFixed gap={'0px'}>
<Text fontSize={24} fontWeight={500} style={{ marginLeft: '10px' }}> <Text fontSize={24} fontWeight={500} style={{ marginLeft: '10px' }}>
{currencies[Field.INPUT]?.symbol} {trade.inputAmount.currency.symbol}
</Text> </Text>
</RowFixed> </RowFixed>
</RowBetween> </RowBetween>
<RowFixed> <RowFixed>
<ArrowDown size="16" color={theme.text2} /> <ArrowDown size="16" color={theme.text2} style={{ marginLeft: '4px', minWidth: '16px' }} />
</RowFixed> </RowFixed>
<RowBetween align="flex-end"> <RowBetween align="flex-end">
<TruncatedText fontSize={24} fontWeight={500} color={priceImpactSeverity > 2 ? theme.red1 : ''}> <RowFixed gap={'0px'}>
{formattedAmounts[Field.OUTPUT]} <CurrencyLogo currency={trade.outputAmount.currency} size={'24px'} style={{ marginRight: '12px' }} />
<TruncatedText
fontSize={24}
fontWeight={500}
color={
priceImpactSeverity > 2
? theme.red1
: showAcceptChanges && trade.tradeType === TradeType.EXACT_INPUT
? theme.primary1
: ''
}
>
{trade.outputAmount.toSignificant(6)}
</TruncatedText> </TruncatedText>
<RowFixed gap="4px"> </RowFixed>
<CurrencyLogo currency={currencies[Field.OUTPUT]} size={'24px'} /> <RowFixed gap={'0px'}>
<Text fontSize={24} fontWeight={500} style={{ marginLeft: '10px' }}> <Text fontSize={24} fontWeight={500} style={{ marginLeft: '10px' }}>
{currencies[Field.OUTPUT]?.symbol} {trade.outputAmount.currency.symbol}
</Text> </Text>
</RowFixed> </RowFixed>
</RowBetween> </RowBetween>
{showAcceptChanges ? (
<SwapShowAcceptChanges justify="flex-start" gap={'0px'}>
<RowBetween>
<RowFixed>
<AlertTriangle size={20} style={{ marginRight: '8px', minWidth: 24 }} />
<TYPE.main color={theme.primary1}> Price Updated</TYPE.main>
</RowFixed>
<ButtonPrimary
style={{ padding: '.5rem', width: 'fit-content', fontSize: '0.825rem', borderRadius: '12px' }}
onClick={onAcceptChanges}
>
Accept
</ButtonPrimary>
</RowBetween>
</SwapShowAcceptChanges>
) : null}
<AutoColumn justify="flex-start" gap="sm" style={{ padding: '12px 0 0 0px' }}> <AutoColumn justify="flex-start" gap="sm" style={{ padding: '12px 0 0 0px' }}>
{independentField === Field.INPUT ? ( {trade.tradeType === TradeType.EXACT_INPUT ? (
<TYPE.italic textAlign="left" style={{ width: '100%' }}> <TYPE.italic textAlign="left" style={{ width: '100%' }}>
{`Output is estimated. You will receive at least `} {`Output is estimated. You will receive at least `}
<b> <b>
{slippageAdjustedAmounts[Field.OUTPUT]?.toSignificant(6)} {currencies[Field.OUTPUT]?.symbol} {slippageAdjustedAmounts[Field.OUTPUT]?.toSignificant(6)} {trade.outputAmount.currency.symbol}
</b> </b>
{' or the transaction will revert.'} {' or the transaction will revert.'}
</TYPE.italic> </TYPE.italic>
...@@ -68,7 +109,7 @@ export default function SwapModalHeader({ ...@@ -68,7 +109,7 @@ export default function SwapModalHeader({
<TYPE.italic textAlign="left" style={{ width: '100%' }}> <TYPE.italic textAlign="left" style={{ width: '100%' }}>
{`Input is estimated. You will sell at most `} {`Input is estimated. You will sell at most `}
<b> <b>
{slippageAdjustedAmounts[Field.INPUT]?.toSignificant(6)} {currencies[Field.INPUT]?.symbol} {slippageAdjustedAmounts[Field.INPUT]?.toSignificant(6)} {trade.inputAmount.currency.symbol}
</b> </b>
{' or the transaction will revert.'} {' or the transaction will revert.'}
</TYPE.italic> </TYPE.italic>
......
// gathers additional user consent for a high price impact
import { Percent } from '@uniswap/sdk' import { Percent } from '@uniswap/sdk'
import { ALLOWED_PRICE_IMPACT_HIGH, PRICE_IMPACT_WITHOUT_FEE_CONFIRM_MIN } from '../../constants' import { ALLOWED_PRICE_IMPACT_HIGH, PRICE_IMPACT_WITHOUT_FEE_CONFIRM_MIN } from '../../constants'
/**
* Given the price impact, get user confirmation.
*
* @param priceImpactWithoutFee price impact of the trade without the fee.
*/
export default function confirmPriceImpactWithoutFee(priceImpactWithoutFee: Percent): boolean { export default function confirmPriceImpactWithoutFee(priceImpactWithoutFee: Percent): boolean {
if (!priceImpactWithoutFee.lessThan(PRICE_IMPACT_WITHOUT_FEE_CONFIRM_MIN)) { if (!priceImpactWithoutFee.lessThan(PRICE_IMPACT_WITHOUT_FEE_CONFIRM_MIN)) {
return ( return (
......
import { transparentize } from 'polished'
import React from 'react'
import { AlertTriangle } from 'react-feather'
import styled, { css } from 'styled-components' import styled, { css } from 'styled-components'
import { AutoColumn } from '../Column'
import { Text } from 'rebass' import { Text } from 'rebass'
import { AutoColumn } from '../Column'
import NumericalInput from '../NumericalInput'
export const Wrapper = styled.div` export const Wrapper = styled.div`
position: relative; position: relative;
...@@ -30,7 +31,6 @@ export const SectionBreak = styled.div` ...@@ -30,7 +31,6 @@ export const SectionBreak = styled.div`
export const BottomGrouping = styled.div` export const BottomGrouping = styled.div`
margin-top: 12px; margin-top: 12px;
position: relative;
` `
export const ErrorText = styled(Text)<{ severity?: 0 | 1 | 2 | 3 | 4 }>` export const ErrorText = styled(Text)<{ severity?: 0 | 1 | 2 | 3 | 4 }>`
...@@ -44,21 +44,6 @@ export const ErrorText = styled(Text)<{ severity?: 0 | 1 | 2 | 3 | 4 }>` ...@@ -44,21 +44,6 @@ export const ErrorText = styled(Text)<{ severity?: 0 | 1 | 2 | 3 | 4 }>`
: theme.green1}; : theme.green1};
` `
export const InputGroup = styled(AutoColumn)`
position: relative;
padding: 40px 0 20px 0;
`
export const StyledNumerical = styled(NumericalInput)`
text-align: center;
font-size: 48px;
font-weight: 500px;
width: 100%;
::placeholder {
color: ${({ theme }) => theme.text4};
}
`
export const StyledBalanceMaxMini = styled.button` export const StyledBalanceMaxMini = styled.button`
height: 22px; height: 22px;
width: 22px; width: 22px;
...@@ -112,3 +97,51 @@ export const Dots = styled.span` ...@@ -112,3 +97,51 @@ export const Dots = styled.span`
} }
} }
` `
const SwapCallbackErrorInner = styled.div`
background-color: ${({ theme }) => transparentize(0.9, theme.red1)};
border-radius: 1rem;
display: flex;
align-items: center;
font-size: 0.825rem;
width: 100%;
padding: 3rem 1.25rem 1rem 1rem;
margin-top: -2rem;
color: ${({ theme }) => theme.red1};
z-index: -1;
p {
padding: 0;
margin: 0;
font-weight: 500;
}
`
const SwapCallbackErrorInnerAlertTriangle = styled.div`
background-color: ${({ theme }) => transparentize(0.9, theme.red1)};
display: flex;
align-items: center;
justify-content: center;
margin-right: 12px;
border-radius: 12px;
min-width: 48px;
height: 48px;
`
export function SwapCallbackError({ error }: { error: string }) {
return (
<SwapCallbackErrorInner>
<SwapCallbackErrorInnerAlertTriangle>
<AlertTriangle size={24} />
</SwapCallbackErrorInnerAlertTriangle>
<p>{error}</p>
</SwapCallbackErrorInner>
)
}
export const SwapShowAcceptChanges = styled(AutoColumn)`
background-color: ${({ theme }) => transparentize(0.9, theme.primary1)};
color: ${({ theme }) => theme.primary1};
padding: 0.5rem;
border-radius: 12px;
margin-top: 8px;
`
{
"extends": "../../../tsconfig.strict.json",
"include": ["**/*"]
}
\ No newline at end of file
...@@ -22,19 +22,83 @@ class RequestError extends Error { ...@@ -22,19 +22,83 @@ class RequestError extends Error {
} }
} }
interface BatchItem {
request: { jsonrpc: '2.0'; id: number; method: string; params: unknown }
resolve: (result: any) => void
reject: (error: Error) => void
}
class MiniRpcProvider implements AsyncSendable { class MiniRpcProvider implements AsyncSendable {
public readonly isMetaMask: false = false public readonly isMetaMask: false = false
public readonly chainId: number public readonly chainId: number
public readonly url: string public readonly url: string
public readonly host: string public readonly host: string
public readonly path: string public readonly path: string
public readonly batchWaitTimeMs: number
private nextId = 1
private batchTimeoutId: ReturnType<typeof setTimeout> | null = null
private batch: BatchItem[] = []
constructor(chainId: number, url: string) { constructor(chainId: number, url: string, batchWaitTimeMs?: number) {
this.chainId = chainId this.chainId = chainId
this.url = url this.url = url
const parsed = new URL(url) const parsed = new URL(url)
this.host = parsed.host this.host = parsed.host
this.path = parsed.pathname this.path = parsed.pathname
// how long to wait to batch calls
this.batchWaitTimeMs = batchWaitTimeMs ?? 50
}
public readonly clearBatch = async () => {
console.debug('Clearing batch', this.batch)
const batch = this.batch
this.batch = []
this.batchTimeoutId = null
let response: Response
try {
response = await fetch(this.url, {
method: 'POST',
headers: { 'content-type': 'application/json', accept: 'application/json' },
body: JSON.stringify(batch.map(item => item.request))
})
} catch (error) {
batch.forEach(({ reject }) => reject(new Error('Failed to send batch call')))
return
}
if (!response.ok) {
batch.forEach(({ reject }) => reject(new RequestError(`${response.status}: ${response.statusText}`, -32000)))
return
}
let json
try {
json = await response.json()
} catch (error) {
batch.forEach(({ reject }) => reject(new Error('Failed to parse JSON response')))
return
}
const byKey = batch.reduce<{ [id: number]: BatchItem }>((memo, current) => {
memo[current.request.id] = current
return memo
}, {})
for (const result of json) {
const {
resolve,
reject,
request: { method }
} = byKey[result.id]
if (resolve && reject) {
if ('error' in result) {
reject(new RequestError(result?.error?.message, result?.error?.code, result?.error?.data))
} else if ('result' in result) {
resolve(result.result)
} else {
reject(new RequestError(`Received unexpected JSON-RPC response to ${method} request.`, -32000, result))
}
}
}
} }
public readonly sendAsync = ( public readonly sendAsync = (
...@@ -56,24 +120,20 @@ class MiniRpcProvider implements AsyncSendable { ...@@ -56,24 +120,20 @@ class MiniRpcProvider implements AsyncSendable {
if (method === 'eth_chainId') { if (method === 'eth_chainId') {
return `0x${this.chainId.toString(16)}` return `0x${this.chainId.toString(16)}`
} }
const response = await fetch(this.url, { const promise = new Promise((resolve, reject) => {
method: 'POST', this.batch.push({
body: JSON.stringify({ request: {
jsonrpc: '2.0', jsonrpc: '2.0',
id: 1, id: this.nextId++,
method, method,
params params
},
resolve,
reject
}) })
}) })
if (!response.ok) throw new RequestError(`${response.status}: ${response.statusText}`, -32000) this.batchTimeoutId = this.batchTimeoutId ?? setTimeout(this.clearBatch, this.batchWaitTimeMs)
const body = await response.json() return promise
if ('error' in body) {
throw new RequestError(body?.error?.message, body?.error?.code, body?.error?.data)
} else if ('result' in body) {
return body.result
} else {
throw new RequestError(`Received unexpected JSON-RPC response to ${method} request.`, -32000, body)
}
} }
} }
......
import { AbstractConnector } from '@web3-react/abstract-connector'
import { ChainId, JSBI, Percent, Token, WETH } from '@uniswap/sdk' import { ChainId, JSBI, Percent, Token, WETH } from '@uniswap/sdk'
import { fortmatic, injected, portis, walletconnect, walletlink } from '../connectors' import { fortmatic, injected, portis, walletconnect, walletlink } from '../connectors'
...@@ -52,7 +53,19 @@ export const PINNED_PAIRS: { readonly [chainId in ChainId]?: [Token, Token][] } ...@@ -52,7 +53,19 @@ export const PINNED_PAIRS: { readonly [chainId in ChainId]?: [Token, Token][] }
] ]
} }
const TESTNET_CAPABLE_WALLETS = { export interface WalletInfo {
connector?: AbstractConnector
name: string
iconName: string
description: string
href: string | null
color: string
primary?: true
mobile?: true
mobileOnly?: true
}
export const SUPPORTED_WALLETS: { [key: string]: WalletInfo } = {
INJECTED: { INJECTED: {
connector: injected, connector: injected,
name: 'Injected', name: 'Injected',
...@@ -69,15 +82,7 @@ const TESTNET_CAPABLE_WALLETS = { ...@@ -69,15 +82,7 @@ const TESTNET_CAPABLE_WALLETS = {
description: 'Easy-to-use browser extension.', description: 'Easy-to-use browser extension.',
href: null, href: null,
color: '#E8831D' color: '#E8831D'
} },
}
export const SUPPORTED_WALLETS =
process.env.REACT_APP_CHAIN_ID !== '1'
? TESTNET_CAPABLE_WALLETS
: {
...TESTNET_CAPABLE_WALLETS,
...{
WALLET_CONNECT: { WALLET_CONNECT: {
connector: walletconnect, connector: walletconnect,
name: 'WalletConnect', name: 'WalletConnect',
...@@ -122,8 +127,7 @@ export const SUPPORTED_WALLETS = ...@@ -122,8 +127,7 @@ export const SUPPORTED_WALLETS =
color: '#4A6C9B', color: '#4A6C9B',
mobile: true mobile: true
} }
} }
}
export const NetworkContextName = 'NETWORK' export const NetworkContextName = 'NETWORK'
......
...@@ -131,7 +131,7 @@ export function useV1Trade( ...@@ -131,7 +131,7 @@ export function useV1Trade(
? new Trade(route, exactAmount, isExactIn ? TradeType.EXACT_INPUT : TradeType.EXACT_OUTPUT) ? new Trade(route, exactAmount, isExactIn ? TradeType.EXACT_INPUT : TradeType.EXACT_OUTPUT)
: undefined : undefined
} catch (error) { } catch (error) {
console.error('Failed to create V1 trade', error) console.debug('Failed to create V1 trade', error)
} }
return v1Trade return v1Trade
} }
......
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
/** /**
* Returns the last truthy value of type T * Returns the last value of type T that passes a filter function
* @param value changing value * @param value changing value
* @param filterFn function that determines whether a given value should be considered for the last value
*/ */
export default function useLast<T>(value: T | undefined | null): T | null | undefined { export default function useLast<T>(
const [last, setLast] = useState<T | null | undefined>(value) value: T | undefined | null,
filterFn?: (value: T | null | undefined) => boolean
): T | null | undefined {
const [last, setLast] = useState<T | null | undefined>(filterFn && filterFn(value) ? value : undefined)
useEffect(() => { useEffect(() => {
setLast(last => value ?? last) setLast(last => {
}, [value]) const shouldUse: boolean = filterFn ? filterFn(value) : true
if (shouldUse) return value
return last
})
}, [filterFn, value])
return last return last
} }
function isDefined<T>(x: T | null | undefined): x is T {
return x !== null && x !== undefined
}
/**
* Returns the last truthy value of type T
* @param value changing value
*/
export function useLastTruthy<T>(value: T | undefined | null): T | null | undefined {
return useLast(value, isDefined)
}
import { BigNumber } from '@ethersproject/bignumber' import { BigNumber } from '@ethersproject/bignumber'
import { Contract } from '@ethersproject/contracts' import { Contract } from '@ethersproject/contracts'
import { JSBI, Percent, Router, Trade, TradeType } from '@uniswap/sdk' import { JSBI, Percent, Router, SwapParameters, Trade, TradeType } from '@uniswap/sdk'
import { useMemo } from 'react' import { useMemo } from 'react'
import { BIPS_BASE, DEFAULT_DEADLINE_FROM_NOW, INITIAL_ALLOWED_SLIPPAGE } from '../constants' import { BIPS_BASE, DEFAULT_DEADLINE_FROM_NOW, INITIAL_ALLOWED_SLIPPAGE } from '../constants'
import { getTradeVersion, useV1TradeExchangeAddress } from '../data/V1' import { getTradeVersion, useV1TradeExchangeAddress } from '../data/V1'
import { useTransactionAdder } from '../state/transactions/hooks' import { useTransactionAdder } from '../state/transactions/hooks'
import { calculateGasMargin, getRouterContract, isAddress, shortenAddress } from '../utils' import { calculateGasMargin, getRouterContract, isAddress, shortenAddress } from '../utils'
import isZero from '../utils/isZero'
import v1SwapArguments from '../utils/v1SwapArguments' import v1SwapArguments from '../utils/v1SwapArguments'
import { useActiveWeb3React } from './index' import { useActiveWeb3React } from './index'
import { useV1ExchangeContract } from './useContract' import { useV1ExchangeContract } from './useContract'
import useENS from './useENS' import useENS from './useENS'
import { Version } from './useToggledVersion' import { Version } from './useToggledVersion'
function isZero(hexNumber: string) { export enum SwapCallbackState {
return /^0x0*$/.test(hexNumber) INVALID,
LOADING,
VALID
} }
// returns a function that will execute a swap, if the parameters are all valid interface SwapCall {
// and the user has approved the slippage adjusted input amount for the trade contract: Contract
export function useSwapCallback( parameters: SwapParameters
}
interface SuccessfulCall {
call: SwapCall
gasEstimate: BigNumber
}
interface FailedCall {
call: SwapCall
error: Error
}
type EstimatedSwapCall = SuccessfulCall | FailedCall
/**
* Returns the swap calls that can be used to make the trade
* @param trade trade to execute
* @param allowedSlippage user allowed slippage
* @param deadline the deadline for the trade
* @param recipientAddressOrName
*/
function useSwapCallArguments(
trade: Trade | undefined, // trade to execute, required trade: Trade | undefined, // trade to execute, required
allowedSlippage: number = INITIAL_ALLOWED_SLIPPAGE, // in bips allowedSlippage: number = INITIAL_ALLOWED_SLIPPAGE, // in bips
deadline: number = DEFAULT_DEADLINE_FROM_NOW, // in seconds from now deadline: number = DEFAULT_DEADLINE_FROM_NOW, // in seconds from now
recipientAddressOrName: string | null // the ENS name or address of the recipient of the trade, or null if swap should be returned to sender recipientAddressOrName: string | null // the ENS name or address of the recipient of the trade, or null if swap should be returned to sender
): null | (() => Promise<string>) { ): SwapCall[] {
const { account, chainId, library } = useActiveWeb3React() const { account, chainId, library } = useActiveWeb3React()
const addTransaction = useTransactionAdder()
const { address: recipientAddress } = useENS(recipientAddressOrName) const { address: recipientAddress } = useENS(recipientAddressOrName)
const recipient = recipientAddressOrName === null ? account : recipientAddress const recipient = recipientAddressOrName === null ? account : recipientAddress
const tradeVersion = getTradeVersion(trade)
const v1Exchange = useV1ExchangeContract(useV1TradeExchangeAddress(trade), true) const v1Exchange = useV1ExchangeContract(useV1TradeExchangeAddress(trade), true)
return useMemo(() => { return useMemo(() => {
if (!trade || !recipient || !library || !account || !tradeVersion || !chainId) return null const tradeVersion = getTradeVersion(trade)
if (!trade || !recipient || !library || !account || !tradeVersion || !chainId) return []
return async function onSwap() {
const contract: Contract | null = const contract: Contract | null =
tradeVersion === Version.v2 ? getRouterContract(chainId, library, account) : v1Exchange tradeVersion === Version.v2 ? getRouterContract(chainId, library, account) : v1Exchange
if (!contract) { if (!contract) {
throw new Error('Failed to get a swap contract') return []
} }
const swapMethods = [] const swapMethods = []
...@@ -77,61 +100,108 @@ export function useSwapCallback( ...@@ -77,61 +100,108 @@ export function useSwapCallback(
) )
break break
} }
return swapMethods.map(parameters => ({ parameters, contract }))
}, [account, allowedSlippage, chainId, deadline, library, recipient, trade, v1Exchange])
}
const safeGasEstimates: (BigNumber | undefined)[] = await Promise.all( const DEFAULT_FAILED_SWAP_ERROR = 'Unexpected error. Please try again or contact support.'
swapMethods.map(({ args, methodName, value }) =>
contract.estimateGas[methodName](...args, value && !isZero(value) ? { value } : {})
.then(calculateGasMargin)
.catch(error => {
console.error(`estimateGas failed for ${methodName}`, error)
return undefined
})
)
)
// we expect failures from left to right, so throw if we see failures // returns a function that will execute a swap, if the parameters are all valid
// from right to left // and the user has approved the slippage adjusted input amount for the trade
for (let i = 0; i < safeGasEstimates.length - 1; i++) { export function useSwapCallback(
// if the FoT method fails, but the regular method does not, we should not trade: Trade | undefined, // trade to execute, required
// use the regular method. this probably means something is wrong with the fot token. allowedSlippage: number = INITIAL_ALLOWED_SLIPPAGE, // in bips
if (BigNumber.isBigNumber(safeGasEstimates[i]) && !BigNumber.isBigNumber(safeGasEstimates[i + 1])) { deadline: number = DEFAULT_DEADLINE_FROM_NOW, // in seconds from now
throw new Error( recipientAddressOrName: string | null // the ENS name or address of the recipient of the trade, or null if swap should be returned to sender
'An error occurred. Please try raising your slippage. If that does not work, contact support.' ): { state: SwapCallbackState; callback: null | (() => Promise<string>); error: string | null } {
) const { account, chainId, library } = useActiveWeb3React()
const swapCalls = useSwapCallArguments(trade, allowedSlippage, deadline, recipientAddressOrName)
const addTransaction = useTransactionAdder()
const { address: recipientAddress } = useENS(recipientAddressOrName)
const recipient = recipientAddressOrName === null ? account : recipientAddress
return useMemo(() => {
if (!trade || !library || !account || !chainId) {
return { state: SwapCallbackState.INVALID, callback: null, error: 'Missing dependencies' }
}
if (!recipient) {
if (recipientAddressOrName !== null) {
return { state: SwapCallbackState.INVALID, callback: null, error: 'Invalid recipient' }
} else {
return { state: SwapCallbackState.LOADING, callback: null, error: null }
} }
} }
const indexOfSuccessfulEstimation = safeGasEstimates.findIndex(safeGasEstimate => const tradeVersion = getTradeVersion(trade)
BigNumber.isBigNumber(safeGasEstimate)
)
// all estimations failed... return {
if (indexOfSuccessfulEstimation === -1) { state: SwapCallbackState.VALID,
// if only 1 method exists, either: callback: async function onSwap(): Promise<string> {
// a) the token is doing something weird not related to FoT (e.g. enforcing a whitelist) const estimatedCalls: EstimatedSwapCall[] = await Promise.all(
// b) the token is FoT and the user specified an exact output, which is not allowed swapCalls.map(call => {
if (swapMethods.length === 1) { const {
throw Error( parameters: { methodName, args, value },
`An error occurred. If either of the tokens you're swapping take a fee on transfer, you must specify an exact input amount.` contract
) } = call
const options = !value || isZero(value) ? {} : { value }
return contract.estimateGas[methodName](...args, options)
.then(gasEstimate => {
return {
call,
gasEstimate
}
})
.catch(gasError => {
console.debug('Gas estimate failed, trying eth_call to extract error', call)
return contract.callStatic[methodName](...args, options)
.then(result => {
console.debug('Unexpected successful call after failed estimate gas', call, gasError, result)
return { call, error: new Error(DEFAULT_FAILED_SWAP_ERROR) }
})
.catch(callError => {
console.debug('Call threw error', call, callError)
let errorMessage: string = DEFAULT_FAILED_SWAP_ERROR
switch (callError.reason) {
case 'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT':
case 'UniswapV2Router: EXCESSIVE_INPUT_AMOUNT':
errorMessage =
'This transaction will not succeed either due to price movement or fee on transfer. Try increasing your slippage tolerance.'
break
} }
// if 2 methods exists, either: return { call, error: new Error(errorMessage) }
// a) the token is doing something weird not related to FoT (e.g. enforcing a whitelist) })
// b) the token is FoT and is taking more than the specified slippage })
else if (swapMethods.length === 2) { })
throw Error(
`An error occurred. If either of the tokens you're swapping take a fee on transfer, you must specify a slippage tolerance higher than the fee.`
) )
} else {
throw Error('This transaction would fail. Please contact support.') // a successful estimation is a bignumber gas estimate and the next call is also a bignumber gas estimate
const successfulEstimation = estimatedCalls.find(
(el, ix, list): el is SuccessfulCall =>
'gasEstimate' in el && (ix === list.length - 1 || 'gasEstimate' in list[ix + 1])
)
if (!successfulEstimation) {
const errorCalls = estimatedCalls.filter((call): call is FailedCall => 'error' in call)
if (errorCalls.length > 0) throw errorCalls[errorCalls.length - 1].error
throw new Error(DEFAULT_FAILED_SWAP_ERROR)
} }
} else {
const { methodName, args, value } = swapMethods[indexOfSuccessfulEstimation] const {
const safeGasEstimate = safeGasEstimates[indexOfSuccessfulEstimation] call: {
contract,
parameters: { methodName, args, value }
},
gasEstimate
} = successfulEstimation
return contract[methodName](...args, { return contract[methodName](...args, {
gasLimit: safeGasEstimate, gasLimit: calculateGasMargin(gasEstimate),
...(value && !isZero(value) ? { value } : {}) ...(value && !isZero(value) ? { value, from: account } : { from: account })
}) })
.then((response: any) => { .then((response: any) => {
const inputSymbol = trade.inputAmount.currency.symbol const inputSymbol = trade.inputAmount.currency.symbol
...@@ -161,27 +231,15 @@ export function useSwapCallback( ...@@ -161,27 +231,15 @@ export function useSwapCallback(
.catch((error: any) => { .catch((error: any) => {
// if the user rejected the tx, pass this along // if the user rejected the tx, pass this along
if (error?.code === 4001) { if (error?.code === 4001) {
throw error throw new Error('Transaction rejected.')
} } else {
// otherwise, the error was unexpected and we need to convey that // otherwise, the error was unexpected and we need to convey that
else {
console.error(`Swap failed`, error, methodName, args, value) console.error(`Swap failed`, error, methodName, args, value)
throw Error('An error occurred while swapping. Please contact support.') throw new Error(DEFAULT_FAILED_SWAP_ERROR)
} }
}) })
},
error: null
} }
} }, [trade, library, account, chainId, recipient, recipientAddressOrName, swapCalls, addTransaction])
}, [
trade,
recipient,
library,
account,
tradeVersion,
chainId,
allowedSlippage,
v1Exchange,
deadline,
recipientAddressOrName,
addTransaction
])
} }
...@@ -23,7 +23,7 @@ export default function useWrapCallback( ...@@ -23,7 +23,7 @@ export default function useWrapCallback(
inputCurrency: Currency | undefined, inputCurrency: Currency | undefined,
outputCurrency: Currency | undefined, outputCurrency: Currency | undefined,
typedValue: string | undefined typedValue: string | undefined
): { wrapType: WrapType; execute?: undefined | (() => Promise<void>); error?: string } { ): { wrapType: WrapType; execute?: undefined | (() => Promise<void>); inputError?: string } {
const { chainId, account } = useActiveWeb3React() const { chainId, account } = useActiveWeb3React()
const wethContract = useWETHContract() const wethContract = useWETHContract()
const balance = useCurrencyBalance(account ?? undefined, inputCurrency) const balance = useCurrencyBalance(account ?? undefined, inputCurrency)
...@@ -50,7 +50,7 @@ export default function useWrapCallback( ...@@ -50,7 +50,7 @@ export default function useWrapCallback(
} }
} }
: undefined, : undefined,
error: sufficientBalance ? undefined : 'Insufficient ETH balance' inputError: sufficientBalance ? undefined : 'Insufficient ETH balance'
} }
} else if (currencyEquals(WETH[chainId], inputCurrency) && outputCurrency === ETHER) { } else if (currencyEquals(WETH[chainId], inputCurrency) && outputCurrency === ETHER) {
return { return {
...@@ -66,7 +66,7 @@ export default function useWrapCallback( ...@@ -66,7 +66,7 @@ export default function useWrapCallback(
} }
} }
: undefined, : undefined,
error: sufficientBalance ? undefined : 'Insufficient WETH balance' inputError: sufficientBalance ? undefined : 'Insufficient WETH balance'
} }
} else { } else {
return NOT_APPLICABLE return NOT_APPLICABLE
......
import { Currency, Fraction, Percent } from '@uniswap/sdk' import { Currency, Percent, Price } from '@uniswap/sdk'
import React, { useContext } from 'react' import React, { useContext } from 'react'
import { Text } from 'rebass' import { Text } from 'rebass'
import { ThemeContext } from 'styled-components' import { ThemeContext } from 'styled-components'
...@@ -8,7 +8,7 @@ import { ONE_BIPS } from '../../constants' ...@@ -8,7 +8,7 @@ import { ONE_BIPS } from '../../constants'
import { Field } from '../../state/mint/actions' import { Field } from '../../state/mint/actions'
import { TYPE } from '../../theme' import { TYPE } from '../../theme'
export const PoolPriceBar = ({ export function PoolPriceBar({
currencies, currencies,
noLiquidity, noLiquidity,
poolTokenPercentage, poolTokenPercentage,
...@@ -17,20 +17,20 @@ export const PoolPriceBar = ({ ...@@ -17,20 +17,20 @@ export const PoolPriceBar = ({
currencies: { [field in Field]?: Currency } currencies: { [field in Field]?: Currency }
noLiquidity?: boolean noLiquidity?: boolean
poolTokenPercentage?: Percent poolTokenPercentage?: Percent
price?: Fraction price?: Price
}) => { }) {
const theme = useContext(ThemeContext) const theme = useContext(ThemeContext)
return ( return (
<AutoColumn gap="md"> <AutoColumn gap="md">
<AutoRow justify="space-around" gap="4px"> <AutoRow justify="space-around" gap="4px">
<AutoColumn justify="center"> <AutoColumn justify="center">
<TYPE.black>{price?.toSignificant(6) ?? '0'}</TYPE.black> <TYPE.black>{price?.toSignificant(6) ?? '-'}</TYPE.black>
<Text fontWeight={500} fontSize={14} color={theme.text2} pt={1}> <Text fontWeight={500} fontSize={14} color={theme.text2} pt={1}>
{currencies[Field.CURRENCY_B]?.symbol} per {currencies[Field.CURRENCY_A]?.symbol} {currencies[Field.CURRENCY_B]?.symbol} per {currencies[Field.CURRENCY_A]?.symbol}
</Text> </Text>
</AutoColumn> </AutoColumn>
<AutoColumn justify="center"> <AutoColumn justify="center">
<TYPE.black>{price?.invert().toSignificant(6) ?? '0'}</TYPE.black> <TYPE.black>{price?.invert()?.toSignificant(6) ?? '-'}</TYPE.black>
<Text fontWeight={500} fontSize={14} color={theme.text2} pt={1}> <Text fontWeight={500} fontSize={14} color={theme.text2} pt={1}>
{currencies[Field.CURRENCY_A]?.symbol} per {currencies[Field.CURRENCY_B]?.symbol} {currencies[Field.CURRENCY_A]?.symbol} per {currencies[Field.CURRENCY_B]?.symbol}
</Text> </Text>
......
...@@ -10,7 +10,7 @@ import { ThemeContext } from 'styled-components' ...@@ -10,7 +10,7 @@ import { ThemeContext } from 'styled-components'
import { ButtonError, ButtonLight, ButtonPrimary } from '../../components/Button' import { ButtonError, ButtonLight, ButtonPrimary } from '../../components/Button'
import { BlueCard, GreyCard, LightCard } from '../../components/Card' import { BlueCard, GreyCard, LightCard } from '../../components/Card'
import { AutoColumn, ColumnCenter } from '../../components/Column' import { AutoColumn, ColumnCenter } from '../../components/Column'
import ConfirmationModal from '../../components/ConfirmationModal' import TransactionConfirmationModal, { ConfirmationModalContent } from '../../components/TransactionConfirmationModal'
import CurrencyInputPanel from '../../components/CurrencyInputPanel' import CurrencyInputPanel from '../../components/CurrencyInputPanel'
import DoubleCurrencyLogo from '../../components/DoubleLogo' import DoubleCurrencyLogo from '../../components/DoubleLogo'
import { AddRemoveTabs } from '../../components/NavigationTabs' import { AddRemoveTabs } from '../../components/NavigationTabs'
...@@ -294,27 +294,34 @@ export default function AddLiquidity({ ...@@ -294,27 +294,34 @@ export default function AddLiquidity({
[currencyIdA, history, currencyIdB] [currencyIdA, history, currencyIdB]
) )
return ( const handleDismissConfirmation = useCallback(() => {
<>
<AppBody>
<AddRemoveTabs adding={true} />
<Wrapper>
<ConfirmationModal
isOpen={showConfirm}
onDismiss={() => {
setShowConfirm(false) setShowConfirm(false)
// if there was a tx hash, we want to clear the input // if there was a tx hash, we want to clear the input
if (txHash) { if (txHash) {
onFieldAInput('') onFieldAInput('')
} }
setTxHash('') setTxHash('')
}} }, [onFieldAInput, txHash])
return (
<>
<AppBody>
<AddRemoveTabs adding={true} />
<Wrapper>
<TransactionConfirmationModal
isOpen={showConfirm}
onDismiss={handleDismissConfirmation}
attemptingTxn={attemptingTxn} attemptingTxn={attemptingTxn}
hash={txHash} hash={txHash}
content={() => (
<ConfirmationModalContent
title={noLiquidity ? 'You are creating a pool' : 'You will receive'}
onDismiss={handleDismissConfirmation}
topContent={modalHeader} topContent={modalHeader}
bottomContent={modalBottom} bottomContent={modalBottom}
/>
)}
pendingText={pendingText} pendingText={pendingText}
title={noLiquidity ? 'You are creating a pool' : 'You will receive'}
/> />
<AutoColumn gap="20px"> <AutoColumn gap="20px">
{noLiquidity && ( {noLiquidity && (
......
...@@ -11,7 +11,7 @@ import { ThemeContext } from 'styled-components' ...@@ -11,7 +11,7 @@ import { ThemeContext } from 'styled-components'
import { ButtonPrimary, ButtonLight, ButtonError, ButtonConfirmed } from '../../components/Button' import { ButtonPrimary, ButtonLight, ButtonError, ButtonConfirmed } from '../../components/Button'
import { LightCard } from '../../components/Card' import { LightCard } from '../../components/Card'
import { AutoColumn, ColumnCenter } from '../../components/Column' import { AutoColumn, ColumnCenter } from '../../components/Column'
import ConfirmationModal from '../../components/ConfirmationModal' import TransactionConfirmationModal, { ConfirmationModalContent } from '../../components/TransactionConfirmationModal'
import CurrencyInputPanel from '../../components/CurrencyInputPanel' import CurrencyInputPanel from '../../components/CurrencyInputPanel'
import DoubleCurrencyLogo from '../../components/DoubleLogo' import DoubleCurrencyLogo from '../../components/DoubleLogo'
import { AddRemoveTabs } from '../../components/NavigationTabs' import { AddRemoveTabs } from '../../components/NavigationTabs'
...@@ -274,12 +274,13 @@ export default function RemoveLiquidity({ ...@@ -274,12 +274,13 @@ export default function RemoveLiquidity({
throw new Error('Attempting to confirm without approval or a signature. Please contact support.') throw new Error('Attempting to confirm without approval or a signature. Please contact support.')
} }
const safeGasEstimates = await Promise.all( const safeGasEstimates: (BigNumber | undefined)[] = await Promise.all(
methodNames.map(methodName => methodNames.map(methodName =>
router.estimateGas[methodName](...args) router.estimateGas[methodName](...args)
.then(calculateGasMargin) .then(calculateGasMargin)
.catch(error => { .catch(error => {
console.error(`estimateGas failed for ${methodName}`, error) console.error(`estimateGas failed`, methodName, args, error)
return undefined
}) })
) )
) )
...@@ -447,14 +448,7 @@ export default function RemoveLiquidity({ ...@@ -447,14 +448,7 @@ export default function RemoveLiquidity({
[currencyIdA, currencyIdB, history] [currencyIdA, currencyIdB, history]
) )
return ( const handleDismissConfirmation = useCallback(() => {
<>
<AppBody>
<AddRemoveTabs adding={false} />
<Wrapper>
<ConfirmationModal
isOpen={showConfirm}
onDismiss={() => {
setShowConfirm(false) setShowConfirm(false)
setSignatureData(null) // important that we clear signature data to avoid bad sigs setSignatureData(null) // important that we clear signature data to avoid bad sigs
// if there was a tx hash, we want to clear the input // if there was a tx hash, we want to clear the input
...@@ -462,13 +456,27 @@ export default function RemoveLiquidity({ ...@@ -462,13 +456,27 @@ export default function RemoveLiquidity({
onUserInput(Field.LIQUIDITY_PERCENT, '0') onUserInput(Field.LIQUIDITY_PERCENT, '0')
} }
setTxHash('') setTxHash('')
}} }, [onUserInput, txHash])
return (
<>
<AppBody>
<AddRemoveTabs adding={false} />
<Wrapper>
<TransactionConfirmationModal
isOpen={showConfirm}
onDismiss={handleDismissConfirmation}
attemptingTxn={attemptingTxn} attemptingTxn={attemptingTxn}
hash={txHash ? txHash : ''} hash={txHash ? txHash : ''}
content={() => (
<ConfirmationModalContent
title={'You will receive'}
onDismiss={handleDismissConfirmation}
topContent={modalHeader} topContent={modalHeader}
bottomContent={modalBottom} bottomContent={modalBottom}
/>
)}
pendingText={pendingText} pendingText={pendingText}
title="You will receive"
/> />
<AutoColumn gap="md"> <AutoColumn gap="md">
<LightCard> <LightCard>
......
import { CurrencyAmount, JSBI } from '@uniswap/sdk' import { CurrencyAmount, JSBI, Trade } from '@uniswap/sdk'
import React, { useCallback, useContext, useEffect, useState } from 'react' import React, { useCallback, useContext, useEffect, useState } from 'react'
import { ArrowDown } from 'react-feather' import { ArrowDown } from 'react-feather'
import ReactGA from 'react-ga' import ReactGA from 'react-ga'
...@@ -8,16 +8,14 @@ import AddressInputPanel from '../../components/AddressInputPanel' ...@@ -8,16 +8,14 @@ import AddressInputPanel from '../../components/AddressInputPanel'
import { ButtonError, ButtonLight, ButtonPrimary } from '../../components/Button' import { ButtonError, ButtonLight, ButtonPrimary } from '../../components/Button'
import Card, { GreyCard } from '../../components/Card' import Card, { GreyCard } from '../../components/Card'
import { AutoColumn } from '../../components/Column' import { AutoColumn } from '../../components/Column'
import ConfirmationModal from '../../components/ConfirmationModal' import ConfirmSwapModal from '../../components/swap/ConfirmSwapModal'
import CurrencyInputPanel from '../../components/CurrencyInputPanel' import CurrencyInputPanel from '../../components/CurrencyInputPanel'
import { SwapPoolTabs } from '../../components/NavigationTabs' import { SwapPoolTabs } from '../../components/NavigationTabs'
import { AutoRow, RowBetween } from '../../components/Row' import { AutoRow, RowBetween } from '../../components/Row'
import AdvancedSwapDetailsDropdown from '../../components/swap/AdvancedSwapDetailsDropdown' import AdvancedSwapDetailsDropdown from '../../components/swap/AdvancedSwapDetailsDropdown'
import BetterTradeLink from '../../components/swap/BetterTradeLink' import BetterTradeLink from '../../components/swap/BetterTradeLink'
import confirmPriceImpactWithoutFee from '../../components/swap/confirmPriceImpactWithoutFee' import confirmPriceImpactWithoutFee from '../../components/swap/confirmPriceImpactWithoutFee'
import { ArrowWrapper, BottomGrouping, Dots, Wrapper } from '../../components/swap/styleds' import { ArrowWrapper, BottomGrouping, Dots, SwapCallbackError, Wrapper } from '../../components/swap/styleds'
import SwapModalFooter from '../../components/swap/SwapModalFooter'
import SwapModalHeader from '../../components/swap/SwapModalHeader'
import TradePrice from '../../components/swap/TradePrice' import TradePrice from '../../components/swap/TradePrice'
import { TokenWarningCards } from '../../components/TokenWarningCard' import { TokenWarningCards } from '../../components/TokenWarningCard'
...@@ -39,13 +37,13 @@ import { ...@@ -39,13 +37,13 @@ import {
} from '../../state/swap/hooks' } from '../../state/swap/hooks'
import { import {
useExpertModeManager, useExpertModeManager,
useTokenWarningDismissal,
useUserDeadline, useUserDeadline,
useUserSlippageTolerance, useUserSlippageTolerance
useTokenWarningDismissal
} from '../../state/user/hooks' } from '../../state/user/hooks'
import { CursorPointer, LinkStyledButton, TYPE } from '../../theme' import { CursorPointer, LinkStyledButton, TYPE } from '../../theme'
import { maxAmountSpend } from '../../utils/maxAmountSpend' import { maxAmountSpend } from '../../utils/maxAmountSpend'
import { computeSlippageAdjustedAmounts, computeTradePriceBreakdown, warningSeverity } from '../../utils/prices' import { computeTradePriceBreakdown, warningSeverity } from '../../utils/prices'
import AppBody from '../AppBody' import AppBody from '../AppBody'
import { ClickableText } from '../Pool/styleds' import { ClickableText } from '../Pool/styleds'
...@@ -60,7 +58,7 @@ export default function Swap() { ...@@ -60,7 +58,7 @@ export default function Swap() {
// for expert mode // for expert mode
const toggleSettings = useToggleSettingsMenu() const toggleSettings = useToggleSettingsMenu()
const [expertMode] = useExpertModeManager() const [isExpertMode] = useExpertModeManager()
// get custom setting values for user // get custom setting values for user
const [deadline] = useUserDeadline() const [deadline] = useUserDeadline()
...@@ -68,8 +66,15 @@ export default function Swap() { ...@@ -68,8 +66,15 @@ export default function Swap() {
// swap state // swap state
const { independentField, typedValue, recipient } = useSwapState() const { independentField, typedValue, recipient } = useSwapState()
const { v1Trade, v2Trade, currencyBalances, parsedAmount, currencies, error } = useDerivedSwapInfo() const {
const { wrapType, execute: onWrap, error: wrapError } = useWrapCallback( v1Trade,
v2Trade,
currencyBalances,
parsedAmount,
currencies,
inputError: swapInputError
} = useDerivedSwapInfo()
const { wrapType, execute: onWrap, inputError: wrapInputError } = useWrapCallback(
currencies[Field.INPUT], currencies[Field.INPUT],
currencies[Field.OUTPUT], currencies[Field.OUTPUT],
typedValue typedValue
...@@ -102,7 +107,7 @@ export default function Swap() { ...@@ -102,7 +107,7 @@ export default function Swap() {
} }
const { onSwitchTokens, onCurrencySelection, onUserInput, onChangeRecipient } = useSwapActionHandlers() const { onSwitchTokens, onCurrencySelection, onUserInput, onChangeRecipient } = useSwapActionHandlers()
const isValid = !error const isValid = !swapInputError
const dependentField: Field = independentField === Field.INPUT ? Field.OUTPUT : Field.INPUT const dependentField: Field = independentField === Field.INPUT ? Field.OUTPUT : Field.INPUT
const handleTypeInput = useCallback( const handleTypeInput = useCallback(
...@@ -119,9 +124,19 @@ export default function Swap() { ...@@ -119,9 +124,19 @@ export default function Swap() {
) )
// modal and loading // modal and loading
const [showConfirm, setShowConfirm] = useState<boolean>(false) // show confirmation modal const [{ showConfirm, tradeToConfirm, swapErrorMessage, attemptingTxn, txHash }, setSwapState] = useState<{
const [attemptingTxn, setAttemptingTxn] = useState<boolean>(false) // waiting for user confirmaion/rejection showConfirm: boolean
const [txHash, setTxHash] = useState<string>('') tradeToConfirm: Trade | undefined
attemptingTxn: boolean
swapErrorMessage: string | undefined
txHash: string | undefined
}>({
showConfirm: false,
tradeToConfirm: undefined,
attemptingTxn: false,
swapErrorMessage: undefined,
txHash: undefined
})
const formattedAmounts = { const formattedAmounts = {
[independentField]: typedValue, [independentField]: typedValue,
...@@ -152,25 +167,27 @@ export default function Swap() { ...@@ -152,25 +167,27 @@ export default function Swap() {
const maxAmountInput: CurrencyAmount | undefined = maxAmountSpend(currencyBalances[Field.INPUT]) const maxAmountInput: CurrencyAmount | undefined = maxAmountSpend(currencyBalances[Field.INPUT])
const atMaxAmountInput = Boolean(maxAmountInput && parsedAmounts[Field.INPUT]?.equalTo(maxAmountInput)) const atMaxAmountInput = Boolean(maxAmountInput && parsedAmounts[Field.INPUT]?.equalTo(maxAmountInput))
const slippageAdjustedAmounts = computeSlippageAdjustedAmounts(trade, allowedSlippage)
// the callback to execute the swap // the callback to execute the swap
const swapCallback = useSwapCallback(trade, allowedSlippage, deadline, recipient) const { callback: swapCallback, error: swapCallbackError } = useSwapCallback(
trade,
allowedSlippage,
deadline,
recipient
)
const { priceImpactWithoutFee, realizedLPFee } = computeTradePriceBreakdown(trade) const { priceImpactWithoutFee } = computeTradePriceBreakdown(trade)
function onSwap() { const handleSwap = useCallback(() => {
if (priceImpactWithoutFee && !confirmPriceImpactWithoutFee(priceImpactWithoutFee)) { if (priceImpactWithoutFee && !confirmPriceImpactWithoutFee(priceImpactWithoutFee)) {
return return
} }
if (!swapCallback) { if (!swapCallback) {
return return
} }
setAttemptingTxn(true) setSwapState({ attemptingTxn: true, tradeToConfirm, showConfirm, swapErrorMessage: undefined, txHash: undefined })
swapCallback() swapCallback()
.then(hash => { .then(hash => {
setAttemptingTxn(false) setSwapState({ attemptingTxn: false, tradeToConfirm, showConfirm, swapErrorMessage: undefined, txHash: hash })
setTxHash(hash)
ReactGA.event({ ReactGA.event({
category: 'Swap', category: 'Swap',
...@@ -188,13 +205,15 @@ export default function Swap() { ...@@ -188,13 +205,15 @@ export default function Swap() {
}) })
}) })
.catch(error => { .catch(error => {
setAttemptingTxn(false) setSwapState({
// we only care if the error is something _other_ than the user rejected the tx attemptingTxn: false,
if (error?.code !== 4001) { tradeToConfirm,
console.error(error) showConfirm,
} swapErrorMessage: error.message,
txHash: undefined
}) })
} })
}, [tradeToConfirm, account, priceImpactWithoutFee, recipient, recipientAddress, showConfirm, swapCallback, trade])
// errors // errors
const [showInverted, setShowInverted] = useState<boolean>(false) const [showInverted, setShowInverted] = useState<boolean>(false)
...@@ -205,74 +224,47 @@ export default function Swap() { ...@@ -205,74 +224,47 @@ export default function Swap() {
// show approve flow when: no error on inputs, not approved or pending, or approved in current session // show approve flow when: no error on inputs, not approved or pending, or approved in current session
// never show if price impact is above threshold in non expert mode // never show if price impact is above threshold in non expert mode
const showApproveFlow = const showApproveFlow =
!error && !swapInputError &&
(approval === ApprovalState.NOT_APPROVED || (approval === ApprovalState.NOT_APPROVED ||
approval === ApprovalState.PENDING || approval === ApprovalState.PENDING ||
(approvalSubmitted && approval === ApprovalState.APPROVED)) && (approvalSubmitted && approval === ApprovalState.APPROVED)) &&
!(priceImpactSeverity > 3 && !expertMode) !(priceImpactSeverity > 3 && !isExpertMode)
function modalHeader() {
return (
<SwapModalHeader
currencies={currencies}
formattedAmounts={formattedAmounts}
slippageAdjustedAmounts={slippageAdjustedAmounts}
priceImpactSeverity={priceImpactSeverity}
independentField={independentField}
recipient={recipient}
/>
)
}
function modalBottom() {
return (
<SwapModalFooter
confirmText={priceImpactSeverity > 2 ? 'Swap Anyway' : 'Confirm Swap'}
showInverted={showInverted}
severity={priceImpactSeverity}
setShowInverted={setShowInverted}
onSwap={onSwap}
realizedLPFee={realizedLPFee}
parsedAmounts={parsedAmounts}
priceImpactWithoutFee={priceImpactWithoutFee}
slippageAdjustedAmounts={slippageAdjustedAmounts}
trade={trade}
/>
)
}
// text to show while loading
const pendingText = `Swapping ${parsedAmounts[Field.INPUT]?.toSignificant(6)} ${
currencies[Field.INPUT]?.symbol
} for ${parsedAmounts[Field.OUTPUT]?.toSignificant(6)} ${currencies[Field.OUTPUT]?.symbol}`
const [dismissedToken0] = useTokenWarningDismissal(chainId, currencies[Field.INPUT]) const [dismissedToken0] = useTokenWarningDismissal(chainId, currencies[Field.INPUT])
const [dismissedToken1] = useTokenWarningDismissal(chainId, currencies[Field.OUTPUT]) const [dismissedToken1] = useTokenWarningDismissal(chainId, currencies[Field.OUTPUT])
const showWarning = const showWarning =
(!dismissedToken0 && !!currencies[Field.INPUT]) || (!dismissedToken1 && !!currencies[Field.OUTPUT]) (!dismissedToken0 && !!currencies[Field.INPUT]) || (!dismissedToken1 && !!currencies[Field.OUTPUT])
const handleConfirmDismiss = useCallback(() => {
setSwapState({ showConfirm: false, tradeToConfirm, attemptingTxn, swapErrorMessage, txHash })
// if there was a tx hash, we want to clear the input
if (txHash) {
onUserInput(Field.INPUT, '')
}
}, [attemptingTxn, onUserInput, swapErrorMessage, tradeToConfirm, txHash])
const handleAcceptChanges = useCallback(() => {
setSwapState({ tradeToConfirm: trade, swapErrorMessage, txHash, attemptingTxn, showConfirm })
}, [attemptingTxn, showConfirm, swapErrorMessage, trade, txHash])
return ( return (
<> <>
{showWarning && <TokenWarningCards currencies={currencies} />} {showWarning && <TokenWarningCards currencies={currencies} />}
<AppBody disabled={!!showWarning}> <AppBody disabled={showWarning}>
<SwapPoolTabs active={'swap'} /> <SwapPoolTabs active={'swap'} />
<Wrapper id="swap-page"> <Wrapper id="swap-page">
<ConfirmationModal <ConfirmSwapModal
isOpen={showConfirm} isOpen={showConfirm}
title="Confirm Swap" trade={trade}
onDismiss={() => { originalTrade={tradeToConfirm}
setShowConfirm(false) onAcceptChanges={handleAcceptChanges}
// if there was a tx hash, we want to clear the input
if (txHash) {
onUserInput(Field.INPUT, '')
}
setTxHash('')
}}
attemptingTxn={attemptingTxn} attemptingTxn={attemptingTxn}
hash={txHash} txHash={txHash}
topContent={modalHeader} recipient={recipient}
bottomContent={modalBottom} allowedSlippage={allowedSlippage}
pendingText={pendingText} onConfirm={handleSwap}
swapErrorMessage={swapErrorMessage}
onDismiss={handleConfirmDismiss}
/> />
<AutoColumn gap={'md'}> <AutoColumn gap={'md'}>
...@@ -373,8 +365,9 @@ export default function Swap() { ...@@ -373,8 +365,9 @@ export default function Swap() {
{!account ? ( {!account ? (
<ButtonLight onClick={toggleWalletModal}>Connect Wallet</ButtonLight> <ButtonLight onClick={toggleWalletModal}>Connect Wallet</ButtonLight>
) : showWrap ? ( ) : showWrap ? (
<ButtonPrimary disabled={Boolean(wrapError)} onClick={onWrap}> <ButtonPrimary disabled={Boolean(wrapInputError)} onClick={onWrap}>
{wrapError ?? (wrapType === WrapType.WRAP ? 'Wrap' : wrapType === WrapType.UNWRAP ? 'Unwrap' : null)} {wrapInputError ??
(wrapType === WrapType.WRAP ? 'Wrap' : wrapType === WrapType.UNWRAP ? 'Unwrap' : null)}
</ButtonPrimary> </ButtonPrimary>
) : noRoute && userHasSpecifiedInputOutput ? ( ) : noRoute && userHasSpecifiedInputOutput ? (
<GreyCard style={{ textAlign: 'center' }}> <GreyCard style={{ textAlign: 'center' }}>
...@@ -398,15 +391,27 @@ export default function Swap() { ...@@ -398,15 +391,27 @@ export default function Swap() {
</ButtonPrimary> </ButtonPrimary>
<ButtonError <ButtonError
onClick={() => { onClick={() => {
expertMode ? onSwap() : setShowConfirm(true) if (isExpertMode) {
handleSwap()
} else {
setSwapState({
tradeToConfirm: trade,
attemptingTxn: false,
swapErrorMessage: undefined,
showConfirm: true,
txHash: undefined
})
}
}} }}
width="48%" width="48%"
id="swap-button" id="swap-button"
disabled={!isValid || approval !== ApprovalState.APPROVED || (priceImpactSeverity > 3 && !expertMode)} disabled={
!isValid || approval !== ApprovalState.APPROVED || (priceImpactSeverity > 3 && !isExpertMode)
}
error={isValid && priceImpactSeverity > 2} error={isValid && priceImpactSeverity > 2}
> >
<Text fontSize={16} fontWeight={500}> <Text fontSize={16} fontWeight={500}>
{priceImpactSeverity > 3 && !expertMode {priceImpactSeverity > 3 && !isExpertMode
? `Price Impact High` ? `Price Impact High`
: `Swap${priceImpactSeverity > 2 ? ' Anyway' : ''}`} : `Swap${priceImpactSeverity > 2 ? ' Anyway' : ''}`}
</Text> </Text>
...@@ -415,21 +420,32 @@ export default function Swap() { ...@@ -415,21 +420,32 @@ export default function Swap() {
) : ( ) : (
<ButtonError <ButtonError
onClick={() => { onClick={() => {
expertMode ? onSwap() : setShowConfirm(true) if (isExpertMode) {
handleSwap()
} else {
setSwapState({
tradeToConfirm: trade,
attemptingTxn: false,
swapErrorMessage: undefined,
showConfirm: true,
txHash: undefined
})
}
}} }}
id="swap-button" id="swap-button"
disabled={!isValid || (priceImpactSeverity > 3 && !expertMode)} disabled={!isValid || (priceImpactSeverity > 3 && !isExpertMode) || !!swapCallbackError}
error={isValid && priceImpactSeverity > 2} error={isValid && priceImpactSeverity > 2 && !swapCallbackError}
> >
<Text fontSize={20} fontWeight={500}> <Text fontSize={20} fontWeight={500}>
{error {swapInputError
? error ? swapInputError
: priceImpactSeverity > 3 && !expertMode : priceImpactSeverity > 3 && !isExpertMode
? `Price Impact Too High` ? `Price Impact Too High`
: `Swap${priceImpactSeverity > 2 ? ' Anyway' : ''}`} : `Swap${priceImpactSeverity > 2 ? ' Anyway' : ''}`}
</Text> </Text>
</ButtonError> </ButtonError>
)} )}
{isExpertMode && swapErrorMessage ? <SwapCallbackError error={swapErrorMessage} /> : null}
{betterTradeLinkVersion && <BetterTradeLink version={betterTradeLinkVersion} />} {betterTradeLinkVersion && <BetterTradeLink version={betterTradeLinkVersion} />}
</BottomGrouping> </BottomGrouping>
</Wrapper> </Wrapper>
......
...@@ -50,9 +50,10 @@ export function useDerivedMintInfo( ...@@ -50,9 +50,10 @@ export function useDerivedMintInfo(
// pair // pair
const [pairState, pair] = usePair(currencies[Field.CURRENCY_A], currencies[Field.CURRENCY_B]) const [pairState, pair] = usePair(currencies[Field.CURRENCY_A], currencies[Field.CURRENCY_B])
const totalSupply = useTotalSupply(pair?.liquidityToken)
const noLiquidity: boolean = const noLiquidity: boolean =
pairState === PairState.NOT_EXISTS || pairState === PairState.NOT_EXISTS || Boolean(totalSupply && JSBI.equal(totalSupply.raw, ZERO))
Boolean(pair && JSBI.equal(pair.reserve0.raw, ZERO) && JSBI.equal(pair.reserve1.raw, ZERO))
// balances // balances
const balances = useCurrencyBalances(account ?? undefined, [ const balances = useCurrencyBalances(account ?? undefined, [
...@@ -94,16 +95,20 @@ export function useDerivedMintInfo( ...@@ -94,16 +95,20 @@ export function useDerivedMintInfo(
[Field.CURRENCY_B]: independentField === Field.CURRENCY_A ? dependentAmount : independentAmount [Field.CURRENCY_B]: independentField === Field.CURRENCY_A ? dependentAmount : independentAmount
} }
const token0Price = pair?.token0Price
const price = useMemo(() => { const price = useMemo(() => {
if (noLiquidity) {
const { [Field.CURRENCY_A]: currencyAAmount, [Field.CURRENCY_B]: currencyBAmount } = parsedAmounts const { [Field.CURRENCY_A]: currencyAAmount, [Field.CURRENCY_B]: currencyBAmount } = parsedAmounts
if (currencyAAmount && currencyBAmount) { if (currencyAAmount && currencyBAmount) {
return new Price(currencyAAmount.currency, currencyBAmount.currency, currencyAAmount.raw, currencyBAmount.raw) return new Price(currencyAAmount.currency, currencyBAmount.currency, currencyAAmount.raw, currencyBAmount.raw)
} }
return return
}, [parsedAmounts]) } else {
return token0Price
}
}, [noLiquidity, token0Price, parsedAmounts])
// liquidity minted // liquidity minted
const totalSupply = useTotalSupply(pair?.liquidityToken)
const liquidityMinted = useMemo(() => { const liquidityMinted = useMemo(() => {
const { [Field.CURRENCY_A]: currencyAAmount, [Field.CURRENCY_B]: currencyBAmount } = parsedAmounts const { [Field.CURRENCY_A]: currencyAAmount, [Field.CURRENCY_B]: currencyBAmount } = parsedAmounts
const [tokenAmountA, tokenAmountB] = [ const [tokenAmountA, tokenAmountB] = [
......
import { Contract } from '@ethersproject/contracts' import { Contract } from '@ethersproject/contracts'
import { useEffect, useMemo } from 'react' import { useEffect, useMemo, useRef } from 'react'
import { useDispatch, useSelector } from 'react-redux' import { useDispatch, useSelector } from 'react-redux'
import { useActiveWeb3React } from '../../hooks' import { useActiveWeb3React } from '../../hooks'
import { useMulticallContract } from '../../hooks/useContract' import { useMulticallContract } from '../../hooks/useContract'
import useDebounce from '../../hooks/useDebounce' import useDebounce from '../../hooks/useDebounce'
import chunkArray from '../../utils/chunkArray' import chunkArray from '../../utils/chunkArray'
import { retry } from '../../utils/retry' import { CancelledError, retry, RetryableError } from '../../utils/retry'
import { useBlockNumber } from '../application/hooks' import { useBlockNumber } from '../application/hooks'
import { AppDispatch, AppState } from '../index' import { AppDispatch, AppState } from '../index'
import { import {
...@@ -30,11 +30,17 @@ async function fetchChunk( ...@@ -30,11 +30,17 @@ async function fetchChunk(
chunk: Call[], chunk: Call[],
minBlockNumber: number minBlockNumber: number
): Promise<{ results: string[]; blockNumber: number }> { ): Promise<{ results: string[]; blockNumber: number }> {
const [resultsBlockNumber, returnData] = await multicallContract.aggregate( console.debug('Fetching chunk', multicallContract, chunk, minBlockNumber)
chunk.map(obj => [obj.address, obj.callData]) let resultsBlockNumber, returnData
) try {
;[resultsBlockNumber, returnData] = await multicallContract.aggregate(chunk.map(obj => [obj.address, obj.callData]))
} catch (error) {
console.debug('Failed to fetch chunk inside retry', error)
throw error
}
if (resultsBlockNumber.toNumber() < minBlockNumber) { if (resultsBlockNumber.toNumber() < minBlockNumber) {
throw new Error('Fetched for old block number') console.debug(`Fetched results for old block number: ${resultsBlockNumber.toString()} vs. ${minBlockNumber}`)
throw new RetryableError('Fetched for old block number')
} }
return { results: returnData, blockNumber: resultsBlockNumber.toNumber() } return { results: returnData, blockNumber: resultsBlockNumber.toNumber() }
} }
...@@ -112,6 +118,7 @@ export default function Updater() { ...@@ -112,6 +118,7 @@ export default function Updater() {
const latestBlockNumber = useBlockNumber() const latestBlockNumber = useBlockNumber()
const { chainId } = useActiveWeb3React() const { chainId } = useActiveWeb3React()
const multicallContract = useMulticallContract() const multicallContract = useMulticallContract()
const cancellations = useRef<{ blockNumber: number; cancellations: (() => void)[] }>()
const listeningKeys: { [callKey: string]: number } = useMemo(() => { const listeningKeys: { [callKey: string]: number } = useMemo(() => {
return activeListeningKeys(debouncedListeners, chainId) return activeListeningKeys(debouncedListeners, chainId)
...@@ -134,6 +141,10 @@ export default function Updater() { ...@@ -134,6 +141,10 @@ export default function Updater() {
const chunkedCalls = chunkArray(calls, CALL_CHUNK_SIZE) const chunkedCalls = chunkArray(calls, CALL_CHUNK_SIZE)
if (cancellations.current?.blockNumber !== latestBlockNumber) {
cancellations.current?.cancellations?.forEach(c => c())
}
dispatch( dispatch(
fetchingMulticallResults({ fetchingMulticallResults({
calls, calls,
...@@ -142,10 +153,18 @@ export default function Updater() { ...@@ -142,10 +153,18 @@ export default function Updater() {
}) })
) )
chunkedCalls.forEach((chunk, index) => cancellations.current = {
// todo: cancel retries when the block number updates blockNumber: latestBlockNumber,
retry(() => fetchChunk(multicallContract, chunk, latestBlockNumber), { n: 10, minWait: 2500, maxWait: 5000 }) cancellations: chunkedCalls.map((chunk, index) => {
const { cancel, promise } = retry(() => fetchChunk(multicallContract, chunk, latestBlockNumber), {
n: Infinity,
minWait: 2500,
maxWait: 3500
})
promise
.then(({ results: returnData, blockNumber: fetchBlockNumber }) => { .then(({ results: returnData, blockNumber: fetchBlockNumber }) => {
cancellations.current = { cancellations: [], blockNumber: latestBlockNumber }
// accumulates the length of all previous indices // accumulates the length of all previous indices
const firstCallKeyIndex = chunkedCalls.slice(0, index).reduce<number>((memo, curr) => memo + curr.length, 0) const firstCallKeyIndex = chunkedCalls.slice(0, index).reduce<number>((memo, curr) => memo + curr.length, 0)
const lastCallKeyIndex = firstCallKeyIndex + returnData.length const lastCallKeyIndex = firstCallKeyIndex + returnData.length
...@@ -164,6 +183,10 @@ export default function Updater() { ...@@ -164,6 +183,10 @@ export default function Updater() {
) )
}) })
.catch((error: any) => { .catch((error: any) => {
if (error instanceof CancelledError) {
console.debug('Cancelled fetch for blockNumber', latestBlockNumber)
return
}
console.error('Failed to fetch multicall chunk', chunk, chainId, error) console.error('Failed to fetch multicall chunk', chunk, chainId, error)
dispatch( dispatch(
errorFetchingMulticallResults({ errorFetchingMulticallResults({
...@@ -173,7 +196,9 @@ export default function Updater() { ...@@ -173,7 +196,9 @@ export default function Updater() {
}) })
) )
}) })
) return cancel
})
}
}, [chainId, multicallContract, dispatch, serializedOutdatedCallKeys, latestBlockNumber]) }, [chainId, multicallContract, dispatch, serializedOutdatedCallKeys, latestBlockNumber])
return null return null
......
...@@ -94,7 +94,7 @@ export function useDerivedSwapInfo(): { ...@@ -94,7 +94,7 @@ export function useDerivedSwapInfo(): {
currencyBalances: { [field in Field]?: CurrencyAmount } currencyBalances: { [field in Field]?: CurrencyAmount }
parsedAmount: CurrencyAmount | undefined parsedAmount: CurrencyAmount | undefined
v2Trade: Trade | undefined v2Trade: Trade | undefined
error?: string inputError?: string
v1Trade: Trade | undefined v1Trade: Trade | undefined
} { } {
const { account } = useActiveWeb3React() const { account } = useActiveWeb3React()
...@@ -140,21 +140,21 @@ export function useDerivedSwapInfo(): { ...@@ -140,21 +140,21 @@ export function useDerivedSwapInfo(): {
// get link to trade on v1, if a better rate exists // get link to trade on v1, if a better rate exists
const v1Trade = useV1Trade(isExactIn, currencies[Field.INPUT], currencies[Field.OUTPUT], parsedAmount) const v1Trade = useV1Trade(isExactIn, currencies[Field.INPUT], currencies[Field.OUTPUT], parsedAmount)
let error: string | undefined let inputError: string | undefined
if (!account) { if (!account) {
error = 'Connect Wallet' inputError = 'Connect Wallet'
} }
if (!parsedAmount) { if (!parsedAmount) {
error = error ?? 'Enter an amount' inputError = inputError ?? 'Enter an amount'
} }
if (!currencies[Field.INPUT] || !currencies[Field.OUTPUT]) { if (!currencies[Field.INPUT] || !currencies[Field.OUTPUT]) {
error = error ?? 'Select a token' inputError = inputError ?? 'Select a token'
} }
if (!to) { if (!to) {
error = error ?? 'Enter a recipient' inputError = inputError ?? 'Enter a recipient'
} }
const [allowedSlippage] = useUserSlippageTolerance() const [allowedSlippage] = useUserSlippageTolerance()
...@@ -177,7 +177,7 @@ export function useDerivedSwapInfo(): { ...@@ -177,7 +177,7 @@ export function useDerivedSwapInfo(): {
] ]
if (balanceIn && amountIn && balanceIn.lessThan(amountIn)) { if (balanceIn && amountIn && balanceIn.lessThan(amountIn)) {
error = 'Insufficient ' + amountIn.currency.symbol + ' balance' inputError = 'Insufficient ' + amountIn.currency.symbol + ' balance'
} }
return { return {
...@@ -185,7 +185,7 @@ export function useDerivedSwapInfo(): { ...@@ -185,7 +185,7 @@ export function useDerivedSwapInfo(): {
currencyBalances, currencyBalances,
parsedAmount, parsedAmount,
v2Trade: v2Trade ?? undefined, v2Trade: v2Trade ?? undefined,
error, inputError,
v1Trade v1Trade
} }
} }
......
...@@ -55,7 +55,7 @@ export function colors(darkMode: boolean): Colors { ...@@ -55,7 +55,7 @@ export function colors(darkMode: boolean): Colors {
bg5: darkMode ? '#565A69' : '#888D9B', bg5: darkMode ? '#565A69' : '#888D9B',
//specialty colors //specialty colors
modalBG: darkMode ? 'rgba(0,0,0,0.85)' : 'rgba(0,0,0,0.6)', modalBG: darkMode ? 'rgba(0,0,0,42.5)' : 'rgba(0,0,0,0.3)',
advancedBG: darkMode ? 'rgba(0,0,0,0.1)' : 'rgba(255,255,255,0.6)', advancedBG: darkMode ? 'rgba(0,0,0,0.1)' : 'rgba(255,255,255,0.6)',
//primary colors //primary colors
......
...@@ -91,7 +91,7 @@ export function getContract(address: string, ABI: any, library: Web3Provider, ac ...@@ -91,7 +91,7 @@ export function getContract(address: string, ABI: any, library: Web3Provider, ac
} }
// account is optional // account is optional
export function getRouterContract(_: number, library: Web3Provider, account?: string) { export function getRouterContract(_: number, library: Web3Provider, account?: string): Contract {
return getContract(ROUTER_ADDRESS, IUniswapV2Router02ABI, library, account) return getContract(ROUTER_ADDRESS, IUniswapV2Router02ABI, library, account)
} }
......
/**
* Returns true if the string value is zero in hex
* @param hexNumberString
*/
export default function isZero(hexNumberString: string) {
return /^0x0*$/.test(hexNumberString)
}
import { retry } from './retry' import { retry, RetryableError } from './retry'
describe('retry', () => { describe('retry', () => {
function makeFn<T>(fails: number, result: T): () => Promise<T> { function makeFn<T>(fails: number, result: T, retryable = true): () => Promise<T> {
return async () => { return async () => {
if (fails > 0) { if (fails > 0) {
fails-- fails--
throw new Error('failure') throw retryable ? new RetryableError('failure') : new Error('bad failure')
} }
return result return result
} }
} }
it('fails for non-retryable error', async () => {
await expect(retry(makeFn(1, 'abc', false), { n: 3, maxWait: 0, minWait: 0 }).promise).rejects.toThrow(
'bad failure'
)
})
it('works after one fail', async () => { it('works after one fail', async () => {
await expect(retry(makeFn(1, 'abc'), { n: 3, maxWait: 0, minWait: 0 })).resolves.toEqual('abc') await expect(retry(makeFn(1, 'abc'), { n: 3, maxWait: 0, minWait: 0 }).promise).resolves.toEqual('abc')
}) })
it('works after two fails', async () => { it('works after two fails', async () => {
await expect(retry(makeFn(2, 'abc'), { n: 3, maxWait: 0, minWait: 0 })).resolves.toEqual('abc') await expect(retry(makeFn(2, 'abc'), { n: 3, maxWait: 0, minWait: 0 }).promise).resolves.toEqual('abc')
}) })
it('throws if too many fails', async () => { it('throws if too many fails', async () => {
await expect(retry(makeFn(4, 'abc'), { n: 3, maxWait: 0, minWait: 0 })).rejects.toThrow('failure') await expect(retry(makeFn(4, 'abc'), { n: 3, maxWait: 0, minWait: 0 }).promise).rejects.toThrow('failure')
})
it('cancel causes promise to reject', async () => {
const { promise, cancel } = retry(makeFn(2, 'abc'), { n: 3, minWait: 100, maxWait: 100 })
cancel()
await expect(promise).rejects.toThrow('Cancelled')
})
it('cancel no-op after complete', async () => {
const { promise, cancel } = retry(makeFn(0, 'abc'), { n: 3, minWait: 100, maxWait: 100 })
// defer
setTimeout(cancel, 0)
await expect(promise).resolves.toEqual('abc')
}) })
async function checkTime(fn: () => Promise<any>, min: number, max: number) { async function checkTime(fn: () => Promise<any>, min: number, max: number) {
...@@ -36,7 +55,7 @@ describe('retry', () => { ...@@ -36,7 +55,7 @@ describe('retry', () => {
for (let i = 0; i < 10; i++) { for (let i = 0; i < 10; i++) {
promises.push( promises.push(
checkTime( checkTime(
() => expect(retry(makeFn(4, 'abc'), { n: 3, maxWait: 100, minWait: 50 })).rejects.toThrow('failure'), () => expect(retry(makeFn(4, 'abc'), { n: 3, maxWait: 100, minWait: 50 }).promise).rejects.toThrow('failure'),
150, 150,
305 305
) )
......
...@@ -6,6 +6,20 @@ function waitRandom(min: number, max: number): Promise<void> { ...@@ -6,6 +6,20 @@ function waitRandom(min: number, max: number): Promise<void> {
return wait(min + Math.round(Math.random() * Math.max(0, max - min))) return wait(min + Math.round(Math.random() * Math.max(0, max - min)))
} }
/**
* This error is thrown if the function is cancelled before completing
*/
export class CancelledError extends Error {
constructor() {
super('Cancelled')
}
}
/**
* Throw this error if the function should retry
*/
export class RetryableError extends Error {}
/** /**
* Retries the function that returns the promise until the promise successfully resolves up to n retries * Retries the function that returns the promise until the promise successfully resolves up to n retries
* @param fn function to retry * @param fn function to retry
...@@ -13,13 +27,43 @@ function waitRandom(min: number, max: number): Promise<void> { ...@@ -13,13 +27,43 @@ function waitRandom(min: number, max: number): Promise<void> {
* @param minWait min wait between retries in ms * @param minWait min wait between retries in ms
* @param maxWait max wait between retries in ms * @param maxWait max wait between retries in ms
*/ */
// todo: support cancelling the retry
export function retry<T>( export function retry<T>(
fn: () => Promise<T>, fn: () => Promise<T>,
{ n = 3, minWait = 500, maxWait = 1000 }: { n?: number; minWait?: number; maxWait?: number } = {} { n, minWait, maxWait }: { n: number; minWait: number; maxWait: number }
): Promise<T> { ): { promise: Promise<T>; cancel: () => void } {
return fn().catch(error => { let completed = false
if (n === 0) throw error let rejectCancelled: (error: Error) => void
return waitRandom(minWait, maxWait).then(() => retry(fn, { n: n - 1, minWait, maxWait })) const promise = new Promise<T>(async (resolve, reject) => {
rejectCancelled = reject
while (true) {
let result: T
try {
result = await fn()
if (!completed) {
resolve(result)
completed = true
}
break
} catch (error) {
if (completed) {
break
}
if (n <= 0 || !(error instanceof RetryableError)) {
reject(error)
completed = true
break
}
n--
}
await waitRandom(minWait, maxWait)
}
}) })
return {
promise,
cancel: () => {
if (completed) return
completed = true
rejectCancelled(new CancelledError())
}
}
} }
...@@ -1296,7 +1296,7 @@ ...@@ -1296,7 +1296,7 @@
bech32 "^1.1.3" bech32 "^1.1.3"
crypto-addr-codec "^0.1.7" crypto-addr-codec "^0.1.7"
"@ethersproject/abi@>=5.0.0-beta.137", "@ethersproject/abi@^5.0.0": "@ethersproject/abi@^5.0.0":
version "5.0.2" version "5.0.2"
resolved "https://registry.yarnpkg.com/@ethersproject/abi/-/abi-5.0.2.tgz#7fe8f080aa1483fe32cd27bb5b8f2019266af1e2" resolved "https://registry.yarnpkg.com/@ethersproject/abi/-/abi-5.0.2.tgz#7fe8f080aa1483fe32cd27bb5b8f2019266af1e2"
integrity sha512-Z+5f7xOgtRLu/W2l9Ry5xF7ehh9QVQ0m1vhynmTcS7DMfHgqTd1/PDFC62aw91ZPRCRZsYdZJu8ymokC5e1JSw== integrity sha512-Z+5f7xOgtRLu/W2l9Ry5xF7ehh9QVQ0m1vhynmTcS7DMfHgqTd1/PDFC62aw91ZPRCRZsYdZJu8ymokC5e1JSw==
...@@ -1311,7 +1311,7 @@ ...@@ -1311,7 +1311,7 @@
"@ethersproject/properties" "^5.0.0" "@ethersproject/properties" "^5.0.0"
"@ethersproject/strings" "^5.0.0" "@ethersproject/strings" "^5.0.0"
"@ethersproject/abstract-provider@>=5.0.0-beta.131", "@ethersproject/abstract-provider@>=5.0.0-beta.139", "@ethersproject/abstract-provider@^5.0.0": "@ethersproject/abstract-provider@^5.0.0":
version "5.0.2" version "5.0.2"
resolved "https://registry.yarnpkg.com/@ethersproject/abstract-provider/-/abstract-provider-5.0.2.tgz#9b4e8f4870f0691463e8d5b092c95dd5275c635d" resolved "https://registry.yarnpkg.com/@ethersproject/abstract-provider/-/abstract-provider-5.0.2.tgz#9b4e8f4870f0691463e8d5b092c95dd5275c635d"
integrity sha512-U1s60+nG02x8FKNMoVNI6MG8SguWCoG9HJtwOqWZ38LBRMsDV4c0w4izKx98kcsN3wXw4U2/YAyJ9LlH7+/hkg== integrity sha512-U1s60+nG02x8FKNMoVNI6MG8SguWCoG9HJtwOqWZ38LBRMsDV4c0w4izKx98kcsN3wXw4U2/YAyJ9LlH7+/hkg==
...@@ -1324,7 +1324,7 @@ ...@@ -1324,7 +1324,7 @@
"@ethersproject/transactions" "^5.0.0" "@ethersproject/transactions" "^5.0.0"
"@ethersproject/web" "^5.0.0" "@ethersproject/web" "^5.0.0"
"@ethersproject/abstract-signer@>=5.0.0-beta.132", "@ethersproject/abstract-signer@>=5.0.0-beta.142", "@ethersproject/abstract-signer@^5.0.0": "@ethersproject/abstract-signer@^5.0.0":
version "5.0.2" version "5.0.2"
resolved "https://registry.yarnpkg.com/@ethersproject/abstract-signer/-/abstract-signer-5.0.2.tgz#5776f888fda816de1d08ddb0e74778ecb9590f69" resolved "https://registry.yarnpkg.com/@ethersproject/abstract-signer/-/abstract-signer-5.0.2.tgz#5776f888fda816de1d08ddb0e74778ecb9590f69"
integrity sha512-CzzXbeqKlgayE4YTnvvreGBG3n+HxakGXrxaGM6LjBZnOOIVSYi6HMFG8ZXls7UspRY4hvMrtnKEJKDCOngSBw== integrity sha512-CzzXbeqKlgayE4YTnvvreGBG3n+HxakGXrxaGM6LjBZnOOIVSYi6HMFG8ZXls7UspRY4hvMrtnKEJKDCOngSBw==
...@@ -1335,19 +1335,7 @@ ...@@ -1335,19 +1335,7 @@
"@ethersproject/logger" "^5.0.0" "@ethersproject/logger" "^5.0.0"
"@ethersproject/properties" "^5.0.0" "@ethersproject/properties" "^5.0.0"
"@ethersproject/address@5.0.0-beta.134": "@ethersproject/address@^5.0.0":
version "5.0.0-beta.134"
resolved "https://registry.yarnpkg.com/@ethersproject/address/-/address-5.0.0-beta.134.tgz#9c1790c87b763dc547ac12e2dbc9fa78d0799a71"
integrity sha512-FHhUVJTUIg2pXvOOhIt8sB1cQbcwrzZKzf9CPV7JM1auli20nGoYhyMFYGK7u++GXzTMJduIkU1OwlIBupewDw==
dependencies:
"@ethersproject/bignumber" ">=5.0.0-beta.130"
"@ethersproject/bytes" ">=5.0.0-beta.129"
"@ethersproject/keccak256" ">=5.0.0-beta.127"
"@ethersproject/logger" ">=5.0.0-beta.129"
"@ethersproject/rlp" ">=5.0.0-beta.126"
bn.js "^4.4.0"
"@ethersproject/address@>=5.0.0-beta.128", "@ethersproject/address@>=5.0.0-beta.134", "@ethersproject/address@^5.0.0":
version "5.0.2" version "5.0.2"
resolved "https://registry.yarnpkg.com/@ethersproject/address/-/address-5.0.2.tgz#80d0ddfb7d4bd0d32657747fa4bdd2defef2e00a" resolved "https://registry.yarnpkg.com/@ethersproject/address/-/address-5.0.2.tgz#80d0ddfb7d4bd0d32657747fa4bdd2defef2e00a"
integrity sha512-+rz26RKj7ujGfQynys4V9VJRbR+wpC6eL8F22q3raWMH3152Ha31GwJPWzxE/bEA+43M/zTNVwY0R53gn53L2Q== integrity sha512-+rz26RKj7ujGfQynys4V9VJRbR+wpC6eL8F22q3raWMH3152Ha31GwJPWzxE/bEA+43M/zTNVwY0R53gn53L2Q==
...@@ -1374,17 +1362,7 @@ ...@@ -1374,17 +1362,7 @@
"@ethersproject/bytes" "^5.0.0" "@ethersproject/bytes" "^5.0.0"
"@ethersproject/properties" "^5.0.0" "@ethersproject/properties" "^5.0.0"
"@ethersproject/bignumber@5.0.0-beta.138": "@ethersproject/bignumber@^5.0.0":
version "5.0.0-beta.138"
resolved "https://registry.yarnpkg.com/@ethersproject/bignumber/-/bignumber-5.0.0-beta.138.tgz#a635f2f9a6f1b262cc38e1c7ee561fb13d79fda4"
integrity sha512-DTlOEJw6jAFz7/qkY8p4mPGGHVwgYUUC5rk1Pbg2/gR/gHPFDim+uBY+XGavh0QSWd1i3hXKafVPre92j4fs5g==
dependencies:
"@ethersproject/bytes" ">=5.0.0-beta.129"
"@ethersproject/logger" ">=5.0.0-beta.129"
"@ethersproject/properties" ">=5.0.0-beta.131"
bn.js "^4.4.0"
"@ethersproject/bignumber@>=5.0.0-beta.130", "@ethersproject/bignumber@>=5.0.0-beta.138", "@ethersproject/bignumber@^5.0.0":
version "5.0.5" version "5.0.5"
resolved "https://registry.yarnpkg.com/@ethersproject/bignumber/-/bignumber-5.0.5.tgz#31bd7e75aad46ace345fae69b1f5bb120906af1b" resolved "https://registry.yarnpkg.com/@ethersproject/bignumber/-/bignumber-5.0.5.tgz#31bd7e75aad46ace345fae69b1f5bb120906af1b"
integrity sha512-24ln7PV0g8ZzjcVZiLW9Wod0i+XCmK6zKkAaxw5enraTIT1p7gVOcSXFSzNQ9WYAwtiFQPvvA+TIO2oEITZNJA== integrity sha512-24ln7PV0g8ZzjcVZiLW9Wod0i+XCmK6zKkAaxw5enraTIT1p7gVOcSXFSzNQ9WYAwtiFQPvvA+TIO2oEITZNJA==
...@@ -1393,43 +1371,20 @@ ...@@ -1393,43 +1371,20 @@
"@ethersproject/logger" "^5.0.0" "@ethersproject/logger" "^5.0.0"
bn.js "^4.4.0" bn.js "^4.4.0"
"@ethersproject/bytes@>=5.0.0-beta.129", "@ethersproject/bytes@>=5.0.0-beta.137", "@ethersproject/bytes@^5.0.0": "@ethersproject/bytes@^5.0.0":
version "5.0.3" version "5.0.3"
resolved "https://registry.yarnpkg.com/@ethersproject/bytes/-/bytes-5.0.3.tgz#b3769963ae0188a35713d343890a903bda20af9c" resolved "https://registry.yarnpkg.com/@ethersproject/bytes/-/bytes-5.0.3.tgz#b3769963ae0188a35713d343890a903bda20af9c"
integrity sha512-AyPMAlY+Amaw4Zfp8OAivm1xYPI8mqiUYmEnSUk1CnS2NrQGHEMmFJFiOJdS3gDDpgSOFhWIjZwxKq2VZpqNTA== integrity sha512-AyPMAlY+Amaw4Zfp8OAivm1xYPI8mqiUYmEnSUk1CnS2NrQGHEMmFJFiOJdS3gDDpgSOFhWIjZwxKq2VZpqNTA==
dependencies: dependencies:
"@ethersproject/logger" "^5.0.0" "@ethersproject/logger" "^5.0.0"
"@ethersproject/constants@5.0.0-beta.133": "@ethersproject/constants@^5.0.0":
version "5.0.0-beta.133"
resolved "https://registry.yarnpkg.com/@ethersproject/constants/-/constants-5.0.0-beta.133.tgz#af4ccd7232f3ed73aebe066a695ede32c497a394"
integrity sha512-VCTpk3AF00mlWQw1vg+fI6qCo0qO5EVWK574t4HNBKW6X748jc9UJPryKUz9JgZ64ZQupyLM92wHilsG/YTpNQ==
dependencies:
"@ethersproject/bignumber" ">=5.0.0-beta.130"
"@ethersproject/constants@>=5.0.0-beta.128", "@ethersproject/constants@^5.0.0":
version "5.0.2" version "5.0.2"
resolved "https://registry.yarnpkg.com/@ethersproject/constants/-/constants-5.0.2.tgz#f7ac0b320e2bbec1a5950da075015f8bc4e8fed1" resolved "https://registry.yarnpkg.com/@ethersproject/constants/-/constants-5.0.2.tgz#f7ac0b320e2bbec1a5950da075015f8bc4e8fed1"
integrity sha512-nNoVlNP6bgpog7pQ2EyD1xjlaXcy1Cl4kK5v1KoskHj58EtB6TK8M8AFGi3GgHTdMldfT4eN3OsoQ/CdOTVNFA== integrity sha512-nNoVlNP6bgpog7pQ2EyD1xjlaXcy1Cl4kK5v1KoskHj58EtB6TK8M8AFGi3GgHTdMldfT4eN3OsoQ/CdOTVNFA==
dependencies: dependencies:
"@ethersproject/bignumber" "^5.0.0" "@ethersproject/bignumber" "^5.0.0"
"@ethersproject/contracts@5.0.0-beta.151":
version "5.0.0-beta.151"
resolved "https://registry.yarnpkg.com/@ethersproject/contracts/-/contracts-5.0.0-beta.151.tgz#4cee195c01b6865e8e7d8849777427864819e931"
integrity sha512-ELmsmZ/vE/rz5ydJNlU04aXsh7sw22tzmy7vM5JXCgMm5nEFhGoRF+dRIrUFCuUV2Mxe0bALN11qGkRqFKlXRQ==
dependencies:
"@ethersproject/abi" ">=5.0.0-beta.137"
"@ethersproject/abstract-provider" ">=5.0.0-beta.131"
"@ethersproject/abstract-signer" ">=5.0.0-beta.132"
"@ethersproject/address" ">=5.0.0-beta.128"
"@ethersproject/bignumber" ">=5.0.0-beta.130"
"@ethersproject/bytes" ">=5.0.0-beta.129"
"@ethersproject/constants" ">=5.0.0-beta.128"
"@ethersproject/logger" ">=5.0.0-beta.129"
"@ethersproject/properties" ">=5.0.0-beta.131"
"@ethersproject/transactions" ">=5.0.0-beta.128"
"@ethersproject/contracts@^5.0.0": "@ethersproject/contracts@^5.0.0":
version "5.0.2" version "5.0.2"
resolved "https://registry.yarnpkg.com/@ethersproject/contracts/-/contracts-5.0.2.tgz#f19ed8335ceeb6abb60f5d45641f0a2a62b6fbc5" resolved "https://registry.yarnpkg.com/@ethersproject/contracts/-/contracts-5.0.2.tgz#f19ed8335ceeb6abb60f5d45641f0a2a62b6fbc5"
...@@ -1445,17 +1400,17 @@ ...@@ -1445,17 +1400,17 @@
"@ethersproject/logger" "^5.0.0" "@ethersproject/logger" "^5.0.0"
"@ethersproject/properties" "^5.0.0" "@ethersproject/properties" "^5.0.0"
"@ethersproject/experimental@5.0.0-beta.141": "@ethersproject/experimental@^5.0.1":
version "5.0.0-beta.141" version "5.0.1"
resolved "https://registry.yarnpkg.com/@ethersproject/experimental/-/experimental-5.0.0-beta.141.tgz#2dc7e1f1c33f818cda1799b63b2ecb9e226f46bb" resolved "https://registry.yarnpkg.com/@ethersproject/experimental/-/experimental-5.0.1.tgz#c488d43092543c49e4cb70fbaeafad0956c826e0"
integrity sha512-SFUfN5c6Wcpq18ZZBQdpf6ie50aIkz3jco/8PPv5PFkRSIrGTP4HfobAu6A3eORd/tnvlgm1H2XWOLuRJ3WujA== integrity sha512-PAVv/i4PwO2L4E2PWgPgEGP9FOt/5qaTv7W9YDTSL7Tq2zfp41jolRBI1o7X0UdnPWUe54TiibOp4xJR65Dwpw==
dependencies: dependencies:
"@ensdomains/address-encoder" "^0.1.2" "@ensdomains/address-encoder" "^0.1.2"
"@ethersproject/web" ">=5.0.0-beta.138" "@ethersproject/web" "^5.0.0"
ethers ">=5.0.0-beta.186" ethers "^5.0.0"
scrypt-js "3.0.0" scrypt-js "3.0.1"
"@ethersproject/hash@>=5.0.0-beta.128", "@ethersproject/hash@>=5.0.0-beta.133", "@ethersproject/hash@^5.0.0": "@ethersproject/hash@^5.0.0":
version "5.0.2" version "5.0.2"
resolved "https://registry.yarnpkg.com/@ethersproject/hash/-/hash-5.0.2.tgz#6d69558786961836d530b8b4a8714eac5388aec7" resolved "https://registry.yarnpkg.com/@ethersproject/hash/-/hash-5.0.2.tgz#6d69558786961836d530b8b4a8714eac5388aec7"
integrity sha512-dWGvNwmVRX2bxoQQ3ciMw46Vzl1nqfL+5R8+2ZxsRXD3Cjgw1dL2mdjJF7xMMWPvPdrlhKXWSK0gb8VLwHZ8Cw== integrity sha512-dWGvNwmVRX2bxoQQ3ciMw46Vzl1nqfL+5R8+2ZxsRXD3Cjgw1dL2mdjJF7xMMWPvPdrlhKXWSK0gb8VLwHZ8Cw==
...@@ -1465,7 +1420,7 @@ ...@@ -1465,7 +1420,7 @@
"@ethersproject/logger" "^5.0.0" "@ethersproject/logger" "^5.0.0"
"@ethersproject/strings" "^5.0.0" "@ethersproject/strings" "^5.0.0"
"@ethersproject/hdnode@>=5.0.0-beta.139", "@ethersproject/hdnode@^5.0.0": "@ethersproject/hdnode@^5.0.0":
version "5.0.2" version "5.0.2"
resolved "https://registry.yarnpkg.com/@ethersproject/hdnode/-/hdnode-5.0.2.tgz#c4f2152590a64822d0c0feb90f09cc247af657e0" resolved "https://registry.yarnpkg.com/@ethersproject/hdnode/-/hdnode-5.0.2.tgz#c4f2152590a64822d0c0feb90f09cc247af657e0"
integrity sha512-QAUI5tfseTFqv00Vnbwzofqse81wN9TaL+x5GufTHIHJXgVdguxU+l39E3VYDCmO+eVAA6RCn5dJgeyra+PU2g== integrity sha512-QAUI5tfseTFqv00Vnbwzofqse81wN9TaL+x5GufTHIHJXgVdguxU+l39E3VYDCmO+eVAA6RCn5dJgeyra+PU2g==
...@@ -1483,7 +1438,7 @@ ...@@ -1483,7 +1438,7 @@
"@ethersproject/transactions" "^5.0.0" "@ethersproject/transactions" "^5.0.0"
"@ethersproject/wordlists" "^5.0.0" "@ethersproject/wordlists" "^5.0.0"
"@ethersproject/json-wallets@>=5.0.0-beta.138", "@ethersproject/json-wallets@^5.0.0": "@ethersproject/json-wallets@^5.0.0":
version "5.0.3" version "5.0.3"
resolved "https://registry.yarnpkg.com/@ethersproject/json-wallets/-/json-wallets-5.0.3.tgz#072021fe79f69c9ca1300f780abd9b9d0c8ea42e" resolved "https://registry.yarnpkg.com/@ethersproject/json-wallets/-/json-wallets-5.0.3.tgz#072021fe79f69c9ca1300f780abd9b9d0c8ea42e"
integrity sha512-VfDXn5ylugkfiM6SrvQfhX9oAHVU5dsNpRw8PjjTCn4k5E2JuVRO5A8sibkYXDhcBmRISZIWqclIxka6FI/chg== integrity sha512-VfDXn5ylugkfiM6SrvQfhX9oAHVU5dsNpRw8PjjTCn4k5E2JuVRO5A8sibkYXDhcBmRISZIWqclIxka6FI/chg==
...@@ -1502,7 +1457,7 @@ ...@@ -1502,7 +1457,7 @@
aes-js "3.0.0" aes-js "3.0.0"
scrypt-js "3.0.1" scrypt-js "3.0.1"
"@ethersproject/keccak256@>=5.0.0-beta.127", "@ethersproject/keccak256@>=5.0.0-beta.131", "@ethersproject/keccak256@^5.0.0", "@ethersproject/keccak256@^5.0.0-beta.130": "@ethersproject/keccak256@^5.0.0", "@ethersproject/keccak256@^5.0.0-beta.130":
version "5.0.2" version "5.0.2"
resolved "https://registry.yarnpkg.com/@ethersproject/keccak256/-/keccak256-5.0.2.tgz#7ed4a95bb45ee502cf4532223833740a83602797" resolved "https://registry.yarnpkg.com/@ethersproject/keccak256/-/keccak256-5.0.2.tgz#7ed4a95bb45ee502cf4532223833740a83602797"
integrity sha512-MbroXutc0gPNYIrUjS4Aw0lDuXabdzI7+l7elRWr1G6G+W0v00e/3gbikWkCReGtt2Jnt4lQSgnflhDwQGcIhA== integrity sha512-MbroXutc0gPNYIrUjS4Aw0lDuXabdzI7+l7elRWr1G6G+W0v00e/3gbikWkCReGtt2Jnt4lQSgnflhDwQGcIhA==
...@@ -1510,19 +1465,12 @@ ...@@ -1510,19 +1465,12 @@
"@ethersproject/bytes" "^5.0.0" "@ethersproject/bytes" "^5.0.0"
js-sha3 "0.5.7" js-sha3 "0.5.7"
"@ethersproject/logger@>=5.0.0-beta.129", "@ethersproject/logger@>=5.0.0-beta.137", "@ethersproject/logger@^5.0.0": "@ethersproject/logger@^5.0.0":
version "5.0.4" version "5.0.4"
resolved "https://registry.yarnpkg.com/@ethersproject/logger/-/logger-5.0.4.tgz#09fa4765b5691233e3afb6617cb38a700f9dd2e4" resolved "https://registry.yarnpkg.com/@ethersproject/logger/-/logger-5.0.4.tgz#09fa4765b5691233e3afb6617cb38a700f9dd2e4"
integrity sha512-alA2LiAy1LdQ/L1SA9ajUC7MvGAEQLsICEfKK4erX5qhkXE1LwLSPIzobtOWFsMHf2yrXGKBLnnpuVHprI3sAw== integrity sha512-alA2LiAy1LdQ/L1SA9ajUC7MvGAEQLsICEfKK4erX5qhkXE1LwLSPIzobtOWFsMHf2yrXGKBLnnpuVHprI3sAw==
"@ethersproject/networks@5.0.0-beta.136": "@ethersproject/networks@^5.0.0":
version "5.0.0-beta.136"
resolved "https://registry.yarnpkg.com/@ethersproject/networks/-/networks-5.0.0-beta.136.tgz#8d6fdae297c0ce7ebe1893e601c4a57f7e38dc7a"
integrity sha512-skMDix0LVOhpfCItbg6Z1fXLK6vAtUkzAKaslDxVczEPUvjQ0kiJ5ceurmL+ROOO1owURGxUac5BrIarbO7Zgw==
dependencies:
"@ethersproject/logger" ">=5.0.0-beta.129"
"@ethersproject/networks@>=5.0.0-beta.129", "@ethersproject/networks@^5.0.0":
version "5.0.2" version "5.0.2"
resolved "https://registry.yarnpkg.com/@ethersproject/networks/-/networks-5.0.2.tgz#a49e82cf071e3618e87e3c5d69fdbcf54dc6766c" resolved "https://registry.yarnpkg.com/@ethersproject/networks/-/networks-5.0.2.tgz#a49e82cf071e3618e87e3c5d69fdbcf54dc6766c"
integrity sha512-T7HVd62D4izNU2tDHf6xUDo7k4JOGX4Lk7vDmVcDKrepSWwL2OmGWrqSlkRe2a1Dnz4+1VPE6fb6+KsmSRe82g== integrity sha512-T7HVd62D4izNU2tDHf6xUDo7k4JOGX4Lk7vDmVcDKrepSWwL2OmGWrqSlkRe2a1Dnz4+1VPE6fb6+KsmSRe82g==
...@@ -1537,35 +1485,13 @@ ...@@ -1537,35 +1485,13 @@
"@ethersproject/bytes" "^5.0.0" "@ethersproject/bytes" "^5.0.0"
"@ethersproject/sha2" "^5.0.0" "@ethersproject/sha2" "^5.0.0"
"@ethersproject/properties@>=5.0.0-beta.131", "@ethersproject/properties@>=5.0.0-beta.140", "@ethersproject/properties@^5.0.0": "@ethersproject/properties@^5.0.0":
version "5.0.2" version "5.0.2"
resolved "https://registry.yarnpkg.com/@ethersproject/properties/-/properties-5.0.2.tgz#2facb62d2f2d968c7b3d0befa5bcc884cc565d3b" resolved "https://registry.yarnpkg.com/@ethersproject/properties/-/properties-5.0.2.tgz#2facb62d2f2d968c7b3d0befa5bcc884cc565d3b"
integrity sha512-FxAisPGAOACQjMJzewl9OJG6lsGCPTm5vpUMtfeoxzAlAb2lv+kHzQPUh9h4jfAILzE8AR1jgXMzRmlhwyra1Q== integrity sha512-FxAisPGAOACQjMJzewl9OJG6lsGCPTm5vpUMtfeoxzAlAb2lv+kHzQPUh9h4jfAILzE8AR1jgXMzRmlhwyra1Q==
dependencies: dependencies:
"@ethersproject/logger" "^5.0.0" "@ethersproject/logger" "^5.0.0"
"@ethersproject/providers@5.0.0-beta.162":
version "5.0.0-beta.162"
resolved "https://registry.yarnpkg.com/@ethersproject/providers/-/providers-5.0.0-beta.162.tgz#cb4efbeea2c776d0ce97712e05ffaa3e0a8df215"
integrity sha512-mXT5pQLOmRkXP5pza6TuV9RitaI50b1O2r0og8VzUIHcjO9bq4yppVbWs0Zcxn4KQAiIrAd2xXbYE3q2KdfUYQ==
dependencies:
"@ethersproject/abstract-provider" ">=5.0.0-beta.131"
"@ethersproject/abstract-signer" ">=5.0.0-beta.132"
"@ethersproject/address" ">=5.0.0-beta.128"
"@ethersproject/bignumber" ">=5.0.0-beta.130"
"@ethersproject/bytes" ">=5.0.0-beta.129"
"@ethersproject/constants" ">=5.0.0-beta.128"
"@ethersproject/hash" ">=5.0.0-beta.128"
"@ethersproject/logger" ">=5.0.0-beta.129"
"@ethersproject/networks" ">=5.0.0-beta.129"
"@ethersproject/properties" ">=5.0.0-beta.131"
"@ethersproject/random" ">=5.0.0-beta.128"
"@ethersproject/rlp" ">=5.0.0-beta.126"
"@ethersproject/strings" ">=5.0.0-beta.130"
"@ethersproject/transactions" ">=5.0.0-beta.128"
"@ethersproject/web" ">=5.0.0-beta.129"
ws "7.2.3"
"@ethersproject/providers@^5.0.0": "@ethersproject/providers@^5.0.0":
version "5.0.5" version "5.0.5"
resolved "https://registry.yarnpkg.com/@ethersproject/providers/-/providers-5.0.5.tgz#fa28498ce9683d1d99f6cb11e1a7fe8d4886e0ce" resolved "https://registry.yarnpkg.com/@ethersproject/providers/-/providers-5.0.5.tgz#fa28498ce9683d1d99f6cb11e1a7fe8d4886e0ce"
...@@ -1588,7 +1514,7 @@ ...@@ -1588,7 +1514,7 @@
"@ethersproject/web" "^5.0.0" "@ethersproject/web" "^5.0.0"
ws "7.2.3" ws "7.2.3"
"@ethersproject/random@>=5.0.0-beta.128", "@ethersproject/random@>=5.0.0-beta.135", "@ethersproject/random@^5.0.0": "@ethersproject/random@^5.0.0":
version "5.0.2" version "5.0.2"
resolved "https://registry.yarnpkg.com/@ethersproject/random/-/random-5.0.2.tgz#bb58aca69a85e8de506686117f050d03dac69023" resolved "https://registry.yarnpkg.com/@ethersproject/random/-/random-5.0.2.tgz#bb58aca69a85e8de506686117f050d03dac69023"
integrity sha512-kLeS+6bwz37WR2zbe69gudyoGVoUzljQO0LhifnATsZ7rW0JZ9Zgt0h5aXY7tqFDo9TvdqeCwUFdp1t3T5Fkhg== integrity sha512-kLeS+6bwz37WR2zbe69gudyoGVoUzljQO0LhifnATsZ7rW0JZ9Zgt0h5aXY7tqFDo9TvdqeCwUFdp1t3T5Fkhg==
...@@ -1596,7 +1522,7 @@ ...@@ -1596,7 +1522,7 @@
"@ethersproject/bytes" "^5.0.0" "@ethersproject/bytes" "^5.0.0"
"@ethersproject/logger" "^5.0.0" "@ethersproject/logger" "^5.0.0"
"@ethersproject/rlp@>=5.0.0-beta.126", "@ethersproject/rlp@^5.0.0": "@ethersproject/rlp@^5.0.0":
version "5.0.2" version "5.0.2"
resolved "https://registry.yarnpkg.com/@ethersproject/rlp/-/rlp-5.0.2.tgz#d6b550a2ac5e484f15f0f63337e522004d2e78cd" resolved "https://registry.yarnpkg.com/@ethersproject/rlp/-/rlp-5.0.2.tgz#d6b550a2ac5e484f15f0f63337e522004d2e78cd"
integrity sha512-oE0M5jqQ67fi2SuMcrpoewOpEuoXaD8M9JeR9md1bXRMvDYgKXUtDHs22oevpEOdnO2DPIRabp6MVHa4aDuWmw== integrity sha512-oE0M5jqQ67fi2SuMcrpoewOpEuoXaD8M9JeR9md1bXRMvDYgKXUtDHs22oevpEOdnO2DPIRabp6MVHa4aDuWmw==
...@@ -1613,7 +1539,7 @@ ...@@ -1613,7 +1539,7 @@
"@ethersproject/logger" "^5.0.0" "@ethersproject/logger" "^5.0.0"
hash.js "1.1.3" hash.js "1.1.3"
"@ethersproject/signing-key@>=5.0.0-beta.135", "@ethersproject/signing-key@^5.0.0": "@ethersproject/signing-key@^5.0.0":
version "5.0.3" version "5.0.3"
resolved "https://registry.yarnpkg.com/@ethersproject/signing-key/-/signing-key-5.0.3.tgz#adb84360e147bfd336cb2fe114100120732dc10a" resolved "https://registry.yarnpkg.com/@ethersproject/signing-key/-/signing-key-5.0.3.tgz#adb84360e147bfd336cb2fe114100120732dc10a"
integrity sha512-5QPZaBRGCLzfVMbFb3LcVjNR0UbTXnwDHASnQYfbzwUOnFYHKxHsrcbl/5ONGoppgi8yXgOocKqlPCFycJJVWQ== integrity sha512-5QPZaBRGCLzfVMbFb3LcVjNR0UbTXnwDHASnQYfbzwUOnFYHKxHsrcbl/5ONGoppgi8yXgOocKqlPCFycJJVWQ==
...@@ -1623,7 +1549,7 @@ ...@@ -1623,7 +1549,7 @@
"@ethersproject/properties" "^5.0.0" "@ethersproject/properties" "^5.0.0"
elliptic "6.5.3" elliptic "6.5.3"
"@ethersproject/solidity@5.0.2", "@ethersproject/solidity@^5.0.0": "@ethersproject/solidity@^5.0.0":
version "5.0.2" version "5.0.2"
resolved "https://registry.yarnpkg.com/@ethersproject/solidity/-/solidity-5.0.2.tgz#431cee341ec51e022bd897b93fef04521f414756" resolved "https://registry.yarnpkg.com/@ethersproject/solidity/-/solidity-5.0.2.tgz#431cee341ec51e022bd897b93fef04521f414756"
integrity sha512-RygurUe1hPW1LDYAPXy4471AklGWNnxgFWc3YUE6H11gzkit26jr6AyZH4Yyjw38eBBL6j0AOfQzMWm+NhxZ9g== integrity sha512-RygurUe1hPW1LDYAPXy4471AklGWNnxgFWc3YUE6H11gzkit26jr6AyZH4Yyjw38eBBL6j0AOfQzMWm+NhxZ9g==
...@@ -1634,16 +1560,7 @@ ...@@ -1634,16 +1560,7 @@
"@ethersproject/sha2" "^5.0.0" "@ethersproject/sha2" "^5.0.0"
"@ethersproject/strings" "^5.0.0" "@ethersproject/strings" "^5.0.0"
"@ethersproject/strings@5.0.0-beta.136": "@ethersproject/strings@^5.0.0":
version "5.0.0-beta.136"
resolved "https://registry.yarnpkg.com/@ethersproject/strings/-/strings-5.0.0-beta.136.tgz#053cbf4f9f96a7537cbc50300597f2d707907f51"
integrity sha512-Hb9RvTrgGcOavHvtQZz+AuijB79BO3g1cfF2MeMfCU9ID4j3mbZv/olzDMS2pK9r4aERJpAS94AmlWzCgoY2LQ==
dependencies:
"@ethersproject/bytes" ">=5.0.0-beta.129"
"@ethersproject/constants" ">=5.0.0-beta.128"
"@ethersproject/logger" ">=5.0.0-beta.129"
"@ethersproject/strings@>=5.0.0-beta.130", "@ethersproject/strings@^5.0.0":
version "5.0.2" version "5.0.2"
resolved "https://registry.yarnpkg.com/@ethersproject/strings/-/strings-5.0.2.tgz#1753408c3c889813fd0992abd76393e3e47a2619" resolved "https://registry.yarnpkg.com/@ethersproject/strings/-/strings-5.0.2.tgz#1753408c3c889813fd0992abd76393e3e47a2619"
integrity sha512-oNa+xvSqsFU96ndzog0IBTtsRFGOqGpzrXJ7shXLBT7juVeSEyZA/sYs0DMZB5mJ9FEjHdZKxR/rTyBY91vuXg== integrity sha512-oNa+xvSqsFU96ndzog0IBTtsRFGOqGpzrXJ7shXLBT7juVeSEyZA/sYs0DMZB5mJ9FEjHdZKxR/rTyBY91vuXg==
...@@ -1652,7 +1569,7 @@ ...@@ -1652,7 +1569,7 @@
"@ethersproject/constants" "^5.0.0" "@ethersproject/constants" "^5.0.0"
"@ethersproject/logger" "^5.0.0" "@ethersproject/logger" "^5.0.0"
"@ethersproject/transactions@>=5.0.0-beta.128", "@ethersproject/transactions@>=5.0.0-beta.135", "@ethersproject/transactions@^5.0.0": "@ethersproject/transactions@^5.0.0":
version "5.0.2" version "5.0.2"
resolved "https://registry.yarnpkg.com/@ethersproject/transactions/-/transactions-5.0.2.tgz#590ede71fc87b45be7bd46002e18ae52246a2347" resolved "https://registry.yarnpkg.com/@ethersproject/transactions/-/transactions-5.0.2.tgz#590ede71fc87b45be7bd46002e18ae52246a2347"
integrity sha512-jZp0ZbbJlq4JLZY6qoMzNtp2HQsX6USQposi3ns0MPUdn3OdZJBDtrcO15r/2VS5t/K1e1GE5MI1HmMKlcTbbQ== integrity sha512-jZp0ZbbJlq4JLZY6qoMzNtp2HQsX6USQposi3ns0MPUdn3OdZJBDtrcO15r/2VS5t/K1e1GE5MI1HmMKlcTbbQ==
...@@ -1667,15 +1584,6 @@ ...@@ -1667,15 +1584,6 @@
"@ethersproject/rlp" "^5.0.0" "@ethersproject/rlp" "^5.0.0"
"@ethersproject/signing-key" "^5.0.0" "@ethersproject/signing-key" "^5.0.0"
"@ethersproject/units@5.0.0-beta.132":
version "5.0.0-beta.132"
resolved "https://registry.yarnpkg.com/@ethersproject/units/-/units-5.0.0-beta.132.tgz#54c03c821e515a09ef79a22704ad57994ee66c45"
integrity sha512-3GZDup1uTydvqaP5wpwoRF36irp6kx/gd3buPG+aoGWLPCoPjyk76OiGoxNQKfEaynOdZ7zG2lM8WevlBDJ57g==
dependencies:
"@ethersproject/bignumber" ">=5.0.0-beta.130"
"@ethersproject/constants" ">=5.0.0-beta.128"
"@ethersproject/logger" ">=5.0.0-beta.129"
"@ethersproject/units@^5.0.0": "@ethersproject/units@^5.0.0":
version "5.0.2" version "5.0.2"
resolved "https://registry.yarnpkg.com/@ethersproject/units/-/units-5.0.2.tgz#de1461ff3ad2587e57bf367d056b6b72cfceda78" resolved "https://registry.yarnpkg.com/@ethersproject/units/-/units-5.0.2.tgz#de1461ff3ad2587e57bf367d056b6b72cfceda78"
...@@ -1685,27 +1593,6 @@ ...@@ -1685,27 +1593,6 @@
"@ethersproject/constants" "^5.0.0" "@ethersproject/constants" "^5.0.0"
"@ethersproject/logger" "^5.0.0" "@ethersproject/logger" "^5.0.0"
"@ethersproject/wallet@5.0.0-beta.141":
version "5.0.0-beta.141"
resolved "https://registry.yarnpkg.com/@ethersproject/wallet/-/wallet-5.0.0-beta.141.tgz#2a4a72cf2423c6ac08c38b5faa28e72f8e9a4f03"
integrity sha512-N/69EgBOhRXYmDj91ZUrDK7V38Eb4mrC8OvUdmGEwjHVO3VIz0sH+Li1IDVRdyGSWYhoxfVRP650ObMzL9a7dQ==
dependencies:
"@ethersproject/abstract-provider" ">=5.0.0-beta.139"
"@ethersproject/abstract-signer" ">=5.0.0-beta.142"
"@ethersproject/address" ">=5.0.0-beta.134"
"@ethersproject/bignumber" ">=5.0.0-beta.138"
"@ethersproject/bytes" ">=5.0.0-beta.137"
"@ethersproject/hash" ">=5.0.0-beta.133"
"@ethersproject/hdnode" ">=5.0.0-beta.139"
"@ethersproject/json-wallets" ">=5.0.0-beta.138"
"@ethersproject/keccak256" ">=5.0.0-beta.131"
"@ethersproject/logger" ">=5.0.0-beta.137"
"@ethersproject/properties" ">=5.0.0-beta.140"
"@ethersproject/random" ">=5.0.0-beta.135"
"@ethersproject/signing-key" ">=5.0.0-beta.135"
"@ethersproject/transactions" ">=5.0.0-beta.135"
"@ethersproject/wordlists" ">=5.0.0-beta.136"
"@ethersproject/wallet@^5.0.0": "@ethersproject/wallet@^5.0.0":
version "5.0.2" version "5.0.2"
resolved "https://registry.yarnpkg.com/@ethersproject/wallet/-/wallet-5.0.2.tgz#714ca8324c1b3b66e51b9b4e0358c882e88caf1d" resolved "https://registry.yarnpkg.com/@ethersproject/wallet/-/wallet-5.0.2.tgz#714ca8324c1b3b66e51b9b4e0358c882e88caf1d"
...@@ -1727,7 +1614,7 @@ ...@@ -1727,7 +1614,7 @@
"@ethersproject/transactions" "^5.0.0" "@ethersproject/transactions" "^5.0.0"
"@ethersproject/wordlists" "^5.0.0" "@ethersproject/wordlists" "^5.0.0"
"@ethersproject/web@>=5.0.0-beta.129", "@ethersproject/web@>=5.0.0-beta.138", "@ethersproject/web@^5.0.0": "@ethersproject/web@^5.0.0":
version "5.0.2" version "5.0.2"
resolved "https://registry.yarnpkg.com/@ethersproject/web/-/web-5.0.2.tgz#6565b4c4fe2f56de9556d0e9a966c4ccc1b7b7da" resolved "https://registry.yarnpkg.com/@ethersproject/web/-/web-5.0.2.tgz#6565b4c4fe2f56de9556d0e9a966c4ccc1b7b7da"
integrity sha512-uAlcxdrAWB9PXZlb5NPzbOOt5/m9EJP2c6eLw15/PXPkNNohEIKvdXXOWdcQgTjZ0pcAaD/9mnJ6HXg7NbqXiw== integrity sha512-uAlcxdrAWB9PXZlb5NPzbOOt5/m9EJP2c6eLw15/PXPkNNohEIKvdXXOWdcQgTjZ0pcAaD/9mnJ6HXg7NbqXiw==
...@@ -1737,7 +1624,7 @@ ...@@ -1737,7 +1624,7 @@
"@ethersproject/properties" "^5.0.0" "@ethersproject/properties" "^5.0.0"
"@ethersproject/strings" "^5.0.0" "@ethersproject/strings" "^5.0.0"
"@ethersproject/wordlists@>=5.0.0-beta.136", "@ethersproject/wordlists@^5.0.0": "@ethersproject/wordlists@^5.0.0":
version "5.0.2" version "5.0.2"
resolved "https://registry.yarnpkg.com/@ethersproject/wordlists/-/wordlists-5.0.2.tgz#eded47314509c8608373fc2b22879ee2b71b7c7c" resolved "https://registry.yarnpkg.com/@ethersproject/wordlists/-/wordlists-5.0.2.tgz#eded47314509c8608373fc2b22879ee2b71b7c7c"
integrity sha512-6vKDQcjjpnfdSCr0+jNxpFH3ieKxUPkm29tQX2US7a3zT/sJU/BGlKBR7D8oOpwdE0hpkHhJyMlypRBK+A2avA== integrity sha512-6vKDQcjjpnfdSCr0+jNxpFH3ieKxUPkm29tQX2US7a3zT/sJU/BGlKBR7D8oOpwdE0hpkHhJyMlypRBK+A2avA==
...@@ -2704,92 +2591,92 @@ ...@@ -2704,92 +2591,92 @@
"@uniswap/lib" "1.1.1" "@uniswap/lib" "1.1.1"
"@uniswap/v2-core" "1.0.0" "@uniswap/v2-core" "1.0.0"
"@walletconnect/client@^1.1.0": "@walletconnect/client@^1.1.1-alpha.0":
version "1.1.0" version "1.1.1-alpha.0"
resolved "https://registry.yarnpkg.com/@walletconnect/client/-/client-1.1.0.tgz#f2454cba82da3d8c7375b2a5d9d47f34ed7348ec" resolved "https://registry.yarnpkg.com/@walletconnect/client/-/client-1.1.1-alpha.0.tgz#18362a6b05150f02adfd281ca2251539cd727606"
integrity sha512-pHxvUDCkD4oP3AFxYLU7yeE+qDZtcHF20b2K8/HNvyuyu3eWFX4jpHgx6FdvcIcFcAXGs5nk24zBUEO8p+axWg== integrity sha512-/aOvwouwXgSMnAMypVlZB6MhIbLwEZOHF2Waa6CvcRRFYe9dA/LqI+vF/dABevg7B4R2q012ZF22NQmhZOVZsw==
dependencies: dependencies:
"@walletconnect/core" "^1.1.0" "@walletconnect/core" "^1.1.1-alpha.0"
"@walletconnect/iso-crypto" "^1.1.0" "@walletconnect/iso-crypto" "^1.1.1-alpha.0"
"@walletconnect/types" "^1.1.0" "@walletconnect/types" "^1.1.1-alpha.0"
"@walletconnect/utils" "^1.1.0" "@walletconnect/utils" "^1.1.1-alpha.0"
"@walletconnect/core@^1.1.0": "@walletconnect/core@^1.1.1-alpha.0":
version "1.1.0" version "1.1.1-alpha.0"
resolved "https://registry.yarnpkg.com/@walletconnect/core/-/core-1.1.0.tgz#053f08b0ccfdfb14ccd27b7fd425d9849cedba14" resolved "https://registry.yarnpkg.com/@walletconnect/core/-/core-1.1.1-alpha.0.tgz#ffc80babfe271ff7de07a1159f2e52e5a6487e34"
integrity sha512-Bhe4gnR6Az11u7OAOw0UDZKM6emUjIQtQ2PVdPDWke6ryC0DWMg9vTYbVPf3lDHBv5hy5eAyDst30N5E91SuYw== integrity sha512-EZf2aqB/nAouHX9T/niCcBxRTPat4B92hcHKKOhBZgKwXF4ajB0LfC1tXwhTDeQGt6PpJ1HLjtnCCJ7+/TLhJg==
dependencies: dependencies:
"@walletconnect/socket-transport" "^1.1.0" "@walletconnect/socket-transport" "^1.1.1-alpha.0"
"@walletconnect/types" "^1.1.0" "@walletconnect/types" "^1.1.1-alpha.0"
"@walletconnect/utils" "^1.1.0" "@walletconnect/utils" "^1.1.1-alpha.0"
"@walletconnect/http-connection@^1.1.0": "@walletconnect/http-connection@^1.1.1-alpha.0":
version "1.1.0" version "1.1.1-alpha.0"
resolved "https://registry.yarnpkg.com/@walletconnect/http-connection/-/http-connection-1.1.0.tgz#c6650c12a07244d30f20647420cdcd8c69c6daca" resolved "https://registry.yarnpkg.com/@walletconnect/http-connection/-/http-connection-1.1.1-alpha.0.tgz#5d02efa2a4dd70d502bbf917adc6798ff068c2b0"
integrity sha512-ugxDW/NaSgn7rmdPZhrpJIS79gASLvzBnGHScMs8zpYDHwcFxh2DP3HTspC8o5FyMqjRlEGtNi4zSGKY6EOrkw== integrity sha512-IBAwBu9xCmnDMRNiHqaRHgbNibGf4tqtY5BfzU2I49Awmbk//H8TmZ4pDRlXe6/ADWkB2CFcsDZpdjRAkfAWvg==
dependencies: dependencies:
"@walletconnect/types" "^1.1.0" "@walletconnect/types" "^1.1.1-alpha.0"
"@walletconnect/utils" "^1.1.0" "@walletconnect/utils" "^1.1.1-alpha.0"
xhr2-cookies "1.1.0" xhr2-cookies "1.1.0"
"@walletconnect/iso-crypto@^1.1.0": "@walletconnect/iso-crypto@^1.1.1-alpha.0":
version "1.1.0" version "1.1.1-alpha.0"
resolved "https://registry.yarnpkg.com/@walletconnect/iso-crypto/-/iso-crypto-1.1.0.tgz#a8235049c1b239adcf9fc6a6c38b7e9ad13004a6" resolved "https://registry.yarnpkg.com/@walletconnect/iso-crypto/-/iso-crypto-1.1.1-alpha.0.tgz#f18a336e310341427603b201f973bb8ee13800cd"
integrity sha512-ttWLj4rTy2NGQnSAKnAar1LSrsJuCQ2JnQUl8hsgc9oTwXKgnRvtxGy2Kajoih/tNKnK959Ilj4WI2HaSJ9G1g== integrity sha512-A8U57SgskexAF9TBisfYvtXc+rhjSEakF/hOJxY/vyGlITN4fS9fp/qmz+8dqSOTO4vmTj69dXHowaAhKj+PpQ==
dependencies: dependencies:
"@walletconnect/types" "^1.1.0" "@walletconnect/types" "^1.1.1-alpha.0"
"@walletconnect/utils" "^1.1.0" "@walletconnect/utils" "^1.1.1-alpha.0"
eccrypto-js "5.2.0" eccrypto-js "5.2.0"
"@walletconnect/mobile-registry@^1.1.0": "@walletconnect/mobile-registry@^1.1.1-alpha.0":
version "1.1.0" version "1.1.1-alpha.0"
resolved "https://registry.yarnpkg.com/@walletconnect/mobile-registry/-/mobile-registry-1.1.0.tgz#72173a4fcee61f4f8819f6d9fc7cfbf824ed3548" resolved "https://registry.yarnpkg.com/@walletconnect/mobile-registry/-/mobile-registry-1.1.1-alpha.0.tgz#65e6708df784e838d05fbe03459eb30a7fedc0ba"
integrity sha512-OOHQa4NeK2lbfI9WD2d+hTHGwSDzBLoTCeofdLNO2ibaTltQ6S+WNDAVuho6U8CkUTzs5cHPFgLJ6nxYZ8sr/g== integrity sha512-ncX2+XOEYu6OoIXrcLJiy2mrrMTJDuXgcJUpv5Ghwl9CbLczaiq7AlVDNPjMxZJFQj4aalR/idz6RKJbPBA6SQ==
"@walletconnect/qrcode-modal@^1.1.0": "@walletconnect/qrcode-modal@^1.1.1-alpha.0":
version "1.1.0" version "1.1.1-alpha.0"
resolved "https://registry.yarnpkg.com/@walletconnect/qrcode-modal/-/qrcode-modal-1.1.0.tgz#4cd0c2c2c713be3f49ef00293a1b23a079d4c7b7" resolved "https://registry.yarnpkg.com/@walletconnect/qrcode-modal/-/qrcode-modal-1.1.1-alpha.0.tgz#42eea4e8b091ded92bb47ba497cd3772196b7a3a"
integrity sha512-vYsu1MBE0D+kx1+xdXmaCs7JqhhWPw8orKk9Br64YIPF5pv/48i+Yi/m28/0myJm54YPlVcgzTnuf8PzAH7jgA== integrity sha512-bD7LFVdTzlAb2GFaOR2ymbPVkta8Ezct39VPkteip41KKyta468sZN2AzFTY/wPbTBn+YYwhOliOZhj+Gm9U3A==
dependencies: dependencies:
"@walletconnect/mobile-registry" "^1.1.0" "@walletconnect/mobile-registry" "^1.1.1-alpha.0"
"@walletconnect/types" "^1.1.0" "@walletconnect/types" "^1.1.1-alpha.0"
"@walletconnect/utils" "^1.1.0" "@walletconnect/utils" "^1.1.1-alpha.0"
preact "10.4.1" preact "10.4.1"
qrcode "1.4.4" qrcode "1.4.4"
"@walletconnect/socket-transport@^1.1.0": "@walletconnect/socket-transport@^1.1.1-alpha.0":
version "1.1.0" version "1.1.1-alpha.0"
resolved "https://registry.yarnpkg.com/@walletconnect/socket-transport/-/socket-transport-1.1.0.tgz#d80b5e6b3b904f131961259ca16de816ae2b003b" resolved "https://registry.yarnpkg.com/@walletconnect/socket-transport/-/socket-transport-1.1.1-alpha.0.tgz#6a1f4abb537f891a1e6c282e0a2a8d1410270554"
integrity sha512-plo5WHjL3RTDENH7MTgs7D/ePGHfSuc/HLzkVGvgZSOtoPlRR916nSZNeL4bStYF1ZRJCrds10x36C0DlZjpQg== integrity sha512-epS/zNL4GQclYZ3dDiumR0krwYEpHHGC+LsaNxkrSHTh/URuqfmf6QqCOdcjTU6qW5G1cliHC9Kk0UKcC+VeDA==
dependencies: dependencies:
"@walletconnect/types" "^1.1.0" "@walletconnect/types" "^1.1.1-alpha.0"
ws "7.3.0" ws "7.3.0"
"@walletconnect/types@^1.1.0": "@walletconnect/types@^1.1.1-alpha.0":
version "1.1.0" version "1.1.1-alpha.0"
resolved "https://registry.yarnpkg.com/@walletconnect/types/-/types-1.1.0.tgz#1e4efbf033ad89910cbb86f1f381cd5fe7e764fd" resolved "https://registry.yarnpkg.com/@walletconnect/types/-/types-1.1.1-alpha.0.tgz#9fbb8356aa347240f4356abea4aaa8b0137396d0"
integrity sha512-cgDEuYHZZTiaXFRwQs3Zhhar+l2T58/YjhWrfZTMKWuc77geIbF7682i9lE9bNEQqQvQ76jjKxJfSLGjCu++sA== integrity sha512-ro6yJ53kTG8aibKyoUv79CFMujkZk6W5FNHCk6SJdIkPec03XICrt9cqLUaPDt0wx+FM5z94ZHAg46Wzqkh5NA==
"@walletconnect/utils@^1.1.0": "@walletconnect/utils@^1.1.1-alpha.0":
version "1.1.0" version "1.1.1-alpha.0"
resolved "https://registry.yarnpkg.com/@walletconnect/utils/-/utils-1.1.0.tgz#7b0bcf5c77e8079ac055013537a9620244db2da9" resolved "https://registry.yarnpkg.com/@walletconnect/utils/-/utils-1.1.1-alpha.0.tgz#62a274290263dcca45102d1cb0cea617827c1849"
integrity sha512-y5v8PCmd/2kASOncYaz5QJiAzwBRT5MK398PmIkImX9tNEeBh00ifeQGZKkCGi6JYXbde0UC5jsGTGkH8hdxeg== integrity sha512-35NbpD7JeyzKzh/UPM+TYuxJgudeIr1LuCWdbhGX9KyIoeYxXPVJiCu7RkX5cXI0Fh1dGghQx7++4O3xkqLVcg==
dependencies: dependencies:
"@walletconnect/types" "^1.1.0" "@walletconnect/types" "^1.1.1-alpha.0"
detect-browser "5.1.0" detect-browser "5.1.0"
enc-utils "2.1.0" enc-utils "2.1.0"
js-sha3 "0.8.0" js-sha3 "0.8.0"
"@walletconnect/web3-provider@^1.0.11": "@walletconnect/web3-provider@1.1.1-alpha.0", "@walletconnect/web3-provider@^1.0.11":
version "1.1.0" version "1.1.1-alpha.0"
resolved "https://registry.yarnpkg.com/@walletconnect/web3-provider/-/web3-provider-1.1.0.tgz#c8a30c4121d3ade159022b10d3a18ecd804c8993" resolved "https://registry.yarnpkg.com/@walletconnect/web3-provider/-/web3-provider-1.1.1-alpha.0.tgz#b8ca2158da4974b692f57e4f939b5128d8e4696f"
integrity sha512-1DaYG+aK2pjCBKXrB0c2JKeFk27ObUsu09LlZN1VvIi1+zvHftaubNsSGViLmrq25w72yPle/SDjhgmxvKVMQQ== integrity sha512-1AoTeCOtK8u2jIH+0NsvisPv2TySZLWHwWu0BIb72wzvzJeG3uD383/stHX8mBOI6a0aPoyDEYzA2R4c/O0vWQ==
dependencies: dependencies:
"@walletconnect/client" "^1.1.0" "@walletconnect/client" "^1.1.1-alpha.0"
"@walletconnect/http-connection" "^1.1.0" "@walletconnect/http-connection" "^1.1.1-alpha.0"
"@walletconnect/qrcode-modal" "^1.1.0" "@walletconnect/qrcode-modal" "^1.1.1-alpha.0"
"@walletconnect/types" "^1.1.0" "@walletconnect/types" "^1.1.1-alpha.0"
"@walletconnect/utils" "^1.1.0" "@walletconnect/utils" "^1.1.1-alpha.0"
web3-provider-engine "15.0.12" web3-provider-engine "15.0.12"
"@web3-react/abstract-connector@^6.0.7": "@web3-react/abstract-connector@^6.0.7":
...@@ -6937,7 +6824,7 @@ ethereumjs-vm@^2.1.0, ethereumjs-vm@^2.3.4, ethereumjs-vm@^2.6.0: ...@@ -6937,7 +6824,7 @@ ethereumjs-vm@^2.1.0, ethereumjs-vm@^2.3.4, ethereumjs-vm@^2.6.0:
rustbn.js "~0.2.0" rustbn.js "~0.2.0"
safe-buffer "^5.1.1" safe-buffer "^5.1.1"
ethers@>=5.0.0-beta.186: ethers@^5.0.0, ethers@^5.0.7:
version "5.0.7" version "5.0.7"
resolved "https://registry.yarnpkg.com/ethers/-/ethers-5.0.7.tgz#41c3d774e0a57bfde12b0198885789fb41a14976" resolved "https://registry.yarnpkg.com/ethers/-/ethers-5.0.7.tgz#41c3d774e0a57bfde12b0198885789fb41a14976"
integrity sha512-1Zu9s+z4BgsDAZcGIYACJdWBB6mVtCCmUonj68Njul7STcSdgwOyj0sCAxCUr2Nsmsamckr4E12q3ecvZPGAUw== integrity sha512-1Zu9s+z4BgsDAZcGIYACJdWBB6mVtCCmUonj68Njul7STcSdgwOyj0sCAxCUr2Nsmsamckr4E12q3ecvZPGAUw==
...@@ -12063,7 +11950,7 @@ prompts@^2.0.1: ...@@ -12063,7 +11950,7 @@ prompts@^2.0.1:
kleur "^3.0.3" kleur "^3.0.3"
sisteransi "^1.0.4" sisteransi "^1.0.4"
prop-types@^15.5.4, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.7.2: prop-types@^15.5.4, prop-types@^15.5.8, prop-types@^15.6.2, prop-types@^15.7.2:
version "15.7.2" version "15.7.2"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==
...@@ -12159,19 +12046,6 @@ q@^1.1.2: ...@@ -12159,19 +12046,6 @@ q@^1.1.2:
resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7"
integrity sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc= integrity sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=
qr.js@0.0.0:
version "0.0.0"
resolved "https://registry.yarnpkg.com/qr.js/-/qr.js-0.0.0.tgz#cace86386f59a0db8050fa90d9b6b0e88a1e364f"
integrity sha1-ys6GOG9ZoNuAUPqQ2baw6IoeNk8=
qrcode.react@^0.9.3:
version "0.9.3"
resolved "https://registry.yarnpkg.com/qrcode.react/-/qrcode.react-0.9.3.tgz#91de1287912bdc5ccfb3b091737b828d6ced60c5"
integrity sha512-gGd30Ez7cmrKxyN2M3nueaNLk/f9J7NDRgaD5fVgxGpPLsYGWMn9UQ+XnDpv95cfszTQTdaf4QGLNMf3xU0hmw==
dependencies:
prop-types "^15.6.0"
qr.js "0.0.0"
qrcode@1.4.4: qrcode@1.4.4:
version "1.4.4" version "1.4.4"
resolved "https://registry.yarnpkg.com/qrcode/-/qrcode-1.4.4.tgz#f0c43568a7e7510a55efc3b88d9602f71963ea83" resolved "https://registry.yarnpkg.com/qrcode/-/qrcode-1.4.4.tgz#f0c43568a7e7510a55efc3b88d9602f71963ea83"
...@@ -13243,11 +13117,6 @@ schema-utils@^2.5.0, schema-utils@^2.6.0, schema-utils@^2.6.1, schema-utils@^2.6 ...@@ -13243,11 +13117,6 @@ schema-utils@^2.5.0, schema-utils@^2.6.0, schema-utils@^2.6.1, schema-utils@^2.6
ajv "^6.12.2" ajv "^6.12.2"
ajv-keywords "^3.4.1" ajv-keywords "^3.4.1"
scrypt-js@3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/scrypt-js/-/scrypt-js-3.0.0.tgz#52361c1f272eeaab09ec1f806ea82078bca58b15"
integrity sha512-7CC7aufwukEvqdmllR0ny0QaSg0+S22xKXrXz3ZahaV6J+fgD2YAtrjtImuoDWog17/Ty9Q4HBmnXEXJ3JkfQA==
scrypt-js@3.0.1, scrypt-js@^3.0.0: scrypt-js@3.0.1, scrypt-js@^3.0.0:
version "3.0.1" version "3.0.1"
resolved "https://registry.yarnpkg.com/scrypt-js/-/scrypt-js-3.0.1.tgz#d314a57c2aef69d1ad98a138a21fe9eafa9ee312" resolved "https://registry.yarnpkg.com/scrypt-js/-/scrypt-js-3.0.1.tgz#d314a57c2aef69d1ad98a138a21fe9eafa9ee312"
...@@ -14647,11 +14516,6 @@ use-callback-ref@^1.2.1, use-callback-ref@^1.2.3: ...@@ -14647,11 +14516,6 @@ use-callback-ref@^1.2.1, use-callback-ref@^1.2.3:
resolved "https://registry.yarnpkg.com/use-callback-ref/-/use-callback-ref-1.2.4.tgz#d86d1577bfd0b955b6e04aaf5971025f406bea3c" resolved "https://registry.yarnpkg.com/use-callback-ref/-/use-callback-ref-1.2.4.tgz#d86d1577bfd0b955b6e04aaf5971025f406bea3c"
integrity sha512-rXpsyvOnqdScyied4Uglsp14qzag1JIemLeTWGKbwpotWht57hbP78aNT+Q4wdFKQfQibbUX4fb6Qb4y11aVOQ== integrity sha512-rXpsyvOnqdScyied4Uglsp14qzag1JIemLeTWGKbwpotWht57hbP78aNT+Q4wdFKQfQibbUX4fb6Qb4y11aVOQ==
use-media@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/use-media/-/use-media-1.4.0.tgz#e777bf1f382a7aacabbd1f9ce3da2b62e58b2a98"
integrity sha512-XsgyUAf3nhzZmEfhc5MqLHwyaPjs78bgytpVJ/xDl0TF4Bptf3vEpBNBBT/EIKOmsOc8UbuECq3mrP3mt1QANA==
use-sidecar@^1.0.1: use-sidecar@^1.0.1:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/use-sidecar/-/use-sidecar-1.0.2.tgz#e72f582a75842f7de4ef8becd6235a4720ad8af6" resolved "https://registry.yarnpkg.com/use-sidecar/-/use-sidecar-1.0.2.tgz#e72f582a75842f7de4ef8becd6235a4720ad8af6"
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment