Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NFT collections and instances - link to markets #1277

Merged
merged 2 commits into from
Oct 16, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions configs/app/ui/views/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export { default as block } from './block';
export { default as address } from './address';
export { default as nft } from './nft';
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'}}]

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 @@ -22,6 +22,7 @@ import { type NetworkVerificationType, type NetworkExplorer, type FeaturedNetwor
import { 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 { replaceQuotes } from '../../../configs/app/utils';
import * as regexp from '../../../lib/regexp';
Expand Down Expand Up @@ -248,6 +249,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 @@ -360,6 +369,11 @@ const schema = yup
.json()
.of(yup.string<BlockFieldId>().oneOf(BLOCK_FIELDS_IDS)),
NEXT_PUBLIC_VIEWS_ADDRESS_IDENTICON_TYPE: yup.string().oneOf(IDENTICON_TYPES),
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 @@ -158,6 +158,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 @@ -136,4 +136,6 @@ frontend:
NEXT_PUBLIC_VIEWS_ADDRESS_IDENTICON_TYPE:
_default: gradient_avatar
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 @@ -16,6 +16,7 @@ The app instance could be customized by passing following variables to NodeJS en
- [Meta](ENVS.md#meta)
- [Views](ENVS.md#views)
- [Block](ENVS.md#block-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 @@ -187,6 +188,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.
Loading