CurrencyLogo currency=USDC
+
100%
@@ -157,20 +144,7 @@ exports[`renders single route 1`] = `
style="justify-content: space-evenly; z-index: 2;"
width="100%"
>
-
-
- DoubleCurrencyLogo currency0=DAI currency1=USDC
-
-
- 0.05%
-
-
+ Popover
CurrencyLogo currency=DAI
@@ -182,7 +156,7 @@ exports[`renders single route 1`] = `
exports[`renders when no routes are provided 1`] = `
`;
diff --git a/src/components/Settings/index.tsx b/src/components/Settings/index.tsx
index 36a01b4947..36f9aebbf1 100644
--- a/src/components/Settings/index.tsx
+++ b/src/components/Settings/index.tsx
@@ -1,12 +1,12 @@
// eslint-disable-next-line no-restricted-imports
import { t, Trans } from '@lingui/macro'
import { Percent } from '@uniswap/sdk-core'
-import { SupportedChainId } from 'constants/chains'
import { useActiveWeb3React } from 'hooks/web3'
import { useContext, useRef, useState } from 'react'
import { Settings, X } from 'react-feather'
import ReactGA from 'react-ga'
import { Text } from 'rebass'
+import { AUTO_ROUTER_SUPPORTED_CHAINS } from 'state/routing/clientSideSmartOrderRouter/constants'
import styled, { ThemeContext } from 'styled-components/macro'
import { useOnClickOutside } from '../../hooks/useOnClickOutside'
@@ -27,7 +27,7 @@ const StyledMenuIcon = styled(Settings)`
width: 20px;
> * {
- stroke: ${({ theme }) => theme.text2};
+ stroke: ${({ theme }) => theme.text1};
}
:hover {
@@ -199,16 +199,13 @@ export default function SettingsTab({ placeholderSlippage }: { placeholderSlippa
Interface Settings
-
- {chainId === SupportedChainId.MAINNET && (
+ {chainId && AUTO_ROUTER_SUPPORTED_CHAINS.includes(chainId) && (
- Auto Router
+ Auto Router API
- Use the Uniswap Labs API to get better pricing through a more efficient route.}
- />
+ Use the Uniswap Labs API to get faster quotes.} />
)}
-
diff --git a/src/components/Tooltip/index.tsx b/src/components/Tooltip/index.tsx
index 143eff36c4..ec8606c1cd 100644
--- a/src/components/Tooltip/index.tsx
+++ b/src/components/Tooltip/index.tsx
@@ -5,7 +5,7 @@ import styled from 'styled-components/macro'
import Popover, { PopoverProps } from '../Popover'
export const TooltipContainer = styled.div`
- width: 256px;
+ max-width: 256px;
padding: 0.6rem 1rem;
font-weight: 400;
word-break: break-word;
@@ -25,6 +25,7 @@ interface TooltipContentProps extends Omit {
onOpen?: () => void
// whether to wrap the content in a `TooltipContainer`
wrap?: boolean
+ disableHover?: boolean // disable the hover and content display
}
export default function Tooltip({ text, ...rest }: TooltipProps) {
@@ -52,6 +53,7 @@ export function MouseoverTooltipContent({
content,
children,
onOpen: openCallback = undefined,
+ disableHover,
...rest
}: Omit) {
const [show, setShow] = useState(false)
@@ -61,7 +63,7 @@ export function MouseoverTooltipContent({
}, [openCallback])
const close = useCallback(() => setShow(false), [setShow])
return (
-
+
theme.primary1};
+ background-color: ${({ theme }) => theme.bg1};
color: ${({ theme }) => theme.white};
:hover {
@@ -402,6 +402,16 @@ export default function WalletModal({
+ {walletView === WALLET_VIEWS.PENDING ? (
+
+ ) : (
+
{getOptions()}
+ )}
setWalletView(WALLET_VIEWS.LEGAL)}>
@@ -413,16 +423,6 @@ export default function WalletModal({
- {walletView === WALLET_VIEWS.PENDING ? (
-
- ) : (
-
{getOptions()}
- )}
diff --git a/src/components/swap/AdvancedSwapDetails.tsx b/src/components/swap/AdvancedSwapDetails.tsx
index c7f7eb82f5..6b22820308 100644
--- a/src/components/swap/AdvancedSwapDetails.tsx
+++ b/src/components/swap/AdvancedSwapDetails.tsx
@@ -1,22 +1,28 @@
import { Trans } from '@lingui/macro'
import { Currency, Percent, TradeType } from '@uniswap/sdk-core'
-import { Trade as V2Trade } from '@uniswap/v2-sdk'
-import { Trade as V3Trade } from '@uniswap/v3-sdk'
+import Card from 'components/Card'
import { LoadingRows } from 'components/Loader/styled'
+import { useActiveWeb3React } from 'hooks/web3'
import { useContext, useMemo } from 'react'
-import { ThemeContext } from 'styled-components/macro'
+import { InterfaceTrade } from 'state/routing/types'
+import styled, { ThemeContext } from 'styled-components/macro'
-import { ThemedText } from '../../theme'
+import { Separator, ThemedText } from '../../theme'
import { computeRealizedLPFeePercent } from '../../utils/prices'
import { AutoColumn } from '../Column'
import { RowBetween, RowFixed } from '../Row'
import FormattedPriceImpact from './FormattedPriceImpact'
-import { TransactionDetailsLabel } from './styleds'
+import { SUPPORTED_GAS_ESTIMATE_CHAIN_IDS } from './GasEstimateBadge'
+
+const StyledCard = styled(Card)`
+ padding: 0;
+`
interface AdvancedSwapDetailsProps {
- trade?: V2Trade
| V3Trade
+ trade?: InterfaceTrade
allowedSlippage: Percent
syncing?: boolean
+ hideRouteDiagram?: boolean
}
function TextWithLoadingPlaceholder({
@@ -39,74 +45,78 @@ function TextWithLoadingPlaceholder({
export function AdvancedSwapDetails({ trade, allowedSlippage, syncing = false }: AdvancedSwapDetailsProps) {
const theme = useContext(ThemeContext)
+ const { chainId } = useActiveWeb3React()
- const { realizedLPFee, priceImpact } = useMemo(() => {
- if (!trade) return { realizedLPFee: undefined, priceImpact: undefined }
-
+ const { expectedOutputAmount, priceImpact } = useMemo(() => {
+ if (!trade) return { expectedOutputAmount: undefined, priceImpact: undefined }
+ const expectedOutputAmount = trade.outputAmount
const realizedLpFeePercent = computeRealizedLPFeePercent(trade)
- const realizedLPFee = trade.inputAmount.multiply(realizedLpFeePercent)
const priceImpact = trade.priceImpact.subtract(realizedLpFeePercent)
- return { priceImpact, realizedLPFee }
+ return { expectedOutputAmount, priceImpact }
}, [trade])
return !trade ? null : (
-
-
- Transaction Details
-
-
-
-
- Liquidity Provider Fee
-
-
-
-
- {realizedLPFee ? `${realizedLPFee.toSignificant(4)} ${realizedLPFee.currency.symbol}` : '-'}
-
-
-
-
-
-
-
- Price Impact
-
-
-
-
-
-
-
-
-
-
-
-
- Allowed Slippage
-
-
-
-
- {allowedSlippage.toFixed(2)}%
-
-
-
-
-
-
-
- {trade.tradeType === TradeType.EXACT_INPUT ? Minimum received : Maximum sent}
-
-
-
-
- {trade.tradeType === TradeType.EXACT_INPUT
- ? `${trade.minimumAmountOut(allowedSlippage).toSignificant(6)} ${trade.outputAmount.currency.symbol}`
- : `${trade.maximumAmountIn(allowedSlippage).toSignificant(6)} ${trade.inputAmount.currency.symbol}`}
-
-
-
-
+
+
+
+
+
+ Expected Output
+
+
+
+
+ {expectedOutputAmount
+ ? `${expectedOutputAmount.toSignificant(6)} ${expectedOutputAmount.currency.symbol}`
+ : '-'}
+
+
+
+
+
+
+ Price Impact
+
+
+
+
+
+
+
+
+
+
+
+
+ {trade.tradeType === TradeType.EXACT_INPUT ? (
+ Minimum received
+ ) : (
+ Maximum sent
+ )}{' '}
+ after slippage ({allowedSlippage.toFixed(2)}%)
+
+
+
+
+ {trade.tradeType === TradeType.EXACT_INPUT
+ ? `${trade.minimumAmountOut(allowedSlippage).toSignificant(6)} ${trade.outputAmount.currency.symbol}`
+ : `${trade.maximumAmountIn(allowedSlippage).toSignificant(6)} ${trade.inputAmount.currency.symbol}`}
+
+
+
+ {!trade?.gasUseEstimateUSD || !chainId || !SUPPORTED_GAS_ESTIMATE_CHAIN_IDS.includes(chainId) ? null : (
+
+
+ Network Fee
+
+
+
+ ~${trade.gasUseEstimateUSD.toFixed(2)}
+
+
+
+ )}
+
+
)
}
diff --git a/src/components/swap/ConfirmSwapModal.tsx b/src/components/swap/ConfirmSwapModal.tsx
index 4324607236..f95f2f2ba3 100644
--- a/src/components/swap/ConfirmSwapModal.tsx
+++ b/src/components/swap/ConfirmSwapModal.tsx
@@ -1,8 +1,8 @@
import { Trans } from '@lingui/macro'
+import { Trade } from '@uniswap/router-sdk'
import { Currency, Percent, TradeType } from '@uniswap/sdk-core'
-import { Trade as V2Trade } from '@uniswap/v2-sdk'
-import { Trade as V3Trade } from '@uniswap/v3-sdk'
import { ReactNode, useCallback, useMemo } from 'react'
+import { InterfaceTrade } from 'state/routing/types'
import TransactionConfirmationModal, {
ConfirmationModalContent,
@@ -16,9 +16,7 @@ import SwapModalHeader from './SwapModalHeader'
* @param args either a pair of V2 trades or a pair of V3 trades
*/
function tradeMeaningfullyDiffers(
- ...args:
- | [V2Trade, V2Trade]
- | [V3Trade, V3Trade]
+ ...args: [Trade, Trade]
): boolean {
const [tradeA, tradeB] = args
return (
@@ -44,8 +42,8 @@ export default function ConfirmSwapModal({
txHash,
}: {
isOpen: boolean
- trade: V2Trade | V3Trade | undefined
- originalTrade: V2Trade | V3Trade | undefined
+ trade: InterfaceTrade | undefined
+ originalTrade: Trade | undefined
attemptingTxn: boolean
txHash: string | undefined
recipient: string | null
@@ -56,15 +54,7 @@ export default function ConfirmSwapModal({
onDismiss: () => void
}) {
const showAcceptChanges = useMemo(
- () =>
- Boolean(
- (trade instanceof V2Trade &&
- originalTrade instanceof V2Trade &&
- tradeMeaningfullyDiffers(trade, originalTrade)) ||
- (trade instanceof V3Trade &&
- originalTrade instanceof V3Trade &&
- tradeMeaningfullyDiffers(trade, originalTrade))
- ),
+ () => Boolean(trade && originalTrade && tradeMeaningfullyDiffers(trade, originalTrade)),
[originalTrade, trade]
)
diff --git a/src/components/swap/GasEstimateBadge.tsx b/src/components/swap/GasEstimateBadge.tsx
new file mode 100644
index 0000000000..341c4990f3
--- /dev/null
+++ b/src/components/swap/GasEstimateBadge.tsx
@@ -0,0 +1,105 @@
+import { Trans } from '@lingui/macro'
+import { Currency, TradeType } from '@uniswap/sdk-core'
+import { ChainId } from '@uniswap/smart-order-router'
+import { AutoColumn } from 'components/Column'
+import { LoadingOpacityContainer } from 'components/Loader/styled'
+import { RowFixed } from 'components/Row'
+import { MouseoverTooltipContent } from 'components/Tooltip'
+import ReactGA from 'react-ga'
+import { InterfaceTrade } from 'state/routing/types'
+import styled from 'styled-components/macro'
+import { ThemedText } from 'theme'
+
+import { ReactComponent as GasIcon } from '../../assets/images/gas-icon.svg'
+import { ResponsiveTooltipContainer } from './styleds'
+import SwapRoute from './SwapRoute'
+
+const GasWrapper = styled(RowFixed)`
+ border-radius: 8px;
+ padding: 4px 6px;
+ height: 24px;
+ color: ${({ theme }) => theme.text3};
+ background-color: ${({ theme }) => theme.bg1};
+ font-size: 14px;
+ font-weight: 500;
+ user-select: none;
+`
+const StyledGasIcon = styled(GasIcon)`
+ margin-right: 4px;
+ height: 14px;
+ & > * {
+ stroke: ${({ theme }) => theme.text3};
+ }
+`
+
+export const SUPPORTED_GAS_ESTIMATE_CHAIN_IDS = [ChainId.MAINNET]
+
+export default function GasEstimateBadge({
+ trade,
+ loading,
+ showRoute,
+ disableHover,
+}: {
+ trade: InterfaceTrade | undefined | null // dollar amount in active chain's stablecoin
+ loading: boolean
+ showRoute?: boolean // show route instead of gas estimation summary
+ disableHover?: boolean
+}) {
+ const formattedGasPriceString = trade?.gasUseEstimateUSD
+ ? trade.gasUseEstimateUSD.toFixed(2) === '0.00'
+ ? '<$0.01'
+ : '$' + trade.gasUseEstimateUSD.toFixed(2)
+ : undefined
+
+ return (
+
+ {showRoute ? (
+ trade ? (
+
+ ) : null
+ ) : (
+
+
+ Estimated network fee
+
+
+ ${trade?.gasUseEstimateUSD?.toFixed(2)}
+
+
+ Estimate may differ due to your wallet gas settings
+
+
+ )}
+
+ )
+ }
+ placement="bottom"
+ onOpen={() =>
+ ReactGA.event({
+ category: 'Gas',
+ action: 'Gas Details Tooltip Open',
+ })
+ }
+ >
+
+
+
+ {formattedGasPriceString ?? null}
+
+
+
+ )
+}
diff --git a/src/components/swap/RouterLabel.tsx b/src/components/swap/RouterLabel.tsx
index 6879820467..48b75aec40 100644
--- a/src/components/swap/RouterLabel.tsx
+++ b/src/components/swap/RouterLabel.tsx
@@ -1,5 +1,5 @@
import { Trans } from '@lingui/macro'
-import { useRoutingAPIEnabled } from 'state/user/hooks'
+import useAutoRouterSupported from 'hooks/useAutoRouterSupported'
import styled from 'styled-components/macro'
import { ThemedText } from 'theme'
@@ -40,15 +40,15 @@ const StyledAutoRouterLabel = styled(ThemedText.Black)`
`
export function AutoRouterLogo() {
- const routingAPIEnabled = useRoutingAPIEnabled()
+ const autoRouterSupported = useAutoRouterSupported()
- return routingAPIEnabled ? :
+ return autoRouterSupported ? :
}
export function AutoRouterLabel() {
- const routingAPIEnabled = useRoutingAPIEnabled()
+ const autoRouterSupported = useAutoRouterSupported()
- return routingAPIEnabled ? (
+ return autoRouterSupported ? (
Auto Router
) : (
diff --git a/src/components/swap/SwapDetailsDropdown.tsx b/src/components/swap/SwapDetailsDropdown.tsx
new file mode 100644
index 0000000000..bcf633a88a
--- /dev/null
+++ b/src/components/swap/SwapDetailsDropdown.tsx
@@ -0,0 +1,202 @@
+import { Trans } from '@lingui/macro'
+import { Currency, Percent, TradeType } from '@uniswap/sdk-core'
+import AnimatedDropdown from 'components/AnimatedDropdown'
+import Card, { OutlineCard } from 'components/Card'
+import { AutoColumn } from 'components/Column'
+import { LoadingOpacityContainer } from 'components/Loader/styled'
+import Row, { RowBetween, RowFixed } from 'components/Row'
+import { MouseoverTooltipContent } from 'components/Tooltip'
+import { useActiveWeb3React } from 'hooks/web3'
+import { darken } from 'polished'
+import { useState } from 'react'
+import { ChevronDown, Info } from 'react-feather'
+import { InterfaceTrade } from 'state/routing/types'
+import styled, { keyframes, useTheme } from 'styled-components/macro'
+import { HideSmall, ThemedText } from 'theme'
+
+import { AdvancedSwapDetails } from './AdvancedSwapDetails'
+import GasEstimateBadge, { SUPPORTED_GAS_ESTIMATE_CHAIN_IDS } from './GasEstimateBadge'
+import { ResponsiveTooltipContainer } from './styleds'
+import SwapRoute from './SwapRoute'
+import TradePrice from './TradePrice'
+
+const Wrapper = styled(Row)`
+ width: 100%;
+ justify-content: center;
+`
+
+const StyledInfoIcon = styled(Info)`
+ height: 16px;
+ width: 16px;
+ margin-right: 4px;
+ color: ${({ theme }) => theme.text3};
+`
+
+const StyledCard = styled(OutlineCard)`
+ padding: 12px;
+ border: 1px solid ${({ theme }) => theme.bg2};
+`
+
+const StyledHeaderRow = styled(RowBetween)<{ disabled: boolean; open: boolean }>`
+ padding: 4px 8px;
+ border-radius: 12px;
+ background-color: ${({ open, theme }) => (open ? theme.bg1 : 'transparent')};
+ align-items: center;
+ cursor: ${({ disabled }) => (disabled ? 'initial' : 'pointer')};
+ min-height: 40px;
+
+ :hover {
+ background-color: ${({ theme, disabled }) => (disabled ? theme.bg1 : darken(0.015, theme.bg1))};
+ }
+`
+
+const RotatingArrow = styled(ChevronDown)<{ open?: boolean }>`
+ transform: ${({ open }) => (open ? 'rotate(180deg)' : 'none')};
+ transition: transform 0.1s linear;
+`
+
+const StyledPolling = styled.div`
+ display: flex;
+ height: 16px;
+ width: 16px;
+ margin-right: 2px;
+ margin-left: 10px;
+ align-items: center;
+ color: ${({ theme }) => theme.text1};
+ transition: 250ms ease color;
+
+ ${({ theme }) => theme.mediaWidth.upToMedium`
+ display: none;
+ `}
+`
+
+const StyledPollingDot = styled.div`
+ width: 8px;
+ height: 8px;
+ min-height: 8px;
+ min-width: 8px;
+ border-radius: 50%;
+ position: relative;
+ background-color: ${({ theme }) => theme.bg2};
+ transition: 250ms ease background-color;
+`
+
+const rotate360 = keyframes`
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+`
+
+const Spinner = styled.div`
+ animation: ${rotate360} 1s cubic-bezier(0.83, 0, 0.17, 1) infinite;
+ transform: translateZ(0);
+ border-top: 1px solid transparent;
+ border-right: 1px solid transparent;
+ border-bottom: 1px solid transparent;
+ border-left: 2px solid ${({ theme }) => theme.text1};
+ background: transparent;
+ width: 14px;
+ height: 14px;
+ border-radius: 50%;
+ position: relative;
+ transition: 250ms ease border-color;
+ left: -3px;
+ top: -3px;
+`
+
+interface SwapDetailsInlineProps {
+ trade: InterfaceTrade | undefined
+ syncing: boolean
+ loading: boolean
+ showInverted: boolean
+ setShowInverted: React.Dispatch>
+ allowedSlippage: Percent
+}
+
+export default function SwapDetailsDropdown({
+ trade,
+ syncing,
+ loading,
+ showInverted,
+ setShowInverted,
+ allowedSlippage,
+}: SwapDetailsInlineProps) {
+ const theme = useTheme()
+ const { chainId } = useActiveWeb3React()
+ const [showDetails, setShowDetails] = useState(false)
+
+ return (
+
+
+ setShowDetails(!showDetails)} disabled={!trade} open={showDetails}>
+
+ {loading || syncing ? (
+
+
+
+
+
+ ) : (
+
+
+
+
+
+
+ }
+ placement="bottom"
+ disableHover={showDetails}
+ >
+
+
+
+ )}
+ {trade ? (
+
+
+
+ ) : loading || syncing ? (
+
+ Fetching best price...
+
+ ) : null}
+
+
+ {!trade?.gasUseEstimateUSD ||
+ showDetails ||
+ !chainId ||
+ !SUPPORTED_GAS_ESTIMATE_CHAIN_IDS.includes(chainId) ? null : (
+
+ )}
+
+
+
+
+
+ {trade ? (
+
+
+
+ ) : null}
+ {trade ? : null}
+
+
+
+
+ )
+}
diff --git a/src/components/swap/SwapModalFooter.tsx b/src/components/swap/SwapModalFooter.tsx
index a63dddde87..97dd995022 100644
--- a/src/components/swap/SwapModalFooter.tsx
+++ b/src/components/swap/SwapModalFooter.tsx
@@ -1,7 +1,6 @@
import { Trans } from '@lingui/macro'
+import { Trade } from '@uniswap/router-sdk'
import { Currency, TradeType } from '@uniswap/sdk-core'
-import { Trade as V2Trade } from '@uniswap/v2-sdk'
-import { Trade as V3Trade } from '@uniswap/v3-sdk'
import { ReactNode } from 'react'
import { Text } from 'rebass'
@@ -14,7 +13,7 @@ export default function SwapModalFooter({
swapErrorMessage,
disabledConfirm,
}: {
- trade: V2Trade | V3Trade
+ trade: Trade
onConfirm: () => void
swapErrorMessage: ReactNode | undefined
disabledConfirm: boolean
diff --git a/src/components/swap/SwapModalHeader.tsx b/src/components/swap/SwapModalHeader.tsx
index 1affbed34f..acc218b524 100644
--- a/src/components/swap/SwapModalHeader.tsx
+++ b/src/components/swap/SwapModalHeader.tsx
@@ -1,10 +1,9 @@
import { Trans } from '@lingui/macro'
import { Currency, Percent, TradeType } from '@uniswap/sdk-core'
-import { Trade as V2Trade } from '@uniswap/v2-sdk'
-import { Trade as V3Trade } from '@uniswap/v3-sdk'
import { useContext, useState } from 'react'
import { AlertTriangle, ArrowDown } from 'react-feather'
import { Text } from 'rebass'
+import { InterfaceTrade } from 'state/routing/types'
import styled, { ThemeContext } from 'styled-components/macro'
import { useUSDCValue } from '../../hooks/useUSDCPrice'
@@ -46,7 +45,7 @@ export default function SwapModalHeader({
showAcceptChanges,
onAcceptChanges,
}: {
- trade: V2Trade | V3Trade
+ trade: InterfaceTrade
allowedSlippage: Percent
recipient: string | null
showAcceptChanges: boolean
@@ -63,19 +62,7 @@ export default function SwapModalHeader({
-
-
- From
-
-
-
-
-
-
- {trade.inputAmount.currency.symbol}
-
-
+
+
+
+ {trade.inputAmount.currency.symbol}
+
+
+
+
+
@@ -93,43 +89,35 @@ export default function SwapModalHeader({
-
-
- To
-
-
-
-
-
+
+
+ {trade.outputAmount.toSignificant(6)}
+
+
{trade.outputAmount.currency.symbol}
-
-
- {trade.outputAmount.toSignificant(6)}
-
-
+
+
+
+
+
-
- Price
-
-
-
{showAcceptChanges ? (
diff --git a/src/components/swap/SwapRoute.tsx b/src/components/swap/SwapRoute.tsx
index 1a1fdc80d9..eb61c80979 100644
--- a/src/components/swap/SwapRoute.tsx
+++ b/src/components/swap/SwapRoute.tsx
@@ -1,92 +1,113 @@
import { Trans } from '@lingui/macro'
+import { Trade } from '@uniswap/router-sdk'
import { Currency, Percent, TradeType } from '@uniswap/sdk-core'
-import { Trade as V2Trade } from '@uniswap/v2-sdk'
-import { FeeAmount, Trade as V3Trade } from '@uniswap/v3-sdk'
-import Badge from 'components/Badge'
+import { Pair } from '@uniswap/v2-sdk'
+import AnimatedDropdown from 'components/AnimatedDropdown'
import { AutoColumn } from 'components/Column'
import { LoadingRows } from 'components/Loader/styled'
import RoutingDiagram, { RoutingDiagramEntry } from 'components/RoutingDiagram/RoutingDiagram'
import { AutoRow, RowBetween } from 'components/Row'
-import { Version } from 'hooks/useToggledVersion'
-import { memo } from 'react'
-import { useRoutingAPIEnabled } from 'state/user/hooks'
+import useAutoRouterSupported from 'hooks/useAutoRouterSupported'
+import { useActiveWeb3React } from 'hooks/web3'
+import { memo, useState } from 'react'
+import { Plus } from 'react-feather'
+import { InterfaceTrade } from 'state/routing/types'
+import { useDarkModeManager } from 'state/user/hooks'
import styled from 'styled-components/macro'
-import { ThemedText } from 'theme'
-import { getTradeVersion } from 'utils/getTradeVersion'
+import { Separator, ThemedText } from 'theme'
+import { SUPPORTED_GAS_ESTIMATE_CHAIN_IDS } from './GasEstimateBadge'
import { AutoRouterLabel, AutoRouterLogo } from './RouterLabel'
-const Separator = styled.div`
- border-top: 1px solid ${({ theme }) => theme.bg2};
- height: 1px;
- width: 100%;
+const Wrapper = styled(AutoColumn)<{ darkMode?: boolean; fixedOpen?: boolean }>`
+ padding: ${({ fixedOpen }) => (fixedOpen ? '12px' : '12px 8px 12px 12px')};
+ border-radius: 16px;
+ border: 1px solid ${({ theme, fixedOpen }) => (fixedOpen ? 'transparent' : theme.bg2)};
+ cursor: pointer;
+`
+
+const OpenCloseIcon = styled(Plus)<{ open?: boolean }>`
+ margin-left: 8px;
+ height: 20px;
+ stroke-width: 2px;
+ transition: transform 0.1s;
+ transform: ${({ open }) => (open ? 'rotate(45deg)' : 'none')};
+ stroke: ${({ theme }) => theme.text3};
+ cursor: pointer;
+ :hover {
+ opacity: 0.8;
+ }
`
const V2_DEFAULT_FEE_TIER = 3000
-export default memo(function SwapRoute({
- trade,
- syncing,
-}: {
- trade: V2Trade | V3Trade
+interface SwapRouteProps extends React.HTMLAttributes {
+ trade: InterfaceTrade
syncing: boolean
-}) {
- const routingAPIEnabled = useRoutingAPIEnabled()
+ fixedOpen?: boolean // fixed in open state, hide open/close icon
+}
+
+export default memo(function SwapRoute({ trade, syncing, fixedOpen = false, ...rest }: SwapRouteProps) {
+ const autoRouterSupported = useAutoRouterSupported()
+ const routes = getTokenPath(trade)
+ const [open, setOpen] = useState(false)
+ const { chainId } = useActiveWeb3React()
+
+ const [darkMode] = useDarkModeManager()
+
+ const formattedGasPriceString = trade?.gasUseEstimateUSD
+ ? trade.gasUseEstimateUSD.toFixed(2) === '0.00'
+ ? '<$0.01'
+ : '$' + trade.gasUseEstimateUSD.toFixed(2)
+ : undefined
return (
-
-
+
+ setOpen(!open)}>
- {syncing ? (
-
-
-
- ) : (
-
-
- {getTradeVersion(trade) === Version.v2 ? V2 : V3}
-
-
- )}
+ {fixedOpen ? null : }
-
- {syncing ? (
-
-
-
- ) : (
-
- )}
- {routingAPIEnabled && (
-
- This route optimizes your price by considering split routes, multiple hops, and gas costs.
-
- )}
-
+
+
+ {syncing ? (
+
+
+
+ ) : (
+
+ )}
+
+ {autoRouterSupported &&
+ (syncing ? (
+
+
+
+ ) : (
+
+ {trade?.gasUseEstimateUSD && chainId && SUPPORTED_GAS_ESTIMATE_CHAIN_IDS.includes(chainId) ? (
+ Best price route costs ~{formattedGasPriceString} in gas.
+ ) : null}{' '}
+
+ This route optimizes your total output by considering split routes, multiple hops, and the gas cost of
+ each step.
+
+
+ ))}
+
+
+
)
})
-function getTokenPath(
- trade: V2Trade | V3Trade
-): RoutingDiagramEntry[] {
- // convert V2 path to a list of routes
- if (trade instanceof V2Trade) {
- const { path: tokenPath } = (trade as V2Trade).route
- const path = []
- for (let i = 1; i < tokenPath.length; i++) {
- path.push([tokenPath[i - 1], tokenPath[i], V2_DEFAULT_FEE_TIER] as RoutingDiagramEntry['path'][0])
- }
- return [{ percent: new Percent(100, 100), path }]
- }
-
- return trade.swaps.map(({ route: { tokenPath, pools }, inputAmount, outputAmount }) => {
+function getTokenPath(trade: Trade): RoutingDiagramEntry[] {
+ return trade.swaps.map(({ route: { path: tokenPath, pools, protocol }, inputAmount, outputAmount }) => {
const portion =
trade.tradeType === TradeType.EXACT_INPUT
? inputAmount.divide(trade.inputAmount)
@@ -94,18 +115,25 @@ function getTokenPath(
const percent = new Percent(portion.numerator, portion.denominator)
- const path: [Currency, Currency, FeeAmount][] = []
+ const path: RoutingDiagramEntry['path'] = []
for (let i = 0; i < pools.length; i++) {
const nextPool = pools[i]
const tokenIn = tokenPath[i]
const tokenOut = tokenPath[i + 1]
- path.push([tokenIn, tokenOut, nextPool.fee])
+ const entry: RoutingDiagramEntry['path'][0] = [
+ tokenIn,
+ tokenOut,
+ nextPool instanceof Pair ? V2_DEFAULT_FEE_TIER : nextPool.fee,
+ ]
+
+ path.push(entry)
}
return {
percent,
path,
+ protocol,
}
})
}
diff --git a/src/components/swap/SwapWarningDropdown.tsx b/src/components/swap/SwapWarningDropdown.tsx
new file mode 100644
index 0000000000..56ac4c0f55
--- /dev/null
+++ b/src/components/swap/SwapWarningDropdown.tsx
@@ -0,0 +1,75 @@
+import { Trans } from '@lingui/macro'
+import { Currency, CurrencyAmount, Token, TradeType } from '@uniswap/sdk-core'
+import { RowBetween } from 'components/Row'
+import { MouseoverTooltipContent } from 'components/Tooltip'
+import { Info } from 'react-feather'
+import { InterfaceTrade } from 'state/routing/types'
+import styled from 'styled-components/macro'
+import { ThemedText } from 'theme'
+
+import { ResponsiveTooltipContainer } from './styleds'
+
+const Wrapper = styled.div`
+ background-color: ${({ theme }) => theme.bg1};
+ border-bottom-left-radius: 20px;
+ border-bottom-right-radius: 20px;
+ padding: 14px;
+ margin-top: -20px;
+ padding-top: 32px;
+`
+
+const StyledInfoIcon = styled(Info)`
+ stroke: ${({ theme }) => theme.text3};
+`
+
+/**
+ * @returns Dropdown card for showing edge case warnings outside of button
+ */
+export default function SwapWarningDropdown({
+ fiatValueInput,
+ trade,
+}: {
+ fiatValueInput: CurrencyAmount | null
+ trade: InterfaceTrade | undefined
+}) {
+ // gas cost estimate is more than half of input value
+ const showNetworkFeeWarning = Boolean(
+ fiatValueInput &&
+ trade?.gasUseEstimateUSD &&
+ parseFloat(trade.gasUseEstimateUSD.toSignificant(6)) > parseFloat(fiatValueInput.toFixed(6)) / 2
+ )
+
+ if (!showNetworkFeeWarning) {
+ return null
+ }
+
+ return (
+
+ {showNetworkFeeWarning ? (
+
+
+ Network fees exceed 50% of the swap amount!
+
+
+
+
+ The cost of sending this transaction is more than half of the value of the input amount.
+
+
+
+ You might consider waiting until the network fees go down to complete this transaction.
+
+
+ }
+ placement="bottom"
+ >
+
+
+
+ ) : null}
+
+ )
+}
diff --git a/src/components/swap/TradePrice.tsx b/src/components/swap/TradePrice.tsx
index 7aaa39f15d..15f3382436 100644
--- a/src/components/swap/TradePrice.tsx
+++ b/src/components/swap/TradePrice.tsx
@@ -13,16 +13,20 @@ interface TradePriceProps {
}
const StyledPriceContainer = styled.button`
- align-items: center;
background-color: transparent;
border: none;
cursor: pointer;
- display: grid;
- height: 24px;
- justify-content: center;
+ align-items: center
+ justify-content: flex-start;
padding: 0;
grid-template-columns: 1fr auto;
grid-gap: 0.25rem;
+ display: flex;
+ flex-direction: row;
+ text-align: left;
+ flex-wrap: wrap;
+ padding: 8px 0;
+ user-select: text;
`
export default function TradePrice({ price, showInverted, setShowInverted }: TradePriceProps) {
@@ -44,8 +48,14 @@ export default function TradePrice({ price, showInverted, setShowInverted }: Tra
const text = `${'1 ' + labelInverted + ' = ' + formattedPrice ?? '-'} ${label}`
return (
-
-
+ {
+ e.stopPropagation() // dont want this click to affect dropdowns / hovers
+ flipPrice()
+ }}
+ title={text}
+ >
+
{text}
{' '}
{usdcPrice && (
diff --git a/src/connectors/index.ts b/src/connectors/index.ts
index 54eaf84ff8..5a242642ba 100644
--- a/src/connectors/index.ts
+++ b/src/connectors/index.ts
@@ -19,7 +19,7 @@ if (typeof INFURA_KEY === 'undefined') {
throw new Error(`REACT_APP_INFURA_KEY must be a defined environment variable`)
}
-const NETWORK_URLS: { [key in SupportedChainId]: string } = {
+export const NETWORK_URLS: { [key in SupportedChainId]: string } = {
[SupportedChainId.MAINNET]: `https://mainnet.infura.io/v3/${INFURA_KEY}`,
[SupportedChainId.RINKEBY]: `https://rinkeby.infura.io/v3/${INFURA_KEY}`,
[SupportedChainId.ROPSTEN]: `https://ropsten.infura.io/v3/${INFURA_KEY}`,
diff --git a/src/constants/addresses.ts b/src/constants/addresses.ts
index a2a84c01ef..513aa77eea 100644
--- a/src/constants/addresses.ts
+++ b/src/constants/addresses.ts
@@ -16,7 +16,20 @@ export const MULTICALL_ADDRESS: AddressMap = {
[SupportedChainId.ARBITRUM_RINKEBY]: '0xa501c031958F579dB7676fF1CE78AD305794d579',
}
export const V2_FACTORY_ADDRESSES: AddressMap = constructSameAddressMap(V2_FACTORY_ADDRESS)
+
export const V2_ROUTER_ADDRESS: AddressMap = constructSameAddressMap('0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D')
+export const V3_ROUTER_ADDRESS: AddressMap = constructSameAddressMap('0xE592427A0AEce92De3Edee1F18E0157C05861564', [
+ SupportedChainId.OPTIMISM,
+ SupportedChainId.OPTIMISTIC_KOVAN,
+ SupportedChainId.ARBITRUM_ONE,
+ SupportedChainId.ARBITRUM_RINKEBY,
+])
+export const SWAP_ROUTER_ADDRESSES: AddressMap = constructSameAddressMap('0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45', [
+ SupportedChainId.OPTIMISM,
+ SupportedChainId.OPTIMISTIC_KOVAN,
+ SupportedChainId.ARBITRUM_ONE,
+ SupportedChainId.ARBITRUM_RINKEBY,
+])
/**
* The oldest V0 governance address
@@ -75,12 +88,7 @@ export const ENS_REGISTRAR_ADDRESSES: AddressMap = {
export const SOCKS_CONTROLLER_ADDRESSES: AddressMap = {
[SupportedChainId.MAINNET]: '0x65770b5283117639760beA3F867b69b3697a91dd',
}
-export const SWAP_ROUTER_ADDRESSES: AddressMap = constructSameAddressMap('0xE592427A0AEce92De3Edee1F18E0157C05861564', [
- SupportedChainId.OPTIMISM,
- SupportedChainId.OPTIMISTIC_KOVAN,
- SupportedChainId.ARBITRUM_ONE,
- SupportedChainId.ARBITRUM_RINKEBY,
-])
+
export const V3_MIGRATOR_ADDRESSES: AddressMap = constructSameAddressMap('0xA5644E29708357803b5A882D272c41cC0dF92B34', [
SupportedChainId.ARBITRUM_ONE,
SupportedChainId.ARBITRUM_RINKEBY,
diff --git a/src/hooks/useApproveCallback.ts b/src/hooks/useApproveCallback.ts
index 5ec7f5c079..4e9ccfadd9 100644
--- a/src/hooks/useApproveCallback.ts
+++ b/src/hooks/useApproveCallback.ts
@@ -1,11 +1,13 @@
import { MaxUint256 } from '@ethersproject/constants'
import { TransactionResponse } from '@ethersproject/providers'
+import { Protocol, Trade } from '@uniswap/router-sdk'
import { Currency, CurrencyAmount, Percent, TradeType } from '@uniswap/sdk-core'
-import { Trade as V2Trade } from '@uniswap/v2-sdk'
-import { Trade as V3Trade } from '@uniswap/v3-sdk'
+import { Pair, Route as V2Route, Trade as V2Trade } from '@uniswap/v2-sdk'
+import { Pool, Route as V3Route, Trade as V3Trade } from '@uniswap/v3-sdk'
import { useCallback, useMemo } from 'react'
+import { getTxOptimizedSwapRouter, SwapRouterVersion } from 'utils/getTxOptimizedSwapRouter'
-import { SWAP_ROUTER_ADDRESSES, V2_ROUTER_ADDRESS } from '../constants/addresses'
+import { SWAP_ROUTER_ADDRESSES, V2_ROUTER_ADDRESS, V3_ROUTER_ADDRESS } from '../constants/addresses'
import { TransactionType } from '../state/transactions/actions'
import { useHasPendingApproval, useTransactionAdder } from '../state/transactions/hooks'
import { calculateGasMargin } from '../utils/calculateGasMargin'
@@ -20,18 +22,14 @@ export enum ApprovalState {
APPROVED = 'APPROVED',
}
-// returns a variable indicating the state of the approval and a function which approves if necessary or early returns
-export function useApproveCallback(
- amountToApprove?: CurrencyAmount,
- spender?: string
-): [ApprovalState, () => Promise] {
- const { account, chainId } = useActiveWeb3React()
+export function useApprovalState(amountToApprove?: CurrencyAmount, spender?: string) {
+ const { account } = useActiveWeb3React()
const token = amountToApprove?.currency?.isToken ? amountToApprove.currency : undefined
+
const currentAllowance = useTokenAllowance(token, account ?? undefined, spender)
const pendingApproval = useHasPendingApproval(token?.address, spender)
- // check the current approval status
- const approvalState: ApprovalState = useMemo(() => {
+ return useMemo(() => {
if (!amountToApprove || !spender) return ApprovalState.UNKNOWN
if (amountToApprove.currency.isNative) return ApprovalState.APPROVED
// we might not have enough data to know whether or not we need to approve
@@ -44,6 +42,40 @@ export function useApproveCallback(
: ApprovalState.NOT_APPROVED
: ApprovalState.APPROVED
}, [amountToApprove, currentAllowance, pendingApproval, spender])
+}
+
+/** Returns approval state for all known swap routers */
+export function useAllApprovalStates(
+ trade: Trade | undefined,
+ allowedSlippage: Percent
+) {
+ const { chainId } = useActiveWeb3React()
+
+ const amountToApprove = useMemo(
+ () => (trade && trade.inputAmount.currency.isToken ? trade.maximumAmountIn(allowedSlippage) : undefined),
+ [trade, allowedSlippage]
+ )
+
+ const v2ApprovalState = useApprovalState(amountToApprove, chainId ? V2_ROUTER_ADDRESS[chainId] : undefined)
+ const v3ApprovalState = useApprovalState(amountToApprove, chainId ? V3_ROUTER_ADDRESS[chainId] : undefined)
+ const v2V3ApprovalState = useApprovalState(amountToApprove, chainId ? SWAP_ROUTER_ADDRESSES[chainId] : undefined)
+
+ return useMemo(
+ () => ({ v2: v2ApprovalState, v3: v3ApprovalState, v2V3: v2V3ApprovalState }),
+ [v2ApprovalState, v2V3ApprovalState, v3ApprovalState]
+ )
+}
+
+// returns a variable indicating the state of the approval and a function which approves if necessary or early returns
+export function useApproveCallback(
+ amountToApprove?: CurrencyAmount,
+ spender?: string
+): [ApprovalState, () => Promise] {
+ const { chainId } = useActiveWeb3React()
+ const token = amountToApprove?.currency?.isToken ? amountToApprove.currency : undefined
+
+ // check the current approval status
+ const approvalState = useApprovalState(amountToApprove, spender)
const tokenContract = useTokenContract(token?.address)
const addTransaction = useTransactionAdder()
@@ -103,23 +135,91 @@ export function useApproveCallback(
// wraps useApproveCallback in the context of a swap
export function useApproveCallbackFromTrade(
- trade: V2Trade | V3Trade | undefined,
+ trade:
+ | V2Trade
+ | V3Trade
+ | Trade
+ | undefined,
allowedSlippage: Percent
) {
const { chainId } = useActiveWeb3React()
- const v3SwapRouterAddress = chainId ? SWAP_ROUTER_ADDRESSES[chainId] : undefined
const amountToApprove = useMemo(
() => (trade && trade.inputAmount.currency.isToken ? trade.maximumAmountIn(allowedSlippage) : undefined),
[trade, allowedSlippage]
)
- return useApproveCallback(
+
+ const approveCallback = useApproveCallback(
amountToApprove,
chainId
? trade instanceof V2Trade
? V2_ROUTER_ADDRESS[chainId]
: trade instanceof V3Trade
- ? v3SwapRouterAddress
- : undefined
+ ? V3_ROUTER_ADDRESS[chainId]
+ : SWAP_ROUTER_ADDRESSES[chainId]
: undefined
)
+
+ // TODO: remove L162-168 after testing is done. This error will help detect mistakes in the logic.
+ if (
+ (Trade instanceof V2Trade && approveCallback[0] !== ApprovalState.APPROVED) ||
+ (trade instanceof V3Trade && approveCallback[0] !== ApprovalState.APPROVED)
+ ) {
+ throw new Error('Trying to approve legacy router')
+ }
+
+ return approveCallback
+}
+
+export function useApprovalOptimizedTrade(
+ trade: Trade | undefined,
+ allowedSlippage: Percent
+):
+ | V2Trade
+ | V3Trade
+ | Trade
+ | undefined {
+ const onlyV2Routes = trade?.routes.every((route) => route.protocol === Protocol.V2)
+ const onlyV3Routes = trade?.routes.every((route) => route.protocol === Protocol.V3)
+ const tradeHasSplits = (trade?.routes.length ?? 0) > 1
+
+ const approvalStates = useAllApprovalStates(trade, allowedSlippage)
+
+ const optimizedSwapRouter = useMemo(
+ () => getTxOptimizedSwapRouter({ onlyV2Routes, onlyV3Routes, tradeHasSplits, approvalStates }),
+ [approvalStates, tradeHasSplits, onlyV2Routes, onlyV3Routes]
+ )
+
+ return useMemo(() => {
+ if (!trade) return undefined
+
+ try {
+ switch (optimizedSwapRouter) {
+ case SwapRouterVersion.V2V3:
+ return trade
+ case SwapRouterVersion.V2:
+ const pairs = trade.swaps[0].route.pools.filter((pool) => pool instanceof Pair) as Pair[]
+ const v2Route = new V2Route(pairs, trade.inputAmount.currency, trade.outputAmount.currency)
+ return new V2Trade(v2Route, trade.inputAmount, trade.tradeType)
+ case SwapRouterVersion.V3:
+ return V3Trade.createUncheckedTradeWithMultipleRoutes({
+ routes: trade.swaps.map(({ route, inputAmount, outputAmount }) => ({
+ route: new V3Route(
+ route.pools.filter((p) => p instanceof Pool) as Pool[],
+ inputAmount.currency,
+ outputAmount.currency
+ ),
+ inputAmount,
+ outputAmount,
+ })),
+ tradeType: trade.tradeType,
+ })
+ default:
+ return undefined
+ }
+ } catch (e) {
+ // TODO(#2989): remove try-catch
+ console.debug(e)
+ return undefined
+ }
+ }, [trade, optimizedSwapRouter])
}
diff --git a/src/hooks/useAutoRouterSupported.tsx b/src/hooks/useAutoRouterSupported.tsx
new file mode 100644
index 0000000000..64c616d1c6
--- /dev/null
+++ b/src/hooks/useAutoRouterSupported.tsx
@@ -0,0 +1,8 @@
+import { AUTO_ROUTER_SUPPORTED_CHAINS } from 'state/routing/clientSideSmartOrderRouter/constants'
+
+import { useActiveWeb3React } from './web3'
+
+export default function useAutoRouterSupported(): boolean {
+ const { chainId } = useActiveWeb3React()
+ return Boolean(chainId && AUTO_ROUTER_SUPPORTED_CHAINS.includes(chainId))
+}
diff --git a/src/hooks/useBestV3Trade.test.ts b/src/hooks/useBestTrade.test.ts
similarity index 52%
rename from src/hooks/useBestV3Trade.test.ts
rename to src/hooks/useBestTrade.test.ts
index c09322daed..b15c423d57 100644
--- a/src/hooks/useBestV3Trade.test.ts
+++ b/src/hooks/useBestTrade.test.ts
@@ -1,11 +1,11 @@
import { renderHook } from '@testing-library/react-hooks'
import { CurrencyAmount, TradeType } from '@uniswap/sdk-core'
import { DAI, USDC } from 'constants/tokens'
-import { V3TradeState } from 'state/routing/types'
-import { useRoutingAPIEnabled } from 'state/user/hooks'
+import { TradeState } from 'state/routing/types'
import { useRoutingAPITrade } from '../state/routing/useRoutingAPITrade'
-import { useBestV3Trade } from './useBestV3Trade'
+import useAutoRouterSupported from './useAutoRouterSupported'
+import { useBestTrade } from './useBestTrade'
import { useClientSideV3Trade } from './useClientSideV3Trade'
import useDebounce from './useDebounce'
import useIsWindowVisible from './useIsWindowVisible'
@@ -13,31 +13,27 @@ import useIsWindowVisible from './useIsWindowVisible'
const USDCAmount = CurrencyAmount.fromRawAmount(USDC, '10000')
const DAIAmount = CurrencyAmount.fromRawAmount(DAI, '10000')
+jest.mock('./useAutoRouterSupported')
+jest.mock('./useClientSideV3Trade')
jest.mock('./useDebounce')
-const mockUseDebounce = useDebounce as jest.MockedFunction
-
-// mock modules containing hooks
+jest.mock('./useIsWindowVisible')
jest.mock('state/routing/useRoutingAPITrade')
-jest.mock('./useClientSideV3Trade')
jest.mock('state/user/hooks')
-jest.mock('./useIsWindowVisible')
-const mockUseRoutingAPIEnabled = useRoutingAPIEnabled as jest.MockedFunction
+const mockUseDebounce = useDebounce as jest.MockedFunction
+const mockUseAutoRouterSupported = useAutoRouterSupported as jest.MockedFunction
const mockUseIsWindowVisible = useIsWindowVisible as jest.MockedFunction
-// useRouterTrade mocks
const mockUseRoutingAPITrade = useRoutingAPITrade as jest.MockedFunction
-
-// useClientSideV3Trade mocks
const mockUseClientSideV3Trade = useClientSideV3Trade as jest.MockedFunction
// helpers to set mock expectations
-const expectRouterMock = (state: V3TradeState) => {
- mockUseRoutingAPITrade.mockReturnValue({ state, trade: null })
+const expectRouterMock = (state: TradeState) => {
+ mockUseRoutingAPITrade.mockReturnValue({ state, trade: undefined })
}
-const expectClientSideMock = (state: V3TradeState) => {
- mockUseClientSideV3Trade.mockReturnValue({ state, trade: null })
+const expectClientSideMock = (state: TradeState) => {
+ mockUseClientSideV3Trade.mockReturnValue({ state, trade: undefined })
}
beforeEach(() => {
@@ -45,156 +41,156 @@ beforeEach(() => {
mockUseDebounce.mockImplementation((value) => value)
mockUseIsWindowVisible.mockReturnValue(true)
- mockUseRoutingAPIEnabled.mockReturnValue(true)
+ mockUseAutoRouterSupported.mockReturnValue(true)
})
-describe('#useBestV3TradeExactIn', () => {
- it('does not compute routing api trade when routing API is disabled', () => {
- mockUseRoutingAPIEnabled.mockReturnValue(false)
- expectRouterMock(V3TradeState.INVALID)
- expectClientSideMock(V3TradeState.VALID)
+describe('#useBestV3Trade ExactIn', () => {
+ it('does not compute routing api trade when routing API is not supported', () => {
+ mockUseAutoRouterSupported.mockReturnValue(false)
+ expectRouterMock(TradeState.INVALID)
+ expectClientSideMock(TradeState.VALID)
- const { result } = renderHook(() => useBestV3Trade(TradeType.EXACT_INPUT, USDCAmount, DAI))
+ const { result } = renderHook(() => useBestTrade(TradeType.EXACT_INPUT, USDCAmount, DAI))
expect(mockUseRoutingAPITrade).toHaveBeenCalledWith(TradeType.EXACT_INPUT, undefined, DAI)
expect(mockUseClientSideV3Trade).toHaveBeenCalledWith(TradeType.EXACT_INPUT, USDCAmount, DAI)
- expect(result.current).toEqual({ state: V3TradeState.VALID, trade: null })
+ expect(result.current).toEqual({ state: TradeState.VALID, trade: undefined })
})
it('does not compute routing api trade when window is not focused', () => {
mockUseIsWindowVisible.mockReturnValue(false)
- expectRouterMock(V3TradeState.NO_ROUTE_FOUND)
- expectClientSideMock(V3TradeState.VALID)
+ expectRouterMock(TradeState.NO_ROUTE_FOUND)
+ expectClientSideMock(TradeState.VALID)
- const { result } = renderHook(() => useBestV3Trade(TradeType.EXACT_INPUT, USDCAmount, DAI))
+ const { result } = renderHook(() => useBestTrade(TradeType.EXACT_INPUT, USDCAmount, DAI))
expect(mockUseRoutingAPITrade).toHaveBeenCalledWith(TradeType.EXACT_INPUT, undefined, DAI)
expect(mockUseClientSideV3Trade).toHaveBeenCalledWith(TradeType.EXACT_INPUT, USDCAmount, DAI)
- expect(result.current).toEqual({ state: V3TradeState.VALID, trade: null })
+ expect(result.current).toEqual({ state: TradeState.VALID, trade: undefined })
})
describe('when routing api is in non-error state', () => {
it('does not compute client side v3 trade if routing api is LOADING', () => {
- expectRouterMock(V3TradeState.LOADING)
+ expectRouterMock(TradeState.LOADING)
- const { result } = renderHook(() => useBestV3Trade(TradeType.EXACT_INPUT, USDCAmount, DAI))
+ const { result } = renderHook(() => useBestTrade(TradeType.EXACT_INPUT, USDCAmount, DAI))
expect(mockUseClientSideV3Trade).toHaveBeenCalledWith(TradeType.EXACT_INPUT, undefined, undefined)
- expect(result.current).toEqual({ state: V3TradeState.LOADING, trade: null })
+ expect(result.current).toEqual({ state: TradeState.LOADING, trade: undefined })
})
it('does not compute client side v3 trade if routing api is VALID', () => {
- expectRouterMock(V3TradeState.VALID)
+ expectRouterMock(TradeState.VALID)
- const { result } = renderHook(() => useBestV3Trade(TradeType.EXACT_INPUT, USDCAmount, DAI))
+ const { result } = renderHook(() => useBestTrade(TradeType.EXACT_INPUT, USDCAmount, DAI))
expect(mockUseClientSideV3Trade).toHaveBeenCalledWith(TradeType.EXACT_INPUT, undefined, undefined)
- expect(result.current).toEqual({ state: V3TradeState.VALID, trade: null })
+ expect(result.current).toEqual({ state: TradeState.VALID, trade: undefined })
})
it('does not compute client side v3 trade if routing api is SYNCING', () => {
- expectRouterMock(V3TradeState.SYNCING)
+ expectRouterMock(TradeState.SYNCING)
- const { result } = renderHook(() => useBestV3Trade(TradeType.EXACT_INPUT, USDCAmount, DAI))
+ const { result } = renderHook(() => useBestTrade(TradeType.EXACT_INPUT, USDCAmount, DAI))
expect(mockUseClientSideV3Trade).toHaveBeenCalledWith(TradeType.EXACT_INPUT, undefined, undefined)
- expect(result.current).toEqual({ state: V3TradeState.SYNCING, trade: null })
+ expect(result.current).toEqual({ state: TradeState.SYNCING, trade: undefined })
})
})
describe('when routing api is in error state', () => {
it('does not compute client side v3 trade if routing api is INVALID', () => {
- expectRouterMock(V3TradeState.INVALID)
- expectClientSideMock(V3TradeState.VALID)
+ expectRouterMock(TradeState.INVALID)
+ expectClientSideMock(TradeState.VALID)
- renderHook(() => useBestV3Trade(TradeType.EXACT_INPUT, USDCAmount, DAI))
+ renderHook(() => useBestTrade(TradeType.EXACT_INPUT, USDCAmount, DAI))
expect(mockUseClientSideV3Trade).toHaveBeenCalledWith(TradeType.EXACT_INPUT, undefined, undefined)
})
it('computes client side v3 trade if routing api is NO_ROUTE_FOUND', () => {
- expectRouterMock(V3TradeState.NO_ROUTE_FOUND)
- expectClientSideMock(V3TradeState.VALID)
+ expectRouterMock(TradeState.NO_ROUTE_FOUND)
+ expectClientSideMock(TradeState.VALID)
- const { result } = renderHook(() => useBestV3Trade(TradeType.EXACT_INPUT, USDCAmount, DAI))
+ const { result } = renderHook(() => useBestTrade(TradeType.EXACT_INPUT, USDCAmount, DAI))
expect(mockUseClientSideV3Trade).toHaveBeenCalledWith(TradeType.EXACT_INPUT, USDCAmount, DAI)
- expect(result.current).toEqual({ state: V3TradeState.VALID, trade: null })
+ expect(result.current).toEqual({ state: TradeState.VALID, trade: undefined })
})
})
})
-describe('#useBestV3TradeExactOut', () => {
- it('does not compute routing api trade when routing API is disabled', () => {
- mockUseRoutingAPIEnabled.mockReturnValue(false)
- expectRouterMock(V3TradeState.INVALID)
- expectClientSideMock(V3TradeState.VALID)
+describe('#useBestV3Trade ExactOut', () => {
+ it('does not compute routing api trade when routing API is not supported', () => {
+ mockUseAutoRouterSupported.mockReturnValue(false)
+ expectRouterMock(TradeState.INVALID)
+ expectClientSideMock(TradeState.VALID)
- const { result } = renderHook(() => useBestV3Trade(TradeType.EXACT_OUTPUT, DAIAmount, USDC))
+ const { result } = renderHook(() => useBestTrade(TradeType.EXACT_OUTPUT, DAIAmount, USDC))
expect(mockUseRoutingAPITrade).toHaveBeenCalledWith(TradeType.EXACT_OUTPUT, undefined, USDC)
expect(mockUseClientSideV3Trade).toHaveBeenCalledWith(TradeType.EXACT_OUTPUT, DAIAmount, USDC)
- expect(result.current).toEqual({ state: V3TradeState.VALID, trade: null })
+ expect(result.current).toEqual({ state: TradeState.VALID, trade: undefined })
})
it('does not compute routing api trade when window is not focused', () => {
mockUseIsWindowVisible.mockReturnValue(false)
- expectRouterMock(V3TradeState.NO_ROUTE_FOUND)
- expectClientSideMock(V3TradeState.VALID)
+ expectRouterMock(TradeState.NO_ROUTE_FOUND)
+ expectClientSideMock(TradeState.VALID)
- const { result } = renderHook(() => useBestV3Trade(TradeType.EXACT_OUTPUT, DAIAmount, USDC))
+ const { result } = renderHook(() => useBestTrade(TradeType.EXACT_OUTPUT, DAIAmount, USDC))
expect(mockUseRoutingAPITrade).toHaveBeenCalledWith(TradeType.EXACT_OUTPUT, undefined, USDC)
expect(mockUseClientSideV3Trade).toHaveBeenCalledWith(TradeType.EXACT_OUTPUT, DAIAmount, USDC)
- expect(result.current).toEqual({ state: V3TradeState.VALID, trade: null })
+ expect(result.current).toEqual({ state: TradeState.VALID, trade: undefined })
})
describe('when routing api is in non-error state', () => {
it('does not compute client side v3 trade if routing api is LOADING', () => {
- expectRouterMock(V3TradeState.LOADING)
+ expectRouterMock(TradeState.LOADING)
- const { result } = renderHook(() => useBestV3Trade(TradeType.EXACT_OUTPUT, DAIAmount, USDC))
+ const { result } = renderHook(() => useBestTrade(TradeType.EXACT_OUTPUT, DAIAmount, USDC))
expect(mockUseClientSideV3Trade).toHaveBeenCalledWith(TradeType.EXACT_OUTPUT, undefined, undefined)
- expect(result.current).toEqual({ state: V3TradeState.LOADING, trade: null })
+ expect(result.current).toEqual({ state: TradeState.LOADING, trade: undefined })
})
it('does not compute client side v3 trade if routing api is VALID', () => {
- expectRouterMock(V3TradeState.VALID)
+ expectRouterMock(TradeState.VALID)
- const { result } = renderHook(() => useBestV3Trade(TradeType.EXACT_OUTPUT, DAIAmount, USDC))
+ const { result } = renderHook(() => useBestTrade(TradeType.EXACT_OUTPUT, DAIAmount, USDC))
expect(mockUseClientSideV3Trade).toHaveBeenCalledWith(TradeType.EXACT_OUTPUT, undefined, undefined)
- expect(result.current).toEqual({ state: V3TradeState.VALID, trade: null })
+ expect(result.current).toEqual({ state: TradeState.VALID, trade: undefined })
})
it('does not compute client side v3 trade if routing api is SYNCING', () => {
- expectRouterMock(V3TradeState.SYNCING)
+ expectRouterMock(TradeState.SYNCING)
- const { result } = renderHook(() => useBestV3Trade(TradeType.EXACT_OUTPUT, DAIAmount, USDC))
+ const { result } = renderHook(() => useBestTrade(TradeType.EXACT_OUTPUT, DAIAmount, USDC))
expect(mockUseClientSideV3Trade).toHaveBeenCalledWith(TradeType.EXACT_OUTPUT, undefined, undefined)
- expect(result.current).toEqual({ state: V3TradeState.SYNCING, trade: null })
+ expect(result.current).toEqual({ state: TradeState.SYNCING, trade: undefined })
})
})
describe('when routing api is in error state', () => {
it('computes client side v3 trade if routing api is INVALID', () => {
- expectRouterMock(V3TradeState.INVALID)
- expectClientSideMock(V3TradeState.VALID)
+ expectRouterMock(TradeState.INVALID)
+ expectClientSideMock(TradeState.VALID)
- renderHook(() => useBestV3Trade(TradeType.EXACT_OUTPUT, DAIAmount, USDC))
+ renderHook(() => useBestTrade(TradeType.EXACT_OUTPUT, DAIAmount, USDC))
expect(mockUseClientSideV3Trade).toHaveBeenCalledWith(TradeType.EXACT_OUTPUT, undefined, undefined)
})
it('computes client side v3 trade if routing api is NO_ROUTE_FOUND', () => {
- expectRouterMock(V3TradeState.NO_ROUTE_FOUND)
- expectClientSideMock(V3TradeState.VALID)
+ expectRouterMock(TradeState.NO_ROUTE_FOUND)
+ expectClientSideMock(TradeState.VALID)
- const { result } = renderHook(() => useBestV3Trade(TradeType.EXACT_OUTPUT, DAIAmount, USDC))
+ const { result } = renderHook(() => useBestTrade(TradeType.EXACT_OUTPUT, DAIAmount, USDC))
expect(mockUseClientSideV3Trade).toHaveBeenCalledWith(TradeType.EXACT_OUTPUT, DAIAmount, USDC)
- expect(result.current).toEqual({ state: V3TradeState.VALID, trade: null })
+ expect(result.current).toEqual({ state: TradeState.VALID, trade: undefined })
})
})
})
diff --git a/src/hooks/useBestV3Trade.ts b/src/hooks/useBestTrade.ts
similarity index 69%
rename from src/hooks/useBestV3Trade.ts
rename to src/hooks/useBestTrade.ts
index bf650163a6..4d8183e5d4 100644
--- a/src/hooks/useBestV3Trade.ts
+++ b/src/hooks/useBestTrade.ts
@@ -1,36 +1,34 @@
import { Currency, CurrencyAmount, TradeType } from '@uniswap/sdk-core'
-import { Trade } from '@uniswap/v3-sdk'
-import { V3TradeState } from 'state/routing/types'
+import { InterfaceTrade, TradeState } from 'state/routing/types'
import { useRoutingAPITrade } from 'state/routing/useRoutingAPITrade'
-import { useRoutingAPIEnabled } from 'state/user/hooks'
+import useAutoRouterSupported from './useAutoRouterSupported'
import { useClientSideV3Trade } from './useClientSideV3Trade'
import useDebounce from './useDebounce'
import useIsWindowVisible from './useIsWindowVisible'
/**
- * Returns the best v3 trade for a desired swap.
- * Uses optimized routes from the Routing API and falls back to the v3 router.
+ * Returns the best v2+v3 trade for a desired swap.
* @param tradeType whether the swap is an exact in/out
* @param amountSpecified the exact amount to swap in/out
* @param otherCurrency the desired output/payment currency
*/
-export function useBestV3Trade(
+export function useBestTrade(
tradeType: TradeType,
amountSpecified?: CurrencyAmount,
otherCurrency?: Currency
): {
- state: V3TradeState
- trade: Trade | null
+ state: TradeState
+ trade: InterfaceTrade | undefined
} {
- const routingAPIEnabled = useRoutingAPIEnabled()
+ const autoRouterSupported = useAutoRouterSupported()
const isWindowVisible = useIsWindowVisible()
const [debouncedAmount, debouncedOtherCurrency] = useDebounce([amountSpecified, otherCurrency], 200)
const routingAPITrade = useRoutingAPITrade(
tradeType,
- routingAPIEnabled && isWindowVisible ? debouncedAmount : undefined,
+ autoRouterSupported && isWindowVisible ? debouncedAmount : undefined,
debouncedOtherCurrency
)
@@ -48,18 +46,19 @@ export function useBestV3Trade(
!amountSpecified.currency.equals(routingAPITrade.trade.outputAmount.currency) ||
!debouncedOtherCurrency?.equals(routingAPITrade.trade.inputAmount.currency))
- const useFallback = !routingAPIEnabled || (!debouncing && routingAPITrade.state === V3TradeState.NO_ROUTE_FOUND)
+ const useFallback = !autoRouterSupported || (!debouncing && routingAPITrade.state === TradeState.NO_ROUTE_FOUND)
- // only use client side router if routing api trade failed
+ // only use client side router if routing api trade failed or is not supported
const bestV3Trade = useClientSideV3Trade(
tradeType,
useFallback ? debouncedAmount : undefined,
useFallback ? debouncedOtherCurrency : undefined
)
+ // only return gas estimate from api if routing api trade is used
return {
...(useFallback ? bestV3Trade : routingAPITrade),
- ...(debouncing ? { state: V3TradeState.SYNCING } : {}),
- ...(isLoading ? { state: V3TradeState.LOADING } : {}),
+ ...(debouncing ? { state: TradeState.SYNCING } : {}),
+ ...(isLoading ? { state: TradeState.LOADING } : {}),
}
}
diff --git a/src/hooks/useClientSideV3Trade.ts b/src/hooks/useClientSideV3Trade.ts
index d2e498ff5a..0a3d890a16 100644
--- a/src/hooks/useClientSideV3Trade.ts
+++ b/src/hooks/useClientSideV3Trade.ts
@@ -1,9 +1,9 @@
import { Currency, CurrencyAmount, TradeType } from '@uniswap/sdk-core'
-import { Route, SwapQuoter, Trade } from '@uniswap/v3-sdk'
+import { Route, SwapQuoter } from '@uniswap/v3-sdk'
import { SupportedChainId } from 'constants/chains'
import JSBI from 'jsbi'
import { useMemo } from 'react'
-import { V3TradeState } from 'state/routing/types'
+import { InterfaceTrade, TradeState } from 'state/routing/types'
import { useSingleContractWithCallData } from '../state/multicall/hooks'
import { useAllV3Routes } from './useAllV3Routes'
@@ -27,7 +27,7 @@ export function useClientSideV3Trade(
tradeType: TTradeType,
amountSpecified?: CurrencyAmount,
otherCurrency?: Currency
-): { state: V3TradeState; trade: Trade | null } {
+): { state: TradeState; trade: InterfaceTrade | undefined } {
const [currencyIn, currencyOut] = useMemo(
() =>
tradeType === TradeType.EXACT_INPUT
@@ -61,15 +61,15 @@ export function useClientSideV3Trade(
: amountSpecified.currency.equals(currencyIn))
) {
return {
- state: V3TradeState.INVALID,
- trade: null,
+ state: TradeState.INVALID,
+ trade: undefined,
}
}
if (routesLoading || quotesResults.some(({ loading }) => loading)) {
return {
- state: V3TradeState.LOADING,
- trade: null,
+ state: TradeState.LOADING,
+ trade: undefined,
}
}
@@ -117,18 +117,23 @@ export function useClientSideV3Trade(
if (!bestRoute || !amountIn || !amountOut) {
return {
- state: V3TradeState.NO_ROUTE_FOUND,
- trade: null,
+ state: TradeState.NO_ROUTE_FOUND,
+ trade: undefined,
}
}
return {
- state: V3TradeState.VALID,
- trade: Trade.createUncheckedTrade({
- route: bestRoute,
+ state: TradeState.VALID,
+ trade: new InterfaceTrade({
+ v2Routes: [],
+ v3Routes: [
+ {
+ routev3: bestRoute,
+ inputAmount: amountIn,
+ outputAmount: amountOut,
+ },
+ ],
tradeType,
- inputAmount: amountIn,
- outputAmount: amountOut,
}),
}
}, [amountSpecified, currencyIn, currencyOut, quotesResults, routes, routesLoading, tradeType])
diff --git a/src/hooks/useERC20Permit.ts b/src/hooks/useERC20Permit.ts
index 3af4a338d0..f5ca9b484d 100644
--- a/src/hooks/useERC20Permit.ts
+++ b/src/hooks/useERC20Permit.ts
@@ -1,11 +1,12 @@
import { splitSignature } from '@ethersproject/bytes'
+import { Trade } from '@uniswap/router-sdk'
import { Currency, CurrencyAmount, Percent, Token, TradeType } from '@uniswap/sdk-core'
import { Trade as V2Trade } from '@uniswap/v2-sdk'
import { Trade as V3Trade } from '@uniswap/v3-sdk'
import JSBI from 'jsbi'
import { useMemo, useState } from 'react'
-import { SWAP_ROUTER_ADDRESSES } from '../constants/addresses'
+import { SWAP_ROUTER_ADDRESSES, V3_ROUTER_ADDRESS } from '../constants/addresses'
import { DAI, UNI, USDC } from '../constants/tokens'
import { useSingleCallResult } from '../state/multicall/hooks'
import { useEIP2612Contract } from './useContract'
@@ -272,20 +273,26 @@ export function useV2LiquidityTokenPermit(
}
export function useERC20PermitFromTrade(
- trade: V2Trade | V3Trade | undefined,
+ trade:
+ | V2Trade
+ | V3Trade
+ | Trade
+ | undefined,
allowedSlippage: Percent
) {
const { chainId } = useActiveWeb3React()
- const swapRouterAddress = chainId ? SWAP_ROUTER_ADDRESSES[chainId] : undefined
+ const swapRouterAddress = chainId
+ ? // v2 router does not support
+ trade instanceof V2Trade
+ ? undefined
+ : trade instanceof V3Trade
+ ? V3_ROUTER_ADDRESS[chainId]
+ : SWAP_ROUTER_ADDRESSES[chainId]
+ : undefined
const amountToApprove = useMemo(
() => (trade ? trade.maximumAmountIn(allowedSlippage) : undefined),
[trade, allowedSlippage]
)
- return useERC20Permit(
- amountToApprove,
- // v2 router does not support
- trade instanceof V2Trade ? undefined : trade instanceof V3Trade ? swapRouterAddress : undefined,
- null
- )
+ return useERC20Permit(amountToApprove, swapRouterAddress, null)
}
diff --git a/src/hooks/useSwapCallback.tsx b/src/hooks/useSwapCallback.tsx
index 41f1f2fec1..8c56838a34 100644
--- a/src/hooks/useSwapCallback.tsx
+++ b/src/hooks/useSwapCallback.tsx
@@ -1,12 +1,13 @@
import { BigNumber } from '@ethersproject/bignumber'
// eslint-disable-next-line no-restricted-imports
import { t, Trans } from '@lingui/macro'
+import { SwapRouter, Trade } from '@uniswap/router-sdk'
import { Currency, Percent, TradeType } from '@uniswap/sdk-core'
-import { Router, Trade as V2Trade } from '@uniswap/v2-sdk'
-import { SwapRouter, Trade as V3Trade } from '@uniswap/v3-sdk'
+import { Router as V2SwapRouter, Trade as V2Trade } from '@uniswap/v2-sdk'
+import { SwapRouter as V3SwapRouter, Trade as V3Trade } from '@uniswap/v3-sdk'
import { ReactNode, useMemo } from 'react'
-import { SWAP_ROUTER_ADDRESSES } from '../constants/addresses'
+import { SWAP_ROUTER_ADDRESSES, V3_ROUTER_ADDRESS } from '../constants/addresses'
import { TransactionType } from '../state/transactions/actions'
import { useTransactionAdder } from '../state/transactions/hooks'
import approveAmountCalldata from '../utils/approveAmountCalldata'
@@ -20,6 +21,11 @@ import { SignatureData } from './useERC20Permit'
import useTransactionDeadline from './useTransactionDeadline'
import { useActiveWeb3React } from './web3'
+type AnyTrade =
+ | V2Trade
+ | V3Trade
+ | Trade
+
enum SwapCallbackState {
INVALID,
LOADING,
@@ -45,7 +51,6 @@ interface FailedCall extends SwapCallEstimate {
call: SwapCall
error: Error
}
-
/**
* Returns the swap calls that can be used to make the trade
* @param trade trade to execute
@@ -54,7 +59,7 @@ interface FailedCall extends SwapCallEstimate {
* @param signatureData the signature data of the permit of the input token amount, if available
*/
function useSwapCallArguments(
- trade: V2Trade | V3Trade | undefined, // trade to execute, required
+ trade: AnyTrade | undefined, // trade to execute, required
allowedSlippage: Percent, // in bips
recipientAddressOrName: string | null, // the ENS name or address of the recipient of the trade, or null if swap should be returned to sender
signatureData: SignatureData | null | undefined
@@ -75,7 +80,7 @@ function useSwapCallArguments(
const swapMethods = []
swapMethods.push(
- Router.swapCallParameters(trade, {
+ V2SwapRouter.swapCallParameters(trade, {
feeOnTransfer: false,
allowedSlippage,
recipient,
@@ -85,7 +90,7 @@ function useSwapCallArguments(
if (trade.tradeType === TradeType.EXACT_INPUT) {
swapMethods.push(
- Router.swapCallParameters(trade, {
+ V2SwapRouter.swapCallParameters(trade, {
feeOnTransfer: true,
allowedSlippage,
recipient,
@@ -118,14 +123,10 @@ function useSwapCallArguments(
}
})
} else {
- // trade is V3Trade
- const swapRouterAddress = chainId ? SWAP_ROUTER_ADDRESSES[chainId] : undefined
- if (!swapRouterAddress) return []
-
- const { value, calldata } = SwapRouter.swapCallParameters(trade, {
+ // swap options shared by v3 and v2+v3 swap routers
+ const sharedSwapOptions = {
recipient,
slippageTolerance: allowedSlippage,
- deadline: deadline.toString(),
...(signatureData
? {
inputTokenPermit:
@@ -146,7 +147,26 @@ function useSwapCallArguments(
},
}
: {}),
- })
+ }
+
+ const swapRouterAddress = chainId
+ ? trade instanceof V3Trade
+ ? V3_ROUTER_ADDRESS[chainId]
+ : SWAP_ROUTER_ADDRESSES[chainId]
+ : undefined
+ if (!swapRouterAddress) return []
+
+ const { value, calldata } =
+ trade instanceof V3Trade
+ ? V3SwapRouter.swapCallParameters(trade, {
+ ...sharedSwapOptions,
+ deadline: deadline.toString(),
+ })
+ : SwapRouter.swapCallParameters(trade, {
+ ...sharedSwapOptions,
+ deadlineOrPreviousBlockhash: deadline.toString(),
+ })
+
if (argentWalletContract && trade.inputAmount.currency.isToken) {
return [
{
@@ -174,16 +194,16 @@ function useSwapCallArguments(
]
}
}, [
+ trade,
+ recipient,
+ library,
account,
- allowedSlippage,
- argentWalletContract,
chainId,
deadline,
- library,
- recipient,
routerContract,
+ allowedSlippage,
+ argentWalletContract,
signatureData,
- trade,
])
}
@@ -267,7 +287,7 @@ function swapErrorToUserReadableMessage(error: any): ReactNode {
// 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(
- trade: V2Trade | V3Trade | undefined, // trade to execute, required
+ trade: AnyTrade | undefined, // trade to execute, required
allowedSlippage: Percent, // in bips
recipientAddressOrName: string | null, // the ENS name or address of the recipient of the trade, or null if swap should be returned to sender
signatureData: SignatureData | undefined | null
diff --git a/src/hooks/useSwapSlippageTolerance.ts b/src/hooks/useSwapSlippageTolerance.ts
index 4434d6f2e4..b678c7f75d 100644
--- a/src/hooks/useSwapSlippageTolerance.ts
+++ b/src/hooks/useSwapSlippageTolerance.ts
@@ -1,9 +1,10 @@
+import { Trade } from '@uniswap/router-sdk'
import { Currency, CurrencyAmount, Percent, TradeType } from '@uniswap/sdk-core'
-import { Trade as V2Trade } from '@uniswap/v2-sdk'
-import { Trade as V3Trade } from '@uniswap/v3-sdk'
+import { SUPPORTED_GAS_ESTIMATE_CHAIN_IDS } from 'components/swap/GasEstimateBadge'
import { L2_CHAIN_IDS } from 'constants/chains'
import JSBI from 'jsbi'
import { useMemo } from 'react'
+import { InterfaceTrade } from 'state/routing/types'
import { useUserSlippageToleranceWithDefault } from '../state/user/hooks'
import { useCurrency } from './Tokens'
@@ -11,7 +12,6 @@ import useGasPrice from './useGasPrice'
import useUSDCPrice, { useUSDCValue } from './useUSDCPrice'
import { useActiveWeb3React } from './web3'
-const V2_SWAP_DEFAULT_SLIPPAGE = new Percent(50, 10_000) // .50%
const V3_SWAP_DEFAULT_SLIPPAGE = new Percent(50, 10_000) // .50%
const ONE_TENTHS_PERCENT = new Percent(10, 10_000) // .10%
@@ -19,12 +19,8 @@ const ONE_TENTHS_PERCENT = new Percent(10, 10_000) // .10%
* Return a guess of the gas cost used in computing slippage tolerance for a given trade
* @param trade the trade for which to _guess_ the amount of gas it would cost to execute
*/
-function guesstimateGas(
- trade: V2Trade | V3Trade | undefined
-): number | undefined {
- if (trade instanceof V2Trade) {
- return 90_000 + trade.route.pairs.length * 30_000
- } else if (trade instanceof V3Trade) {
+function guesstimateGas(trade: Trade | undefined): number | undefined {
+ if (!!trade) {
return 100_000 + trade.swaps.reduce((memo, swap) => swap.route.pools.length + memo, 0) * 30_000
}
return undefined
@@ -34,7 +30,7 @@ const MIN_AUTO_SLIPPAGE_TOLERANCE = new Percent(5, 1000) // 0.5%
const MAX_AUTO_SLIPPAGE_TOLERANCE = new Percent(25, 100) // 25%
export default function useSwapSlippageTolerance(
- trade: V2Trade | V3Trade | undefined
+ trade: InterfaceTrade | undefined
): Percent {
const { chainId } = useActiveWeb3React()
const onL2 = chainId && L2_CHAIN_IDS.includes(chainId)
@@ -53,19 +49,26 @@ export default function useSwapSlippageTolerance(
const dollarGasCost =
ether && ethGasCost && etherPrice ? etherPrice.quote(CurrencyAmount.fromRawAmount(ether, ethGasCost)) : undefined
- if (outputDollarValue && dollarGasCost) {
+ // if valid estimate from api and using api trade, use gas estimate from api
+ // NOTE - dont use gas estimate for L2s yet - need to verify accuracy
+ // if not, use local heuristic
+ const dollarCostToUse =
+ chainId && SUPPORTED_GAS_ESTIMATE_CHAIN_IDS.includes(chainId) && trade?.gasUseEstimateUSD
+ ? trade.gasUseEstimateUSD
+ : dollarGasCost
+
+ if (outputDollarValue && dollarCostToUse) {
// the rationale is that a user will not want their trade to fail for a loss due to slippage that is less than
// the cost of the gas of the failed transaction
- const fraction = dollarGasCost.asFraction.divide(outputDollarValue.asFraction)
+ const fraction = dollarCostToUse.asFraction.divide(outputDollarValue.asFraction)
const result = new Percent(fraction.numerator, fraction.denominator)
if (result.greaterThan(MAX_AUTO_SLIPPAGE_TOLERANCE)) return MAX_AUTO_SLIPPAGE_TOLERANCE
if (result.lessThan(MIN_AUTO_SLIPPAGE_TOLERANCE)) return MIN_AUTO_SLIPPAGE_TOLERANCE
return result
}
- if (trade instanceof V2Trade) return V2_SWAP_DEFAULT_SLIPPAGE
return V3_SWAP_DEFAULT_SLIPPAGE
- }, [ethGasPrice, ether, etherPrice, gasEstimate, onL2, outputDollarValue, trade])
+ }, [trade, onL2, ethGasPrice, gasEstimate, ether, etherPrice, chainId, outputDollarValue])
return useUserSlippageToleranceWithDefault(defaultSlippageTolerance)
}
diff --git a/src/hooks/useToggledVersion.ts b/src/hooks/useToggledVersion.ts
deleted file mode 100644
index 71f3d5359e..0000000000
--- a/src/hooks/useToggledVersion.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import useParsedQueryString from './useParsedQueryString'
-
-export enum Version {
- v2 = 'V2',
- v3 = 'V3',
-}
-
-export default function useToggledVersion(): Version | undefined {
- const { use } = useParsedQueryString()
- if (typeof use !== 'string') {
- return undefined
- }
- switch (use.toLowerCase()) {
- case 'v2':
- return Version.v2
- case 'v3':
- return Version.v3
- default:
- return undefined
- }
-}
diff --git a/src/hooks/useUSDCPrice.ts b/src/hooks/useUSDCPrice.ts
index 5ca74ebabe..4d1a941359 100644
--- a/src/hooks/useUSDCPrice.ts
+++ b/src/hooks/useUSDCPrice.ts
@@ -1,5 +1,6 @@
import { Currency, CurrencyAmount, Price, Token, TradeType } from '@uniswap/sdk-core'
import { useMemo } from 'react'
+import { tryParseAmount } from 'state/swap/hooks'
import { SupportedChainId } from '../constants/chains'
import { DAI_OPTIMISM, USDC, USDC_ARBITRUM } from '../constants/tokens'
@@ -9,7 +10,7 @@ import { useActiveWeb3React } from './web3'
// Stablecoin amounts used when calculating spot price for a given currency.
// The amount is large enough to filter low liquidity pairs.
-const STABLECOIN_AMOUNT_OUT: { [chainId: number]: CurrencyAmount } = {
+export const STABLECOIN_AMOUNT_OUT: { [chainId: number]: CurrencyAmount } = {
[SupportedChainId.MAINNET]: CurrencyAmount.fromRawAmount(USDC, 100_000e6),
[SupportedChainId.ARBITRUM_ONE]: CurrencyAmount.fromRawAmount(USDC_ARBITRUM, 10_000e6),
[SupportedChainId.OPTIMISM]: CurrencyAmount.fromRawAmount(DAI_OPTIMISM, 10_000e18),
@@ -20,11 +21,12 @@ const STABLECOIN_AMOUNT_OUT: { [chainId: number]: CurrencyAmount } = {
* @param currency currency to compute the USDC price of
*/
export default function useUSDCPrice(currency?: Currency): Price | undefined {
- const { chainId } = useActiveWeb3React()
+ const chainId = currency?.chainId
const amountOut = chainId ? STABLECOIN_AMOUNT_OUT[chainId] : undefined
const stablecoin = amountOut?.currency
+ // TODO(#2808): remove dependency on useBestV2Trade
const v2USDCTrade = useBestV2Trade(TradeType.EXACT_OUTPUT, amountOut, currency, {
maxHops: 2,
})
@@ -45,7 +47,7 @@ export default function useUSDCPrice(currency?: Currency): Price | undefine
}
}, [currencyAmount, price])
}
+
+/**
+ *
+ * @param fiatValue string representation of a USD amount
+ * @returns CurrencyAmount where currency is stablecoin on active chain
+ */
+export function useStablecoinAmountFromFiatValue(fiatValue: string | null | undefined) {
+ const { chainId } = useActiveWeb3React()
+ const stablecoin = chainId ? STABLECOIN_AMOUNT_OUT[chainId]?.currency : undefined
+
+ if (fiatValue === null || fiatValue === undefined || !chainId || !stablecoin) {
+ return undefined
+ }
+
+ // trim for decimal precision when parsing
+ const parsedForDecimals = parseFloat(fiatValue).toFixed(stablecoin.decimals).toString()
+
+ try {
+ // parse USD string into CurrencyAmount based on stablecoin decimals
+ return tryParseAmount(parsedForDecimals, stablecoin)
+ } catch (error) {
+ return undefined
+ }
+}
diff --git a/src/pages/App.tsx b/src/pages/App.tsx
index 865eb13b80..4fc9a37374 100644
--- a/src/pages/App.tsx
+++ b/src/pages/App.tsx
@@ -48,7 +48,7 @@ const BodyWrapper = styled.div`
z-index: 1;
${({ theme }) => theme.mediaWidth.upToSmall`
- padding: 6rem 16px 16px 16px;
+ padding: 4rem 8px 16px 8px;
`};
`
diff --git a/src/pages/Swap/index.tsx b/src/pages/Swap/index.tsx
index 414af255bd..dee46c866d 100644
--- a/src/pages/Swap/index.tsx
+++ b/src/pages/Swap/index.tsx
@@ -1,23 +1,20 @@
import { Trans } from '@lingui/macro'
+import { Trade } from '@uniswap/router-sdk'
import { Currency, CurrencyAmount, Token, TradeType } from '@uniswap/sdk-core'
import { Trade as V2Trade } from '@uniswap/v2-sdk'
import { Trade as V3Trade } from '@uniswap/v3-sdk'
-import { LoadingOpacityContainer } from 'components/Loader/styled'
import { NetworkAlert } from 'components/NetworkAlert/NetworkAlert'
-import { AdvancedSwapDetails } from 'components/swap/AdvancedSwapDetails'
-import { AutoRouterLogo } from 'components/swap/RouterLabel'
-import SwapRoute from 'components/swap/SwapRoute'
-import TradePrice from 'components/swap/TradePrice'
+import SwapDetailsDropdown from 'components/swap/SwapDetailsDropdown'
import UnsupportedCurrencyFooter from 'components/swap/UnsupportedCurrencyFooter'
-import { MouseoverTooltip, MouseoverTooltipContent } from 'components/Tooltip'
+import { MouseoverTooltip } from 'components/Tooltip'
import JSBI from 'jsbi'
import { useCallback, useContext, useEffect, useMemo, useState } from 'react'
-import { ArrowDown, CheckCircle, HelpCircle, Info } from 'react-feather'
+import { ArrowDown, CheckCircle, HelpCircle } from 'react-feather'
import ReactGA from 'react-ga'
import { RouteComponentProps } from 'react-router-dom'
import { Text } from 'rebass'
-import { V3TradeState } from 'state/routing/types'
-import styled, { ThemeContext } from 'styled-components/macro'
+import { TradeState } from 'state/routing/types'
+import { ThemeContext } from 'styled-components/macro'
import AddressInputPanel from '../../components/AddressInputPanel'
import { ButtonConfirmed, ButtonError, ButtonLight, ButtonPrimary } from '../../components/Button'
@@ -26,27 +23,20 @@ import { AutoColumn } from '../../components/Column'
import CurrencyInputPanel from '../../components/CurrencyInputPanel'
import CurrencyLogo from '../../components/CurrencyLogo'
import Loader from '../../components/Loader'
-import Row, { AutoRow, RowFixed } from '../../components/Row'
+import { AutoRow } from '../../components/Row'
import confirmPriceImpactWithoutFee from '../../components/swap/confirmPriceImpactWithoutFee'
import ConfirmSwapModal from '../../components/swap/ConfirmSwapModal'
-import {
- ArrowWrapper,
- Dots,
- ResponsiveTooltipContainer,
- SwapCallbackError,
- Wrapper,
-} from '../../components/swap/styleds'
+import { ArrowWrapper, SwapCallbackError, Wrapper } from '../../components/swap/styleds'
import SwapHeader from '../../components/swap/SwapHeader'
import { SwitchLocaleLink } from '../../components/SwitchLocaleLink'
import TokenWarningModal from '../../components/TokenWarningModal'
import { useAllTokens, useCurrency } from '../../hooks/Tokens'
-import { ApprovalState, useApproveCallbackFromTrade } from '../../hooks/useApproveCallback'
+import { ApprovalState, useApprovalOptimizedTrade, useApproveCallbackFromTrade } from '../../hooks/useApproveCallback'
import useENSAddress from '../../hooks/useENSAddress'
import { useERC20PermitFromTrade, UseERC20PermitState } from '../../hooks/useERC20Permit'
import useIsArgentWallet from '../../hooks/useIsArgentWallet'
import { useIsSwapUnsupported } from '../../hooks/useIsSwapUnsupported'
import { useSwapCallback } from '../../hooks/useSwapCallback'
-import useToggledVersion from '../../hooks/useToggledVersion'
import { useUSDCValue } from '../../hooks/useUSDCPrice'
import useWrapCallback, { WrapType } from '../../hooks/useWrapCallback'
import { useActiveWeb3React } from '../../hooks/web3'
@@ -61,21 +51,10 @@ import {
import { useExpertModeManager } from '../../state/user/hooks'
import { LinkStyledButton, ThemedText } from '../../theme'
import { computeFiatValuePriceImpact } from '../../utils/computeFiatValuePriceImpact'
-import { getTradeVersion } from '../../utils/getTradeVersion'
import { maxAmountSpend } from '../../utils/maxAmountSpend'
import { warningSeverity } from '../../utils/prices'
import AppBody from '../AppBody'
-const StyledInfo = styled(Info)`
- height: 16px;
- width: 16px;
- margin-left: 4px;
- color: ${({ theme }) => theme.text3};
- :hover {
- color: ${({ theme }) => theme.text1};
- }
-`
-
export default function Swap({ history }: RouteComponentProps) {
const { account } = useActiveWeb3React()
const loadedUrlParams = useDefaultsFromURLSearch()
@@ -113,20 +92,16 @@ export default function Swap({ history }: RouteComponentProps) {
// for expert mode
const [isExpertMode] = useExpertModeManager()
- // get version from the url
- const toggledVersion = useToggledVersion()
-
// swap state
const { independentField, typedValue, recipient } = useSwapState()
const {
- v3Trade: { state: v3TradeState },
- bestTrade: trade,
+ trade: { state: tradeState, trade },
allowedSlippage,
currencyBalances,
parsedAmount,
currencies,
inputError: swapInputError,
- } = useDerivedSwapInfo(toggledVersion)
+ } = useDerivedSwapInfo()
const {
wrapType,
@@ -151,12 +126,8 @@ export default function Swap({ history }: RouteComponentProps) {
)
const [routeNotFound, routeIsLoading, routeIsSyncing] = useMemo(
- () => [
- trade instanceof V3Trade ? !trade?.swaps : !trade?.route,
- V3TradeState.LOADING === v3TradeState,
- V3TradeState.SYNCING === v3TradeState,
- ],
- [trade, v3TradeState]
+ () => [!trade?.swaps, TradeState.LOADING === tradeState, TradeState.SYNCING === tradeState],
+ [trade, tradeState]
)
const fiatValueInput = useUSDCValue(parsedAmounts[Field.INPUT])
@@ -189,7 +160,7 @@ export default function Swap({ history }: RouteComponentProps) {
// modal and loading
const [{ showConfirm, tradeToConfirm, swapErrorMessage, attemptingTxn, txHash }, setSwapState] = useState<{
showConfirm: boolean
- tradeToConfirm: V2Trade | V3Trade | undefined
+ tradeToConfirm: Trade | undefined
attemptingTxn: boolean
swapErrorMessage: string | undefined
txHash: string | undefined
@@ -215,13 +186,21 @@ export default function Swap({ history }: RouteComponentProps) {
currencies[Field.INPUT] && currencies[Field.OUTPUT] && parsedAmounts[independentField]?.greaterThan(JSBI.BigInt(0))
)
+ const approvalOptimizedTrade = useApprovalOptimizedTrade(trade, allowedSlippage)
+ const approvalOptimizedTradeString =
+ approvalOptimizedTrade instanceof V2Trade
+ ? 'V2SwapRouter'
+ : approvalOptimizedTrade instanceof V3Trade
+ ? 'V3SwapRouter'
+ : 'SwapRouter'
+
// check whether the user has approved the router on the input token
- const [approvalState, approveCallback] = useApproveCallbackFromTrade(trade, allowedSlippage)
+ const [approvalState, approveCallback] = useApproveCallbackFromTrade(approvalOptimizedTrade, allowedSlippage)
const {
state: signatureState,
signatureData,
gatherPermitSignature,
- } = useERC20PermitFromTrade(trade, allowedSlippage)
+ } = useERC20PermitFromTrade(approvalOptimizedTrade, allowedSlippage)
const handleApprove = useCallback(async () => {
if (signatureState === UseERC20PermitState.NOT_SIGNED && gatherPermitSignature) {
@@ -239,10 +218,16 @@ export default function Swap({ history }: RouteComponentProps) {
ReactGA.event({
category: 'Swap',
action: 'Approve',
- label: [trade?.inputAmount.currency.symbol, toggledVersion].join('/'),
+ label: [approvalOptimizedTradeString, approvalOptimizedTrade?.inputAmount?.currency.symbol].join('/'),
})
}
- }, [approveCallback, gatherPermitSignature, signatureState, toggledVersion, trade?.inputAmount.currency.symbol])
+ }, [
+ signatureState,
+ gatherPermitSignature,
+ approveCallback,
+ approvalOptimizedTradeString,
+ approvalOptimizedTrade?.inputAmount?.currency.symbol,
+ ])
// check if user has gone through approval process, used to show two step buttons, reset on token change
const [approvalSubmitted, setApprovalSubmitted] = useState(false)
@@ -262,7 +247,7 @@ export default function Swap({ history }: RouteComponentProps) {
// the callback to execute the swap
const { callback: swapCallback, error: swapCallbackError } = useSwapCallback(
- trade,
+ approvalOptimizedTrade,
allowedSlippage,
recipient,
signatureData
@@ -288,9 +273,9 @@ export default function Swap({ history }: RouteComponentProps) {
? 'Swap w/o Send + recipient'
: 'Swap w/ Send',
label: [
- trade?.inputAmount?.currency?.symbol,
- trade?.outputAmount?.currency?.symbol,
- getTradeVersion(trade),
+ approvalOptimizedTradeString,
+ approvalOptimizedTrade?.inputAmount?.currency?.symbol,
+ approvalOptimizedTrade?.outputAmount?.currency?.symbol,
'MH',
].join('/'),
})
@@ -304,7 +289,18 @@ export default function Swap({ history }: RouteComponentProps) {
txHash: undefined,
})
})
- }, [swapCallback, priceImpact, tradeToConfirm, showConfirm, recipient, recipientAddress, account, trade])
+ }, [
+ swapCallback,
+ priceImpact,
+ tradeToConfirm,
+ showConfirm,
+ recipient,
+ recipientAddress,
+ account,
+ approvalOptimizedTradeString,
+ approvalOptimizedTrade?.inputAmount?.currency?.symbol,
+ approvalOptimizedTrade?.outputAmount?.currency?.symbol,
+ ])
// errors
const [showInverted, setShowInverted] = useState(false)
@@ -454,64 +450,16 @@ export default function Swap({ history }: RouteComponentProps) {
>
) : null}
-
- {!showWrap && trade && (
-
-
-
-
-
- }
- placement="bottom"
- onOpen={() =>
- ReactGA.event({
- category: 'Swap',
- action: 'Router Tooltip Open',
- })
- }
- >
-
-
-
- {trade instanceof V3Trade && trade.swaps.length > 1 && (
- {trade.swaps.length} routes
- )}
-
-
-
-
-
-
-
-
-
-
-
- }
- placement="bottom"
- onOpen={() =>
- ReactGA.event({
- category: 'Swap',
- action: 'Transaction Details Tooltip Open',
- })
- }
- >
-
-
-
-
+ {!showWrap && userHasSpecifiedInputOutput && (trade || routeIsLoading || routeIsSyncing) && (
+
)}
-
{swapIsUnsupported ? (
@@ -532,15 +480,7 @@ export default function Swap({ history }: RouteComponentProps) {
Unwrap
) : null)}
- ) : routeIsSyncing || routeIsLoading ? (
-
-
-
- Loading
-
-
-
- ) : routeNotFound && userHasSpecifiedInputOutput ? (
+ ) : routeNotFound && userHasSpecifiedInputOutput && !routeIsLoading && !routeIsSyncing ? (
Insufficient liquidity for this trade.
@@ -613,6 +553,8 @@ export default function Swap({ history }: RouteComponentProps) {
id="swap-button"
disabled={
!isValid ||
+ routeIsSyncing ||
+ routeIsLoading ||
(approvalState !== ApprovalState.APPROVED && signatureState !== UseERC20PermitState.SIGNED) ||
priceImpactTooHigh
}
@@ -621,7 +563,7 @@ export default function Swap({ history }: RouteComponentProps) {
{priceImpactTooHigh ? (
High Price Impact
- ) : priceImpactSeverity > 2 ? (
+ ) : trade && priceImpactSeverity > 2 ? (
Swap Anyway
) : (
Swap
@@ -646,16 +588,18 @@ export default function Swap({ history }: RouteComponentProps) {
}
}}
id="swap-button"
- disabled={!isValid || priceImpactTooHigh || !!swapCallbackError}
+ disabled={!isValid || routeIsSyncing || routeIsLoading || priceImpactTooHigh || !!swapCallbackError}
error={isValid && priceImpactSeverity > 2 && !swapCallbackError}
>
{swapInputError ? (
swapInputError
- ) : priceImpactTooHigh ? (
- Price Impact Too High
+ ) : routeIsSyncing || routeIsLoading ? (
+ Swap
) : priceImpactSeverity > 2 ? (
Swap Anyway
+ ) : priceImpactTooHigh ? (
+ Price Impact Too High
) : (
Swap
)}
diff --git a/src/state/routing/clientSideSmartOrderRouter/constants.ts b/src/state/routing/clientSideSmartOrderRouter/constants.ts
new file mode 100644
index 0000000000..444980beba
--- /dev/null
+++ b/src/state/routing/clientSideSmartOrderRouter/constants.ts
@@ -0,0 +1,3 @@
+import { ChainId } from '@uniswap/smart-order-router'
+
+export const AUTO_ROUTER_SUPPORTED_CHAINS: ChainId[] = Object.values(ChainId) as number[]
diff --git a/src/state/routing/clientSideSmartOrderRouter/dependencies.ts b/src/state/routing/clientSideSmartOrderRouter/dependencies.ts
new file mode 100644
index 0000000000..839d9b5587
--- /dev/null
+++ b/src/state/routing/clientSideSmartOrderRouter/dependencies.ts
@@ -0,0 +1,43 @@
+import { AlphaRouterParams, IMetric, MetricLoggerUnit, setGlobalMetric } from '@uniswap/smart-order-router'
+import { NETWORK_URLS } from 'connectors'
+import { SupportedChainId } from 'constants/chains'
+import { providers } from 'ethers/lib/ethers'
+import ReactGA from 'react-ga'
+
+import { AUTO_ROUTER_SUPPORTED_CHAINS } from './constants'
+
+export type Dependencies = {
+ [chainId in SupportedChainId]?: AlphaRouterParams
+}
+
+/** Minimal set of dependencies for the router to work locally. */
+export function buildDependencies(): Dependencies {
+ const dependenciesByChain: Dependencies = {}
+ for (const chainId of AUTO_ROUTER_SUPPORTED_CHAINS) {
+ const provider = new providers.JsonRpcProvider(NETWORK_URLS[chainId])
+
+ dependenciesByChain[chainId] = {
+ chainId,
+ provider,
+ }
+ }
+
+ return dependenciesByChain
+}
+
+class GAMetric extends IMetric {
+ putDimensions() {
+ return
+ }
+
+ putMetric(key: string, value: number, unit?: MetricLoggerUnit) {
+ ReactGA.timing({
+ category: 'Routing API',
+ variable: `${key} | ${unit}`,
+ value,
+ label: 'client',
+ })
+ }
+}
+
+setGlobalMetric(new GAMetric())
diff --git a/src/state/routing/clientSideSmartOrderRouter/index.ts b/src/state/routing/clientSideSmartOrderRouter/index.ts
new file mode 100644
index 0000000000..89c19e724c
--- /dev/null
+++ b/src/state/routing/clientSideSmartOrderRouter/index.ts
@@ -0,0 +1,52 @@
+import { BigintIsh, CurrencyAmount, Token, TradeType } from '@uniswap/sdk-core'
+import { AlphaRouter, AlphaRouterConfig, ChainId } from '@uniswap/smart-order-router'
+import JSBI from 'jsbi'
+import { GetQuoteResult } from 'state/routing/types'
+import { transformSwapRouteToGetQuoteResult } from 'utils/transformSwapRouteToGetQuoteResult'
+
+import { buildDependencies } from './dependencies'
+
+const routerParamsByChain = buildDependencies()
+
+export async function getQuote(
+ {
+ type,
+ chainId,
+ tokenIn,
+ tokenOut,
+ amount: amountRaw,
+ }: {
+ type: 'exactIn' | 'exactOut'
+ chainId: ChainId
+ tokenIn: { address: string; chainId: number; decimals: number; symbol?: string }
+ tokenOut: { address: string; chainId: number; decimals: number; symbol?: string }
+ amount: BigintIsh
+ },
+ alphaRouterConfig: Partial
+): Promise<{ data: GetQuoteResult; error?: unknown }> {
+ const params = routerParamsByChain[chainId]
+ if (!params) {
+ throw new Error('Router dependencies not initialized.')
+ }
+
+ const router = new AlphaRouter(params)
+
+ const currencyIn = new Token(tokenIn.chainId, tokenIn.address, tokenIn.decimals, tokenIn.symbol)
+ const currencyOut = new Token(tokenOut.chainId, tokenOut.address, tokenOut.decimals, tokenOut.symbol)
+
+ const baseCurrency = type === 'exactIn' ? currencyIn : currencyOut
+ const quoteCurrency = type === 'exactIn' ? currencyOut : currencyIn
+ const amount = CurrencyAmount.fromRawAmount(baseCurrency, JSBI.BigInt(amountRaw))
+
+ const swapRoute = await router.route(
+ amount,
+ quoteCurrency,
+ type === 'exactIn' ? TradeType.EXACT_INPUT : TradeType.EXACT_OUTPUT,
+ /*swapConfig=*/ undefined,
+ alphaRouterConfig
+ )
+
+ if (!swapRoute) throw new Error('Failed to generate client side quote')
+
+ return { data: transformSwapRouteToGetQuoteResult(type, amount, swapRoute) }
+}
diff --git a/src/state/routing/slice.ts b/src/state/routing/slice.ts
index 73ec57bba8..4a59f7dade 100644
--- a/src/state/routing/slice.ts
+++ b/src/state/routing/slice.ts
@@ -1,9 +1,65 @@
-import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
-import { SupportedChainId } from 'constants/chains'
+import { createApi, fetchBaseQuery, FetchBaseQueryError } from '@reduxjs/toolkit/query/react'
+import { Protocol } from '@uniswap/router-sdk'
+import { ChainId } from '@uniswap/smart-order-router'
+import ms from 'ms.macro'
import qs from 'qs'
import { GetQuoteResult } from './types'
+const protocols: Protocol[] = [Protocol.V2, Protocol.V3]
+
+const DEFAULT_QUERY_PARAMS = {
+ protocols: protocols.map((p) => p.toLowerCase()).join(','),
+ // example other params
+ // forceCrossProtocol: 'true',
+ // minSplits: '5',
+}
+
+async function getClientSideQuote({
+ tokenInAddress,
+ tokenInChainId,
+ tokenInDecimals,
+ tokenInSymbol,
+ tokenOutAddress,
+ tokenOutChainId,
+ tokenOutDecimals,
+ tokenOutSymbol,
+ amount,
+ type,
+}: {
+ tokenInAddress: string
+ tokenInChainId: ChainId
+ tokenInDecimals: number
+ tokenInSymbol?: string
+ tokenOutAddress: string
+ tokenOutChainId: ChainId
+ tokenOutDecimals: number
+ tokenOutSymbol?: string
+ amount: string
+ type: 'exactIn' | 'exactOut'
+}) {
+ return (await import('./clientSideSmartOrderRouter')).getQuote(
+ {
+ type,
+ chainId: tokenInChainId,
+ tokenIn: {
+ address: tokenInAddress,
+ chainId: tokenInChainId,
+ decimals: tokenInDecimals,
+ symbol: tokenInSymbol,
+ },
+ tokenOut: {
+ address: tokenOutAddress,
+ chainId: tokenOutChainId,
+ decimals: tokenOutDecimals,
+ symbol: tokenOutSymbol,
+ },
+ amount,
+ },
+ { protocols }
+ )
+}
+
export const routingApi = createApi({
reducerPath: 'routingApi',
baseQuery: fetchBaseQuery({
@@ -14,14 +70,51 @@ export const routingApi = createApi({
GetQuoteResult,
{
tokenInAddress: string
- tokenInChainId: SupportedChainId
+ tokenInChainId: ChainId
+ tokenInDecimals: number
+ tokenInSymbol?: string
tokenOutAddress: string
- tokenOutChainId: SupportedChainId
+ tokenOutChainId: ChainId
+ tokenOutDecimals: number
+ tokenOutSymbol?: string
amount: string
+ useClientSideRouter: boolean // included in key to invalidate on change
type: 'exactIn' | 'exactOut'
}
>({
- query: (args) => `quote?${qs.stringify({ ...args, protocols: 'v3' })}`,
+ async queryFn(args, _api, _extraOptions, fetch) {
+ const { tokenInAddress, tokenInChainId, tokenOutAddress, tokenOutChainId, amount, useClientSideRouter, type } =
+ args
+
+ let result
+
+ try {
+ if (useClientSideRouter) {
+ result = await getClientSideQuote(args)
+ } else {
+ const query = qs.stringify({
+ ...DEFAULT_QUERY_PARAMS,
+ tokenInAddress,
+ tokenInChainId,
+ tokenOutAddress,
+ tokenOutChainId,
+ amount,
+ type,
+ })
+ result = await fetch(`quote?${query}`)
+ }
+
+ return { data: result.data as GetQuoteResult }
+ } catch (e) {
+ // TODO: fall back to client-side quoter when auto router fails.
+ // deprecate 'legacy' v2/v3 routers first.
+ return { error: e as FetchBaseQueryError }
+ }
+ },
+ keepUnusedDataFor: ms`10s`,
+ extraOptions: {
+ maxRetries: 0,
+ },
}),
}),
})
diff --git a/src/state/routing/types.ts b/src/state/routing/types.ts
index d1f214a4ba..a32f301055 100644
--- a/src/state/routing/types.ts
+++ b/src/state/routing/types.ts
@@ -1,6 +1,9 @@
-import { Token } from '@uniswap/sdk-core'
+import { Trade } from '@uniswap/router-sdk'
+import { Currency, CurrencyAmount, Token, TradeType } from '@uniswap/sdk-core'
+import { Route as V2Route } from '@uniswap/v2-sdk'
+import { Route as V3Route } from '@uniswap/v3-sdk'
-export enum V3TradeState {
+export enum TradeState {
LOADING,
INVALID,
NO_ROUTE_FOUND,
@@ -8,11 +11,12 @@ export enum V3TradeState {
SYNCING,
}
+// from https://github.com/Uniswap/routing-api/blob/main/lib/handlers/schema.ts
+
export type TokenInRoute = Pick
-export type PoolInRoute = {
+export type V3PoolInRoute = {
type: 'v3-pool'
- address: string
tokenIn: TokenInRoute
tokenOut: TokenInRoute
sqrtRatioX96: string
@@ -21,6 +25,28 @@ export type PoolInRoute = {
fee: string
amountIn?: string
amountOut?: string
+
+ // not used in the interface
+ address?: string
+}
+
+export type V2Reserve = {
+ token: TokenInRoute
+ quotient: string
+}
+
+export type V2PoolInRoute = {
+ type: 'v2-pool'
+ tokenIn: TokenInRoute
+ tokenOut: TokenInRoute
+ reserve0: V2Reserve
+ reserve1: V2Reserve
+ amountIn?: string
+ amountOut?: string
+
+ // not used in the interface
+ // avoid returning it from the client-side smart-order-router
+ address?: string
}
export interface GetQuoteResult {
@@ -38,6 +64,35 @@ export interface GetQuoteResult {
quoteDecimals: string
quoteGasAdjusted: string
quoteGasAdjustedDecimals: string
- route: PoolInRoute[][]
+ route: Array
routeString: string
}
+
+export class InterfaceTrade<
+ TInput extends Currency,
+ TOutput extends Currency,
+ TTradeType extends TradeType
+> extends Trade {
+ gasUseEstimateUSD: CurrencyAmount | null | undefined
+
+ constructor({
+ gasUseEstimateUSD,
+ ...routes
+ }: {
+ gasUseEstimateUSD?: CurrencyAmount | undefined | null
+ v2Routes: {
+ routev2: V2Route
+ inputAmount: CurrencyAmount
+ outputAmount: CurrencyAmount
+ }[]
+ v3Routes: {
+ routev3: V3Route
+ inputAmount: CurrencyAmount
+ outputAmount: CurrencyAmount
+ }[]
+ tradeType: TTradeType
+ }) {
+ super(routes)
+ this.gasUseEstimateUSD = gasUseEstimateUSD
+ }
+}
diff --git a/src/state/routing/useRoutingAPITrade.ts b/src/state/routing/useRoutingAPITrade.ts
index 7ed0c2db71..1490c5f5ed 100644
--- a/src/state/routing/useRoutingAPITrade.ts
+++ b/src/state/routing/useRoutingAPITrade.ts
@@ -1,13 +1,14 @@
import { skipToken } from '@reduxjs/toolkit/query/react'
import { Currency, CurrencyAmount, TradeType } from '@uniswap/sdk-core'
-import { Trade } from '@uniswap/v3-sdk'
+import { useStablecoinAmountFromFiatValue } from 'hooks/useUSDCPrice'
import ms from 'ms.macro'
import { useMemo } from 'react'
import { useBlockNumber } from 'state/application/hooks'
import { useGetQuoteQuery } from 'state/routing/slice'
+import { useClientSideRouter } from 'state/user/hooks'
-import { V3TradeState } from './types'
-import { computeRoutes } from './utils'
+import { GetQuoteResult, InterfaceTrade, TradeState } from './types'
+import { computeRoutes, transformRoutesToTrade } from './utils'
function useFreshData(data: T, dataBlockNumber: number, maxBlockAge = 10): T | undefined {
const localBlockNumber = useBlockNumber()
@@ -35,22 +36,31 @@ function useRoutingAPIArguments({
amount: CurrencyAmount | undefined
tradeType: TradeType
}) {
- if (!tokenIn || !tokenOut || !amount || tokenIn.equals(tokenOut)) {
- return undefined
- }
+ const [clientSideRouter] = useClientSideRouter()
- return {
- tokenInAddress: tokenIn.wrapped.address,
- tokenInChainId: tokenIn.chainId,
- tokenOutAddress: tokenOut.wrapped.address,
- tokenOutChainId: tokenOut.chainId,
- amount: amount.quotient.toString(),
- type: (tradeType === TradeType.EXACT_INPUT ? 'exactIn' : 'exactOut') as 'exactIn' | 'exactOut',
- }
+ return useMemo(
+ () =>
+ !tokenIn || !tokenOut || !amount || tokenIn.equals(tokenOut)
+ ? undefined
+ : {
+ amount: amount.quotient.toString(),
+ tokenInAddress: tokenIn.wrapped.address,
+ tokenInChainId: tokenIn.wrapped.chainId,
+ tokenInDecimals: tokenIn.wrapped.decimals,
+ tokenInSymbol: tokenIn.wrapped.symbol,
+ tokenOutAddress: tokenOut.wrapped.address,
+ tokenOutChainId: tokenOut.wrapped.chainId,
+ tokenOutDecimals: tokenOut.wrapped.decimals,
+ tokenOutSymbol: tokenOut.wrapped.symbol,
+ useClientSideRouter: clientSideRouter,
+ type: (tradeType === TradeType.EXACT_INPUT ? 'exactIn' : 'exactOut') as 'exactIn' | 'exactOut',
+ },
+ [amount, clientSideRouter, tokenIn, tokenOut, tradeType]
+ )
}
/**
- * Returns the best v3 trade by invoking the routing api
+ * Returns the best trade by invoking the routing api or the smart order router on the client
* @param tradeType whether the swap is an exact in/out
* @param amountSpecified the exact amount to swap in/out
* @param otherCurrency the desired output/payment currency
@@ -59,7 +69,10 @@ export function useRoutingAPITrade(
tradeType: TTradeType,
amountSpecified?: CurrencyAmount,
otherCurrency?: Currency
-): { state: V3TradeState; trade: Trade | null } {
+): {
+ state: TradeState
+ trade: InterfaceTrade | undefined
+} {
const [currencyIn, currencyOut]: [Currency | undefined, Currency | undefined] = useMemo(
() =>
tradeType === TradeType.EXACT_INPUT
@@ -76,30 +89,33 @@ export function useRoutingAPITrade(
})
const { isLoading, isError, data } = useGetQuoteQuery(queryArgs ?? skipToken, {
- pollingInterval: ms`10s`,
+ pollingInterval: ms`15s`,
refetchOnFocus: true,
})
- const quoteResult = useFreshData(data, Number(data?.blockNumber) || 0)
+ const quoteResult: GetQuoteResult | undefined = useFreshData(data, Number(data?.blockNumber) || 0)
- const routes = useMemo(
- () => computeRoutes(currencyIn, currencyOut, quoteResult),
- [currencyIn, currencyOut, quoteResult]
+ const route = useMemo(
+ () => computeRoutes(currencyIn, currencyOut, tradeType, quoteResult),
+ [currencyIn, currencyOut, quoteResult, tradeType]
)
+ // get USD gas cost of trade in active chains stablecoin amount
+ const gasUseEstimateUSD = useStablecoinAmountFromFiatValue(quoteResult?.gasUseEstimateUSD) ?? null
+
return useMemo(() => {
if (!currencyIn || !currencyOut) {
return {
- state: V3TradeState.INVALID,
- trade: null,
+ state: TradeState.INVALID,
+ trade: undefined,
}
}
if (isLoading && !quoteResult) {
// only on first hook render
return {
- state: V3TradeState.LOADING,
- trade: null,
+ state: TradeState.LOADING,
+ trade: undefined,
}
}
@@ -112,22 +128,23 @@ export function useRoutingAPITrade(
? CurrencyAmount.fromRawAmount(currencyIn, quoteResult.quote)
: undefined
- if (isError || !otherAmount || !routes || routes.length === 0 || !queryArgs) {
+ if (isError || !otherAmount || !route || route.length === 0 || !queryArgs) {
return {
- state: V3TradeState.NO_ROUTE_FOUND,
- trade: null,
+ state: TradeState.NO_ROUTE_FOUND,
+ trade: undefined,
}
}
- const trade = Trade.createUncheckedTradeWithMultipleRoutes({
- routes,
- tradeType,
- })
-
- return {
- // always return VALID regardless of isFetching status
- state: V3TradeState.VALID,
- trade,
+ try {
+ const trade = transformRoutesToTrade(route, tradeType, gasUseEstimateUSD)
+ return {
+ // always return VALID regardless of isFetching status
+ state: TradeState.VALID,
+ trade,
+ }
+ } catch (e) {
+ console.debug('transformRoutesToTrade failed: ', e)
+ return { state: TradeState.INVALID, trade: undefined }
}
- }, [currencyIn, currencyOut, isLoading, quoteResult, isError, routes, queryArgs, tradeType])
+ }, [currencyIn, currencyOut, isLoading, quoteResult, tradeType, isError, route, queryArgs, gasUseEstimateUSD])
}
diff --git a/src/state/routing/utils.test.ts b/src/state/routing/utils.test.ts
index 6e33a05598..bec5c0c079 100644
--- a/src/state/routing/utils.test.ts
+++ b/src/state/routing/utils.test.ts
@@ -1,4 +1,4 @@
-import { Ether, Token } from '@uniswap/sdk-core'
+import { Ether, Token, TradeType } from '@uniswap/sdk-core'
import { computeRoutes } from './utils'
@@ -13,21 +13,21 @@ const amount = (raw: TemplateStringsArray) => (parseInt(raw[0]) * 1e6).toString(
describe('#useRoute', () => {
it('handles an undefined payload', () => {
- const result = computeRoutes(undefined, undefined, undefined)
+ const result = computeRoutes(undefined, undefined, TradeType.EXACT_INPUT, undefined)
expect(result).toBeUndefined()
})
it('handles empty edges and nodes', () => {
- const result = computeRoutes(USDC, DAI, {
+ const result = computeRoutes(USDC, DAI, TradeType.EXACT_INPUT, {
route: [],
})
expect(result).toEqual([])
})
- it('handles a single route trade from DAI to USDC', () => {
- const result = computeRoutes(DAI, USDC, {
+ it('handles a single route trade from DAI to USDC from v3', () => {
+ const result = computeRoutes(DAI, USDC, TradeType.EXACT_INPUT, {
route: [
[
{
@@ -46,30 +46,73 @@ describe('#useRoute', () => {
],
})
+ const r = result?.[0]
+
+ expect(result).toBeDefined()
+ expect(result?.length).toBe(1)
+ expect(r?.routev3?.input).toStrictEqual(DAI)
+ expect(r?.routev3?.output).toStrictEqual(USDC)
+ expect(r?.routev3?.tokenPath).toStrictEqual([DAI, USDC])
+ expect(r?.routev2).toBeNull()
+ expect(r?.inputAmount.toSignificant()).toBe('1')
+ expect(r?.outputAmount.toSignificant()).toBe('5')
+ })
+
+ it('handles a single route trade from DAI to USDC from v2', () => {
+ const result = computeRoutes(DAI, USDC, TradeType.EXACT_INPUT, {
+ route: [
+ [
+ {
+ type: 'v2-pool',
+ address: '0x1f8F72aA9304c8B593d555F12eF6589cC3A579A2',
+ amountIn: amount`1`,
+ amountOut: amount`5`,
+ tokenIn: DAI,
+ tokenOut: USDC,
+ reserve0: {
+ token: DAI,
+ quotient: amount`100`,
+ },
+ reserve1: {
+ token: USDC,
+ quotient: amount`200`,
+ },
+ },
+ ],
+ ],
+ })
+
+ const r = result?.[0]
+
expect(result).toBeDefined()
expect(result?.length).toBe(1)
- expect(result && result[0].route.input).toStrictEqual(DAI)
- expect(result && result[0].route.output).toStrictEqual(USDC)
- expect(result && result[0].route.tokenPath).toStrictEqual([DAI, USDC])
- expect(result && result[0].inputAmount.toSignificant()).toBe('1')
- expect(result && result[0].outputAmount.toSignificant()).toBe('5')
+ expect(r?.routev2?.input).toStrictEqual(DAI)
+ expect(r?.routev2?.output).toStrictEqual(USDC)
+ expect(r?.routev2?.path).toStrictEqual([DAI, USDC])
+ expect(r?.routev3).toBeNull()
+ expect(r?.inputAmount.toSignificant()).toBe('1')
+ expect(r?.outputAmount.toSignificant()).toBe('5')
})
it('handles a multi-route trade from DAI to USDC', () => {
- const result = computeRoutes(DAI, USDC, {
+ const result = computeRoutes(DAI, USDC, TradeType.EXACT_OUTPUT, {
route: [
[
{
- type: 'v3-pool',
+ type: 'v2-pool',
address: '0x1f8F72aA9304c8B593d555F12eF6589cC3A579A2',
amountIn: amount`5`,
amountOut: amount`6`,
- fee: '500',
tokenIn: DAI,
tokenOut: USDC,
- sqrtRatioX96: '2437312313659959819381354528',
- liquidity: '10272714736694327408',
- tickCurrent: '-69633',
+ reserve0: {
+ token: DAI,
+ quotient: amount`1000`,
+ },
+ reserve1: {
+ token: USDC,
+ quotient: amount`500`,
+ },
},
],
[
@@ -104,21 +147,24 @@ describe('#useRoute', () => {
expect(result).toBeDefined()
expect(result?.length).toBe(2)
- expect(result && result[0].route.input).toStrictEqual(DAI)
- expect(result && result[0].route.output).toStrictEqual(USDC)
- expect(result && result[0].route.tokenPath).toEqual([DAI, USDC])
- expect(result && result[1].route.input).toStrictEqual(DAI)
- expect(result && result[1].route.output).toStrictEqual(USDC)
- expect(result && result[1].route.tokenPath).toEqual([DAI, MKR, USDC])
-
- expect(result && result[0].inputAmount.toSignificant()).toBe('5')
- expect(result && result[0].outputAmount.toSignificant()).toBe('6')
- expect(result && result[1].inputAmount.toSignificant()).toBe('10')
- expect(result && result[1].outputAmount.toSignificant()).toBe('200')
+ // first route is v2
+ expect(result?.[0].routev2?.input).toStrictEqual(DAI)
+ expect(result?.[0].routev2?.output).toStrictEqual(USDC)
+ expect(result?.[0].routev2?.path).toEqual([DAI, USDC])
+ expect(result?.[0].routev3).toBeNull()
+
+ // second route is v3
+ expect(result?.[1].routev3?.input).toStrictEqual(DAI)
+ expect(result?.[1].routev3?.output).toStrictEqual(USDC)
+ expect(result?.[1].routev3?.tokenPath).toEqual([DAI, MKR, USDC])
+ expect(result?.[1].routev2).toBeNull()
+
+ expect(result?.[0].outputAmount.toSignificant()).toBe('6')
+ expect(result?.[1].outputAmount.toSignificant()).toBe('200')
})
it('handles a single route trade with same token pair, different fee tiers', () => {
- const result = computeRoutes(DAI, USDC, {
+ const result = computeRoutes(DAI, USDC, TradeType.EXACT_INPUT, {
route: [
[
{
@@ -153,18 +199,17 @@ describe('#useRoute', () => {
expect(result).toBeDefined()
expect(result?.length).toBe(2)
- expect(result && result[0].route.input).toStrictEqual(DAI)
- expect(result && result[0].route.output).toStrictEqual(USDC)
- expect(result && result[0].route.tokenPath).toEqual([DAI, USDC])
- expect(result && result[0].inputAmount.toSignificant()).toBe('1')
- expect(result && result[0].outputAmount.toSignificant()).toBe('5')
+ expect(result?.[0].routev3?.input).toStrictEqual(DAI)
+ expect(result?.[0].routev3?.output).toStrictEqual(USDC)
+ expect(result?.[0].routev3?.tokenPath).toEqual([DAI, USDC])
+ expect(result?.[0].inputAmount.toSignificant()).toBe('1')
})
describe('with ETH', () => {
it('outputs native ETH as input currency', () => {
const WETH = ETH.wrapped
- const result = computeRoutes(ETH, USDC, {
+ const result = computeRoutes(ETH, USDC, TradeType.EXACT_OUTPUT, {
route: [
[
{
@@ -185,16 +230,15 @@ describe('#useRoute', () => {
expect(result).toBeDefined()
expect(result?.length).toBe(1)
- expect(result && result[0].route.input).toStrictEqual(ETH)
- expect(result && result[0].route.output).toStrictEqual(USDC)
- expect(result && result[0].route.tokenPath).toStrictEqual([WETH, USDC])
- expect(result && result[0].inputAmount.toSignificant()).toBe('1')
+ expect(result?.[0].routev3?.input).toStrictEqual(ETH)
+ expect(result?.[0].routev3?.output).toStrictEqual(USDC)
+ expect(result?.[0].routev3?.tokenPath).toStrictEqual([WETH, USDC])
expect(result && result[0].outputAmount.toSignificant()).toBe('5')
})
it('outputs native ETH as output currency', () => {
const WETH = new Token(1, ETH.wrapped.address, 18, 'WETH')
- const result = computeRoutes(USDC, ETH, {
+ const result = computeRoutes(USDC, ETH, TradeType.EXACT_OUTPUT, {
route: [
[
{
@@ -214,11 +258,76 @@ describe('#useRoute', () => {
})
expect(result?.length).toBe(1)
- expect(result && result[0].route.input).toStrictEqual(USDC)
- expect(result && result[0].route.output).toStrictEqual(ETH)
- expect(result && result[0].route.tokenPath).toStrictEqual([USDC, WETH])
- expect(result && result[0].inputAmount.toSignificant()).toBe('5')
- expect(result && result[0].outputAmount.toSignificant()).toBe('1')
+ expect(result?.[0].routev3?.input).toStrictEqual(USDC)
+ expect(result?.[0].routev3?.output).toStrictEqual(ETH)
+ expect(result?.[0].routev3?.tokenPath).toStrictEqual([USDC, WETH])
+ expect(result?.[0].outputAmount.toSignificant()).toBe('1')
+ })
+
+ it('outputs native ETH as input currency for v2 routes', () => {
+ const WETH = ETH.wrapped
+
+ const result = computeRoutes(ETH, USDC, TradeType.EXACT_OUTPUT, {
+ route: [
+ [
+ {
+ type: 'v2-pool',
+ address: '0x1f8F72aA9304c8B593d555F12eF6589cC3A579A2',
+ amountIn: (1e18).toString(),
+ amountOut: amount`5`,
+ tokenIn: WETH,
+ tokenOut: USDC,
+ reserve0: {
+ token: WETH,
+ quotient: amount`100`,
+ },
+ reserve1: {
+ token: USDC,
+ quotient: amount`200`,
+ },
+ },
+ ],
+ ],
+ })
+
+ expect(result).toBeDefined()
+ expect(result?.length).toBe(1)
+ expect(result?.[0].routev2?.input).toStrictEqual(ETH)
+ expect(result?.[0].routev2?.output).toStrictEqual(USDC)
+ expect(result?.[0].routev2?.path).toStrictEqual([WETH, USDC])
+ expect(result && result[0].outputAmount.toSignificant()).toBe('5')
+ })
+
+ it('outputs native ETH as output currency for v2 routes', () => {
+ const WETH = new Token(1, ETH.wrapped.address, 18, 'WETH')
+ const result = computeRoutes(USDC, ETH, TradeType.EXACT_OUTPUT, {
+ route: [
+ [
+ {
+ type: 'v2-pool',
+ address: '0x1f8F72aA9304c8B593d555F12eF6589cC3A579A2',
+ amountIn: amount`5`,
+ amountOut: (1e18).toString(),
+ tokenIn: USDC,
+ tokenOut: WETH,
+ reserve0: {
+ token: WETH,
+ quotient: amount`100`,
+ },
+ reserve1: {
+ token: USDC,
+ quotient: amount`200`,
+ },
+ },
+ ],
+ ],
+ })
+
+ expect(result?.length).toBe(1)
+ expect(result?.[0].routev2?.input).toStrictEqual(USDC)
+ expect(result?.[0].routev2?.output).toStrictEqual(ETH)
+ expect(result?.[0].routev2?.path).toStrictEqual([USDC, WETH])
+ expect(result?.[0].outputAmount.toSignificant()).toBe('1')
})
})
})
diff --git a/src/state/routing/utils.ts b/src/state/routing/utils.ts
index a43ce06553..df8ced9541 100644
--- a/src/state/routing/utils.ts
+++ b/src/state/routing/utils.ts
@@ -1,37 +1,38 @@
-import { Currency, CurrencyAmount, Ether, Token } from '@uniswap/sdk-core'
-import { FeeAmount, Pool, Route } from '@uniswap/v3-sdk'
+import { Currency, CurrencyAmount, Ether, Token, TradeType } from '@uniswap/sdk-core'
+import { Pair, Route as V2Route } from '@uniswap/v2-sdk'
+import { FeeAmount, Pool, Route as V3Route } from '@uniswap/v3-sdk'
-import { GetQuoteResult } from './types'
+import { GetQuoteResult, InterfaceTrade, V2PoolInRoute, V3PoolInRoute } from './types'
/**
- * Transforms a Routing API quote into an array of routes that
- * can be used to create a V3 `Trade`.
+ * Transforms a Routing API quote into an array of routes that can be used to create
+ * a `Trade`.
*/
export function computeRoutes(
currencyIn: Currency | undefined,
currencyOut: Currency | undefined,
+ tradeType: TradeType,
quoteResult: Pick | undefined
-):
- | {
- route: Route
- inputAmount: CurrencyAmount
- outputAmount: CurrencyAmount
- }[]
- | undefined {
+) {
if (!quoteResult || !quoteResult.route || !currencyIn || !currencyOut) return undefined
if (quoteResult.route.length === 0) return []
- const parsedCurrencyIn = currencyIn.isNative
- ? Ether.onChain(currencyIn.chainId)
- : parseToken(quoteResult.route[0][0].tokenIn)
+ const parsedTokenIn = parseToken(quoteResult.route[0][0].tokenIn)
+ const parsedTokenOut = parseToken(quoteResult.route[0][quoteResult.route[0].length - 1].tokenOut)
- const parsedCurrencyOut = currencyOut.isNative
- ? Ether.onChain(currencyOut.chainId)
- : parseToken(quoteResult.route[0][quoteResult.route[0].length - 1].tokenOut)
+ if (parsedTokenIn.address !== currencyIn.wrapped.address) return undefined
+ if (parsedTokenOut.address !== currencyOut.wrapped.address) return undefined
+
+ const parsedCurrencyIn = currencyIn.isNative ? Ether.onChain(currencyIn.chainId) : parsedTokenIn
+
+ const parsedCurrencyOut = currencyOut.isNative ? Ether.onChain(currencyOut.chainId) : parsedTokenOut
try {
return quoteResult.route.map((route) => {
+ if (route.length === 0) {
+ throw new Error('Expected route to have at least one pair or pool')
+ }
const rawAmountIn = route[0].amountIn
const rawAmountOut = route[route.length - 1].amountOut
@@ -40,7 +41,8 @@ export function computeRoutes(
}
return {
- route: new Route(route.map(parsePool), parsedCurrencyIn, parsedCurrencyOut),
+ routev3: isV3Route(route) ? new V3Route(route.map(parsePool), parsedCurrencyIn, parsedCurrencyOut) : null,
+ routev2: !isV3Route(route) ? new V2Route(route.map(parsePair), parsedCurrencyIn, parsedCurrencyOut) : null,
inputAmount: CurrencyAmount.fromRawAmount(parsedCurrencyIn, rawAmountIn),
outputAmount: CurrencyAmount.fromRawAmount(parsedCurrencyOut, rawAmountOut),
}
@@ -49,22 +51,35 @@ export function computeRoutes(
// `Route` constructor may throw if inputs/outputs are temporarily out of sync
// (RTK-Query always returns the latest data which may not be the right inputs/outputs)
// This is not fatal and will fix itself in future render cycles
+ console.error(e)
return undefined
}
}
+export function transformRoutesToTrade(
+ route: ReturnType,
+ tradeType: TTradeType,
+ gasUseEstimateUSD?: CurrencyAmount | null
+): InterfaceTrade {
+ return new InterfaceTrade({
+ v2Routes:
+ route
+ ?.filter((r): r is typeof route[0] & { routev2: NonNullable } => r.routev2 !== null)
+ .map(({ routev2, inputAmount, outputAmount }) => ({ routev2, inputAmount, outputAmount })) ?? [],
+ v3Routes:
+ route
+ ?.filter((r): r is typeof route[0] & { routev3: NonNullable } => r.routev3 !== null)
+ .map(({ routev3, inputAmount, outputAmount }) => ({ routev3, inputAmount, outputAmount })) ?? [],
+ tradeType,
+ gasUseEstimateUSD,
+ })
+}
+
const parseToken = ({ address, chainId, decimals, symbol }: GetQuoteResult['route'][0][0]['tokenIn']): Token => {
return new Token(chainId, address, parseInt(decimals.toString()), symbol)
}
-const parsePool = ({
- fee,
- sqrtRatioX96,
- liquidity,
- tickCurrent,
- tokenIn,
- tokenOut,
-}: GetQuoteResult['route'][0][0]): Pool =>
+const parsePool = ({ fee, sqrtRatioX96, liquidity, tickCurrent, tokenIn, tokenOut }: V3PoolInRoute): Pool =>
new Pool(
parseToken(tokenIn),
parseToken(tokenOut),
@@ -73,3 +88,13 @@ const parsePool = ({
liquidity,
parseInt(tickCurrent)
)
+
+const parsePair = ({ reserve0, reserve1 }: V2PoolInRoute): Pair =>
+ new Pair(
+ CurrencyAmount.fromRawAmount(parseToken(reserve0.token), reserve0.quotient),
+ CurrencyAmount.fromRawAmount(parseToken(reserve1.token), reserve1.quotient)
+ )
+
+function isV3Route(route: V3PoolInRoute[] | V2PoolInRoute[]): route is V3PoolInRoute[] {
+ return route[0].type === 'v3-pool'
+}
diff --git a/src/state/swap/hooks.tsx b/src/state/swap/hooks.tsx
index 5465034444..c1961371d1 100644
--- a/src/state/swap/hooks.tsx
+++ b/src/state/swap/hooks.tsx
@@ -1,23 +1,17 @@
import { parseUnits } from '@ethersproject/units'
import { Trans } from '@lingui/macro'
import { Currency, CurrencyAmount, Percent, TradeType } from '@uniswap/sdk-core'
-import { Trade as V2Trade } from '@uniswap/v2-sdk'
-import { Trade as V3Trade } from '@uniswap/v3-sdk'
-import { TWO_PERCENT } from 'constants/misc'
-import { useBestV2Trade } from 'hooks/useBestV2Trade'
-import { useBestV3Trade } from 'hooks/useBestV3Trade'
+import { useBestTrade } from 'hooks/useBestTrade'
import JSBI from 'jsbi'
import { ParsedQs } from 'qs'
import { ReactNode, useCallback, useEffect, useMemo, useState } from 'react'
import { useAppDispatch, useAppSelector } from 'state/hooks'
-import { V3TradeState } from 'state/routing/types'
-import { isTradeBetter } from 'utils/isTradeBetter'
+import { InterfaceTrade, TradeState } from 'state/routing/types'
import { useCurrency } from '../../hooks/Tokens'
import useENS from '../../hooks/useENS'
import useParsedQueryString from '../../hooks/useParsedQueryString'
import useSwapSlippageTolerance from '../../hooks/useSwapSlippageTolerance'
-import { Version } from '../../hooks/useToggledVersion'
import { useActiveWeb3React } from '../../hooks/web3'
import { isAddress } from '../../utils'
import { AppState } from '../index'
@@ -98,36 +92,16 @@ const BAD_RECIPIENT_ADDRESSES: { [address: string]: true } = {
'0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D': true, // v2 router 02
}
-/**
- * Returns true if any of the pairs or tokens in a trade have the given checksummed address
- * @param trade to check for the given address
- * @param checksummedAddress address to check in the pairs and tokens
- */
-function involvesAddress(
- trade: V2Trade | V3Trade,
- checksummedAddress: string
-): boolean {
- const path = trade instanceof V2Trade ? trade.route.path : trade.route.tokenPath
- return (
- path.some((token) => token.address === checksummedAddress) ||
- (trade instanceof V2Trade
- ? trade.route.pairs.some((pair) => pair.liquidityToken.address === checksummedAddress)
- : false)
- )
-}
-
// from the current swap inputs, compute the best trade and return it.
-export function useDerivedSwapInfo(toggledVersion: Version | undefined): {
+export function useDerivedSwapInfo(): {
currencies: { [field in Field]?: Currency | null }
currencyBalances: { [field in Field]?: CurrencyAmount }
parsedAmount: CurrencyAmount | undefined
inputError?: ReactNode
- v2Trade: V2Trade | undefined
- v3Trade: {
- trade: V3Trade | null
- state: V3TradeState
+ trade: {
+ trade: InterfaceTrade | undefined
+ state: TradeState
}
- bestTrade: V2Trade | V3Trade | undefined
allowedSlippage: Percent
} {
const { account } = useActiveWeb3React()
@@ -156,35 +130,12 @@ export function useDerivedSwapInfo(toggledVersion: Version | undefined): {
[inputCurrency, isExactIn, outputCurrency, typedValue]
)
- // get v2 and v3 quotes
- // skip if other version is toggled
- const v2Trade = useBestV2Trade(
+ const trade = useBestTrade(
isExactIn ? TradeType.EXACT_INPUT : TradeType.EXACT_OUTPUT,
- toggledVersion !== Version.v3 ? parsedAmount : undefined,
- (isExactIn ? outputCurrency : inputCurrency) ?? undefined
- )
- const v3Trade = useBestV3Trade(
- isExactIn ? TradeType.EXACT_INPUT : TradeType.EXACT_OUTPUT,
- toggledVersion !== Version.v2 ? parsedAmount : undefined,
+ parsedAmount,
(isExactIn ? outputCurrency : inputCurrency) ?? undefined
)
- const isV2TradeBetter = useMemo(() => {
- try {
- // avoids comparing trades when V3Trade is not in a ready state.
- return toggledVersion === Version.v2 ||
- [V3TradeState.VALID, V3TradeState.SYNCING, V3TradeState.NO_ROUTE_FOUND].includes(v3Trade.state)
- ? isTradeBetter(v3Trade.trade, v2Trade, TWO_PERCENT)
- : undefined
- } catch (e) {
- // v3 trade may be debouncing or fetching and have different
- // inputs/ouputs than v2
- return undefined
- }
- }, [toggledVersion, v2Trade, v3Trade.state, v3Trade.trade])
-
- const bestTrade = isV2TradeBetter === undefined ? undefined : isV2TradeBetter ? v2Trade : v3Trade.trade
-
const currencyBalances = {
[Field.INPUT]: relevantTokenBalances[0],
[Field.OUTPUT]: relevantTokenBalances[1],
@@ -200,27 +151,27 @@ export function useDerivedSwapInfo(toggledVersion: Version | undefined): {
inputError = Connect Wallet
}
- if (!parsedAmount) {
- inputError = inputError ?? Enter an amount
- }
-
if (!currencies[Field.INPUT] || !currencies[Field.OUTPUT]) {
inputError = inputError ?? Select a token
}
+ if (!parsedAmount) {
+ inputError = inputError ?? Enter an amount
+ }
+
const formattedTo = isAddress(to)
if (!to || !formattedTo) {
inputError = inputError ?? Enter a recipient
} else {
- if (BAD_RECIPIENT_ADDRESSES[formattedTo] || (v2Trade && involvesAddress(v2Trade, formattedTo))) {
+ if (BAD_RECIPIENT_ADDRESSES[formattedTo]) {
inputError = inputError ?? Invalid recipient
}
}
- const allowedSlippage = useSwapSlippageTolerance(bestTrade ?? undefined)
+ const allowedSlippage = useSwapSlippageTolerance(trade.trade ?? undefined)
// compare input balance to max input based on version
- const [balanceIn, amountIn] = [currencyBalances[Field.INPUT], bestTrade?.maximumAmountIn(allowedSlippage)]
+ const [balanceIn, amountIn] = [currencyBalances[Field.INPUT], trade.trade?.maximumAmountIn(allowedSlippage)]
if (balanceIn && amountIn && balanceIn.lessThan(amountIn)) {
inputError = Insufficient {amountIn.currency.symbol} balance
@@ -231,9 +182,7 @@ export function useDerivedSwapInfo(toggledVersion: Version | undefined): {
currencyBalances,
parsedAmount,
inputError,
- v2Trade: v2Trade ?? undefined,
- v3Trade,
- bestTrade: bestTrade ?? undefined,
+ trade,
allowedSlippage,
}
}
diff --git a/src/state/user/hooks.tsx b/src/state/user/hooks.tsx
index 3548e24382..6d14d906a1 100644
--- a/src/state/user/hooks.tsx
+++ b/src/state/user/hooks.tsx
@@ -1,6 +1,6 @@
import { Percent, Token } from '@uniswap/sdk-core'
import { computePairAddress, Pair } from '@uniswap/v2-sdk'
-import { L2_CHAIN_IDS, SupportedChainId } from 'constants/chains'
+import { L2_CHAIN_IDS } from 'constants/chains'
import { SupportedLocale } from 'constants/locales'
import { L2_DEADLINE_FROM_NOW } from 'constants/misc'
import JSBI from 'jsbi'
@@ -121,13 +121,6 @@ export function useClientSideRouter(): [boolean, (userClientSideRouter: boolean)
return [clientSideRouter, setClientSideRouter]
}
-export function useRoutingAPIEnabled(): boolean {
- const { chainId } = useActiveWeb3React()
- const [clientSideRouter] = useClientSideRouter()
-
- return chainId === SupportedChainId.MAINNET && !clientSideRouter
-}
-
export function useSetUserSlippageTolerance(): (slippageTolerance: Percent | 'auto') => void {
const dispatch = useAppDispatch()
diff --git a/src/theme/components.tsx b/src/theme/components.tsx
index 6b752ffe62..95ed3fe8c0 100644
--- a/src/theme/components.tsx
+++ b/src/theme/components.tsx
@@ -261,3 +261,9 @@ export const SmallOnly = styled.span`
display: block;
`};
`
+
+export const Separator = styled.div`
+ width: 100%;
+ height: 1px;
+ background-color: ${({ theme }) => theme.bg2};
+`
diff --git a/src/types/tuple.ts b/src/types/tuple.ts
index ecf2686f64..6091a770c5 100644
--- a/src/types/tuple.ts
+++ b/src/types/tuple.ts
@@ -10,3 +10,5 @@ export type TupleSplit = TupleSplit[0]
export type SkipFirst = TupleSplit[1]
+
+export type NonNullable = T extends null | undefined ? never : T
diff --git a/src/utils/getTradeVersion.ts b/src/utils/getTradeVersion.ts
deleted file mode 100644
index 00e7ac2b7f..0000000000
--- a/src/utils/getTradeVersion.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-import { Currency, TradeType } from '@uniswap/sdk-core'
-import { Trade as V2Trade } from '@uniswap/v2-sdk'
-import { Trade as V3Trade } from '@uniswap/v3-sdk'
-
-import { Version } from '../hooks/useToggledVersion'
-
-export function getTradeVersion(
- trade?: V2Trade | V3Trade
-): Version | undefined {
- if (!trade) return undefined
- if (trade instanceof V2Trade) return Version.v2
- return Version.v3
-}
diff --git a/src/utils/getTxOptimizedSwapRouter.test.ts b/src/utils/getTxOptimizedSwapRouter.test.ts
new file mode 100644
index 0000000000..3491a27eac
--- /dev/null
+++ b/src/utils/getTxOptimizedSwapRouter.test.ts
@@ -0,0 +1,102 @@
+import { ApprovalState } from 'hooks/useApproveCallback'
+
+import { getTxOptimizedSwapRouter, SwapRouterVersion } from './getTxOptimizedSwapRouter'
+
+const getApprovalState = (approved: SwapRouterVersion[]) => ({
+ v2: approved.includes(SwapRouterVersion.V2) ? ApprovalState.APPROVED : ApprovalState.NOT_APPROVED,
+ v3: approved.includes(SwapRouterVersion.V3) ? ApprovalState.APPROVED : ApprovalState.NOT_APPROVED,
+ v2V3: approved.includes(SwapRouterVersion.V2V3) ? ApprovalState.APPROVED : ApprovalState.NOT_APPROVED,
+})
+
+describe(getTxOptimizedSwapRouter, () => {
+ it('always selects v2v3 when approved', () => {
+ expect(
+ getTxOptimizedSwapRouter({
+ onlyV2Routes: true,
+ onlyV3Routes: false,
+ tradeHasSplits: false,
+ approvalStates: getApprovalState([SwapRouterVersion.V2V3]),
+ })
+ ).toEqual(SwapRouterVersion.V2V3)
+ expect(
+ getTxOptimizedSwapRouter({
+ onlyV2Routes: false,
+ onlyV3Routes: true,
+ tradeHasSplits: false,
+ approvalStates: getApprovalState([SwapRouterVersion.V2V3]),
+ })
+ ).toEqual(SwapRouterVersion.V2V3)
+ expect(
+ getTxOptimizedSwapRouter({
+ onlyV2Routes: false,
+ onlyV3Routes: true,
+ tradeHasSplits: false,
+ approvalStates: getApprovalState([SwapRouterVersion.V2, SwapRouterVersion.V3, SwapRouterVersion.V2V3]),
+ })
+ ).toEqual(SwapRouterVersion.V2V3)
+ })
+
+ it('selects the right router when only v2 routes', () => {
+ const base = { onlyV3Routes: false }
+
+ // selects v2
+ expect(
+ getTxOptimizedSwapRouter({
+ ...base,
+ onlyV2Routes: true,
+ tradeHasSplits: false,
+ approvalStates: getApprovalState([SwapRouterVersion.V2, SwapRouterVersion.V3]),
+ })
+ ).toEqual(SwapRouterVersion.V2)
+
+ // selects v2V3
+ expect(
+ getTxOptimizedSwapRouter({
+ ...base,
+ onlyV2Routes: true,
+ tradeHasSplits: true,
+ approvalStates: getApprovalState([SwapRouterVersion.V2]),
+ })
+ ).toEqual(SwapRouterVersion.V2V3)
+ expect(
+ getTxOptimizedSwapRouter({
+ ...base,
+ onlyV2Routes: true,
+ tradeHasSplits: true,
+ approvalStates: getApprovalState([SwapRouterVersion.V2, SwapRouterVersion.V2V3]),
+ })
+ ).toEqual(SwapRouterVersion.V2V3)
+ })
+
+ it('selects the right router when only v3 routes', () => {
+ const base = { onlyV2Routes: false }
+
+ // select v3
+ expect(
+ getTxOptimizedSwapRouter({
+ ...base,
+ onlyV3Routes: true,
+ tradeHasSplits: false,
+ approvalStates: getApprovalState([SwapRouterVersion.V2, SwapRouterVersion.V3]),
+ })
+ ).toEqual(SwapRouterVersion.V3)
+ expect(
+ getTxOptimizedSwapRouter({
+ ...base,
+ onlyV3Routes: true,
+ tradeHasSplits: true,
+ approvalStates: getApprovalState([SwapRouterVersion.V3]),
+ })
+ ).toEqual(SwapRouterVersion.V3)
+
+ // selects v2V3
+ expect(
+ getTxOptimizedSwapRouter({
+ ...base,
+ onlyV3Routes: true,
+ tradeHasSplits: true,
+ approvalStates: getApprovalState([SwapRouterVersion.V2, SwapRouterVersion.V2V3]),
+ })
+ ).toEqual(SwapRouterVersion.V2V3)
+ })
+})
diff --git a/src/utils/getTxOptimizedSwapRouter.ts b/src/utils/getTxOptimizedSwapRouter.ts
new file mode 100644
index 0000000000..211c4494cb
--- /dev/null
+++ b/src/utils/getTxOptimizedSwapRouter.ts
@@ -0,0 +1,32 @@
+import { ApprovalState } from 'hooks/useApproveCallback'
+
+export enum SwapRouterVersion {
+ V2,
+ V3,
+ V2V3,
+}
+
+/**
+ * Returns the swap router that will result in the least amount of txs (less gas) for a given swap.
+ * Heuristic:
+ * - if trade contains a single v2-only trade & V2 SwapRouter is approved: use V2 SwapRouter
+ * - if trade contains only v3 & V3 SwapRouter is approved: use V3 SwapRouter
+ * - else: approve and use V2+V3 SwapRouter
+ */
+export function getTxOptimizedSwapRouter({
+ onlyV2Routes,
+ onlyV3Routes,
+ tradeHasSplits,
+ approvalStates,
+}: {
+ onlyV2Routes: boolean | undefined
+ onlyV3Routes: boolean | undefined
+ tradeHasSplits: boolean | undefined
+ approvalStates: { v2: ApprovalState; v3: ApprovalState; v2V3: ApprovalState }
+}): SwapRouterVersion | undefined {
+ if ([approvalStates.v2, approvalStates.v3, approvalStates.v2V3].includes(ApprovalState.PENDING)) return undefined
+ if (approvalStates.v2V3 === ApprovalState.APPROVED) return SwapRouterVersion.V2V3
+ if (approvalStates.v2 === ApprovalState.APPROVED && onlyV2Routes && !tradeHasSplits) return SwapRouterVersion.V2
+ if (approvalStates.v3 === ApprovalState.APPROVED && onlyV3Routes) return SwapRouterVersion.V3
+ return SwapRouterVersion.V2V3
+}
diff --git a/src/utils/isTradeBetter.ts b/src/utils/isTradeBetter.ts
index b2137abde6..f4be6e9e52 100644
--- a/src/utils/isTradeBetter.ts
+++ b/src/utils/isTradeBetter.ts
@@ -1,13 +1,13 @@
import { Currency, Percent, TradeType } from '@uniswap/sdk-core'
import { Trade as V2Trade } from '@uniswap/v2-sdk'
-import { Trade as V3Trade } from '@uniswap/v3-sdk'
import { ONE_HUNDRED_PERCENT, ZERO_PERCENT } from '../constants/misc'
// returns whether tradeB is better than tradeA by at least a threshold percentage amount
+// only used by v2 hooks
export function isTradeBetter(
- tradeA: V2Trade | V3Trade | undefined | null,
- tradeB: V2Trade | V3Trade | undefined | null,
+ tradeA: V2Trade | undefined | null,
+ tradeB: V2Trade | undefined | null,
minimumDelta: Percent = ZERO_PERCENT
): boolean | undefined {
if (tradeA && !tradeB) return false
diff --git a/src/utils/prices.test.ts b/src/utils/prices.test.ts
index 7f721f75ee..538f041260 100644
--- a/src/utils/prices.test.ts
+++ b/src/utils/prices.test.ts
@@ -1,50 +1,120 @@
+import { Trade } from '@uniswap/router-sdk'
import { CurrencyAmount, Percent, Token, TradeType } from '@uniswap/sdk-core'
-import { Pair, Route, Trade } from '@uniswap/v2-sdk'
+import { Pair, Route as V2Route } from '@uniswap/v2-sdk'
+import { FeeAmount, Pool, Route as V3Route } from '@uniswap/v3-sdk'
import JSBI from 'jsbi'
import { computeRealizedLPFeeAmount, warningSeverity } from './prices'
-describe('prices', () => {
- const token1 = new Token(1, '0x0000000000000000000000000000000000000001', 18)
- const token2 = new Token(1, '0x0000000000000000000000000000000000000002', 18)
- const token3 = new Token(1, '0x0000000000000000000000000000000000000003', 18)
+const token1 = new Token(1, '0x0000000000000000000000000000000000000001', 18)
+const token2 = new Token(1, '0x0000000000000000000000000000000000000002', 18)
+const token3 = new Token(1, '0x0000000000000000000000000000000000000003', 18)
+
+const pair12 = new Pair(
+ CurrencyAmount.fromRawAmount(token1, JSBI.BigInt(10000)),
+ CurrencyAmount.fromRawAmount(token2, JSBI.BigInt(20000))
+)
+const pair23 = new Pair(
+ CurrencyAmount.fromRawAmount(token2, JSBI.BigInt(20000)),
+ CurrencyAmount.fromRawAmount(token3, JSBI.BigInt(30000))
+)
- const pair12 = new Pair(
- CurrencyAmount.fromRawAmount(token1, JSBI.BigInt(10000)),
- CurrencyAmount.fromRawAmount(token2, JSBI.BigInt(20000))
- )
- const pair23 = new Pair(
- CurrencyAmount.fromRawAmount(token2, JSBI.BigInt(20000)),
- CurrencyAmount.fromRawAmount(token3, JSBI.BigInt(30000))
- )
+const pool12 = new Pool(token1, token2, FeeAmount.HIGH, '2437312313659959819381354528', '10272714736694327408', -69633)
+const pool13 = new Pool(
+ token1,
+ token3,
+ FeeAmount.MEDIUM,
+ '2437312313659959819381354528',
+ '10272714736694327408',
+ -69633
+)
+const currencyAmount = (token: Token, amount: number) => CurrencyAmount.fromRawAmount(token, JSBI.BigInt(amount))
+
+describe('prices', () => {
describe('#computeRealizedLPFeeAmount', () => {
it('returns undefined for undefined', () => {
expect(computeRealizedLPFeeAmount(undefined)).toEqual(undefined)
})
- it('correct realized lp fee for single hop', () => {
+ it('correct realized lp fee for single hop on v2', () => {
+ // v2
+ expect(
+ computeRealizedLPFeeAmount(
+ new Trade({
+ v2Routes: [
+ {
+ routev2: new V2Route([pair12], token1, token2),
+ inputAmount: currencyAmount(token1, 1000),
+ outputAmount: currencyAmount(token2, 1000),
+ },
+ ],
+ v3Routes: [],
+ tradeType: TradeType.EXACT_INPUT,
+ })
+ )
+ ).toEqual(currencyAmount(token1, 3)) // 3% realized fee
+ })
+
+ it('correct realized lp fee for single hop on v3', () => {
+ // v3
expect(
computeRealizedLPFeeAmount(
- new Trade(
- new Route([pair12], token1, token2),
- CurrencyAmount.fromRawAmount(token1, JSBI.BigInt(1000)),
- TradeType.EXACT_INPUT
- )
+ new Trade({
+ v3Routes: [
+ {
+ routev3: new V3Route([pool12], token1, token2),
+ inputAmount: currencyAmount(token1, 1000),
+ outputAmount: currencyAmount(token2, 1000),
+ },
+ ],
+ v2Routes: [],
+ tradeType: TradeType.EXACT_INPUT,
+ })
)
- ).toEqual(CurrencyAmount.fromRawAmount(token1, JSBI.BigInt(3)))
+ ).toEqual(currencyAmount(token1, 10)) // 3% realized fee
})
it('correct realized lp fee for double hop', () => {
expect(
computeRealizedLPFeeAmount(
- new Trade(
- new Route([pair12, pair23], token1, token3),
- CurrencyAmount.fromRawAmount(token1, JSBI.BigInt(1000)),
- TradeType.EXACT_INPUT
- )
+ new Trade({
+ v2Routes: [
+ {
+ routev2: new V2Route([pair12, pair23], token1, token3),
+ inputAmount: currencyAmount(token1, 1000),
+ outputAmount: currencyAmount(token3, 1000),
+ },
+ ],
+ v3Routes: [],
+ tradeType: TradeType.EXACT_INPUT,
+ })
+ )
+ ).toEqual(currencyAmount(token1, 5))
+ })
+
+ it('correct realized lp fee for multi route v2+v3', () => {
+ expect(
+ computeRealizedLPFeeAmount(
+ new Trade({
+ v2Routes: [
+ {
+ routev2: new V2Route([pair12, pair23], token1, token3),
+ inputAmount: currencyAmount(token1, 1000),
+ outputAmount: currencyAmount(token3, 1000),
+ },
+ ],
+ v3Routes: [
+ {
+ routev3: new V3Route([pool13], token1, token3),
+ inputAmount: currencyAmount(token1, 1000),
+ outputAmount: currencyAmount(token3, 1000),
+ },
+ ],
+ tradeType: TradeType.EXACT_INPUT,
+ })
)
- ).toEqual(CurrencyAmount.fromRawAmount(token1, JSBI.BigInt(5)))
+ ).toEqual(currencyAmount(token1, 8))
})
})
diff --git a/src/utils/prices.ts b/src/utils/prices.ts
index b16f54e9f9..3d68171443 100644
--- a/src/utils/prices.ts
+++ b/src/utils/prices.ts
@@ -1,6 +1,7 @@
+import { Trade } from '@uniswap/router-sdk'
import { Currency, CurrencyAmount, Fraction, Percent, TradeType } from '@uniswap/sdk-core'
-import { Trade as V2Trade } from '@uniswap/v2-sdk'
-import { Trade as V3Trade } from '@uniswap/v3-sdk'
+import { Pair } from '@uniswap/v2-sdk'
+import { FeeAmount } from '@uniswap/v3-sdk'
import JSBI from 'jsbi'
import {
@@ -8,29 +9,28 @@ import {
ALLOWED_PRICE_IMPACT_LOW,
ALLOWED_PRICE_IMPACT_MEDIUM,
BLOCKED_PRICE_IMPACT_NON_EXPERT,
+ ONE_HUNDRED_PERCENT,
ZERO_PERCENT,
} from '../constants/misc'
const THIRTY_BIPS_FEE = new Percent(JSBI.BigInt(30), JSBI.BigInt(10000))
-const ONE_HUNDRED_PERCENT = new Percent(JSBI.BigInt(10000), JSBI.BigInt(10000))
const INPUT_FRACTION_AFTER_FEE = ONE_HUNDRED_PERCENT.subtract(THIRTY_BIPS_FEE)
// computes realized lp fee as a percent
-export function computeRealizedLPFeePercent(
- trade: V2Trade | V3Trade
-): Percent {
+export function computeRealizedLPFeePercent(trade: Trade): Percent {
let percent: Percent
- if (trade instanceof V2Trade) {
+
+ // Since routes are either all v2 or all v3 right now, calculate separately
+ if (trade.swaps[0].route.pools instanceof Pair) {
// for each hop in our trade, take away the x*y=k price impact from 0.3% fees
// e.g. for 3 tokens/2 hops: 1 - ((1 - .03) * (1-.03))
percent = ONE_HUNDRED_PERCENT.subtract(
- trade.route.pairs.reduce(
+ trade.swaps.reduce(
(currentFee: Percent): Percent => currentFee.multiply(INPUT_FRACTION_AFTER_FEE),
ONE_HUNDRED_PERCENT
)
)
} else {
- //TODO(judo): validate this
percent = ZERO_PERCENT
for (const swap of trade.swaps) {
const { numerator, denominator } = swap.inputAmount.divide(trade.inputAmount)
@@ -38,11 +38,14 @@ export function computeRealizedLPFeePercent(
const routeRealizedLPFeePercent = overallPercent.multiply(
ONE_HUNDRED_PERCENT.subtract(
- swap.route.pools.reduce(
- (currentFee: Percent, pool): Percent =>
- currentFee.multiply(ONE_HUNDRED_PERCENT.subtract(new Fraction(pool.fee, 1_000_000))),
- ONE_HUNDRED_PERCENT
- )
+ swap.route.pools.reduce((currentFee: Percent, pool): Percent => {
+ const fee =
+ pool instanceof Pair
+ ? // not currently possible given protocol check above, but not fatal
+ FeeAmount.MEDIUM
+ : pool.fee
+ return currentFee.multiply(ONE_HUNDRED_PERCENT.subtract(new Fraction(fee, 1_000_000)))
+ }, ONE_HUNDRED_PERCENT)
)
)
@@ -55,7 +58,7 @@ export function computeRealizedLPFeePercent(
// computes price breakdown for the trade
export function computeRealizedLPFeeAmount(
- trade?: V2Trade | V3Trade | null
+ trade?: Trade | null
): CurrencyAmount | undefined {
if (trade) {
const realizedLPFee = computeRealizedLPFeePercent(trade)
diff --git a/src/utils/transformSwapRouteToGetQuoteResult.ts b/src/utils/transformSwapRouteToGetQuoteResult.ts
new file mode 100644
index 0000000000..e1a7fe64bb
--- /dev/null
+++ b/src/utils/transformSwapRouteToGetQuoteResult.ts
@@ -0,0 +1,150 @@
+import { Protocol } from '@uniswap/router-sdk'
+import { Currency, CurrencyAmount } from '@uniswap/sdk-core'
+import { routeAmountsToString, SwapRoute } from '@uniswap/smart-order-router'
+import { GetQuoteResult, V2PoolInRoute, V3PoolInRoute } from 'state/routing/types'
+
+// from routing-api (https://github.com/Uniswap/routing-api/blob/main/lib/handlers/quote/quote.ts#L243-L311)
+export function transformSwapRouteToGetQuoteResult(
+ type: 'exactIn' | 'exactOut',
+ amount: CurrencyAmount,
+ {
+ quote,
+ quoteGasAdjusted,
+ route,
+ estimatedGasUsed,
+ estimatedGasUsedQuoteToken,
+ estimatedGasUsedUSD,
+ gasPriceWei,
+ methodParameters,
+ blockNumber,
+ }: SwapRoute
+): GetQuoteResult {
+ const routeResponse: Array = []
+
+ for (const subRoute of route) {
+ const { amount, quote, tokenPath } = subRoute
+
+ if (subRoute.protocol === Protocol.V3) {
+ const pools = subRoute.route.pools
+ const curRoute: V3PoolInRoute[] = []
+ for (let i = 0; i < pools.length; i++) {
+ const nextPool = pools[i]
+ const tokenIn = tokenPath[i]
+ const tokenOut = tokenPath[i + 1]
+
+ let edgeAmountIn = undefined
+ if (i === 0) {
+ edgeAmountIn = type === 'exactIn' ? amount.quotient.toString() : quote.quotient.toString()
+ }
+
+ let edgeAmountOut = undefined
+ if (i === pools.length - 1) {
+ edgeAmountOut = type === 'exactIn' ? quote.quotient.toString() : amount.quotient.toString()
+ }
+
+ curRoute.push({
+ type: 'v3-pool',
+ tokenIn: {
+ chainId: tokenIn.chainId,
+ decimals: tokenIn.decimals,
+ address: tokenIn.address,
+ symbol: tokenIn.symbol,
+ },
+ tokenOut: {
+ chainId: tokenOut.chainId,
+ decimals: tokenOut.decimals,
+ address: tokenOut.address,
+ symbol: tokenOut.symbol,
+ },
+ fee: nextPool.fee.toString(),
+ liquidity: nextPool.liquidity.toString(),
+ sqrtRatioX96: nextPool.sqrtRatioX96.toString(),
+ tickCurrent: nextPool.tickCurrent.toString(),
+ amountIn: edgeAmountIn,
+ amountOut: edgeAmountOut,
+ })
+ }
+
+ routeResponse.push(curRoute)
+ } else if (subRoute.protocol === Protocol.V2) {
+ const pools = subRoute.route.pairs
+ const curRoute: V2PoolInRoute[] = []
+ for (let i = 0; i < pools.length; i++) {
+ const nextPool = pools[i]
+ const tokenIn = tokenPath[i]
+ const tokenOut = tokenPath[i + 1]
+
+ let edgeAmountIn = undefined
+ if (i === 0) {
+ edgeAmountIn = type === 'exactIn' ? amount.quotient.toString() : quote.quotient.toString()
+ }
+
+ let edgeAmountOut = undefined
+ if (i === pools.length - 1) {
+ edgeAmountOut = type === 'exactIn' ? quote.quotient.toString() : amount.quotient.toString()
+ }
+
+ const reserve0 = nextPool.reserve0
+ const reserve1 = nextPool.reserve1
+
+ curRoute.push({
+ type: 'v2-pool',
+ tokenIn: {
+ chainId: tokenIn.chainId,
+ decimals: tokenIn.decimals,
+ address: tokenIn.address,
+ symbol: tokenIn.symbol,
+ },
+ tokenOut: {
+ chainId: tokenOut.chainId,
+ decimals: tokenOut.decimals,
+ address: tokenOut.address,
+ symbol: tokenOut.symbol,
+ },
+ reserve0: {
+ token: {
+ chainId: reserve0.currency.wrapped.chainId,
+ decimals: reserve0.currency.wrapped.decimals,
+ address: reserve0.currency.wrapped.address,
+ symbol: reserve0.currency.wrapped.symbol,
+ },
+ quotient: reserve0.quotient.toString(),
+ },
+ reserve1: {
+ token: {
+ chainId: reserve1.currency.wrapped.chainId,
+ decimals: reserve1.currency.wrapped.decimals,
+ address: reserve1.currency.wrapped.address,
+ symbol: reserve1.currency.wrapped.symbol,
+ },
+ quotient: reserve1.quotient.toString(),
+ },
+ amountIn: edgeAmountIn,
+ amountOut: edgeAmountOut,
+ })
+ }
+
+ routeResponse.push(curRoute)
+ }
+ }
+
+ const result: GetQuoteResult = {
+ methodParameters,
+ blockNumber: blockNumber.toString(),
+ amount: amount.quotient.toString(),
+ amountDecimals: amount.toExact(),
+ quote: quote.quotient.toString(),
+ quoteDecimals: quote.toExact(),
+ quoteGasAdjusted: quoteGasAdjusted.quotient.toString(),
+ quoteGasAdjustedDecimals: quoteGasAdjusted.toExact(),
+ gasUseEstimateQuote: estimatedGasUsedQuoteToken.quotient.toString(),
+ gasUseEstimateQuoteDecimals: estimatedGasUsedQuoteToken.toExact(),
+ gasUseEstimate: estimatedGasUsed.toString(),
+ gasUseEstimateUSD: estimatedGasUsedUSD.toExact(),
+ gasPriceWei: gasPriceWei.toString(),
+ route: routeResponse,
+ routeString: routeAmountsToString(route),
+ }
+
+ return result
+}
diff --git a/yarn.lock b/yarn.lock
index 8a81dc40d6..c738441149 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1278,6 +1278,11 @@
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
+"@bitauth/libauth@^1.17.1":
+ version "1.18.1"
+ resolved "https://registry.yarnpkg.com/@bitauth/libauth/-/libauth-1.18.1.tgz#b1c632ed85f73c16a0ff89d81e8a92809fa15108"
+ integrity sha512-s7evdGbdGAnGkv7xt6mCbcWTTNvburc1Z9EX/8JKwcRLqofjDs7VAEz+RP3a8OGEo4MWFV6Ydqu/BeJjIA7Kdg==
+
"@cnakazawa/watch@^1.0.3":
version "1.0.4"
resolved "https://registry.yarnpkg.com/@cnakazawa/watch/-/watch-1.0.4.tgz#f864ae85004d0fcab6f50be9141c4da368d1656a"
@@ -1476,6 +1481,21 @@
"@ethersproject/properties" "^5.4.0"
"@ethersproject/strings" "^5.4.0"
+"@ethersproject/abi@^5.5.0":
+ version "5.5.0"
+ resolved "https://registry.yarnpkg.com/@ethersproject/abi/-/abi-5.5.0.tgz#fb52820e22e50b854ff15ce1647cc508d6660613"
+ integrity sha512-loW7I4AohP5KycATvc0MgujU6JyCHPqHdeoo9z3Nr9xEiNioxa65ccdm1+fsoJhkuhdRtfcL8cfyGamz2AxZ5w==
+ dependencies:
+ "@ethersproject/address" "^5.5.0"
+ "@ethersproject/bignumber" "^5.5.0"
+ "@ethersproject/bytes" "^5.5.0"
+ "@ethersproject/constants" "^5.5.0"
+ "@ethersproject/hash" "^5.5.0"
+ "@ethersproject/keccak256" "^5.5.0"
+ "@ethersproject/logger" "^5.5.0"
+ "@ethersproject/properties" "^5.5.0"
+ "@ethersproject/strings" "^5.5.0"
+
"@ethersproject/abstract-provider@5.4.1", "@ethersproject/abstract-provider@^5.4.0":
version "5.4.1"
resolved "https://registry.yarnpkg.com/@ethersproject/abstract-provider/-/abstract-provider-5.4.1.tgz#e404309a29f771bd4d28dbafadcaa184668c2a6e"
@@ -1489,6 +1509,19 @@
"@ethersproject/transactions" "^5.4.0"
"@ethersproject/web" "^5.4.0"
+"@ethersproject/abstract-provider@^5.5.0":
+ version "5.5.1"
+ resolved "https://registry.yarnpkg.com/@ethersproject/abstract-provider/-/abstract-provider-5.5.1.tgz#2f1f6e8a3ab7d378d8ad0b5718460f85649710c5"
+ integrity sha512-m+MA/ful6eKbxpr99xUYeRvLkfnlqzrF8SZ46d/xFB1A7ZVknYc/sXJG0RcufF52Qn2jeFj1hhcoQ7IXjNKUqg==
+ dependencies:
+ "@ethersproject/bignumber" "^5.5.0"
+ "@ethersproject/bytes" "^5.5.0"
+ "@ethersproject/logger" "^5.5.0"
+ "@ethersproject/networks" "^5.5.0"
+ "@ethersproject/properties" "^5.5.0"
+ "@ethersproject/transactions" "^5.5.0"
+ "@ethersproject/web" "^5.5.0"
+
"@ethersproject/abstract-signer@5.4.1", "@ethersproject/abstract-signer@^5.4.0":
version "5.4.1"
resolved "https://registry.yarnpkg.com/@ethersproject/abstract-signer/-/abstract-signer-5.4.1.tgz#e4e9abcf4dd4f1ba0db7dff9746a5f78f355ea81"
@@ -1500,6 +1533,17 @@
"@ethersproject/logger" "^5.4.0"
"@ethersproject/properties" "^5.4.0"
+"@ethersproject/abstract-signer@^5.5.0":
+ version "5.5.0"
+ resolved "https://registry.yarnpkg.com/@ethersproject/abstract-signer/-/abstract-signer-5.5.0.tgz#590ff6693370c60ae376bf1c7ada59eb2a8dd08d"
+ integrity sha512-lj//7r250MXVLKI7sVarXAbZXbv9P50lgmJQGr2/is82EwEb8r7HrxsmMqAjTsztMYy7ohrIhGMIml+Gx4D3mA==
+ dependencies:
+ "@ethersproject/abstract-provider" "^5.5.0"
+ "@ethersproject/bignumber" "^5.5.0"
+ "@ethersproject/bytes" "^5.5.0"
+ "@ethersproject/logger" "^5.5.0"
+ "@ethersproject/properties" "^5.5.0"
+
"@ethersproject/address@5.4.0", "@ethersproject/address@^5.0.0", "@ethersproject/address@^5.0.2", "@ethersproject/address@^5.4.0":
version "5.4.0"
resolved "https://registry.yarnpkg.com/@ethersproject/address/-/address-5.4.0.tgz#ba2d00a0f8c4c0854933b963b9a3a9f6eb4a37a3"
@@ -1511,6 +1555,17 @@
"@ethersproject/logger" "^5.4.0"
"@ethersproject/rlp" "^5.4.0"
+"@ethersproject/address@^5.5.0":
+ version "5.5.0"
+ resolved "https://registry.yarnpkg.com/@ethersproject/address/-/address-5.5.0.tgz#bcc6f576a553f21f3dd7ba17248f81b473c9c78f"
+ integrity sha512-l4Nj0eWlTUh6ro5IbPTgbpT4wRbdH5l8CQf7icF7sb/SI3Nhd9Y9HzhonTSTi6CefI0necIw7LJqQPopPLZyWw==
+ dependencies:
+ "@ethersproject/bignumber" "^5.5.0"
+ "@ethersproject/bytes" "^5.5.0"
+ "@ethersproject/keccak256" "^5.5.0"
+ "@ethersproject/logger" "^5.5.0"
+ "@ethersproject/rlp" "^5.5.0"
+
"@ethersproject/base64@5.4.0", "@ethersproject/base64@^5.4.0":
version "5.4.0"
resolved "https://registry.yarnpkg.com/@ethersproject/base64/-/base64-5.4.0.tgz#7252bf65295954c9048c7ca5f43e5c86441b2a9a"
@@ -1518,6 +1573,13 @@
dependencies:
"@ethersproject/bytes" "^5.4.0"
+"@ethersproject/base64@^5.5.0":
+ version "5.5.0"
+ resolved "https://registry.yarnpkg.com/@ethersproject/base64/-/base64-5.5.0.tgz#881e8544e47ed976930836986e5eb8fab259c090"
+ integrity sha512-tdayUKhU1ljrlHzEWbStXazDpsx4eg1dBXUSI6+mHlYklOXoXF6lZvw8tnD6oVaWfnMxAgRSKROg3cVKtCcppA==
+ dependencies:
+ "@ethersproject/bytes" "^5.5.0"
+
"@ethersproject/basex@5.4.0", "@ethersproject/basex@^5.4.0":
version "5.4.0"
resolved "https://registry.yarnpkg.com/@ethersproject/basex/-/basex-5.4.0.tgz#0a2da0f4e76c504a94f2b21d3161ed9438c7f8a6"
@@ -1535,6 +1597,24 @@
"@ethersproject/logger" "^5.4.0"
bn.js "^4.11.9"
+"@ethersproject/bignumber@5.4.2", "@ethersproject/bignumber@^5.1.1":
+ version "5.4.2"
+ resolved "https://registry.yarnpkg.com/@ethersproject/bignumber/-/bignumber-5.4.2.tgz#44232e015ae4ce82ac034de549eb3583c71283d8"
+ integrity sha512-oIBDhsKy5bs7j36JlaTzFgNPaZjiNDOXsdSgSpXRucUl+UA6L/1YLlFeI3cPAoodcenzF4nxNPV13pcy7XbWjA==
+ dependencies:
+ "@ethersproject/bytes" "^5.4.0"
+ "@ethersproject/logger" "^5.4.0"
+ bn.js "^4.11.9"
+
+"@ethersproject/bignumber@^5.5.0":
+ version "5.5.0"
+ resolved "https://registry.yarnpkg.com/@ethersproject/bignumber/-/bignumber-5.5.0.tgz#875b143f04a216f4f8b96245bde942d42d279527"
+ integrity sha512-6Xytlwvy6Rn3U3gKEc1vP7nR92frHkv6wtVr95LFR3jREXiCPzdWxKQ1cx4JGQBXxcguAwjA8murlYN2TSiEbg==
+ dependencies:
+ "@ethersproject/bytes" "^5.5.0"
+ "@ethersproject/logger" "^5.5.0"
+ bn.js "^4.11.9"
+
"@ethersproject/bytes@5.4.0", "@ethersproject/bytes@^5.4.0":
version "5.4.0"
resolved "https://registry.yarnpkg.com/@ethersproject/bytes/-/bytes-5.4.0.tgz#56fa32ce3bf67153756dbaefda921d1d4774404e"
@@ -1542,6 +1622,13 @@
dependencies:
"@ethersproject/logger" "^5.4.0"
+"@ethersproject/bytes@^5.5.0":
+ version "5.5.0"
+ resolved "https://registry.yarnpkg.com/@ethersproject/bytes/-/bytes-5.5.0.tgz#cb11c526de657e7b45d2e0f0246fb3b9d29a601c"
+ integrity sha512-ABvc7BHWhZU9PNM/tANm/Qx4ostPGadAuQzWTr3doklZOhDlmcBqclrQe/ZXUIj3K8wC28oYeuRa+A37tX9kog==
+ dependencies:
+ "@ethersproject/logger" "^5.5.0"
+
"@ethersproject/constants@5.4.0", "@ethersproject/constants@^5.4.0":
version "5.4.0"
resolved "https://registry.yarnpkg.com/@ethersproject/constants/-/constants-5.4.0.tgz#ee0bdcb30bf1b532d2353c977bf2ef1ee117958a"
@@ -1549,6 +1636,13 @@
dependencies:
"@ethersproject/bignumber" "^5.4.0"
+"@ethersproject/constants@^5.5.0":
+ version "5.5.0"
+ resolved "https://registry.yarnpkg.com/@ethersproject/constants/-/constants-5.5.0.tgz#d2a2cd7d94bd1d58377d1d66c4f53c9be4d0a45e"
+ integrity sha512-2MsRRVChkvMWR+GyMGY4N1sAX9Mt3J9KykCsgUFd/1mwS0UH1qw+Bv9k1UJb3X3YJYFco9H20pjSlOIfCG5HYQ==
+ dependencies:
+ "@ethersproject/bignumber" "^5.5.0"
+
"@ethersproject/contracts@5.4.1":
version "5.4.1"
resolved "https://registry.yarnpkg.com/@ethersproject/contracts/-/contracts-5.4.1.tgz#3eb4f35b7fe60a962a75804ada2746494df3e470"
@@ -1588,6 +1682,20 @@
"@ethersproject/properties" "^5.4.0"
"@ethersproject/strings" "^5.4.0"
+"@ethersproject/hash@^5.5.0":
+ version "5.5.0"
+ resolved "https://registry.yarnpkg.com/@ethersproject/hash/-/hash-5.5.0.tgz#7cee76d08f88d1873574c849e0207dcb32380cc9"
+ integrity sha512-dnGVpK1WtBjmnp3mUT0PlU2MpapnwWI0PibldQEq1408tQBAbZpPidkWoVVuNMOl/lISO3+4hXZWCL3YV7qzfg==
+ dependencies:
+ "@ethersproject/abstract-signer" "^5.5.0"
+ "@ethersproject/address" "^5.5.0"
+ "@ethersproject/bignumber" "^5.5.0"
+ "@ethersproject/bytes" "^5.5.0"
+ "@ethersproject/keccak256" "^5.5.0"
+ "@ethersproject/logger" "^5.5.0"
+ "@ethersproject/properties" "^5.5.0"
+ "@ethersproject/strings" "^5.5.0"
+
"@ethersproject/hdnode@5.4.0", "@ethersproject/hdnode@^5.4.0":
version "5.4.0"
resolved "https://registry.yarnpkg.com/@ethersproject/hdnode/-/hdnode-5.4.0.tgz#4bc9999b9a12eb5ce80c5faa83114a57e4107cac"
@@ -1633,11 +1741,24 @@
"@ethersproject/bytes" "^5.4.0"
js-sha3 "0.5.7"
+"@ethersproject/keccak256@^5.5.0":
+ version "5.5.0"
+ resolved "https://registry.yarnpkg.com/@ethersproject/keccak256/-/keccak256-5.5.0.tgz#e4b1f9d7701da87c564ffe336f86dcee82983492"
+ integrity sha512-5VoFCTjo2rYbBe1l2f4mccaRFN/4VQEYFwwn04aJV2h7qf4ZvI2wFxUE1XOX+snbwCLRzIeikOqtAoPwMza9kg==
+ dependencies:
+ "@ethersproject/bytes" "^5.5.0"
+ js-sha3 "0.8.0"
+
"@ethersproject/logger@5.4.1", "@ethersproject/logger@^5.4.0":
version "5.4.1"
resolved "https://registry.yarnpkg.com/@ethersproject/logger/-/logger-5.4.1.tgz#503bd33683538b923c578c07d1c2c0dd18672054"
integrity sha512-DZ+bRinnYLPw1yAC64oRl0QyVZj43QeHIhVKfD/+YwSz4wsv1pfwb5SOFjz+r710YEWzU6LrhuSjpSO+6PeE4A==
+"@ethersproject/logger@^5.5.0":
+ version "5.5.0"
+ resolved "https://registry.yarnpkg.com/@ethersproject/logger/-/logger-5.5.0.tgz#0c2caebeff98e10aefa5aef27d7441c7fd18cf5d"
+ integrity sha512-rIY/6WPm7T8n3qS2vuHTUBPdXHl+rGxWxW5okDfo9J4Z0+gRRZT0msvUdIJkE4/HS29GUMziwGaaKO2bWONBrg==
+
"@ethersproject/networks@5.4.2", "@ethersproject/networks@^5.4.0":
version "5.4.2"
resolved "https://registry.yarnpkg.com/@ethersproject/networks/-/networks-5.4.2.tgz#2247d977626e97e2c3b8ee73cd2457babde0ce35"
@@ -1645,6 +1766,13 @@
dependencies:
"@ethersproject/logger" "^5.4.0"
+"@ethersproject/networks@^5.5.0":
+ version "5.5.0"
+ resolved "https://registry.yarnpkg.com/@ethersproject/networks/-/networks-5.5.0.tgz#babec47cab892c51f8dd652ce7f2e3e14283981a"
+ integrity sha512-KWfP3xOnJeF89Uf/FCJdV1a2aDJe5XTN2N52p4fcQ34QhDqQFkgQKZ39VGtiqUgHcLI8DfT0l9azC3KFTunqtA==
+ dependencies:
+ "@ethersproject/logger" "^5.5.0"
+
"@ethersproject/pbkdf2@5.4.0", "@ethersproject/pbkdf2@^5.4.0":
version "5.4.0"
resolved "https://registry.yarnpkg.com/@ethersproject/pbkdf2/-/pbkdf2-5.4.0.tgz#ed88782a67fda1594c22d60d0ca911a9d669641c"
@@ -1660,6 +1788,13 @@
dependencies:
"@ethersproject/logger" "^5.4.0"
+"@ethersproject/properties@^5.5.0":
+ version "5.5.0"
+ resolved "https://registry.yarnpkg.com/@ethersproject/properties/-/properties-5.5.0.tgz#61f00f2bb83376d2071baab02245f92070c59995"
+ integrity sha512-l3zRQg3JkD8EL3CPjNK5g7kMx4qSwiR60/uk5IVjd3oq1MZR5qUg40CNOoEJoX5wc3DyY5bt9EbMk86C7x0DNA==
+ dependencies:
+ "@ethersproject/logger" "^5.5.0"
+
"@ethersproject/providers@5.4.5":
version "5.4.5"
resolved "https://registry.yarnpkg.com/@ethersproject/providers/-/providers-5.4.5.tgz#eb2ea2a743a8115f79604a8157233a3a2c832928"
@@ -1701,6 +1836,14 @@
"@ethersproject/bytes" "^5.4.0"
"@ethersproject/logger" "^5.4.0"
+"@ethersproject/rlp@^5.5.0":
+ version "5.5.0"
+ resolved "https://registry.yarnpkg.com/@ethersproject/rlp/-/rlp-5.5.0.tgz#530f4f608f9ca9d4f89c24ab95db58ab56ab99a0"
+ integrity sha512-hLv8XaQ8PTI9g2RHoQGf/WSxBfTB/NudRacbzdxmst5VHAqd1sMibWG7SENzT5Dj3yZ3kJYx+WiRYEcQTAkcYA==
+ dependencies:
+ "@ethersproject/bytes" "^5.5.0"
+ "@ethersproject/logger" "^5.5.0"
+
"@ethersproject/sha2@5.4.0", "@ethersproject/sha2@^5.4.0":
version "5.4.0"
resolved "https://registry.yarnpkg.com/@ethersproject/sha2/-/sha2-5.4.0.tgz#c9a8db1037014cbc4e9482bd662f86c090440371"
@@ -1722,6 +1865,18 @@
elliptic "6.5.4"
hash.js "1.1.7"
+"@ethersproject/signing-key@^5.5.0":
+ version "5.5.0"
+ resolved "https://registry.yarnpkg.com/@ethersproject/signing-key/-/signing-key-5.5.0.tgz#2aa37169ce7e01e3e80f2c14325f624c29cedbe0"
+ integrity sha512-5VmseH7qjtNmDdZBswavhotYbWB0bOwKIlOTSlX14rKn5c11QmJwGt4GHeo7NrL/Ycl7uo9AHvEqs5xZgFBTng==
+ dependencies:
+ "@ethersproject/bytes" "^5.5.0"
+ "@ethersproject/logger" "^5.5.0"
+ "@ethersproject/properties" "^5.5.0"
+ bn.js "^4.11.9"
+ elliptic "6.5.4"
+ hash.js "1.1.7"
+
"@ethersproject/solidity@5.4.0", "@ethersproject/solidity@^5.0.0", "@ethersproject/solidity@^5.0.9":
version "5.4.0"
resolved "https://registry.yarnpkg.com/@ethersproject/solidity/-/solidity-5.4.0.tgz#1305e058ea02dc4891df18b33232b11a14ece9ec"
@@ -1742,6 +1897,15 @@
"@ethersproject/constants" "^5.4.0"
"@ethersproject/logger" "^5.4.0"
+"@ethersproject/strings@^5.5.0":
+ version "5.5.0"
+ resolved "https://registry.yarnpkg.com/@ethersproject/strings/-/strings-5.5.0.tgz#e6784d00ec6c57710755699003bc747e98c5d549"
+ integrity sha512-9fy3TtF5LrX/wTrBaT8FGE6TDJyVjOvXynXJz5MT5azq+E6D92zuKNx7i29sWW2FjVOaWjAsiZ1ZWznuduTIIQ==
+ dependencies:
+ "@ethersproject/bytes" "^5.5.0"
+ "@ethersproject/constants" "^5.5.0"
+ "@ethersproject/logger" "^5.5.0"
+
"@ethersproject/transactions@5.4.0", "@ethersproject/transactions@^5.4.0":
version "5.4.0"
resolved "https://registry.yarnpkg.com/@ethersproject/transactions/-/transactions-5.4.0.tgz#a159d035179334bd92f340ce0f77e83e9e1522e0"
@@ -1757,6 +1921,21 @@
"@ethersproject/rlp" "^5.4.0"
"@ethersproject/signing-key" "^5.4.0"
+"@ethersproject/transactions@^5.5.0":
+ version "5.5.0"
+ resolved "https://registry.yarnpkg.com/@ethersproject/transactions/-/transactions-5.5.0.tgz#7e9bf72e97bcdf69db34fe0d59e2f4203c7a2908"
+ integrity sha512-9RZYSKX26KfzEd/1eqvv8pLauCKzDTub0Ko4LfIgaERvRuwyaNV78mJs7cpIgZaDl6RJui4o49lHwwCM0526zA==
+ dependencies:
+ "@ethersproject/address" "^5.5.0"
+ "@ethersproject/bignumber" "^5.5.0"
+ "@ethersproject/bytes" "^5.5.0"
+ "@ethersproject/constants" "^5.5.0"
+ "@ethersproject/keccak256" "^5.5.0"
+ "@ethersproject/logger" "^5.5.0"
+ "@ethersproject/properties" "^5.5.0"
+ "@ethersproject/rlp" "^5.5.0"
+ "@ethersproject/signing-key" "^5.5.0"
+
"@ethersproject/units@5.4.0":
version "5.4.0"
resolved "https://registry.yarnpkg.com/@ethersproject/units/-/units-5.4.0.tgz#d57477a4498b14b88b10396062c8cbbaf20c79fe"
@@ -1798,6 +1977,17 @@
"@ethersproject/properties" "^5.4.0"
"@ethersproject/strings" "^5.4.0"
+"@ethersproject/web@^5.5.0":
+ version "5.5.0"
+ resolved "https://registry.yarnpkg.com/@ethersproject/web/-/web-5.5.0.tgz#0e5bb21a2b58fb4960a705bfc6522a6acf461e28"
+ integrity sha512-BEgY0eL5oH4mAo37TNYVrFeHsIXLRxggCRG/ksRIxI2X5uj5IsjGmcNiRN/VirQOlBxcUhCgHhaDLG4m6XAVoA==
+ dependencies:
+ "@ethersproject/base64" "^5.5.0"
+ "@ethersproject/bytes" "^5.5.0"
+ "@ethersproject/logger" "^5.5.0"
+ "@ethersproject/properties" "^5.5.0"
+ "@ethersproject/strings" "^5.5.0"
+
"@ethersproject/wordlists@5.4.0", "@ethersproject/wordlists@^5.4.0":
version "5.4.0"
resolved "https://registry.yarnpkg.com/@ethersproject/wordlists/-/wordlists-5.4.0.tgz#f34205ec3bbc9e2c49cadaee774cf0b07e7573d7"
@@ -2936,6 +3126,11 @@
"@json-rpc-tools/types" "^1.7.6"
"@pedrouid/environment" "^1.0.1"
+"@juggle/resize-observer@^3.3.1":
+ version "3.3.1"
+ resolved "https://registry.yarnpkg.com/@juggle/resize-observer/-/resize-observer-3.3.1.tgz#b50a781709c81e10701004214340f25475a171a0"
+ integrity sha512-zMM9Ds+SawiUkakS7y94Ymqx+S0ORzpG3frZirN3l+UlXUmSUR7hF4wxCVqW+ei94JzV5kt0uXBcoOEAuiydrw==
+
"@lingui/babel-plugin-extract-messages@^3.9.0":
version "3.9.0"
resolved "https://registry.npmjs.org/@lingui/babel-plugin-extract-messages/-/babel-plugin-extract-messages-3.9.0.tgz"
@@ -3073,6 +3268,72 @@
mkdirp "^1.0.4"
rimraf "^3.0.2"
+"@oclif/command@^1.5.20", "@oclif/command@^1.8.0":
+ version "1.8.0"
+ resolved "https://registry.yarnpkg.com/@oclif/command/-/command-1.8.0.tgz#c1a499b10d26e9d1a611190a81005589accbb339"
+ integrity sha512-5vwpq6kbvwkQwKqAoOU3L72GZ3Ta8RRrewKj9OJRolx28KLJJ8Dg9Rf7obRwt5jQA9bkYd8gqzMTrI7H3xLfaw==
+ dependencies:
+ "@oclif/config" "^1.15.1"
+ "@oclif/errors" "^1.3.3"
+ "@oclif/parser" "^3.8.3"
+ "@oclif/plugin-help" "^3"
+ debug "^4.1.1"
+ semver "^7.3.2"
+
+"@oclif/config@^1.15.1":
+ version "1.17.0"
+ resolved "https://registry.yarnpkg.com/@oclif/config/-/config-1.17.0.tgz#ba8639118633102a7e481760c50054623d09fcab"
+ integrity sha512-Lmfuf6ubjQ4ifC/9bz1fSCHc6F6E653oyaRXxg+lgT4+bYf9bk+nqrUpAbrXyABkCqgIBiFr3J4zR/kiFdE1PA==
+ dependencies:
+ "@oclif/errors" "^1.3.3"
+ "@oclif/parser" "^3.8.0"
+ debug "^4.1.1"
+ globby "^11.0.1"
+ is-wsl "^2.1.1"
+ tslib "^2.0.0"
+
+"@oclif/errors@^1.2.2", "@oclif/errors@^1.3.3", "@oclif/errors@^1.3.5":
+ version "1.3.5"
+ resolved "https://registry.yarnpkg.com/@oclif/errors/-/errors-1.3.5.tgz#a1e9694dbeccab10fe2fe15acb7113991bed636c"
+ integrity sha512-OivucXPH/eLLlOT7FkCMoZXiaVYf8I/w1eTAM1+gKzfhALwWTusxEx7wBmW0uzvkSg/9ovWLycPaBgJbM3LOCQ==
+ dependencies:
+ clean-stack "^3.0.0"
+ fs-extra "^8.1"
+ indent-string "^4.0.0"
+ strip-ansi "^6.0.0"
+ wrap-ansi "^7.0.0"
+
+"@oclif/linewrap@^1.0.0":
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/@oclif/linewrap/-/linewrap-1.0.0.tgz#aedcb64b479d4db7be24196384897b5000901d91"
+ integrity sha512-Ups2dShK52xXa8w6iBWLgcjPJWjais6KPJQq3gQ/88AY6BXoTX+MIGFPrWQO1KLMiQfoTpcLnUwloN4brrVUHw==
+
+"@oclif/parser@^3.8.0", "@oclif/parser@^3.8.3":
+ version "3.8.5"
+ resolved "https://registry.yarnpkg.com/@oclif/parser/-/parser-3.8.5.tgz#c5161766a1efca7343e1f25d769efbefe09f639b"
+ integrity sha512-yojzeEfmSxjjkAvMRj0KzspXlMjCfBzNRPkWw8ZwOSoNWoJn+OCS/m/S+yfV6BvAM4u2lTzX9Y5rCbrFIgkJLg==
+ dependencies:
+ "@oclif/errors" "^1.2.2"
+ "@oclif/linewrap" "^1.0.0"
+ chalk "^2.4.2"
+ tslib "^1.9.3"
+
+"@oclif/plugin-help@^3":
+ version "3.2.3"
+ resolved "https://registry.yarnpkg.com/@oclif/plugin-help/-/plugin-help-3.2.3.tgz#cd24010e7eb326782843d3aa6d6b5a4affebb2c3"
+ integrity sha512-l2Pd0lbOMq4u/7xsl9hqISFqyR9gWEz/8+05xmrXFr67jXyS6EUCQB+mFBa0wepltrmJu0sAFg9AvA2mLaMMqQ==
+ dependencies:
+ "@oclif/command" "^1.5.20"
+ "@oclif/config" "^1.15.1"
+ "@oclif/errors" "^1.2.2"
+ chalk "^4.1.0"
+ indent-string "^4.0.0"
+ lodash.template "^4.4.0"
+ string-width "^4.2.0"
+ strip-ansi "^6.0.0"
+ widest-line "^3.1.0"
+ wrap-ansi "^4.0.0"
+
"@openzeppelin/contracts@3.4.1-solc-0.7-2":
version "3.4.1-solc-0.7-2"
resolved "https://registry.npmjs.org/@openzeppelin/contracts/-/contracts-3.4.1-solc-0.7-2.tgz"
@@ -3380,6 +3641,13 @@
dependencies:
"@sinonjs/commons" "^1.7.0"
+"@sinonjs/fake-timers@^7.1.0":
+ version "7.1.2"
+ resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-7.1.2.tgz#2524eae70c4910edccf99b2f4e6efc5894aff7b5"
+ integrity sha512-iQADsW4LBMISqZ6Ci1dupJL9pprqwcVFTcOsEmQOEhW+KLCVn/Y4Jrvg2k19fIHCp+iFprriYPTdRcQR8NbUPg==
+ dependencies:
+ "@sinonjs/commons" "^1.7.0"
+
"@skidding/launch-editor@^2.2.3":
version "2.2.3"
resolved "https://registry.yarnpkg.com/@skidding/launch-editor/-/launch-editor-2.2.3.tgz#553d3bcf3ce468bd0ee46cb081276342e120d2b3"
@@ -3707,6 +3975,18 @@
resolved "https://registry.yarnpkg.com/@types/array.prototype.flatmap/-/array.prototype.flatmap-1.2.2.tgz#9041c2dc907d583ffb80b8882a782b42436d57c1"
integrity sha512-dto5M/8GxPzjaScvQeft2IG0EkoZZfPg2+1noM2BWiU1VR2zsGHf76LonTOnLQKDuJlKDLzKaru4b+5Sci0Yhg==
+"@types/async-retry@^1.4.2":
+ version "1.4.3"
+ resolved "https://registry.yarnpkg.com/@types/async-retry/-/async-retry-1.4.3.tgz#8b78f6ce88d97e568961732cdd9e5325cdc8c246"
+ integrity sha512-B3C9QmmNULVPL2uSJQ088eGWTNPIeUk35hca6CV8rRDJ8GXuQJP5CCVWA1ZUCrb9xYP7Js/RkLqnNNwKhe+Zsw==
+ dependencies:
+ "@types/retry" "*"
+
+"@types/await-timeout@^0.3.1":
+ version "0.3.1"
+ resolved "https://registry.yarnpkg.com/@types/await-timeout/-/await-timeout-0.3.1.tgz#3a0baafc3a96c7a14447a4dcfdcc76b21ce97c3b"
+ integrity sha512-H5PzROT4KuP7XQDua13Iw8did//OCKAZ/3TL15DjvMzDonrk4HvhH1+tLko96f2guU6XaD3AoqRa49ZOwbwNig==
+
"@types/babel__core@^7.0.0", "@types/babel__core@^7.1.7":
version "7.1.15"
resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.15.tgz#2ccfb1ad55a02c83f8e0ad327cbc332f55eb1024"
@@ -3747,6 +4027,20 @@
dependencies:
"@types/node" "*"
+"@types/bunyan-blackhole@^0.2.2":
+ version "0.2.2"
+ resolved "https://registry.yarnpkg.com/@types/bunyan-blackhole/-/bunyan-blackhole-0.2.2.tgz#469e58c5d129027a9e08bcf9a36232a69b6ad011"
+ integrity sha512-nbuxFn2FVw1AAT1h6shgluwz1cgpLKaMBYbEZcMU69Jb1UvSsXcwRiIg+FP4+/JjEUp/uPYLC+twWpfCAaVN1g==
+ dependencies:
+ "@types/bunyan" "*"
+
+"@types/bunyan@*", "@types/bunyan@^1.8.6":
+ version "1.8.7"
+ resolved "https://registry.yarnpkg.com/@types/bunyan/-/bunyan-1.8.7.tgz#63cc65b5ecff6217d1509409a575e7b991f80831"
+ integrity sha512-jaNt6xX5poSmXuDAkQrSqx2zkR66OrdRDuVnU8ldvn3k/Ci/7Sf5nooKspQWimDnw337Bzt/yirqSThTjvrHkg==
+ dependencies:
+ "@types/node" "*"
+
"@types/d3-array@^2":
version "2.12.3"
resolved "https://registry.yarnpkg.com/@types/d3-array/-/d3-array-2.12.3.tgz#8d16d51fb04ad5a5a8ebe14eb8263a579f1efdd1"
@@ -4113,6 +4407,11 @@
"@types/lingui__core" "*"
"@types/react" "*"
+"@types/lodash@^4.14.168":
+ version "4.14.177"
+ resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.177.tgz#f70c0d19c30fab101cad46b52be60363c43c4578"
+ integrity sha512-0fDwydE2clKe9MNfvXHBHF9WEahRuj+msTuQqOmAApNORFvhMYZKNGGJdCzuhheVjMps/ti0Ak/iJPACMaevvw==
+
"@types/long@^4.0.1":
version "4.0.1"
resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.1.tgz#459c65fa1867dafe6a8f322c4c51695663cc55e9"
@@ -4140,6 +4439,11 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-16.7.9.tgz#3bf27710839e62a470ddf6bd8dd321f1737ce5b4"
integrity sha512-KktxVzS4FPDFVHUUOWyZMvRo//8vqOLITtLMhFSW9IdLsYT/sPyXj3wXtaTcR7A7olCe7R2Xy7R+q5pg2bU46g==
+"@types/node@12.12.54":
+ version "12.12.54"
+ resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.54.tgz#a4b58d8df3a4677b6c08bfbc94b7ad7a7a5f82d1"
+ integrity sha512-ge4xZ3vSBornVYlDnk7yZ0gK6ChHf/CHB7Gl1I0Jhah8DDnEQqBzgohYG4FX4p81TNirSETOiSyn+y1r9/IR6w==
+
"@types/node@>=12.12.47", "@types/node@>=13.7.0":
version "16.11.1"
resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.1.tgz#2e50a649a50fc403433a14f829eface1a3443e97"
@@ -4285,6 +4589,11 @@
dependencies:
"@types/node" "*"
+"@types/retry@*":
+ version "0.12.1"
+ resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.1.tgz#d8f1c0d0dc23afad6dc16a9e993a0865774b4065"
+ integrity sha512-xoDlM2S4ortawSWORYqsdU+2rxdh4LRW9ytc3zmT37RIKQh6IHyKwwtKhKis9ah8ol07DCkZxPt8BBvPjC6v4g==
+
"@types/scheduler@*":
version "0.16.2"
resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.2.tgz#1a62f89525723dde24ba1b01b092bf5df8ad4d39"
@@ -4297,6 +4606,13 @@
dependencies:
"@types/node" "*"
+"@types/sinon@^10.0.2":
+ version "10.0.3"
+ resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-10.0.3.tgz#2d17cf53f42981e8ebd3e2339dade748b0da742a"
+ integrity sha512-XUaFuUOQ3A/r6gS1qCU/USMleascaqGeQpGR1AZ5JdRtBPlzijRzKsik1TuGzvdtPA0mdq42JqaJmJ+Afg1LJg==
+ dependencies:
+ "@sinonjs/fake-timers" "^7.1.0"
+
"@types/sinonjs__fake-timers@^6.0.2":
version "6.0.3"
resolved "https://registry.yarnpkg.com/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-6.0.3.tgz#79df6f358ae8f79e628fe35a63608a0ea8e7cf08"
@@ -4317,6 +4633,11 @@
resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c"
integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==
+"@types/stats-lite@^2.2.0":
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/@types/stats-lite/-/stats-lite-2.2.0.tgz#bc8190bf9dfa1e16b89eaa2b433c99dff0804de9"
+ integrity sha512-YV6SS4QC+pbzqjMIV8qVSTDOOazgKBLTVaN+7PfuxELjz/eyzc20KwDVGPrbHt2OcYMA7K2ezLB45Cp6DpNOSQ==
+
"@types/styled-components@*", "@types/styled-components@^5.1.0":
version "5.1.13"
resolved "https://registry.yarnpkg.com/@types/styled-components/-/styled-components-5.1.13.tgz#a2906b68c2c6c811996216983b74ca02e22c6c34"
@@ -4542,6 +4863,11 @@
"@typescript-eslint/types" "4.30.0"
eslint-visitor-keys "^2.0.0"
+"@uniswap/default-token-list@^2.0.0":
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/@uniswap/default-token-list/-/default-token-list-2.2.0.tgz#d85a5c2520f57f4920bd989dfc9f01e1b701a567"
+ integrity sha512-vFPWoGzDjHP4i2l7yLaober/lZMmzOZXXirVF8XNyfNzRxgmYCWKO6SzKtfEUwxpd3/KUebgdK55II4Mnak62A==
+
"@uniswap/governance@^1.0.2":
version "1.0.2"
resolved "https://registry.npmjs.org/@uniswap/governance/-/governance-1.0.2.tgz"
@@ -4574,6 +4900,28 @@
resolved "https://registry.yarnpkg.com/@uniswap/redux-multicall/-/redux-multicall-1.0.0.tgz#0cee4448909a788ea4700e5ede75ffeba05b5d75"
integrity sha512-zR6tNC3XF6JuI6PjGlZW2Hz7tTzRzzVaPJfZ01BBWBJVt/2ixJY0SH514uffD03NHYiXZA//hlPQLfw3TkIxQg==
+"@uniswap/router-sdk@^1.0.1":
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/@uniswap/router-sdk/-/router-sdk-1.0.2.tgz#6f104a9e507a14182b90fb93a69aceb59d6a9a62"
+ integrity sha512-NYsNsSMY+B0F9TuARDPjVsrfS61X87qsLIfHOcza4Tbp8/bLLnXPA67Jwc6iN4u7xUEdDR049mn0ALPlGeTkDw==
+ dependencies:
+ "@ethersproject/abi" "^5.5.0"
+ "@uniswap/sdk-core" "^3.0.1"
+ "@uniswap/swap-router-contracts" "1.0.0"
+ "@uniswap/v2-sdk" "^3.0.1"
+ "@uniswap/v3-sdk" "^3.7.1"
+
+"@uniswap/router-sdk@^1.0.3":
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/@uniswap/router-sdk/-/router-sdk-1.0.3.tgz#378a8cc96a3f17b5627b811e64bfcf1e0e50a551"
+ integrity sha512-9Nq0+J4+u5cPkZlLjCnV1IbbmPVVDxlwrIuYh/fPTVMH4DnPVVEcP68BArtkSUG0OSC87rWrkEHIZd+L8CO6sQ==
+ dependencies:
+ "@ethersproject/abi" "^5.5.0"
+ "@uniswap/sdk-core" "^3.0.1"
+ "@uniswap/swap-router-contracts" "1.1.0"
+ "@uniswap/v2-sdk" "^3.0.1"
+ "@uniswap/v3-sdk" "^3.7.1"
+
"@uniswap/sdk-core@^3.0.0-alpha.3", "@uniswap/sdk-core@^3.0.1":
version "3.0.1"
resolved "https://registry.yarnpkg.com/@uniswap/sdk-core/-/sdk-core-3.0.1.tgz#d08dd68257983af64b9a5f4d6b9cf26124b4138f"
@@ -4586,7 +4934,72 @@
tiny-invariant "^1.1.0"
toformat "^2.0.0"
-"@uniswap/token-lists@^1.0.0-beta.27":
+"@uniswap/smart-order-router@^2.5.4":
+ version "2.5.4"
+ resolved "https://registry.yarnpkg.com/@uniswap/smart-order-router/-/smart-order-router-2.5.4.tgz#dee2580d5fd836aecd46d935ab6ca7f7963c0501"
+ integrity sha512-uInC4+gL/n28XtEVOY/jrV7uT8FFBtgoW7x0Ks6SGoe/Gx/dYRmzhdeIR1rb3Tg1WP10OwnnC0AW73BJghdGxA==
+ dependencies:
+ "@bitauth/libauth" "^1.17.1"
+ "@ethersproject/bignumber" "^5.1.1"
+ "@oclif/command" "^1.8.0"
+ "@oclif/errors" "^1.3.5"
+ "@types/async-retry" "^1.4.2"
+ "@types/await-timeout" "^0.3.1"
+ "@types/bunyan" "^1.8.6"
+ "@types/bunyan-blackhole" "^0.2.2"
+ "@types/lodash" "^4.14.168"
+ "@types/sinon" "^10.0.2"
+ "@types/stats-lite" "^2.2.0"
+ "@uniswap/default-token-list" "^2.0.0"
+ "@uniswap/router-sdk" "^1.0.3"
+ "@uniswap/swap-router-contracts" "1.1.0"
+ "@uniswap/token-lists" "^1.0.0-beta.25"
+ "@uniswap/v2-core" "^1.0.1"
+ "@uniswap/v2-periphery" "^1.1.0-beta.0"
+ "@uniswap/v2-sdk" "^3.0.1"
+ "@uniswap/v3-periphery" "^1.1.1"
+ "@uniswap/v3-sdk" "^3.7.0"
+ async-retry "^1.3.1"
+ await-timeout "^1.1.1"
+ axios "^0.21.1"
+ bunyan "^1.8.15"
+ bunyan-blackhole "^1.1.1"
+ bunyan-debug-stream "^2.0.0"
+ cli-logger "^0.5.40"
+ dotenv "^10.0.0"
+ ethereum-types "^3.5.0"
+ ethers "^5.1.4"
+ graphql "^15.5.0"
+ graphql-request "^3.4.0"
+ lodash "^4.17.21"
+ mnemonist "^0.38.3"
+ node-cache "^5.1.2"
+ stats-lite "^2.2.0"
+ tslib "^1.14.1"
+
+"@uniswap/swap-router-contracts@1.0.0":
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/@uniswap/swap-router-contracts/-/swap-router-contracts-1.0.0.tgz#357a5b681fa5d5a5b1629271b99845c96932d4fa"
+ integrity sha512-EOO94glkJ4KI38IVklmMJkZzQLSY2MCNndYBPWlu64g3DkYJ/d04rbiHTEh7zYwT4gvGW8InQpmQA7mzmGIANw==
+ dependencies:
+ "@openzeppelin/contracts" "3.4.1-solc-0.7-2"
+ "@uniswap/v2-core" "1.0.1"
+ "@uniswap/v3-core" "1.0.0"
+ "@uniswap/v3-periphery" "1.3.0"
+ hardhat-watcher "^2.1.1"
+
+"@uniswap/swap-router-contracts@1.1.0":
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/@uniswap/swap-router-contracts/-/swap-router-contracts-1.1.0.tgz#e027b14d4c172f231c53c48e1fd708a78d7d94d8"
+ integrity sha512-GPmpx1lvjXWloB95+YUabr3UHJYr3scnSS8EzaNXnNrIz9nYZ+XQcMaJxOKe85Yi7IfcUQpj0HzD2TW99dtolA==
+ dependencies:
+ "@openzeppelin/contracts" "3.4.1-solc-0.7-2"
+ "@uniswap/v2-core" "1.0.1"
+ "@uniswap/v3-core" "1.0.0"
+ "@uniswap/v3-periphery" "1.3.0"
+ hardhat-watcher "^2.1.1"
+
+"@uniswap/token-lists@^1.0.0-beta.25", "@uniswap/token-lists@^1.0.0-beta.27":
version "1.0.0-beta.27"
resolved "https://registry.yarnpkg.com/@uniswap/token-lists/-/token-lists-1.0.0-beta.27.tgz#8b02a979b0b0024cc96f60e694a3f6db6b2ccab2"
integrity sha512-x5hmIniQ9TGqOBCRqfWcmZi/U5kB0qrHMDQ9igs3nMbK0wwmYLraL4owbIwXFGR/co6/lJYJC4K/Gjn4wZY5mQ==
@@ -4596,7 +5009,7 @@
resolved "https://registry.npmjs.org/@uniswap/v2-core/-/v2-core-1.0.0.tgz"
integrity sha512-BJiXrBGnN8mti7saW49MXwxDBRFiWemGetE58q8zgfnPPzQKq55ADltEILqOt6VFZ22kVeVKbF8gVd8aY3l7pA==
-"@uniswap/v2-core@1.0.1":
+"@uniswap/v2-core@1.0.1", "@uniswap/v2-core@^1.0.1":
version "1.0.1"
resolved "https://registry.npmjs.org/@uniswap/v2-core/-/v2-core-1.0.1.tgz"
integrity sha512-MtybtkUPSyysqLY2U210NBDeCHX+ltHt3oADGdjqoThZaFRDKwM6k1Nb3F0A3hk5hwuQvytFWhrWHOEq6nVJ8Q==
@@ -4609,10 +5022,10 @@
"@uniswap/lib" "1.1.1"
"@uniswap/v2-core" "1.0.0"
-"@uniswap/v2-sdk@^3.0.0-alpha.2":
- version "3.0.0-alpha.2"
- resolved "https://registry.npmjs.org/@uniswap/v2-sdk/-/v2-sdk-3.0.0-alpha.2.tgz"
- integrity sha512-LkGGZMdJueIIC3OBzgiBMV/1GdZgzTTf2qGEgBAxJUuCSFf5wZQgWNU4KOkoaBrl/FlEctAGSgGh3j3BhWKL5Q==
+"@uniswap/v2-sdk@^3.0.1":
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/@uniswap/v2-sdk/-/v2-sdk-3.0.1.tgz#690c484104c1debd1db56a236e5497def53d698b"
+ integrity sha512-eSpm2gjo2CZh9FACH5fq42str/oSNyWcDxB27o5k44bEew4sxb+pld4gGIf/byJndLBvArR9PtH8c0n/goNOTw==
dependencies:
"@ethersproject/address" "^5.0.0"
"@ethersproject/solidity" "^5.0.0"
@@ -4625,6 +5038,18 @@
resolved "https://registry.npmjs.org/@uniswap/v3-core/-/v3-core-1.0.0.tgz"
integrity sha512-kSC4djMGKMHj7sLMYVnn61k9nu+lHjMIxgg9CDQT+s2QYLoA56GbSK9Oxr+qJXzzygbkrmuY6cwgP6cW2JXPFA==
+"@uniswap/v3-periphery@1.3.0":
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/@uniswap/v3-periphery/-/v3-periphery-1.3.0.tgz#37f0a1ef6025221722e50e9f3f2009c2d5d6e4ec"
+ integrity sha512-HjHdI5RkjBl8zz3bqHShrbULFoZSrjbbrRHoO2vbzn+WRzTa6xY4PWphZv2Tlcb38YEKfKHp6NPl5hVedac8uw==
+ dependencies:
+ "@openzeppelin/contracts" "3.4.1-solc-0.7-2"
+ "@uniswap/lib" "^4.0.1-alpha"
+ "@uniswap/v2-core" "1.0.1"
+ "@uniswap/v3-core" "1.0.0"
+ base64-sol "1.0.1"
+ hardhat-watcher "^2.1.1"
+
"@uniswap/v3-periphery@^1.0.1":
version "1.2.1"
resolved "https://registry.yarnpkg.com/@uniswap/v3-periphery/-/v3-periphery-1.2.1.tgz#7775630bea774a2cf989ab87ce3c328ac52e0d50"
@@ -4649,7 +5074,7 @@
base64-sol "1.0.1"
hardhat-watcher "^2.1.1"
-"@uniswap/v3-sdk@^3.7.1":
+"@uniswap/v3-sdk@^3.7.0", "@uniswap/v3-sdk@^3.7.1":
version "3.7.1"
resolved "https://registry.yarnpkg.com/@uniswap/v3-sdk/-/v3-sdk-3.7.1.tgz#8a3740ff6302d8069e7ce4a38b7588721398048b"
integrity sha512-/0FBsrRijfAEOVO0ejCQX36MwaKzjKCaInUA1dNqFyDNZ5dthvv6jUhMADYuNXZnhN6NcSdIj6xhlc/cpgPm9Q==
@@ -5660,6 +6085,13 @@ async-mutex@^0.2.6:
dependencies:
tslib "^2.0.0"
+async-retry@^1.3.1:
+ version "1.3.3"
+ resolved "https://registry.yarnpkg.com/async-retry/-/async-retry-1.3.3.tgz#0e7f36c04d8478e7a58bdbed80cedf977785f280"
+ integrity sha512-wfr/jstw9xNi/0teMHrRW7dsz3Lt5ARhYNZ2ewpadnhaIp5mbALhOAP+EAdsC7t4Z6wqsDVv9+W6gm1Dk9mEyw==
+ dependencies:
+ retry "0.13.1"
+
async@^1.4.2:
version "1.5.2"
resolved "https://registry.npmjs.org/async/-/async-1.5.2.tgz"
@@ -5732,6 +6164,11 @@ available-typed-arrays@^1.0.5:
resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7"
integrity sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==
+await-timeout@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/await-timeout/-/await-timeout-1.1.1.tgz#d42062ee6bc4eb271fe4d4f851eb658dae7e3906"
+ integrity sha512-gsDXAS6XVc4Jt+7S92MPX6Noq69bdeXUPEaXd8dk3+yVr629LTDLxNt4j1ycBbrU+AStK2PhKIyNIM+xzWMVOQ==
+
aws-sign2@~0.7.0:
version "0.7.0"
resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8"
@@ -6661,6 +7098,11 @@ big.js@^5.2.2:
resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328"
integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==
+bignumber.js@~9.0.0:
+ version "9.0.1"
+ resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.0.1.tgz#8d7ba124c882bfd8e43260c67475518d0689e4e5"
+ integrity sha512-IdZR9mh6ahOBv/hYGiXyVuyCetmGJhtYkqLBpTStdhEGjegpPlUawydyaF3pbIOFynJTpllEs+NP+CS9jKFLjA==
+
binary-extensions@^1.0.0:
version "1.13.1"
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65"
@@ -7034,6 +7476,31 @@ builtin-status-codes@^3.0.0:
resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8"
integrity sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=
+bunyan-blackhole@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/bunyan-blackhole/-/bunyan-blackhole-1.1.1.tgz#b9208586dc0b4e47f4f713215b1bddd65e4f6257"
+ integrity sha1-uSCFhtwLTkf09xMhWxvd1l5PYlc=
+ dependencies:
+ stream-blackhole "^1.0.3"
+
+bunyan-debug-stream@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/bunyan-debug-stream/-/bunyan-debug-stream-2.0.0.tgz#b9593e38753f594e3f9db3eb2fdebdc2af147a9f"
+ integrity sha512-Ovl43CJ7nUwalLzdXc6E1nGIy6ift9Z/QpYXUtsjpDAg35ZFKXifKNZyfpMGuN3N7ijLLqbnxPsMMHsXDdXa9A==
+ dependencies:
+ colors "^1.0.3"
+ exception-formatter "^1.0.4"
+
+bunyan@^1.8.15:
+ version "1.8.15"
+ resolved "https://registry.yarnpkg.com/bunyan/-/bunyan-1.8.15.tgz#8ce34ca908a17d0776576ca1b2f6cbd916e93b46"
+ integrity sha512-0tECWShh6wUysgucJcBAoYegf3JJoZWibxdqhTm7OHPeT42qdjkZ29QCMcKwbgU1kiH+auSIasNRXMLWXafXig==
+ optionalDependencies:
+ dtrace-provider "~0.8"
+ moment "^2.19.3"
+ mv "~2"
+ safe-json-stringify "~1"
+
bytes@3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048"
@@ -7445,6 +7912,11 @@ cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3:
inherits "^2.0.1"
safe-buffer "^5.0.1"
+circular@^1.0.5:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/circular/-/circular-1.0.5.tgz#7da77af98bbde9ce4b5b358cd556b5dded2d3149"
+ integrity sha1-fad6+Yu96c5LWzWM1Va13e0tMUk=
+
cjs-module-lexer@^0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-0.6.0.tgz#4186fcca0eae175970aee870b9fe2d6cf8d5655f"
@@ -7472,6 +7944,13 @@ clean-stack@^2.0.0:
resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b"
integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==
+clean-stack@^3.0.0:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-3.0.1.tgz#155bf0b2221bf5f4fba89528d24c5953f17fe3a8"
+ integrity sha512-lR9wNiMRcVQjSB3a7xXGLuz4cr4wJuuXlaAEbRutGowQTmlp7R72/DOgN21e8jdwblMWl9UOJMJXarX94pzKdg==
+ dependencies:
+ escape-string-regexp "4.0.0"
+
cli-boxes@^1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/cli-boxes/-/cli-boxes-1.0.0.tgz"
@@ -7491,6 +7970,19 @@ cli-cursor@^3.1.0:
dependencies:
restore-cursor "^3.1.0"
+cli-logger@^0.5.40:
+ version "0.5.40"
+ resolved "https://registry.yarnpkg.com/cli-logger/-/cli-logger-0.5.40.tgz#097f0e11b072c7c698a26c47f588a29c20b48b0b"
+ integrity sha1-CX8OEbByx8aYomxH9YiinCC0iws=
+ dependencies:
+ circular "^1.0.5"
+ cli-util "~1.1.27"
+
+cli-regexp@~0.1.0:
+ version "0.1.2"
+ resolved "https://registry.yarnpkg.com/cli-regexp/-/cli-regexp-0.1.2.tgz#6bcd93b09fb2ed1025d30a1155d5997954a53512"
+ integrity sha1-a82TsJ+y7RAl0woRVdWZeVSlNRI=
+
cli-spinners@^2.5.0:
version "2.6.0"
resolved "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.6.0.tgz"
@@ -7529,6 +8021,13 @@ cli-truncate@^2.1.0:
slice-ansi "^3.0.0"
string-width "^4.2.0"
+cli-util@~1.1.27:
+ version "1.1.27"
+ resolved "https://registry.yarnpkg.com/cli-util/-/cli-util-1.1.27.tgz#42d69e36a040a321fc9cf851c1513cadc5093054"
+ integrity sha1-QtaeNqBAoyH8nPhRwVE8rcUJMFQ=
+ dependencies:
+ cli-regexp "~0.1.0"
+
cli-width@^3.0.0:
version "3.0.0"
resolved "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz"
@@ -7576,16 +8075,16 @@ clone-response@^1.0.2:
dependencies:
mimic-response "^1.0.0"
+clone@2.x, clone@^2.0.0, clone@^2.1.1:
+ version "2.1.2"
+ resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f"
+ integrity sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=
+
clone@^1.0.2:
version "1.0.4"
resolved "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz"
integrity sha1-2jCcwmPfFZlMaIypAheco8fNfH4=
-clone@^2.0.0, clone@^2.1.1:
- version "2.1.2"
- resolved "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz"
- integrity sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=
-
clsx@^1.1.0:
version "1.1.1"
resolved "https://registry.npmjs.org/clsx/-/clsx-1.1.1.tgz"
@@ -7699,7 +8198,7 @@ colors@1.0.3:
resolved "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz"
integrity sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=
-colors@^1.1.2:
+colors@^1.0.3, colors@^1.1.2:
version "1.4.0"
resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78"
integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==
@@ -9295,6 +9794,11 @@ dotenv@8.2.0:
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.2.0.tgz#97e619259ada750eea3e4ea3e26bceea5424b16a"
integrity sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==
+dotenv@^10.0.0:
+ version "10.0.0"
+ resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-10.0.0.tgz#3d4227b8fb95f81096cdd2b66653fb2c7085ba81"
+ integrity sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==
+
dotenv@^8.2.0:
version "8.6.0"
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.6.0.tgz#061af664d19f7f4d8fc6e4ff9b584ce237adcb8b"
@@ -9316,6 +9820,13 @@ drbg.js@^1.0.1:
create-hash "^1.1.2"
create-hmac "^1.1.4"
+dtrace-provider@~0.8:
+ version "0.8.8"
+ resolved "https://registry.yarnpkg.com/dtrace-provider/-/dtrace-provider-0.8.8.tgz#2996d5490c37e1347be263b423ed7b297fb0d97e"
+ integrity sha512-b7Z7cNtHPhH9EJhNNbbeqTcXB8LGFFZhq1PGgEvpeHlzd36bhbdTWoE/Ba/YguqpBSlAPKnARWhVlhunCMwfxg==
+ dependencies:
+ nan "^2.14.0"
+
duplexer3@^0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2"
@@ -9632,16 +10143,16 @@ escape-string-regexp@2.0.0, escape-string-regexp@^2.0.0:
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344"
integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==
+escape-string-regexp@4.0.0, escape-string-regexp@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34"
+ integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
+
escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
-escape-string-regexp@^4.0.0:
- version "4.0.0"
- resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34"
- integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
-
escodegen@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-2.0.0.tgz#5e32b12833e8aa8fa35e1bf0befa89380484c7dd"
@@ -10143,6 +10654,14 @@ ethereum-cryptography@^0.1.3:
secp256k1 "^4.0.1"
setimmediate "^1.0.5"
+ethereum-types@^3.5.0:
+ version "3.6.0"
+ resolved "https://registry.yarnpkg.com/ethereum-types/-/ethereum-types-3.6.0.tgz#7cf0a7258537b1f8d113dd51d050189a742a9a6e"
+ integrity sha512-iJX96C9W1elWhCZKUiSQfWn9fC+EO+xU2TvAE/p7QhMwcGibihKsxcG27B/4WZAudd8jNoeIhY4PH2qQPLuUfw==
+ dependencies:
+ "@types/node" "12.12.54"
+ bignumber.js "~9.0.0"
+
ethereumjs-abi@0.6.5:
version "0.6.5"
resolved "https://registry.npmjs.org/ethereumjs-abi/-/ethereumjs-abi-0.6.5.tgz"
@@ -10277,6 +10796,42 @@ 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.1.4:
+ version "5.4.7"
+ resolved "https://registry.yarnpkg.com/ethers/-/ethers-5.4.7.tgz#0fd491a5da7c9793de2d6058d76b41b1e7efba8f"
+ integrity sha512-iZc5p2nqfWK1sj8RabwsPM28cr37Bpq7ehTQ5rWExBr2Y09Sn1lDKZOED26n+TsZMye7Y6mIgQ/1cwpSD8XZew==
+ dependencies:
+ "@ethersproject/abi" "5.4.1"
+ "@ethersproject/abstract-provider" "5.4.1"
+ "@ethersproject/abstract-signer" "5.4.1"
+ "@ethersproject/address" "5.4.0"
+ "@ethersproject/base64" "5.4.0"
+ "@ethersproject/basex" "5.4.0"
+ "@ethersproject/bignumber" "5.4.2"
+ "@ethersproject/bytes" "5.4.0"
+ "@ethersproject/constants" "5.4.0"
+ "@ethersproject/contracts" "5.4.1"
+ "@ethersproject/hash" "5.4.0"
+ "@ethersproject/hdnode" "5.4.0"
+ "@ethersproject/json-wallets" "5.4.0"
+ "@ethersproject/keccak256" "5.4.0"
+ "@ethersproject/logger" "5.4.1"
+ "@ethersproject/networks" "5.4.2"
+ "@ethersproject/pbkdf2" "5.4.0"
+ "@ethersproject/properties" "5.4.1"
+ "@ethersproject/providers" "5.4.5"
+ "@ethersproject/random" "5.4.0"
+ "@ethersproject/rlp" "5.4.0"
+ "@ethersproject/sha2" "5.4.0"
+ "@ethersproject/signing-key" "5.4.0"
+ "@ethersproject/solidity" "5.4.0"
+ "@ethersproject/strings" "5.4.0"
+ "@ethersproject/transactions" "5.4.0"
+ "@ethersproject/units" "5.4.0"
+ "@ethersproject/wallet" "5.4.0"
+ "@ethersproject/web" "5.4.0"
+ "@ethersproject/wordlists" "5.4.0"
+
ethers@^5.4.0, ethers@^5.4.6:
version "5.4.6"
resolved "https://registry.yarnpkg.com/ethers/-/ethers-5.4.6.tgz#fe0a023956b5502c947f58e82fbcf9a73e5e75b6"
@@ -10374,6 +10929,13 @@ evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3:
md5.js "^1.3.4"
safe-buffer "^5.1.1"
+exception-formatter@^1.0.4:
+ version "1.0.7"
+ resolved "https://registry.yarnpkg.com/exception-formatter/-/exception-formatter-1.0.7.tgz#3291616b86fceabefa97aee6a4708032c6e3b96d"
+ integrity sha512-zV45vEsjytJrwfGq6X9qd1Ll56cW4NC2mhCO6lqwMk4ZpA1fZ6C3UiaQM/X7if+7wZFmCgss3ahp9B/uVFuLRw==
+ dependencies:
+ colors "^1.0.3"
+
exec-sh@^0.3.2:
version "0.3.6"
resolved "https://registry.yarnpkg.com/exec-sh/-/exec-sh-0.3.6.tgz#ff264f9e325519a60cb5e273692943483cca63bc"
@@ -11045,7 +11607,7 @@ fs-extra@10.0.0:
jsonfile "^6.0.1"
universalify "^2.0.0"
-fs-extra@8.1.0, fs-extra@^8.1.0:
+fs-extra@8.1.0, fs-extra@^8.1, fs-extra@^8.1.0:
version "8.1.0"
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0"
integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==
@@ -11248,6 +11810,17 @@ glob-parent@^5.1.2, glob-parent@~5.1.0, glob-parent@~5.1.2:
dependencies:
is-glob "^4.0.1"
+glob@^6.0.1:
+ version "6.0.4"
+ resolved "https://registry.yarnpkg.com/glob/-/glob-6.0.4.tgz#0f08860f6a155127b2fadd4f9ce24b1aab6e4d22"
+ integrity sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI=
+ dependencies:
+ inflight "^1.0.4"
+ inherits "2"
+ minimatch "2 || 3"
+ once "^1.3.0"
+ path-is-absolute "^1.0.0"
+
glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@^7.1.7:
version "7.2.0"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023"
@@ -11349,7 +11922,7 @@ globby@11.0.3:
merge2 "^1.3.0"
slash "^3.0.0"
-globby@^11.0.3:
+globby@^11.0.1, globby@^11.0.3:
version "11.0.4"
resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.4.tgz#2cbaff77c2f2a62e71e9b2813a67b97a3a3001a5"
integrity sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg==
@@ -12733,6 +13306,11 @@ isexe@^2.0.0:
resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=
+isnumber@~1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/isnumber/-/isnumber-1.0.0.tgz#0e3f9759b581d99dd85086f0ec2a74909cfadd01"
+ integrity sha1-Dj+XWbWB2Z3YUIbw7Cp0kJz63QE=
+
isobject@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89"
@@ -13990,7 +14568,7 @@ lodash.once@^4.0.0, lodash.once@^4.1.1:
resolved "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz"
integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=
-lodash.template@^4.5.0:
+lodash.template@^4.4.0, lodash.template@^4.5.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-4.5.0.tgz#f976195cf3f347d0d5f52483569fe8031ccce8ab"
integrity sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A==
@@ -14535,7 +15113,7 @@ minimalistic-crypto-utils@^1.0.1:
resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a"
integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=
-minimatch@3.0.4, minimatch@^3.0.4:
+"minimatch@2 || 3", minimatch@3.0.4, minimatch@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==
@@ -14619,6 +15197,18 @@ mkdirp@^1.0.3, mkdirp@^1.0.4:
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
+mnemonist@^0.38.3:
+ version "0.38.3"
+ resolved "https://registry.yarnpkg.com/mnemonist/-/mnemonist-0.38.3.tgz#35ec79c1c1f4357cfda2fe264659c2775ccd7d9d"
+ integrity sha512-2K9QYubXx/NAjv4VLq1d1Ly8pWNC5L3BrixtdkyTegXWJIqY+zLNDhhX/A+ZwWt70tB1S8H4BE8FLYEFyNoOBw==
+ dependencies:
+ obliterator "^1.6.1"
+
+moment@^2.19.3:
+ version "2.29.1"
+ resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3"
+ integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==
+
move-concurrently@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92"
@@ -14706,6 +15296,15 @@ mute-stream@0.0.8:
resolved "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz"
integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==
+mv@~2:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/mv/-/mv-2.1.1.tgz#ae6ce0d6f6d5e0a4f7d893798d03c1ea9559b6a2"
+ integrity sha1-rmzg1vbV4KT32JN5jQPB6pVZtqI=
+ dependencies:
+ mkdirp "~0.5.1"
+ ncp "~2.0.0"
+ rimraf "~2.4.0"
+
nan@^2.12.1, nan@^2.14.0, nan@^2.2.1:
version "2.14.2"
resolved "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz"
@@ -14755,6 +15354,11 @@ natural-compare@^1.4.0:
resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=
+ncp@~2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/ncp/-/ncp-2.0.0.tgz#195a21d6c46e361d2fb1281ba38b91e9df7bdbb3"
+ integrity sha1-GVoh1sRuNh0vsSgbo4uR6d9727M=
+
negotiator@0.6.2:
version "0.6.2"
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb"
@@ -14788,6 +15392,13 @@ node-addon-api@^2.0.0:
resolved "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.2.tgz"
integrity sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==
+node-cache@^5.1.2:
+ version "5.1.2"
+ resolved "https://registry.yarnpkg.com/node-cache/-/node-cache-5.1.2.tgz#f264dc2ccad0a780e76253a694e9fd0ed19c398d"
+ integrity sha512-t1QzWwnk4sjLWaQAS8CHgOJ+RAfmHpxFWmc36IWTiWHQfs0w5JDMBS1b1ZxQteo0vVVuWJvIUKHDkkeK7vIGCg==
+ dependencies:
+ clone "2.x"
+
node-fetch@2.1.2:
version "2.1.2"
resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.1.2.tgz"
@@ -15112,6 +15723,11 @@ object.values@^1.1.0, object.values@^1.1.3, object.values@^1.1.4:
define-properties "^1.1.3"
es-abstract "^1.18.2"
+obliterator@^1.6.1:
+ version "1.6.1"
+ resolved "https://registry.yarnpkg.com/obliterator/-/obliterator-1.6.1.tgz#dea03e8ab821f6c4d96a299e17aef6a3af994ef3"
+ integrity sha512-9WXswnqINnnhOG/5SLimUlzuU1hFJUc8zkwyD59Sd+dPOMf05PmnYG/d6Q7HZ+KmgkZJa1PxRso6QdM3sTNHig==
+
obuf@^1.0.0, obuf@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e"
@@ -18112,6 +18728,11 @@ ret@~0.1.10:
resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc"
integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==
+retry@0.13.1:
+ version "0.13.1"
+ resolved "https://registry.yarnpkg.com/retry/-/retry-0.13.1.tgz#185b1587acf67919d63b357349e03537b2484658"
+ integrity sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==
+
retry@^0.12.0:
version "0.12.0"
resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b"
@@ -18159,6 +18780,13 @@ rimraf@^3.0.0, rimraf@^3.0.2:
dependencies:
glob "^7.1.3"
+rimraf@~2.4.0:
+ version "2.4.5"
+ resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.4.5.tgz#ee710ce5d93a8fdb856fb5ea8ff0e2d75934b2da"
+ integrity sha1-7nEM5dk6j9uFb7Xqj/Di11k0sto=
+ dependencies:
+ glob "^6.0.1"
+
ripemd160@^2.0.0, ripemd160@^2.0.1:
version "2.0.2"
resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c"
@@ -18339,6 +18967,11 @@ safe-identifier@^0.4.2:
resolved "https://registry.yarnpkg.com/safe-identifier/-/safe-identifier-0.4.2.tgz#cf6bfca31c2897c588092d1750d30ef501d59fcb"
integrity sha512-6pNbSMW6OhAi9j+N8V+U715yBQsaWJ7eyEUaOrawX+isg5ZxhUlV1NipNtgaKHmFGiABwt+ZF04Ii+3Xjkg+8w==
+safe-json-stringify@~1:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/safe-json-stringify/-/safe-json-stringify-1.2.0.tgz#356e44bc98f1f93ce45df14bcd7c01cda86e0afd"
+ integrity sha512-gH8eh2nZudPQO6TytOvbxnuhYBOvDBBLW52tz5q6X58lJcd/tkmqFR+5Z9adS8aJtURSXWThWy/xJtJwixErvg==
+
safe-json-utils@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/safe-json-utils/-/safe-json-utils-1.1.1.tgz#0e883874467d95ab914c3f511096b89bfb3e63b1"
@@ -19109,11 +19742,23 @@ static-extend@^0.1.1:
define-property "^0.2.5"
object-copy "^0.1.0"
+stats-lite@^2.2.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/stats-lite/-/stats-lite-2.2.0.tgz#278a5571fa1d2e8b1691295dccc0235282393bbf"
+ integrity sha512-/Kz55rgUIv2KP2MKphwYT/NCuSfAlbbMRv2ZWw7wyXayu230zdtzhxxuXXcvsc6EmmhS8bSJl3uS1wmMHFumbA==
+ dependencies:
+ isnumber "~1.0.0"
+
"statuses@>= 1.4.0 < 2", "statuses@>= 1.5.0 < 2", statuses@~1.5.0:
version "1.5.0"
resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c"
integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=
+stream-blackhole@^1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/stream-blackhole/-/stream-blackhole-1.0.3.tgz#6fc2e2c2e9d9fde6be8c68d3db88de09802e4d63"
+ integrity sha1-b8LiwunZ/ea+jGjT24jeCYAuTWM=
+
stream-browserify@^2.0.1:
version "2.0.2"
resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.2.tgz#87521d38a44aa7ee91ce1cd2a47df0cb49dd660b"
@@ -19220,7 +19865,7 @@ string-width@^3.0.0, string-width@^3.1.0:
is-fullwidth-code-point "^2.0.0"
strip-ansi "^5.1.0"
-string-width@^4.1.0, string-width@^4.2.0:
+string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0:
version "4.2.2"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.2.tgz#dafd4f9559a7585cfba529c6a0a4f73488ebd4c5"
integrity sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==
@@ -19965,7 +20610,7 @@ tslib@2.0.1:
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.0.1.tgz#410eb0d113e5b6356490eec749603725b021b43e"
integrity sha512-SgIkNheinmEBgx1IUNirK0TUD4X9yjjBRTqqjggWCU3pUEqIk3/Uwl3yRixYKT6WjQuGiwDv4NomL3wqRCj+CQ==
-tslib@^1.0.0, tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3:
+tslib@^1.0.0, tslib@^1.14.1, tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3:
version "1.14.1"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
@@ -20404,6 +21049,13 @@ use-elapsed-time@^2.1.6:
resolved "https://registry.npmjs.org/use-elapsed-time/-/use-elapsed-time-2.1.8.tgz"
integrity sha512-lNLTDffKHdHWweQNvnch9tFI2eRP3tXccSLrwE7U6xrfyWFNEgNQZWWsGhQvtwKa0kJ6L+7E5wKbi3jg86opjg==
+use-resize-observer@^8.0.0:
+ version "8.0.0"
+ resolved "https://registry.yarnpkg.com/use-resize-observer/-/use-resize-observer-8.0.0.tgz#69bd80c1ddd94f3758563fe107efb25fed85067a"
+ integrity sha512-n0iKSeiQpJCyaFh5JA0qsVLBIovsF4EIIR1G6XiBwKJN66ZrD4Oj62bjcuTAATPKiSp6an/2UZZxCf/67fk3sQ==
+ dependencies:
+ "@juggle/resize-observer" "^3.3.1"
+
use-sidecar@^1.0.1:
version "1.0.5"
resolved "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.0.5.tgz"
@@ -20930,6 +21582,13 @@ widest-line@^2.0.0:
dependencies:
string-width "^2.1.1"
+widest-line@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-3.1.0.tgz#8292333bbf66cb45ff0de1603b136b7ae1496eca"
+ integrity sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==
+ dependencies:
+ string-width "^4.0.0"
+
word-wrap@^1.2.3, word-wrap@~1.2.3:
version "1.2.3"
resolved "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz"
@@ -21138,6 +21797,15 @@ wrap-ansi@^3.0.1:
string-width "^2.1.1"
strip-ansi "^4.0.0"
+wrap-ansi@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-4.0.0.tgz#b3570d7c70156159a2d42be5cc942e957f7b1131"
+ integrity sha512-uMTsj9rDb0/7kk1PbcbCcwvHUxp60fGDB/NNXpVa0Q+ic/e7y5+BwTxKfQ33VYgDppSwi/FBzpetYzo8s6tfbg==
+ dependencies:
+ ansi-styles "^3.2.0"
+ string-width "^2.1.1"
+ strip-ansi "^4.0.0"
+
wrap-ansi@^5.1.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz#1fd1f67235d5b6d0fee781056001bfb694c03b09"