feat: display an ENS avatar (#2806)

* feat: ens avatar resolution

* chore: uninstall @davatar/react

* fix: add avatar alt

* feat: support data uris

* feat: support arweave uris

* feat: support erc721 avatars

* feat: support erc1155 avatars

* fix: jazzicon integration

* fix: clean usage of status icon

* fix: fix jazzicon svg offset

* refactor: share status icon component

* fix: pass memoized args to multicall
This commit is contained in:
Zach Pomerantz 2021-11-22 10:55:25 -08:00 committed by GitHub
parent 262d984f92
commit d5c4ee0342
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 354 additions and 141 deletions

@ -11,7 +11,6 @@
],
"private": true,
"devDependencies": {
"@davatar/react": "1.8.1",
"@ethersproject/experimental": "^5.4.0",
"@gnosis.pm/safe-apps-web3-react": "^0.6.0",
"@graphql-codegen/cli": "1.21.5",
@ -21,6 +20,7 @@
"@lingui/cli": "^3.9.0",
"@lingui/macro": "^3.9.0",
"@lingui/react": "^3.9.0",
"@metamask/jazzicon": "^2.0.0",
"@popperjs/core": "^2.4.4",
"@reach/dialog": "^0.10.3",
"@reach/portal": "^0.10.3",

49
src/abis/erc1155.json Normal file

@ -0,0 +1,49 @@
[
{
"constant": true,
"inputs": [
{
"internalType": "address",
"name": "_owner",
"type": "address"
},
{
"internalType": "uint256",
"name": "_id",
"type": "uint256"
}
],
"name": "balanceOf",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [
{
"internalType": "uint256",
"name": "_id",
"type": "uint256"
}
],
"name": "uri",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
}
]

40
src/abis/erc721.json Normal file

@ -0,0 +1,40 @@
[
{
"inputs": [
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "ownerOf",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
}
],
"name": "tokenURI",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
}
]

@ -1,15 +1,12 @@
import { Trans } from '@lingui/macro'
import { AbstractConnector } from '@web3-react/abstract-connector'
import { useCallback, useContext } from 'react'
import { ExternalLink as LinkIcon } from 'react-feather'
import { useAppDispatch } from 'state/hooks'
import styled, { ThemeContext } from 'styled-components/macro'
import CoinbaseWalletIcon from '../../assets/images/coinbaseWalletIcon.svg'
import FortmaticIcon from '../../assets/images/fortmaticIcon.png'
import PortisIcon from '../../assets/images/portisIcon.png'
import WalletConnectIcon from '../../assets/images/walletConnectIcon.svg'
import { ReactComponent as Close } from '../../assets/images/x.svg'
import { fortmatic, injected, portis, walletconnect, walletlink } from '../../connectors'
import { injected, portis, walletlink } from '../../connectors'
import { SUPPORTED_WALLETS } from '../../constants/wallet'
import { useActiveWeb3React } from '../../hooks/web3'
import { clearAllTransactions } from '../../state/transactions/actions'
@ -17,7 +14,7 @@ import { ExternalLink, LinkStyledButton, TYPE } from '../../theme'
import { shortenAddress } from '../../utils'
import { ExplorerDataType, getExplorerLink } from '../../utils/getExplorerLink'
import { ButtonSecondary } from '../Button'
import Identicon from '../Identicon'
import StatusIcon from '../Identicon/StatusIcon'
import { AutoRow } from '../Row'
import Copy from './Copy'
import Transaction from './Transaction'
@ -179,6 +176,23 @@ const IconWrapper = styled.div<{ size?: number }>`
`};
`
function WrappedStatusIcon({ connector }: { connector: AbstractConnector }) {
return (
<IconWrapper size={16}>
<StatusIcon connector={connector} />
{connector === portis && (
<MainWalletAction
onClick={() => {
portis.portis.showPortis()
}}
>
<Trans>Show Portis</Trans>
</MainWalletAction>
)}
</IconWrapper>
)
}
const TransactionListWrapper = styled.div`
${({ theme }) => theme.flexColumnNoWrap};
`
@ -244,50 +258,6 @@ export default function AccountDetails({
)
}
function getStatusIcon() {
if (connector === injected) {
return (
<IconWrapper size={16}>
<Identicon />
</IconWrapper>
)
} else if (connector === walletconnect) {
return (
<IconWrapper size={16}>
<img src={WalletConnectIcon} alt={'WalletConnect logo'} />
</IconWrapper>
)
} else if (connector === walletlink) {
return (
<IconWrapper size={16}>
<img src={CoinbaseWalletIcon} alt={'Coinbase Wallet logo'} />
</IconWrapper>
)
} else if (connector === fortmatic) {
return (
<IconWrapper size={16}>
<img src={FortmaticIcon} alt={'Fortmatic logo'} />
</IconWrapper>
)
} else if (connector === portis) {
return (
<>
<IconWrapper size={16}>
<img src={PortisIcon} alt={'Portis logo'} />
<MainWalletAction
onClick={() => {
portis.portis.showPortis()
}}
>
<Trans>Show Portis</Trans>
</MainWalletAction>
</IconWrapper>
</>
)
}
return null
}
const clearAllTransactionsCallback = useCallback(() => {
if (chainId) dispatch(clearAllTransactions({ chainId }))
}, [dispatch, chainId])
@ -332,14 +302,14 @@ export default function AccountDetails({
{ENSName ? (
<>
<div>
{getStatusIcon()}
{connector && <WrappedStatusIcon connector={connector} />}
<p> {ENSName}</p>
</div>
</>
) : (
<>
<div>
{getStatusIcon()}
{connector && <WrappedStatusIcon connector={connector} />}
<p> {account && shortenAddress(account)}</p>
</div>
</>

@ -0,0 +1,25 @@
import { AbstractConnector } from '@web3-react/abstract-connector'
import CoinbaseWalletIcon from '../../assets/images/coinbaseWalletIcon.svg'
import FortmaticIcon from '../../assets/images/fortmaticIcon.png'
import PortisIcon from '../../assets/images/portisIcon.png'
import WalletConnectIcon from '../../assets/images/walletConnectIcon.svg'
import { fortmatic, injected, portis, walletconnect, walletlink } from '../../connectors'
import Identicon from '../Identicon'
export default function StatusIcon({ connector }: { connector: AbstractConnector }) {
switch (connector) {
case injected:
return <Identicon />
case walletconnect:
return <img src={WalletConnectIcon} alt={'WalletConnect'} />
case walletlink:
return <img src={CoinbaseWalletIcon} alt={'Coinbase Wallet'} />
case fortmatic:
return <img src={FortmaticIcon} alt={'Fortmatic'} />
case portis:
return <img src={PortisIcon} alt={'Portis'} />
default:
return null
}
}

@ -1,32 +1,47 @@
import Davatar, { Image } from '@davatar/react'
import { useMemo } from 'react'
import jazzicon from '@metamask/jazzicon'
import useENSAvatar from 'hooks/useENSAvatar'
import { useEffect, useRef, useState } from 'react'
import styled from 'styled-components/macro'
import { useActiveWeb3React } from '../../hooks/web3'
const StyledIdenticonContainer = styled.div`
const StyledIdenticon = styled.div`
height: 1rem;
width: 1rem;
border-radius: 1.125rem;
background-color: ${({ theme }) => theme.bg4};
font-size: initial;
`
const StyledAvatar = styled.img`
height: inherit;
width: inherit;
border-radius: inherit;
`
export default function Identicon() {
const { account, library } = useActiveWeb3React()
const ref = useRef<HTMLDivElement>(null)
const { account } = useActiveWeb3React()
const { avatar } = useENSAvatar(account ?? undefined)
const [fetchable, setFetchable] = useState(true)
// restrict usage of Davatar until it stops sending 3p requests
// see https://github.com/metaphor-xyz/davatar-helpers/issues/18
const supportsENS = useMemo(() => {
return ([1, 3, 4, 5] as Array<number | undefined>).includes(library?.network?.chainId)
}, [library])
useEffect(() => {
if ((!avatar || !fetchable) && account) {
const icon = jazzicon(16, parseInt(account?.slice(2, 10), 16))
const current = ref.current
current?.appendChild(icon)
return () => {
current?.removeChild(icon)
}
}
return
}, [account, avatar, fetchable])
return (
<StyledIdenticonContainer>
{account && supportsENS ? (
<Davatar address={account} size={16} provider={library} />
) : (
<Image address={account} size={16} />
<StyledIdenticon ref={ref}>
{avatar && fetchable && (
<StyledAvatar alt="avatar" src={avatar} onError={() => setFetchable(false)}></StyledAvatar>
)}
</StyledIdenticonContainer>
</StyledIdenticon>
)
}

@ -7,11 +7,6 @@ import { useMemo } from 'react'
import { Activity } from 'react-feather'
import styled, { css } from 'styled-components/macro'
import CoinbaseWalletIcon from '../../assets/images/coinbaseWalletIcon.svg'
import FortmaticIcon from '../../assets/images/fortmaticIcon.png'
import PortisIcon from '../../assets/images/portisIcon.png'
import WalletConnectIcon from '../../assets/images/walletConnectIcon.svg'
import { fortmatic, injected, portis, walletconnect, walletlink } from '../../connectors'
import { NetworkContextName } from '../../constants/misc'
import useENSName from '../../hooks/useENSName'
import { useHasSocks } from '../../hooks/useSocksBalance'
@ -20,7 +15,7 @@ import { isTransactionRecent, useAllTransactions } from '../../state/transaction
import { TransactionDetails } from '../../state/transactions/reducer'
import { shortenAddress } from '../../utils'
import { ButtonSecondary } from '../Button'
import Identicon from '../Identicon'
import StatusIcon from '../Identicon/StatusIcon'
import Loader from '../Loader'
import { RowBetween } from '../Row'
import WalletModal from '../WalletModal'
@ -132,36 +127,12 @@ function Sock() {
)
}
// eslint-disable-next-line react/prop-types
function StatusIcon({ connector }: { connector: AbstractConnector }) {
if (connector === injected) {
return <Identicon />
} else if (connector === walletconnect) {
return (
<IconWrapper size={16}>
<img src={WalletConnectIcon} alt={'WalletConnect'} />
</IconWrapper>
)
} else if (connector === walletlink) {
return (
<IconWrapper size={16}>
<img src={CoinbaseWalletIcon} alt={'CoinbaseWallet'} />
</IconWrapper>
)
} else if (connector === fortmatic) {
return (
<IconWrapper size={16}>
<img src={FortmaticIcon} alt={'Fortmatic'} />
</IconWrapper>
)
} else if (connector === portis) {
return (
<IconWrapper size={16}>
<img src={PortisIcon} alt={'Portis'} />
</IconWrapper>
)
}
return null
function WrappedStatusIcon({ connector }: { connector: AbstractConnector }) {
return (
<IconWrapper size={16}>
<StatusIcon connector={connector} />
</IconWrapper>
)
}
function Web3StatusInner() {
@ -198,7 +169,7 @@ function Web3StatusInner() {
<Text>{ENSName || shortenAddress(account)}</Text>
</>
)}
{!hasPendingTransactions && connector && <StatusIcon connector={connector} />}
{!hasPendingTransactions && connector && <WrappedStatusIcon connector={connector} />}
</Web3StatusConnected>
)
} else if (error) {

@ -15,6 +15,8 @@ import ENS_PUBLIC_RESOLVER_ABI from 'abis/ens-public-resolver.json'
import ENS_ABI from 'abis/ens-registrar.json'
import ERC20_ABI from 'abis/erc20.json'
import ERC20_BYTES32_ABI from 'abis/erc20_bytes32.json'
import ERC721_ABI from 'abis/erc721.json'
import ERC1155_ABI from 'abis/erc1155.json'
import GOVERNOR_BRAVO_ABI from 'abis/governor-bravo.json'
import WETH_ABI from 'abis/weth.json'
import {
@ -35,7 +37,7 @@ import { NonfungiblePositionManager, Quoter, UniswapInterfaceMulticall } from 't
import { V3Migrator } from 'types/v3/V3Migrator'
import { getContract } from 'utils'
import { ArgentWalletDetector, EnsPublicResolver, EnsRegistrar, Erc20, Weth } from '../abis/types'
import { ArgentWalletDetector, EnsPublicResolver, EnsRegistrar, Erc20, Erc721, Erc1155, Weth } from '../abis/types'
import { UNI, WETH9_EXTENDED } from '../constants/tokens'
import { useActiveWeb3React } from './web3'
@ -75,6 +77,14 @@ export function useWETHContract(withSignerIfPossible?: boolean) {
return useContract<Weth>(chainId ? WETH9_EXTENDED[chainId]?.address : undefined, WETH_ABI, withSignerIfPossible)
}
export function useERC721Contract(nftAddress?: string) {
return useContract<Erc721>(nftAddress, ERC721_ABI, false)
}
export function useERC1155Contract(nftAddress?: string) {
return useContract<Erc1155>(nftAddress, ERC1155_ABI, false)
}
export function useArgentWalletDetectorContract() {
return useContract<ArgentWalletDetector>(ARGENT_WALLET_DETECTOR_ADDRESS, ARGENT_WALLET_DETECTOR_ABI, false)
}

131
src/hooks/useENSAvatar.ts Normal file

@ -0,0 +1,131 @@
import { namehash } from '@ethersproject/hash'
import { useEffect, useMemo, useState } from 'react'
import uriToHttp from 'utils/uriToHttp'
import { useSingleCallResult } from '../state/multicall/hooks'
import { isAddress } from '../utils'
import isZero from '../utils/isZero'
import { useENSRegistrarContract, useENSResolverContract, useERC721Contract, useERC1155Contract } from './useContract'
import useDebounce from './useDebounce'
import useENSName from './useENSName'
import { useActiveWeb3React } from './web3'
/**
* Returns the ENS avatar URI, if available.
* Spec: https://gist.github.com/Arachnid/9db60bd75277969ee1689c8742b75182.
*/
export default function useENSAvatar(
address?: string,
enforceOwnership = true
): { avatar: string | null; loading: boolean } {
const debouncedAddress = useDebounce(address, 200)
const node = useMemo(() => {
if (!debouncedAddress || !isAddress(debouncedAddress)) return undefined
try {
return debouncedAddress ? namehash(`${debouncedAddress.toLowerCase().substr(2)}.addr.reverse`) : undefined
} catch (error) {
return undefined
}
}, [debouncedAddress])
const addressAvatar = useAvatarFromNode(node)
const nameAvatar = useAvatarFromNode(namehash(useENSName(address).ENSName ?? ''))
let avatar = addressAvatar.avatar || nameAvatar.avatar
const nftAvatar = useAvatarFromNFT(avatar, enforceOwnership)
avatar = nftAvatar.avatar || avatar
const http = avatar && uriToHttp(avatar)[0]
const changed = debouncedAddress !== address
return {
avatar: changed ? null : http ?? null,
loading: changed || addressAvatar.loading || nameAvatar.loading || nftAvatar.loading,
}
}
function useAvatarFromNode(node?: string): { avatar?: string; loading: boolean } {
const nodeArgument = useMemo(() => [node], [node])
const textArgument = useMemo(() => [node, 'avatar'], [node])
const registrarContract = useENSRegistrarContract(false)
const resolverAddress = useSingleCallResult(registrarContract, 'resolver', nodeArgument)
const resolverAddressResult = resolverAddress.result?.[0]
const resolverContract = useENSResolverContract(
resolverAddressResult && !isZero(resolverAddressResult) ? resolverAddressResult : undefined,
false
)
const avatar = useSingleCallResult(resolverContract, 'text', textArgument)
return {
avatar: avatar.result?.[0],
loading: resolverAddress.loading || avatar.loading,
}
}
function useAvatarFromNFT(nftUri = '', enforceOwnership: boolean): { avatar?: string; loading: boolean } {
const parts = nftUri.toLowerCase().split(':')
const protocol = parts[0]
// ignore the chain from eip155
// TODO: when we are able, pull only from the specified chain
const [, erc] = parts[1]?.split('/') ?? []
const [contractAddress, id] = parts[2]?.split('/') ?? []
const isERC721 = protocol === 'eip155' && erc === 'erc721'
const isERC1155 = protocol === 'eip155' && erc === 'erc1155'
const erc721 = useERC721Uri(isERC721 ? contractAddress : undefined, id, enforceOwnership)
const erc1155 = useERC1155Uri(isERC1155 ? contractAddress : undefined, id, enforceOwnership)
const uri = erc721.uri || erc1155.uri
const http = uri && uriToHttp(uri)[0]
const [loading, setLoading] = useState(false)
const [avatar, setAvatar] = useState(undefined)
useEffect(() => {
setAvatar(undefined)
if (http) {
setLoading(true)
fetch(http)
.then((res) => res.json())
.then(({ image }) => {
setAvatar(image)
})
.catch((e) => console.warn(e))
.finally(() => {
setLoading(false)
})
}
}, [http])
return { avatar, loading: erc721.loading || erc1155.loading || loading }
}
function useERC721Uri(
contractAddress: string | undefined,
id: string | undefined,
enforceOwnership: boolean
): { uri?: string; loading: boolean } {
const idArgument = useMemo(() => [id], [id])
const { account } = useActiveWeb3React()
const contract = useERC721Contract(contractAddress)
const owner = useSingleCallResult(contract, 'ownerOf', idArgument)
const uri = useSingleCallResult(contract, 'tokenURI', idArgument)
return {
uri: !enforceOwnership || account === owner.result?.[0] ? uri.result?.[0] : undefined,
loading: owner.loading || uri.loading,
}
}
function useERC1155Uri(
contractAddress: string | undefined,
id: string | undefined,
enforceOwnership: boolean
): { uri?: string; loading: boolean } {
const { account } = useActiveWeb3React()
const idArgument = useMemo(() => [id], [id])
const accountArgument = useMemo(() => [account || '', id], [account, id])
const contract = useERC1155Contract(contractAddress)
const balance = useSingleCallResult(contract, 'balanceOf', accountArgument)
const uri = useSingleCallResult(contract, 'uri', idArgument)
return {
uri: !enforceOwnership || balance.result?.[0] > 0 ? uri.result?.[0] : undefined,
loading: balance.loading || uri.loading,
}
}

@ -1,10 +1,12 @@
/**
* Given a URI that may be ipfs, ipns, http, or https protocol, return the fetch-able http(s) URLs for the same content
* Given a URI that may be ipfs, ipns, http, https, or data protocol, return the fetch-able http(s) URLs for the same content
* @param uri to convert to fetch-able http url
*/
export default function uriToHttp(uri: string): string[] {
const protocol = uri.split(':')[0].toLowerCase()
switch (protocol) {
case 'data':
return [uri]
case 'https':
return [uri]
case 'http':
@ -15,6 +17,9 @@ export default function uriToHttp(uri: string): string[] {
case 'ipns':
const name = uri.match(/^ipns:(\/\/)?(.*)$/i)?.[2]
return [`https://cloudflare-ipfs.com/ipns/${name}/`, `https://ipfs.io/ipns/${name}/`]
case 'ar':
const tx = uri.match(/^ipns:(\/\/)?(.*)$/i)?.[2]
return [`https://arweave.net/${tx}`]
default:
return []
}

@ -1329,19 +1329,6 @@
debug "^3.1.0"
lodash.once "^4.1.1"
"@davatar/react@1.8.1":
version "1.8.1"
resolved "https://registry.yarnpkg.com/@davatar/react/-/react-1.8.1.tgz#2fe3e619422a46092c57025328be56b1e911550e"
integrity sha512-vq9zNwAfnZCoY8W2eAbjWP1GPQutUfdxG+lKG2fAPqNFP2qrzDhIziKyCKtt7jwaXp79P1Cy1Gjzlvs1XkzwOQ==
dependencies:
"@ethersproject/contracts" "^5.4.1"
"@ethersproject/providers" "^5.4.5"
"@types/react-blockies" "^1.4.1"
bn.js "^5.2.0"
color "^3.2.1"
mersenne-twister "^1.1.0"
react-blockies "^1.4.1"
"@emotion/cache@^10.0.27":
version "10.0.29"
resolved "https://registry.npmjs.org/@emotion/cache/-/cache-10.0.29.tgz"
@ -1562,7 +1549,7 @@
dependencies:
"@ethersproject/bignumber" "^5.4.0"
"@ethersproject/contracts@5.4.1", "@ethersproject/contracts@^5.4.1":
"@ethersproject/contracts@5.4.1":
version "5.4.1"
resolved "https://registry.yarnpkg.com/@ethersproject/contracts/-/contracts-5.4.1.tgz#3eb4f35b7fe60a962a75804ada2746494df3e470"
integrity sha512-m+z2ZgPy4pyR15Je//dUaymRUZq5MtDajF6GwFbGAVmKz/RF+DNIPwF0k5qEcL3wPGVqUjFg2/krlCRVTU4T5w==
@ -1673,7 +1660,7 @@
dependencies:
"@ethersproject/logger" "^5.4.0"
"@ethersproject/providers@5.4.5", "@ethersproject/providers@^5.4.5":
"@ethersproject/providers@5.4.5":
version "5.4.5"
resolved "https://registry.yarnpkg.com/@ethersproject/providers/-/providers-5.4.5.tgz#eb2ea2a743a8115f79604a8157233a3a2c832928"
integrity sha512-1GkrvkiAw3Fj28cwi1Sqm8ED1RtERtpdXmRfwIBGmqBSN5MoeRUHuwHPppMtbPayPgpFcvD7/Gdc9doO5fGYgw==
@ -3034,6 +3021,14 @@
"@babel/runtime" "^7.11.2"
"@lingui/core" "^3.9.0"
"@metamask/jazzicon@^2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@metamask/jazzicon/-/jazzicon-2.0.0.tgz#5615528e91c0fc5c9d79202d1f0954a7922525a0"
integrity sha512-7M+WSZWKcQAo0LEhErKf1z+D3YX0tEDAcGvcKbDyvDg34uvgeKR00mFNIYwAhdAS9t8YXxhxZgsrRBBg6X8UQg==
dependencies:
color "^0.11.3"
mersenne-twister "^1.1.0"
"@metamask/safe-event-emitter@2.0.0", "@metamask/safe-event-emitter@^2.0.0":
version "2.0.0"
resolved "https://registry.npmjs.org/@metamask/safe-event-emitter/-/safe-event-emitter-2.0.0.tgz"
@ -4202,13 +4197,6 @@
resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.7.tgz#63bb7d067db107cc1e457c303bc25d511febf6cb"
integrity sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==
"@types/react-blockies@^1.4.1":
version "1.4.1"
resolved "https://registry.yarnpkg.com/@types/react-blockies/-/react-blockies-1.4.1.tgz#d5f6fff8ece3e90f2e7708f8f3156c87333312df"
integrity sha512-aDX0g0hwzdodkGLSDNUQr6gXxwclGjnhS8jhsR8uQhAfe/7i3GZD/NDcSlQ2SiQiLhfRxX3NlY+nvBwf5Y0tTg==
dependencies:
"@types/react" "*"
"@types/react-dom@>=16.9.0", "@types/react-dom@^17.0.1":
version "17.0.9"
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-17.0.9.tgz#441a981da9d7be117042e1a6fd3dac4b30f55add"
@ -6746,7 +6734,7 @@ bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.10.0, bn.js@^4.11.0, bn.js@^4.11.1, bn.js@^
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88"
integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==
bn.js@^5.0.0, bn.js@^5.1.1, bn.js@^5.2.0:
bn.js@^5.0.0, bn.js@^5.1.1:
version "5.2.0"
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.0.tgz#358860674396c6997771a9d051fcc1b57d4ae002"
integrity sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw==
@ -7640,7 +7628,7 @@ collection-visit@^1.0.0:
map-visit "^1.0.0"
object-visit "^1.0.0"
color-convert@^1.9.0, color-convert@^1.9.3:
color-convert@^1.3.0, color-convert@^1.9.0, color-convert@^1.9.3:
version "1.9.3"
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==
@ -7664,6 +7652,13 @@ color-name@^1.0.0, color-name@~1.1.4:
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
color-string@^0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/color-string/-/color-string-0.3.0.tgz#27d46fb67025c5c2fa25993bfbf579e47841b991"
integrity sha1-J9RvtnAlxcL6JZk7+/V55HhBuZE=
dependencies:
color-name "^1.0.0"
color-string@^1.6.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.6.0.tgz#c3915f61fe267672cb7e1e064c9d692219f6c312"
@ -7672,7 +7667,16 @@ color-string@^1.6.0:
color-name "^1.0.0"
simple-swizzle "^0.2.2"
color@^3.0.0, color@^3.2.1:
color@^0.11.3:
version "0.11.4"
resolved "https://registry.yarnpkg.com/color/-/color-0.11.4.tgz#6d7b5c74fb65e841cd48792ad1ed5e07b904d764"
integrity sha1-bXtcdPtl6EHNSHkq0e1eB7kE12Q=
dependencies:
clone "^1.0.2"
color-convert "^1.3.0"
color-string "^0.3.0"
color@^3.0.0:
version "3.2.1"
resolved "https://registry.yarnpkg.com/color/-/color-3.2.1.tgz#3544dc198caf4490c3ecc9a790b54fe9ff45e164"
integrity sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==
@ -14330,7 +14334,7 @@ meros@1.1.4:
mersenne-twister@^1.1.0:
version "1.1.0"
resolved "https://registry.npmjs.org/mersenne-twister/-/mersenne-twister-1.1.0.tgz"
resolved "https://registry.yarnpkg.com/mersenne-twister/-/mersenne-twister-1.1.0.tgz#f916618ee43d7179efcf641bec4531eb9670978a"
integrity sha1-+RZhjuQ9cXnvz2Qb7EUx65Zwl4o=
messageformat-parser@^4.1.3:
@ -16884,7 +16888,7 @@ prompts@^2.0.1:
kleur "^3.0.3"
sisteransi "^1.0.5"
prop-types@^15.5.10, prop-types@^15.5.8, prop-types@^15.6.2, prop-types@^15.7.2:
prop-types@^15.5.8, prop-types@^15.6.2, prop-types@^15.7.2:
version "15.7.2"
resolved "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz"
integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==
@ -17156,13 +17160,6 @@ react-app-polyfill@^2.0.0:
regenerator-runtime "^0.13.7"
whatwg-fetch "^3.4.1"
react-blockies@^1.4.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/react-blockies/-/react-blockies-1.4.1.tgz#d4f0faf95ac197213a297a370a4d7f77ea3d0b08"
integrity sha512-4N015X5oPNnD3xQPsiqolOFzPZSSWyc5mJhJUZShUCHtiGUxVN+1qsWTcglkHMNySux9hUofaispqcw9QkWP5Q==
dependencies:
prop-types "^15.5.10"
react-clientside-effect@^1.2.2:
version "1.2.5"
resolved "https://registry.npmjs.org/react-clientside-effect/-/react-clientside-effect-1.2.5.tgz"