diff --git a/packages/mask/shared-ui/locales/en-US.json b/packages/mask/shared-ui/locales/en-US.json index d9e3f301a1fb..bbd21169522f 100644 --- a/packages/mask/shared-ui/locales/en-US.json +++ b/packages/mask/shared-ui/locales/en-US.json @@ -64,6 +64,7 @@ "message": "Message", "dashboard_tab_collectibles": "Collectibles", "dashboard_no_collectible_found": "No collectible found.", + "dashboard_collectible_menu_all": "All ({{count}})", "days": "Every {{days}} days", "decrypted_postbox_add_recipients": "Append recipients", "decrypted_postbox_decrypting": "Mask decrypting…", @@ -326,7 +327,6 @@ "plugin_wallet_name_placeholder": "Enter 1-12 characters", "plugin_wallet_fail_to_sign": "Failed to sign password.", "plugin_wallet_cancel_sign": "Signature canceled.", - "plugin_wallet_nft_wall_current_display": "Current display: ", "plugin_red_packet_display_name": "Plugin: Lucky Drop", "plugin_red_packet_claimed": "Claimed", "plugin_red_packet_erc20_tab_title": "Token", diff --git a/packages/mask/shared-ui/locales/qya-AA.json b/packages/mask/shared-ui/locales/qya-AA.json index 81c7caae1388..b89c5996d88a 100644 --- a/packages/mask/shared-ui/locales/qya-AA.json +++ b/packages/mask/shared-ui/locales/qya-AA.json @@ -326,7 +326,6 @@ "plugin_wallet_name_placeholder": "crwdns4705:0crwdne4705:0", "plugin_wallet_fail_to_sign": "crwdns4707:0crwdne4707:0", "plugin_wallet_cancel_sign": "crwdns4709:0crwdne4709:0", - "plugin_wallet_nft_wall_current_display": "crwdns13185:0crwdne13185:0", "plugin_red_packet_display_name": "crwdns4711:0crwdne4711:0", "plugin_red_packet_claimed": "crwdns10205:0crwdne10205:0", "plugin_red_packet_erc20_tab_title": "crwdns8143:0crwdne8143:0", diff --git a/packages/mask/shared-ui/locales/zh-CN.json b/packages/mask/shared-ui/locales/zh-CN.json index 9ed9571c3d11..861de61be2aa 100644 --- a/packages/mask/shared-ui/locales/zh-CN.json +++ b/packages/mask/shared-ui/locales/zh-CN.json @@ -324,7 +324,6 @@ "plugin_wallet_name_placeholder": "输入1-12 个字符", "plugin_wallet_fail_to_sign": "无法签名密码。", "plugin_wallet_cancel_sign": "签名已取消。", - "plugin_wallet_nft_wall_current_display": "当前显示: ", "plugin_red_packet_display_name": "插件:红包", "plugin_red_packet_claimed": "已认领", "plugin_red_packet_erc20_tab_title": "代币", diff --git a/packages/mask/src/extension/options-page/DashboardComponents/CollectibleList/CollectibleCard.tsx b/packages/mask/src/extension/options-page/DashboardComponents/CollectibleList/CollectibleCard.tsx index b867d8b91a0f..7d7ca7f8f172 100644 --- a/packages/mask/src/extension/options-page/DashboardComponents/CollectibleList/CollectibleCard.tsx +++ b/packages/mask/src/extension/options-page/DashboardComponents/CollectibleList/CollectibleCard.tsx @@ -79,7 +79,7 @@ export function CollectibleCard(props: CollectibleCardProps) { (entries) => { entries.forEach((item) => { if (!item.isIntersecting) return - setImageLinkWithLazy(token.info.mediaUrl!) + setImageLinkWithLazy(token.info.imageURL || token.info.mediaUrl!) observer.unobserve(item.target) }) }, @@ -101,7 +101,7 @@ export function CollectibleCard(props: CollectibleCardProps) { theme.palette.mode === 'dark' ? new URL('./nft_token_fallback_dark.png', import.meta.url) : new URL('./nft_token_fallback.png', import.meta.url) - const { value: isImageToken, loading } = useImageChecker(token.info.mediaUrl) + const { value: isImageToken, loading } = useImageChecker(token.info.imageURL || token.info.mediaUrl) return ( )} - {token.info.mediaUrl ? ( + {token.info.imageURL || token.info.mediaUrl ? ( loading ? ( ) : isImageToken && imageLinkWithLazy ? ( diff --git a/packages/mask/src/extension/options-page/DashboardComponents/CollectibleList/CollectionIcon.tsx b/packages/mask/src/extension/options-page/DashboardComponents/CollectibleList/CollectionIcon.tsx new file mode 100644 index 000000000000..0d31b3b041f5 --- /dev/null +++ b/packages/mask/src/extension/options-page/DashboardComponents/CollectibleList/CollectionIcon.tsx @@ -0,0 +1,72 @@ +import { memo } from 'react' +import { Box, Tooltip } from '@mui/material' +import { Image } from '../../../../components/shared/Image' +import { makeStyles } from '@masknet/theme' +import { TokenIcon } from '@masknet/shared' +import classNames from 'classnames' +import type { ERC721ContractDetailed } from '@masknet/web3-shared-evm' +import { isSameAddress } from '@masknet/web3-shared-evm' + +const useStyles = makeStyles()((theme) => ({ + collectionWrap: { + width: '24px', + height: '24px', + borderRadius: '50%', + background: 'rgba(229,232,235,1)', + cursor: 'pointer', + }, + collectionImg: { + objectFit: 'cover', + width: '100%', + height: '100%', + borderRadius: '50%', + color: theme.palette.primary.main, + }, + tip: { + padding: theme.spacing(1), + color: '#ffffff', + }, + selected: { + border: '2px solid #1D9BF0', + borderRadius: '50%', + }, +})) + +interface CollectionIconProps { + selectedCollection?: string + collection?: ERC721ContractDetailed + onClick?(): void +} + +export const CollectionIcon = memo(({ collection, onClick, selectedCollection }) => { + const { classes } = useStyles() + if (!collection) { + return + } + return ( + + + {collection.iconURL ? ( + + ) : ( + + )} + + + ) +}) diff --git a/packages/mask/src/extension/options-page/DashboardComponents/CollectibleList/LoadingCollectible.tsx b/packages/mask/src/extension/options-page/DashboardComponents/CollectibleList/LoadingCollectible.tsx new file mode 100644 index 000000000000..1431851834aa --- /dev/null +++ b/packages/mask/src/extension/options-page/DashboardComponents/CollectibleList/LoadingCollectible.tsx @@ -0,0 +1,34 @@ +import { makeStyles } from '@masknet/theme' +import { Box, Skeleton } from '@mui/material' + +const useStyles = makeStyles()((theme) => ({ + root: { + display: 'grid', + flexWrap: 'wrap', + gridTemplateColumns: 'repeat(auto-fill, minmax(172px, 1fr))', + gridGap: theme.spacing(1), + }, + card: { + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + position: 'relative', + padding: theme.spacing(1), + }, +})) + +export const LoadingCollectible = () => { + const { classes } = useStyles() + return ( + + {Array.from({ length: 3 }) + .fill(0) + .map((_, i) => ( + + + + + ))} + + ) +} diff --git a/packages/mask/src/extension/options-page/DashboardComponents/CollectibleList/index.tsx b/packages/mask/src/extension/options-page/DashboardComponents/CollectibleList/index.tsx index f3c0cc005631..55207735e544 100644 --- a/packages/mask/src/extension/options-page/DashboardComponents/CollectibleList/index.tsx +++ b/packages/mask/src/extension/options-page/DashboardComponents/CollectibleList/index.tsx @@ -1,8 +1,11 @@ -import { createContext, useEffect, useMemo } from 'react' +import { createContext, useEffect, useMemo, useState } from 'react' import { useValueRef } from '@masknet/shared' import { + AddressName, ChainId, + ERC721ContractDetailed, ERC721TokenDetailed, + formatEthereumAddress, isSameAddress, NonFungibleAssetProvider, SocketState, @@ -10,18 +13,31 @@ import { useCollections, Wallet, } from '@masknet/web3-shared-evm' -import { Box, Button, Skeleton, Typography } from '@mui/material' +import { Box, Button, Skeleton, Stack, styled, Typography } from '@mui/material' import { makeStyles, useStylesExtends } from '@masknet/theme' import { currentNonFungibleAssetDataProviderSettings } from '../../../../plugins/Wallet/settings' import { useI18N } from '../../../../utils' import { CollectibleCard } from './CollectibleCard' -import { Image } from '../../../../components/shared/Image' import { WalletMessages } from '@masknet/plugin-wallet' +import { CollectionIcon } from './CollectionIcon' +import { uniqBy } from 'lodash-unified' +import KeyboardArrowDownIcon from '@mui/icons-material/KeyboardArrowDown' export const CollectibleContext = createContext<{ collectiblesRetry: () => void }>(null!) +const AllNetworkButton = styled(Button)(({ theme }) => ({ + display: 'inline-block', + padding: 0, + borderRadius: '50%', + fontSize: 12, + '&:hover': { + boxShadow: 'none', + }, + opacity: 0.5, +})) + const useStyles = makeStyles()((theme) => ({ root: { display: 'grid', @@ -36,19 +52,18 @@ const useStyles = makeStyles()((theme) => ({ justifyContent: 'center', height: '100%', }, - button: { - marginTop: theme.spacing(1), - }, + button: {}, container: { height: 'calc(100% - 52px)', overflow: 'auto', }, card: { + width: 172, display: 'flex', flexDirection: 'column', alignItems: 'center', position: 'relative', - padding: theme.spacing(1), + padding: theme.spacing(1, 0), }, description: { background: theme.palette.mode === 'light' ? '#F7F9FA' : '#2F3336', @@ -81,6 +96,20 @@ const useStyles = makeStyles()((theme) => ({ height: '100%', borderRadius: '50%', }, + networkSelected: { + width: 24, + height: 24, + minHeight: 24, + minWidth: 24, + lineHeight: '24px', + background: theme.palette.primary.main, + color: '#ffffff', + fontSize: 10, + opacity: 1, + '&:hover': { + background: theme.palette.primary.main, + }, + }, })) interface CollectibleItemProps { @@ -168,7 +197,7 @@ function CollectibleListUI(props: CollectibleListUIProps) { provider={provider} wallet={wallet} readonly={readonly} - key={i} + key={x.tokenId + x.contractDetailed.address} /> ))} @@ -180,7 +209,6 @@ function CollectibleListUI(props: CollectibleListUIProps) { export interface CollectibleListProps extends withClasses<'empty' | 'button'> { address: string - collection?: string collectibles: ERC721TokenDetailed[] error?: string loading: boolean @@ -206,51 +234,63 @@ export function CollectibleList(props: CollectibleListProps) { ) } -export function CollectionList({ address }: { address: string }) { +export function CollectionList({ + addressName, + onSelectAddress, +}: { + addressName: AddressName + onSelectAddress: (event: React.MouseEvent) => void +}) { const chainId = ChainId.Mainnet const { t } = useI18N() const { classes } = useStyles() + const [selectedCollection, setSelectedCollection] = useState('all') + const { resolvedAddress: address } = addressName + + const { data: collectionsFormRemote } = useCollections(address, chainId) - const { - data: collections, - retry: retryFetchCollection, - state: loadingCollectionDone, - } = useCollections(address, chainId) const { data: collectibles, state: loadingCollectibleDone, retry: retryFetchCollectible, - } = useCollectibles(address, chainId, !!collections.length) + } = useCollectibles(address, chainId) - const isLoading = loadingCollectibleDone !== SocketState.done || loadingCollectionDone !== SocketState.done + const isLoading = loadingCollectibleDone !== SocketState.done const renderWithRarible = useMemo(() => { if (isLoading) return [] return collectibles.filter((item) => !item.collection) - }, [collections?.length, collectibles?.length]) + }, [collectibles?.length]) - if (loadingCollectionDone !== SocketState.done) { - return ( - - {Array.from({ length: 3 }) - .fill(0) - .map((_, i) => ( - - - - - ))} - - ) - } + const renderCollectibles = useMemo(() => { + if (selectedCollection === 'all') return collectibles + if (!selectedCollection) return collectibles.filter((x) => !x.collection) + + return (collectibles ?? []).filter((x) => { + return isSameAddress(selectedCollection.address, x.contractDetailed.address) + }) + }, [selectedCollection, collectibles.length]) + + const collections = useMemo(() => { + return uniqBy( + collectibles.map((x) => x.contractDetailed), + (x) => x.address.toLowerCase(), + ).map((x) => { + const item = collectionsFormRemote.find((c) => isSameAddress(c.address, x.address)) + if (item) { + return { + name: item.name, + symbol: item.name, + baseURI: item.iconURL, + iconURL: item.iconURL, + address: item.address, + } as ERC721ContractDetailed + } + return x + }) + }, [collectibles.length, collectionsFormRemote.length]) - if (!isLoading && !collections.length) + if (!isLoading && !collectibles.length) return ( @@ -261,67 +301,100 @@ export function CollectionList({ address }: { address: string }) { return ( - {(collections ?? []).map((x, i) => { - const renderCollectibles = collectibles.filter( - (c) => - isSameAddress(c.contractDetailed.address, x.address) || - x.addresses?.find((r) => isSameAddress(r, c.contractDetailed.address)), - ) - return ( - - - - {x.iconURL ? ( - - ) : null} + + + setSelectedCollection('all')}> + ALL + + theme.palette.primary.main} fontSize="12px"> + {t('dashboard_collectible_menu_all', { + count: collectibles.length, + })} + + + + + + + + + + {!selectedCollection && selectedCollection !== 'all' && ( + + + Other + {loadingCollectibleDone && renderCollectibles.length + ? `(${renderCollectibles.length})` + : null} + - - {x.name} - {loadingCollectibleDone && renderCollectibles.length - ? `(${renderCollectibles.length})` - : null} - - + )} + {selectedCollection && selectedCollection !== 'all' && ( + + + + {selectedCollection.name} + {loadingCollectibleDone && renderCollectibles.length + ? `(${renderCollectibles.length})` + : null} + + + )} { - retryFetchCollectible() - retryFetchCollection() - }} + retry={retryFetchCollectible} collectibles={renderCollectibles} loading={loadingCollectibleDone !== SocketState.done && renderCollectibles.length === 0} /> - ) - })} - {!!renderWithRarible.length && ( - - - - Rarible ({renderWithRarible.length}) - - - { - retryFetchCollectible() - retryFetchCollection() - }} - collectibles={renderWithRarible} - loading={false} - /> - )} + + {collections.map((x, i) => { + return ( + + setSelectedCollection(x)} + /> + + ) + })} + {!!renderWithRarible.length && ( + + setSelectedCollection(undefined)} + /> + + )} + + ) } diff --git a/packages/mask/src/plugins/Collectible/SNSAdaptor/NFTPage.tsx b/packages/mask/src/plugins/Collectible/SNSAdaptor/NFTPage.tsx index b3f43ad5b3f2..d7772572fd63 100644 --- a/packages/mask/src/plugins/Collectible/SNSAdaptor/NFTPage.tsx +++ b/packages/mask/src/plugins/Collectible/SNSAdaptor/NFTPage.tsx @@ -1,8 +1,11 @@ import { getMaskColor, makeStyles } from '@masknet/theme' -import { Box, Typography, Link } from '@mui/material' -import { useI18N } from '../../../utils' -import { AddressName, resolveAddressLinkOnExplorer, formatEthereumAddress, ChainId } from '@masknet/web3-shared-evm' +import { MenuItem } from '@mui/material' +import { ShadowRootMenu } from '../../../utils' +import type { AddressName } from '@masknet/web3-shared-evm' import { CollectionList } from '../../../extension/options-page/DashboardComponents/CollectibleList' +import { useState } from 'react' +import { first, uniqBy } from 'lodash-unified' +import { formatEthereumAddress } from '@masknet/web3-shared-evm' const useStyles = makeStyles()((theme) => ({ root: { @@ -29,34 +32,52 @@ const useStyles = makeStyles()((theme) => ({ listStyleType: 'decimal', paddingLeft: 16, }, + button: { + border: `1px solid ${theme.palette.text.primary} !important`, + color: `${theme.palette.text.primary} !important`, + borderRadius: 9999, + background: 'transparent', + '&:hover': { + background: 'rgba(15, 20, 25, 0.1)', + }, + }, })) export interface NFTPageProps { - addressName?: AddressName + addressNames?: AddressName[] } export function NFTPage(props: NFTPageProps) { - const { addressName } = props + const { addressNames } = props const { classes } = useStyles() - const { t } = useI18N() - if (!addressName) return null + const [anchorEl, setAnchorEl] = useState(null) + + const [selectedAddress, setSelectedAddress] = useState(first(addressNames)) + const onOpen = (event: React.MouseEvent) => setAnchorEl(event.currentTarget) + const onClose = () => setAnchorEl(null) + const onSelect = (option: AddressName) => { + setSelectedAddress(option) + onClose() + } + + if (!selectedAddress) return null return (
- - - - {t('plugin_wallet_nft_wall_current_display')} - - {formatEthereumAddress(addressName.resolvedAddress ?? '', 4)} - - - - - + + {uniqBy(addressNames ?? [], (x) => x.resolvedAddress.toLowerCase()).map((x) => { + return ( + onSelect(x)}> + {formatEthereumAddress(x.label, 5)} + + ) + })} + +
) } diff --git a/packages/mask/src/plugins/Collectible/SNSAdaptor/index.tsx b/packages/mask/src/plugins/Collectible/SNSAdaptor/index.tsx index 171a280e2fa5..5fc6612f35c9 100644 --- a/packages/mask/src/plugins/Collectible/SNSAdaptor/index.tsx +++ b/packages/mask/src/plugins/Collectible/SNSAdaptor/index.tsx @@ -1,4 +1,4 @@ -import { uniq, first } from 'lodash-unified' +import { uniq } from 'lodash-unified' import { Plugin, usePostInfoDetails, usePluginWrapper } from '@masknet/plugin-infra' import { PostInspector } from './PostInspector' import { base } from '../base' @@ -30,9 +30,7 @@ const sns: Plugin.SNSAdaptor.Definition = { label: 'NFTs', priority: 1, UI: { - TabContent: ({ addressNames = [] }) => ( - - ), + TabContent: ({ addressNames = [] }) => , }, Utils: { addressNameSorter: (a, z) => { diff --git a/packages/provider-proxy/src/index.ts b/packages/provider-proxy/src/index.ts index b02c896bc350..ad76efa9d6a6 100644 --- a/packages/provider-proxy/src/index.ts +++ b/packages/provider-proxy/src/index.ts @@ -2,9 +2,11 @@ import type { RPCMethodRegistrationValue } from './types' import fungibleTokenProducer from './producers/fungibleTokenAsset' import nonFungibleCollectionAsset from './producers/nonFungibleCollectionAsset' import nonFungibleCollectibleAsset from './producers/nonFungibleCollectibleAsset' +import nonFungibleCollectibleAssetV2 from './producers/nonFungibleCollectibleAssetV2' export const producers: RPCMethodRegistrationValue[] = [ fungibleTokenProducer, nonFungibleCollectibleAsset, + nonFungibleCollectibleAssetV2, nonFungibleCollectionAsset, ] diff --git a/packages/provider-proxy/src/producers/nonFungibleCollectibleAssetV2.ts b/packages/provider-proxy/src/producers/nonFungibleCollectibleAssetV2.ts new file mode 100644 index 000000000000..a47fb30b34a5 --- /dev/null +++ b/packages/provider-proxy/src/producers/nonFungibleCollectibleAssetV2.ts @@ -0,0 +1,55 @@ +import { getOpenSeaNFTList, getRaribleNFTList, getNFTScanNFTs } from '@masknet/web3-providers' +import type { ERC721TokenDetailed } from '@masknet/web3-shared-evm' +import type { ProducerArgBase, ProducerKeyFunction, ProducerPushFunction, RPCMethodRegistrationValue } from '../types' +import { collectAllPageDate } from '../helper/request' + +export interface NonFungibleTokenAssetArgs extends ProducerArgBase { + address: string +} + +const nonFungibleCollectibleAsset = async ( + push: ProducerPushFunction, + getKeys: ProducerKeyFunction, + args: NonFungibleTokenAssetArgs, +): Promise => { + const { address } = args + const size = 50 + const openSeaApiKey = await getKeys('opensea') + + try { + await collectAllPageDate( + (page) => getOpenSeaNFTList(openSeaApiKey, address, page, size), + size, + push, + ) + } finally { + const fromRarible = collectAllPageDate( + (page, pageInfo) => getRaribleNFTList(openSeaApiKey, address, page, size, pageInfo), + size, + push, + ) + + const formNFTScanERC721 = collectAllPageDate( + (page) => getNFTScanNFTs(address, 'erc721', page, size), + size, + push, + ) + + const fromNFTScanERC1155 = collectAllPageDate( + (page) => getNFTScanNFTs(address, 'erc1155', page, size), + size, + push, + ) + + await Promise.allSettled([fromRarible, formNFTScanERC721, fromNFTScanERC1155]) + } +} + +const producer: RPCMethodRegistrationValue = { + method: 'mask.fetchNonFungibleCollectibleAssetV2', + producer: nonFungibleCollectibleAsset, + distinctBy: (item) => + `${item.tokenId.toLowerCase()}_${item.contractDetailed.address.toLowerCase()}_${item.contractDetailed.chainId}`, +} + +export default producer diff --git a/packages/web3-providers/src/NFTScan/index.ts b/packages/web3-providers/src/NFTScan/index.ts index a5710a6138b7..ca59e1b4b571 100644 --- a/packages/web3-providers/src/NFTScan/index.ts +++ b/packages/web3-providers/src/NFTScan/index.ts @@ -1,4 +1,4 @@ -import { ChainId, createERC721ContractDetailed, createERC721Token } from '@masknet/web3-shared-evm' +import { ChainId, createERC721ContractDetailed, createERC721Token, resolveResourceLink } from '@masknet/web3-shared-evm' import addSeconds from 'date-fns/addSeconds' import isBefore from 'date-fns/isBefore' import urlcat from 'urlcat' @@ -58,7 +58,7 @@ function createERC721TokenAsset(asset: NFTScanAsset) { { name: payload?.name ?? asset.nft_name ?? asset.nft_platform_name ?? '', description: payload?.description ?? '', - mediaUrl: payload?.image ?? '', + mediaUrl: resolveResourceLink(asset.nft_cover ?? asset.nft_content_uri ?? payload.image ?? ''), owner: asset.nft_holder ?? '', }, asset.token_id, @@ -117,7 +117,8 @@ export class NFTScanAPI implements NonFungibleTokenAPI.Provider { data: [], hasNextPage: false, } - const data = response.data.content.map(createERC721TokenAsset) + const data = + response.data.content.map(createERC721TokenAsset).map((x) => ({ ...x, provideBy: 'NFTScan' })) ?? [] const total = response.data.total return { data, diff --git a/packages/web3-providers/src/opensea/index.ts b/packages/web3-providers/src/opensea/index.ts index 86cbd320f630..0b708c25c2a2 100644 --- a/packages/web3-providers/src/opensea/index.ts +++ b/packages/web3-providers/src/opensea/index.ts @@ -63,7 +63,7 @@ function createERC721TokenFromAsset( chainId: ChainId, asset: OpenSeaResponse, ): ERC721TokenDetailed { - const imageURL = asset?.image_url ?? asset?.image_preview_url ?? '' + const imageURL = asset?.image_preview_url ?? asset?.image_url ?? '' return createERC721Token( createERC721ContractFromAssetContract(asset?.asset_contract?.address, chainId, asset?.asset_contract), { @@ -284,7 +284,8 @@ export class OpenSeaAPI implements NonFungibleTokenAPI.Provider { ['non-fungible', 'semi-fungible'].includes(x.asset_contract.asset_contract_type) || ['ERC721', 'ERC1155'].includes(x.asset_contract.schema_name), ) - .map((asset: OpenSeaResponse) => createERC721TokenFromAsset(from, asset.token_id, chainId, asset)) ?? [] + .map((asset: OpenSeaResponse) => createERC721TokenFromAsset(from, asset.token_id, chainId, asset)) + .map((x) => ({ ...x, provideBy: 'OpenSea' })) ?? [] return { data: assets, hasNextPage: assets.length === size, diff --git a/packages/web3-providers/src/rarible/index.ts b/packages/web3-providers/src/rarible/index.ts index 2b7119c04d38..257a50fe1fe7 100644 --- a/packages/web3-providers/src/rarible/index.ts +++ b/packages/web3-providers/src/rarible/index.ts @@ -6,6 +6,7 @@ import { ERC721TokenDetailed, EthereumTokenType, FungibleTokenDetailed, + resolveResourceLink, } from '@masknet/web3-shared-evm' import { Ownership, @@ -16,7 +17,6 @@ import { RaribleProfileResponse, } from './types' import { RaribleUserURL, RaribleRopstenUserURL, RaribleMainnetURL, RaribleChainURL, RaribleURL } from './constants' -import { toRaribleImage } from './utils' import { NonFungibleTokenAPI } from '..' import { isProxyENV } from '../helpers' @@ -51,7 +51,7 @@ function createERC721TokenFromAsset( tokenId: string, asset?: RaribleNFTItemMapResponse, ): ERC721TokenDetailed { - const imageURL = toRaribleImage(asset?.meta.image?.url.ORIGINAL ?? asset?.meta.image?.url.PREVIEW ?? '') + const imageURL = resolveResourceLink(asset?.meta.image?.url.ORIGINAL ?? asset?.meta.image?.url.PREVIEW ?? '') return { contractDetailed: { type: EthereumTokenType.ERC721, @@ -64,7 +64,7 @@ function createERC721TokenFromAsset( name: asset?.meta.name ?? '', description: asset?.meta.description ?? '', mediaUrl: - toRaribleImage(asset?.meta.animation?.url.ORIGINAL ?? asset?.meta.animation?.url.PREVIEW ?? '') || + resolveResourceLink(asset?.meta.animation?.url.ORIGINAL ?? asset?.meta.animation?.url.PREVIEW ?? '') || imageURL, imageURL, owner: asset?.owners[0], @@ -80,7 +80,7 @@ function createNFTAsset(asset: RaribleNFTItemMapResponse, chainId: ChainId): Non is_verified: false, is_auction: false, token_address: asset.contract, - image_url: toRaribleImage(asset?.meta.image?.url.ORIGINAL), + image_url: resolveResourceLink(asset?.meta.image?.url.ORIGINAL ?? ''), asset_contract: null, owner: owner ? { @@ -171,9 +171,11 @@ export class RaribleAPI implements NonFungibleTokenAPI.Provider { hasNextPage: false, } - const data = asset.items - .map((asset) => createERC721TokenFromAsset(asset.contract, asset.tokenId, asset)) - .filter((x) => x.info?.owner?.toLowerCase() === from.toLowerCase()) + const data = + asset.items + .map((asset) => createERC721TokenFromAsset(asset.contract, asset.tokenId, asset)) + .filter((x) => x.info?.owner?.toLowerCase() === from.toLowerCase()) + .map((x) => ({ ...x, provideBy: 'Rarible' })) ?? [] return { data, hasNextPage: !!asset.continuation, @@ -211,7 +213,7 @@ export class RaribleAPI implements NonFungibleTokenAPI.Provider { maker_account: { user: { username: ownerInfo?.name ?? '' }, address: ownerInfo?.id ?? '', - profile_img_url: toRaribleImage(ownerInfo?.image), + profile_img_url: resolveResourceLink(ownerInfo?.image ?? ''), link: `${resolveRaribleUserNetwork(chainId as number)}${ownerInfo?.id ?? ''}`, }, } @@ -242,7 +244,7 @@ export class RaribleAPI implements NonFungibleTokenAPI.Provider { maker_account: { user: { username: ownerInfo?.name ?? '' }, address: ownerInfo?.id ?? '', - profile_img_url: toRaribleImage(ownerInfo?.image), + profile_img_url: resolveResourceLink(ownerInfo?.image ?? ''), link: `${resolveRaribleUserNetwork(chainId as number)}${ownerInfo?.id ?? ''}`, }, } diff --git a/packages/web3-providers/src/rarible/utils.ts b/packages/web3-providers/src/rarible/utils.ts deleted file mode 100644 index 70f4b7d55166..000000000000 --- a/packages/web3-providers/src/rarible/utils.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { resolveIPFSLink } from '@masknet/web3-shared-evm' -export function toRaribleImage(url?: string) { - if (!url) return '' - if (url.startsWith('ipfs://ipfs/')) return resolveIPFSLink(url.replace(/^ipfs:\/\/ipfs\//, '')) - return url -} diff --git a/packages/web3-shared/evm/hooks/useCollectibles.ts b/packages/web3-shared/evm/hooks/useCollectibles.ts index a7f302e9dc19..bbd9f29b9a3b 100644 --- a/packages/web3-shared/evm/hooks/useCollectibles.ts +++ b/packages/web3-shared/evm/hooks/useCollectibles.ts @@ -22,7 +22,7 @@ export function useCollectibles(address: string, chainId: ChainId | null, depend const id = `mask.fetchNonFungibleCollectibleAsset_${address}_${chainId}` const message = { id: dependReady === undefined ? id : dependReady ? id : '', - method: 'mask.fetchNonFungibleCollectibleAsset', + method: 'mask.fetchNonFungibleCollectibleAssetV2', params: { address: address, pageSize: 30, @@ -33,7 +33,11 @@ export function useCollectibles(address: string, chainId: ChainId | null, depend const all = uniqWith( [ ...(data ?? []), - ...erc721Tokens.getCurrentValue().filter((x) => !chainId || x.contractDetailed.chainId === chainId), + ...erc721Tokens + .getCurrentValue() + .filter( + (x) => (!chainId || x.contractDetailed.chainId === chainId) && isSameAddress(x.info.owner, address), + ), ], (a, b) => isSameAddress(a.contractDetailed.address, b.contractDetailed.address) && a.tokenId === b.tokenId, ) diff --git a/packages/web3-shared/evm/pipes/index.ts b/packages/web3-shared/evm/pipes/index.ts index 220c857a797e..070d3a080d36 100644 --- a/packages/web3-shared/evm/pipes/index.ts +++ b/packages/web3-shared/evm/pipes/index.ts @@ -172,7 +172,15 @@ export function resolveBlockLinkOnExplorer(chainId: ChainId, block: string): str } export function resolveIPFSLink(ipfs: string): string { - return urlcat('https://coldcdn.com/api/cdn/mipfsygtms/ipfs/:ipfs', { ipfs }) + return urlcat('https://ipfs.fleek.co/ipfs/:ipfs', { ipfs }) +} + +export function resolveResourceLink(originLink: string): string { + if (!originLink) return '' + if (originLink.startsWith('http') || originLink.startsWith('data')) return originLink + if (originLink.startsWith('ipfs://ipfs/')) return resolveIPFSLink(originLink.replace(/^ipfs:\/\/ipfs\//, '')) + if (originLink.startsWith('ipfs://')) return resolveIPFSLink(decodeURIComponent(originLink).replace('ipfs://', '')) + return resolveIPFSLink(originLink) } export function resolveDomainLink(domain?: string) {