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: Callil Capuozzo <callil.capuozzo@gmail.com>
This commit is contained in:
parent
10ef04510a
commit
0f91af1df2
20
package.json
20
package.json
@ -4,17 +4,7 @@
|
||||
"homepage": ".",
|
||||
"private": true,
|
||||
"devDependencies": {
|
||||
"@ethersproject/address": "5.0.0-beta.134",
|
||||
"@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",
|
||||
"@ethersproject/experimental": "^5.0.1",
|
||||
"@popperjs/core": "^2.4.4",
|
||||
"@reach/dialog": "^0.10.3",
|
||||
"@reach/portal": "^0.10.3",
|
||||
@ -52,6 +42,7 @@
|
||||
"eslint-plugin-prettier": "^3.1.3",
|
||||
"eslint-plugin-react": "^7.19.0",
|
||||
"eslint-plugin-react-hooks": "^4.0.0",
|
||||
"ethers": "^5.0.7",
|
||||
"i18next": "^15.0.9",
|
||||
"i18next-browser-languagedetector": "^3.0.1",
|
||||
"i18next-xhr-backend": "^2.0.1",
|
||||
@ -60,7 +51,6 @@
|
||||
"lodash.flatmap": "^4.5.0",
|
||||
"polished": "^3.3.2",
|
||||
"prettier": "^1.17.0",
|
||||
"qrcode.react": "^0.9.3",
|
||||
"qs": "^6.9.4",
|
||||
"react": "^16.13.1",
|
||||
"react-device-detect": "^1.6.2",
|
||||
@ -80,8 +70,10 @@
|
||||
"serve": "^11.3.0",
|
||||
"start-server-and-test": "^1.11.0",
|
||||
"styled-components": "^4.2.0",
|
||||
"typescript": "^3.8.3",
|
||||
"use-media": "^1.4.0"
|
||||
"typescript": "^3.8.3"
|
||||
},
|
||||
"resolutions": {
|
||||
"@walletconnect/web3-provider": "1.1.1-alpha.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
|
@ -251,26 +251,26 @@ export default function AccountDetails({
|
||||
} else if (connector === walletconnect) {
|
||||
return (
|
||||
<IconWrapper size={16}>
|
||||
<img src={WalletConnectIcon} alt={''} />
|
||||
<img src={WalletConnectIcon} alt={'wallet connect logo'} />
|
||||
</IconWrapper>
|
||||
)
|
||||
} else if (connector === walletlink) {
|
||||
return (
|
||||
<IconWrapper size={16}>
|
||||
<img src={CoinbaseWalletIcon} alt={''} />
|
||||
<img src={CoinbaseWalletIcon} alt={'coinbase wallet logo'} />
|
||||
</IconWrapper>
|
||||
)
|
||||
} else if (connector === fortmatic) {
|
||||
return (
|
||||
<IconWrapper size={16}>
|
||||
<img src={FortmaticIcon} alt={''} />
|
||||
<img src={FortmaticIcon} alt={'fortmatic logo'} />
|
||||
</IconWrapper>
|
||||
)
|
||||
} else if (connector === portis) {
|
||||
return (
|
||||
<>
|
||||
<IconWrapper size={16}>
|
||||
<img src={PortisIcon} alt={''} />
|
||||
<img src={PortisIcon} alt={'portis logo'} />
|
||||
<MainWalletAction
|
||||
onClick={() => {
|
||||
portis.portis.showPortis()
|
||||
@ -382,7 +382,6 @@ export default function AccountDetails({
|
||||
</AccountControl>
|
||||
</>
|
||||
)}
|
||||
{/* {formatConnectorName()} */}
|
||||
</AccountGroupingRow>
|
||||
</InfoCard>
|
||||
</YourAccount>
|
||||
|
@ -27,6 +27,8 @@ const Base = styled(RebassButton)<{
|
||||
flex-wrap: nowrap;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
&:disabled {
|
||||
cursor: auto;
|
||||
}
|
||||
|
@ -1,133 +0,0 @@
|
||||
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>
|
||||
)
|
||||
}
|
@ -1,8 +1,6 @@
|
||||
import React from 'react'
|
||||
import styled, { css } from 'styled-components'
|
||||
import { animated, useTransition, useSpring } from 'react-spring'
|
||||
import { Spring } from 'react-spring/renderprops'
|
||||
|
||||
import { DialogOverlay, DialogContent } from '@reach/dialog'
|
||||
import { isMobile } from 'react-device-detect'
|
||||
import '@reach/dialog/styles.css'
|
||||
@ -11,39 +9,25 @@ import { useGesture } from 'react-use-gesture'
|
||||
|
||||
const AnimatedDialogOverlay = animated(DialogOverlay)
|
||||
// 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] {
|
||||
z-index: 2;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: transparent;
|
||||
overflow: hidden;
|
||||
|
||||
${({ mobile }) =>
|
||||
mobile &&
|
||||
css`
|
||||
align-items: flex-end;
|
||||
`}
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
background-color: ${({ theme }) => theme.modalBG};
|
||||
opacity: 0.5;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
position: fixed;
|
||||
z-index: -1;
|
||||
}
|
||||
background-color: ${({ theme }) => theme.modalBG};
|
||||
}
|
||||
`
|
||||
|
||||
const AnimatedDialogContent = animated(DialogContent)
|
||||
// destructure to not pass custom props to Dialog DOM element
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const StyledDialogContent = styled(({ minHeight, maxHeight, mobile, isOpen, ...rest }) => (
|
||||
<DialogContent {...rest} />
|
||||
<AnimatedDialogContent {...rest} />
|
||||
)).attrs({
|
||||
'aria-label': 'dialog'
|
||||
})`
|
||||
@ -55,6 +39,8 @@ const StyledDialogContent = styled(({ minHeight, maxHeight, mobile, isOpen, ...r
|
||||
padding: 0px;
|
||||
width: 50vw;
|
||||
|
||||
align-self: ${({ mobile }) => (mobile ? 'flex-end' : 'center')};
|
||||
|
||||
max-width: 420px;
|
||||
${({ maxHeight }) =>
|
||||
maxHeight &&
|
||||
@ -102,7 +88,7 @@ export default function Modal({
|
||||
initialFocusRef = null,
|
||||
children
|
||||
}: ModalProps) {
|
||||
const transitions = useTransition(isOpen, null, {
|
||||
const fadeTransition = useTransition(isOpen, null, {
|
||||
config: { duration: 200 },
|
||||
from: { opacity: 0 },
|
||||
enter: { opacity: 1 },
|
||||
@ -115,80 +101,37 @@ export default function Modal({
|
||||
set({
|
||||
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()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if (isMobile) {
|
||||
return (
|
||||
<>
|
||||
{transitions.map(
|
||||
({ item, key, props }) =>
|
||||
item && (
|
||||
<StyledDialogOverlay
|
||||
key={key}
|
||||
style={props}
|
||||
onDismiss={onDismiss}
|
||||
initialFocusRef={initialFocusRef}
|
||||
mobile={true}
|
||||
return (
|
||||
<>
|
||||
{fadeTransition.map(
|
||||
({ item, key, props }) =>
|
||||
item && (
|
||||
<StyledDialogOverlay key={key} style={props} onDismiss={onDismiss} initialFocusRef={initialFocusRef}>
|
||||
<StyledDialogContent
|
||||
{...(isMobile
|
||||
? {
|
||||
...bind(),
|
||||
style: { transform: y.interpolate(y => `translateY(${y > 0 ? y : 0}px)`) }
|
||||
}
|
||||
: {})}
|
||||
aria-label="dialog content"
|
||||
minHeight={minHeight}
|
||||
maxHeight={maxHeight}
|
||||
mobile={isMobile}
|
||||
>
|
||||
{/* 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 && (
|
||||
<StyledDialogOverlay key={key} style={props} onDismiss={onDismiss} initialFocusRef={initialFocusRef}>
|
||||
<StyledDialogContent
|
||||
aria-label="dialog content"
|
||||
hidden={true}
|
||||
minHeight={minHeight}
|
||||
maxHeight={maxHeight}
|
||||
isOpen={isOpen}
|
||||
>
|
||||
{children}
|
||||
</StyledDialogContent>
|
||||
</StyledDialogOverlay>
|
||||
)
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
{!initialFocusRef && isMobile ? <div tabIndex={1} /> : null}
|
||||
{children}
|
||||
</StyledDialogContent>
|
||||
</StyledDialogOverlay>
|
||||
)
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ import useInterval from '../../hooks/useInterval'
|
||||
import { PopupContent } from '../../state/application/actions'
|
||||
import { useRemovePopup } from '../../state/application/hooks'
|
||||
import ListUpdatePopup from './ListUpdatePopup'
|
||||
import TxnPopup from './TxnPopup'
|
||||
import TransactionPopup from './TransactionPopup'
|
||||
|
||||
export const StyledClose = styled(X)`
|
||||
position: absolute;
|
||||
@ -68,7 +68,7 @@ export default function PopupItem({ content, popKey }: { content: PopupContent;
|
||||
const {
|
||||
txn: { hash, success, summary }
|
||||
} = content
|
||||
popupContent = <TxnPopup hash={hash} success={success} summary={summary} />
|
||||
popupContent = <TransactionPopup hash={hash} success={success} summary={summary} />
|
||||
} else if ('listUpdate' in content) {
|
||||
const {
|
||||
listUpdate: { listUrl, oldList, newList, auto }
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React, { useContext } from 'react'
|
||||
import { AlertCircle, CheckCircle } from 'react-feather'
|
||||
import { ThemeContext } from 'styled-components'
|
||||
import styled, { ThemeContext } from 'styled-components'
|
||||
import { useActiveWeb3React } from '../../hooks'
|
||||
import { TYPE } from '../../theme'
|
||||
import { ExternalLink } from '../../theme/components'
|
||||
@ -8,13 +8,25 @@ import { getEtherscanLink } from '../../utils'
|
||||
import { AutoColumn } from '../Column'
|
||||
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 theme = useContext(ThemeContext)
|
||||
|
||||
return (
|
||||
<AutoRow>
|
||||
<RowNoFlex>
|
||||
<div style={{ paddingRight: 16 }}>
|
||||
{success ? <CheckCircle color={theme.green1} size={24} /> : <AlertCircle color={theme.red1} size={24} />}
|
||||
</div>
|
||||
@ -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>
|
||||
<ExternalLink href={getEtherscanLink(chainId, hash, 'transaction')}>View on Etherscan</ExternalLink>
|
||||
</AutoColumn>
|
||||
</AutoRow>
|
||||
</RowNoFlex>
|
||||
)
|
||||
}
|
@ -1,6 +1,5 @@
|
||||
import React from 'react'
|
||||
import styled from 'styled-components'
|
||||
import { useMediaLayout } from 'use-media'
|
||||
import { useActivePopups } from '../../state/application/hooks'
|
||||
import { AutoColumn } from '../Column'
|
||||
import PopupItem from './PopupItem'
|
||||
@ -11,6 +10,11 @@ const MobilePopupWrapper = styled.div<{ height: string | number }>`
|
||||
height: ${({ height }) => height};
|
||||
margin: ${({ height }) => (height ? '0 auto;' : 0)};
|
||||
margin-bottom: ${({ height }) => (height ? '20px' : 0)}};
|
||||
|
||||
display: none;
|
||||
${({ theme }) => theme.mediaWidth.upToSmall`
|
||||
display: block;
|
||||
`};
|
||||
`
|
||||
|
||||
const MobilePopupInner = styled.div`
|
||||
@ -26,8 +30,8 @@ const MobilePopupInner = styled.div`
|
||||
`
|
||||
|
||||
const FixedPopupColumn = styled(AutoColumn)`
|
||||
position: absolute;
|
||||
top: 112px;
|
||||
position: fixed;
|
||||
top: 64px;
|
||||
right: 1rem;
|
||||
max-width: 355px !important;
|
||||
width: 100%;
|
||||
@ -41,21 +45,13 @@ export default function Popups() {
|
||||
// get all popups
|
||||
const activePopups = useActivePopups()
|
||||
|
||||
// switch view settings on mobile
|
||||
const isMobile = useMediaLayout({ maxWidth: '600px' })
|
||||
|
||||
if (!isMobile) {
|
||||
return (
|
||||
return (
|
||||
<>
|
||||
<FixedPopupColumn gap="20px">
|
||||
{activePopups.map(item => (
|
||||
<PopupItem key={item.key} content={item.content} popKey={item.key} />
|
||||
))}
|
||||
</FixedPopupColumn>
|
||||
)
|
||||
}
|
||||
//mobile
|
||||
else
|
||||
return (
|
||||
<MobilePopupWrapper height={activePopups?.length > 0 ? 'fit-content' : 0}>
|
||||
<MobilePopupInner>
|
||||
{activePopups // reverse so new items up front
|
||||
@ -66,5 +62,6 @@ export default function Popups() {
|
||||
))}
|
||||
</MobilePopupInner>
|
||||
</MobilePopupWrapper>
|
||||
)
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
195
src/components/TransactionConfirmationModal/index.tsx
Normal file
195
src/components/TransactionConfirmationModal/index.tsx
Normal file
@ -0,0 +1,195 @@
|
||||
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>
|
||||
)
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
{
|
||||
"extends": "../../../tsconfig.strict.json",
|
||||
"include": ["**/*"]
|
||||
}
|
@ -349,9 +349,7 @@ export default function WalletModal({
|
||||
{walletView !== WALLET_VIEWS.PENDING && (
|
||||
<Blurb>
|
||||
<span>New to Ethereum? </span>{' '}
|
||||
<ExternalLink href="https://ethereum.org/use/#3-what-is-a-wallet-and-which-one-should-i-use">
|
||||
Learn more about wallets
|
||||
</ExternalLink>
|
||||
<ExternalLink href="https://ethereum.org/wallets/">Learn more about wallets</ExternalLink>
|
||||
</Blurb>
|
||||
)}
|
||||
</ContentWrapper>
|
||||
|
@ -73,23 +73,27 @@ export function AdvancedSwapDetails({ trade }: AdvancedSwapDetailsProps) {
|
||||
|
||||
const [allowedSlippage] = useUserSlippageTolerance()
|
||||
|
||||
const showRoute = trade?.route?.path?.length > 2
|
||||
const showRoute = Boolean(trade && trade.route.path.length > 2)
|
||||
|
||||
return (
|
||||
<AutoColumn gap="md">
|
||||
{trade && <TradeSummary trade={trade} allowedSlippage={allowedSlippage} />}
|
||||
{showRoute && (
|
||||
{trade && (
|
||||
<>
|
||||
<SectionBreak />
|
||||
<AutoColumn style={{ padding: '0 24px' }}>
|
||||
<RowFixed>
|
||||
<TYPE.black fontSize={14} fontWeight={400} color={theme.text2}>
|
||||
Route
|
||||
</TYPE.black>
|
||||
<QuestionHelper text="Routing through these tokens resulted in the best price for your trade." />
|
||||
</RowFixed>
|
||||
<SwapRoute trade={trade} />
|
||||
</AutoColumn>
|
||||
<TradeSummary trade={trade} allowedSlippage={allowedSlippage} />
|
||||
{showRoute && (
|
||||
<>
|
||||
<SectionBreak />
|
||||
<AutoColumn style={{ padding: '0 24px' }}>
|
||||
<RowFixed>
|
||||
<TYPE.black fontSize={14} fontWeight={400} color={theme.text2}>
|
||||
Route
|
||||
</TYPE.black>
|
||||
<QuestionHelper text="Routing through these tokens resulted in the best price for your trade." />
|
||||
</RowFixed>
|
||||
<SwapRoute trade={trade} />
|
||||
</AutoColumn>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</AutoColumn>
|
||||
|
@ -1,6 +1,6 @@
|
||||
import React from 'react'
|
||||
import styled from 'styled-components'
|
||||
import useLast from '../../hooks/useLast'
|
||||
import { useLastTruthy } from '../../hooks/useLast'
|
||||
import { AdvancedSwapDetails, AdvancedSwapDetailsProps } from './AdvancedSwapDetails'
|
||||
|
||||
const AdvancedDetailsFooter = styled.div<{ show: boolean }>`
|
||||
@ -20,11 +20,11 @@ const AdvancedDetailsFooter = styled.div<{ show: boolean }>`
|
||||
`
|
||||
|
||||
export default function AdvancedSwapDetailsDropdown({ trade, ...rest }: AdvancedSwapDetailsProps) {
|
||||
const lastTrade = useLast(trade)
|
||||
const lastTrade = useLastTruthy(trade)
|
||||
|
||||
return (
|
||||
<AdvancedDetailsFooter show={Boolean(trade)}>
|
||||
<AdvancedSwapDetails {...rest} trade={trade ?? lastTrade} />
|
||||
<AdvancedSwapDetails {...rest} trade={trade ?? lastTrade ?? undefined} />
|
||||
</AdvancedDetailsFooter>
|
||||
)
|
||||
}
|
||||
|
109
src/components/swap/ConfirmSwapModal.tsx
Normal file
109
src/components/swap/ConfirmSwapModal.tsx
Normal file
@ -0,0 +1,109 @@
|
||||
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'
|
||||
import { warningSeverity } from '../../utils/prices'
|
||||
import { ErrorText } from './styleds'
|
||||
|
||||
/**
|
||||
* Formatted version of price impact text with warning colors
|
||||
*/
|
||||
export default function FormattedPriceImpact({ priceImpact }: { priceImpact?: Percent }) {
|
||||
return (
|
||||
<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>
|
||||
)
|
||||
}
|
||||
|
@ -1,46 +1,44 @@
|
||||
import { CurrencyAmount, Percent, Trade, TradeType } from '@uniswap/sdk'
|
||||
import React, { useContext } from 'react'
|
||||
import { Trade, TradeType } from '@uniswap/sdk'
|
||||
import React, { useContext, useMemo, useState } from 'react'
|
||||
import { Repeat } from 'react-feather'
|
||||
import { Text } from 'rebass'
|
||||
import { ThemeContext } from 'styled-components'
|
||||
import { Field } from '../../state/swap/actions'
|
||||
import { TYPE } from '../../theme'
|
||||
import { formatExecutionPrice } from '../../utils/prices'
|
||||
import {
|
||||
computeSlippageAdjustedAmounts,
|
||||
computeTradePriceBreakdown,
|
||||
formatExecutionPrice,
|
||||
warningSeverity
|
||||
} from '../../utils/prices'
|
||||
import { ButtonError } from '../Button'
|
||||
import { AutoColumn } from '../Column'
|
||||
import QuestionHelper from '../QuestionHelper'
|
||||
import { AutoRow, RowBetween, RowFixed } from '../Row'
|
||||
import FormattedPriceImpact from './FormattedPriceImpact'
|
||||
import { StyledBalanceMaxMini } from './styleds'
|
||||
import { StyledBalanceMaxMini, SwapCallbackError } from './styleds'
|
||||
|
||||
export default function SwapModalFooter({
|
||||
trade,
|
||||
showInverted,
|
||||
setShowInverted,
|
||||
severity,
|
||||
slippageAdjustedAmounts,
|
||||
onSwap,
|
||||
parsedAmounts,
|
||||
realizedLPFee,
|
||||
priceImpactWithoutFee,
|
||||
confirmText
|
||||
onConfirm,
|
||||
allowedSlippage,
|
||||
swapErrorMessage,
|
||||
disabledConfirm
|
||||
}: {
|
||||
trade?: Trade
|
||||
showInverted: boolean
|
||||
setShowInverted: (inverted: boolean) => void
|
||||
severity: number
|
||||
slippageAdjustedAmounts?: { [field in Field]?: CurrencyAmount }
|
||||
onSwap: () => any
|
||||
parsedAmounts?: { [field in Field]?: CurrencyAmount }
|
||||
realizedLPFee?: CurrencyAmount
|
||||
priceImpactWithoutFee?: Percent
|
||||
confirmText: string
|
||||
trade: Trade
|
||||
allowedSlippage: number
|
||||
onConfirm: () => void
|
||||
swapErrorMessage: string | undefined
|
||||
disabledConfirm: boolean
|
||||
}) {
|
||||
const [showInverted, setShowInverted] = useState<boolean>(false)
|
||||
const theme = useContext(ThemeContext)
|
||||
|
||||
if (!trade) {
|
||||
return null
|
||||
}
|
||||
const slippageAdjustedAmounts = useMemo(() => computeSlippageAdjustedAmounts(trade, allowedSlippage), [
|
||||
allowedSlippage,
|
||||
trade
|
||||
])
|
||||
const { priceImpactWithoutFee, realizedLPFee } = useMemo(() => computeTradePriceBreakdown(trade), [trade])
|
||||
const severity = warningSeverity(priceImpactWithoutFee)
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -71,23 +69,21 @@ export default function SwapModalFooter({
|
||||
<RowBetween>
|
||||
<RowFixed>
|
||||
<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>
|
||||
<QuestionHelper text="Your transaction will revert if there is a large, unfavorable price movement before it is confirmed." />
|
||||
</RowFixed>
|
||||
<RowFixed>
|
||||
<TYPE.black fontSize={14}>
|
||||
{trade?.tradeType === TradeType.EXACT_INPUT
|
||||
{trade.tradeType === TradeType.EXACT_INPUT
|
||||
? slippageAdjustedAmounts[Field.OUTPUT]?.toSignificant(4) ?? '-'
|
||||
: slippageAdjustedAmounts[Field.INPUT]?.toSignificant(4) ?? '-'}
|
||||
</TYPE.black>
|
||||
{parsedAmounts[Field.OUTPUT] && parsedAmounts[Field.INPUT] && (
|
||||
<TYPE.black fontSize={14} marginLeft={'4px'}>
|
||||
{trade?.tradeType === TradeType.EXACT_INPUT
|
||||
? parsedAmounts[Field.OUTPUT]?.currency?.symbol
|
||||
: parsedAmounts[Field.INPUT]?.currency?.symbol}
|
||||
</TYPE.black>
|
||||
)}
|
||||
<TYPE.black fontSize={14} marginLeft={'4px'}>
|
||||
{trade.tradeType === TradeType.EXACT_INPUT
|
||||
? trade.outputAmount.currency.symbol
|
||||
: trade.inputAmount.currency.symbol}
|
||||
</TYPE.black>
|
||||
</RowFixed>
|
||||
</RowBetween>
|
||||
<RowBetween>
|
||||
@ -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." />
|
||||
</RowFixed>
|
||||
<TYPE.black fontSize={14}>
|
||||
{realizedLPFee ? realizedLPFee?.toSignificant(6) + ' ' + trade?.inputAmount?.currency?.symbol : '-'}
|
||||
{realizedLPFee ? realizedLPFee?.toSignificant(6) + ' ' + trade.inputAmount.currency.symbol : '-'}
|
||||
</TYPE.black>
|
||||
</RowBetween>
|
||||
</AutoColumn>
|
||||
|
||||
<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}>
|
||||
{confirmText}
|
||||
{severity > 2 ? 'Swap Anyway' : 'Confirm Swap'}
|
||||
</Text>
|
||||
</ButtonError>
|
||||
|
||||
{swapErrorMessage ? <SwapCallbackError error={swapErrorMessage} /> : null}
|
||||
</AutoRow>
|
||||
</>
|
||||
)
|
||||
|
@ -1,66 +1,107 @@
|
||||
import { Currency, CurrencyAmount } from '@uniswap/sdk'
|
||||
import React, { useContext } from 'react'
|
||||
import { ArrowDown } from 'react-feather'
|
||||
import { Trade, TradeType } from '@uniswap/sdk'
|
||||
import React, { useContext, useMemo } from 'react'
|
||||
import { ArrowDown, AlertTriangle } from 'react-feather'
|
||||
import { Text } from 'rebass'
|
||||
import { ThemeContext } from 'styled-components'
|
||||
import { Field } from '../../state/swap/actions'
|
||||
import { TYPE } from '../../theme'
|
||||
import { ButtonPrimary } from '../Button'
|
||||
import { isAddress, shortenAddress } from '../../utils'
|
||||
import { computeSlippageAdjustedAmounts, computeTradePriceBreakdown, warningSeverity } from '../../utils/prices'
|
||||
import { AutoColumn } from '../Column'
|
||||
import { RowBetween, RowFixed } from '../Row'
|
||||
import CurrencyLogo from '../CurrencyLogo'
|
||||
import { TruncatedText } from './styleds'
|
||||
import { RowBetween, RowFixed } from '../Row'
|
||||
import { TruncatedText, SwapShowAcceptChanges } from './styleds'
|
||||
|
||||
export default function SwapModalHeader({
|
||||
currencies,
|
||||
formattedAmounts,
|
||||
slippageAdjustedAmounts,
|
||||
priceImpactSeverity,
|
||||
independentField,
|
||||
recipient
|
||||
trade,
|
||||
allowedSlippage,
|
||||
recipient,
|
||||
showAcceptChanges,
|
||||
onAcceptChanges
|
||||
}: {
|
||||
currencies: { [field in Field]?: Currency }
|
||||
formattedAmounts: { [field in Field]?: string }
|
||||
slippageAdjustedAmounts: { [field in Field]?: CurrencyAmount }
|
||||
priceImpactSeverity: number
|
||||
independentField: Field
|
||||
trade: Trade
|
||||
allowedSlippage: number
|
||||
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)
|
||||
|
||||
return (
|
||||
<AutoColumn gap={'md'} style={{ marginTop: '20px' }}>
|
||||
<RowBetween align="flex-end">
|
||||
<TruncatedText fontSize={24} fontWeight={500}>
|
||||
{formattedAmounts[Field.INPUT]}
|
||||
</TruncatedText>
|
||||
<RowFixed gap="4px">
|
||||
<CurrencyLogo currency={currencies[Field.INPUT]} size={'24px'} />
|
||||
<RowFixed gap={'0px'}>
|
||||
<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>
|
||||
</RowFixed>
|
||||
<RowFixed gap={'0px'}>
|
||||
<Text fontSize={24} fontWeight={500} style={{ marginLeft: '10px' }}>
|
||||
{currencies[Field.INPUT]?.symbol}
|
||||
{trade.inputAmount.currency.symbol}
|
||||
</Text>
|
||||
</RowFixed>
|
||||
</RowBetween>
|
||||
<RowFixed>
|
||||
<ArrowDown size="16" color={theme.text2} />
|
||||
<ArrowDown size="16" color={theme.text2} style={{ marginLeft: '4px', minWidth: '16px' }} />
|
||||
</RowFixed>
|
||||
<RowBetween align="flex-end">
|
||||
<TruncatedText fontSize={24} fontWeight={500} color={priceImpactSeverity > 2 ? theme.red1 : ''}>
|
||||
{formattedAmounts[Field.OUTPUT]}
|
||||
</TruncatedText>
|
||||
<RowFixed gap="4px">
|
||||
<CurrencyLogo currency={currencies[Field.OUTPUT]} size={'24px'} />
|
||||
<RowFixed gap={'0px'}>
|
||||
<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>
|
||||
</RowFixed>
|
||||
<RowFixed gap={'0px'}>
|
||||
<Text fontSize={24} fontWeight={500} style={{ marginLeft: '10px' }}>
|
||||
{currencies[Field.OUTPUT]?.symbol}
|
||||
{trade.outputAmount.currency.symbol}
|
||||
</Text>
|
||||
</RowFixed>
|
||||
</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' }}>
|
||||
{independentField === Field.INPUT ? (
|
||||
{trade.tradeType === TradeType.EXACT_INPUT ? (
|
||||
<TYPE.italic textAlign="left" style={{ width: '100%' }}>
|
||||
{`Output is estimated. You will receive at least `}
|
||||
<b>
|
||||
{slippageAdjustedAmounts[Field.OUTPUT]?.toSignificant(6)} {currencies[Field.OUTPUT]?.symbol}
|
||||
{slippageAdjustedAmounts[Field.OUTPUT]?.toSignificant(6)} {trade.outputAmount.currency.symbol}
|
||||
</b>
|
||||
{' or the transaction will revert.'}
|
||||
</TYPE.italic>
|
||||
@ -68,7 +109,7 @@ export default function SwapModalHeader({
|
||||
<TYPE.italic textAlign="left" style={{ width: '100%' }}>
|
||||
{`Input is estimated. You will sell at most `}
|
||||
<b>
|
||||
{slippageAdjustedAmounts[Field.INPUT]?.toSignificant(6)} {currencies[Field.INPUT]?.symbol}
|
||||
{slippageAdjustedAmounts[Field.INPUT]?.toSignificant(6)} {trade.inputAmount.currency.symbol}
|
||||
</b>
|
||||
{' or the transaction will revert.'}
|
||||
</TYPE.italic>
|
||||
|
@ -1,7 +1,11 @@
|
||||
// gathers additional user consent for a high price impact
|
||||
import { Percent } from '@uniswap/sdk'
|
||||
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 {
|
||||
if (!priceImpactWithoutFee.lessThan(PRICE_IMPACT_WITHOUT_FEE_CONFIRM_MIN)) {
|
||||
return (
|
||||
|
@ -1,8 +1,9 @@
|
||||
import { transparentize } from 'polished'
|
||||
import React from 'react'
|
||||
import { AlertTriangle } from 'react-feather'
|
||||
import styled, { css } from 'styled-components'
|
||||
import { AutoColumn } from '../Column'
|
||||
import { Text } from 'rebass'
|
||||
|
||||
import NumericalInput from '../NumericalInput'
|
||||
import { AutoColumn } from '../Column'
|
||||
|
||||
export const Wrapper = styled.div`
|
||||
position: relative;
|
||||
@ -30,7 +31,6 @@ export const SectionBreak = styled.div`
|
||||
|
||||
export const BottomGrouping = styled.div`
|
||||
margin-top: 12px;
|
||||
position: relative;
|
||||
`
|
||||
|
||||
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};
|
||||
`
|
||||
|
||||
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`
|
||||
height: 22px;
|
||||
width: 22px;
|
||||
@ -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;
|
||||
`
|
4
src/components/swap/tsconfig.json
Normal file
4
src/components/swap/tsconfig.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"extends": "../../../tsconfig.strict.json",
|
||||
"include": ["**/*"]
|
||||
}
|
@ -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 {
|
||||
public readonly isMetaMask: false = false
|
||||
public readonly chainId: number
|
||||
public readonly url: string
|
||||
public readonly host: string
|
||||
public readonly path: string
|
||||
public readonly batchWaitTimeMs: number
|
||||
|
||||
constructor(chainId: number, url: string) {
|
||||
private nextId = 1
|
||||
private batchTimeoutId: ReturnType<typeof setTimeout> | null = null
|
||||
private batch: BatchItem[] = []
|
||||
|
||||
constructor(chainId: number, url: string, batchWaitTimeMs?: number) {
|
||||
this.chainId = chainId
|
||||
this.url = url
|
||||
const parsed = new URL(url)
|
||||
this.host = parsed.host
|
||||
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 = (
|
||||
@ -56,24 +120,20 @@ class MiniRpcProvider implements AsyncSendable {
|
||||
if (method === 'eth_chainId') {
|
||||
return `0x${this.chainId.toString(16)}`
|
||||
}
|
||||
const response = await fetch(this.url, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
jsonrpc: '2.0',
|
||||
id: 1,
|
||||
method,
|
||||
params
|
||||
const promise = new Promise((resolve, reject) => {
|
||||
this.batch.push({
|
||||
request: {
|
||||
jsonrpc: '2.0',
|
||||
id: this.nextId++,
|
||||
method,
|
||||
params
|
||||
},
|
||||
resolve,
|
||||
reject
|
||||
})
|
||||
})
|
||||
if (!response.ok) throw new RequestError(`${response.status}: ${response.statusText}`, -32000)
|
||||
const body = await response.json()
|
||||
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)
|
||||
}
|
||||
this.batchTimeoutId = this.batchTimeoutId ?? setTimeout(this.clearBatch, this.batchWaitTimeMs)
|
||||
return promise
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { AbstractConnector } from '@web3-react/abstract-connector'
|
||||
import { ChainId, JSBI, Percent, Token, WETH } from '@uniswap/sdk'
|
||||
|
||||
import { fortmatic, injected, portis, walletconnect, walletlink } from '../connectors'
|
||||
@ -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: {
|
||||
connector: injected,
|
||||
name: 'Injected',
|
||||
@ -69,62 +82,53 @@ const TESTNET_CAPABLE_WALLETS = {
|
||||
description: 'Easy-to-use browser extension.',
|
||||
href: null,
|
||||
color: '#E8831D'
|
||||
},
|
||||
WALLET_CONNECT: {
|
||||
connector: walletconnect,
|
||||
name: 'WalletConnect',
|
||||
iconName: 'walletConnectIcon.svg',
|
||||
description: 'Connect to Trust Wallet, Rainbow Wallet and more...',
|
||||
href: null,
|
||||
color: '#4196FC',
|
||||
mobile: true
|
||||
},
|
||||
WALLET_LINK: {
|
||||
connector: walletlink,
|
||||
name: 'Coinbase Wallet',
|
||||
iconName: 'coinbaseWalletIcon.svg',
|
||||
description: 'Use Coinbase Wallet app on mobile device',
|
||||
href: null,
|
||||
color: '#315CF5'
|
||||
},
|
||||
COINBASE_LINK: {
|
||||
name: 'Open in Coinbase Wallet',
|
||||
iconName: 'coinbaseWalletIcon.svg',
|
||||
description: 'Open in Coinbase Wallet app.',
|
||||
href: 'https://go.cb-w.com/mtUDhEZPy1',
|
||||
color: '#315CF5',
|
||||
mobile: true,
|
||||
mobileOnly: true
|
||||
},
|
||||
FORTMATIC: {
|
||||
connector: fortmatic,
|
||||
name: 'Fortmatic',
|
||||
iconName: 'fortmaticIcon.png',
|
||||
description: 'Login using Fortmatic hosted wallet',
|
||||
href: null,
|
||||
color: '#6748FF',
|
||||
mobile: true
|
||||
},
|
||||
Portis: {
|
||||
connector: portis,
|
||||
name: 'Portis',
|
||||
iconName: 'portisIcon.png',
|
||||
description: 'Login using Portis hosted wallet',
|
||||
href: null,
|
||||
color: '#4A6C9B',
|
||||
mobile: true
|
||||
}
|
||||
}
|
||||
|
||||
export const SUPPORTED_WALLETS =
|
||||
process.env.REACT_APP_CHAIN_ID !== '1'
|
||||
? TESTNET_CAPABLE_WALLETS
|
||||
: {
|
||||
...TESTNET_CAPABLE_WALLETS,
|
||||
...{
|
||||
WALLET_CONNECT: {
|
||||
connector: walletconnect,
|
||||
name: 'WalletConnect',
|
||||
iconName: 'walletConnectIcon.svg',
|
||||
description: 'Connect to Trust Wallet, Rainbow Wallet and more...',
|
||||
href: null,
|
||||
color: '#4196FC',
|
||||
mobile: true
|
||||
},
|
||||
WALLET_LINK: {
|
||||
connector: walletlink,
|
||||
name: 'Coinbase Wallet',
|
||||
iconName: 'coinbaseWalletIcon.svg',
|
||||
description: 'Use Coinbase Wallet app on mobile device',
|
||||
href: null,
|
||||
color: '#315CF5'
|
||||
},
|
||||
COINBASE_LINK: {
|
||||
name: 'Open in Coinbase Wallet',
|
||||
iconName: 'coinbaseWalletIcon.svg',
|
||||
description: 'Open in Coinbase Wallet app.',
|
||||
href: 'https://go.cb-w.com/mtUDhEZPy1',
|
||||
color: '#315CF5',
|
||||
mobile: true,
|
||||
mobileOnly: true
|
||||
},
|
||||
FORTMATIC: {
|
||||
connector: fortmatic,
|
||||
name: 'Fortmatic',
|
||||
iconName: 'fortmaticIcon.png',
|
||||
description: 'Login using Fortmatic hosted wallet',
|
||||
href: null,
|
||||
color: '#6748FF',
|
||||
mobile: true
|
||||
},
|
||||
Portis: {
|
||||
connector: portis,
|
||||
name: 'Portis',
|
||||
iconName: 'portisIcon.png',
|
||||
description: 'Login using Portis hosted wallet',
|
||||
href: null,
|
||||
color: '#4A6C9B',
|
||||
mobile: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const NetworkContextName = 'NETWORK'
|
||||
|
||||
// default allowed slippage, in bips
|
||||
|
@ -131,7 +131,7 @@ export function useV1Trade(
|
||||
? new Trade(route, exactAmount, isExactIn ? TradeType.EXACT_INPUT : TradeType.EXACT_OUTPUT)
|
||||
: undefined
|
||||
} catch (error) {
|
||||
console.error('Failed to create V1 trade', error)
|
||||
console.debug('Failed to create V1 trade', error)
|
||||
}
|
||||
return v1Trade
|
||||
}
|
||||
|
@ -1,13 +1,33 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
/**
|
||||
* Returns the last value of type T that passes a filter function
|
||||
* @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,
|
||||
filterFn?: (value: T | null | undefined) => boolean
|
||||
): T | null | undefined {
|
||||
const [last, setLast] = useState<T | null | undefined>(filterFn && filterFn(value) ? value : undefined)
|
||||
useEffect(() => {
|
||||
setLast(last => {
|
||||
const shouldUse: boolean = filterFn ? filterFn(value) : true
|
||||
if (shouldUse) return value
|
||||
return last
|
||||
})
|
||||
}, [filterFn, value])
|
||||
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 default function useLast<T>(value: T | undefined | null): T | null | undefined {
|
||||
const [last, setLast] = useState<T | null | undefined>(value)
|
||||
useEffect(() => {
|
||||
setLast(last => value ?? last)
|
||||
}, [value])
|
||||
return last
|
||||
export function useLastTruthy<T>(value: T | undefined | null): T | null | undefined {
|
||||
return useLast(value, isDefined)
|
||||
}
|
||||
|
@ -1,21 +1,111 @@
|
||||
import { BigNumber } from '@ethersproject/bignumber'
|
||||
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 { BIPS_BASE, DEFAULT_DEADLINE_FROM_NOW, INITIAL_ALLOWED_SLIPPAGE } from '../constants'
|
||||
import { getTradeVersion, useV1TradeExchangeAddress } from '../data/V1'
|
||||
import { useTransactionAdder } from '../state/transactions/hooks'
|
||||
import { calculateGasMargin, getRouterContract, isAddress, shortenAddress } from '../utils'
|
||||
import isZero from '../utils/isZero'
|
||||
import v1SwapArguments from '../utils/v1SwapArguments'
|
||||
import { useActiveWeb3React } from './index'
|
||||
import { useV1ExchangeContract } from './useContract'
|
||||
import useENS from './useENS'
|
||||
import { Version } from './useToggledVersion'
|
||||
|
||||
function isZero(hexNumber: string) {
|
||||
return /^0x0*$/.test(hexNumber)
|
||||
export enum SwapCallbackState {
|
||||
INVALID,
|
||||
LOADING,
|
||||
VALID
|
||||
}
|
||||
|
||||
interface SwapCall {
|
||||
contract: Contract
|
||||
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
|
||||
allowedSlippage: number = INITIAL_ALLOWED_SLIPPAGE, // in bips
|
||||
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
|
||||
): SwapCall[] {
|
||||
const { account, chainId, library } = useActiveWeb3React()
|
||||
|
||||
const { address: recipientAddress } = useENS(recipientAddressOrName)
|
||||
const recipient = recipientAddressOrName === null ? account : recipientAddress
|
||||
|
||||
const v1Exchange = useV1ExchangeContract(useV1TradeExchangeAddress(trade), true)
|
||||
|
||||
return useMemo(() => {
|
||||
const tradeVersion = getTradeVersion(trade)
|
||||
if (!trade || !recipient || !library || !account || !tradeVersion || !chainId) return []
|
||||
|
||||
const contract: Contract | null =
|
||||
tradeVersion === Version.v2 ? getRouterContract(chainId, library, account) : v1Exchange
|
||||
if (!contract) {
|
||||
return []
|
||||
}
|
||||
|
||||
const swapMethods = []
|
||||
|
||||
switch (tradeVersion) {
|
||||
case Version.v2:
|
||||
swapMethods.push(
|
||||
Router.swapCallParameters(trade, {
|
||||
feeOnTransfer: false,
|
||||
allowedSlippage: new Percent(JSBI.BigInt(allowedSlippage), BIPS_BASE),
|
||||
recipient,
|
||||
ttl: deadline
|
||||
})
|
||||
)
|
||||
|
||||
if (trade.tradeType === TradeType.EXACT_INPUT) {
|
||||
swapMethods.push(
|
||||
Router.swapCallParameters(trade, {
|
||||
feeOnTransfer: true,
|
||||
allowedSlippage: new Percent(JSBI.BigInt(allowedSlippage), BIPS_BASE),
|
||||
recipient,
|
||||
ttl: deadline
|
||||
})
|
||||
)
|
||||
}
|
||||
break
|
||||
case Version.v1:
|
||||
swapMethods.push(
|
||||
v1SwapArguments(trade, {
|
||||
allowedSlippage: new Percent(JSBI.BigInt(allowedSlippage), BIPS_BASE),
|
||||
recipient,
|
||||
ttl: deadline
|
||||
})
|
||||
)
|
||||
break
|
||||
}
|
||||
return swapMethods.map(parameters => ({ parameters, contract }))
|
||||
}, [account, allowedSlippage, chainId, deadline, library, recipient, trade, v1Exchange])
|
||||
}
|
||||
|
||||
const DEFAULT_FAILED_SWAP_ERROR = 'Unexpected error. Please try again or contact support.'
|
||||
|
||||
// returns a function that will execute a swap, if the parameters are all valid
|
||||
// and the user has approved the slippage adjusted input amount for the trade
|
||||
export function useSwapCallback(
|
||||
@ -23,115 +113,95 @@ export function useSwapCallback(
|
||||
allowedSlippage: number = INITIAL_ALLOWED_SLIPPAGE, // in bips
|
||||
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
|
||||
): null | (() => Promise<string>) {
|
||||
): { 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
|
||||
|
||||
const tradeVersion = getTradeVersion(trade)
|
||||
const v1Exchange = useV1ExchangeContract(useV1TradeExchangeAddress(trade), true)
|
||||
|
||||
return useMemo(() => {
|
||||
if (!trade || !recipient || !library || !account || !tradeVersion || !chainId) return null
|
||||
|
||||
return async function onSwap() {
|
||||
const contract: Contract | null =
|
||||
tradeVersion === Version.v2 ? getRouterContract(chainId, library, account) : v1Exchange
|
||||
if (!contract) {
|
||||
throw new Error('Failed to get a swap contract')
|
||||
}
|
||||
|
||||
const swapMethods = []
|
||||
|
||||
switch (tradeVersion) {
|
||||
case Version.v2:
|
||||
swapMethods.push(
|
||||
Router.swapCallParameters(trade, {
|
||||
feeOnTransfer: false,
|
||||
allowedSlippage: new Percent(JSBI.BigInt(allowedSlippage), BIPS_BASE),
|
||||
recipient,
|
||||
ttl: deadline
|
||||
})
|
||||
)
|
||||
|
||||
if (trade.tradeType === TradeType.EXACT_INPUT) {
|
||||
swapMethods.push(
|
||||
Router.swapCallParameters(trade, {
|
||||
feeOnTransfer: true,
|
||||
allowedSlippage: new Percent(JSBI.BigInt(allowedSlippage), BIPS_BASE),
|
||||
recipient,
|
||||
ttl: deadline
|
||||
})
|
||||
)
|
||||
}
|
||||
break
|
||||
case Version.v1:
|
||||
swapMethods.push(
|
||||
v1SwapArguments(trade, {
|
||||
allowedSlippage: new Percent(JSBI.BigInt(allowedSlippage), BIPS_BASE),
|
||||
recipient,
|
||||
ttl: deadline
|
||||
})
|
||||
)
|
||||
break
|
||||
}
|
||||
|
||||
const safeGasEstimates: (BigNumber | undefined)[] = await Promise.all(
|
||||
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
|
||||
// from right to left
|
||||
for (let i = 0; i < safeGasEstimates.length - 1; i++) {
|
||||
// if the FoT method fails, but the regular method does not, we should not
|
||||
// use the regular method. this probably means something is wrong with the fot token.
|
||||
if (BigNumber.isBigNumber(safeGasEstimates[i]) && !BigNumber.isBigNumber(safeGasEstimates[i + 1])) {
|
||||
throw new Error(
|
||||
'An error occurred. Please try raising your slippage. If that does not work, contact support.'
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const indexOfSuccessfulEstimation = safeGasEstimates.findIndex(safeGasEstimate =>
|
||||
BigNumber.isBigNumber(safeGasEstimate)
|
||||
)
|
||||
|
||||
// all estimations failed...
|
||||
if (indexOfSuccessfulEstimation === -1) {
|
||||
// if only 1 method exists, either:
|
||||
// a) the token is doing something weird not related to FoT (e.g. enforcing a whitelist)
|
||||
// b) the token is FoT and the user specified an exact output, which is not allowed
|
||||
if (swapMethods.length === 1) {
|
||||
throw Error(
|
||||
`An error occurred. If either of the tokens you're swapping take a fee on transfer, you must specify an exact input amount.`
|
||||
)
|
||||
}
|
||||
// if 2 methods exists, either:
|
||||
// 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.')
|
||||
}
|
||||
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 {
|
||||
const { methodName, args, value } = swapMethods[indexOfSuccessfulEstimation]
|
||||
const safeGasEstimate = safeGasEstimates[indexOfSuccessfulEstimation]
|
||||
return { state: SwapCallbackState.LOADING, callback: null, error: null }
|
||||
}
|
||||
}
|
||||
|
||||
const tradeVersion = getTradeVersion(trade)
|
||||
|
||||
return {
|
||||
state: SwapCallbackState.VALID,
|
||||
callback: async function onSwap(): Promise<string> {
|
||||
const estimatedCalls: EstimatedSwapCall[] = await Promise.all(
|
||||
swapCalls.map(call => {
|
||||
const {
|
||||
parameters: { methodName, args, value },
|
||||
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
|
||||
}
|
||||
return { call, error: new Error(errorMessage) }
|
||||
})
|
||||
})
|
||||
})
|
||||
)
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
const {
|
||||
call: {
|
||||
contract,
|
||||
parameters: { methodName, args, value }
|
||||
},
|
||||
gasEstimate
|
||||
} = successfulEstimation
|
||||
|
||||
return contract[methodName](...args, {
|
||||
gasLimit: safeGasEstimate,
|
||||
...(value && !isZero(value) ? { value } : {})
|
||||
gasLimit: calculateGasMargin(gasEstimate),
|
||||
...(value && !isZero(value) ? { value, from: account } : { from: account })
|
||||
})
|
||||
.then((response: any) => {
|
||||
const inputSymbol = trade.inputAmount.currency.symbol
|
||||
@ -161,27 +231,15 @@ export function useSwapCallback(
|
||||
.catch((error: any) => {
|
||||
// if the user rejected the tx, pass this along
|
||||
if (error?.code === 4001) {
|
||||
throw error
|
||||
}
|
||||
// otherwise, the error was unexpected and we need to convey that
|
||||
else {
|
||||
throw new Error('Transaction rejected.')
|
||||
} else {
|
||||
// otherwise, the error was unexpected and we need to convey that
|
||||
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,
|
||||
recipient,
|
||||
library,
|
||||
account,
|
||||
tradeVersion,
|
||||
chainId,
|
||||
allowedSlippage,
|
||||
v1Exchange,
|
||||
deadline,
|
||||
recipientAddressOrName,
|
||||
addTransaction
|
||||
])
|
||||
}, [trade, library, account, chainId, recipient, recipientAddressOrName, swapCalls, addTransaction])
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ export default function useWrapCallback(
|
||||
inputCurrency: Currency | undefined,
|
||||
outputCurrency: Currency | 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 wethContract = useWETHContract()
|
||||
const balance = useCurrencyBalance(account ?? undefined, inputCurrency)
|
||||
@ -50,7 +50,7 @@ export default function useWrapCallback(
|
||||
}
|
||||
}
|
||||
: undefined,
|
||||
error: sufficientBalance ? undefined : 'Insufficient ETH balance'
|
||||
inputError: sufficientBalance ? undefined : 'Insufficient ETH balance'
|
||||
}
|
||||
} else if (currencyEquals(WETH[chainId], inputCurrency) && outputCurrency === ETHER) {
|
||||
return {
|
||||
@ -66,7 +66,7 @@ export default function useWrapCallback(
|
||||
}
|
||||
}
|
||||
: undefined,
|
||||
error: sufficientBalance ? undefined : 'Insufficient WETH balance'
|
||||
inputError: sufficientBalance ? undefined : 'Insufficient WETH balance'
|
||||
}
|
||||
} else {
|
||||
return NOT_APPLICABLE
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Currency, Fraction, Percent } from '@uniswap/sdk'
|
||||
import { Currency, Percent, Price } from '@uniswap/sdk'
|
||||
import React, { useContext } from 'react'
|
||||
import { Text } from 'rebass'
|
||||
import { ThemeContext } from 'styled-components'
|
||||
@ -8,7 +8,7 @@ import { ONE_BIPS } from '../../constants'
|
||||
import { Field } from '../../state/mint/actions'
|
||||
import { TYPE } from '../../theme'
|
||||
|
||||
export const PoolPriceBar = ({
|
||||
export function PoolPriceBar({
|
||||
currencies,
|
||||
noLiquidity,
|
||||
poolTokenPercentage,
|
||||
@ -17,20 +17,20 @@ export const PoolPriceBar = ({
|
||||
currencies: { [field in Field]?: Currency }
|
||||
noLiquidity?: boolean
|
||||
poolTokenPercentage?: Percent
|
||||
price?: Fraction
|
||||
}) => {
|
||||
price?: Price
|
||||
}) {
|
||||
const theme = useContext(ThemeContext)
|
||||
return (
|
||||
<AutoColumn gap="md">
|
||||
<AutoRow justify="space-around" gap="4px">
|
||||
<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}>
|
||||
{currencies[Field.CURRENCY_B]?.symbol} per {currencies[Field.CURRENCY_A]?.symbol}
|
||||
</Text>
|
||||
</AutoColumn>
|
||||
<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}>
|
||||
{currencies[Field.CURRENCY_A]?.symbol} per {currencies[Field.CURRENCY_B]?.symbol}
|
||||
</Text>
|
||||
|
@ -10,7 +10,7 @@ import { ThemeContext } from 'styled-components'
|
||||
import { ButtonError, ButtonLight, ButtonPrimary } from '../../components/Button'
|
||||
import { BlueCard, GreyCard, LightCard } from '../../components/Card'
|
||||
import { AutoColumn, ColumnCenter } from '../../components/Column'
|
||||
import ConfirmationModal from '../../components/ConfirmationModal'
|
||||
import TransactionConfirmationModal, { ConfirmationModalContent } from '../../components/TransactionConfirmationModal'
|
||||
import CurrencyInputPanel from '../../components/CurrencyInputPanel'
|
||||
import DoubleCurrencyLogo from '../../components/DoubleLogo'
|
||||
import { AddRemoveTabs } from '../../components/NavigationTabs'
|
||||
@ -294,27 +294,34 @@ export default function AddLiquidity({
|
||||
[currencyIdA, history, currencyIdB]
|
||||
)
|
||||
|
||||
const handleDismissConfirmation = useCallback(() => {
|
||||
setShowConfirm(false)
|
||||
// if there was a tx hash, we want to clear the input
|
||||
if (txHash) {
|
||||
onFieldAInput('')
|
||||
}
|
||||
setTxHash('')
|
||||
}, [onFieldAInput, txHash])
|
||||
|
||||
return (
|
||||
<>
|
||||
<AppBody>
|
||||
<AddRemoveTabs adding={true} />
|
||||
<Wrapper>
|
||||
<ConfirmationModal
|
||||
<TransactionConfirmationModal
|
||||
isOpen={showConfirm}
|
||||
onDismiss={() => {
|
||||
setShowConfirm(false)
|
||||
// if there was a tx hash, we want to clear the input
|
||||
if (txHash) {
|
||||
onFieldAInput('')
|
||||
}
|
||||
setTxHash('')
|
||||
}}
|
||||
onDismiss={handleDismissConfirmation}
|
||||
attemptingTxn={attemptingTxn}
|
||||
hash={txHash}
|
||||
topContent={modalHeader}
|
||||
bottomContent={modalBottom}
|
||||
content={() => (
|
||||
<ConfirmationModalContent
|
||||
title={noLiquidity ? 'You are creating a pool' : 'You will receive'}
|
||||
onDismiss={handleDismissConfirmation}
|
||||
topContent={modalHeader}
|
||||
bottomContent={modalBottom}
|
||||
/>
|
||||
)}
|
||||
pendingText={pendingText}
|
||||
title={noLiquidity ? 'You are creating a pool' : 'You will receive'}
|
||||
/>
|
||||
<AutoColumn gap="20px">
|
||||
{noLiquidity && (
|
||||
|
@ -11,7 +11,7 @@ import { ThemeContext } from 'styled-components'
|
||||
import { ButtonPrimary, ButtonLight, ButtonError, ButtonConfirmed } from '../../components/Button'
|
||||
import { LightCard } from '../../components/Card'
|
||||
import { AutoColumn, ColumnCenter } from '../../components/Column'
|
||||
import ConfirmationModal from '../../components/ConfirmationModal'
|
||||
import TransactionConfirmationModal, { ConfirmationModalContent } from '../../components/TransactionConfirmationModal'
|
||||
import CurrencyInputPanel from '../../components/CurrencyInputPanel'
|
||||
import DoubleCurrencyLogo from '../../components/DoubleLogo'
|
||||
import { AddRemoveTabs } from '../../components/NavigationTabs'
|
||||
@ -274,12 +274,13 @@ export default function RemoveLiquidity({
|
||||
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 =>
|
||||
router.estimateGas[methodName](...args)
|
||||
.then(calculateGasMargin)
|
||||
.catch(error => {
|
||||
console.error(`estimateGas failed for ${methodName}`, error)
|
||||
console.error(`estimateGas failed`, methodName, args, error)
|
||||
return undefined
|
||||
})
|
||||
)
|
||||
)
|
||||
@ -447,28 +448,35 @@ export default function RemoveLiquidity({
|
||||
[currencyIdA, currencyIdB, history]
|
||||
)
|
||||
|
||||
const handleDismissConfirmation = useCallback(() => {
|
||||
setShowConfirm(false)
|
||||
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 (txHash) {
|
||||
onUserInput(Field.LIQUIDITY_PERCENT, '0')
|
||||
}
|
||||
setTxHash('')
|
||||
}, [onUserInput, txHash])
|
||||
|
||||
return (
|
||||
<>
|
||||
<AppBody>
|
||||
<AddRemoveTabs adding={false} />
|
||||
<Wrapper>
|
||||
<ConfirmationModal
|
||||
<TransactionConfirmationModal
|
||||
isOpen={showConfirm}
|
||||
onDismiss={() => {
|
||||
setShowConfirm(false)
|
||||
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 (txHash) {
|
||||
onUserInput(Field.LIQUIDITY_PERCENT, '0')
|
||||
}
|
||||
setTxHash('')
|
||||
}}
|
||||
onDismiss={handleDismissConfirmation}
|
||||
attemptingTxn={attemptingTxn}
|
||||
hash={txHash ? txHash : ''}
|
||||
topContent={modalHeader}
|
||||
bottomContent={modalBottom}
|
||||
content={() => (
|
||||
<ConfirmationModalContent
|
||||
title={'You will receive'}
|
||||
onDismiss={handleDismissConfirmation}
|
||||
topContent={modalHeader}
|
||||
bottomContent={modalBottom}
|
||||
/>
|
||||
)}
|
||||
pendingText={pendingText}
|
||||
title="You will receive"
|
||||
/>
|
||||
<AutoColumn gap="md">
|
||||
<LightCard>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { CurrencyAmount, JSBI } from '@uniswap/sdk'
|
||||
import { CurrencyAmount, JSBI, Trade } from '@uniswap/sdk'
|
||||
import React, { useCallback, useContext, useEffect, useState } from 'react'
|
||||
import { ArrowDown } from 'react-feather'
|
||||
import ReactGA from 'react-ga'
|
||||
@ -8,16 +8,14 @@ import AddressInputPanel from '../../components/AddressInputPanel'
|
||||
import { ButtonError, ButtonLight, ButtonPrimary } from '../../components/Button'
|
||||
import Card, { GreyCard } from '../../components/Card'
|
||||
import { AutoColumn } from '../../components/Column'
|
||||
import ConfirmationModal from '../../components/ConfirmationModal'
|
||||
import ConfirmSwapModal from '../../components/swap/ConfirmSwapModal'
|
||||
import CurrencyInputPanel from '../../components/CurrencyInputPanel'
|
||||
import { SwapPoolTabs } from '../../components/NavigationTabs'
|
||||
import { AutoRow, RowBetween } from '../../components/Row'
|
||||
import AdvancedSwapDetailsDropdown from '../../components/swap/AdvancedSwapDetailsDropdown'
|
||||
import BetterTradeLink from '../../components/swap/BetterTradeLink'
|
||||
import confirmPriceImpactWithoutFee from '../../components/swap/confirmPriceImpactWithoutFee'
|
||||
import { ArrowWrapper, BottomGrouping, Dots, Wrapper } from '../../components/swap/styleds'
|
||||
import SwapModalFooter from '../../components/swap/SwapModalFooter'
|
||||
import SwapModalHeader from '../../components/swap/SwapModalHeader'
|
||||
import { ArrowWrapper, BottomGrouping, Dots, SwapCallbackError, Wrapper } from '../../components/swap/styleds'
|
||||
import TradePrice from '../../components/swap/TradePrice'
|
||||
import { TokenWarningCards } from '../../components/TokenWarningCard'
|
||||
|
||||
@ -39,13 +37,13 @@ import {
|
||||
} from '../../state/swap/hooks'
|
||||
import {
|
||||
useExpertModeManager,
|
||||
useTokenWarningDismissal,
|
||||
useUserDeadline,
|
||||
useUserSlippageTolerance,
|
||||
useTokenWarningDismissal
|
||||
useUserSlippageTolerance
|
||||
} from '../../state/user/hooks'
|
||||
import { CursorPointer, LinkStyledButton, TYPE } from '../../theme'
|
||||
import { maxAmountSpend } from '../../utils/maxAmountSpend'
|
||||
import { computeSlippageAdjustedAmounts, computeTradePriceBreakdown, warningSeverity } from '../../utils/prices'
|
||||
import { computeTradePriceBreakdown, warningSeverity } from '../../utils/prices'
|
||||
import AppBody from '../AppBody'
|
||||
import { ClickableText } from '../Pool/styleds'
|
||||
|
||||
@ -60,7 +58,7 @@ export default function Swap() {
|
||||
|
||||
// for expert mode
|
||||
const toggleSettings = useToggleSettingsMenu()
|
||||
const [expertMode] = useExpertModeManager()
|
||||
const [isExpertMode] = useExpertModeManager()
|
||||
|
||||
// get custom setting values for user
|
||||
const [deadline] = useUserDeadline()
|
||||
@ -68,8 +66,15 @@ export default function Swap() {
|
||||
|
||||
// swap state
|
||||
const { independentField, typedValue, recipient } = useSwapState()
|
||||
const { v1Trade, v2Trade, currencyBalances, parsedAmount, currencies, error } = useDerivedSwapInfo()
|
||||
const { wrapType, execute: onWrap, error: wrapError } = useWrapCallback(
|
||||
const {
|
||||
v1Trade,
|
||||
v2Trade,
|
||||
currencyBalances,
|
||||
parsedAmount,
|
||||
currencies,
|
||||
inputError: swapInputError
|
||||
} = useDerivedSwapInfo()
|
||||
const { wrapType, execute: onWrap, inputError: wrapInputError } = useWrapCallback(
|
||||
currencies[Field.INPUT],
|
||||
currencies[Field.OUTPUT],
|
||||
typedValue
|
||||
@ -102,7 +107,7 @@ export default function Swap() {
|
||||
}
|
||||
|
||||
const { onSwitchTokens, onCurrencySelection, onUserInput, onChangeRecipient } = useSwapActionHandlers()
|
||||
const isValid = !error
|
||||
const isValid = !swapInputError
|
||||
const dependentField: Field = independentField === Field.INPUT ? Field.OUTPUT : Field.INPUT
|
||||
|
||||
const handleTypeInput = useCallback(
|
||||
@ -119,9 +124,19 @@ export default function Swap() {
|
||||
)
|
||||
|
||||
// modal and loading
|
||||
const [showConfirm, setShowConfirm] = useState<boolean>(false) // show confirmation modal
|
||||
const [attemptingTxn, setAttemptingTxn] = useState<boolean>(false) // waiting for user confirmaion/rejection
|
||||
const [txHash, setTxHash] = useState<string>('')
|
||||
const [{ showConfirm, tradeToConfirm, swapErrorMessage, attemptingTxn, txHash }, setSwapState] = useState<{
|
||||
showConfirm: boolean
|
||||
tradeToConfirm: Trade | undefined
|
||||
attemptingTxn: boolean
|
||||
swapErrorMessage: string | undefined
|
||||
txHash: string | undefined
|
||||
}>({
|
||||
showConfirm: false,
|
||||
tradeToConfirm: undefined,
|
||||
attemptingTxn: false,
|
||||
swapErrorMessage: undefined,
|
||||
txHash: undefined
|
||||
})
|
||||
|
||||
const formattedAmounts = {
|
||||
[independentField]: typedValue,
|
||||
@ -152,25 +167,27 @@ export default function Swap() {
|
||||
const maxAmountInput: CurrencyAmount | undefined = maxAmountSpend(currencyBalances[Field.INPUT])
|
||||
const atMaxAmountInput = Boolean(maxAmountInput && parsedAmounts[Field.INPUT]?.equalTo(maxAmountInput))
|
||||
|
||||
const slippageAdjustedAmounts = computeSlippageAdjustedAmounts(trade, allowedSlippage)
|
||||
|
||||
// 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)) {
|
||||
return
|
||||
}
|
||||
if (!swapCallback) {
|
||||
return
|
||||
}
|
||||
setAttemptingTxn(true)
|
||||
setSwapState({ attemptingTxn: true, tradeToConfirm, showConfirm, swapErrorMessage: undefined, txHash: undefined })
|
||||
swapCallback()
|
||||
.then(hash => {
|
||||
setAttemptingTxn(false)
|
||||
setTxHash(hash)
|
||||
setSwapState({ attemptingTxn: false, tradeToConfirm, showConfirm, swapErrorMessage: undefined, txHash: hash })
|
||||
|
||||
ReactGA.event({
|
||||
category: 'Swap',
|
||||
@ -188,13 +205,15 @@ export default function Swap() {
|
||||
})
|
||||
})
|
||||
.catch(error => {
|
||||
setAttemptingTxn(false)
|
||||
// we only care if the error is something _other_ than the user rejected the tx
|
||||
if (error?.code !== 4001) {
|
||||
console.error(error)
|
||||
}
|
||||
setSwapState({
|
||||
attemptingTxn: false,
|
||||
tradeToConfirm,
|
||||
showConfirm,
|
||||
swapErrorMessage: error.message,
|
||||
txHash: undefined
|
||||
})
|
||||
})
|
||||
}
|
||||
}, [tradeToConfirm, account, priceImpactWithoutFee, recipient, recipientAddress, showConfirm, swapCallback, trade])
|
||||
|
||||
// errors
|
||||
const [showInverted, setShowInverted] = useState<boolean>(false)
|
||||
@ -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
|
||||
// never show if price impact is above threshold in non expert mode
|
||||
const showApproveFlow =
|
||||
!error &&
|
||||
!swapInputError &&
|
||||
(approval === ApprovalState.NOT_APPROVED ||
|
||||
approval === ApprovalState.PENDING ||
|
||||
(approvalSubmitted && approval === ApprovalState.APPROVED)) &&
|
||||
!(priceImpactSeverity > 3 && !expertMode)
|
||||
|
||||
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}`
|
||||
!(priceImpactSeverity > 3 && !isExpertMode)
|
||||
|
||||
const [dismissedToken0] = useTokenWarningDismissal(chainId, currencies[Field.INPUT])
|
||||
const [dismissedToken1] = useTokenWarningDismissal(chainId, currencies[Field.OUTPUT])
|
||||
const showWarning =
|
||||
(!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 (
|
||||
<>
|
||||
{showWarning && <TokenWarningCards currencies={currencies} />}
|
||||
<AppBody disabled={!!showWarning}>
|
||||
<AppBody disabled={showWarning}>
|
||||
<SwapPoolTabs active={'swap'} />
|
||||
<Wrapper id="swap-page">
|
||||
<ConfirmationModal
|
||||
<ConfirmSwapModal
|
||||
isOpen={showConfirm}
|
||||
title="Confirm Swap"
|
||||
onDismiss={() => {
|
||||
setShowConfirm(false)
|
||||
// if there was a tx hash, we want to clear the input
|
||||
if (txHash) {
|
||||
onUserInput(Field.INPUT, '')
|
||||
}
|
||||
setTxHash('')
|
||||
}}
|
||||
trade={trade}
|
||||
originalTrade={tradeToConfirm}
|
||||
onAcceptChanges={handleAcceptChanges}
|
||||
attemptingTxn={attemptingTxn}
|
||||
hash={txHash}
|
||||
topContent={modalHeader}
|
||||
bottomContent={modalBottom}
|
||||
pendingText={pendingText}
|
||||
txHash={txHash}
|
||||
recipient={recipient}
|
||||
allowedSlippage={allowedSlippage}
|
||||
onConfirm={handleSwap}
|
||||
swapErrorMessage={swapErrorMessage}
|
||||
onDismiss={handleConfirmDismiss}
|
||||
/>
|
||||
|
||||
<AutoColumn gap={'md'}>
|
||||
@ -373,8 +365,9 @@ export default function Swap() {
|
||||
{!account ? (
|
||||
<ButtonLight onClick={toggleWalletModal}>Connect Wallet</ButtonLight>
|
||||
) : showWrap ? (
|
||||
<ButtonPrimary disabled={Boolean(wrapError)} onClick={onWrap}>
|
||||
{wrapError ?? (wrapType === WrapType.WRAP ? 'Wrap' : wrapType === WrapType.UNWRAP ? 'Unwrap' : null)}
|
||||
<ButtonPrimary disabled={Boolean(wrapInputError)} onClick={onWrap}>
|
||||
{wrapInputError ??
|
||||
(wrapType === WrapType.WRAP ? 'Wrap' : wrapType === WrapType.UNWRAP ? 'Unwrap' : null)}
|
||||
</ButtonPrimary>
|
||||
) : noRoute && userHasSpecifiedInputOutput ? (
|
||||
<GreyCard style={{ textAlign: 'center' }}>
|
||||
@ -398,15 +391,27 @@ export default function Swap() {
|
||||
</ButtonPrimary>
|
||||
<ButtonError
|
||||
onClick={() => {
|
||||
expertMode ? onSwap() : setShowConfirm(true)
|
||||
if (isExpertMode) {
|
||||
handleSwap()
|
||||
} else {
|
||||
setSwapState({
|
||||
tradeToConfirm: trade,
|
||||
attemptingTxn: false,
|
||||
swapErrorMessage: undefined,
|
||||
showConfirm: true,
|
||||
txHash: undefined
|
||||
})
|
||||
}
|
||||
}}
|
||||
width="48%"
|
||||
id="swap-button"
|
||||
disabled={!isValid || approval !== ApprovalState.APPROVED || (priceImpactSeverity > 3 && !expertMode)}
|
||||
disabled={
|
||||
!isValid || approval !== ApprovalState.APPROVED || (priceImpactSeverity > 3 && !isExpertMode)
|
||||
}
|
||||
error={isValid && priceImpactSeverity > 2}
|
||||
>
|
||||
<Text fontSize={16} fontWeight={500}>
|
||||
{priceImpactSeverity > 3 && !expertMode
|
||||
{priceImpactSeverity > 3 && !isExpertMode
|
||||
? `Price Impact High`
|
||||
: `Swap${priceImpactSeverity > 2 ? ' Anyway' : ''}`}
|
||||
</Text>
|
||||
@ -415,21 +420,32 @@ export default function Swap() {
|
||||
) : (
|
||||
<ButtonError
|
||||
onClick={() => {
|
||||
expertMode ? onSwap() : setShowConfirm(true)
|
||||
if (isExpertMode) {
|
||||
handleSwap()
|
||||
} else {
|
||||
setSwapState({
|
||||
tradeToConfirm: trade,
|
||||
attemptingTxn: false,
|
||||
swapErrorMessage: undefined,
|
||||
showConfirm: true,
|
||||
txHash: undefined
|
||||
})
|
||||
}
|
||||
}}
|
||||
id="swap-button"
|
||||
disabled={!isValid || (priceImpactSeverity > 3 && !expertMode)}
|
||||
error={isValid && priceImpactSeverity > 2}
|
||||
disabled={!isValid || (priceImpactSeverity > 3 && !isExpertMode) || !!swapCallbackError}
|
||||
error={isValid && priceImpactSeverity > 2 && !swapCallbackError}
|
||||
>
|
||||
<Text fontSize={20} fontWeight={500}>
|
||||
{error
|
||||
? error
|
||||
: priceImpactSeverity > 3 && !expertMode
|
||||
{swapInputError
|
||||
? swapInputError
|
||||
: priceImpactSeverity > 3 && !isExpertMode
|
||||
? `Price Impact Too High`
|
||||
: `Swap${priceImpactSeverity > 2 ? ' Anyway' : ''}`}
|
||||
</Text>
|
||||
</ButtonError>
|
||||
)}
|
||||
{isExpertMode && swapErrorMessage ? <SwapCallbackError error={swapErrorMessage} /> : null}
|
||||
{betterTradeLinkVersion && <BetterTradeLink version={betterTradeLinkVersion} />}
|
||||
</BottomGrouping>
|
||||
</Wrapper>
|
||||
|
@ -50,9 +50,10 @@ export function useDerivedMintInfo(
|
||||
|
||||
// pair
|
||||
const [pairState, pair] = usePair(currencies[Field.CURRENCY_A], currencies[Field.CURRENCY_B])
|
||||
const totalSupply = useTotalSupply(pair?.liquidityToken)
|
||||
|
||||
const noLiquidity: boolean =
|
||||
pairState === PairState.NOT_EXISTS ||
|
||||
Boolean(pair && JSBI.equal(pair.reserve0.raw, ZERO) && JSBI.equal(pair.reserve1.raw, ZERO))
|
||||
pairState === PairState.NOT_EXISTS || Boolean(totalSupply && JSBI.equal(totalSupply.raw, ZERO))
|
||||
|
||||
// balances
|
||||
const balances = useCurrencyBalances(account ?? undefined, [
|
||||
@ -94,16 +95,20 @@ export function useDerivedMintInfo(
|
||||
[Field.CURRENCY_B]: independentField === Field.CURRENCY_A ? dependentAmount : independentAmount
|
||||
}
|
||||
|
||||
const token0Price = pair?.token0Price
|
||||
const price = useMemo(() => {
|
||||
const { [Field.CURRENCY_A]: currencyAAmount, [Field.CURRENCY_B]: currencyBAmount } = parsedAmounts
|
||||
if (currencyAAmount && currencyBAmount) {
|
||||
return new Price(currencyAAmount.currency, currencyBAmount.currency, currencyAAmount.raw, currencyBAmount.raw)
|
||||
if (noLiquidity) {
|
||||
const { [Field.CURRENCY_A]: currencyAAmount, [Field.CURRENCY_B]: currencyBAmount } = parsedAmounts
|
||||
if (currencyAAmount && currencyBAmount) {
|
||||
return new Price(currencyAAmount.currency, currencyBAmount.currency, currencyAAmount.raw, currencyBAmount.raw)
|
||||
}
|
||||
return
|
||||
} else {
|
||||
return token0Price
|
||||
}
|
||||
return
|
||||
}, [parsedAmounts])
|
||||
}, [noLiquidity, token0Price, parsedAmounts])
|
||||
|
||||
// liquidity minted
|
||||
const totalSupply = useTotalSupply(pair?.liquidityToken)
|
||||
const liquidityMinted = useMemo(() => {
|
||||
const { [Field.CURRENCY_A]: currencyAAmount, [Field.CURRENCY_B]: currencyBAmount } = parsedAmounts
|
||||
const [tokenAmountA, tokenAmountB] = [
|
||||
|
@ -1,11 +1,11 @@
|
||||
import { Contract } from '@ethersproject/contracts'
|
||||
import { useEffect, useMemo } from 'react'
|
||||
import { useEffect, useMemo, useRef } from 'react'
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
import { useActiveWeb3React } from '../../hooks'
|
||||
import { useMulticallContract } from '../../hooks/useContract'
|
||||
import useDebounce from '../../hooks/useDebounce'
|
||||
import chunkArray from '../../utils/chunkArray'
|
||||
import { retry } from '../../utils/retry'
|
||||
import { CancelledError, retry, RetryableError } from '../../utils/retry'
|
||||
import { useBlockNumber } from '../application/hooks'
|
||||
import { AppDispatch, AppState } from '../index'
|
||||
import {
|
||||
@ -30,11 +30,17 @@ async function fetchChunk(
|
||||
chunk: Call[],
|
||||
minBlockNumber: number
|
||||
): Promise<{ results: string[]; blockNumber: number }> {
|
||||
const [resultsBlockNumber, returnData] = await multicallContract.aggregate(
|
||||
chunk.map(obj => [obj.address, obj.callData])
|
||||
)
|
||||
console.debug('Fetching chunk', multicallContract, chunk, minBlockNumber)
|
||||
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) {
|
||||
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() }
|
||||
}
|
||||
@ -112,6 +118,7 @@ export default function Updater() {
|
||||
const latestBlockNumber = useBlockNumber()
|
||||
const { chainId } = useActiveWeb3React()
|
||||
const multicallContract = useMulticallContract()
|
||||
const cancellations = useRef<{ blockNumber: number; cancellations: (() => void)[] }>()
|
||||
|
||||
const listeningKeys: { [callKey: string]: number } = useMemo(() => {
|
||||
return activeListeningKeys(debouncedListeners, chainId)
|
||||
@ -134,6 +141,10 @@ export default function Updater() {
|
||||
|
||||
const chunkedCalls = chunkArray(calls, CALL_CHUNK_SIZE)
|
||||
|
||||
if (cancellations.current?.blockNumber !== latestBlockNumber) {
|
||||
cancellations.current?.cancellations?.forEach(c => c())
|
||||
}
|
||||
|
||||
dispatch(
|
||||
fetchingMulticallResults({
|
||||
calls,
|
||||
@ -142,38 +153,52 @@ export default function Updater() {
|
||||
})
|
||||
)
|
||||
|
||||
chunkedCalls.forEach((chunk, index) =>
|
||||
// todo: cancel retries when the block number updates
|
||||
retry(() => fetchChunk(multicallContract, chunk, latestBlockNumber), { n: 10, minWait: 2500, maxWait: 5000 })
|
||||
.then(({ results: returnData, blockNumber: fetchBlockNumber }) => {
|
||||
// accumulates the length of all previous indices
|
||||
const firstCallKeyIndex = chunkedCalls.slice(0, index).reduce<number>((memo, curr) => memo + curr.length, 0)
|
||||
const lastCallKeyIndex = firstCallKeyIndex + returnData.length
|
||||
cancellations.current = {
|
||||
blockNumber: latestBlockNumber,
|
||||
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 }) => {
|
||||
cancellations.current = { cancellations: [], blockNumber: latestBlockNumber }
|
||||
|
||||
dispatch(
|
||||
updateMulticallResults({
|
||||
chainId,
|
||||
results: outdatedCallKeys
|
||||
.slice(firstCallKeyIndex, lastCallKeyIndex)
|
||||
.reduce<{ [callKey: string]: string | null }>((memo, callKey, i) => {
|
||||
memo[callKey] = returnData[i] ?? null
|
||||
return memo
|
||||
}, {}),
|
||||
blockNumber: fetchBlockNumber
|
||||
})
|
||||
)
|
||||
})
|
||||
.catch((error: any) => {
|
||||
console.error('Failed to fetch multicall chunk', chunk, chainId, error)
|
||||
dispatch(
|
||||
errorFetchingMulticallResults({
|
||||
calls: chunk,
|
||||
chainId,
|
||||
fetchingBlockNumber: latestBlockNumber
|
||||
})
|
||||
)
|
||||
})
|
||||
)
|
||||
// accumulates the length of all previous indices
|
||||
const firstCallKeyIndex = chunkedCalls.slice(0, index).reduce<number>((memo, curr) => memo + curr.length, 0)
|
||||
const lastCallKeyIndex = firstCallKeyIndex + returnData.length
|
||||
|
||||
dispatch(
|
||||
updateMulticallResults({
|
||||
chainId,
|
||||
results: outdatedCallKeys
|
||||
.slice(firstCallKeyIndex, lastCallKeyIndex)
|
||||
.reduce<{ [callKey: string]: string | null }>((memo, callKey, i) => {
|
||||
memo[callKey] = returnData[i] ?? null
|
||||
return memo
|
||||
}, {}),
|
||||
blockNumber: fetchBlockNumber
|
||||
})
|
||||
)
|
||||
})
|
||||
.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)
|
||||
dispatch(
|
||||
errorFetchingMulticallResults({
|
||||
calls: chunk,
|
||||
chainId,
|
||||
fetchingBlockNumber: latestBlockNumber
|
||||
})
|
||||
)
|
||||
})
|
||||
return cancel
|
||||
})
|
||||
}
|
||||
}, [chainId, multicallContract, dispatch, serializedOutdatedCallKeys, latestBlockNumber])
|
||||
|
||||
return null
|
||||
|
@ -94,7 +94,7 @@ export function useDerivedSwapInfo(): {
|
||||
currencyBalances: { [field in Field]?: CurrencyAmount }
|
||||
parsedAmount: CurrencyAmount | undefined
|
||||
v2Trade: Trade | undefined
|
||||
error?: string
|
||||
inputError?: string
|
||||
v1Trade: Trade | undefined
|
||||
} {
|
||||
const { account } = useActiveWeb3React()
|
||||
@ -140,21 +140,21 @@ export function useDerivedSwapInfo(): {
|
||||
// get link to trade on v1, if a better rate exists
|
||||
const v1Trade = useV1Trade(isExactIn, currencies[Field.INPUT], currencies[Field.OUTPUT], parsedAmount)
|
||||
|
||||
let error: string | undefined
|
||||
let inputError: string | undefined
|
||||
if (!account) {
|
||||
error = 'Connect Wallet'
|
||||
inputError = 'Connect Wallet'
|
||||
}
|
||||
|
||||
if (!parsedAmount) {
|
||||
error = error ?? 'Enter an amount'
|
||||
inputError = inputError ?? 'Enter an amount'
|
||||
}
|
||||
|
||||
if (!currencies[Field.INPUT] || !currencies[Field.OUTPUT]) {
|
||||
error = error ?? 'Select a token'
|
||||
inputError = inputError ?? 'Select a token'
|
||||
}
|
||||
|
||||
if (!to) {
|
||||
error = error ?? 'Enter a recipient'
|
||||
inputError = inputError ?? 'Enter a recipient'
|
||||
}
|
||||
|
||||
const [allowedSlippage] = useUserSlippageTolerance()
|
||||
@ -177,7 +177,7 @@ export function useDerivedSwapInfo(): {
|
||||
]
|
||||
|
||||
if (balanceIn && amountIn && balanceIn.lessThan(amountIn)) {
|
||||
error = 'Insufficient ' + amountIn.currency.symbol + ' balance'
|
||||
inputError = 'Insufficient ' + amountIn.currency.symbol + ' balance'
|
||||
}
|
||||
|
||||
return {
|
||||
@ -185,7 +185,7 @@ export function useDerivedSwapInfo(): {
|
||||
currencyBalances,
|
||||
parsedAmount,
|
||||
v2Trade: v2Trade ?? undefined,
|
||||
error,
|
||||
inputError,
|
||||
v1Trade
|
||||
}
|
||||
}
|
||||
|
@ -55,7 +55,7 @@ export function colors(darkMode: boolean): Colors {
|
||||
bg5: darkMode ? '#565A69' : '#888D9B',
|
||||
|
||||
//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)',
|
||||
|
||||
//primary colors
|
||||
|
@ -91,7 +91,7 @@ export function getContract(address: string, ABI: any, library: Web3Provider, ac
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
|
7
src/utils/isZero.ts
Normal file
7
src/utils/isZero.ts
Normal file
@ -0,0 +1,7 @@
|
||||
/**
|
||||
* Returns true if the string value is zero in hex
|
||||
* @param hexNumberString
|
||||
*/
|
||||
export default function isZero(hexNumberString: string) {
|
||||
return /^0x0*$/.test(hexNumberString)
|
||||
}
|
@ -1,26 +1,45 @@
|
||||
import { retry } from './retry'
|
||||
import { retry, RetryableError } from './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 () => {
|
||||
if (fails > 0) {
|
||||
fails--
|
||||
throw new Error('failure')
|
||||
throw retryable ? new RetryableError('failure') : new Error('bad failure')
|
||||
}
|
||||
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 () => {
|
||||
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 () => {
|
||||
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 () => {
|
||||
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) {
|
||||
@ -36,7 +55,7 @@ describe('retry', () => {
|
||||
for (let i = 0; i < 10; i++) {
|
||||
promises.push(
|
||||
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,
|
||||
305
|
||||
)
|
||||
|
@ -6,6 +6,20 @@ function waitRandom(min: number, max: number): Promise<void> {
|
||||
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
|
||||
* @param fn function to retry
|
||||
@ -13,13 +27,43 @@ function waitRandom(min: number, max: number): Promise<void> {
|
||||
* @param minWait min wait between retries in ms
|
||||
* @param maxWait max wait between retries in ms
|
||||
*/
|
||||
// todo: support cancelling the retry
|
||||
export function retry<T>(
|
||||
fn: () => Promise<T>,
|
||||
{ n = 3, minWait = 500, maxWait = 1000 }: { n?: number; minWait?: number; maxWait?: number } = {}
|
||||
): Promise<T> {
|
||||
return fn().catch(error => {
|
||||
if (n === 0) throw error
|
||||
return waitRandom(minWait, maxWait).then(() => retry(fn, { n: n - 1, minWait, maxWait }))
|
||||
{ n, minWait, maxWait }: { n: number; minWait: number; maxWait: number }
|
||||
): { promise: Promise<T>; cancel: () => void } {
|
||||
let completed = false
|
||||
let rejectCancelled: (error: Error) => void
|
||||
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())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
320
yarn.lock
320
yarn.lock
@ -1296,7 +1296,7 @@
|
||||
bech32 "^1.1.3"
|
||||
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"
|
||||
resolved "https://registry.yarnpkg.com/@ethersproject/abi/-/abi-5.0.2.tgz#7fe8f080aa1483fe32cd27bb5b8f2019266af1e2"
|
||||
integrity sha512-Z+5f7xOgtRLu/W2l9Ry5xF7ehh9QVQ0m1vhynmTcS7DMfHgqTd1/PDFC62aw91ZPRCRZsYdZJu8ymokC5e1JSw==
|
||||
@ -1311,7 +1311,7 @@
|
||||
"@ethersproject/properties" "^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"
|
||||
resolved "https://registry.yarnpkg.com/@ethersproject/abstract-provider/-/abstract-provider-5.0.2.tgz#9b4e8f4870f0691463e8d5b092c95dd5275c635d"
|
||||
integrity sha512-U1s60+nG02x8FKNMoVNI6MG8SguWCoG9HJtwOqWZ38LBRMsDV4c0w4izKx98kcsN3wXw4U2/YAyJ9LlH7+/hkg==
|
||||
@ -1324,7 +1324,7 @@
|
||||
"@ethersproject/transactions" "^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"
|
||||
resolved "https://registry.yarnpkg.com/@ethersproject/abstract-signer/-/abstract-signer-5.0.2.tgz#5776f888fda816de1d08ddb0e74778ecb9590f69"
|
||||
integrity sha512-CzzXbeqKlgayE4YTnvvreGBG3n+HxakGXrxaGM6LjBZnOOIVSYi6HMFG8ZXls7UspRY4hvMrtnKEJKDCOngSBw==
|
||||
@ -1335,19 +1335,7 @@
|
||||
"@ethersproject/logger" "^5.0.0"
|
||||
"@ethersproject/properties" "^5.0.0"
|
||||
|
||||
"@ethersproject/address@5.0.0-beta.134":
|
||||
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":
|
||||
"@ethersproject/address@^5.0.0":
|
||||
version "5.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@ethersproject/address/-/address-5.0.2.tgz#80d0ddfb7d4bd0d32657747fa4bdd2defef2e00a"
|
||||
integrity sha512-+rz26RKj7ujGfQynys4V9VJRbR+wpC6eL8F22q3raWMH3152Ha31GwJPWzxE/bEA+43M/zTNVwY0R53gn53L2Q==
|
||||
@ -1374,17 +1362,7 @@
|
||||
"@ethersproject/bytes" "^5.0.0"
|
||||
"@ethersproject/properties" "^5.0.0"
|
||||
|
||||
"@ethersproject/bignumber@5.0.0-beta.138":
|
||||
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":
|
||||
"@ethersproject/bignumber@^5.0.0":
|
||||
version "5.0.5"
|
||||
resolved "https://registry.yarnpkg.com/@ethersproject/bignumber/-/bignumber-5.0.5.tgz#31bd7e75aad46ace345fae69b1f5bb120906af1b"
|
||||
integrity sha512-24ln7PV0g8ZzjcVZiLW9Wod0i+XCmK6zKkAaxw5enraTIT1p7gVOcSXFSzNQ9WYAwtiFQPvvA+TIO2oEITZNJA==
|
||||
@ -1393,43 +1371,20 @@
|
||||
"@ethersproject/logger" "^5.0.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"
|
||||
resolved "https://registry.yarnpkg.com/@ethersproject/bytes/-/bytes-5.0.3.tgz#b3769963ae0188a35713d343890a903bda20af9c"
|
||||
integrity sha512-AyPMAlY+Amaw4Zfp8OAivm1xYPI8mqiUYmEnSUk1CnS2NrQGHEMmFJFiOJdS3gDDpgSOFhWIjZwxKq2VZpqNTA==
|
||||
dependencies:
|
||||
"@ethersproject/logger" "^5.0.0"
|
||||
|
||||
"@ethersproject/constants@5.0.0-beta.133":
|
||||
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":
|
||||
"@ethersproject/constants@^5.0.0":
|
||||
version "5.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@ethersproject/constants/-/constants-5.0.2.tgz#f7ac0b320e2bbec1a5950da075015f8bc4e8fed1"
|
||||
integrity sha512-nNoVlNP6bgpog7pQ2EyD1xjlaXcy1Cl4kK5v1KoskHj58EtB6TK8M8AFGi3GgHTdMldfT4eN3OsoQ/CdOTVNFA==
|
||||
dependencies:
|
||||
"@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":
|
||||
version "5.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@ethersproject/contracts/-/contracts-5.0.2.tgz#f19ed8335ceeb6abb60f5d45641f0a2a62b6fbc5"
|
||||
@ -1445,17 +1400,17 @@
|
||||
"@ethersproject/logger" "^5.0.0"
|
||||
"@ethersproject/properties" "^5.0.0"
|
||||
|
||||
"@ethersproject/experimental@5.0.0-beta.141":
|
||||
version "5.0.0-beta.141"
|
||||
resolved "https://registry.yarnpkg.com/@ethersproject/experimental/-/experimental-5.0.0-beta.141.tgz#2dc7e1f1c33f818cda1799b63b2ecb9e226f46bb"
|
||||
integrity sha512-SFUfN5c6Wcpq18ZZBQdpf6ie50aIkz3jco/8PPv5PFkRSIrGTP4HfobAu6A3eORd/tnvlgm1H2XWOLuRJ3WujA==
|
||||
"@ethersproject/experimental@^5.0.1":
|
||||
version "5.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@ethersproject/experimental/-/experimental-5.0.1.tgz#c488d43092543c49e4cb70fbaeafad0956c826e0"
|
||||
integrity sha512-PAVv/i4PwO2L4E2PWgPgEGP9FOt/5qaTv7W9YDTSL7Tq2zfp41jolRBI1o7X0UdnPWUe54TiibOp4xJR65Dwpw==
|
||||
dependencies:
|
||||
"@ensdomains/address-encoder" "^0.1.2"
|
||||
"@ethersproject/web" ">=5.0.0-beta.138"
|
||||
ethers ">=5.0.0-beta.186"
|
||||
scrypt-js "3.0.0"
|
||||
"@ethersproject/web" "^5.0.0"
|
||||
ethers "^5.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"
|
||||
resolved "https://registry.yarnpkg.com/@ethersproject/hash/-/hash-5.0.2.tgz#6d69558786961836d530b8b4a8714eac5388aec7"
|
||||
integrity sha512-dWGvNwmVRX2bxoQQ3ciMw46Vzl1nqfL+5R8+2ZxsRXD3Cjgw1dL2mdjJF7xMMWPvPdrlhKXWSK0gb8VLwHZ8Cw==
|
||||
@ -1465,7 +1420,7 @@
|
||||
"@ethersproject/logger" "^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"
|
||||
resolved "https://registry.yarnpkg.com/@ethersproject/hdnode/-/hdnode-5.0.2.tgz#c4f2152590a64822d0c0feb90f09cc247af657e0"
|
||||
integrity sha512-QAUI5tfseTFqv00Vnbwzofqse81wN9TaL+x5GufTHIHJXgVdguxU+l39E3VYDCmO+eVAA6RCn5dJgeyra+PU2g==
|
||||
@ -1483,7 +1438,7 @@
|
||||
"@ethersproject/transactions" "^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"
|
||||
resolved "https://registry.yarnpkg.com/@ethersproject/json-wallets/-/json-wallets-5.0.3.tgz#072021fe79f69c9ca1300f780abd9b9d0c8ea42e"
|
||||
integrity sha512-VfDXn5ylugkfiM6SrvQfhX9oAHVU5dsNpRw8PjjTCn4k5E2JuVRO5A8sibkYXDhcBmRISZIWqclIxka6FI/chg==
|
||||
@ -1502,7 +1457,7 @@
|
||||
aes-js "3.0.0"
|
||||
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"
|
||||
resolved "https://registry.yarnpkg.com/@ethersproject/keccak256/-/keccak256-5.0.2.tgz#7ed4a95bb45ee502cf4532223833740a83602797"
|
||||
integrity sha512-MbroXutc0gPNYIrUjS4Aw0lDuXabdzI7+l7elRWr1G6G+W0v00e/3gbikWkCReGtt2Jnt4lQSgnflhDwQGcIhA==
|
||||
@ -1510,19 +1465,12 @@
|
||||
"@ethersproject/bytes" "^5.0.0"
|
||||
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"
|
||||
resolved "https://registry.yarnpkg.com/@ethersproject/logger/-/logger-5.0.4.tgz#09fa4765b5691233e3afb6617cb38a700f9dd2e4"
|
||||
integrity sha512-alA2LiAy1LdQ/L1SA9ajUC7MvGAEQLsICEfKK4erX5qhkXE1LwLSPIzobtOWFsMHf2yrXGKBLnnpuVHprI3sAw==
|
||||
|
||||
"@ethersproject/networks@5.0.0-beta.136":
|
||||
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":
|
||||
"@ethersproject/networks@^5.0.0":
|
||||
version "5.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@ethersproject/networks/-/networks-5.0.2.tgz#a49e82cf071e3618e87e3c5d69fdbcf54dc6766c"
|
||||
integrity sha512-T7HVd62D4izNU2tDHf6xUDo7k4JOGX4Lk7vDmVcDKrepSWwL2OmGWrqSlkRe2a1Dnz4+1VPE6fb6+KsmSRe82g==
|
||||
@ -1537,35 +1485,13 @@
|
||||
"@ethersproject/bytes" "^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"
|
||||
resolved "https://registry.yarnpkg.com/@ethersproject/properties/-/properties-5.0.2.tgz#2facb62d2f2d968c7b3d0befa5bcc884cc565d3b"
|
||||
integrity sha512-FxAisPGAOACQjMJzewl9OJG6lsGCPTm5vpUMtfeoxzAlAb2lv+kHzQPUh9h4jfAILzE8AR1jgXMzRmlhwyra1Q==
|
||||
dependencies:
|
||||
"@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":
|
||||
version "5.0.5"
|
||||
resolved "https://registry.yarnpkg.com/@ethersproject/providers/-/providers-5.0.5.tgz#fa28498ce9683d1d99f6cb11e1a7fe8d4886e0ce"
|
||||
@ -1588,7 +1514,7 @@
|
||||
"@ethersproject/web" "^5.0.0"
|
||||
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"
|
||||
resolved "https://registry.yarnpkg.com/@ethersproject/random/-/random-5.0.2.tgz#bb58aca69a85e8de506686117f050d03dac69023"
|
||||
integrity sha512-kLeS+6bwz37WR2zbe69gudyoGVoUzljQO0LhifnATsZ7rW0JZ9Zgt0h5aXY7tqFDo9TvdqeCwUFdp1t3T5Fkhg==
|
||||
@ -1596,7 +1522,7 @@
|
||||
"@ethersproject/bytes" "^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"
|
||||
resolved "https://registry.yarnpkg.com/@ethersproject/rlp/-/rlp-5.0.2.tgz#d6b550a2ac5e484f15f0f63337e522004d2e78cd"
|
||||
integrity sha512-oE0M5jqQ67fi2SuMcrpoewOpEuoXaD8M9JeR9md1bXRMvDYgKXUtDHs22oevpEOdnO2DPIRabp6MVHa4aDuWmw==
|
||||
@ -1613,7 +1539,7 @@
|
||||
"@ethersproject/logger" "^5.0.0"
|
||||
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"
|
||||
resolved "https://registry.yarnpkg.com/@ethersproject/signing-key/-/signing-key-5.0.3.tgz#adb84360e147bfd336cb2fe114100120732dc10a"
|
||||
integrity sha512-5QPZaBRGCLzfVMbFb3LcVjNR0UbTXnwDHASnQYfbzwUOnFYHKxHsrcbl/5ONGoppgi8yXgOocKqlPCFycJJVWQ==
|
||||
@ -1623,7 +1549,7 @@
|
||||
"@ethersproject/properties" "^5.0.0"
|
||||
elliptic "6.5.3"
|
||||
|
||||
"@ethersproject/solidity@5.0.2", "@ethersproject/solidity@^5.0.0":
|
||||
"@ethersproject/solidity@^5.0.0":
|
||||
version "5.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@ethersproject/solidity/-/solidity-5.0.2.tgz#431cee341ec51e022bd897b93fef04521f414756"
|
||||
integrity sha512-RygurUe1hPW1LDYAPXy4471AklGWNnxgFWc3YUE6H11gzkit26jr6AyZH4Yyjw38eBBL6j0AOfQzMWm+NhxZ9g==
|
||||
@ -1634,16 +1560,7 @@
|
||||
"@ethersproject/sha2" "^5.0.0"
|
||||
"@ethersproject/strings" "^5.0.0"
|
||||
|
||||
"@ethersproject/strings@5.0.0-beta.136":
|
||||
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":
|
||||
"@ethersproject/strings@^5.0.0":
|
||||
version "5.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@ethersproject/strings/-/strings-5.0.2.tgz#1753408c3c889813fd0992abd76393e3e47a2619"
|
||||
integrity sha512-oNa+xvSqsFU96ndzog0IBTtsRFGOqGpzrXJ7shXLBT7juVeSEyZA/sYs0DMZB5mJ9FEjHdZKxR/rTyBY91vuXg==
|
||||
@ -1652,7 +1569,7 @@
|
||||
"@ethersproject/constants" "^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"
|
||||
resolved "https://registry.yarnpkg.com/@ethersproject/transactions/-/transactions-5.0.2.tgz#590ede71fc87b45be7bd46002e18ae52246a2347"
|
||||
integrity sha512-jZp0ZbbJlq4JLZY6qoMzNtp2HQsX6USQposi3ns0MPUdn3OdZJBDtrcO15r/2VS5t/K1e1GE5MI1HmMKlcTbbQ==
|
||||
@ -1667,15 +1584,6 @@
|
||||
"@ethersproject/rlp" "^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":
|
||||
version "5.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@ethersproject/units/-/units-5.0.2.tgz#de1461ff3ad2587e57bf367d056b6b72cfceda78"
|
||||
@ -1685,27 +1593,6 @@
|
||||
"@ethersproject/constants" "^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":
|
||||
version "5.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@ethersproject/wallet/-/wallet-5.0.2.tgz#714ca8324c1b3b66e51b9b4e0358c882e88caf1d"
|
||||
@ -1727,7 +1614,7 @@
|
||||
"@ethersproject/transactions" "^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"
|
||||
resolved "https://registry.yarnpkg.com/@ethersproject/web/-/web-5.0.2.tgz#6565b4c4fe2f56de9556d0e9a966c4ccc1b7b7da"
|
||||
integrity sha512-uAlcxdrAWB9PXZlb5NPzbOOt5/m9EJP2c6eLw15/PXPkNNohEIKvdXXOWdcQgTjZ0pcAaD/9mnJ6HXg7NbqXiw==
|
||||
@ -1737,7 +1624,7 @@
|
||||
"@ethersproject/properties" "^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"
|
||||
resolved "https://registry.yarnpkg.com/@ethersproject/wordlists/-/wordlists-5.0.2.tgz#eded47314509c8608373fc2b22879ee2b71b7c7c"
|
||||
integrity sha512-6vKDQcjjpnfdSCr0+jNxpFH3ieKxUPkm29tQX2US7a3zT/sJU/BGlKBR7D8oOpwdE0hpkHhJyMlypRBK+A2avA==
|
||||
@ -2704,92 +2591,92 @@
|
||||
"@uniswap/lib" "1.1.1"
|
||||
"@uniswap/v2-core" "1.0.0"
|
||||
|
||||
"@walletconnect/client@^1.1.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@walletconnect/client/-/client-1.1.0.tgz#f2454cba82da3d8c7375b2a5d9d47f34ed7348ec"
|
||||
integrity sha512-pHxvUDCkD4oP3AFxYLU7yeE+qDZtcHF20b2K8/HNvyuyu3eWFX4jpHgx6FdvcIcFcAXGs5nk24zBUEO8p+axWg==
|
||||
"@walletconnect/client@^1.1.1-alpha.0":
|
||||
version "1.1.1-alpha.0"
|
||||
resolved "https://registry.yarnpkg.com/@walletconnect/client/-/client-1.1.1-alpha.0.tgz#18362a6b05150f02adfd281ca2251539cd727606"
|
||||
integrity sha512-/aOvwouwXgSMnAMypVlZB6MhIbLwEZOHF2Waa6CvcRRFYe9dA/LqI+vF/dABevg7B4R2q012ZF22NQmhZOVZsw==
|
||||
dependencies:
|
||||
"@walletconnect/core" "^1.1.0"
|
||||
"@walletconnect/iso-crypto" "^1.1.0"
|
||||
"@walletconnect/types" "^1.1.0"
|
||||
"@walletconnect/utils" "^1.1.0"
|
||||
"@walletconnect/core" "^1.1.1-alpha.0"
|
||||
"@walletconnect/iso-crypto" "^1.1.1-alpha.0"
|
||||
"@walletconnect/types" "^1.1.1-alpha.0"
|
||||
"@walletconnect/utils" "^1.1.1-alpha.0"
|
||||
|
||||
"@walletconnect/core@^1.1.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@walletconnect/core/-/core-1.1.0.tgz#053f08b0ccfdfb14ccd27b7fd425d9849cedba14"
|
||||
integrity sha512-Bhe4gnR6Az11u7OAOw0UDZKM6emUjIQtQ2PVdPDWke6ryC0DWMg9vTYbVPf3lDHBv5hy5eAyDst30N5E91SuYw==
|
||||
"@walletconnect/core@^1.1.1-alpha.0":
|
||||
version "1.1.1-alpha.0"
|
||||
resolved "https://registry.yarnpkg.com/@walletconnect/core/-/core-1.1.1-alpha.0.tgz#ffc80babfe271ff7de07a1159f2e52e5a6487e34"
|
||||
integrity sha512-EZf2aqB/nAouHX9T/niCcBxRTPat4B92hcHKKOhBZgKwXF4ajB0LfC1tXwhTDeQGt6PpJ1HLjtnCCJ7+/TLhJg==
|
||||
dependencies:
|
||||
"@walletconnect/socket-transport" "^1.1.0"
|
||||
"@walletconnect/types" "^1.1.0"
|
||||
"@walletconnect/utils" "^1.1.0"
|
||||
"@walletconnect/socket-transport" "^1.1.1-alpha.0"
|
||||
"@walletconnect/types" "^1.1.1-alpha.0"
|
||||
"@walletconnect/utils" "^1.1.1-alpha.0"
|
||||
|
||||
"@walletconnect/http-connection@^1.1.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@walletconnect/http-connection/-/http-connection-1.1.0.tgz#c6650c12a07244d30f20647420cdcd8c69c6daca"
|
||||
integrity sha512-ugxDW/NaSgn7rmdPZhrpJIS79gASLvzBnGHScMs8zpYDHwcFxh2DP3HTspC8o5FyMqjRlEGtNi4zSGKY6EOrkw==
|
||||
"@walletconnect/http-connection@^1.1.1-alpha.0":
|
||||
version "1.1.1-alpha.0"
|
||||
resolved "https://registry.yarnpkg.com/@walletconnect/http-connection/-/http-connection-1.1.1-alpha.0.tgz#5d02efa2a4dd70d502bbf917adc6798ff068c2b0"
|
||||
integrity sha512-IBAwBu9xCmnDMRNiHqaRHgbNibGf4tqtY5BfzU2I49Awmbk//H8TmZ4pDRlXe6/ADWkB2CFcsDZpdjRAkfAWvg==
|
||||
dependencies:
|
||||
"@walletconnect/types" "^1.1.0"
|
||||
"@walletconnect/utils" "^1.1.0"
|
||||
"@walletconnect/types" "^1.1.1-alpha.0"
|
||||
"@walletconnect/utils" "^1.1.1-alpha.0"
|
||||
xhr2-cookies "1.1.0"
|
||||
|
||||
"@walletconnect/iso-crypto@^1.1.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@walletconnect/iso-crypto/-/iso-crypto-1.1.0.tgz#a8235049c1b239adcf9fc6a6c38b7e9ad13004a6"
|
||||
integrity sha512-ttWLj4rTy2NGQnSAKnAar1LSrsJuCQ2JnQUl8hsgc9oTwXKgnRvtxGy2Kajoih/tNKnK959Ilj4WI2HaSJ9G1g==
|
||||
"@walletconnect/iso-crypto@^1.1.1-alpha.0":
|
||||
version "1.1.1-alpha.0"
|
||||
resolved "https://registry.yarnpkg.com/@walletconnect/iso-crypto/-/iso-crypto-1.1.1-alpha.0.tgz#f18a336e310341427603b201f973bb8ee13800cd"
|
||||
integrity sha512-A8U57SgskexAF9TBisfYvtXc+rhjSEakF/hOJxY/vyGlITN4fS9fp/qmz+8dqSOTO4vmTj69dXHowaAhKj+PpQ==
|
||||
dependencies:
|
||||
"@walletconnect/types" "^1.1.0"
|
||||
"@walletconnect/utils" "^1.1.0"
|
||||
"@walletconnect/types" "^1.1.1-alpha.0"
|
||||
"@walletconnect/utils" "^1.1.1-alpha.0"
|
||||
eccrypto-js "5.2.0"
|
||||
|
||||
"@walletconnect/mobile-registry@^1.1.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@walletconnect/mobile-registry/-/mobile-registry-1.1.0.tgz#72173a4fcee61f4f8819f6d9fc7cfbf824ed3548"
|
||||
integrity sha512-OOHQa4NeK2lbfI9WD2d+hTHGwSDzBLoTCeofdLNO2ibaTltQ6S+WNDAVuho6U8CkUTzs5cHPFgLJ6nxYZ8sr/g==
|
||||
"@walletconnect/mobile-registry@^1.1.1-alpha.0":
|
||||
version "1.1.1-alpha.0"
|
||||
resolved "https://registry.yarnpkg.com/@walletconnect/mobile-registry/-/mobile-registry-1.1.1-alpha.0.tgz#65e6708df784e838d05fbe03459eb30a7fedc0ba"
|
||||
integrity sha512-ncX2+XOEYu6OoIXrcLJiy2mrrMTJDuXgcJUpv5Ghwl9CbLczaiq7AlVDNPjMxZJFQj4aalR/idz6RKJbPBA6SQ==
|
||||
|
||||
"@walletconnect/qrcode-modal@^1.1.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@walletconnect/qrcode-modal/-/qrcode-modal-1.1.0.tgz#4cd0c2c2c713be3f49ef00293a1b23a079d4c7b7"
|
||||
integrity sha512-vYsu1MBE0D+kx1+xdXmaCs7JqhhWPw8orKk9Br64YIPF5pv/48i+Yi/m28/0myJm54YPlVcgzTnuf8PzAH7jgA==
|
||||
"@walletconnect/qrcode-modal@^1.1.1-alpha.0":
|
||||
version "1.1.1-alpha.0"
|
||||
resolved "https://registry.yarnpkg.com/@walletconnect/qrcode-modal/-/qrcode-modal-1.1.1-alpha.0.tgz#42eea4e8b091ded92bb47ba497cd3772196b7a3a"
|
||||
integrity sha512-bD7LFVdTzlAb2GFaOR2ymbPVkta8Ezct39VPkteip41KKyta468sZN2AzFTY/wPbTBn+YYwhOliOZhj+Gm9U3A==
|
||||
dependencies:
|
||||
"@walletconnect/mobile-registry" "^1.1.0"
|
||||
"@walletconnect/types" "^1.1.0"
|
||||
"@walletconnect/utils" "^1.1.0"
|
||||
"@walletconnect/mobile-registry" "^1.1.1-alpha.0"
|
||||
"@walletconnect/types" "^1.1.1-alpha.0"
|
||||
"@walletconnect/utils" "^1.1.1-alpha.0"
|
||||
preact "10.4.1"
|
||||
qrcode "1.4.4"
|
||||
|
||||
"@walletconnect/socket-transport@^1.1.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@walletconnect/socket-transport/-/socket-transport-1.1.0.tgz#d80b5e6b3b904f131961259ca16de816ae2b003b"
|
||||
integrity sha512-plo5WHjL3RTDENH7MTgs7D/ePGHfSuc/HLzkVGvgZSOtoPlRR916nSZNeL4bStYF1ZRJCrds10x36C0DlZjpQg==
|
||||
"@walletconnect/socket-transport@^1.1.1-alpha.0":
|
||||
version "1.1.1-alpha.0"
|
||||
resolved "https://registry.yarnpkg.com/@walletconnect/socket-transport/-/socket-transport-1.1.1-alpha.0.tgz#6a1f4abb537f891a1e6c282e0a2a8d1410270554"
|
||||
integrity sha512-epS/zNL4GQclYZ3dDiumR0krwYEpHHGC+LsaNxkrSHTh/URuqfmf6QqCOdcjTU6qW5G1cliHC9Kk0UKcC+VeDA==
|
||||
dependencies:
|
||||
"@walletconnect/types" "^1.1.0"
|
||||
"@walletconnect/types" "^1.1.1-alpha.0"
|
||||
ws "7.3.0"
|
||||
|
||||
"@walletconnect/types@^1.1.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@walletconnect/types/-/types-1.1.0.tgz#1e4efbf033ad89910cbb86f1f381cd5fe7e764fd"
|
||||
integrity sha512-cgDEuYHZZTiaXFRwQs3Zhhar+l2T58/YjhWrfZTMKWuc77geIbF7682i9lE9bNEQqQvQ76jjKxJfSLGjCu++sA==
|
||||
"@walletconnect/types@^1.1.1-alpha.0":
|
||||
version "1.1.1-alpha.0"
|
||||
resolved "https://registry.yarnpkg.com/@walletconnect/types/-/types-1.1.1-alpha.0.tgz#9fbb8356aa347240f4356abea4aaa8b0137396d0"
|
||||
integrity sha512-ro6yJ53kTG8aibKyoUv79CFMujkZk6W5FNHCk6SJdIkPec03XICrt9cqLUaPDt0wx+FM5z94ZHAg46Wzqkh5NA==
|
||||
|
||||
"@walletconnect/utils@^1.1.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@walletconnect/utils/-/utils-1.1.0.tgz#7b0bcf5c77e8079ac055013537a9620244db2da9"
|
||||
integrity sha512-y5v8PCmd/2kASOncYaz5QJiAzwBRT5MK398PmIkImX9tNEeBh00ifeQGZKkCGi6JYXbde0UC5jsGTGkH8hdxeg==
|
||||
"@walletconnect/utils@^1.1.1-alpha.0":
|
||||
version "1.1.1-alpha.0"
|
||||
resolved "https://registry.yarnpkg.com/@walletconnect/utils/-/utils-1.1.1-alpha.0.tgz#62a274290263dcca45102d1cb0cea617827c1849"
|
||||
integrity sha512-35NbpD7JeyzKzh/UPM+TYuxJgudeIr1LuCWdbhGX9KyIoeYxXPVJiCu7RkX5cXI0Fh1dGghQx7++4O3xkqLVcg==
|
||||
dependencies:
|
||||
"@walletconnect/types" "^1.1.0"
|
||||
"@walletconnect/types" "^1.1.1-alpha.0"
|
||||
detect-browser "5.1.0"
|
||||
enc-utils "2.1.0"
|
||||
js-sha3 "0.8.0"
|
||||
|
||||
"@walletconnect/web3-provider@^1.0.11":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@walletconnect/web3-provider/-/web3-provider-1.1.0.tgz#c8a30c4121d3ade159022b10d3a18ecd804c8993"
|
||||
integrity sha512-1DaYG+aK2pjCBKXrB0c2JKeFk27ObUsu09LlZN1VvIi1+zvHftaubNsSGViLmrq25w72yPle/SDjhgmxvKVMQQ==
|
||||
"@walletconnect/web3-provider@1.1.1-alpha.0", "@walletconnect/web3-provider@^1.0.11":
|
||||
version "1.1.1-alpha.0"
|
||||
resolved "https://registry.yarnpkg.com/@walletconnect/web3-provider/-/web3-provider-1.1.1-alpha.0.tgz#b8ca2158da4974b692f57e4f939b5128d8e4696f"
|
||||
integrity sha512-1AoTeCOtK8u2jIH+0NsvisPv2TySZLWHwWu0BIb72wzvzJeG3uD383/stHX8mBOI6a0aPoyDEYzA2R4c/O0vWQ==
|
||||
dependencies:
|
||||
"@walletconnect/client" "^1.1.0"
|
||||
"@walletconnect/http-connection" "^1.1.0"
|
||||
"@walletconnect/qrcode-modal" "^1.1.0"
|
||||
"@walletconnect/types" "^1.1.0"
|
||||
"@walletconnect/utils" "^1.1.0"
|
||||
"@walletconnect/client" "^1.1.1-alpha.0"
|
||||
"@walletconnect/http-connection" "^1.1.1-alpha.0"
|
||||
"@walletconnect/qrcode-modal" "^1.1.1-alpha.0"
|
||||
"@walletconnect/types" "^1.1.1-alpha.0"
|
||||
"@walletconnect/utils" "^1.1.1-alpha.0"
|
||||
web3-provider-engine "15.0.12"
|
||||
|
||||
"@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:
|
||||
rustbn.js "~0.2.0"
|
||||
safe-buffer "^5.1.1"
|
||||
|
||||
ethers@>=5.0.0-beta.186:
|
||||
ethers@^5.0.0, ethers@^5.0.7:
|
||||
version "5.0.7"
|
||||
resolved "https://registry.yarnpkg.com/ethers/-/ethers-5.0.7.tgz#41c3d774e0a57bfde12b0198885789fb41a14976"
|
||||
integrity sha512-1Zu9s+z4BgsDAZcGIYACJdWBB6mVtCCmUonj68Njul7STcSdgwOyj0sCAxCUr2Nsmsamckr4E12q3ecvZPGAUw==
|
||||
@ -12063,7 +11950,7 @@ prompts@^2.0.1:
|
||||
kleur "^3.0.3"
|
||||
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"
|
||||
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
|
||||
integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==
|
||||
@ -12159,19 +12046,6 @@ q@^1.1.2:
|
||||
resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7"
|
||||
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:
|
||||
version "1.4.4"
|
||||
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
|
||||
ajv "^6.12.2"
|
||||
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:
|
||||
version "3.0.1"
|
||||
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:
|
||||
resolved "https://registry.yarnpkg.com/use-callback-ref/-/use-callback-ref-1.2.4.tgz#d86d1577bfd0b955b6e04aaf5971025f406bea3c"
|
||||
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:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/use-sidecar/-/use-sidecar-1.0.2.tgz#e72f582a75842f7de4ef8becd6235a4720ad8af6"
|
||||
|
Loading…
Reference in New Issue
Block a user