Skip to content

Commit

Permalink
NFT collections and instances - link to markets (#1277)
Browse files Browse the repository at this point in the history
Fixes #1213
  • Loading branch information
tom2drum authored Oct 16, 2023
1 parent eae41d1 commit 973d949
Show file tree
Hide file tree
Showing 18 changed files with 125 additions and 4 deletions.
1 change: 1 addition & 0 deletions configs/app/ui/views/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export { default as address } from './address';
export { default as block } from './block';
export { default as nft } from './nft';
export { default as tx } from './tx';
9 changes: 9 additions & 0 deletions configs/app/ui/views/nft.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import type { NftMarketplaceItem } from 'types/views/nft';

import { getEnvValue, parseEnvJson } from 'configs/app/utils';

const config = Object.freeze({
marketplaces: parseEnvJson<Array<NftMarketplaceItem>>(getEnvValue('NEXT_PUBLIC_VIEWS_NFT_MARKETPLACES')) || [],
});

export default config;
2 changes: 2 additions & 0 deletions configs/envs/.env.eth
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs', 'coin_price', 'market_cap']
## sidebar
NEXT_PUBLIC_FEATURED_NETWORKS=https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/featured-networks/eth.json
## footer
##views
NEXT_PUBLIC_VIEWS_NFT_MARKETPLACES=[{'name':'OpenSea','collection_url':'https://opensea.io/assets/ethereum/{hash}','instance_url':'https://opensea.io/assets/ethereum/{hash}/{id}','logo_url':'https://opensea.io/static/images/logos/opensea-logo.svg'},{'name':'LooksRare','collection_url':'https://looksrare.org/collections/{hash}','instance_url':'https://looksrare.org/collections/{hash}/{id}','logo_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/nft-marketplace-logos/looks-rare.png'}]
## misc
NEXT_PUBLIC_NETWORK_EXPLORERS=[{'title':'Etherscan','baseUrl':'https://etherscan.io/','paths':{'tx':'/tx','address':'/address','token':'/token','block':'/block'}}]

Expand Down
2 changes: 2 additions & 0 deletions configs/envs/.env.eth_goerli
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ NEXT_PUBLIC_FEATURED_NETWORKS=https://raw.githubusercontent.com/blockscout/front
NEXT_PUBLIC_NETWORK_LOGO=https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/network-logos/goerli.svg
NEXT_PUBLIC_NETWORK_ICON=https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/network-icons/goerli.svg
## footer
##views
NEXT_PUBLIC_VIEWS_NFT_MARKETPLACES=[{'name':'LooksRare','collection_url':'https://goerli.looksrare.org/collections/{hash}','instance_url':'https://goerli.looksrare.org/collections/{hash}/{id}','logo_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/nft-marketplace-logos/looks-rare.png'}]
## misc
NEXT_PUBLIC_NETWORK_EXPLORERS=[{'title':'Bitquery','baseUrl':'https://explorer.bitquery.io/','paths':{'tx':'/goerli/tx','address':'/goerli/address','token':'/goerli/token','block':'/goerli/block'}},{'title':'Etherscan','baseUrl':'https://goerli.etherscan.io/','paths':{'tx':'/tx','address':'/address','token':'/token','block':'/block'}}]

Expand Down
2 changes: 2 additions & 0 deletions configs/envs/.env.main
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ NEXT_PUBLIC_FEATURED_NETWORKS=https://raw.githubusercontent.com/blockscout/front
NEXT_PUBLIC_NETWORK_LOGO=https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/network-logos/goerli.svg
NEXT_PUBLIC_NETWORK_ICON=https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/network-icons/goerli.svg
## footer
##views
NEXT_PUBLIC_VIEWS_NFT_MARKETPLACES=[{'name':'LooksRare','collection_url':'https://goerli.looksrare.org/collections/{hash}','instance_url':'https://goerli.looksrare.org/collections/{hash}/{id}','logo_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/nft-marketplace-logos/looks-rare.png'}]
## misc
NEXT_PUBLIC_NETWORK_EXPLORERS=[{'title':'Bitquery','baseUrl':'https://explorer.bitquery.io/','paths':{'tx':'/goerli/tx','address':'/goerli/address','token':'/goerli/token','block':'/goerli/block'}},{'title':'Etherscan','baseUrl':'https://goerli.etherscan.io/','paths':{'tx':'/tx','address':'/address','token':'/token','block':'/block'}}]
## views
Expand Down
1 change: 1 addition & 0 deletions configs/envs/.env.pw
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ NEXT_PUBLIC_HOMEPAGE_SHOW_GAS_TRACKER=true
## footer
NEXT_PUBLIC_GIT_TAG=v1.0.11
## views
NEXT_PUBLIC_VIEWS_NFT_MARKETPLACES=[{'name':'OpenSea','collection_url':'https://opensea.io/assets/ethereum/{hash}','instance_url':'https://opensea.io/assets/ethereum/{hash}/{id}','logo_url':'http://localhost:3000/nft-marketplace-logo.png'},{'name':'LooksRare','collection_url':'https://looksrare.org/collections/{hash}','instance_url':'https://looksrare.org/collections/{hash}/{id}','logo_url':'http://localhost:3000/nft-marketplace-logo.png'}]
## misc
NEXT_PUBLIC_NETWORK_EXPLORERS=[{'title':'Bitquery','baseUrl':'https://explorer.bitquery.io/','paths':{'tx':'/goerli/tx','address':'/goerli/address','token':'/goerli/token','block':'/goerli/block'}},{'title':'Etherscan','baseUrl':'https://goerli.etherscan.io/','paths':{'tx':'/tx','address':'/address','token':'/token','block':'/block'}}]
NEXT_PUBLIC_MAINTENANCE_ALERT_MESSAGE=
Expand Down
14 changes: 14 additions & 0 deletions deploy/tools/envs-validator/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import type { AddressViewId } from '../../../types/views/address';
import { ADDRESS_VIEWS_IDS, IDENTICON_TYPES } from '../../../types/views/address';
import { BLOCK_FIELDS_IDS } from '../../../types/views/block';
import type { BlockFieldId } from '../../../types/views/block';
import type { NftMarketplaceItem } from '../../../types/views/nft';
import type { TxAdditionalFieldsId, TxFieldsId } from '../../../types/views/tx';
import { TX_ADDITIONAL_FIELDS_IDS, TX_FIELDS_IDS } from '../../../types/views/tx';

Expand Down Expand Up @@ -251,6 +252,14 @@ const networkExplorerSchema: yup.ObjectSchema<NetworkExplorer> = yup
}),
});

