Compare commits
35 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f50bcbdb2d | ||
|
|
cbe421ee23 | ||
|
|
3439786c38 | ||
|
|
6294915be6 | ||
|
|
984c742d0e | ||
|
|
00b151d7fa | ||
|
|
5967cf5d9d | ||
|
|
e480f0ebe5 | ||
|
|
f6ceecbc5e | ||
|
|
a0348b45be | ||
|
|
e4b37cffcc | ||
|
|
dd69cccf91 | ||
|
|
8b228de88f | ||
|
|
f91fc3c6a6 | ||
|
|
e0e2b40f9f | ||
|
|
bc1c61b63a | ||
|
|
446ad3e0d4 | ||
|
|
65e58a08cf | ||
|
|
71b20b432c | ||
|
|
ecfa179b3f | ||
|
|
6c94a0f585 | ||
|
|
600aeaaff1 | ||
|
|
3bfbc74e47 | ||
|
|
84f76e34b2 | ||
|
|
b965bed865 | ||
|
|
a9039e8d0b | ||
|
|
60d35b46f3 | ||
|
|
3d422cf707 | ||
|
|
84c70ac84d | ||
|
|
de3a33dfcb | ||
|
|
99a084f230 | ||
|
|
e880955743 | ||
|
|
bbf43fcd27 | ||
|
|
a00ac56389 | ||
|
|
7201944bc2 |
16
package.json
16
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@uniswap/widgets",
|
||||
"version": "0.0.26-beta",
|
||||
"version": "1.0.6",
|
||||
"description": "Uniswap Interface",
|
||||
"homepage": ".",
|
||||
"files": [
|
||||
@@ -90,8 +90,8 @@
|
||||
"@uniswap/v2-periphery": "^1.1.0-beta.0",
|
||||
"@uniswap/v3-core": "1.0.0",
|
||||
"@uniswap/v3-periphery": "^1.1.1",
|
||||
"@web3-react/metamask": "^8.0.18-beta.0",
|
||||
"@web3-react/walletconnect": "^8.0.25-beta.0",
|
||||
"@web3-react/metamask": "^8.0.19-beta.0",
|
||||
"@web3-react/walletconnect": "^8.0.26-beta.0",
|
||||
"array.prototype.flat": "^1.2.4",
|
||||
"array.prototype.flatmap": "^1.2.4",
|
||||
"babel-plugin-macros": "^3.1.0",
|
||||
@@ -205,11 +205,11 @@
|
||||
"@uniswap/token-lists": "^1.0.0-beta.27",
|
||||
"@uniswap/v2-sdk": "^3.0.1",
|
||||
"@uniswap/v3-sdk": "^3.8.2",
|
||||
"@web3-react/core": "^8.0.22-beta.0",
|
||||
"@web3-react/eip1193": "^8.0.17-beta.0",
|
||||
"@web3-react/empty": "^8.0.11-beta.0",
|
||||
"@web3-react/types": "^8.0.11-beta.0",
|
||||
"@web3-react/url": "^8.0.16-beta.0",
|
||||
"@web3-react/core": "^8.0.23-beta.0",
|
||||
"@web3-react/eip1193": "^8.0.18-beta.0",
|
||||
"@web3-react/empty": "^8.0.12-beta.0",
|
||||
"@web3-react/types": "^8.0.12-beta.0",
|
||||
"@web3-react/url": "^8.0.17-beta.0",
|
||||
"ajv": "^6.12.3",
|
||||
"cids": "^1.0.0",
|
||||
"ethers": "^5.1.4",
|
||||
|
||||
@@ -33,6 +33,7 @@ const BLOCKED_ADDRESSES: string[] = [
|
||||
'0x5512d943ed1f7c8a43f3435c85f7ab68b30121b0',
|
||||
'0xc455f7fd3e0e12afd51fba5c106909934d8a0e4a',
|
||||
'0x629e7Da20197a5429d30da36E77d06CdF796b71A',
|
||||
'0x7FF9cFad3877F21d41Da833E2F775dB0569eE3D9',
|
||||
]
|
||||
|
||||
export default function Blocklist({ children }: { children: ReactNode }) {
|
||||
|
||||
@@ -49,9 +49,11 @@ type ErrorBoundaryState = {
|
||||
|
||||
const IS_UNISWAP = window.location.hostname === 'app.uniswap.org'
|
||||
|
||||
async function updateServiceWorker(): Promise<void> {
|
||||
async function updateServiceWorker(): Promise<ServiceWorkerRegistration> {
|
||||
const ready = await navigator.serviceWorker.ready
|
||||
await ready.update()
|
||||
// the return type of update is incorrectly typed as Promise<void>. See
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerRegistration/update
|
||||
return ready.update() as unknown as Promise<ServiceWorkerRegistration>
|
||||
}
|
||||
|
||||
export default class ErrorBoundary extends React.Component<unknown, ErrorBoundaryState> {
|
||||
@@ -62,8 +64,19 @@ export default class ErrorBoundary extends React.Component<unknown, ErrorBoundar
|
||||
|
||||
static getDerivedStateFromError(error: Error): ErrorBoundaryState {
|
||||
updateServiceWorker()
|
||||
.then(() => {
|
||||
window.location.reload()
|
||||
.then(async (registration) => {
|
||||
// We want to refresh only if we detect a new service worker is waiting to be activated.
|
||||
// See details about it: https://developers.google.com/web/fundamentals/primers/service-workers/lifecycle
|
||||
if (registration?.waiting) {
|
||||
await registration.unregister()
|
||||
|
||||
// Makes Workbox call skipWaiting(). For more info on skipWaiting see: https://developer.chrome.com/docs/workbox/handling-service-worker-updates/
|
||||
registration.waiting.postMessage({ type: 'SKIP_WAITING' })
|
||||
|
||||
// Once the service worker is unregistered, we can reload the page to let
|
||||
// the browser download a fresh copy of our app (invalidating the cache)
|
||||
window.location.reload()
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Failed to update service worker', error)
|
||||
|
||||
@@ -13,6 +13,7 @@ import useUSDCPrice, { useUSDCValue } from './useUSDCPrice'
|
||||
|
||||
const V3_SWAP_DEFAULT_SLIPPAGE = new Percent(50, 10_000) // .50%
|
||||
const ONE_TENTHS_PERCENT = new Percent(10, 10_000) // .10%
|
||||
export const DEFAULT_AUTO_SLIPPAGE = ONE_TENTHS_PERCENT
|
||||
|
||||
/**
|
||||
* Return a guess of the gas cost used in computing slippage tolerance for a given trade
|
||||
@@ -41,10 +42,10 @@ export default function useAutoSlippageTolerance(
|
||||
|
||||
const gasEstimate = guesstimateGas(trade)
|
||||
const nativeCurrency = useNativeCurrency()
|
||||
const nativeCurrencyPrice = useUSDCPrice(nativeCurrency ?? undefined)
|
||||
const nativeCurrencyPrice = useUSDCPrice((trade && nativeCurrency) ?? undefined)
|
||||
|
||||
return useMemo(() => {
|
||||
if (!trade || onL2) return ONE_TENTHS_PERCENT
|
||||
if (!trade || onL2) return DEFAULT_AUTO_SLIPPAGE
|
||||
|
||||
const nativeGasCost =
|
||||
nativeGasPrice && typeof gasEstimate === 'number'
|
||||
|
||||
@@ -3,7 +3,7 @@ import { SupportedChainId } from 'constants/chains'
|
||||
import uriToHttp from 'lib/utils/uriToHttp'
|
||||
import Vibrant from 'node-vibrant/lib/bundle.js'
|
||||
import { shade } from 'polished'
|
||||
import { useLayoutEffect, useState } from 'react'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { WrappedTokenInfo } from 'state/lists/wrappedTokenInfo'
|
||||
import { hex } from 'wcag-contrast'
|
||||
|
||||
@@ -64,7 +64,7 @@ async function getColorFromUriPath(uri: string): Promise<string | null> {
|
||||
export function useColor(token?: Token) {
|
||||
const [color, setColor] = useState('#2172E5')
|
||||
|
||||
useLayoutEffect(() => {
|
||||
useEffect(() => {
|
||||
let stale = false
|
||||
|
||||
if (token) {
|
||||
@@ -87,7 +87,7 @@ export function useColor(token?: Token) {
|
||||
export function useListColor(listImageUri?: string) {
|
||||
const [color, setColor] = useState('#2172E5')
|
||||
|
||||
useLayoutEffect(() => {
|
||||
useEffect(() => {
|
||||
let stale = false
|
||||
|
||||
if (listImageUri) {
|
||||
|
||||
@@ -12,13 +12,14 @@ function isWindowVisible() {
|
||||
* Returns whether the window is currently visible to the user.
|
||||
*/
|
||||
export default function useIsWindowVisible(): boolean {
|
||||
const [focused, setFocused] = useState<boolean>(isWindowVisible())
|
||||
const [focused, setFocused] = useState<boolean>(false)
|
||||
const listener = useCallback(() => {
|
||||
setFocused(isWindowVisible())
|
||||
}, [setFocused])
|
||||
|
||||
useEffect(() => {
|
||||
if (!isVisibilityStateSupported()) return undefined
|
||||
setFocused((focused) => isWindowVisible())
|
||||
|
||||
document.addEventListener('visibilitychange', listener)
|
||||
return () => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Currency, CurrencyAmount, Price, Token, TradeType } from '@uniswap/sdk-core'
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
import tryParseCurrencyAmount from 'lib/utils/tryParseCurrencyAmount'
|
||||
import { useMemo } from 'react'
|
||||
import { useMemo, useRef } from 'react'
|
||||
|
||||
import { SupportedChainId } from '../constants/chains'
|
||||
import { DAI_OPTIMISM, USDC_ARBITRUM, USDC_MAINNET, USDC_POLYGON } from '../constants/tokens'
|
||||
@@ -32,8 +32,7 @@ export default function useUSDCPrice(currency?: Currency): Price<Currency, Token
|
||||
maxHops: 2,
|
||||
})
|
||||
const v3USDCTrade = useClientSideV3Trade(TradeType.EXACT_OUTPUT, amountOut, currency)
|
||||
|
||||
return useMemo(() => {
|
||||
const price = useMemo(() => {
|
||||
if (!currency || !stablecoin) {
|
||||
return undefined
|
||||
}
|
||||
@@ -54,6 +53,12 @@ export default function useUSDCPrice(currency?: Currency): Price<Currency, Token
|
||||
|
||||
return undefined
|
||||
}, [currency, stablecoin, v2USDCTrade, v3USDCTrade.trade])
|
||||
|
||||
const lastPrice = useRef(price)
|
||||
if (!price || !lastPrice.current || !price.equalTo(lastPrice.current)) {
|
||||
lastPrice.current = price
|
||||
}
|
||||
return lastPrice.current
|
||||
}
|
||||
|
||||
export function useUSDCValue(currencyAmount: CurrencyAmount<Currency> | undefined | null) {
|
||||
|
||||
@@ -23,13 +23,11 @@ export function useV3PositionFees(
|
||||
const tokenIdHexString = tokenId?.toHexString()
|
||||
const latestBlockNumber = useBlockNumber()
|
||||
|
||||
// TODO find a way to get this into multicall
|
||||
// we can't use multicall for this because we need to simulate the call from a specific address
|
||||
// latestBlockNumber is included to ensure data stays up-to-date every block
|
||||
const [amounts, setAmounts] = useState<[BigNumber, BigNumber]>()
|
||||
const [amounts, setAmounts] = useState<[BigNumber, BigNumber] | undefined>()
|
||||
useEffect(() => {
|
||||
let stale = false
|
||||
|
||||
if (positionManager && tokenIdHexString && owner && typeof latestBlockNumber === 'number') {
|
||||
if (positionManager && tokenIdHexString && owner) {
|
||||
positionManager.callStatic
|
||||
.collect(
|
||||
{
|
||||
@@ -41,19 +39,15 @@ export function useV3PositionFees(
|
||||
{ from: owner } // need to simulate the call as the owner
|
||||
)
|
||||
.then((results) => {
|
||||
if (!stale) setAmounts([results.amount0, results.amount1])
|
||||
setAmounts([results.amount0, results.amount1])
|
||||
})
|
||||
}
|
||||
|
||||
return () => {
|
||||
stale = true
|
||||
}
|
||||
}, [positionManager, tokenIdHexString, owner, latestBlockNumber])
|
||||
|
||||
if (pool && amounts) {
|
||||
return [
|
||||
CurrencyAmount.fromRawAmount(!asWETH ? unwrappedToken(pool.token0) : pool.token0, amounts[0].toString()),
|
||||
CurrencyAmount.fromRawAmount(!asWETH ? unwrappedToken(pool.token1) : pool.token1, amounts[1].toString()),
|
||||
CurrencyAmount.fromRawAmount(asWETH ? pool.token0 : unwrappedToken(pool.token0), amounts[0].toString()),
|
||||
CurrencyAmount.fromRawAmount(asWETH ? pool.token1 : unwrappedToken(pool.token1), amounts[1].toString()),
|
||||
]
|
||||
} else {
|
||||
return [undefined, undefined]
|
||||
|
||||
@@ -3,7 +3,7 @@ import 'inter-ui'
|
||||
import 'polyfills'
|
||||
import 'components/analytics'
|
||||
|
||||
import { BlockUpdater } from 'lib/hooks/useBlockNumber'
|
||||
import { BlockNumberProvider } from 'lib/hooks/useBlockNumber'
|
||||
import { MulticallUpdater } from 'lib/state/multicall'
|
||||
import { StrictMode } from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
@@ -40,7 +40,6 @@ function Updaters() {
|
||||
<UserUpdater />
|
||||
<ApplicationUpdater />
|
||||
<TransactionUpdater />
|
||||
<BlockUpdater />
|
||||
<MulticallUpdater />
|
||||
<LogsUpdater />
|
||||
</>
|
||||
@@ -55,11 +54,13 @@ ReactDOM.render(
|
||||
<Web3ReactProvider getLibrary={getLibrary}>
|
||||
<Web3ProviderNetwork getLibrary={getLibrary}>
|
||||
<Blocklist>
|
||||
<Updaters />
|
||||
<ThemeProvider>
|
||||
<ThemedGlobalStyle />
|
||||
<App />
|
||||
</ThemeProvider>
|
||||
<BlockNumberProvider>
|
||||
<Updaters />
|
||||
<ThemeProvider>
|
||||
<ThemedGlobalStyle />
|
||||
<App />
|
||||
</ThemeProvider>
|
||||
</BlockNumberProvider>
|
||||
</Blocklist>
|
||||
</Web3ProviderNetwork>
|
||||
</Web3ReactProvider>
|
||||
|
||||
@@ -5,17 +5,7 @@ import { ReactNode, useMemo } from 'react'
|
||||
import Button from './Button'
|
||||
import Row from './Row'
|
||||
|
||||
const fadeIn = keyframes`
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
`
|
||||
|
||||
const StyledButton = styled(Button)`
|
||||
animation: ${fadeIn} 0.25s ease-in;
|
||||
border-radius: ${({ theme }) => theme.borderRadius * 0.75}em;
|
||||
flex-grow: 1;
|
||||
transition: background-color 0.25s ease-out, border-radius 0.25s ease-out, flex-grow 0.25s ease-out;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Icon } from 'lib/icons'
|
||||
import styled, { Color } from 'lib/theme'
|
||||
import styled, { Color, css } from 'lib/theme'
|
||||
import { ComponentProps, forwardRef } from 'react'
|
||||
|
||||
export const BaseButton = styled.button`
|
||||
@@ -15,17 +15,26 @@ export const BaseButton = styled.button`
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
:enabled {
|
||||
transition: filter 0.125s linear;
|
||||
}
|
||||
|
||||
:disabled {
|
||||
cursor: initial;
|
||||
filter: saturate(0) opacity(0.4);
|
||||
}
|
||||
`
|
||||
const transitionCss = css`
|
||||
transition: background-color 0.125s linear, border-color 0.125s linear, filter 0.125s linear;
|
||||
`
|
||||
|
||||
export default styled(BaseButton)<{ color?: Color }>`
|
||||
export default styled(BaseButton)<{ color?: Color; transition?: boolean }>`
|
||||
border: 1px solid transparent;
|
||||
color: ${({ color = 'interactive', theme }) => color === 'interactive' && theme.onInteractive};
|
||||
|
||||
:enabled {
|
||||
background-color: ${({ color = 'interactive', theme }) => theme[color]};
|
||||
${({ transition = true }) => transition && transitionCss};
|
||||
}
|
||||
|
||||
:enabled:hover {
|
||||
@@ -33,9 +42,8 @@ export default styled(BaseButton)<{ color?: Color }>`
|
||||
}
|
||||
|
||||
:disabled {
|
||||
border: 1px solid ${({ theme }) => theme.outline};
|
||||
border-color: ${({ theme }) => theme.outline};
|
||||
color: ${({ theme }) => theme.secondary};
|
||||
cursor: initial;
|
||||
}
|
||||
`
|
||||
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
import { SUPPORTED_LOCALES } from 'constants/locales'
|
||||
import { WidgetProps } from 'lib/components/Widget'
|
||||
import { IntegrationError } from 'lib/errors'
|
||||
import { PropsWithChildren, useEffect } from 'react'
|
||||
|
||||
export default function WidgetsPropsValidator(props: PropsWithChildren<WidgetProps>) {
|
||||
const { jsonRpcEndpoint, provider } = props
|
||||
|
||||
useEffect(() => {
|
||||
if (!provider && !jsonRpcEndpoint) {
|
||||
throw new IntegrationError('This widget requires a provider or jsonRpcEndpoint.')
|
||||
}
|
||||
}, [provider, jsonRpcEndpoint])
|
||||
|
||||
const { width } = props
|
||||
useEffect(() => {
|
||||
if (width && width < 300) {
|
||||
throw new IntegrationError(`Set widget width to at least 300px. (You set it to ${width}.)`)
|
||||
}
|
||||
}, [width])
|
||||
|
||||
const { locale } = props
|
||||
useEffect(() => {
|
||||
if (locale && locale !== 'pseudo' && !SUPPORTED_LOCALES.includes(locale)) {
|
||||
console.warn('Unsupported locale: ', locale)
|
||||
}
|
||||
}, [locale])
|
||||
|
||||
return null
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
import { loadingOpacity } from 'lib/css/loading'
|
||||
import styled, { css } from 'lib/theme'
|
||||
import { transparentize } from 'polished'
|
||||
import { ChangeEvent, forwardRef, HTMLProps, useCallback } from 'react'
|
||||
|
||||
const Input = styled.input`
|
||||
@@ -34,6 +36,16 @@ const Input = styled.input`
|
||||
::placeholder {
|
||||
color: ${({ theme }) => theme.secondary};
|
||||
}
|
||||
|
||||
:enabled {
|
||||
transition: color 0.125s linear;
|
||||
}
|
||||
|
||||
:disabled {
|
||||
// Overrides WebKit's override of input:disabled color.
|
||||
-webkit-text-fill-color: ${({ theme }) => transparentize(1 - loadingOpacity, theme.primary)};
|
||||
color: ${({ theme }) => transparentize(1 - loadingOpacity, theme.primary)};
|
||||
}
|
||||
`
|
||||
|
||||
export default Input
|
||||
|
||||
@@ -80,7 +80,7 @@ export default function Input({ disabled, focused }: InputProps) {
|
||||
// extract eagerly in case of reversal
|
||||
usePrefetchCurrencyColor(inputCurrency)
|
||||
|
||||
const isRouteLoading = tradeState === TradeState.SYNCING || tradeState === TradeState.LOADING
|
||||
const isRouteLoading = disabled || tradeState === TradeState.SYNCING || tradeState === TradeState.LOADING
|
||||
const isDependentField = !useIsSwapFieldIndependent(Field.INPUT)
|
||||
const isLoading = isRouteLoading && isDependentField
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ export default function Output({ disabled, focused, children }: PropsWithChildre
|
||||
const [swapOutputAmount, updateSwapOutputAmount] = useSwapAmount(Field.OUTPUT)
|
||||
const [swapOutputCurrency, updateSwapOutputCurrency] = useSwapCurrency(Field.OUTPUT)
|
||||
|
||||
const isRouteLoading = tradeState === TradeState.SYNCING || tradeState === TradeState.LOADING
|
||||
const isRouteLoading = disabled || tradeState === TradeState.SYNCING || tradeState === TradeState.LOADING
|
||||
const isDependentField = !useIsSwapFieldIndependent(Field.OUTPUT)
|
||||
const isLoading = isRouteLoading && isDependentField
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ const Overlay = styled.div`
|
||||
|
||||
const StyledReverseButton = styled(Button)<{ turns: number }>`
|
||||
border-radius: ${({ theme }) => theme.borderRadius * 0.75}em;
|
||||
color: ${({ theme }) => theme.primary};
|
||||
height: 2.5em;
|
||||
position: relative;
|
||||
width: 2.5em;
|
||||
|
||||
@@ -3,7 +3,7 @@ import { SupportedChainId } from 'constants/chains'
|
||||
import { nativeOnChain } from 'constants/tokens'
|
||||
import { useUpdateAtom } from 'jotai/utils'
|
||||
import { useSwapInfo } from 'lib/hooks/swap'
|
||||
import { SwapInfoUpdater } from 'lib/hooks/swap/useSwapInfo'
|
||||
import { SwapInfoProvider } from 'lib/hooks/swap/useSwapInfo'
|
||||
import { Field, swapAtom } from 'lib/state/swap'
|
||||
import { useEffect } from 'react'
|
||||
import { WrappedTokenInfo } from 'state/lists/wrappedTokenInfo'
|
||||
@@ -54,7 +54,8 @@ function Fixture() {
|
||||
|
||||
export default (
|
||||
<>
|
||||
<SwapInfoUpdater />
|
||||
<Fixture />
|
||||
<SwapInfoProvider>
|
||||
<Fixture />
|
||||
</SwapInfoProvider>
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { tokens } from '@uniswap/default-token-list'
|
||||
import { DAI, USDC_MAINNET } from 'constants/tokens'
|
||||
import { useUpdateAtom } from 'jotai/utils'
|
||||
import { useEffect } from 'react'
|
||||
@@ -65,7 +64,6 @@ function Fixture() {
|
||||
defaultInputAmount={defaultInputAmount}
|
||||
defaultOutputTokenAddress={optionsToAddressMap[defaultOutputToken]}
|
||||
defaultOutputAmount={defaultOutputAmount}
|
||||
tokenList={tokens}
|
||||
onConnectWallet={() => console.log('onConnectWallet')} // this handler is included as a test of functionality, but only logs
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -14,6 +14,7 @@ import { TransactionType } from 'lib/state/transactions'
|
||||
import { useTheme } from 'lib/theme'
|
||||
import { isAnimating } from 'lib/utils/animations'
|
||||
import { memo, useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { TradeState } from 'state/routing/types'
|
||||
import invariant from 'tiny-invariant'
|
||||
|
||||
import ActionButton, { ActionButtonProps } from '../../ActionButton'
|
||||
@@ -152,13 +153,17 @@ export default memo(function SwapButton({ disabled }: SwapButtonProps) {
|
||||
if (disableSwap) {
|
||||
return { disabled: true }
|
||||
} else if (wrapType === WrapType.NONE) {
|
||||
return approvalAction ? { action: approvalAction } : { onClick: () => setOpen(true) }
|
||||
return approvalAction
|
||||
? { action: approvalAction }
|
||||
: trade.state === TradeState.VALID
|
||||
? { onClick: () => setOpen(true) }
|
||||
: { disabled: true }
|
||||
} else {
|
||||
return isPending
|
||||
? { action: { message: <Trans>Confirm in your wallet</Trans>, icon: Spinner } }
|
||||
: { onClick: onWrap }
|
||||
}
|
||||
}, [approvalAction, disableSwap, isPending, onWrap, wrapType])
|
||||
}, [approvalAction, disableSwap, isPending, onWrap, trade.state, wrapType])
|
||||
const Label = useCallback(() => {
|
||||
switch (wrapType) {
|
||||
case WrapType.UNWRAP:
|
||||
|
||||
@@ -18,7 +18,8 @@ const TokenInputRow = styled(Row)`
|
||||
|
||||
const ValueInput = styled(DecimalInput)`
|
||||
color: ${({ theme }) => theme.primary};
|
||||
height: 1em;
|
||||
height: 1.5em;
|
||||
margin: -0.25em 0;
|
||||
|
||||
:hover:not(:focus-within) {
|
||||
color: ${({ theme }) => theme.onHover(theme.primary)};
|
||||
|
||||
@@ -32,6 +32,19 @@ function Caption({ icon: Icon = AlertTriangle, caption }: CaptionProps) {
|
||||
)
|
||||
}
|
||||
|
||||
export function Connecting() {
|
||||
return (
|
||||
<Caption
|
||||
icon={InlineSpinner}
|
||||
caption={
|
||||
<Loading>
|
||||
<Trans>Connecting…</Trans>
|
||||
</Loading>
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export function ConnectWallet() {
|
||||
return <Caption caption={<Trans>Connect wallet to swap</Trans>} />
|
||||
}
|
||||
|
||||
@@ -17,8 +17,8 @@ const ToolbarRow = styled(Row)`
|
||||
${largeIconCss}
|
||||
`
|
||||
|
||||
export default memo(function Toolbar({ disabled }: { disabled?: boolean }) {
|
||||
const { chainId } = useActiveWeb3React()
|
||||
export default memo(function Toolbar() {
|
||||
const { active, activating, chainId } = useActiveWeb3React()
|
||||
const {
|
||||
[Field.INPUT]: { currency: inputCurrency, balance: inputBalance, amount: inputAmount },
|
||||
[Field.OUTPUT]: { currency: outputCurrency, usdc: outputUSDC },
|
||||
@@ -28,11 +28,12 @@ export default memo(function Toolbar({ disabled }: { disabled?: boolean }) {
|
||||
const isAmountPopulated = useIsAmountPopulated()
|
||||
const { type: wrapType } = useWrapCallback()
|
||||
const caption = useMemo(() => {
|
||||
if (disabled) {
|
||||
if (!active || !chainId) {
|
||||
if (activating) return <Caption.Connecting />
|
||||
return <Caption.ConnectWallet />
|
||||
}
|
||||
|
||||
if (chainId && !ALL_SUPPORTED_CHAIN_IDS.includes(chainId)) {
|
||||
if (!ALL_SUPPORTED_CHAIN_IDS.includes(chainId)) {
|
||||
return <Caption.UnsupportedNetwork />
|
||||
}
|
||||
|
||||
@@ -59,8 +60,9 @@ export default memo(function Toolbar({ disabled }: { disabled?: boolean }) {
|
||||
|
||||
return <Caption.Empty />
|
||||
}, [
|
||||
activating,
|
||||
active,
|
||||
chainId,
|
||||
disabled,
|
||||
impact,
|
||||
inputAmount,
|
||||
inputBalance,
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { TokenInfo } from '@uniswap/token-lists'
|
||||
import { useAtom } from 'jotai'
|
||||
import { SwapInfoUpdater } from 'lib/hooks/swap/useSwapInfo'
|
||||
import { SwapInfoProvider } from 'lib/hooks/swap/useSwapInfo'
|
||||
import useSyncConvenienceFee, { FeeOptions } from 'lib/hooks/swap/useSyncConvenienceFee'
|
||||
import useSyncTokenDefaults, { TokenDefaults } from 'lib/hooks/swap/useSyncTokenDefaults'
|
||||
import { usePendingTransactions } from 'lib/hooks/transactions'
|
||||
import useActiveWeb3React from 'lib/hooks/useActiveWeb3React'
|
||||
import useHasFocus from 'lib/hooks/useHasFocus'
|
||||
import useOnSupportedNetwork from 'lib/hooks/useOnSupportedNetwork'
|
||||
import useTokenList, { useSyncTokenList } from 'lib/hooks/useTokenList'
|
||||
import { displayTxHashAtom } from 'lib/state/swap'
|
||||
import { SwapTransactionInfo, Transaction, TransactionType, WrapTransactionInfo } from 'lib/state/transactions'
|
||||
import { useMemo, useState } from 'react'
|
||||
import { useState } from 'react'
|
||||
|
||||
import Dialog from '../Dialog'
|
||||
import Header from '../Header'
|
||||
@@ -23,8 +21,8 @@ import ReverseButton from './ReverseButton'
|
||||
import Settings from './Settings'
|
||||
import { StatusDialog } from './Status'
|
||||
import SwapButton from './SwapButton'
|
||||
import SwapPropValidator from './SwapPropValidator'
|
||||
import Toolbar from './Toolbar'
|
||||
import useValidate from './useValidate'
|
||||
|
||||
function getTransactionFromMap(
|
||||
txs: { [hash: string]: Transaction },
|
||||
@@ -43,19 +41,14 @@ function getTransactionFromMap(
|
||||
}
|
||||
|
||||
export interface SwapProps extends TokenDefaults, FeeOptions {
|
||||
tokenList?: string | TokenInfo[]
|
||||
onConnectWallet?: () => void
|
||||
}
|
||||
|
||||
function Updaters(props: SwapProps & { disabled: boolean }) {
|
||||
useSyncTokenList(props.tokenList)
|
||||
useSyncTokenDefaults(props)
|
||||
useSyncConvenienceFee(props)
|
||||
|
||||
return props.disabled ? null : <SwapInfoUpdater />
|
||||
}
|
||||
|
||||
export default function Swap(props: SwapProps) {
|
||||
useValidate(props)
|
||||
useSyncConvenienceFee(props)
|
||||
useSyncTokenDefaults(props)
|
||||
|
||||
const { active, account } = useActiveWeb3React()
|
||||
const [wrapper, setWrapper] = useState<HTMLDivElement | null>(null)
|
||||
|
||||
@@ -63,33 +56,27 @@ export default function Swap(props: SwapProps) {
|
||||
const pendingTxs = usePendingTransactions()
|
||||
const displayTx = getTransactionFromMap(pendingTxs, displayTxHash)
|
||||
|
||||
const tokenList = useTokenList()
|
||||
const onSupportedNetwork = useOnSupportedNetwork()
|
||||
const isSwapSupported = useMemo(
|
||||
() => Boolean(active && onSupportedNetwork && tokenList?.length),
|
||||
[active, onSupportedNetwork, tokenList?.length]
|
||||
)
|
||||
const isDisabled = !(active && onSupportedNetwork)
|
||||
|
||||
const focused = useHasFocus(wrapper)
|
||||
|
||||
const isInteractive = Boolean(active && onSupportedNetwork)
|
||||
|
||||
return (
|
||||
<>
|
||||
<SwapPropValidator {...props} />
|
||||
<Updaters {...props} disabled={!isSwapSupported} />
|
||||
<Header title={<Trans>Swap</Trans>}>
|
||||
{active && <Wallet disabled={!account} onClick={props.onConnectWallet} />}
|
||||
<Settings disabled={!isInteractive} />
|
||||
<Settings disabled={isDisabled} />
|
||||
</Header>
|
||||
<div ref={setWrapper}>
|
||||
<BoundaryProvider value={wrapper}>
|
||||
<Input disabled={!isInteractive} focused={focused} />
|
||||
<ReverseButton disabled={!isInteractive} />
|
||||
<Output disabled={!isInteractive} focused={focused}>
|
||||
<Toolbar disabled={!active} />
|
||||
<SwapButton disabled={!isSwapSupported} />
|
||||
</Output>
|
||||
<SwapInfoProvider disabled={isDisabled}>
|
||||
<Input disabled={isDisabled} focused={focused} />
|
||||
<ReverseButton disabled={isDisabled} />
|
||||
<Output disabled={isDisabled} focused={focused}>
|
||||
<Toolbar />
|
||||
<SwapButton disabled={isDisabled} />
|
||||
</Output>
|
||||
</SwapInfoProvider>
|
||||
</BoundaryProvider>
|
||||
</div>
|
||||
{displayTx && (
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { BigNumber } from '@ethersproject/bignumber'
|
||||
import { IntegrationError } from 'lib/errors'
|
||||
import { FeeOptions } from 'lib/hooks/swap/useSyncConvenienceFee'
|
||||
import { DefaultAddress, TokenDefaults } from 'lib/hooks/swap/useSyncTokenDefaults'
|
||||
@@ -18,12 +17,12 @@ function isAddressOrAddressMap(addressOrMap: DefaultAddress): boolean {
|
||||
|
||||
type ValidatorProps = PropsWithChildren<TokenDefaults & FeeOptions>
|
||||
|
||||
export default function SwapPropValidator(props: ValidatorProps) {
|
||||
export default function useValidate(props: ValidatorProps) {
|
||||
const { convenienceFee, convenienceFeeRecipient } = props
|
||||
useEffect(() => {
|
||||
if (convenienceFee) {
|
||||
if (convenienceFee > 100 || convenienceFee < 0) {
|
||||
throw new IntegrationError(`convenienceFee must be between 0 and 100. (You set it to ${convenienceFee})`)
|
||||
throw new IntegrationError(`convenienceFee must be between 0 and 100 (you set it to ${convenienceFee}).`)
|
||||
}
|
||||
if (!convenienceFeeRecipient) {
|
||||
throw new IntegrationError('convenienceFeeRecipient is required when convenienceFee is set.')
|
||||
@@ -32,7 +31,7 @@ export default function SwapPropValidator(props: ValidatorProps) {
|
||||
if (typeof convenienceFeeRecipient === 'string') {
|
||||
if (!isAddress(convenienceFeeRecipient)) {
|
||||
throw new IntegrationError(
|
||||
`convenienceFeeRecipient must be a valid address. (You set it to ${convenienceFeeRecipient}.)`
|
||||
`convenienceFeeRecipient must be a valid address (you set it to ${convenienceFeeRecipient}).`
|
||||
)
|
||||
}
|
||||
} else if (typeof convenienceFeeRecipient === 'object') {
|
||||
@@ -40,7 +39,7 @@ export default function SwapPropValidator(props: ValidatorProps) {
|
||||
if (!isAddress(recipient)) {
|
||||
const values = Object.values(convenienceFeeRecipient).join(', ')
|
||||
throw new IntegrationError(
|
||||
`All values in convenienceFeeRecipient object must be valid addresses. (You used ${values}.)`
|
||||
`All values in convenienceFeeRecipient object must be valid addresses (you used ${values}).`
|
||||
)
|
||||
}
|
||||
})
|
||||
@@ -48,26 +47,30 @@ export default function SwapPropValidator(props: ValidatorProps) {
|
||||
}
|
||||
}, [convenienceFee, convenienceFeeRecipient])
|
||||
|
||||
const { defaultInputTokenAddress, defaultInputAmount, defaultOutputTokenAddress, defaultOutputAmount } = props
|
||||
const { defaultInputAmount, defaultOutputAmount } = props
|
||||
useEffect(() => {
|
||||
if (defaultOutputAmount && defaultInputAmount) {
|
||||
throw new IntegrationError('defaultInputAmount and defaultOutputAmount may not both be defined.')
|
||||
}
|
||||
if (defaultInputAmount && BigNumber.from(defaultInputAmount).lt(0)) {
|
||||
throw new IntegrationError(`defaultInputAmount must be a positive number. (You set it to ${defaultInputAmount})`)
|
||||
if (defaultInputAmount && (isNaN(+defaultInputAmount) || defaultInputAmount < 0)) {
|
||||
throw new IntegrationError(`defaultInputAmount must be a positive number (you set it to ${defaultInputAmount})`)
|
||||
}
|
||||
if (defaultOutputAmount && BigNumber.from(defaultOutputAmount).lt(0)) {
|
||||
if (defaultOutputAmount && (isNaN(+defaultOutputAmount) || defaultOutputAmount < 0)) {
|
||||
throw new IntegrationError(
|
||||
`defaultOutputAmount must be a positive number. (You set it to ${defaultOutputAmount})`
|
||||
`defaultOutputAmount must be a positive number (you set it to ${defaultOutputAmount}).`
|
||||
)
|
||||
}
|
||||
}, [defaultInputAmount, defaultOutputAmount])
|
||||
|
||||
const { defaultInputTokenAddress, defaultOutputTokenAddress } = props
|
||||
useEffect(() => {
|
||||
if (
|
||||
defaultInputTokenAddress &&
|
||||
!isAddressOrAddressMap(defaultInputTokenAddress) &&
|
||||
defaultInputTokenAddress !== 'NATIVE'
|
||||
) {
|
||||
throw new IntegrationError(
|
||||
`defaultInputTokenAddress(es) must be a valid address or "NATIVE". (You set it to ${defaultInputTokenAddress}`
|
||||
`defaultInputTokenAddress must be a valid address or "NATIVE" (you set it to ${defaultInputTokenAddress}).`
|
||||
)
|
||||
}
|
||||
if (
|
||||
@@ -76,10 +79,8 @@ export default function SwapPropValidator(props: ValidatorProps) {
|
||||
defaultOutputTokenAddress !== 'NATIVE'
|
||||
) {
|
||||
throw new IntegrationError(
|
||||
`defaultOutputTokenAddress(es) must be a valid address or "NATIVE". (You set it to ${defaultOutputTokenAddress}`
|
||||
`defaultOutputTokenAddress must be a valid address or "NATIVE" (you set it to ${defaultOutputTokenAddress}).`
|
||||
)
|
||||
}
|
||||
}, [defaultInputTokenAddress, defaultInputAmount, defaultOutputTokenAddress, defaultOutputAmount])
|
||||
|
||||
return null
|
||||
}, [defaultInputTokenAddress, defaultOutputTokenAddress])
|
||||
}
|
||||
@@ -26,16 +26,17 @@ function TokenImg({ token, ...rest }: TokenImgProps) {
|
||||
setAttempt((attempt) => ++attempt)
|
||||
}, [])
|
||||
|
||||
return useMemo(() => {
|
||||
const src = useMemo(() => {
|
||||
// Trigger a re-render when an error occurs.
|
||||
void attempt
|
||||
|
||||
const src = srcs.find((src) => !badSrcs.has(src))
|
||||
if (!src) return <MissingToken color="secondary" {...rest} />
|
||||
return srcs.find((src) => !badSrcs.has(src))
|
||||
}, [attempt, srcs])
|
||||
|
||||
const alt = tokenInfo.name || tokenInfo.symbol
|
||||
return <img src={src} alt={alt} key={alt} onError={onError} {...rest} />
|
||||
}, [attempt, onError, rest, srcs, tokenInfo.name, tokenInfo.symbol])
|
||||
if (!src) return <MissingToken color="secondary" {...rest} />
|
||||
|
||||
const alt = tokenInfo.name || tokenInfo.symbol
|
||||
return <img src={src} alt={alt} key={alt} onError={onError} {...rest} />
|
||||
}
|
||||
|
||||
export default styled(TokenImg)<{ size?: number }>`
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import DEFAULT_TOKEN_LIST from '@uniswap/default-token-list'
|
||||
import { useSyncTokenList } from 'lib/hooks/useTokenList'
|
||||
import { TokenListProvider } from 'lib/hooks/useTokenList'
|
||||
|
||||
import { Modal } from './Dialog'
|
||||
import { TokenSelectDialog } from './TokenSelect'
|
||||
|
||||
export default function Fixture() {
|
||||
useSyncTokenList(DEFAULT_TOKEN_LIST.tokens)
|
||||
|
||||
return (
|
||||
<Modal color="module">
|
||||
<TokenSelectDialog onSelect={() => void 0} />
|
||||
<TokenListProvider list={DEFAULT_TOKEN_LIST.tokens}>
|
||||
<TokenSelectDialog onSelect={() => void 0} />
|
||||
</TokenListProvider>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,29 +1,33 @@
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { Currency } from '@uniswap/sdk-core'
|
||||
import { ChevronDown } from 'lib/icons'
|
||||
import styled, { ThemedText } from 'lib/theme'
|
||||
import { useMemo } from 'react'
|
||||
import styled, { css, ThemedText } from 'lib/theme'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
|
||||
import Button from '../Button'
|
||||
import Row from '../Row'
|
||||
import TokenImg from '../TokenImg'
|
||||
|
||||
const StyledTokenButton = styled(Button)<{ empty?: boolean }>`
|
||||
const transitionCss = css`
|
||||
transition: background-color 0.125s linear, border-color 0.125s linear, filter 0.125s linear, width 0.125s ease-out;
|
||||
`
|
||||
|
||||
const StyledTokenButton = styled(Button)`
|
||||
border-radius: ${({ theme }) => theme.borderRadius}em;
|
||||
padding: 0.25em;
|
||||
padding-left: ${({ empty }) => (empty ? 0.75 : 0.25)}em;
|
||||
|
||||
:disabled {
|
||||
// prevents border from expanding the button's box size
|
||||
padding: calc(0.25em - 1px);
|
||||
padding-left: calc(${({ empty }) => (empty ? 0.75 : 0.25)}em - 1px);
|
||||
:enabled {
|
||||
${({ transition }) => transition && transitionCss};
|
||||
}
|
||||
`
|
||||
|
||||
const TokenButtonRow = styled(Row)<{ collapsed: boolean }>`
|
||||
const TokenButtonRow = styled(Row)<{ empty: boolean; collapsed: boolean }>`
|
||||
float: right;
|
||||
height: 1.2em;
|
||||
// max-width must have an absolute value in order to transition.
|
||||
max-width: ${({ collapsed }) => (collapsed ? '1.2em' : '12em')};
|
||||
padding-left: ${({ empty }) => empty && 0.5}em;
|
||||
width: fit-content;
|
||||
overflow: hidden;
|
||||
transition: max-width 0.25s linear;
|
||||
|
||||
@@ -42,10 +46,42 @@ interface TokenButtonProps {
|
||||
export default function TokenButton({ value, collapsed, disabled, onClick }: TokenButtonProps) {
|
||||
const buttonBackgroundColor = useMemo(() => (value ? 'interactive' : 'accent'), [value])
|
||||
const contentColor = useMemo(() => (value || disabled ? 'onInteractive' : 'onAccent'), [value, disabled])
|
||||
|
||||
// Transition the button only if transitioning from a disabled state.
|
||||
// This makes initialization cleaner without adding distracting UX to normal swap flows.
|
||||
const [shouldTransition, setShouldTransition] = useState(disabled)
|
||||
useEffect(() => {
|
||||
if (disabled) {
|
||||
setShouldTransition(true)
|
||||
}
|
||||
}, [disabled])
|
||||
|
||||
// width must have an absolute value in order to transition, so it is taken from the row ref.
|
||||
const [row, setRow] = useState<HTMLDivElement | null>(null)
|
||||
const style = useMemo(() => {
|
||||
if (!shouldTransition) return
|
||||
return { width: row ? row.clientWidth + /* padding= */ 8 + /* border= */ 2 : undefined }
|
||||
}, [row, shouldTransition])
|
||||
|
||||
return (
|
||||
<StyledTokenButton onClick={onClick} empty={!value} color={buttonBackgroundColor} disabled={disabled}>
|
||||
<StyledTokenButton
|
||||
onClick={onClick}
|
||||
color={buttonBackgroundColor}
|
||||
disabled={disabled}
|
||||
style={style}
|
||||
transition={shouldTransition}
|
||||
onTransitionEnd={() => setShouldTransition(false)}
|
||||
>
|
||||
<ThemedText.ButtonLarge color={contentColor}>
|
||||
<TokenButtonRow gap={0.4} collapsed={Boolean(value) && collapsed}>
|
||||
<TokenButtonRow
|
||||
gap={0.4}
|
||||
empty={!value}
|
||||
collapsed={collapsed}
|
||||
// ref is used to set an absolute width, so it must be reset for each value passed.
|
||||
// To force this, value?.symbol is passed as a key.
|
||||
ref={setRow}
|
||||
key={value?.symbol}
|
||||
>
|
||||
{value ? (
|
||||
<>
|
||||
<TokenImg token={value} size={1.2} />
|
||||
|
||||
@@ -25,9 +25,9 @@ const SearchInput = styled(StringInput)`
|
||||
function usePrefetchBalances() {
|
||||
const { account } = useActiveWeb3React()
|
||||
const tokenList = useTokenList()
|
||||
const [prefetchedTokenList, setPrefetchedTokenList] = useState(tokenList)
|
||||
useEffect(() => setPrefetchedTokenList(tokenList), [tokenList])
|
||||
useCurrencyBalances(account, tokenList !== prefetchedTokenList ? tokenList : undefined)
|
||||
const prefetchedTokenList = useRef<typeof tokenList>()
|
||||
useCurrencyBalances(account, tokenList !== prefetchedTokenList.current ? tokenList : undefined)
|
||||
prefetchedTokenList.current = tokenList
|
||||
}
|
||||
|
||||
function useAreBalancesLoaded(): boolean {
|
||||
|
||||
@@ -1,20 +1,21 @@
|
||||
import { JsonRpcProvider } from '@ethersproject/providers'
|
||||
import { TokenInfo } from '@uniswap/token-lists'
|
||||
import { Provider as Eip1193Provider } from '@web3-react/types'
|
||||
import { DEFAULT_LOCALE, SupportedLocale } from 'constants/locales'
|
||||
import { DEFAULT_LOCALE, SUPPORTED_LOCALES, SupportedLocale } from 'constants/locales'
|
||||
import { Provider as AtomProvider } from 'jotai'
|
||||
import { TransactionsUpdater } from 'lib/hooks/transactions'
|
||||
import { ActiveWeb3Provider } from 'lib/hooks/useActiveWeb3React'
|
||||
import { BlockUpdater } from 'lib/hooks/useBlockNumber'
|
||||
import { BlockNumberProvider } from 'lib/hooks/useBlockNumber'
|
||||
import { TokenListProvider } from 'lib/hooks/useTokenList'
|
||||
import { Provider as I18nProvider } from 'lib/i18n'
|
||||
import { MulticallUpdater, store as multicallStore } from 'lib/state/multicall'
|
||||
import styled, { keyframes, Theme, ThemeProvider } from 'lib/theme'
|
||||
import { UNMOUNTING } from 'lib/utils/animations'
|
||||
import { PropsWithChildren, StrictMode, useState } from 'react'
|
||||
import { PropsWithChildren, StrictMode, useMemo, useState } from 'react'
|
||||
import { Provider as ReduxProvider } from 'react-redux'
|
||||
|
||||
import { Modal, Provider as DialogProvider } from './Dialog'
|
||||
import ErrorBoundary, { ErrorHandler } from './Error/ErrorBoundary'
|
||||
import WidgetPropValidator from './Error/WidgetsPropsValidator'
|
||||
|
||||
const WidgetWrapper = styled.div<{ width?: number | string }>`
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
@@ -30,7 +31,7 @@ const WidgetWrapper = styled.div<{ width?: number | string }>`
|
||||
font-size: 16px;
|
||||
font-smooth: always;
|
||||
font-variant: none;
|
||||
height: 356px;
|
||||
height: 360px;
|
||||
min-width: 300px;
|
||||
padding: 0.25em;
|
||||
position: relative;
|
||||
@@ -81,21 +82,12 @@ const DialogWrapper = styled.div`
|
||||
}
|
||||
`
|
||||
|
||||
function Updaters() {
|
||||
return (
|
||||
<>
|
||||
<BlockUpdater />
|
||||
<MulticallUpdater />
|
||||
<TransactionsUpdater />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export type WidgetProps = {
|
||||
theme?: Theme
|
||||
locale?: SupportedLocale
|
||||
provider?: Eip1193Provider | JsonRpcProvider
|
||||
jsonRpcEndpoint?: string | JsonRpcProvider
|
||||
tokenList?: string | TokenInfo[]
|
||||
width?: string | number
|
||||
dialog?: HTMLElement | null
|
||||
className?: string
|
||||
@@ -103,17 +95,22 @@ export type WidgetProps = {
|
||||
}
|
||||
|
||||
export default function Widget(props: PropsWithChildren<WidgetProps>) {
|
||||
const {
|
||||
children,
|
||||
theme,
|
||||
locale = DEFAULT_LOCALE,
|
||||
provider,
|
||||
jsonRpcEndpoint,
|
||||
width = 360,
|
||||
dialog: userDialog,
|
||||
className,
|
||||
onError,
|
||||
} = props
|
||||
const { children, theme, provider, jsonRpcEndpoint, dialog: userDialog, className, onError } = props
|
||||
const width = useMemo(() => {
|
||||
if (props.width && props.width < 300) {
|
||||
console.warn(`Widget width must be at least 300px (you set it to ${props.width}). Falling back to 300px.`)
|
||||
return 300
|
||||
}
|
||||
return props.width ?? 360
|
||||
}, [props.width])
|
||||
const locale = useMemo(() => {
|
||||
if (props.locale && ![...SUPPORTED_LOCALES, 'pseudo'].includes(props.locale)) {
|
||||
console.warn(`Unsupported locale: ${props.locale}. Falling back to ${DEFAULT_LOCALE}.`)
|
||||
return DEFAULT_LOCALE
|
||||
}
|
||||
return props.locale ?? DEFAULT_LOCALE
|
||||
}, [props.locale])
|
||||
|
||||
const [dialog, setDialog] = useState<HTMLDivElement | null>(null)
|
||||
return (
|
||||
<StrictMode>
|
||||
@@ -123,12 +120,14 @@ export default function Widget(props: PropsWithChildren<WidgetProps>) {
|
||||
<DialogWrapper ref={setDialog} />
|
||||
<DialogProvider value={userDialog || dialog}>
|
||||
<ErrorBoundary onError={onError}>
|
||||
<WidgetPropValidator {...props} />
|
||||
<ReduxProvider store={multicallStore}>
|
||||
<AtomProvider>
|
||||
<ActiveWeb3Provider provider={provider} jsonRpcEndpoint={jsonRpcEndpoint}>
|
||||
<Updaters />
|
||||
{children}
|
||||
<BlockNumberProvider>
|
||||
<MulticallUpdater />
|
||||
<TransactionsUpdater />
|
||||
<TokenListProvider list={props.tokenList}>{children}</TokenListProvider>
|
||||
</BlockNumberProvider>
|
||||
</ActiveWeb3Provider>
|
||||
</AtomProvider>
|
||||
</ReduxProvider>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { tokens } from '@uniswap/default-token-list'
|
||||
import { initializeConnector } from '@web3-react/core'
|
||||
import { MetaMask } from '@web3-react/metamask'
|
||||
import { Connector } from '@web3-react/types'
|
||||
@@ -17,14 +18,15 @@ const [walletConnect] = initializeConnector<WalletConnect>(
|
||||
|
||||
export default function Wrapper({ children }: { children: ReactNode }) {
|
||||
const [width] = useValue('width', { defaultValue: 360 })
|
||||
const [locale] = useSelect('locale', { defaultValue: DEFAULT_LOCALE, options: ['pseudo', ...SUPPORTED_LOCALES] })
|
||||
const [locale] = useSelect('locale', {
|
||||
defaultValue: DEFAULT_LOCALE,
|
||||
options: ['fa-KE (unsupported)', 'pseudo', ...SUPPORTED_LOCALES],
|
||||
})
|
||||
const [darkMode] = useValue('dark mode', { defaultValue: false })
|
||||
const [theme, setTheme] = useValue('theme', { defaultValue: { ...defaultTheme, ...lightTheme } })
|
||||
useEffect(() => {
|
||||
setTheme({ ...defaultTheme, ...(darkMode ? darkTheme : lightTheme) })
|
||||
// cosmos does not maintain referential equality for setters
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [darkMode])
|
||||
}, [darkMode, setTheme])
|
||||
|
||||
const NO_JSON_RPC = 'None'
|
||||
const [jsonRpcEndpoint] = useSelect('JSON-RPC', {
|
||||
@@ -74,6 +76,7 @@ export default function Wrapper({ children }: { children: ReactNode }) {
|
||||
locale={locale}
|
||||
jsonRpcEndpoint={jsonRpcEndpoint === NO_JSON_RPC ? undefined : jsonRpcEndpoint}
|
||||
provider={connector?.provider}
|
||||
tokenList={tokens}
|
||||
>
|
||||
{children}
|
||||
</Widget>
|
||||
|
||||
@@ -9,6 +9,6 @@ export const loadingCss = css`
|
||||
|
||||
// need to use isLoading as `loading` is a reserved prop
|
||||
export const loadingTransitionCss = css<{ isLoading: boolean }>`
|
||||
${({ isLoading }) => isLoading && loadingCss};
|
||||
transition: opacity ${({ isLoading }) => (isLoading ? 0 : 0.2)}s ease-in-out;
|
||||
opacity: ${({ isLoading }) => isLoading && loadingOpacity};
|
||||
transition: color 0.125s linear, opacity ${({ isLoading }) => (isLoading ? 0 : 0.25)}s ease-in-out;
|
||||
`
|
||||
|
||||
@@ -92,7 +92,7 @@ export default function useClientSideSmartOrderRouterTrade<TTradeType extends Tr
|
||||
const getIsValidBlock = useGetIsValidBlock()
|
||||
const { data: quoteResult, error } = usePoll(getQuoteResult, JSON.stringify(queryArgs), {
|
||||
debounce: isDebouncing,
|
||||
staleCallback: useCallback(({ data }) => !getIsValidBlock(Number(data?.blockNumber) || 0), [getIsValidBlock]),
|
||||
isStale: useCallback(({ data }) => !getIsValidBlock(Number(data?.blockNumber) || 0), [getIsValidBlock]),
|
||||
}) ?? {
|
||||
error: undefined,
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ import { InterfaceTrade, TradeState } from 'state/routing/types'
|
||||
|
||||
import useClientSideSmartOrderRouterTrade from '../routing/useClientSideSmartOrderRouterTrade'
|
||||
|
||||
export const INVALID_TRADE = { state: TradeState.INVALID, trade: undefined }
|
||||
|
||||
/**
|
||||
* Returns the best v2+v3 trade for a desired swap.
|
||||
* @param tradeType whether the swap is an exact in/out
|
||||
@@ -39,6 +41,7 @@ export function useBestTrade(
|
||||
return useMemo(() => {
|
||||
const { state, trade } = tradeObject
|
||||
// If the trade is in a settled state, return it.
|
||||
if (state === TradeState.INVALID) return INVALID_TRADE
|
||||
if ((state !== TradeState.LOADING && state !== TradeState.SYNCING) || trade) return tradeObject
|
||||
|
||||
const [currencyIn, currencyOut] =
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
import { Currency, CurrencyAmount, Percent, TradeType } from '@uniswap/sdk-core'
|
||||
import { atom } from 'jotai'
|
||||
import { useAtomValue, useUpdateAtom } from 'jotai/utils'
|
||||
import { Currency, CurrencyAmount, TradeType } from '@uniswap/sdk-core'
|
||||
import { useAtomValue } from 'jotai/utils'
|
||||
import useActiveWeb3React from 'lib/hooks/useActiveWeb3React'
|
||||
import { useCurrencyBalances } from 'lib/hooks/useCurrencyBalance'
|
||||
import useSlippage, { DEFAULT_SLIPPAGE, Slippage } from 'lib/hooks/useSlippage'
|
||||
import useUSDCPriceImpact, { PriceImpact } from 'lib/hooks/useUSDCPriceImpact'
|
||||
import { Field, swapAtom } from 'lib/state/swap'
|
||||
import tryParseCurrencyAmount from 'lib/utils/tryParseCurrencyAmount'
|
||||
import { useEffect, useMemo } from 'react'
|
||||
import { createContext, PropsWithChildren, useContext, useMemo } from 'react'
|
||||
import { InterfaceTrade, TradeState } from 'state/routing/types'
|
||||
|
||||
import useActiveWeb3React from '../useActiveWeb3React'
|
||||
import useSlippage, { Slippage } from '../useSlippage'
|
||||
import useUSDCPriceImpact, { PriceImpact } from '../useUSDCPriceImpact'
|
||||
import { useBestTrade } from './useBestTrade'
|
||||
import { INVALID_TRADE, useBestTrade } from './useBestTrade'
|
||||
import useWrapCallback, { WrapType } from './useWrapCallback'
|
||||
|
||||
interface SwapField {
|
||||
@@ -33,7 +32,6 @@ interface SwapInfo {
|
||||
|
||||
// from the current swap inputs, compute the best trade and return it.
|
||||
function useComputeSwapInfo(): SwapInfo {
|
||||
const { account } = useActiveWeb3React()
|
||||
const { type: wrapType } = useWrapCallback()
|
||||
const isWrapping = wrapType === WrapType.WRAP || wrapType === WrapType.UNWRAP
|
||||
const { independentField, amount, [Field.INPUT]: currencyIn, [Field.OUTPUT]: currencyOut } = useAtomValue(swapAtom)
|
||||
@@ -43,10 +41,11 @@ function useComputeSwapInfo(): SwapInfo {
|
||||
() => tryParseCurrencyAmount(amount, (isExactIn ? currencyIn : currencyOut) ?? undefined),
|
||||
[amount, isExactIn, currencyIn, currencyOut]
|
||||
)
|
||||
const hasAmounts = currencyIn && currencyOut && parsedAmount && !isWrapping
|
||||
const trade = useBestTrade(
|
||||
isExactIn ? TradeType.EXACT_INPUT : TradeType.EXACT_OUTPUT,
|
||||
parsedAmount,
|
||||
(isExactIn ? currencyOut : currencyIn) ?? undefined
|
||||
hasAmounts ? parsedAmount : undefined,
|
||||
hasAmounts ? (isExactIn ? currencyOut : currencyIn) : undefined
|
||||
)
|
||||
|
||||
const amountIn = useMemo(
|
||||
@@ -57,6 +56,8 @@ function useComputeSwapInfo(): SwapInfo {
|
||||
() => (isWrapping || !isExactIn ? parsedAmount : trade.trade?.outputAmount),
|
||||
[isExactIn, isWrapping, parsedAmount, trade.trade?.outputAmount]
|
||||
)
|
||||
|
||||
const { account } = useActiveWeb3React()
|
||||
const [balanceIn, balanceOut] = useCurrencyBalances(
|
||||
account,
|
||||
useMemo(() => [currencyIn, currencyOut], [currencyIn, currencyOut])
|
||||
@@ -101,21 +102,24 @@ function useComputeSwapInfo(): SwapInfo {
|
||||
)
|
||||
}
|
||||
|
||||
const swapInfoAtom = atom<SwapInfo>({
|
||||
const DEFAULT_SWAP_INFO: SwapInfo = {
|
||||
[Field.INPUT]: {},
|
||||
[Field.OUTPUT]: {},
|
||||
trade: { state: TradeState.INVALID },
|
||||
slippage: { auto: true, allowed: new Percent(0) },
|
||||
})
|
||||
trade: INVALID_TRADE,
|
||||
slippage: DEFAULT_SLIPPAGE,
|
||||
}
|
||||
|
||||
export function SwapInfoUpdater() {
|
||||
const setSwapInfo = useUpdateAtom(swapInfoAtom)
|
||||
const SwapInfoContext = createContext(DEFAULT_SWAP_INFO)
|
||||
|
||||
export function SwapInfoProvider({ children, disabled }: PropsWithChildren<{ disabled?: boolean }>) {
|
||||
const swapInfo = useComputeSwapInfo()
|
||||
useEffect(() => setSwapInfo(swapInfo), [swapInfo, setSwapInfo])
|
||||
return null
|
||||
if (disabled) {
|
||||
return <SwapInfoContext.Provider value={DEFAULT_SWAP_INFO}>{children}</SwapInfoContext.Provider>
|
||||
}
|
||||
return <SwapInfoContext.Provider value={swapInfo}>{children}</SwapInfoContext.Provider>
|
||||
}
|
||||
|
||||
/** Requires that SwapInfoUpdater be installed in the DOM tree. **/
|
||||
export default function useSwapInfo(): SwapInfo {
|
||||
return useAtomValue(swapInfoAtom)
|
||||
return useContext(SwapInfoContext)
|
||||
}
|
||||
|
||||
@@ -5,9 +5,10 @@ import useActiveWeb3React from 'lib/hooks/useActiveWeb3React'
|
||||
import { useToken } from 'lib/hooks/useCurrency'
|
||||
import useNativeCurrency from 'lib/hooks/useNativeCurrency'
|
||||
import { Field, Swap, swapAtom } from 'lib/state/swap'
|
||||
import { useCallback, useLayoutEffect, useState } from 'react'
|
||||
import { useCallback, useRef } from 'react'
|
||||
|
||||
import useOnSupportedNetwork from '../useOnSupportedNetwork'
|
||||
import { useIsTokenListLoaded } from '../useTokenList'
|
||||
|
||||
export type DefaultAddress = string | { [chainId: number]: string | 'NATIVE' } | 'NATIVE'
|
||||
|
||||
@@ -71,13 +72,10 @@ export default function useSyncTokenDefaults({
|
||||
updateSwap((swap) => ({ ...swap, ...defaultSwapState }))
|
||||
}, [defaultInputAmount, defaultInputToken, defaultOutputAmount, defaultOutputToken, updateSwap])
|
||||
|
||||
const [previousChainId, setPreviousChainId] = useState(chainId)
|
||||
useLayoutEffect(() => {
|
||||
setPreviousChainId(chainId)
|
||||
}, [chainId])
|
||||
useLayoutEffect(() => {
|
||||
if (chainId && chainId !== previousChainId) {
|
||||
setToDefaults()
|
||||
}
|
||||
}, [chainId, previousChainId, setToDefaults])
|
||||
const lastChainId = useRef<number | undefined>(undefined)
|
||||
const shouldSync = useIsTokenListLoaded() && chainId && chainId !== lastChainId.current
|
||||
if (shouldSync) {
|
||||
setToDefaults()
|
||||
lastChainId.current = chainId
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ type Web3ContextType = {
|
||||
accounts?: ReturnType<Web3ReactHooks['useAccounts']>
|
||||
account?: ReturnType<Web3ReactHooks['useAccount']>
|
||||
active?: ReturnType<Web3ReactHooks['useIsActive']>
|
||||
activating?: ReturnType<Web3ReactHooks['useIsActivating']>
|
||||
error?: ReturnType<Web3ReactHooks['useError']>
|
||||
ensNames?: ReturnType<Web3ReactHooks['useENSNames']>
|
||||
ensName?: ReturnType<Web3ReactHooks['useENSName']>
|
||||
@@ -23,7 +24,7 @@ type Web3ContextType = {
|
||||
|
||||
const EMPTY_CONNECTOR = initializeConnector(() => EMPTY)
|
||||
const EMPTY_CONTEXT: Web3ContextType = { connector: EMPTY }
|
||||
const urlConnectorAtom = atom<[Connector, Web3ReactHooks, Web3ReactStore]>(EMPTY_CONNECTOR)
|
||||
const jsonRpcConnectorAtom = atom<[Connector, Web3ReactHooks, Web3ReactStore]>(EMPTY_CONNECTOR)
|
||||
const injectedConnectorAtom = atom<[Connector, Web3ReactHooks, Web3ReactStore]>(EMPTY_CONNECTOR)
|
||||
const Web3Context = createContext(EMPTY_CONTEXT)
|
||||
|
||||
@@ -69,23 +70,31 @@ export function ActiveWeb3Provider({
|
||||
return EIP1193
|
||||
}, [provider]) as { new (actions: Actions, initializer: typeof provider): Connector }
|
||||
const injectedConnector = useConnector(injectedConnectorAtom, Injected, provider)
|
||||
const urlConnector = useConnector(urlConnectorAtom, Url, jsonRpcEndpoint)
|
||||
const [connector, hooks] = injectedConnector[1].useIsActive() ? injectedConnector : urlConnector ?? EMPTY_CONNECTOR
|
||||
const JsonRpc = useMemo(() => {
|
||||
if (JsonRpcProvider.isProvider(jsonRpcEndpoint)) return JsonRpcConnector
|
||||
return Url
|
||||
}, [jsonRpcEndpoint]) as { new (actions: Actions, initializer: typeof jsonRpcEndpoint): Connector }
|
||||
const jsonRpcConnector = useConnector(jsonRpcConnectorAtom, JsonRpc, jsonRpcEndpoint)
|
||||
const [connector, hooks] = injectedConnector[1].useIsActive()
|
||||
? injectedConnector
|
||||
: jsonRpcConnector ?? EMPTY_CONNECTOR
|
||||
|
||||
const library = hooks.useProvider()
|
||||
const chainId = hooks.useChainId()
|
||||
|
||||
const accounts = hooks.useAccounts()
|
||||
const account = hooks.useAccount()
|
||||
const activating = hooks.useIsActivating()
|
||||
const active = hooks.useIsActive()
|
||||
const error = hooks.useError()
|
||||
const chainId = hooks.useChainId()
|
||||
const ensNames = hooks.useENSNames()
|
||||
const ensName = hooks.useENSName()
|
||||
const error = hooks.useError()
|
||||
const web3 = useMemo(() => {
|
||||
if (connector === EMPTY || !active) {
|
||||
if (connector === EMPTY || !(active || activating)) {
|
||||
return EMPTY_CONTEXT
|
||||
}
|
||||
return { connector, library, chainId, accounts, account, active, error, ensNames, ensName }
|
||||
}, [account, accounts, active, chainId, connector, ensName, ensNames, error, library])
|
||||
return { connector, library, chainId, accounts, account, active, activating, error, ensNames, ensName }
|
||||
}, [account, accounts, activating, active, chainId, connector, ensName, ensNames, error, library])
|
||||
|
||||
// Log web3 errors to facilitate debugging.
|
||||
useEffect(() => {
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
import useDebounce from 'hooks/useDebounce'
|
||||
import useIsWindowVisible from 'hooks/useIsWindowVisible'
|
||||
import { atom } from 'jotai'
|
||||
import { useAtomValue, useUpdateAtom } from 'jotai/utils'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
|
||||
function useBlock() {
|
||||
const { chainId, library } = useActiveWeb3React()
|
||||
const windowVisible = useIsWindowVisible()
|
||||
const [state, setState] = useState<{ chainId?: number; block?: number }>({ chainId })
|
||||
|
||||
const onBlock = useCallback(
|
||||
(block: number) => {
|
||||
setState((state) => {
|
||||
if (state.chainId === chainId) {
|
||||
if (typeof state.block !== 'number') return { chainId, block }
|
||||
return { chainId, block: Math.max(block, state.block) }
|
||||
}
|
||||
return state
|
||||
})
|
||||
},
|
||||
[chainId]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (library && chainId && windowVisible) {
|
||||
// If chainId hasn't changed, don't clear the block. This prevents re-fetching still valid data.
|
||||
setState((state) => (state.chainId === chainId ? state : { chainId }))
|
||||
|
||||
library
|
||||
.getBlockNumber()
|
||||
.then(onBlock)
|
||||
.catch((error) => {
|
||||
console.error(`Failed to get block number for chainId ${chainId}`, error)
|
||||
})
|
||||
|
||||
library.on('block', onBlock)
|
||||
return () => {
|
||||
library.removeListener('block', onBlock)
|
||||
}
|
||||
}
|
||||
return undefined
|
||||
}, [chainId, library, onBlock, windowVisible])
|
||||
|
||||
const debouncedBlock = useDebounce(state.block, 100)
|
||||
return state.block ? debouncedBlock : undefined
|
||||
}
|
||||
|
||||
const blockAtom = atom<number | undefined>(undefined)
|
||||
|
||||
export function BlockUpdater() {
|
||||
const setBlock = useUpdateAtom(blockAtom)
|
||||
const block = useBlock()
|
||||
useEffect(() => {
|
||||
setBlock(block)
|
||||
}, [block, setBlock])
|
||||
return null
|
||||
}
|
||||
|
||||
/** Requires that BlockUpdater be installed in the DOM tree. */
|
||||
export default function useBlockNumber(): number | undefined {
|
||||
const { chainId } = useActiveWeb3React()
|
||||
const block = useAtomValue(blockAtom)
|
||||
return chainId ? block : undefined
|
||||
}
|
||||
|
||||
export function useFastForwardBlockNumber(): (block: number) => void {
|
||||
return useUpdateAtom(blockAtom)
|
||||
}
|
||||
78
src/lib/hooks/useBlockNumber.tsx
Normal file
78
src/lib/hooks/useBlockNumber.tsx
Normal file
@@ -0,0 +1,78 @@
|
||||
import useActiveWeb3React from 'hooks/useActiveWeb3React'
|
||||
import useIsWindowVisible from 'hooks/useIsWindowVisible'
|
||||
import { createContext, ReactNode, useCallback, useContext, useEffect, useMemo, useState } from 'react'
|
||||
|
||||
const MISSING_PROVIDER = Symbol()
|
||||
const BlockNumberContext = createContext<
|
||||
| {
|
||||
value?: number
|
||||
fastForward(block: number): void
|
||||
}
|
||||
| typeof MISSING_PROVIDER
|
||||
>(MISSING_PROVIDER)
|
||||
|
||||
function useBlockNumberContext() {
|
||||
const blockNumber = useContext(BlockNumberContext)
|
||||
if (blockNumber === MISSING_PROVIDER) {
|
||||
throw new Error('BlockNumber hooks must be wrapped in a <BlockNumberProvider>')
|
||||
}
|
||||
return blockNumber
|
||||
}
|
||||
|
||||
/** Requires that BlockUpdater be installed in the DOM tree. */
|
||||
export default function useBlockNumber(): number | undefined {
|
||||
return useBlockNumberContext().value
|
||||
}
|
||||
|
||||
export function useFastForwardBlockNumber(): (block: number) => void {
|
||||
return useBlockNumberContext().fastForward
|
||||
}
|
||||
|
||||
export function BlockNumberProvider({ children }: { children: ReactNode }) {
|
||||
const { chainId: activeChainId, library } = useActiveWeb3React()
|
||||
const [{ chainId, block }, setChainBlock] = useState<{ chainId?: number; block?: number }>({ chainId: activeChainId })
|
||||
|
||||
const onBlock = useCallback(
|
||||
(block: number) => {
|
||||
setChainBlock((chainBlock) => {
|
||||
if (chainBlock.chainId === activeChainId) {
|
||||
if (!chainBlock.block || chainBlock.block < block) {
|
||||
return { chainId: activeChainId, block }
|
||||
}
|
||||
}
|
||||
return chainBlock
|
||||
})
|
||||
},
|
||||
[activeChainId, setChainBlock]
|
||||
)
|
||||
|
||||
const windowVisible = useIsWindowVisible()
|
||||
useEffect(() => {
|
||||
if (library && activeChainId && windowVisible) {
|
||||
// If chainId hasn't changed, don't clear the block. This prevents re-fetching still valid data.
|
||||
setChainBlock((chainBlock) => (chainBlock.chainId === activeChainId ? chainBlock : { chainId: activeChainId }))
|
||||
|
||||
library
|
||||
.getBlockNumber()
|
||||
.then(onBlock)
|
||||
.catch((error) => {
|
||||
console.error(`Failed to get block number for chainId ${activeChainId}`, error)
|
||||
})
|
||||
|
||||
library.on('block', onBlock)
|
||||
return () => {
|
||||
library.removeListener('block', onBlock)
|
||||
}
|
||||
}
|
||||
return undefined
|
||||
}, [activeChainId, library, onBlock, setChainBlock, windowVisible])
|
||||
|
||||
const value = useMemo(
|
||||
() => ({
|
||||
value: chainId === activeChainId ? block : undefined,
|
||||
fastForward: (block: number) => setChainBlock({ chainId: activeChainId, block }),
|
||||
}),
|
||||
[activeChainId, block, chainId]
|
||||
)
|
||||
return <BlockNumberContext.Provider value={value}>{children}</BlockNumberContext.Provider>
|
||||
}
|
||||
@@ -47,7 +47,7 @@ export function useNativeCurrencyBalances(uncheckedAddresses?: (string | undefin
|
||||
}
|
||||
|
||||
const ERC20Interface = new Interface(ERC20ABI) as Erc20Interface
|
||||
const tokenBalancesGasRequirement = { gasRequired: 125_000 }
|
||||
const tokenBalancesGasRequirement = { gasRequired: 185_000 }
|
||||
|
||||
/**
|
||||
* Returns a map of token addresses to their eventually consistent token balances for a single account.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Currency } from '@uniswap/sdk-core'
|
||||
import { useTheme } from 'lib/theme'
|
||||
import Vibrant from 'node-vibrant/lib/bundle.js'
|
||||
import { useEffect, useLayoutEffect, useState } from 'react'
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
import useCurrencyLogoURIs from './useCurrencyLogoURIs'
|
||||
|
||||
@@ -57,7 +57,7 @@ export default function useCurrencyColor(token?: Currency) {
|
||||
const theme = useTheme()
|
||||
const logoURIs = useCurrencyLogoURIs(token)
|
||||
|
||||
useLayoutEffect(() => {
|
||||
useEffect(() => {
|
||||
let stale = false
|
||||
|
||||
if (theme.tokenColorExtraction && token) {
|
||||
|
||||
@@ -5,7 +5,7 @@ import useActiveWeb3React from './useActiveWeb3React'
|
||||
|
||||
function useOnSupportedNetwork() {
|
||||
const { chainId } = useActiveWeb3React()
|
||||
return useMemo(() => chainId && ALL_SUPPORTED_CHAIN_IDS.includes(chainId), [chainId])
|
||||
return useMemo(() => Boolean(chainId && ALL_SUPPORTED_CHAIN_IDS.includes(chainId)), [chainId])
|
||||
}
|
||||
|
||||
export default useOnSupportedNetwork
|
||||
|
||||
@@ -9,7 +9,7 @@ interface PollingOptions<T> {
|
||||
debounce?: boolean
|
||||
|
||||
// If stale, any cached result will be returned, and a new fetch will be initiated.
|
||||
staleCallback?: (value: T) => boolean
|
||||
isStale?: (value: T) => boolean
|
||||
|
||||
pollingInterval?: number
|
||||
keepUnusedDataFor?: number
|
||||
@@ -25,7 +25,7 @@ export default function usePoll<T>(
|
||||
key = '',
|
||||
{
|
||||
debounce = false,
|
||||
staleCallback,
|
||||
isStale,
|
||||
pollingInterval = DEFAULT_POLLING_INTERVAL,
|
||||
keepUnusedDataFor = DEFAULT_KEEP_UNUSED_DATA_FOR,
|
||||
}: PollingOptions<T>
|
||||
@@ -39,11 +39,10 @@ export default function usePoll<T>(
|
||||
let timeout: number
|
||||
|
||||
const entry = cache.get(key)
|
||||
const isStale = staleCallback && entry?.result !== undefined ? staleCallback(entry.result) : false
|
||||
if (entry) {
|
||||
// If there is not a pending fetch (and there should be), queue one.
|
||||
if (entry.ttl) {
|
||||
if (isStale) {
|
||||
if (isStale && entry?.result !== undefined ? isStale(entry.result) : false) {
|
||||
poll() // stale results should be refetched immediately
|
||||
} else if (entry.ttl && entry.ttl + keepUnusedDataFor > Date.now()) {
|
||||
timeout = setTimeout(poll, Math.max(0, entry.ttl - Date.now()))
|
||||
@@ -57,6 +56,7 @@ export default function usePoll<T>(
|
||||
|
||||
return () => {
|
||||
clearTimeout(timeout)
|
||||
timeout = 0
|
||||
}
|
||||
|
||||
async function poll(ttl = Date.now() + pollingInterval) {
|
||||
@@ -66,9 +66,9 @@ export default function usePoll<T>(
|
||||
// Always set the result in the cache, but only set it as data if the key is still being queried.
|
||||
const result = await fetch()
|
||||
cache.set(key, { ttl, result })
|
||||
setData((data) => (data.key === key ? { key, result } : data))
|
||||
if (timeout) setData((data) => (data.key === key ? { key, result } : data))
|
||||
}
|
||||
}, [cache, debounce, fetch, keepUnusedDataFor, key, pollingInterval, staleCallback])
|
||||
}, [cache, debounce, fetch, isStale, keepUnusedDataFor, key, pollingInterval])
|
||||
|
||||
useEffect(() => {
|
||||
// Cleanup stale entries when a new key is used.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Currency, Percent, TradeType } from '@uniswap/sdk-core'
|
||||
import useAutoSlippageTolerance from 'hooks/useAutoSlippageTolerance'
|
||||
import useAutoSlippageTolerance, { DEFAULT_AUTO_SLIPPAGE } from 'hooks/useAutoSlippageTolerance'
|
||||
import { useAtomValue } from 'jotai/utils'
|
||||
import { autoSlippageAtom, maxSlippageAtom } from 'lib/state/settings'
|
||||
import { useMemo } from 'react'
|
||||
@@ -17,6 +17,8 @@ export interface Slippage {
|
||||
warning?: 'warning' | 'error'
|
||||
}
|
||||
|
||||
export const DEFAULT_SLIPPAGE = { auto: true, allowed: DEFAULT_AUTO_SLIPPAGE }
|
||||
|
||||
/** Returns the allowed slippage, and whether it is auto-slippage. */
|
||||
export default function useSlippage(trade: InterfaceTrade<Currency, Currency, TradeType> | undefined): Slippage {
|
||||
const shouldUseAutoSlippage = useAtomValue(autoSlippageAtom)
|
||||
@@ -27,6 +29,9 @@ export default function useSlippage(trade: InterfaceTrade<Currency, Currency, Tr
|
||||
const auto = shouldUseAutoSlippage || !maxSlippage
|
||||
const allowed = shouldUseAutoSlippage ? autoSlippage : maxSlippage ?? autoSlippage
|
||||
const warning = auto ? undefined : getSlippageWarning(allowed)
|
||||
if (auto && allowed === DEFAULT_AUTO_SLIPPAGE) {
|
||||
return DEFAULT_SLIPPAGE
|
||||
}
|
||||
return { auto, allowed, warning }
|
||||
}, [autoSlippage, maxSlippage, shouldUseAutoSlippage])
|
||||
}
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import { NativeCurrency, Token } from '@uniswap/sdk-core'
|
||||
import { TokenInfo, TokenList } from '@uniswap/token-lists'
|
||||
import { atom, useAtom } from 'jotai'
|
||||
import { useAtomValue } from 'jotai/utils'
|
||||
import useActiveWeb3React from 'lib/hooks/useActiveWeb3React'
|
||||
import resolveENSContentHash from 'lib/utils/resolveENSContentHash'
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import { createContext, PropsWithChildren, useCallback, useContext, useEffect, useMemo, useState } from 'react'
|
||||
import { WrappedTokenInfo } from 'state/lists/wrappedTokenInfo'
|
||||
|
||||
import fetchTokenList from './fetchTokenList'
|
||||
@@ -14,19 +12,61 @@ import { validateTokens } from './validateTokenList'
|
||||
|
||||
export const DEFAULT_TOKEN_LIST = 'https://gateway.ipfs.io/ipns/tokens.uniswap.org'
|
||||
|
||||
const chainTokenMapAtom = atom<ChainTokenMap | null>(null)
|
||||
const MISSING_PROVIDER = Symbol()
|
||||
const ChainTokenMapContext = createContext<ChainTokenMap | undefined | typeof MISSING_PROVIDER>(MISSING_PROVIDER)
|
||||
|
||||
export function useIsTokenListLoaded() {
|
||||
return Boolean(useAtomValue(chainTokenMapAtom))
|
||||
function useChainTokenMapContext() {
|
||||
const chainTokenMap = useContext(ChainTokenMapContext)
|
||||
if (chainTokenMap === MISSING_PROVIDER) {
|
||||
throw new Error('TokenList hooks must be wrapped in a <TokenListProvider>')
|
||||
}
|
||||
return chainTokenMap
|
||||
}
|
||||
|
||||
export function useSyncTokenList(list: string | TokenInfo[] = DEFAULT_TOKEN_LIST): void {
|
||||
export function useIsTokenListLoaded() {
|
||||
return Boolean(useChainTokenMapContext())
|
||||
}
|
||||
|
||||
export default function useTokenList(): WrappedTokenInfo[] {
|
||||
const { chainId } = useActiveWeb3React()
|
||||
const chainTokenMap = useChainTokenMapContext()
|
||||
const tokenMap = chainId && chainTokenMap?.[chainId]
|
||||
return useMemo(() => {
|
||||
if (!tokenMap) return []
|
||||
return Object.values(tokenMap).map(({ token }) => token)
|
||||
}, [tokenMap])
|
||||
}
|
||||
|
||||
export type TokenMap = { [address: string]: Token }
|
||||
|
||||
export function useTokenMap(): TokenMap {
|
||||
const { chainId } = useActiveWeb3React()
|
||||
const chainTokenMap = useChainTokenMapContext()
|
||||
const tokenMap = chainId && chainTokenMap?.[chainId]
|
||||
return useMemo(() => {
|
||||
if (!tokenMap) return {}
|
||||
return Object.entries(tokenMap).reduce((map, [address, { token }]) => {
|
||||
map[address] = token
|
||||
return map
|
||||
}, {} as TokenMap)
|
||||
}, [tokenMap])
|
||||
}
|
||||
|
||||
export function useQueryCurrencies(query = ''): (WrappedTokenInfo | NativeCurrency)[] {
|
||||
return useQueryTokens(query, useTokenList())
|
||||
}
|
||||
|
||||
export function TokenListProvider({
|
||||
list = DEFAULT_TOKEN_LIST,
|
||||
children,
|
||||
}: PropsWithChildren<{ list?: string | TokenInfo[] }>) {
|
||||
// Error boundaries will not catch (non-rendering) async errors, but it should still be shown
|
||||
const [error, setError] = useState<Error>()
|
||||
if (error) throw error
|
||||
|
||||
const [chainTokenMap, setChainTokenMap] = useAtom(chainTokenMapAtom)
|
||||
useEffect(() => setChainTokenMap(null), [list, setChainTokenMap])
|
||||
const [chainTokenMap, setChainTokenMap] = useState<ChainTokenMap>()
|
||||
|
||||
useEffect(() => setChainTokenMap(undefined), [list])
|
||||
|
||||
const { chainId, library } = useActiveWeb3React()
|
||||
const resolver = useCallback(
|
||||
@@ -70,34 +110,7 @@ export function useSyncTokenList(list: string | TokenInfo[] = DEFAULT_TOKEN_LIST
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [chainTokenMap, list, resolver, setChainTokenMap])
|
||||
}
|
||||
}, [chainTokenMap, list, resolver])
|
||||
|
||||
export default function useTokenList(): WrappedTokenInfo[] {
|
||||
const { chainId } = useActiveWeb3React()
|
||||
const chainTokenMap = useAtomValue(chainTokenMapAtom)
|
||||
const tokenMap = chainId && chainTokenMap?.[chainId]
|
||||
return useMemo(() => {
|
||||
if (!tokenMap) return []
|
||||
return Object.values(tokenMap).map(({ token }) => token)
|
||||
}, [tokenMap])
|
||||
}
|
||||
|
||||
export type TokenMap = { [address: string]: Token }
|
||||
|
||||
export function useTokenMap(): TokenMap {
|
||||
const { chainId } = useActiveWeb3React()
|
||||
const chainTokenMap = useAtomValue(chainTokenMapAtom)
|
||||
const tokenMap = chainId && chainTokenMap?.[chainId]
|
||||
return useMemo(() => {
|
||||
if (!tokenMap) return {}
|
||||
return Object.entries(tokenMap).reduce((map, [address, { token }]) => {
|
||||
map[address] = token
|
||||
return map
|
||||
}, {} as TokenMap)
|
||||
}, [tokenMap])
|
||||
}
|
||||
|
||||
export function useQueryCurrencies(query = ''): (WrappedTokenInfo | NativeCurrency)[] {
|
||||
return useQueryTokens(query, useTokenList())
|
||||
return <ChainTokenMapContext.Provider value={chainTokenMap}>{children}</ChainTokenMapContext.Provider>
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import { i18n } from '@lingui/core'
|
||||
import { I18nProvider } from '@lingui/react'
|
||||
import { SupportedLocale } from 'constants/locales'
|
||||
import { DEFAULT_LOCALE, SupportedLocale } from 'constants/locales'
|
||||
import {
|
||||
af,
|
||||
ar,
|
||||
@@ -79,8 +79,6 @@ const plurals: LocalePlural = {
|
||||
export async function dynamicActivate(locale: SupportedLocale) {
|
||||
i18n.loadLocaleData(locale, { plurals: () => plurals[locale] })
|
||||
try {
|
||||
// There are no default messages in production,
|
||||
// see https://github.com/lingui/js-lingui/issues/388#issuecomment-497779030
|
||||
const catalog = await import(`${process.env.REACT_APP_LOCALES}/${locale}.js`)
|
||||
// Bundlers will either export it as default or as a named export named default.
|
||||
i18n.load(locale, catalog.messages || catalog.default.messages)
|
||||
@@ -104,6 +102,16 @@ export function Provider({ locale, forceRenderAfterLocaleChange = true, onActiva
|
||||
})
|
||||
}, [locale, onActivate])
|
||||
|
||||
// Initialize the locale immediately if it is DEFAULT_LOCALE, so that keys are shown while the translation messages load.
|
||||
// This renders the translation _keys_, not the translation _messages_, which is only acceptable while loading the DEFAULT_LOCALE,
|
||||
// as [there are no "default" messages](https://github.com/lingui/js-lingui/issues/388#issuecomment-497779030).
|
||||
// See https://github.com/lingui/js-lingui/issues/1194#issuecomment-1068488619.
|
||||
if (i18n.locale === undefined && locale === DEFAULT_LOCALE) {
|
||||
i18n.loadLocaleData(DEFAULT_LOCALE, { plurals: () => plurals[DEFAULT_LOCALE] })
|
||||
i18n.load(DEFAULT_LOCALE, {})
|
||||
i18n.activate(DEFAULT_LOCALE)
|
||||
}
|
||||
|
||||
return (
|
||||
<I18nProvider forceRenderOnLocaleChange={forceRenderAfterLocaleChange} i18n={i18n}>
|
||||
{children}
|
||||
|
||||
@@ -11,8 +11,8 @@ export const store = createStore(reducer)
|
||||
export default multicall
|
||||
|
||||
export function MulticallUpdater() {
|
||||
const latestBlockNumber = useBlockNumber()
|
||||
const { chainId } = useActiveWeb3React()
|
||||
const latestBlockNumber = useBlockNumber()
|
||||
const contract = useInterfaceMulticall()
|
||||
return <multicall.Updater chainId={chainId} latestBlockNumber={latestBlockNumber} contract={contract} />
|
||||
}
|
||||
|
||||
@@ -336,11 +336,11 @@ export function PositionPage({
|
||||
|
||||
const removed = liquidity?.eq(0)
|
||||
|
||||
const metadata = usePositionTokenURI(parsedTokenId)
|
||||
|
||||
const token0 = useToken(token0Address)
|
||||
const token1 = useToken(token1Address)
|
||||
|
||||
const metadata = usePositionTokenURI(parsedTokenId)
|
||||
|
||||
const currency0 = token0 ? unwrappedToken(token0) : undefined
|
||||
const currency1 = token1 ? unwrappedToken(token1) : undefined
|
||||
|
||||
@@ -389,6 +389,10 @@ export function PositionPage({
|
||||
// fees
|
||||
const [feeValue0, feeValue1] = useV3PositionFees(pool ?? undefined, positionDetails?.tokenId, receiveWETH)
|
||||
|
||||
// these currencies will match the feeValue{0,1} currencies for the purposes of fee collection
|
||||
const currency0ForFeeCollectionPurposes = pool ? (receiveWETH ? pool.token0 : unwrappedToken(pool.token0)) : undefined
|
||||
const currency1ForFeeCollectionPurposes = pool ? (receiveWETH ? pool.token1 : unwrappedToken(pool.token1)) : undefined
|
||||
|
||||
const [collecting, setCollecting] = useState<boolean>(false)
|
||||
const [collectMigrationHash, setCollectMigrationHash] = useState<string | null>(null)
|
||||
const isCollectPending = useIsTransactionPending(collectMigrationHash ?? undefined)
|
||||
@@ -422,14 +426,25 @@ export function PositionPage({
|
||||
const addTransaction = useTransactionAdder()
|
||||
const positionManager = useV3NFTPositionManagerContract()
|
||||
const collect = useCallback(() => {
|
||||
if (!chainId || !feeValue0 || !feeValue1 || !positionManager || !account || !tokenId || !library) return
|
||||
if (
|
||||
!currency0ForFeeCollectionPurposes ||
|
||||
!currency1ForFeeCollectionPurposes ||
|
||||
!chainId ||
|
||||
!positionManager ||
|
||||
!account ||
|
||||
!tokenId ||
|
||||
!library
|
||||
)
|
||||
return
|
||||
|
||||
setCollecting(true)
|
||||
|
||||
// we fall back to expecting 0 fees in case the fetch fails, which is safe in the
|
||||
// vast majority of cases
|
||||
const { calldata, value } = NonfungiblePositionManager.collectCallParameters({
|
||||
tokenId: tokenId.toString(),
|
||||
expectedCurrencyOwed0: feeValue0,
|
||||
expectedCurrencyOwed1: feeValue1,
|
||||
expectedCurrencyOwed0: feeValue0 ?? CurrencyAmount.fromRawAmount(currency0ForFeeCollectionPurposes, 0),
|
||||
expectedCurrencyOwed1: feeValue1 ?? CurrencyAmount.fromRawAmount(currency1ForFeeCollectionPurposes, 0),
|
||||
recipient: account,
|
||||
})
|
||||
|
||||
@@ -458,13 +473,13 @@ export function PositionPage({
|
||||
ReactGA.event({
|
||||
category: 'Liquidity',
|
||||
action: 'CollectV3',
|
||||
label: [feeValue0.currency.symbol, feeValue1.currency.symbol].join('/'),
|
||||
label: [currency0ForFeeCollectionPurposes.symbol, currency1ForFeeCollectionPurposes.symbol].join('/'),
|
||||
})
|
||||
|
||||
addTransaction(response, {
|
||||
type: TransactionType.COLLECT_FEES,
|
||||
currencyId0: currencyId(feeValue0.currency),
|
||||
currencyId1: currencyId(feeValue1.currency),
|
||||
currencyId0: currencyId(currency0ForFeeCollectionPurposes),
|
||||
currencyId1: currencyId(currency1ForFeeCollectionPurposes),
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -472,7 +487,18 @@ export function PositionPage({
|
||||
setCollecting(false)
|
||||
console.error(error)
|
||||
})
|
||||
}, [chainId, feeValue0, feeValue1, positionManager, account, tokenId, addTransaction, library])
|
||||
}, [
|
||||
chainId,
|
||||
feeValue0,
|
||||
feeValue1,
|
||||
currency0ForFeeCollectionPurposes,
|
||||
currency1ForFeeCollectionPurposes,
|
||||
positionManager,
|
||||
account,
|
||||
tokenId,
|
||||
addTransaction,
|
||||
library,
|
||||
])
|
||||
|
||||
const owner = useSingleCallResult(!!tokenId ? positionManager : null, 'ownerOf', [tokenId]).result?.[0]
|
||||
const ownsNFT = owner === account || positionDetails?.operator === account
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { BigNumber } from '@ethersproject/bignumber'
|
||||
import { TransactionResponse } from '@ethersproject/providers'
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { Percent } from '@uniswap/sdk-core'
|
||||
import { CurrencyAmount, Percent } from '@uniswap/sdk-core'
|
||||
import { NonfungiblePositionManager } from '@uniswap/v3-sdk'
|
||||
import RangeBadge from 'components/Badge/RangeBadge'
|
||||
import { ButtonConfirmed, ButtonPrimary } from 'components/Button'
|
||||
@@ -109,8 +109,6 @@ function Remove({ tokenId }: { tokenId: BigNumber }) {
|
||||
!deadline ||
|
||||
!account ||
|
||||
!chainId ||
|
||||
!feeValue0 ||
|
||||
!feeValue1 ||
|
||||
!positionSDK ||
|
||||
!liquidityPercentage ||
|
||||
!library
|
||||
@@ -118,14 +116,16 @@ function Remove({ tokenId }: { tokenId: BigNumber }) {
|
||||
return
|
||||
}
|
||||
|
||||
// we fall back to expecting 0 fees in case the fetch fails, which is safe in the
|
||||
// vast majority of cases
|
||||
const { calldata, value } = NonfungiblePositionManager.removeCallParameters(positionSDK, {
|
||||
tokenId: tokenId.toString(),
|
||||
liquidityPercentage,
|
||||
slippageTolerance: allowedSlippage,
|
||||
deadline: deadline.toString(),
|
||||
collectOptions: {
|
||||
expectedCurrencyOwed0: feeValue0,
|
||||
expectedCurrencyOwed1: feeValue1,
|
||||
expectedCurrencyOwed0: feeValue0 ?? CurrencyAmount.fromRawAmount(liquidityValue0.currency, 0),
|
||||
expectedCurrencyOwed1: feeValue1 ?? CurrencyAmount.fromRawAmount(liquidityValue1.currency, 0),
|
||||
recipient: account,
|
||||
},
|
||||
})
|
||||
|
||||
110
yarn.lock
110
yarn.lock
@@ -5235,74 +5235,74 @@
|
||||
dependencies:
|
||||
"@web3-react/types" "^6.0.7"
|
||||
|
||||
"@web3-react/core@^8.0.22-beta.0":
|
||||
version "8.0.22-beta.0"
|
||||
resolved "https://registry.yarnpkg.com/@web3-react/core/-/core-8.0.22-beta.0.tgz#4a198a1ad9abe76af69d853b07a80dd2f9463b93"
|
||||
integrity sha512-WfMV6VOK+cJ6RoOMtXpcb8PCLRQAUWwZPADzY8n1tYL+5LaH/dkFoeGyAanrIYiNA/EfbvE/T8fP0QD6ooMx7g==
|
||||
"@web3-react/core@^8.0.23-beta.0":
|
||||
version "8.0.23-beta.0"
|
||||
resolved "https://registry.yarnpkg.com/@web3-react/core/-/core-8.0.23-beta.0.tgz#3a33234fbe25459ae2ce82b8bada5077386961f1"
|
||||
integrity sha512-NmVWUKSzYr+Yk0nbZdOLMtidtgXs6qOCR1DcxLF4Aa0zJ/8hRrX23siFfrVNTSpck4fgyURv8QVzE5+VHQx2HA==
|
||||
dependencies:
|
||||
"@web3-react/store" "^8.0.16-beta.0"
|
||||
"@web3-react/types" "^8.0.11-beta.0"
|
||||
zustand "^4.0.0-beta.2"
|
||||
"@web3-react/store" "^8.0.17-beta.0"
|
||||
"@web3-react/types" "^8.0.12-beta.0"
|
||||
zustand "^4.0.0-beta.3"
|
||||
optionalDependencies:
|
||||
"@ethersproject/providers" "^5"
|
||||
|
||||
"@web3-react/eip1193@^8.0.17-beta.0":
|
||||
version "8.0.17-beta.0"
|
||||
resolved "https://registry.yarnpkg.com/@web3-react/eip1193/-/eip1193-8.0.17-beta.0.tgz#84d1eb6bfbc04d4c5be19130daf9be1b3aeb962d"
|
||||
integrity sha512-PMy4UXpe/4QM2arKykyYY6iLgO6sQEmiHXuEXUxeU6r6V/G9Eq5bv6Yfo8owd9QJEMlYWgR4v8rpx8VCMqOdKw==
|
||||
dependencies:
|
||||
"@web3-react/types" "^8.0.11-beta.0"
|
||||
|
||||
"@web3-react/empty@^8.0.11-beta.0":
|
||||
version "8.0.11-beta.0"
|
||||
resolved "https://registry.yarnpkg.com/@web3-react/empty/-/empty-8.0.11-beta.0.tgz#2905654cd4e608fa3a7ccfed4e971e446c6768e8"
|
||||
integrity sha512-/zMps6bgG+17rEQS0b4HCBHqo6xuEtiJnoO9z0uPKFXrS6dJ5X85chyq6MTmZ+rieeG8O/NILn6/eEJPuCAkcg==
|
||||
dependencies:
|
||||
"@web3-react/types" "^8.0.11-beta.0"
|
||||
|
||||
"@web3-react/metamask@^8.0.18-beta.0":
|
||||
"@web3-react/eip1193@^8.0.18-beta.0":
|
||||
version "8.0.18-beta.0"
|
||||
resolved "https://registry.yarnpkg.com/@web3-react/metamask/-/metamask-8.0.18-beta.0.tgz#7ce17c1f86af7ba042e56f0ecc93f0f688c9a646"
|
||||
integrity sha512-40sWOJnYIO5u007GMqjiXvgdRAi02WCnlRcMTU2UhJpAtqALNKG14Gyd01vPMAyVYlVmsoGHr5mkFE/i75BpAA==
|
||||
resolved "https://registry.yarnpkg.com/@web3-react/eip1193/-/eip1193-8.0.18-beta.0.tgz#4c1af5c50f19d65bb221bf5d0512bcab9bd16a6f"
|
||||
integrity sha512-GsPLRP6VUw+uBhesYOrentD51gdj3yDK6oBsUKZLDwgcVaIWgmGjf0J7PTEWyOhs484ezewSaSmoitmWG19rZg==
|
||||
dependencies:
|
||||
"@web3-react/types" "^8.0.12-beta.0"
|
||||
|
||||
"@web3-react/empty@^8.0.12-beta.0":
|
||||
version "8.0.12-beta.0"
|
||||
resolved "https://registry.yarnpkg.com/@web3-react/empty/-/empty-8.0.12-beta.0.tgz#e71cb6a6085876177a8aa7bac224de2eee4c3d91"
|
||||
integrity sha512-gUOuaeOaf5brx3Qi38vPShajOsnBPXeZBDbMNBEIaWmXf5RYYcwLnjdmauLTfRcvja+8FszPuMCs2GLNQOdEag==
|
||||
dependencies:
|
||||
"@web3-react/types" "^8.0.12-beta.0"
|
||||
|
||||
"@web3-react/metamask@^8.0.19-beta.0":
|
||||
version "8.0.19-beta.0"
|
||||
resolved "https://registry.yarnpkg.com/@web3-react/metamask/-/metamask-8.0.19-beta.0.tgz#7d7c306c1245ada7dff2e1c4bdae871abb126956"
|
||||
integrity sha512-KRW+uvOnEadQEWAhX//o/OsCHuQCGuHmRUwcH8o5Q1jnjXtelTA9HmjWt8tyLQchPM7PHgrxfY1OH/Li05XTEA==
|
||||
dependencies:
|
||||
"@metamask/detect-provider" "^1.2.0"
|
||||
"@web3-react/types" "^8.0.11-beta.0"
|
||||
"@web3-react/types" "^8.0.12-beta.0"
|
||||
|
||||
"@web3-react/store@^8.0.16-beta.0":
|
||||
version "8.0.16-beta.0"
|
||||
resolved "https://registry.yarnpkg.com/@web3-react/store/-/store-8.0.16-beta.0.tgz#2e76571f6d6443173a89097825f4abe7331eaa6a"
|
||||
integrity sha512-W79ZDgGZ/pyA1CaxbsfWZ56Ud142eHJw4oP8TSwH9lmRqfPXvFeKUCACy9YpKBjmTMDRR3bQ67kEMYQjjGkY6g==
|
||||
"@web3-react/store@^8.0.17-beta.0":
|
||||
version "8.0.17-beta.0"
|
||||
resolved "https://registry.yarnpkg.com/@web3-react/store/-/store-8.0.17-beta.0.tgz#a059b6e761598e108c25dfccabb227682b32899d"
|
||||
integrity sha512-1JGkYs8HCd7ixLB5Yb7TZzz98QmCTvg0vhR+7s2m29wbp9ADsJN/EI9Avr1Kxi0UbRXTrXnEfgvl68HHmAC0GQ==
|
||||
dependencies:
|
||||
"@ethersproject/address" "^5"
|
||||
"@web3-react/types" "^8.0.11-beta.0"
|
||||
zustand "^4.0.0-beta.2"
|
||||
"@web3-react/types" "^8.0.12-beta.0"
|
||||
zustand "^4.0.0-beta.3"
|
||||
|
||||
"@web3-react/types@^6.0.7", "web3-react-types@npm:@web3-react/types@^6.0.7":
|
||||
version "6.0.7"
|
||||
resolved "https://registry.yarnpkg.com/@web3-react/types/-/types-6.0.7.tgz#34a6204224467eedc6123abaf55fbb6baeb2809f"
|
||||
integrity sha512-ofGmfDhxmNT1/P/MgVa8IKSkCStFiyvXe+U5tyZurKdrtTDFU+wJ/LxClPDtFerWpczNFPUSrKcuhfPX1sI6+A==
|
||||
|
||||
"@web3-react/types@^8.0.11-beta.0":
|
||||
version "8.0.11-beta.0"
|
||||
resolved "https://registry.yarnpkg.com/@web3-react/types/-/types-8.0.11-beta.0.tgz#9b34ff16dfc8f37f2dbdbc69b89ba7fc66429827"
|
||||
integrity sha512-KFaQn/5+1uA5+pPCGytqRK2zgdkgKJIL/KnzljkAoR3JuBkgqqAkbFlkJyiyzETAJPjwutKnl2st+ufZVPecTw==
|
||||
"@web3-react/types@^8.0.12-beta.0":
|
||||
version "8.0.12-beta.0"
|
||||
resolved "https://registry.yarnpkg.com/@web3-react/types/-/types-8.0.12-beta.0.tgz#787c7ca8b4dfd66bed0e4801fef7039ba42db59e"
|
||||
integrity sha512-BWz8RnpO0gdySjL62iURcn41ZDGxq4kokV2qbgqgASHwst5/uZ7RI2MJHTzn8vH0AUrCsg/y6E8oeTNsCAQvfg==
|
||||
dependencies:
|
||||
zustand "^4.0.0-beta.2"
|
||||
zustand "^4.0.0-beta.3"
|
||||
|
||||
"@web3-react/url@^8.0.16-beta.0":
|
||||
version "8.0.16-beta.0"
|
||||
resolved "https://registry.yarnpkg.com/@web3-react/url/-/url-8.0.16-beta.0.tgz#28b51448072c9cac6b84ea5e5a8f11560a10a829"
|
||||
integrity sha512-9Lz2/yNottir9ymkH3QmNWr/I7yRS46LHj10TGh6n3ZReEa1116kL/pTw2U7OaP285ieKq93Hev+OaP7upxQOg==
|
||||
"@web3-react/url@^8.0.17-beta.0":
|
||||
version "8.0.17-beta.0"
|
||||
resolved "https://registry.yarnpkg.com/@web3-react/url/-/url-8.0.17-beta.0.tgz#c972c0e5445902d05a5562bef3cdd3e8f0a32ea5"
|
||||
integrity sha512-03fGMa22wdabeCNrD2sIr/IePn+VoL0Bbpyz0jJrh9dUfrDEgsLrtcdmktLhfM4LCtGAye1W1FZpi/65mczSXA==
|
||||
dependencies:
|
||||
"@ethersproject/providers" "^5"
|
||||
"@web3-react/types" "^8.0.11-beta.0"
|
||||
"@web3-react/types" "^8.0.12-beta.0"
|
||||
|
||||
"@web3-react/walletconnect@^8.0.25-beta.0":
|
||||
version "8.0.25-beta.0"
|
||||
resolved "https://registry.yarnpkg.com/@web3-react/walletconnect/-/walletconnect-8.0.25-beta.0.tgz#52634ea0cd160b7a28baa3164cd0e54b9dd62dd4"
|
||||
integrity sha512-8On75QWiykvMuMuc6j+Qzb1o4CtJTJRGDNd1hFTYbj99SJJGUFgVJsvhJuHutm2Iv6NuC0wmeggb8S0iqYrahQ==
|
||||
"@web3-react/walletconnect@^8.0.26-beta.0":
|
||||
version "8.0.26-beta.0"
|
||||
resolved "https://registry.yarnpkg.com/@web3-react/walletconnect/-/walletconnect-8.0.26-beta.0.tgz#0dd903bcb90c986876e142a948c8c92d51e65072"
|
||||
integrity sha512-v7cdAmVjIxqMhH8xTuOFJOKphb089SOYHlk61GhOZCEyLcfbbc+rFuGkKiY0lFtD1/ET3cHJjRok2buXOJRn8g==
|
||||
dependencies:
|
||||
"@web3-react/types" "^8.0.11-beta.0"
|
||||
"@web3-react/types" "^8.0.12-beta.0"
|
||||
eventemitter3 "^4.0.7"
|
||||
|
||||
"@webassemblyjs/ast@1.9.0":
|
||||
@@ -19617,10 +19617,10 @@ use-sidecar@^1.0.1:
|
||||
detect-node-es "^1.1.0"
|
||||
tslib "^1.9.3"
|
||||
|
||||
use-sync-external-store@1.0.0-rc.1-next-629036a9c-20220224:
|
||||
version "1.0.0-rc.1-next-629036a9c-20220224"
|
||||
resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.0.0-rc.1-next-629036a9c-20220224.tgz#40cf472454789403c2de6c8471d177459d184dc1"
|
||||
integrity sha512-IhuMl0apVVYsT3XPfV+0nuwf0T6+3d4YxQXV4tDRsGpSQcYVG4zoWwfX4zdtouUfuelYg4t2SEmFifIMrxPfIw==
|
||||
use-sync-external-store@1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.0.0.tgz#d98f4a9c2e73d0f958e7e2d2c2bfb5f618cbd8fd"
|
||||
integrity sha512-AFVsxg5GkFg8GDcxnl+Z0lMAz9rE8DGJCc28qnBuQF7lac57B5smLcT37aXpXIIPz75rW4g3eXHPjhHwdGskOw==
|
||||
|
||||
use@^3.1.0:
|
||||
version "3.1.1"
|
||||
@@ -20707,9 +20707,9 @@ yocto-queue@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-1.0.0.tgz#7f816433fb2cbc511ec8bf7d263c3b58a1a3c251"
|
||||
integrity sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==
|
||||
|
||||
zustand@^4.0.0-beta.2:
|
||||
version "4.0.0-beta.2"
|
||||
resolved "https://registry.yarnpkg.com/zustand/-/zustand-4.0.0-beta.2.tgz#27fdc32b62225cc18976c0cf8866ecee9a9f4a98"
|
||||
integrity sha512-aJ5ypnOwPIa/uSjdZv/oHChTWPplpFOG/hvWwzkR5ahFiPI5R6ifyObf8Fz1Vi6Obz2wY1N32fT2pNrpT2hzPw==
|
||||
zustand@^4.0.0-beta.3:
|
||||
version "4.0.0-beta.3"
|
||||
resolved "https://registry.yarnpkg.com/zustand/-/zustand-4.0.0-beta.3.tgz#16dc82b48b65ed61fe2bae5dea4501f49bd450c7"
|
||||
integrity sha512-cVDcspaK0CXgVmGcXB/oenhT7EFaKqD46pTmg30ciMsOoQN0ZuxEuHzpNIy9ejah0gzBL8aqHN89IMT2uFNOaA==
|
||||
dependencies:
|
||||
use-sync-external-store "1.0.0-rc.1-next-629036a9c-20220224"
|
||||
use-sync-external-store "1.0.0"
|
||||
|
||||
Reference in New Issue
Block a user