diff --git a/package.json b/package.json
index 1691aa0c06..c09a9a8606 100644
--- a/package.json
+++ b/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",
diff --git a/src/components/AccountDetails/index.tsx b/src/components/AccountDetails/index.tsx
index d64df15533..82caecdd2f 100644
--- a/src/components/AccountDetails/index.tsx
+++ b/src/components/AccountDetails/index.tsx
@@ -251,26 +251,26 @@ export default function AccountDetails({
} else if (connector === walletconnect) {
return (
-
+
)
} else if (connector === walletlink) {
return (
-
+
)
} else if (connector === fortmatic) {
return (
-
+
)
} else if (connector === portis) {
return (
<>
-
+
{
portis.portis.showPortis()
@@ -382,7 +382,6 @@ export default function AccountDetails({
>
)}
- {/* {formatConnectorName()} */}
diff --git a/src/components/Button/index.tsx b/src/components/Button/index.tsx
index 2a14966df1..6d1e8ce355 100644
--- a/src/components/Button/index.tsx
+++ b/src/components/Button/index.tsx
@@ -27,6 +27,8 @@ const Base = styled(RebassButton)<{
flex-wrap: nowrap;
align-items: center;
cursor: pointer;
+ position: relative;
+ z-index: 1;
&:disabled {
cursor: auto;
}
diff --git a/src/components/ConfirmationModal/index.tsx b/src/components/ConfirmationModal/index.tsx
deleted file mode 100644
index b17e48398c..0000000000
--- a/src/components/ConfirmationModal/index.tsx
+++ /dev/null
@@ -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 (
-
-
-
-
-
-
-
-
- {transactionBroadcast ? (
-
- ) : (
-
- )}
-
-
-
- {transactionBroadcast ? 'Transaction Submitted' : 'Waiting For Confirmation'}
-
-
-
- {pendingText}
-
-
-
- {transactionBroadcast ? (
- <>
-
-
- View on Etherscan
-
-
-
-
- Close
-
-
- >
- ) : (
-
- Confirm this transaction in your wallet
-
- )}
-
-
-
-
- )
- }
-
- // confirmation screen
- return (
-
-
-
-
-
- {title}
-
-
-
- {topContent()}
-
- {bottomContent()}
-
-
- )
-}
diff --git a/src/components/Modal/index.tsx b/src/components/Modal/index.tsx
index b76ac1b508..bf21475660 100644
--- a/src/components/Modal/index.tsx
+++ b/src/components/Modal/index.tsx
@@ -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 }) => )<{ 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 }) => (
-
+
)).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 && (
-
+ {fadeTransition.map(
+ ({ item, key, props }) =>
+ item && (
+
+ `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 : }
-
- {props => (
- `translateY(${y > 0 ? y : 0}px)`)
- }}
- >
-
- {children}
-
-
- )}
-
-
- )
- )}
- >
- )
- } else {
- return (
- <>
- {transitions.map(
- ({ item, key, props }) =>
- item && (
-
-
- {children}
-
-
- )
- )}
- >
- )
- }
+ {!initialFocusRef && isMobile ? : null}
+ {children}
+
+
+ )
+ )}
+ >
+ )
}
diff --git a/src/components/Popups/PopupItem.tsx b/src/components/Popups/PopupItem.tsx
index 6d6f513737..f7f0436052 100644
--- a/src/components/Popups/PopupItem.tsx
+++ b/src/components/Popups/PopupItem.tsx
@@ -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 =
+ popupContent =
} else if ('listUpdate' in content) {
const {
listUpdate: { listUrl, oldList, newList, auto }
diff --git a/src/components/Popups/TxnPopup.tsx b/src/components/Popups/TransactionPopup.tsx
similarity index 76%
rename from src/components/Popups/TxnPopup.tsx
rename to src/components/Popups/TransactionPopup.tsx
index 4cde7d290a..53495a8da9 100644
--- a/src/components/Popups/TxnPopup.tsx
+++ b/src/components/Popups/TransactionPopup.tsx
@@ -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 (
-
+
@@ -22,6 +34,6 @@ export default function TxnPopup({ hash, success, summary }: { hash: string; suc
{summary ?? 'Hash: ' + hash.slice(0, 8) + '...' + hash.slice(58, 65)}
View on Etherscan
-
+
)
}
diff --git a/src/components/Popups/index.tsx b/src/components/Popups/index.tsx
index 58f68f8260..25528ccfde 100644
--- a/src/components/Popups/index.tsx
+++ b/src/components/Popups/index.tsx
@@ -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 (
+ <>
{activePopups.map(item => (
))}
- )
- }
- //mobile
- else
- return (
0 ? 'fit-content' : 0}>
{activePopups // reverse so new items up front
@@ -66,5 +62,6 @@ export default function Popups() {
))}
- )
+ >
+ )
}
diff --git a/src/components/TransactionConfirmationModal/index.tsx b/src/components/TransactionConfirmationModal/index.tsx
new file mode 100644
index 0000000000..dcfb77f6f0
--- /dev/null
+++ b/src/components/TransactionConfirmationModal/index.tsx
@@ -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 (
+
+
+
+
+
+
+
+
+
+
+
+ Waiting For Confirmation
+
+
+
+ {pendingText}
+
+
+
+ Confirm this transaction in your wallet
+
+
+
+
+ )
+}
+
+function TransactionSubmittedContent({
+ onDismiss,
+ chainId,
+ hash
+}: {
+ onDismiss: () => void
+ hash: string | undefined
+ chainId: ChainId
+}) {
+ const theme = useContext(ThemeContext)
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ Transaction Submitted
+
+
+
+
+ View on Etherscan
+
+
+
+
+ Close
+
+
+
+
+
+ )
+}
+
+export function ConfirmationModalContent({
+ title,
+ bottomContent,
+ onDismiss,
+ topContent
+}: {
+ title: string
+ onDismiss: () => void
+ topContent: () => React.ReactNode
+ bottomContent: () => React.ReactNode
+}) {
+ return (
+
+
+
+
+ {title}
+
+
+
+ {topContent()}
+
+ {bottomContent()}
+
+ )
+}
+
+export function TransactionErrorContent({ message, onDismiss }: { message: string; onDismiss: () => void }) {
+ const theme = useContext(ThemeContext)
+ return (
+
+
+
+
+ Error
+
+
+
+
+
+
+ {message}
+
+
+
+
+ Dismiss
+
+
+ )
+}
+
+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 (
+
+ {attemptingTxn ? (
+
+ ) : hash ? (
+
+ ) : (
+ content()
+ )}
+
+ )
+}
diff --git a/src/components/TransactionConfirmationModal/tsconfig.json b/src/components/TransactionConfirmationModal/tsconfig.json
new file mode 100644
index 0000000000..638227fff6
--- /dev/null
+++ b/src/components/TransactionConfirmationModal/tsconfig.json
@@ -0,0 +1,4 @@
+{
+ "extends": "../../../tsconfig.strict.json",
+ "include": ["**/*"]
+}
\ No newline at end of file
diff --git a/src/components/WalletModal/index.tsx b/src/components/WalletModal/index.tsx
index 6b21dee3eb..b6b2356bba 100644
--- a/src/components/WalletModal/index.tsx
+++ b/src/components/WalletModal/index.tsx
@@ -349,9 +349,7 @@ export default function WalletModal({
{walletView !== WALLET_VIEWS.PENDING && (
New to Ethereum? {' '}
-
- Learn more about wallets
-
+ Learn more about wallets
)}
diff --git a/src/components/swap/AdvancedSwapDetails.tsx b/src/components/swap/AdvancedSwapDetails.tsx
index f4847c2601..c7a2e8d150 100644
--- a/src/components/swap/AdvancedSwapDetails.tsx
+++ b/src/components/swap/AdvancedSwapDetails.tsx
@@ -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 (
- {trade && }
- {showRoute && (
+ {trade && (
<>
-
-
-
-
- Route
-
-
-
-
-
+
+ {showRoute && (
+ <>
+
+
+
+
+ Route
+
+
+
+
+
+ >
+ )}
>
)}
diff --git a/src/components/swap/AdvancedSwapDetailsDropdown.tsx b/src/components/swap/AdvancedSwapDetailsDropdown.tsx
index e3da41b3e8..4529ba3400 100644
--- a/src/components/swap/AdvancedSwapDetailsDropdown.tsx
+++ b/src/components/swap/AdvancedSwapDetailsDropdown.tsx
@@ -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 (
-
+
)
}
diff --git a/src/components/swap/ConfirmSwapModal.tsx b/src/components/swap/ConfirmSwapModal.tsx
new file mode 100644
index 0000000000..24ed3f3d66
--- /dev/null
+++ b/src/components/swap/ConfirmSwapModal.tsx
@@ -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 ? (
+
+ ) : null
+ }, [allowedSlippage, onAcceptChanges, recipient, showAcceptChanges, trade])
+
+ const modalBottom = useCallback(() => {
+ return trade ? (
+
+ ) : 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 ? (
+
+ ) : (
+
+ ),
+ [onDismiss, modalBottom, modalHeader, swapErrorMessage]
+ )
+
+ return (
+
+ )
+}
diff --git a/src/components/swap/FormattedPriceImpact.tsx b/src/components/swap/FormattedPriceImpact.tsx
index c79da60a08..c67727e06e 100644
--- a/src/components/swap/FormattedPriceImpact.tsx
+++ b/src/components/swap/FormattedPriceImpact.tsx
@@ -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 (
- {priceImpact?.lessThan(ONE_BIPS) ? '<0.01%' : `${priceImpact?.toFixed(2)}%` ?? '-'}
+ {priceImpact ? (priceImpact.lessThan(ONE_BIPS) ? '<0.01%' : `${priceImpact.toFixed(2)}%`) : '-'}
)
}
diff --git a/src/components/swap/SwapModalFooter.tsx b/src/components/swap/SwapModalFooter.tsx
index 55412f4d64..369ff88b03 100644
--- a/src/components/swap/SwapModalFooter.tsx
+++ b/src/components/swap/SwapModalFooter.tsx
@@ -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(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({
- {trade?.tradeType === TradeType.EXACT_INPUT ? 'Minimum received' : 'Maximum sold'}
+ {trade.tradeType === TradeType.EXACT_INPUT ? 'Minimum received' : 'Maximum sold'}
- {trade?.tradeType === TradeType.EXACT_INPUT
+ {trade.tradeType === TradeType.EXACT_INPUT
? slippageAdjustedAmounts[Field.OUTPUT]?.toSignificant(4) ?? '-'
: slippageAdjustedAmounts[Field.INPUT]?.toSignificant(4) ?? '-'}
- {parsedAmounts[Field.OUTPUT] && parsedAmounts[Field.INPUT] && (
-
- {trade?.tradeType === TradeType.EXACT_INPUT
- ? parsedAmounts[Field.OUTPUT]?.currency?.symbol
- : parsedAmounts[Field.INPUT]?.currency?.symbol}
-
- )}
+
+ {trade.tradeType === TradeType.EXACT_INPUT
+ ? trade.outputAmount.currency.symbol
+ : trade.inputAmount.currency.symbol}
+
@@ -107,17 +103,25 @@ export default function SwapModalFooter({
- {realizedLPFee ? realizedLPFee?.toSignificant(6) + ' ' + trade?.inputAmount?.currency?.symbol : '-'}
+ {realizedLPFee ? realizedLPFee?.toSignificant(6) + ' ' + trade.inputAmount.currency.symbol : '-'}
- 2} style={{ margin: '10px 0 0 0' }} id="confirm-swap-or-send">
+ 2}
+ style={{ margin: '10px 0 0 0' }}
+ id="confirm-swap-or-send"
+ >
- {confirmText}
+ {severity > 2 ? 'Swap Anyway' : 'Confirm Swap'}
+
+ {swapErrorMessage ? : null}
>
)
diff --git a/src/components/swap/SwapModalHeader.tsx b/src/components/swap/SwapModalHeader.tsx
index 57c5294136..e82e51ad08 100644
--- a/src/components/swap/SwapModalHeader.tsx
+++ b/src/components/swap/SwapModalHeader.tsx
@@ -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 (
-
- {formattedAmounts[Field.INPUT]}
-
-
-
+
+
+
+ {trade.inputAmount.toSignificant(6)}
+
+
+
- {currencies[Field.INPUT]?.symbol}
+ {trade.inputAmount.currency.symbol}
-
+
- 2 ? theme.red1 : ''}>
- {formattedAmounts[Field.OUTPUT]}
-
-
-
+
+
+ 2
+ ? theme.red1
+ : showAcceptChanges && trade.tradeType === TradeType.EXACT_INPUT
+ ? theme.primary1
+ : ''
+ }
+ >
+ {trade.outputAmount.toSignificant(6)}
+
+
+
- {currencies[Field.OUTPUT]?.symbol}
+ {trade.outputAmount.currency.symbol}
+ {showAcceptChanges ? (
+
+
+
+
+ Price Updated
+
+
+ Accept
+
+
+
+ ) : null}
- {independentField === Field.INPUT ? (
+ {trade.tradeType === TradeType.EXACT_INPUT ? (
{`Output is estimated. You will receive at least `}
- {slippageAdjustedAmounts[Field.OUTPUT]?.toSignificant(6)} {currencies[Field.OUTPUT]?.symbol}
+ {slippageAdjustedAmounts[Field.OUTPUT]?.toSignificant(6)} {trade.outputAmount.currency.symbol}
{' or the transaction will revert.'}
@@ -68,7 +109,7 @@ export default function SwapModalHeader({
{`Input is estimated. You will sell at most `}
- {slippageAdjustedAmounts[Field.INPUT]?.toSignificant(6)} {currencies[Field.INPUT]?.symbol}
+ {slippageAdjustedAmounts[Field.INPUT]?.toSignificant(6)} {trade.inputAmount.currency.symbol}
{' or the transaction will revert.'}
diff --git a/src/components/swap/confirmPriceImpactWithoutFee.ts b/src/components/swap/confirmPriceImpactWithoutFee.ts
index 982e19eec9..1b955c3386 100644
--- a/src/components/swap/confirmPriceImpactWithoutFee.ts
+++ b/src/components/swap/confirmPriceImpactWithoutFee.ts
@@ -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 (
diff --git a/src/components/swap/styleds.ts b/src/components/swap/styleds.tsx
similarity index 59%
rename from src/components/swap/styleds.ts
rename to src/components/swap/styleds.tsx
index 612dd02f5f..bfc98be689 100644
--- a/src/components/swap/styleds.ts
+++ b/src/components/swap/styleds.tsx
@@ -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 (
+
+
+
+
+ {error}
+
+ )
+}
+
+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;
+`
diff --git a/src/components/swap/tsconfig.json b/src/components/swap/tsconfig.json
new file mode 100644
index 0000000000..638227fff6
--- /dev/null
+++ b/src/components/swap/tsconfig.json
@@ -0,0 +1,4 @@
+{
+ "extends": "../../../tsconfig.strict.json",
+ "include": ["**/*"]
+}
\ No newline at end of file
diff --git a/src/connectors/NetworkConnector.ts b/src/connectors/NetworkConnector.ts
index 415affcf59..8e27145996 100644
--- a/src/connectors/NetworkConnector.ts
+++ b/src/connectors/NetworkConnector.ts
@@ -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 | 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
}
}
diff --git a/src/constants/index.ts b/src/constants/index.ts
index 0420b4f1aa..aa5470c37c 100644
--- a/src/constants/index.ts
+++ b/src/constants/index.ts
@@ -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
diff --git a/src/data/V1.ts b/src/data/V1.ts
index 73ec3931a2..67f7b78ccf 100644
--- a/src/data/V1.ts
+++ b/src/data/V1.ts
@@ -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
}
diff --git a/src/hooks/useLast.ts b/src/hooks/useLast.ts
index 9703044eb3..6260997b83 100644
--- a/src/hooks/useLast.ts
+++ b/src/hooks/useLast.ts
@@ -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(
+ value: T | undefined | null,
+ filterFn?: (value: T | null | undefined) => boolean
+): T | null | undefined {
+ const [last, setLast] = useState(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(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(value: T | undefined | null): T | null | undefined {
- const [last, setLast] = useState(value)
- useEffect(() => {
- setLast(last => value ?? last)
- }, [value])
- return last
+export function useLastTruthy(value: T | undefined | null): T | null | undefined {
+ return useLast(value, isDefined)
}
diff --git a/src/hooks/useSwapCallback.ts b/src/hooks/useSwapCallback.ts
index 2a1431329c..e8b39cf082 100644
--- a/src/hooks/useSwapCallback.ts
+++ b/src/hooks/useSwapCallback.ts
@@ -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) {
+): { state: SwapCallbackState; callback: null | (() => Promise); 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 {
+ 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])
}
diff --git a/src/hooks/useWrapCallback.ts b/src/hooks/useWrapCallback.ts
index 43211c38e4..46a4f8e2d3 100644
--- a/src/hooks/useWrapCallback.ts
+++ b/src/hooks/useWrapCallback.ts
@@ -23,7 +23,7 @@ export default function useWrapCallback(
inputCurrency: Currency | undefined,
outputCurrency: Currency | undefined,
typedValue: string | undefined
-): { wrapType: WrapType; execute?: undefined | (() => Promise); error?: string } {
+): { wrapType: WrapType; execute?: undefined | (() => Promise); 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
diff --git a/src/pages/AddLiquidity/PoolPriceBar.tsx b/src/pages/AddLiquidity/PoolPriceBar.tsx
index 04593a82da..9d2f920be3 100644
--- a/src/pages/AddLiquidity/PoolPriceBar.tsx
+++ b/src/pages/AddLiquidity/PoolPriceBar.tsx
@@ -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 (
- {price?.toSignificant(6) ?? '0'}
+ {price?.toSignificant(6) ?? '-'}
{currencies[Field.CURRENCY_B]?.symbol} per {currencies[Field.CURRENCY_A]?.symbol}
- {price?.invert().toSignificant(6) ?? '0'}
+ {price?.invert()?.toSignificant(6) ?? '-'}
{currencies[Field.CURRENCY_A]?.symbol} per {currencies[Field.CURRENCY_B]?.symbol}
diff --git a/src/pages/AddLiquidity/index.tsx b/src/pages/AddLiquidity/index.tsx
index c481b2b3b4..bc53c5092e 100644
--- a/src/pages/AddLiquidity/index.tsx
+++ b/src/pages/AddLiquidity/index.tsx
@@ -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 (
<>
- {
- 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={() => (
+
+ )}
pendingText={pendingText}
- title={noLiquidity ? 'You are creating a pool' : 'You will receive'}
/>
{noLiquidity && (
diff --git a/src/pages/RemoveLiquidity/index.tsx b/src/pages/RemoveLiquidity/index.tsx
index abfe3c06b8..ef4729967e 100644
--- a/src/pages/RemoveLiquidity/index.tsx
+++ b/src/pages/RemoveLiquidity/index.tsx
@@ -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 (
<>
- {
- 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={() => (
+
+ )}
pendingText={pendingText}
- title="You will receive"
/>
diff --git a/src/pages/Swap/index.tsx b/src/pages/Swap/index.tsx
index 5d5dbdc566..8068fbc251 100644
--- a/src/pages/Swap/index.tsx
+++ b/src/pages/Swap/index.tsx
@@ -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(false) // show confirmation modal
- const [attemptingTxn, setAttemptingTxn] = useState(false) // waiting for user confirmaion/rejection
- const [txHash, setTxHash] = useState('')
+ 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(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 (
-
- )
- }
-
- function modalBottom() {
- return (
- 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 && }
-
+
- {
- 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}
/>
@@ -373,8 +365,9 @@ export default function Swap() {
{!account ? (
Connect Wallet
) : showWrap ? (
-
- {wrapError ?? (wrapType === WrapType.WRAP ? 'Wrap' : wrapType === WrapType.UNWRAP ? 'Unwrap' : null)}
+
+ {wrapInputError ??
+ (wrapType === WrapType.WRAP ? 'Wrap' : wrapType === WrapType.UNWRAP ? 'Unwrap' : null)}
) : noRoute && userHasSpecifiedInputOutput ? (
@@ -398,15 +391,27 @@ export default function Swap() {
{
- 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}
>
- {priceImpactSeverity > 3 && !expertMode
+ {priceImpactSeverity > 3 && !isExpertMode
? `Price Impact High`
: `Swap${priceImpactSeverity > 2 ? ' Anyway' : ''}`}
@@ -415,21 +420,32 @@ export default function Swap() {
) : (
{
- 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}
>
- {error
- ? error
- : priceImpactSeverity > 3 && !expertMode
+ {swapInputError
+ ? swapInputError
+ : priceImpactSeverity > 3 && !isExpertMode
? `Price Impact Too High`
: `Swap${priceImpactSeverity > 2 ? ' Anyway' : ''}`}
)}
+ {isExpertMode && swapErrorMessage ? : null}
{betterTradeLinkVersion && }
diff --git a/src/state/mint/hooks.ts b/src/state/mint/hooks.ts
index a587ff120b..c7d5fbf121 100644
--- a/src/state/mint/hooks.ts
+++ b/src/state/mint/hooks.ts
@@ -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] = [
diff --git a/src/state/multicall/updater.tsx b/src/state/multicall/updater.tsx
index e0a51571dd..a360f7652b 100644
--- a/src/state/multicall/updater.tsx
+++ b/src/state/multicall/updater.tsx
@@ -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((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((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
diff --git a/src/state/swap/hooks.ts b/src/state/swap/hooks.ts
index e8a5f1ed54..fa4d6dc43a 100644
--- a/src/state/swap/hooks.ts
+++ b/src/state/swap/hooks.ts
@@ -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
}
}
diff --git a/src/theme/index.tsx b/src/theme/index.tsx
index 19c9acc2fa..b46511a1da 100644
--- a/src/theme/index.tsx
+++ b/src/theme/index.tsx
@@ -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
diff --git a/src/utils/index.ts b/src/utils/index.ts
index fb04ac52a5..48d417fac9 100644
--- a/src/utils/index.ts
+++ b/src/utils/index.ts
@@ -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)
}
diff --git a/src/utils/isZero.ts b/src/utils/isZero.ts
new file mode 100644
index 0000000000..1b84e67560
--- /dev/null
+++ b/src/utils/isZero.ts
@@ -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)
+}
diff --git a/src/utils/retry.test.ts b/src/utils/retry.test.ts
index 788dc55bd5..2ea7ca3930 100644
--- a/src/utils/retry.test.ts
+++ b/src/utils/retry.test.ts
@@ -1,26 +1,45 @@
-import { retry } from './retry'
+import { retry, RetryableError } from './retry'
describe('retry', () => {
- function makeFn(fails: number, result: T): () => Promise {
+ function makeFn(fails: number, result: T, retryable = true): () => Promise {
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, 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
)
diff --git a/src/utils/retry.ts b/src/utils/retry.ts
index 9a92cb5eb0..e2d8530287 100644
--- a/src/utils/retry.ts
+++ b/src/utils/retry.ts
@@ -6,6 +6,20 @@ function waitRandom(min: number, max: number): Promise {
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 {
* @param minWait min wait between retries in ms
* @param maxWait max wait between retries in ms
*/
-// todo: support cancelling the retry
export function retry(
fn: () => Promise,
- { n = 3, minWait = 500, maxWait = 1000 }: { n?: number; minWait?: number; maxWait?: number } = {}
-): Promise {
- 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; cancel: () => void } {
+ let completed = false
+ let rejectCancelled: (error: Error) => void
+ const promise = new Promise(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())
+ }
+ }
}
diff --git a/yarn.lock b/yarn.lock
index b9fcaaab68..67b7f1bb4b 100644
--- a/yarn.lock
+++ b/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"