const nftMarketplaceSchema: yup.ObjectSchema<NftMarketplaceItem> = yup
.object({
name: yup.string().required(),
collection_url: yup.string().test(urlTest).required(),
instance_url: yup.string().test(urlTest).required(),
logo_url: yup.string().test(urlTest).required(),
});

const bridgedTokenChainSchema: yup.ObjectSchema<BridgedTokenChain> = yup
.object({
id: yup.string().required(),
Expand Down Expand Up @@ -378,6 +387,11 @@ const schema = yup
.transform(replaceQuotes)
.json()
.of(yup.string<TxAdditionalFieldsId>().oneOf(TX_ADDITIONAL_FIELDS_IDS)),
NEXT_PUBLIC_VIEWS_NFT_MARKETPLACES: yup
.array()
.transform(replaceQuotes)
.json()
.of(nftMarketplaceSchema),

// e. misc
NEXT_PUBLIC_NETWORK_EXPLORERS: yup
Expand Down
1 change: 1 addition & 0 deletions deploy/values/main/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ frontend:
NEXT_PUBLIC_MARKETPLACE_CONFIG_URL: https://raw.githubusercontent.com/blockscout/frontend-configs/dev/configs/marketplace/eth-goerli.json
NEXT_PUBLIC_GRAPHIQL_TRANSACTION: 0xf7d4972356e6ae44ae948d0cf19ef2beaf0e574c180997e969a2837da15e349d
NEXT_PUBLIC_WEB3_WALLETS: "['token_pocket','coinbase','metamask']"
NEXT_PUBLIC_VIEWS_NFT_MARKETPLACES: "[{'name':'LooksRare','collection_url':'https://goerli.looksrare.org/collections/{hash}','instance_url':'https://goerli.looksrare.org/collections/{hash}/{id}','logo_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/nft-marketplace-logos/looks-rare.png'}]"
envFromSecret:
NEXT_PUBLIC_SENTRY_DSN: ref+vault://deployment-values/blockscout/dev/front-main?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/NEXT_PUBLIC_SENTRY_DSN
SENTRY_CSP_REPORT_URI: ref+vault://deployment-values/blockscout/dev/front-main?token_env=VAULT_TOKEN&address=https://vault.k8s.blockscout.com#/SENTRY_CSP_REPORT_URI
Expand Down
4 changes: 3 additions & 1 deletion deploy/values/review/values.yaml.gotmpl
Original file line number Diff line number Diff line change
Expand Up @@ -142,4 +142,6 @@ frontend:
NEXT_PUBLIC_VIEWS_TX_ADDITIONAL_FIELDS:
_default: "['fee_per_gas']"
NEXT_PUBLIC_USE_NEXT_JS_PROXY:
_default: true
_default: true
NEXT_PUBLIC_VIEWS_NFT_MARKETPLACES:
_default: "[{'name':'LooksRare','collection_url':'https://goerli.looksrare.org/collections/{hash}','instance_url':'https://goerli.looksrare.org/collections/{hash}/{id}','logo_url':'https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/nft-marketplace-logos/looks-rare.png'}]"
20 changes: 20 additions & 0 deletions docs/ENVS.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ The app instance could be customized by passing following variables to NodeJS en
- [Block](ENVS.md#block-views)
- [Address](ENVS.md#address-views)
- [Transaction](ENVS.md#transaction-views)
- [NFT](ENVS.md#nft-views)
- [Misc](ENVS.md#misc)
- [App features](ENVS.md#app-features)
- [My account](ENVS.md#my-account)
Expand Down Expand Up @@ -219,6 +220,25 @@ Settings for meta tags and OG tags

&nbsp;

#### NFT views

| Variable | Type | Description | Compulsoriness | Default value | Example value |
| --- | --- | --- | --- | --- | --- |
| NEXT_PUBLIC_VIEWS_NFT_MARKETPLACES | `Array<NftMarketplace>` where `NftMarketplace` can have following [properties](#nft-marketplace-properties) | Used to build up links to NFT collections and NFT instances in external marketplaces. | - | - | `[{'name':'OpenSea','collection_url':'https://opensea.io/assets/ethereum/{hash}','instance_url':'https://opensea.io/assets/ethereum/{hash}/{id}','logo_url':'https://opensea.io/static/images/logos/opensea-logo.svg'}]` |


##### NFT marketplace properties
| Variable | Type| Description | Compulsoriness | Default value | Example value |
| --- | --- | --- | --- | --- | --- |
| name | `string` | Displayed name of the marketplace | Required | - | `OpenSea` |
| collection_url | `string` | URL template for NFT collection | Required | - | `https://opensea.io/assets/ethereum/{hash}` |
| instance_url | `string` | URL template for NFT instance | Required | - | `https://opensea.io/assets/ethereum/{hash}/{id}` |
| logo_url | `string` | URL of marketplace logo | Required | - | `https://opensea.io/static/images/logos/opensea-logo.svg` |

*Note* URL templates should contain placeholders of NFT hash (`{hash}`) and NFT id (`{id}`). This placeholders will be substituted with particular values for every collection or instance.

&nbsp;

### Misc

| Variable | Type| Description | Compulsoriness | Default value | Example value |
Expand Down
6 changes: 6 additions & 0 deletions types/views/nft.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export interface NftMarketplaceItem {
name: string;
collection_url: string;
instance_url: string;
logo_url: string;
}
12 changes: 9 additions & 3 deletions ui/token/TokenDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,29 +15,32 @@ import DetailsInfoItem from 'ui/shared/DetailsInfoItem';
import DetailsSponsoredItem from 'ui/shared/DetailsSponsoredItem';
import TruncatedValue from 'ui/shared/TruncatedValue';

import TokenNftMarketplaces from './TokenNftMarketplaces';

interface Props {
tokenQuery: UseQueryResult<TokenInfo>;
}

const TokenDetails = ({ tokenQuery }: Props) => {
const router = useRouter();
const hash = router.query.hash?.toString();

const tokenCountersQuery = useApiQuery('token_counters', {
pathParams: { hash: router.query.hash?.toString() },
pathParams: { hash },
queryOptions: { enabled: Boolean(router.query.hash), placeholderData: TOKEN_COUNTERS },
});

const changeUrlAndScroll = useCallback((tab: TokenTabs) => () => {
router.push(
{ pathname: '/token/[hash]', query: { hash: router.query.hash?.toString() || '', tab } },
{ pathname: '/token/[hash]', query: { hash: hash || '', tab } },
undefined,
{ shallow: true },
);
scroller.scrollTo('token-tabs', {
duration: 500,
smooth: true,
});
}, [ router ]);
}, [ hash, router ]);

const countersItem = useCallback((item: 'token_holders_count' | 'transfers_count') => {
const itemValue = tokenCountersQuery.data?.[item];
Expand Down Expand Up @@ -158,6 +161,9 @@ const TokenDetails = ({ tokenQuery }: Props) => {
</Skeleton>
</DetailsInfoItem>
) }

{ type !== 'ERC-20' && <TokenNftMarketplaces hash={ hash } isLoading={ tokenQuery.isPlaceholderData }/> }

<DetailsSponsoredItem isLoading={ tokenQuery.isPlaceholderData }/>
</Grid>
);
Expand Down
49 changes: 49 additions & 0 deletions ui/token/TokenNftMarketplaces.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { Image, Link, Skeleton, Tooltip } from '@chakra-ui/react';
import React from 'react';

import config from 'configs/app';
import DetailsInfoItem from 'ui/shared/DetailsInfoItem';

interface Props {
hash: string | undefined;
id?: string;
isLoading?: boolean;
}

const TokenNftMarketplaces = ({ hash, id, isLoading }: Props) => {
if (!hash || config.UI.views.nft.marketplaces.length === 0) {
return null;
}

return (
<DetailsInfoItem
title="Marketplaces"
hint="Marketplaces trading this NFT"
alignSelf="center"
isLoading={ isLoading }
>
<Skeleton isLoaded={ !isLoading } display="flex" columnGap={ 3 } flexWrap="wrap">
{ config.UI.views.nft.marketplaces.map((item) => {

const hrefTemplate = id ? item.instance_url : item.collection_url;
const href = hrefTemplate.replace('{id}', id || '').replace('{hash}', hash || '');

return (
<Tooltip label={ `View on ${ item.name }` } key={ item.name }>
<Link href={ href } target="_blank">
<Image
src={ item.logo_url }
alt={ `${ item.name } marketplace logo` }
boxSize={ 5 }
borderRadius="full"
/>
</Link>
</Tooltip>
);
}) }
</Skeleton>
</DetailsInfoItem>
);
};

export default React.memo(TokenNftMarketplaces);
4 changes: 4 additions & 0 deletions ui/tokenInstance/TokenInstanceDetails.pw.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ const API_URL_TOKEN_TRANSFERS_COUNT = buildApiUrl('token_instance_transfers_coun
});

test('base view +@dark-mode +@mobile', async({ mount, page }) => {
await page.route('http://localhost:3000/nft-marketplace-logo.png', (route) => route.fulfill({
status: 200,
path: './playwright/mocks/image_s.jpg',
}));
await page.route(API_URL_ADDRESS, (route) => route.fulfill({
status: 200,
body: JSON.stringify(addressMock.contract),
Expand Down
2 changes: 2 additions & 0 deletions ui/tokenInstance/TokenInstanceDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import AddressEntity from 'ui/shared/entities/address/AddressEntity';
import TokenEntity from 'ui/shared/entities/token/TokenEntity';
import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic';
import NftMedia from 'ui/shared/nft/NftMedia';
import TokenNftMarketplaces from 'ui/token/TokenNftMarketplaces';

import TokenInstanceCreatorAddress from './details/TokenInstanceCreatorAddress';
import TokenInstanceMetadataInfo from './details/TokenInstanceMetadataInfo';
Expand Down Expand Up @@ -81,6 +82,7 @@ const TokenInstanceDetails = ({ data, scrollRef, isLoading }: Props) => {
</Flex>
</DetailsInfoItem>
<TokenInstanceTransfersCount hash={ isLoading ? '' : data.token.address } id={ isLoading ? '' : data.id } onClick={ handleCounterItemClick }/>
<TokenNftMarketplaces isLoading={ isLoading } hash={ data.token.address } id={ data.id }/>
</Grid>
<NftMedia
url={ data.animation_url || data.image_url }
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 973d949

Please sign in to comment.