Commit 850a20f6 authored by Zach Pomerantz's avatar Zach Pomerantz Committed by GitHub

feat: include native currency in widget select (#3124)

* fix: token image for chains / natives

* feat: include native currency in select

- Updates widgets swap state to use Currency (and deals with downstream updates)
- Refactors logoURI code to a new lib/hooks/useCurrencyLogoURIs
- Adds native currency to useQueryTokenList

NB: This does not build because tests must be updated to use Currency (they currently use mock tokens)

* test: update fixtures to use real currency

* fix: data uri color extraction

* fix: token img state

* fix: use new array
parent 99f68181
// eslint-disable-next-line @typescript-eslint/no-var-requires /* eslint-disable @typescript-eslint/no-var-requires */
const HtmlWebpackPlugin = require('html-webpack-plugin') const HtmlWebpackPlugin = require('html-webpack-plugin')
const { DefinePlugin } = require('webpack')
// Renders the cosmos fixtures in isolation, instead of using public/index.html. // Renders the cosmos fixtures in isolation, instead of using public/index.html.
module.exports = (webpackConfig) => ({ module.exports = (webpackConfig) => ({
...webpackConfig, ...webpackConfig,
plugins: webpackConfig.plugins.map((plugin) => plugins: webpackConfig.plugins.map((plugin) => {
plugin instanceof HtmlWebpackPlugin if (plugin instanceof HtmlWebpackPlugin) {
? new HtmlWebpackPlugin({ return new HtmlWebpackPlugin({
templateContent: '<body></body>', templateContent: '<body></body>',
}) })
: plugin }
), if (plugin instanceof DefinePlugin) {
return new DefinePlugin({
...plugin.definitions,
'process.env': {
...plugin.definitions['process.env'],
REACT_APP_IS_WIDGET: true,
},
})
}
return plugin
}),
}) })
...@@ -56,6 +56,7 @@ ...@@ -56,6 +56,7 @@
"@types/wcag-contrast": "^3.0.0", "@types/wcag-contrast": "^3.0.0",
"@typescript-eslint/eslint-plugin": "^4.1.0", "@typescript-eslint/eslint-plugin": "^4.1.0",
"@typescript-eslint/parser": "^4.1.0", "@typescript-eslint/parser": "^4.1.0",
"@uniswap/default-token-list": "^3.0.0",
"@uniswap/governance": "^1.0.2", "@uniswap/governance": "^1.0.2",
"@uniswap/liquidity-staker": "^1.0.2", "@uniswap/liquidity-staker": "^1.0.2",
"@uniswap/merkle-distributor": "1.0.1", "@uniswap/merkle-distributor": "1.0.1",
...@@ -94,7 +95,7 @@ ...@@ -94,7 +95,7 @@
"prettier": "^2.2.1", "prettier": "^2.2.1",
"qs": "^6.9.4", "qs": "^6.9.4",
"react-confetti": "^6.0.0", "react-confetti": "^6.0.0",
"react-cosmos": "^5.6.3", "react-cosmos": "^5.6.6",
"react-ga": "^2.5.7", "react-ga": "^2.5.7",
"react-is": "^17.0.2", "react-is": "^17.0.2",
"react-markdown": "^4.3.1", "react-markdown": "^4.3.1",
...@@ -138,7 +139,7 @@ ...@@ -138,7 +139,7 @@
"build": "react-scripts build", "build": "react-scripts build",
"test": "react-scripts test --env=./custom-test-env.js", "test": "react-scripts test --env=./custom-test-env.js",
"test:e2e": "start-server-and-test 'serve build -l 3000' http://localhost:3000 'cypress run --record'", "test:e2e": "start-server-and-test 'serve build -l 3000' http://localhost:3000 'cypress run --record'",
"widgets:start": "cross-env FAST_REFRESH=false REACT_APP_IS_WIDGET=true cosmos", "widgets:start": "cosmos",
"widgets:build": "rollup --config --failAfterWarnings --configPlugin typescript2" "widgets:build": "rollup --config --failAfterWarnings --configPlugin typescript2"
}, },
"browserslist": { "browserslist": {
......
import { Currency } from '@uniswap/sdk-core' import { Currency } from '@uniswap/sdk-core'
import EthereumLogo from 'assets/images/ethereum-logo.png' import useCurrencyLogoURIs from 'lib/hooks/useCurrencyLogoURIs'
import MaticLogo from 'assets/svg/matic-token-icon.svg' import React from 'react'
import { SupportedChainId } from 'constants/chains'
import useHttpLocations from 'hooks/useHttpLocations'
import React, { useMemo } from 'react'
import { WrappedTokenInfo } from 'state/lists/wrappedTokenInfo'
import styled from 'styled-components/macro' import styled from 'styled-components/macro'
import Logo from '../Logo' import Logo from '../Logo'
type Network = 'ethereum' | 'arbitrum' | 'optimism' const StyledLogo = styled(Logo)<{ size: string; native: boolean }>`
function chainIdToNetworkName(networkId: SupportedChainId): Network {
switch (networkId) {
case SupportedChainId.MAINNET:
return 'ethereum'
case SupportedChainId.ARBITRUM_ONE:
return 'arbitrum'
case SupportedChainId.OPTIMISM:
return 'optimism'
default:
return 'ethereum'
}
}
export const getTokenLogoURL = (
address: string,
chainId: SupportedChainId = SupportedChainId.MAINNET
): string | void => {
const networkName = chainIdToNetworkName(chainId)
const networksWithUrls = [SupportedChainId.ARBITRUM_ONE, SupportedChainId.MAINNET, SupportedChainId.OPTIMISM]
if (networksWithUrls.includes(chainId)) {
return `https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/${networkName}/assets/${address}/logo.png`
}
}
const StyledNativeLogo = styled.img<{ size: string }>`
width: ${({ size }) => size}; width: ${({ size }) => size};
height: ${({ size }) => size}; height: ${({ size }) => size};
background: radial-gradient(white 50%, #ffffff00 calc(75% + 1px), #ffffff00 100%); background: radial-gradient(white 50%, #ffffff00 calc(75% + 1px), #ffffff00 100%);
border-radius: 50%; border-radius: 50%;
-mox-box-shadow: 0 0 1px white; -mox-box-shadow: 0 0 1px ${({ native }) => (native ? 'white' : 'black')};
-webkit-box-shadow: 0 0 1px white; -webkit-box-shadow: 0 0 1px ${({ native }) => (native ? 'white' : 'black')};
box-shadow: 0 0 1px white; box-shadow: 0 0 1px ${({ native }) => (native ? 'white' : 'black')};
border: 0px solid rgba(255, 255, 255, 0);
`
const StyledLogo = styled(Logo)<{ size: string }>`
width: ${({ size }) => size};
height: ${({ size }) => size};
background: radial-gradient(white 50%, #ffffff00 calc(75% + 1px), #ffffff00 100%);
border-radius: 50%;
-mox-box-shadow: 0 0 1px black;
-webkit-box-shadow: 0 0 1px black;
box-shadow: 0 0 1px black;
border: 0px solid rgba(255, 255, 255, 0); border: 0px solid rgba(255, 255, 255, 0);
` `
...@@ -68,38 +26,16 @@ export default function CurrencyLogo({ ...@@ -68,38 +26,16 @@ export default function CurrencyLogo({
size?: string size?: string
style?: React.CSSProperties style?: React.CSSProperties
}) { }) {
const uriLocations = useHttpLocations(currency instanceof WrappedTokenInfo ? currency.logoURI : undefined) const logoURIs = useCurrencyLogoURIs(currency)
const srcs: string[] = useMemo(() => { return (
if (!currency || currency.isNative) return [] <StyledLogo
size={size}
if (currency.isToken) { native={currency?.isNative ?? false}
const defaultUrls = [] srcs={logoURIs}
const url = getTokenLogoURL(currency.address, currency.chainId) alt={`${currency?.symbol ?? 'token'} logo`}
if (url) { style={style}
defaultUrls.push(url) {...rest}
} />
if (currency instanceof WrappedTokenInfo) { )
return [...uriLocations, ...defaultUrls]
}
return defaultUrls
}
return []
}, [currency, uriLocations])
if (currency?.isNative) {
let nativeLogoUrl: string
switch (currency.chainId) {
case SupportedChainId.POLYGON_MUMBAI:
case SupportedChainId.POLYGON:
nativeLogoUrl = MaticLogo
break
default:
nativeLogoUrl = EthereumLogo
break
}
return <StyledNativeLogo src={nativeLogoUrl} alt="ethereum logo" size={size} style={style} {...rest} />
}
return <StyledLogo size={size} srcs={srcs} alt={`${currency?.symbol ?? 'token'} logo`} style={style} {...rest} />
} }
import { Currency, Token } from '@uniswap/sdk-core' import { Currency, Token } from '@uniswap/sdk-core'
import useActiveWeb3React from 'hooks/useActiveWeb3React' import useActiveWeb3React from 'hooks/useActiveWeb3React'
import useCurrencyLogoURIs from 'lib/hooks/useCurrencyLogoURIs'
import { useCallback, useState } from 'react' import { useCallback, useState } from 'react'
import { getTokenLogoURL } from './../components/CurrencyLogo/index'
export default function useAddTokenToMetamask(currencyToAdd: Currency | undefined): { export default function useAddTokenToMetamask(currencyToAdd: Currency | undefined): {
addToken: () => void addToken: () => void
success: boolean | undefined success: boolean | undefined
...@@ -13,6 +12,7 @@ export default function useAddTokenToMetamask(currencyToAdd: Currency | undefine ...@@ -13,6 +12,7 @@ export default function useAddTokenToMetamask(currencyToAdd: Currency | undefine
const token: Token | undefined = currencyToAdd?.wrapped const token: Token | undefined = currencyToAdd?.wrapped
const [success, setSuccess] = useState<boolean | undefined>() const [success, setSuccess] = useState<boolean | undefined>()
const logoURL = useCurrencyLogoURIs(token)[0]
const addToken = useCallback(() => { const addToken = useCallback(() => {
if (library && library.provider.isMetaMask && library.provider.request && token) { if (library && library.provider.isMetaMask && library.provider.request && token) {
...@@ -26,7 +26,7 @@ export default function useAddTokenToMetamask(currencyToAdd: Currency | undefine ...@@ -26,7 +26,7 @@ export default function useAddTokenToMetamask(currencyToAdd: Currency | undefine
address: token.address, address: token.address,
symbol: token.symbol, symbol: token.symbol,
decimals: token.decimals, decimals: token.decimals,
image: getTokenLogoURL(token.address), image: logoURL,
}, },
}, },
}) })
...@@ -37,7 +37,7 @@ export default function useAddTokenToMetamask(currencyToAdd: Currency | undefine ...@@ -37,7 +37,7 @@ export default function useAddTokenToMetamask(currencyToAdd: Currency | undefine
} else { } else {
setSuccess(false) setSuccess(false)
} }
}, [library, token]) }, [library, logoURL, token])
return { addToken, success } return { addToken, success }
} }
...@@ -12,7 +12,6 @@ const Column = styled.div<{ ...@@ -12,7 +12,6 @@ const Column = styled.div<{
css?: ReturnType<typeof css> css?: ReturnType<typeof css>
}>` }>`
align-items: ${({ align }) => align ?? 'center'}; align-items: ${({ align }) => align ?? 'center'};
background-color: inherit;
color: ${({ color, theme }) => color && theme[color]}; color: ${({ color, theme }) => color && theme[color]};
display: ${({ flex }) => (flex ? 'flex' : 'grid')}; display: ${({ flex }) => (flex ? 'flex' : 'grid')};
flex-direction: column; flex-direction: column;
......
import { Trans } from '@lingui/macro' import { Trans } from '@lingui/macro'
import { Currency } from '@uniswap/sdk-core'
import { AlertTriangle, ArrowRight, CheckCircle, Spinner, Trash2 } from 'lib/icons' import { AlertTriangle, ArrowRight, CheckCircle, Spinner, Trash2 } from 'lib/icons'
import { DAI, ETH, UNI, USDC } from 'lib/mocks'
import styled, { ThemedText } from 'lib/theme' import styled, { ThemedText } from 'lib/theme'
import { Token } from 'lib/types'
import { useMemo, useState } from 'react' import { useMemo, useState } from 'react'
import Button from './Button' import Button from './Button'
...@@ -13,7 +12,7 @@ import TokenImg from './TokenImg' ...@@ -13,7 +12,7 @@ import TokenImg from './TokenImg'
interface ITokenAmount { interface ITokenAmount {
value: number value: number
token: Token token: Currency
} }
export enum TransactionStatus { export enum TransactionStatus {
...@@ -28,25 +27,6 @@ interface ITransaction { ...@@ -28,25 +27,6 @@ interface ITransaction {
status: TransactionStatus status: TransactionStatus
} }
// TODO: integrate with web3-react context
export const mockTxs: ITransaction[] = [
{
input: { value: 4170.15, token: USDC },
output: { value: 4167.44, token: DAI },
status: TransactionStatus.SUCCESS,
},
{
input: { value: 1.23, token: ETH },
output: { value: 4125.02, token: DAI },
status: TransactionStatus.PENDING,
},
{
input: { value: 10, token: UNI },
output: { value: 2125.02, token: ETH },
status: TransactionStatus.ERROR,
},
]
const TransactionRow = styled(Row)` const TransactionRow = styled(Row)`
padding: 0.5em 1em; padding: 0.5em 1em;
...@@ -94,7 +74,7 @@ function Transaction({ tx }: { tx: ITransaction }) { ...@@ -94,7 +74,7 @@ function Transaction({ tx }: { tx: ITransaction }) {
} }
export default function RecentTransactionsDialog() { export default function RecentTransactionsDialog() {
const [txs, setTxs] = useState(mockTxs) const [txs, setTxs] = useState([])
return ( return (
<> <>
......
import { Trans } from '@lingui/macro' import { Trans } from '@lingui/macro'
import { atom } from 'jotai' import { atom } from 'jotai'
import { useAtomValue } from 'jotai/utils' import { useAtomValue } from 'jotai/utils'
import useColor, { usePrefetchColor } from 'lib/hooks/useColor' import useCurrencyColor, { usePrefetchCurrencyColor } from 'lib/hooks/useCurrencyColor'
import { inputAtom, outputAtom, useUpdateInputToken, useUpdateInputValue } from 'lib/state/swap' import { inputAtom, outputAtom, useUpdateInputToken, useUpdateInputValue } from 'lib/state/swap'
import styled, { DynamicThemeProvider, ThemedText } from 'lib/theme' import styled, { DynamicThemeProvider, ThemedText } from 'lib/theme'
import { ReactNode, useMemo } from 'react' import { ReactNode, useMemo } from 'react'
...@@ -40,8 +40,8 @@ export default function Output({ disabled, children }: OutputProps) { ...@@ -40,8 +40,8 @@ export default function Output({ disabled, children }: OutputProps) {
const balance = 123.45 const balance = 123.45
const overrideColor = useAtomValue(colorAtom) const overrideColor = useAtomValue(colorAtom)
const dynamicColor = useColor(output.token) const dynamicColor = useCurrencyColor(output.token)
usePrefetchColor(input.token) // extract eagerly in case of reversal usePrefetchCurrencyColor(input.token) // extract eagerly in case of reversal
const color = overrideColor || dynamicColor const color = overrideColor || dynamicColor
const hasColor = output.token ? Boolean(color) || null : false const hasColor = output.token ? Boolean(color) || null : false
......
import { tokens } from '@uniswap/default-token-list'
import { SupportedChainId } from 'constants/chains'
import { nativeOnChain } from 'constants/tokens'
import { useUpdateAtom } from 'jotai/utils' import { useUpdateAtom } from 'jotai/utils'
import { DAI, ETH } from 'lib/mocks'
import { transactionAtom } from 'lib/state/swap' import { transactionAtom } from 'lib/state/swap'
import { useEffect } from 'react' import { useEffect } from 'react'
import { useSelect } from 'react-cosmos/fixture' import { useSelect } from 'react-cosmos/fixture'
import { WrappedTokenInfo } from 'state/lists/wrappedTokenInfo'
import invariant from 'tiny-invariant' import invariant from 'tiny-invariant'
import { Modal } from '../Dialog' import { Modal } from '../Dialog'
import { StatusDialog } from './Status' import { StatusDialog } from './Status'
const ETH = nativeOnChain(SupportedChainId.MAINNET)
const UNI = (function () {
const token = tokens.find(({ symbol }) => symbol === 'UNI')
invariant(token)
return new WrappedTokenInfo(token)
})()
function Fixture() { function Fixture() {
const setTransaction = useUpdateAtom(transactionAtom) const setTransaction = useUpdateAtom(transactionAtom)
...@@ -17,7 +27,7 @@ function Fixture() { ...@@ -17,7 +27,7 @@ function Fixture() {
useEffect(() => { useEffect(() => {
setTransaction({ setTransaction({
input: { token: ETH, value: 1 }, input: { token: ETH, value: 1 },
output: { token: DAI, value: 4200 }, output: { token: UNI, value: 42 },
receipt: '', receipt: '',
timestamp: Date.now(), timestamp: Date.now(),
}) })
...@@ -27,7 +37,7 @@ function Fixture() { ...@@ -27,7 +37,7 @@ function Fixture() {
case 'PENDING': case 'PENDING':
setTransaction({ setTransaction({
input: { token: ETH, value: 1 }, input: { token: ETH, value: 1 },
output: { token: DAI, value: 4200 }, output: { token: UNI, value: 42 },
receipt: '', receipt: '',
timestamp: Date.now(), timestamp: Date.now(),
}) })
......
import { tokens } from '@uniswap/default-token-list'
import { SupportedChainId } from 'constants/chains'
import { nativeOnChain } from 'constants/tokens'
import { useUpdateAtom } from 'jotai/utils' import { useUpdateAtom } from 'jotai/utils'
import { DAI, ETH } from 'lib/mocks'
import { Field, outputAtom, stateAtom } from 'lib/state/swap' import { Field, outputAtom, stateAtom } from 'lib/state/swap'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { useValue } from 'react-cosmos/fixture' import { useValue } from 'react-cosmos/fixture'
import { WrappedTokenInfo } from 'state/lists/wrappedTokenInfo'
import invariant from 'tiny-invariant'
import { Modal } from '../Dialog' import { Modal } from '../Dialog'
import { SummaryDialog } from './Summary' import { SummaryDialog } from './Summary'
const ETH = nativeOnChain(SupportedChainId.MAINNET)
const UNI = (function () {
const token = tokens.find(({ symbol }) => symbol === 'UNI')
invariant(token)
return new WrappedTokenInfo(token)
})()
function Fixture() { function Fixture() {
const setState = useUpdateAtom(stateAtom) const setState = useUpdateAtom(stateAtom)
const [, setInitialized] = useState(false) const [, setInitialized] = useState(false)
...@@ -15,7 +26,7 @@ function Fixture() { ...@@ -15,7 +26,7 @@ function Fixture() {
setState({ setState({
activeInput: Field.INPUT, activeInput: Field.INPUT,
input: { token: ETH, value: 1, usdc: 4195 }, input: { token: ETH, value: 1, usdc: 4195 },
output: { token: DAI, value: 4200, usdc: 4200 }, output: { token: UNI, value: 42, usdc: 42 },
swap: { swap: {
lpFee: 0.0005, lpFee: 0.0005,
integratorFee: 0.00025, integratorFee: 0.00025,
...@@ -30,7 +41,7 @@ function Fixture() { ...@@ -30,7 +41,7 @@ function Fixture() {
const setOutput = useUpdateAtom(outputAtom) const setOutput = useUpdateAtom(outputAtom)
const [price] = useValue('output value', { defaultValue: 4200 }) const [price] = useValue('output value', { defaultValue: 4200 })
useEffect(() => { useEffect(() => {
setState((state) => ({ ...state, output: { token: DAI, value: price, usdc: price } })) setState((state) => ({ ...state, output: { token: UNI, value: price, usdc: price } }))
}, [price, setOutput, setState]) }, [price, setOutput, setState])
return ( return (
......
import { t } from '@lingui/macro' import { t } from '@lingui/macro'
import { Currency } from '@uniswap/sdk-core'
import { State } from 'lib/state/swap' import { State } from 'lib/state/swap'
import { ThemedText } from 'lib/theme' import { ThemedText } from 'lib/theme'
import { Token } from 'lib/types'
import { useMemo } from 'react' import { useMemo } from 'react'
import Row from '../../Row' import Row from '../../Row'
...@@ -24,8 +24,8 @@ function Detail({ label, value }: DetailProps) { ...@@ -24,8 +24,8 @@ function Detail({ label, value }: DetailProps) {
interface DetailsProps { interface DetailsProps {
swap: Required<State>['swap'] swap: Required<State>['swap']
input: Token input: Currency
output: Token output: Currency
} }
export default function Details({ export default function Details({
......
import { tokens } from '@uniswap/default-token-list'
import { useAtom } from 'jotai' import { useAtom } from 'jotai'
import { useUpdateAtom } from 'jotai/utils' import { useUpdateAtom } from 'jotai/utils'
import { inputAtom, outputAtom, swapAtom } from 'lib/state/swap' import { inputAtom, outputAtom, swapAtom } from 'lib/state/swap'
...@@ -61,7 +62,7 @@ function Fixture() { ...@@ -61,7 +62,7 @@ function Fixture() {
} }
}, [color, setColor]) }, [color, setColor])
return <Swap /> return <Swap defaults={{ tokenList: tokens }} />
} }
export default <Fixture /> export default <Fixture />
import { Trans } from '@lingui/macro' import { Trans } from '@lingui/macro'
import { Currency } from '@uniswap/sdk-core'
import { Input } from 'lib/state/swap' import { Input } from 'lib/state/swap'
import styled, { keyframes, ThemedText } from 'lib/theme' import styled, { keyframes, ThemedText } from 'lib/theme'
import { Token } from 'lib/types'
import { FocusEvent, ReactNode, useCallback, useRef, useState } from 'react' import { FocusEvent, ReactNode, useCallback, useRef, useState } from 'react'
import Button from '../Button' import Button from '../Button'
...@@ -49,7 +49,7 @@ interface TokenInputProps { ...@@ -49,7 +49,7 @@ interface TokenInputProps {
disabled?: boolean disabled?: boolean
onMax?: () => void onMax?: () => void
onChangeInput: (input: number | undefined) => void onChangeInput: (input: number | undefined) => void
onChangeToken: (token: Token) => void onChangeToken: (token: Currency) => void
children: ReactNode children: ReactNode
} }
......
import useNativeEvent from 'lib/hooks/useNativeEvent' import { Currency } from '@uniswap/sdk-core'
import useCurrencyLogoURIs from 'lib/hooks/useCurrencyLogoURIs'
import { Slash } from 'lib/icons'
import styled from 'lib/theme' import styled from 'lib/theme'
import uriToHttp from 'lib/utils/uriToHttp' import { useCallback, useEffect, useState } from 'react'
import { useState } from 'react'
const badSrcs = new Set<string>()
interface TokenImgProps { interface TokenImgProps {
className?: string className?: string
token: { token: Currency
name?: string
symbol: string
logoURI?: string
}
} }
const TRANSPARENT_SRC = 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=='
function TokenImg({ className, token }: TokenImgProps) { function TokenImg({ className, token }: TokenImgProps) {
const [img, setImg] = useState<HTMLImageElement | null>(null) const srcs = useCurrencyLogoURIs(token)
const src = token.logoURI ? uriToHttp(token.logoURI)[0] : TRANSPARENT_SRC const [src, setSrc] = useState<string | undefined>()
useNativeEvent(img, 'error', () => { useEffect(() => {
if (img) { setSrc(srcs.find((src) => !badSrcs.has(src)))
// Use a local transparent gif to avoid the browser-dependent broken img icon. }, [srcs])
// The icon may still flash, but using a native event further reduces the duration. const onError = useCallback(() => {
img.src = TRANSPARENT_SRC if (src) badSrcs.add(src)
} setSrc(srcs.find((src) => !badSrcs.has(src)))
}) }, [src, srcs])
return <img className={className} src={src} alt={token.name || token.symbol} ref={setImg} />
if (src) {
return <img className={className} src={src} alt={token.name || token.symbol} onError={onError} />
}
return <Slash className={className} color="secondary" />
} }
export default styled(TokenImg)<{ size?: number }>` export default styled(TokenImg)<{ size?: number }>`
......
import { Currency } from '@uniswap/sdk-core'
import styled, { ThemedText } from 'lib/theme' import styled, { ThemedText } from 'lib/theme'
import { Token } from 'lib/types'
import Button from '../Button' import Button from '../Button'
import Row from '../Row' import Row from '../Row'
...@@ -11,8 +11,8 @@ const TokenButton = styled(Button)` ...@@ -11,8 +11,8 @@ const TokenButton = styled(Button)`
` `
interface TokenBaseProps { interface TokenBaseProps {
value: Token value: Currency
onClick: (value: Token) => void onClick: (value: Currency) => void
} }
export default function TokenBase({ value, onClick }: TokenBaseProps) { export default function TokenBase({ value, onClick }: TokenBaseProps) {
......
import { Trans } from '@lingui/macro' import { Trans } from '@lingui/macro'
import { Currency } from '@uniswap/sdk-core'
import { ChevronDown } from 'lib/icons' import { ChevronDown } from 'lib/icons'
import styled, { ThemedText } from 'lib/theme' import styled, { ThemedText } from 'lib/theme'
import { Token } from 'lib/types'
import Button from '../Button' import Button from '../Button'
import Row from '../Row' import Row from '../Row'
...@@ -27,7 +27,7 @@ const TokenButtonRow = styled(Row)<{ collapsed: boolean }>` ...@@ -27,7 +27,7 @@ const TokenButtonRow = styled(Row)<{ collapsed: boolean }>`
` `
interface TokenButtonProps { interface TokenButtonProps {
value?: Token value?: Currency
collapsed: boolean collapsed: boolean
disabled?: boolean disabled?: boolean
onClick: () => void onClick: () => void
......
import { Currency } from '@uniswap/sdk-core'
import useActiveWeb3React from 'lib/hooks/useActiveWeb3React' import useActiveWeb3React from 'lib/hooks/useActiveWeb3React'
import useCurrencyBalance from 'lib/hooks/useCurrencyBalance' import useCurrencyBalance from 'lib/hooks/useCurrencyBalance'
import useNativeEvent from 'lib/hooks/useNativeEvent' import useNativeEvent from 'lib/hooks/useNativeEvent'
...@@ -18,7 +19,6 @@ import { ...@@ -18,7 +19,6 @@ import {
} from 'react' } from 'react'
import AutoSizer from 'react-virtualized-auto-sizer' import AutoSizer from 'react-virtualized-auto-sizer'
import { areEqual, FixedSizeList, FixedSizeListProps } from 'react-window' import { areEqual, FixedSizeList, FixedSizeListProps } from 'react-window'
import { WrappedTokenInfo } from 'state/lists/wrappedTokenInfo'
import invariant from 'tiny-invariant' import invariant from 'tiny-invariant'
import { BaseButton } from '../Button' import { BaseButton } from '../Button'
...@@ -33,7 +33,7 @@ const TokenButton = styled(BaseButton)` ...@@ -33,7 +33,7 @@ const TokenButton = styled(BaseButton)`
` `
const ITEM_SIZE = 56 const ITEM_SIZE = 56
type ItemData = WrappedTokenInfo[] type ItemData = Currency[]
interface FixedSizeTokenList extends FixedSizeList<ItemData>, ComponentClass<FixedSizeListProps<ItemData>> {} interface FixedSizeTokenList extends FixedSizeList<ItemData>, ComponentClass<FixedSizeListProps<ItemData>> {}
const TokenList = styled(FixedSizeList as unknown as FixedSizeTokenList)<{ const TokenList = styled(FixedSizeList as unknown as FixedSizeTokenList)<{
hover: number hover: number
...@@ -57,13 +57,13 @@ const OnHover = styled.div<{ hover: number }>` ...@@ -57,13 +57,13 @@ const OnHover = styled.div<{ hover: number }>`
interface TokenOptionProps { interface TokenOptionProps {
index: number index: number
value: WrappedTokenInfo value: Currency
style: CSSProperties style: CSSProperties
} }
interface BubbledEvent extends SyntheticEvent { interface BubbledEvent extends SyntheticEvent {
index?: number index?: number
token?: WrappedTokenInfo token?: Currency
ref?: HTMLButtonElement ref?: HTMLButtonElement
} }
...@@ -107,7 +107,10 @@ function TokenOption({ index, value, style }: TokenOptionProps) { ...@@ -107,7 +107,10 @@ function TokenOption({ index, value, style }: TokenOptionProps) {
) )
} }
const itemKey = (index: number, tokens: ItemData) => tokens[index]?.address const itemKey = (index: number, tokens: ItemData) => {
if (tokens[index].isNative) return 'native'
return tokens[index].wrapped.address
}
const ItemRow = memo(function ItemRow({ const ItemRow = memo(function ItemRow({
data: tokens, data: tokens,
index, index,
...@@ -127,8 +130,8 @@ interface TokenOptionsHandle { ...@@ -127,8 +130,8 @@ interface TokenOptionsHandle {
} }
interface TokenOptionsProps { interface TokenOptionsProps {
tokens: WrappedTokenInfo[] tokens: Currency[]
onSelect: (token: WrappedTokenInfo) => void onSelect: (token: Currency) => void
} }
const TokenOptions = forwardRef<TokenOptionsHandle, TokenOptionsProps>(function TokenOptions( const TokenOptions = forwardRef<TokenOptionsHandle, TokenOptionsProps>(function TokenOptions(
......
import { t, Trans } from '@lingui/macro' import { t, Trans } from '@lingui/macro'
import { Currency } from '@uniswap/sdk-core'
import { useQueryTokenList } from 'lib/hooks/useTokenList' import { useQueryTokenList } from 'lib/hooks/useTokenList'
import styled, { ThemedText } from 'lib/theme' import styled, { ThemedText } from 'lib/theme'
import { Token } from 'lib/types'
import { ElementRef, useCallback, useEffect, useRef, useState } from 'react' import { ElementRef, useCallback, useEffect, useRef, useState } from 'react'
import Column from '../Column' import Column from '../Column'
...@@ -17,11 +17,11 @@ const SearchInput = styled(StringInput)` ...@@ -17,11 +17,11 @@ const SearchInput = styled(StringInput)`
${inputCss} ${inputCss}
` `
export function TokenSelectDialog({ onSelect }: { onSelect: (token: Token) => void }) { export function TokenSelectDialog({ onSelect }: { onSelect: (token: Currency) => void }) {
const [query, setQuery] = useState('') const [query, setQuery] = useState('')
const tokens = useQueryTokenList(query) const tokens = useQueryTokenList(query)
const baseTokens: Token[] = [] // TODO(zzmp): Add base tokens to token list functionality const baseTokens: Currency[] = [] // TODO(zzmp): Add base tokens to token list functionality
// TODO(zzmp): Disable already selected tokens (passed as props?) // TODO(zzmp): Disable already selected tokens (passed as props?)
...@@ -49,7 +49,7 @@ export function TokenSelectDialog({ onSelect }: { onSelect: (token: Token) => vo ...@@ -49,7 +49,7 @@ export function TokenSelectDialog({ onSelect }: { onSelect: (token: Token) => vo
{Boolean(baseTokens.length) && ( {Boolean(baseTokens.length) && (
<Row pad={0.75} gap={0.25} justify="flex-start" flex> <Row pad={0.75} gap={0.25} justify="flex-start" flex>
{baseTokens.map((token) => ( {baseTokens.map((token) => (
<TokenBase value={token} onClick={onSelect} key={token.address} /> <TokenBase value={token} onClick={onSelect} key={token.wrapped.address} />
))} ))}
</Row> </Row>
)} )}
...@@ -61,16 +61,16 @@ export function TokenSelectDialog({ onSelect }: { onSelect: (token: Token) => vo ...@@ -61,16 +61,16 @@ export function TokenSelectDialog({ onSelect }: { onSelect: (token: Token) => vo
} }
interface TokenSelectProps { interface TokenSelectProps {
value?: Token value?: Currency
collapsed: boolean collapsed: boolean
disabled?: boolean disabled?: boolean
onSelect: (value: Token) => void onSelect: (value: Currency) => void
} }
export default function TokenSelect({ value, collapsed, disabled, onSelect }: TokenSelectProps) { export default function TokenSelect({ value, collapsed, disabled, onSelect }: TokenSelectProps) {
const [open, setOpen] = useState(false) const [open, setOpen] = useState(false)
const selectAndClose = useCallback( const selectAndClose = useCallback(
(value: Token) => { (value: Currency) => {
onSelect(value) onSelect(value)
setOpen(false) setOpen(false)
}, },
......
import { Currency } from '@uniswap/sdk-core'
import { useTheme } from 'lib/theme' import { useTheme } from 'lib/theme'
import { Token } from 'lib/types'
import uriToHttp from 'lib/utils/uriToHttp'
import Vibrant from 'node-vibrant/lib/bundle' import Vibrant from 'node-vibrant/lib/bundle'
import { useLayoutEffect, useState } from 'react' import { useEffect, useLayoutEffect, useState } from 'react'
const colors = new Map<string, string | undefined>() import useCurrencyLogoURIs from './useCurrencyLogoURIs'
function UriForEthToken(address: string) { const colors = new Map<string, string | undefined>()
return `https://raw.githubusercontent.com/uniswap/assets/master/blockchains/ethereum/assets/${address}/logo.png?color`
}
/** /**
* Extracts the prominent color from a token. * Extracts the prominent color from a token.
* NB: If cached, this function returns synchronously; using a callback allows sync or async returns. * NB: If cached, this function returns synchronously; using a callback allows sync or async returns.
*/ */
async function getColorFromToken(token: Token, cb: (color: string | undefined) => void = () => void 0) { async function getColorFromLogoURIs(logoURIs: string[], cb: (color: string | undefined) => void = () => void 0) {
const { address, chainId, logoURI } = token const key = logoURIs[0]
const key = chainId + address
let color = colors.get(key) let color = colors.get(key)
if (!color && logoURI) { if (!color) {
// Color extraction must use a CORS-compatible resource, but the resource is already cached. for (const logoURI of logoURIs) {
// Add a dummy parameter to force a different browser resource cache entry. let uri = logoURI
// Without this, color extraction prevents resource caching. if (logoURI.startsWith('http')) {
const uri = uriToHttp(logoURI)[0] + '?color' // Color extraction must use a CORS-compatible resource, but the resource may already be cached.
color = await getColorFromUriPath(uri) // Adds a dummy parameter to force a different browser resource cache entry. Without this, color extraction prevents resource caching.
} uri += '?color'
}
if (!color && chainId === 1) { color = await getColorFromUriPath(uri)
const fallbackUri = UriForEthToken(address) if (color) break
color = await getColorFromUriPath(fallbackUri) }
} }
colors.set(key, color) colors.set(key, color)
...@@ -44,23 +41,27 @@ async function getColorFromUriPath(uri: string): Promise<string | undefined> { ...@@ -44,23 +41,27 @@ async function getColorFromUriPath(uri: string): Promise<string | undefined> {
return return
} }
export function usePrefetchColor(token?: Token) { export function usePrefetchCurrencyColor(token?: Currency) {
const theme = useTheme() const theme = useTheme()
const logoURIs = useCurrencyLogoURIs(token)
if (theme.tokenColorExtraction && token) { useEffect(() => {
getColorFromToken(token) if (theme.tokenColorExtraction && token) {
} getColorFromLogoURIs(logoURIs)
}
}, [token, logoURIs, theme.tokenColorExtraction])
} }
export default function useColor(token?: Token) { export default function useCurrencyColor(token?: Currency) {
const [color, setColor] = useState<string | undefined>(undefined) const [color, setColor] = useState<string | undefined>(undefined)
const theme = useTheme() const theme = useTheme()
const logoURIs = useCurrencyLogoURIs(token)
useLayoutEffect(() => { useLayoutEffect(() => {
let stale = false let stale = false
if (theme.tokenColorExtraction && token) { if (theme.tokenColorExtraction && token) {
getColorFromToken(token, (color) => { getColorFromLogoURIs(logoURIs, (color) => {
if (!stale && color) { if (!stale && color) {
setColor(color) setColor(color)
} }
...@@ -71,7 +72,7 @@ export default function useColor(token?: Token) { ...@@ -71,7 +72,7 @@ export default function useColor(token?: Token) {
stale = true stale = true
setColor(undefined) setColor(undefined)
} }
}, [token, theme]) }, [token, logoURIs, theme.tokenColorExtraction])
return color return color
} }
import { Currency } from '@uniswap/sdk-core'
import { SupportedChainId } from 'constants/chains'
import useHttpLocations from 'hooks/useHttpLocations'
import { useMemo } from 'react'
import { WrappedTokenInfo } from 'state/lists/wrappedTokenInfo'
import EthereumLogo from '../../assets/images/ethereum-logo.png'
import MaticLogo from '../../assets/svg/matic-token-icon.svg'
type Network = 'ethereum' | 'arbitrum' | 'optimism'
function chainIdToNetworkName(networkId: SupportedChainId): Network {
switch (networkId) {
case SupportedChainId.MAINNET:
return 'ethereum'
case SupportedChainId.ARBITRUM_ONE:
return 'arbitrum'
case SupportedChainId.OPTIMISM:
return 'optimism'
default:
return 'ethereum'
}
}
function getNativeLogoURI(chainId: SupportedChainId = SupportedChainId.MAINNET): string {
switch (chainId) {
case SupportedChainId.POLYGON_MUMBAI:
case SupportedChainId.POLYGON:
return MaticLogo
default:
return EthereumLogo
}
}
function getTokenLogoURI(address: string, chainId: SupportedChainId = SupportedChainId.MAINNET): string | void {
const networkName = chainIdToNetworkName(chainId)
const networksWithUrls = [SupportedChainId.ARBITRUM_ONE, SupportedChainId.MAINNET, SupportedChainId.OPTIMISM]
if (networksWithUrls.includes(chainId)) {
return `https://raw.githubusercontent.com/Uniswap/assets/master/blockchains/${networkName}/assets/${address}/logo.png`
}
}
export default function useCurrencyLogoURIs(currency?: Currency | null): string[] {
const locations = useHttpLocations(currency instanceof WrappedTokenInfo ? currency.logoURI : undefined)
return useMemo(() => {
const logoURIs = [...locations]
if (currency) {
if (currency.isNative) {
logoURIs.push(getNativeLogoURI(currency.chainId))
} else if (currency.isToken) {
const logoURI = getTokenLogoURI(currency.address, currency.chainId)
if (logoURI) {
logoURIs.push(logoURI)
}
}
}
return logoURIs
}, [currency, locations])
}
import { Currency } from '@uniswap/sdk-core' import { NativeCurrency } from '@uniswap/sdk-core'
import { SupportedChainId } from 'constants/chains' import { SupportedChainId } from 'constants/chains'
import { nativeOnChain } from 'constants/tokens' import { nativeOnChain } from 'constants/tokens'
import useActiveWeb3React from 'hooks/useActiveWeb3React' import useActiveWeb3React from 'hooks/useActiveWeb3React'
import { useMemo } from 'react' import { useMemo } from 'react'
export default function useNativeCurrency(): Currency { export default function useNativeCurrency(): NativeCurrency {
const { chainId } = useActiveWeb3React() const { chainId } = useActiveWeb3React()
return useMemo( return useMemo(
() => () =>
......
import { Token } from '@uniswap/sdk-core' import { NativeCurrency, Token } from '@uniswap/sdk-core'
import { TokenInfo } from '@uniswap/token-lists' import { TokenInfo } from '@uniswap/token-lists'
import { isAddress } from '../../../utils' import { isAddress } from '../../../utils'
...@@ -6,12 +6,12 @@ import { isAddress } from '../../../utils' ...@@ -6,12 +6,12 @@ import { isAddress } from '../../../utils'
const alwaysTrue = () => true const alwaysTrue = () => true
/** Creates a filter function that filters tokens that do not match the query. */ /** Creates a filter function that filters tokens that do not match the query. */
export function getTokenFilter<T extends Token | TokenInfo>(query: string): (token: T) => boolean { export function getTokenFilter<T extends Token | TokenInfo>(query: string): (token: T | NativeCurrency) => boolean {
const searchingAddress = isAddress(query) const searchingAddress = isAddress(query)
if (searchingAddress) { if (searchingAddress) {
const lower = searchingAddress.toLowerCase() const address = searchingAddress.toLowerCase()
return (t: T) => ('isToken' in t ? searchingAddress === t.address : lower === t.address.toLowerCase()) return (t: T | NativeCurrency) => 'address' in t && address === t.address.toLowerCase()
} }
const queryParts = query const queryParts = query
...@@ -30,5 +30,5 @@ export function getTokenFilter<T extends Token | TokenInfo>(query: string): (tok ...@@ -30,5 +30,5 @@ export function getTokenFilter<T extends Token | TokenInfo>(query: string): (tok
return queryParts.every((p) => p.length === 0 || parts.some((sp) => sp.startsWith(p) || sp.endsWith(p))) return queryParts.every((p) => p.length === 0 || parts.some((sp) => sp.startsWith(p) || sp.endsWith(p)))
} }
return ({ name, symbol }: T): boolean => Boolean((symbol && match(symbol)) || (name && match(name))) return ({ name, symbol }: T | NativeCurrency): boolean => Boolean((symbol && match(symbol)) || (name && match(name)))
} }
import useDebounce from 'hooks/useDebounce' import useDebounce from 'hooks/useDebounce'
import useActiveWeb3React from 'lib/hooks/useActiveWeb3React' import useActiveWeb3React from 'lib/hooks/useActiveWeb3React'
import { useTokenBalances } from 'lib/hooks/useCurrencyBalance' import { useTokenBalances } from 'lib/hooks/useCurrencyBalance'
import useNativeCurrency from 'lib/hooks/useNativeCurrency'
import { useMemo } from 'react' import { useMemo } from 'react'
import { WrappedTokenInfo } from 'state/lists/wrappedTokenInfo' import { WrappedTokenInfo } from 'state/lists/wrappedTokenInfo'
...@@ -10,13 +11,23 @@ import { tokenComparator, useSortTokensByQuery } from './sorting' ...@@ -10,13 +11,23 @@ import { tokenComparator, useSortTokensByQuery } from './sorting'
export function useQueryTokens(query: string, tokens: WrappedTokenInfo[]) { export function useQueryTokens(query: string, tokens: WrappedTokenInfo[]) {
const { account } = useActiveWeb3React() const { account } = useActiveWeb3React()
const balances = useTokenBalances(account, tokens) const balances = useTokenBalances(account, tokens)
const sortedTokens = useMemo(() => [...tokens.sort(tokenComparator.bind(null, balances))], [balances, tokens]) const sortedTokens = useMemo(
// Create a new array because sort is in-place and returns a referentially equivalent array.
() => Array.from(tokens).sort(tokenComparator.bind(null, balances)),
[balances, tokens]
)
const debouncedQuery = useDebounce(query, 200) const debouncedQuery = useDebounce(query, 200)
const filteredTokens = useMemo( const filter = useMemo(() => getTokenFilter(debouncedQuery), [debouncedQuery])
() => sortedTokens.filter(getTokenFilter(debouncedQuery)), const filteredTokens = useMemo(() => sortedTokens.filter(filter), [filter, sortedTokens])
[debouncedQuery, sortedTokens]
) const queriedTokens = useSortTokensByQuery(debouncedQuery, filteredTokens)
return useSortTokensByQuery(debouncedQuery, filteredTokens) const native = useNativeCurrency()
return useMemo(() => {
if (native && filter(native)) {
return [native, ...queriedTokens]
}
return queriedTokens
}, [filter, native, queriedTokens])
} }
...@@ -14,6 +14,7 @@ import { ...@@ -14,6 +14,7 @@ import {
HelpCircle as HelpCircleIcon, HelpCircle as HelpCircleIcon,
Info as InfoIcon, Info as InfoIcon,
Settings as SettingsIcon, Settings as SettingsIcon,
Slash as SlashIcon,
Trash2 as Trash2Icon, Trash2 as Trash2Icon,
X as XIcon, X as XIcon,
} from 'react-feather' } from 'react-feather'
...@@ -77,6 +78,7 @@ export const CreditCard = icon(CreditCardIcon) ...@@ -77,6 +78,7 @@ export const CreditCard = icon(CreditCardIcon)
export const HelpCircle = icon(HelpCircleIcon) export const HelpCircle = icon(HelpCircleIcon)
export const Info = icon(InfoIcon) export const Info = icon(InfoIcon)
export const Settings = icon(SettingsIcon) export const Settings = icon(SettingsIcon)
export const Slash = icon(SlashIcon)
export const Trash2 = icon(Trash2Icon) export const Trash2 = icon(Trash2Icon)
export const X = icon(XIcon) export const X = icon(XIcon)
......
export const USDC = {
name: 'USDCoin',
symbol: 'USDC',
chainId: 1,
decimals: 18,
address: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
logoURI:
'https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/ethereum/assets/0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48/logo.png',
}
export const DAI = {
name: 'DaiStablecoin',
symbol: 'DAI',
chainId: 1,
decimals: 18,
address: '0x6B175474E89094C44Da98b954EedeAC495271d0F',
logoURI: 'https://gemini.com/images/currencies/icons/default/dai.svg',
}
export const ETH = {
name: 'Ether',
symbol: 'ETH',
chainId: 1,
decimals: 18,
address: 'ETHER',
logoURI: 'https://raw.githubusercontent.com/Uniswap/interface/main/src/assets/images/ethereum-logo.png',
}
export const UNI = {
name: 'Uniswap',
symbol: 'UNI',
chainId: 1,
decimals: 18,
address: '0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984',
logoURI: 'https://gemini.com/images/currencies/icons/default/uni.svg',
}
import { Currency } from '@uniswap/sdk-core'
import { SupportedChainId } from 'constants/chains'
import { nativeOnChain } from 'constants/tokens'
import { atom, WritableAtom } from 'jotai' import { atom, WritableAtom } from 'jotai'
import { atomWithImmer } from 'jotai/immer' import { atomWithImmer } from 'jotai/immer'
import { useUpdateAtom } from 'jotai/utils' import { useUpdateAtom } from 'jotai/utils'
import { atomWithReset } from 'jotai/utils' import { atomWithReset } from 'jotai/utils'
import { ETH } from 'lib/mocks'
import { Customizable, pickAtom, setCustomizable, setTogglable } from 'lib/state/atoms' import { Customizable, pickAtom, setCustomizable, setTogglable } from 'lib/state/atoms'
import { Token } from 'lib/types'
import { useMemo } from 'react' import { useMemo } from 'react'
/** Max slippage, as a percentage. */ /** Max slippage, as a percentage. */
...@@ -42,7 +43,7 @@ export enum Field { ...@@ -42,7 +43,7 @@ export enum Field {
export interface Input { export interface Input {
value?: number value?: number
token?: Token token?: Currency
usdc?: number usdc?: number
} }
...@@ -62,7 +63,7 @@ export interface State { ...@@ -62,7 +63,7 @@ export interface State {
export const stateAtom = atomWithImmer<State>({ export const stateAtom = atomWithImmer<State>({
activeInput: Field.INPUT, activeInput: Field.INPUT,
input: { token: ETH }, input: { token: nativeOnChain(SupportedChainId.MAINNET) },
output: {}, output: {},
}) })
......
export interface Token {
name: string
symbol: string
chainId: number
decimals: number
address: string
logoURI?: string
}
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
"hooks/*": ["../hooks/*"], "hooks/*": ["../hooks/*"],
"state/*": ["../state/*"], "state/*": ["../state/*"],
"types/*": ["../types/*"], "types/*": ["../types/*"],
"utils/*": ["../utils/*"],
}, },
}, },
"exclude": ["node_modules", "src/lib/**/*.test.*"], "exclude": ["node_modules", "src/lib/**/*.test.*"],
......
...@@ -5069,6 +5069,11 @@ ...@@ -5069,6 +5069,11 @@
resolved "https://registry.yarnpkg.com/@uniswap/default-token-list/-/default-token-list-2.2.0.tgz#d85a5c2520f57f4920bd989dfc9f01e1b701a567" resolved "https://registry.yarnpkg.com/@uniswap/default-token-list/-/default-token-list-2.2.0.tgz#d85a5c2520f57f4920bd989dfc9f01e1b701a567"
integrity sha512-vFPWoGzDjHP4i2l7yLaober/lZMmzOZXXirVF8XNyfNzRxgmYCWKO6SzKtfEUwxpd3/KUebgdK55II4Mnak62A== integrity sha512-vFPWoGzDjHP4i2l7yLaober/lZMmzOZXXirVF8XNyfNzRxgmYCWKO6SzKtfEUwxpd3/KUebgdK55II4Mnak62A==
"@uniswap/default-token-list@^3.0.0":
version "3.0.0"
resolved "https://registry.yarnpkg.com/@uniswap/default-token-list/-/default-token-list-3.0.0.tgz#427ff2a65bbc77a5a24e60f6158441956773f684"
integrity sha512-t0H96s1Mx2ga6cGMj/wP/3NWdX4c9yZFd0ydiTOWLhWf1i5RjhWWND/ZTdn8QhmNsdHlhrGsWrEU62xoAY11bw==
"@uniswap/governance@^1.0.2": "@uniswap/governance@^1.0.2":
version "1.0.2" version "1.0.2"
resolved "https://registry.npmjs.org/@uniswap/governance/-/governance-1.0.2.tgz" resolved "https://registry.npmjs.org/@uniswap/governance/-/governance-1.0.2.tgz"
...@@ -17498,10 +17503,10 @@ react-cosmos-shared2@^5.6.3: ...@@ -17498,10 +17503,10 @@ react-cosmos-shared2@^5.6.3:
react-is "^17.0.2" react-is "^17.0.2"
socket.io-client "2.2.0" socket.io-client "2.2.0"
react-cosmos@^5.6.3: react-cosmos@^5.6.6:
version "5.6.3" version "5.6.6"
resolved "https://registry.yarnpkg.com/react-cosmos/-/react-cosmos-5.6.3.tgz#bd2c0e1334b2c9992ddb3a5d8dfcbe5fc723cbc8" resolved "https://registry.yarnpkg.com/react-cosmos/-/react-cosmos-5.6.6.tgz#93d66e347a63da7dfe046c2cb23221dcf815ce9d"
integrity sha512-FG6VIc4prnqnNQ9ToBq2cNPkPxZcZRauL0tMkbi01aoS90c3sNoQt6koL/IsntSpPcR67KRe+OtJspq9OtBjxg== integrity sha512-RMLRjl2gFq9370N6QszjPRMaT5WsEBEkJBsFbz56h00xPnJAxsab8gu5yj6yDDDSFibL/jBgxjJLdqbF00HMNw==
dependencies: dependencies:
"@skidding/launch-editor" "^2.2.3" "@skidding/launch-editor" "^2.2.3"
"@skidding/webpack-hot-middleware" "^2.25.0" "@skidding/webpack-hot-middleware" "^2.25.0"
...@@ -17520,7 +17525,7 @@ react-cosmos@^5.6.3: ...@@ -17520,7 +17525,7 @@ react-cosmos@^5.6.3:
react-cosmos-playground2 "^5.6.3" react-cosmos-playground2 "^5.6.3"
react-cosmos-plugin "^5.6.0" react-cosmos-plugin "^5.6.0"
react-cosmos-shared2 "^5.6.3" react-cosmos-shared2 "^5.6.3"
react-error-overlay "^6.0.9" react-error-overlay "6.0.9"
regenerator-runtime "^0.13.7" regenerator-runtime "^0.13.7"
resolve-from "^5.0.0" resolve-from "^5.0.0"
slash "^3.0.0" slash "^3.0.0"
...@@ -17584,7 +17589,7 @@ react-error-boundary@^3.1.0: ...@@ -17584,7 +17589,7 @@ react-error-boundary@^3.1.0:
dependencies: dependencies:
"@babel/runtime" "^7.12.5" "@babel/runtime" "^7.12.5"
react-error-overlay@^6.0.9: react-error-overlay@6.0.9, react-error-overlay@^6.0.9:
version "6.0.9" version "6.0.9"
resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.9.tgz#3c743010c9359608c375ecd6bc76f35d93995b0a" resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.9.tgz#3c743010c9359608c375ecd6bc76f35d93995b0a"
integrity sha512-nQTTcUu+ATDbrSD1BZHr5kgSD4oF8OFjxun8uAaL8RwPBacGBNPf/yAuVVdx17N8XNzRDMrZ9XcKZHCjPW+9ew== integrity sha512-nQTTcUu+ATDbrSD1BZHr5kgSD4oF8OFjxun8uAaL8RwPBacGBNPf/yAuVVdx17N8XNzRDMrZ9XcKZHCjPW+9ew==
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment