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:
parent
262d984f92
commit
d5c4ee0342
@ -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
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
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>
|
||||
</>
|
||||
|
25
src/components/Identicon/StatusIcon.tsx
Normal file
25
src/components/Identicon/StatusIcon.tsx
Normal file
@ -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
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 []
|
||||
}
|
||||
|
65
yarn.lock
65
yarn.lock
@ -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"
|
||||
|
Loading…
Reference in New Issue
Block a user