Commit 266aa165 authored by tom's avatar tom

Merge branch 'main' of github.com:blockscout/frontend into long-skeleton

parents 8d116569 c9cfe3cc
...@@ -53,3 +53,7 @@ NEXT_PUBLIC_VISUALIZE_API_HOST=__PLACEHOLDER_FOR_NEXT_PUBLIC_VISUALIZE_API_HOST_ ...@@ -53,3 +53,7 @@ NEXT_PUBLIC_VISUALIZE_API_HOST=__PLACEHOLDER_FOR_NEXT_PUBLIC_VISUALIZE_API_HOST_
NEXT_PUBLIC_SENTRY_DSN=__PLACEHOLDER_FOR_NEXT_PUBLIC_SENTRY_DSN__ NEXT_PUBLIC_SENTRY_DSN=__PLACEHOLDER_FOR_NEXT_PUBLIC_SENTRY_DSN__
NEXT_PUBLIC_AUTH0_CLIENT_ID=__PLACEHOLDER_FOR_NEXT_PUBLIC_AUTH0_CLIENT_ID__ NEXT_PUBLIC_AUTH0_CLIENT_ID=__PLACEHOLDER_FOR_NEXT_PUBLIC_AUTH0_CLIENT_ID__
NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID=__PLACEHOLDER_FOR_NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID__ NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID=__PLACEHOLDER_FOR_NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID__
# l2 config
NEXT_PUBLIC_IS_L2_NETWORK=__PLACEHOLDER_FOR_NEXT_PUBLIC_IS_L2_NETWORKL__
NEXT_PUBLIC_L1_BASE_URL=__PLACEHOLDER_FOR_NEXT_PUBLIC_L1_BASE_URL__
...@@ -44,8 +44,8 @@ ...@@ -44,8 +44,8 @@
} }
}, },
{ {
"type": "npm", "type": "shell",
"script": "dev:goerli", "command": "NEXT_PUBLIC_API_HOST=${input:goerliApiHost} yarn dev:goerli",
"problemMatcher": [], "problemMatcher": [],
"label": "dev server: goerli", "label": "dev server: goerli",
"detail": "start local dev server for Goerli network", "detail": "start local dev server for Goerli network",
...@@ -327,5 +327,15 @@ ...@@ -327,5 +327,15 @@
], ],
"default": "" "default": ""
}, },
{
"type": "pickString",
"id": "goerliApiHost",
"description": "Choose API host:",
"options": [
"blockscout-main.test.aws-k8s.blockscout.com",
"eth-goerli.blockscout.com",
],
"default": ""
},
], ],
} }
\ No newline at end of file
...@@ -128,6 +128,12 @@ The app instance could be customized by passing following variables to NodeJS en ...@@ -128,6 +128,12 @@ The app instance could be customized by passing following variables to NodeJS en
| NEXT_PUBLIC_AUTH0_CLIENT_ID | `string` *(optional)* | Client id for [Auth0](https://auth0.com/) provider | `<secret>` | | NEXT_PUBLIC_AUTH0_CLIENT_ID | `string` *(optional)* | Client id for [Auth0](https://auth0.com/) provider | `<secret>` |
| NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID | `string` | Project id for [WalletConnect](https://docs.walletconnect.com/2.0/web3modal/react/installation#obtain-project-id) integration | `<secret>` | | NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID | `string` | Project id for [WalletConnect](https://docs.walletconnect.com/2.0/web3modal/react/installation#obtain-project-id) integration | `<secret>` |
### L2 configuration
| Variable | Type | Description | Default value
| --- | --- | --- | --- |
| NEXT_PUBLIC_IS_L2_NETWORK | `boolean` *(optional)* | Set to true for L2 solutions (Optimism Bedrock based) | false |
| NEXT_PUBLIC_L1_BASE_URL | `string` *(optional)* | Base Blockscout URL for L1 network | `'http://eth-goerli.blockscout.com'` |
### Marketplace app configuration properties ### Marketplace app configuration properties
| Property | Type | Description | Example value | Property | Type | Description | Example value
......
...@@ -104,6 +104,10 @@ const config = Object.freeze({ ...@@ -104,6 +104,10 @@ const config = Object.freeze({
socket: apiHost ? `wss://${ apiHost }` : 'wss://blockscout.com', socket: apiHost ? `wss://${ apiHost }` : 'wss://blockscout.com',
basePath: stripTrailingSlash(getEnvValue(process.env.NEXT_PUBLIC_API_BASE_PATH) || ''), basePath: stripTrailingSlash(getEnvValue(process.env.NEXT_PUBLIC_API_BASE_PATH) || ''),
}, },
L2: {
isL2Network: getEnvValue(process.env.NEXT_PUBLIC_IS_L2_NETWORK) === 'true',
L1BaseUrl: getEnvValue(process.env.NEXT_PUBLIC_L1_BASE_URL),
},
statsApi: { statsApi: {
endpoint: getEnvValue(process.env.NEXT_PUBLIC_STATS_API_HOST), endpoint: getEnvValue(process.env.NEXT_PUBLIC_STATS_API_HOST),
basePath: '', basePath: '',
......
This diff is collapsed.
...@@ -40,6 +40,10 @@ blockscout: ...@@ -40,6 +40,10 @@ blockscout:
_default: ENC[AES256_GCM,data:mTY6sjNKZ+VEvH47eyyoUHt//beWvuxyreu+1WrmMvkdkwR6jgXnSXh+1pTuiG8e3it96Egxraz0hjZxlkY9kSmE91/dfoqKTns=,iv:op8OuOXXeBmmUnVkCL14j4HrfjHHoN3n2XZqLdg03Mw=,tag:bbFQYi0Aa1sUdfoUjuQ/+w==,type:str] _default: ENC[AES256_GCM,data:mTY6sjNKZ+VEvH47eyyoUHt//beWvuxyreu+1WrmMvkdkwR6jgXnSXh+1pTuiG8e3it96Egxraz0hjZxlkY9kSmE91/dfoqKTns=,iv:op8OuOXXeBmmUnVkCL14j4HrfjHHoN3n2XZqLdg03Mw=,tag:bbFQYi0Aa1sUdfoUjuQ/+w==,type:str]
ACCOUNT_AUTH0_LOGOUT_URL: ACCOUNT_AUTH0_LOGOUT_URL:
_default: ENC[AES256_GCM,data:IZFfi6pn+hy7g0wnEtP9TYHH1fNiC2gqgRHVdgm4C9smPerEvS0pq9dBwVY=,iv:BxbSInFQ6GE2loTv+IzdYr25PlyzdWZI1wdT6r+uvBg=,tag:psujCU372k59OGmlRdH9Fg==,type:str] _default: ENC[AES256_GCM,data:IZFfi6pn+hy7g0wnEtP9TYHH1fNiC2gqgRHVdgm4C9smPerEvS0pq9dBwVY=,iv:BxbSInFQ6GE2loTv+IzdYr25PlyzdWZI1wdT6r+uvBg=,tag:psujCU372k59OGmlRdH9Fg==,type:str]
RE_CAPTCHA_SECRET_KEY:
_default: ENC[AES256_GCM,data:e9jfs4PwY+UPk0EuXZxERylKUFSUo6zsAz8oly3YwwybipP+A5rSBQ==,iv:TvrD/a+6+11IGDVFLUCn8U+H3v1YfIRrcndOVdt/taI=,tag:OAFVIznkeeeVX9UsigfP4A==,type:str]
RE_CAPTCHA_CLIENT_KEY:
_default: ENC[AES256_GCM,data:6GPj8CQddvUofViL26AIBsVeMaYyrQ1Mh0kYUJ0HiIJc0otDewUc2Q==,iv:1LR4GBdSqN6ciKNCRI67d8oPWsTXnVI55hPSMvLL4hE=,tag:58pE8s9X7+eMOCY6bl4Z5w==,type:str]
scVerifier: scVerifier:
environment: environment:
SMART_CONTRACT_VERIFIER__SOLIDITY__FETCHER__S3__ACCESS_KEY: SMART_CONTRACT_VERIFIER__SOLIDITY__FETCHER__S3__ACCESS_KEY:
...@@ -134,8 +138,8 @@ sops: ...@@ -134,8 +138,8 @@ sops:
azure_kv: [] azure_kv: []
hc_vault: [] hc_vault: []
age: [] age: []
lastmodified: "2023-02-14T08:03:14Z" lastmodified: "2023-02-19T10:34:54Z"
mac: ENC[AES256_GCM,data:B2AkQb4I83dP3UUitRCcrUfzm3nWmcknIUoMWHyYaG9jasnccbr8zZatYdpbvKFcELVTtjhYk6ly5Sx7+6sk2PZm6o7dN3yHG5lSWmnZqNXkwo42GIk/F6vzDdLutZsu8HH8pWHd9y5R272CIPOOh4+Ur0OtwiGgj3Bp1od76qM=,iv:j7aIPflH0FsYhE/iylvBh5nDmVdghhxAFvaeXlR560k=,tag:/oe6OeitIHaZ4TgM7w/0pg==,type:str] mac: ENC[AES256_GCM,data:c7Nlguw+82tpgNAclx6XgXQip+gHh37Wrb9pq0MQ+bgp3LmfWpNDYYtxvdkIxMPNYsLxQOvw9Kc26qYWY1qDggmkHFvIeImCScjxBmrjSAf1K61lk0NIQ7AnEVh7ahipAopUO7y/0ogcjyjJCGs/QPRg9yelsONhCIjRkeUi7mQ=,iv:Y5tWWJJCqdNujHczKnouk4TiHBwMBezKhKCmvWeL9Kc=,tag:hIjUobEooUAQuKwHFraryw==,type:str]
pgp: pgp:
- created_at: "2022-09-14T13:42:28Z" - created_at: "2022-09-14T13:42:28Z"
enc: | enc: |
......
...@@ -23,8 +23,15 @@ import type { LogsResponseTx, LogsResponseAddress } from 'types/api/log'; ...@@ -23,8 +23,15 @@ import type { LogsResponseTx, LogsResponseAddress } from 'types/api/log';
import type { RawTracesResponse } from 'types/api/rawTrace'; import type { RawTracesResponse } from 'types/api/rawTrace';
import type { SearchResult, SearchResultFilters } from 'types/api/search'; import type { SearchResult, SearchResultFilters } from 'types/api/search';
import type { Counters, StatsCharts, StatsChart, HomeStats } from 'types/api/stats'; import type { Counters, StatsCharts, StatsChart, HomeStats } from 'types/api/stats';
import type { TokenCounters, TokenInfo, TokenHolders, TokenInventoryResponse, TokenInstance, TokenInstanceTransfersCount } from 'types/api/token'; import type {
import type { TokensResponse, TokensFilters } from 'types/api/tokens'; TokenCounters,
TokenInfo,
TokenHolders,
TokenInventoryResponse,
TokenInstance,
TokenInstanceTransfersCount,
} from 'types/api/token';
import type { TokensResponse, TokensFilters, TokenInstanceTransferResponse } from 'types/api/tokens';
import type { TokenTransferResponse, TokenTransferFilters } from 'types/api/tokenTransfer'; import type { TokenTransferResponse, TokenTransferFilters } from 'types/api/tokenTransfer';
import type { TransactionsResponseValidated, TransactionsResponsePending, Transaction } from 'types/api/transaction'; import type { TransactionsResponseValidated, TransactionsResponsePending, Transaction } from 'types/api/transaction';
import type { TTxsFilters } from 'types/api/txsFilters'; import type { TTxsFilters } from 'types/api/txsFilters';
...@@ -293,6 +300,12 @@ export const RESOURCES = { ...@@ -293,6 +300,12 @@ export const RESOURCES = {
path: '/api/v2/tokens/:hash/instances/:id/transfers-count', path: '/api/v2/tokens/:hash/instances/:id/transfers-count',
pathParams: [ 'hash' as const, 'id' as const ], pathParams: [ 'hash' as const, 'id' as const ],
}, },
token_instance_transfers: {
path: '/api/v2/tokens/:hash/instances/:id/transfers',
pathParams: [ 'hash' as const, 'id' as const ],
paginationFields: [ 'block_number' as const, 'items_count' as const, 'index' as const, 'token_id' as const ],
filterFields: [],
},
// HOMEPAGE // HOMEPAGE
homepage_stats: { homepage_stats: {
...@@ -376,7 +389,8 @@ export type PaginatedResources = 'blocks' | 'block_txs' | ...@@ -376,7 +389,8 @@ export type PaginatedResources = 'blocks' | 'block_txs' |
'address_txs' | 'address_internal_txs' | 'address_token_transfers' | 'address_blocks_validated' | 'address_coin_balance' | 'address_txs' | 'address_internal_txs' | 'address_token_transfers' | 'address_blocks_validated' | 'address_coin_balance' |
'search' | 'search' |
'address_logs' | 'address_tokens' | 'address_logs' | 'address_tokens' |
'token_transfers' | 'token_holders' | 'token_inventory' | 'tokens'; 'token_transfers' | 'token_holders' | 'token_inventory' | 'tokens' |
'token_instance_transfers';
export type PaginatedResponse<Q extends PaginatedResources> = ResourcePayload<Q>; export type PaginatedResponse<Q extends PaginatedResources> = ResourcePayload<Q>;
...@@ -425,6 +439,7 @@ Q extends 'token_transfers' ? TokenTransferResponse : ...@@ -425,6 +439,7 @@ Q extends 'token_transfers' ? TokenTransferResponse :
Q extends 'token_holders' ? TokenHolders : Q extends 'token_holders' ? TokenHolders :
Q extends 'token_instance' ? TokenInstance : Q extends 'token_instance' ? TokenInstance :
Q extends 'token_instance_transfers_count' ? TokenInstanceTransfersCount : Q extends 'token_instance_transfers_count' ? TokenInstanceTransfersCount :
Q extends 'token_instance_transfers' ? TokenInstanceTransferResponse :
Q extends 'token_inventory' ? TokenInventoryResponse : Q extends 'token_inventory' ? TokenInventoryResponse :
Q extends 'tokens' ? TokensResponse : Q extends 'tokens' ? TokensResponse :
Q extends 'search' ? SearchResult : Q extends 'search' ? SearchResult :
......
import type { GetServerSideProps } from 'next';
import appConfig from 'configs/app/config';
import type { Props } from 'lib/next/getServerSideProps';
import { getServerSideProps as getServerSidePropsBase } from 'lib/next/getServerSideProps';
export const getServerSideProps: GetServerSideProps<Props> = async(args) => {
if (!appConfig.L2.isL2Network) {
return {
notFound: true,
};
}
return getServerSidePropsBase(args);
};
...@@ -112,7 +112,7 @@ export const erc1155: TokenTransfer = { ...@@ -112,7 +112,7 @@ export const erc1155: TokenTransfer = {
exchange_rate: null, exchange_rate: null,
holders: '1', holders: '1',
name: null, name: null,
symbol: null, symbol: 'MY_SYMBOL_IS_VERY_LONG',
type: 'ERC-1155', type: 'ERC-1155',
total_supply: '0', total_supply: '0',
}, },
......
...@@ -12,6 +12,7 @@ ...@@ -12,6 +12,7 @@
"dev:local": "./node_modules/.bin/dotenv -e ./configs/envs/.env.localhost -e ./configs/envs/.env.secrets next dev | ./node_modules/.bin/pino-pretty", "dev:local": "./node_modules/.bin/dotenv -e ./configs/envs/.env.localhost -e ./configs/envs/.env.secrets next dev | ./node_modules/.bin/pino-pretty",
"dev:poa_core": "./node_modules/.bin/dotenv -e ./configs/envs/.env.poa_core -e ./configs/envs/.env.common -e ./configs/envs/.env.secrets next dev | ./node_modules/.bin/pino-pretty", "dev:poa_core": "./node_modules/.bin/dotenv -e ./configs/envs/.env.poa_core -e ./configs/envs/.env.common -e ./configs/envs/.env.secrets next dev | ./node_modules/.bin/pino-pretty",
"dev:goerli": "./node_modules/.bin/dotenv -e ./configs/envs/.env.goerli -e ./configs/envs/.env.common -e ./configs/envs/.env.secrets next dev | ./node_modules/.bin/pino-pretty", "dev:goerli": "./node_modules/.bin/dotenv -e ./configs/envs/.env.goerli -e ./configs/envs/.env.common -e ./configs/envs/.env.secrets next dev | ./node_modules/.bin/pino-pretty",
"dev:goerli:l2test": "./node_modules/.bin/dotenv -e ./configs/envs/.env.goerli.l2test -e ./configs/envs/.env.common -e ./configs/envs/.env.secrets next dev | ./node_modules/.bin/pino-pretty",
"build": "next build", "build": "next build",
"build:docker": "docker build --build-arg GIT_COMMIT_SHA=$(git rev-parse HEAD) -t blockscout ./", "build:docker": "docker build --build-arg GIT_COMMIT_SHA=$(git rev-parse HEAD) -t blockscout ./",
"start": "next start", "start": "next start",
......
...@@ -46,7 +46,6 @@ export type TokenTransferPagination = { ...@@ -46,7 +46,6 @@ export type TokenTransferPagination = {
block_number: number; block_number: number;
index: number; index: number;
items_count: number; items_count: number;
transaction_hash: string;
} }
export interface TokenTransferResponse { export interface TokenTransferResponse {
......
import type { TokenInfo, TokenType } from './token'; import type { TokenInfo, TokenType } from './token';
import type { TokenTransfer } from './tokenTransfer';
export type TokensResponse = { export type TokensResponse = {
items: Array<TokenInfo>; items: Array<TokenInfo>;
...@@ -10,3 +11,15 @@ export type TokensResponse = { ...@@ -10,3 +11,15 @@ export type TokensResponse = {
} }
export type TokensFilters = { filter: string; type: Array<TokenType> | undefined }; export type TokensFilters = { filter: string; type: Array<TokenType> | undefined };
export interface TokenInstanceTransferResponse {
items: Array<TokenTransfer>;
next_page_params: TokenInstanceTransferPagination | null;
}
export interface TokenInstanceTransferPagination {
block_number: number;
index: number;
items_count: number;
token_id: string;
}
...@@ -106,7 +106,7 @@ const AddressDetails = ({ addressQuery, scrollRef }: Props) => { ...@@ -106,7 +106,7 @@ const AddressDetails = ({ addressQuery, scrollRef }: Props) => {
{ data.is_contract && data.creation_tx_hash && data.creator_address_hash && ( { data.is_contract && data.creation_tx_hash && data.creator_address_hash && (
<DetailsInfoItem <DetailsInfoItem
title="Creator" title="Creator"
hint="Transaction and address of creation." hint="Transaction and address of creation"
> >
<AddressLink type="address" hash={ data.creator_address_hash } truncation="constant"/> <AddressLink type="address" hash={ data.creator_address_hash } truncation="constant"/>
<Text whiteSpace="pre"> at txn </Text> <Text whiteSpace="pre"> at txn </Text>
...@@ -116,7 +116,7 @@ const AddressDetails = ({ addressQuery, scrollRef }: Props) => { ...@@ -116,7 +116,7 @@ const AddressDetails = ({ addressQuery, scrollRef }: Props) => {
{ data.is_contract && data.implementation_address && ( { data.is_contract && data.implementation_address && (
<DetailsInfoItem <DetailsInfoItem
title="Implementation" title="Implementation"
hint="Implementation address of the proxy contract." hint="Implementation address of the proxy contract"
columnGap={ 1 } columnGap={ 1 }
> >
<LinkInternal href={ route({ pathname: '/address/[hash]', query: { hash: data.implementation_address } }) } overflow="hidden"> <LinkInternal href={ route({ pathname: '/address/[hash]', query: { hash: data.implementation_address } }) } overflow="hidden">
...@@ -133,7 +133,7 @@ const AddressDetails = ({ addressQuery, scrollRef }: Props) => { ...@@ -133,7 +133,7 @@ const AddressDetails = ({ addressQuery, scrollRef }: Props) => {
{ data.has_tokens && ( { data.has_tokens && (
<DetailsInfoItem <DetailsInfoItem
title="Tokens" title="Tokens"
hint="All tokens in the account and total value." hint="All tokens in the account and total value"
alignSelf="center" alignSelf="center"
py={ 0 } py={ 0 }
> >
...@@ -142,7 +142,7 @@ const AddressDetails = ({ addressQuery, scrollRef }: Props) => { ...@@ -142,7 +142,7 @@ const AddressDetails = ({ addressQuery, scrollRef }: Props) => {
) } ) }
<DetailsInfoItem <DetailsInfoItem
title="Transactions" title="Transactions"
hint="Number of transactions related to this address." hint="Number of transactions related to this address"
> >
{ addressQuery.data ? { addressQuery.data ?
<AddressCounterItem prop="transactions_count" query={ countersQuery } address={ data.hash } onClick={ handleCounterItemClick }/> : <AddressCounterItem prop="transactions_count" query={ countersQuery } address={ data.hash } onClick={ handleCounterItemClick }/> :
...@@ -151,7 +151,7 @@ const AddressDetails = ({ addressQuery, scrollRef }: Props) => { ...@@ -151,7 +151,7 @@ const AddressDetails = ({ addressQuery, scrollRef }: Props) => {
{ data.has_token_transfers && ( { data.has_token_transfers && (
<DetailsInfoItem <DetailsInfoItem
title="Transfers" title="Transfers"
hint="Number of transfers to/from this address." hint="Number of transfers to/from this address"
> >
{ addressQuery.data ? { addressQuery.data ?
<AddressCounterItem prop="token_transfers_count" query={ countersQuery } address={ data.hash } onClick={ handleCounterItemClick }/> : <AddressCounterItem prop="token_transfers_count" query={ countersQuery } address={ data.hash } onClick={ handleCounterItemClick }/> :
...@@ -160,7 +160,7 @@ const AddressDetails = ({ addressQuery, scrollRef }: Props) => { ...@@ -160,7 +160,7 @@ const AddressDetails = ({ addressQuery, scrollRef }: Props) => {
) } ) }
<DetailsInfoItem <DetailsInfoItem
title="Gas used" title="Gas used"
hint="Gas used by the address." hint="Gas used by the address"
> >
{ addressQuery.data ? { addressQuery.data ?
<AddressCounterItem prop="gas_usage_count" query={ countersQuery } address={ data.hash } onClick={ handleCounterItemClick }/> : <AddressCounterItem prop="gas_usage_count" query={ countersQuery } address={ data.hash } onClick={ handleCounterItemClick }/> :
...@@ -169,7 +169,7 @@ const AddressDetails = ({ addressQuery, scrollRef }: Props) => { ...@@ -169,7 +169,7 @@ const AddressDetails = ({ addressQuery, scrollRef }: Props) => {
{ data.has_validated_blocks && ( { data.has_validated_blocks && (
<DetailsInfoItem <DetailsInfoItem
title="Blocks validated" title="Blocks validated"
hint="Number of blocks validated by this validator." hint="Number of blocks validated by this validator"
> >
{ addressQuery.data ? { addressQuery.data ?
<AddressCounterItem prop="validations_count" query={ countersQuery } address={ data.hash } onClick={ handleCounterItemClick }/> : <AddressCounterItem prop="validations_count" query={ countersQuery } address={ data.hash } onClick={ handleCounterItemClick }/> :
...@@ -179,7 +179,7 @@ const AddressDetails = ({ addressQuery, scrollRef }: Props) => { ...@@ -179,7 +179,7 @@ const AddressDetails = ({ addressQuery, scrollRef }: Props) => {
{ data.block_number_balance_updated_at && ( { data.block_number_balance_updated_at && (
<DetailsInfoItem <DetailsInfoItem
title="Last balance update" title="Last balance update"
hint="Block number in which the address was updated." hint="Block number in which the address was updated"
alignSelf="center" alignSelf="center"
py={{ base: '2px', lg: 1 }} py={{ base: '2px', lg: 1 }}
> >
......
...@@ -4,6 +4,7 @@ import { route } from 'nextjs-routes'; ...@@ -4,6 +4,7 @@ import { route } from 'nextjs-routes';
import React from 'react'; import React from 'react';
import useApiQuery from 'lib/api/useApiQuery'; import useApiQuery from 'lib/api/useApiQuery';
import dayjs from 'lib/date/dayjs';
import getQueryParamString from 'lib/router/getQueryParamString'; import getQueryParamString from 'lib/router/getQueryParamString';
import Address from 'ui/shared/address/Address'; import Address from 'ui/shared/address/Address';
import AddressIcon from 'ui/shared/address/AddressIcon'; import AddressIcon from 'ui/shared/address/AddressIcon';
...@@ -15,12 +16,12 @@ import RawDataSnippet from 'ui/shared/RawDataSnippet'; ...@@ -15,12 +16,12 @@ import RawDataSnippet from 'ui/shared/RawDataSnippet';
import ContractSourceCode from './ContractSourceCode'; import ContractSourceCode from './ContractSourceCode';
const InfoItem = ({ label, value }: { label: string; value: string }) => ( const InfoItem = chakra(({ label, value, className }: { label: string; value: string; className?: string }) => (
<GridItem display="flex" columnGap={ 6 }> <GridItem display="flex" columnGap={ 6 } wordBreak="break-all" className={ className }>
<Text w="170px" flexShrink={ 0 } fontWeight={ 500 }>{ label }</Text> <Text w="170px" flexShrink={ 0 } fontWeight={ 500 }>{ label }</Text>
<Text wordBreak="break-all">{ value }</Text> <Text>{ value }</Text>
</GridItem> </GridItem>
); ));
const ContractCode = () => { const ContractCode = () => {
const router = useRouter(); const router = useRouter();
...@@ -153,10 +154,10 @@ const ContractCode = () => { ...@@ -153,10 +154,10 @@ const ContractCode = () => {
<Grid templateColumns={{ base: '1fr', lg: '1fr 1fr' }} rowGap={ 4 } columnGap={ 6 } mb={ 8 }> <Grid templateColumns={{ base: '1fr', lg: '1fr 1fr' }} rowGap={ 4 } columnGap={ 6 } mb={ 8 }>
{ data.name && <InfoItem label="Contract name" value={ data.name }/> } { data.name && <InfoItem label="Contract name" value={ data.name }/> }
{ data.compiler_version && <InfoItem label="Compiler version" value={ data.compiler_version }/> } { data.compiler_version && <InfoItem label="Compiler version" value={ data.compiler_version }/> }
{ data.evm_version && <InfoItem label="EVM version" value={ data.evm_version }/> } { data.evm_version && <InfoItem label="EVM version" value={ data.evm_version } textTransform="capitalize"/> }
{ typeof data.optimization_enabled === 'boolean' && <InfoItem label="Optimization enabled" value={ data.optimization_enabled ? 'true' : 'false' }/> } { typeof data.optimization_enabled === 'boolean' && <InfoItem label="Optimization enabled" value={ data.optimization_enabled ? 'true' : 'false' }/> }
{ data.optimization_runs && <InfoItem label="Optimization runs" value={ String(data.optimization_runs) }/> } { data.optimization_runs && <InfoItem label="Optimization runs" value={ String(data.optimization_runs) }/> }
{ data.verified_at && <InfoItem label="Verified at" value={ data.verified_at }/> } { data.verified_at && <InfoItem label="Verified at" value={ dayjs(data.verified_at).format('LLLL') } wordBreak="break-word"/> }
</Grid> </Grid>
) } ) }
<Flex flexDir="column" rowGap={ 6 }> <Flex flexDir="column" rowGap={ 6 }>
......
...@@ -9,7 +9,7 @@ import ContractRead from './ContractRead'; ...@@ -9,7 +9,7 @@ import ContractRead from './ContractRead';
const addressHash = 'hash'; const addressHash = 'hash';
const CONTRACT_READ_METHODS_API_URL = buildApiUrl('contract_methods_read', { hash: addressHash }) + '?is_custom_abi=false'; const CONTRACT_READ_METHODS_API_URL = buildApiUrl('contract_methods_read', { hash: addressHash }) + '?is_custom_abi=false';
const CONTRACT_QUERY_METHOD_API_URL = buildApiUrl('contract_method_query', { hash: addressHash }); const CONTRACT_QUERY_METHOD_API_URL = buildApiUrl('contract_method_query', { hash: addressHash }) + '?is_custom_abi=false';
const hooksConfig = { const hooksConfig = {
router: { router: {
query: { hash: addressHash }, query: { hash: addressHash },
......
...@@ -35,6 +35,7 @@ const ContractRead = ({ isProxy, isCustomAbi }: Props) => { ...@@ -35,6 +35,7 @@ const ContractRead = ({ isProxy, isCustomAbi }: Props) => {
pathParams: { hash: addressHash }, pathParams: { hash: addressHash },
queryParams: { queryParams: {
is_custom_abi: isCustomAbi ? 'true' : 'false', is_custom_abi: isCustomAbi ? 'true' : 'false',
from: userAddress,
}, },
queryOptions: { queryOptions: {
enabled: Boolean(addressHash), enabled: Boolean(addressHash),
...@@ -44,6 +45,9 @@ const ContractRead = ({ isProxy, isCustomAbi }: Props) => { ...@@ -44,6 +45,9 @@ const ContractRead = ({ isProxy, isCustomAbi }: Props) => {
const handleMethodFormSubmit = React.useCallback(async(item: SmartContractReadMethod, args: Array<string | Array<string>>) => { const handleMethodFormSubmit = React.useCallback(async(item: SmartContractReadMethod, args: Array<string | Array<string>>) => {
return apiFetch<'contract_method_query', SmartContractQueryMethodRead>('contract_method_query', { return apiFetch<'contract_method_query', SmartContractQueryMethodRead>('contract_method_query', {
pathParams: { hash: addressHash }, pathParams: { hash: addressHash },
queryParams: {
is_custom_abi: isCustomAbi ? 'true' : 'false',
},
fetchParams: { fetchParams: {
method: 'POST', method: 'POST',
body: { body: {
...@@ -54,11 +58,11 @@ const ContractRead = ({ isProxy, isCustomAbi }: Props) => { ...@@ -54,11 +58,11 @@ const ContractRead = ({ isProxy, isCustomAbi }: Props) => {
}, },
}, },
}); });
}, [ addressHash, apiFetch, isProxy, userAddress ]); }, [ addressHash, apiFetch, isCustomAbi, isProxy, userAddress ]);
const renderContent = React.useCallback((item: SmartContractReadMethod, index: number, id: number) => { const renderContent = React.useCallback((item: SmartContractReadMethod, index: number, id: number) => {
if (item.error) { if (item.error) {
return <Alert status="error" fontSize="sm">{ item.error }</Alert>; return <Alert status="error" fontSize="sm" wordBreak="break-word">{ item.error }</Alert>;
} }
if (item.outputs.some(({ value }) => value)) { if (item.outputs.some(({ value }) => value)) {
......
...@@ -18,12 +18,12 @@ const ContractReadResult = ({ item, result, onSettle }: Props) => { ...@@ -18,12 +18,12 @@ const ContractReadResult = ({ item, result, onSettle }: Props) => {
}, [ onSettle ]); }, [ onSettle ]);
if ('status' in result) { if ('status' in result) {
return <Alert status="error" mt={ 3 } p={ 4 } borderRadius="md" fontSize="sm">{ result.statusText }</Alert>; return <Alert status="error" mt={ 3 } p={ 4 } borderRadius="md" fontSize="sm" wordBreak="break-word">{ result.statusText }</Alert>;
} }
if (result.is_error) { if (result.is_error) {
const message = 'error' in result.result ? result.result.error : result.result.message; const message = 'error' in result.result ? result.result.error : result.result.message;
return <Alert status="error" mt={ 3 } p={ 4 } borderRadius="md" fontSize="sm">{ message }</Alert>; return <Alert status="error" mt={ 3 } p={ 4 } borderRadius="md" fontSize="sm" wordBreak="break-word">{ message }</Alert>;
} }
return ( return (
......
...@@ -42,8 +42,18 @@ const ContractWrite = ({ isProxy, isCustomAbi }: Props) => { ...@@ -42,8 +42,18 @@ const ContractWrite = ({ isProxy, isCustomAbi }: Props) => {
}, },
}); });
const { contract, proxy } = useContractContext(); const { contract, proxy, custom } = useContractContext();
const _contract = isProxy ? proxy : contract; const _contract = (() => {
if (isProxy) {
return proxy;
}
if (isCustomAbi) {
return custom;
}
return contract;
})();
const handleMethodFormSubmit = React.useCallback(async(item: SmartContractWriteMethod, args: Array<string | Array<string>>) => { const handleMethodFormSubmit = React.useCallback(async(item: SmartContractWriteMethod, args: Array<string | Array<string>>) => {
if (!isConnected) { if (!isConnected) {
...@@ -52,7 +62,7 @@ const ContractWrite = ({ isProxy, isCustomAbi }: Props) => { ...@@ -52,7 +62,7 @@ const ContractWrite = ({ isProxy, isCustomAbi }: Props) => {
try { try {
if (!_contract) { if (!_contract) {
return; throw new Error('Something went wrong. Try again later.');
} }
if (item.type === 'receive') { if (item.type === 'receive') {
......
...@@ -15,11 +15,13 @@ type ProviderProps = { ...@@ -15,11 +15,13 @@ type ProviderProps = {
type TContractContext = { type TContractContext = {
contract: Contract | null; contract: Contract | null;
proxy: Contract | null; proxy: Contract | null;
custom: Contract | null;
}; };
const ContractContext = React.createContext<TContractContext>({ const ContractContext = React.createContext<TContractContext>({
contract: null, contract: null,
proxy: null, proxy: null,
custom: null,
}); });
export function ContractContextProvider({ children }: ProviderProps) { export function ContractContextProvider({ children }: ProviderProps) {
...@@ -49,21 +51,36 @@ export function ContractContextProvider({ children }: ProviderProps) { ...@@ -49,21 +51,36 @@ export function ContractContextProvider({ children }: ProviderProps) {
}, },
}); });
const { data: customInfo } = useApiQuery('contract_methods_write', {
pathParams: { hash: addressHash },
queryParams: { is_custom_abi: 'true' },
queryOptions: {
enabled: Boolean(addressInfo?.has_custom_methods_write),
refetchOnMount: false,
},
});
const contract = useContract({ const contract = useContract({
address: addressHash, address: addressHash,
abi: contractInfo?.abi || undefined, abi: contractInfo?.abi ?? undefined,
signerOrProvider: signer || provider, signerOrProvider: signer ?? provider,
}); });
const proxy = useContract({ const proxy = useContract({
address: addressInfo?.implementation_address ?? undefined, address: addressInfo?.implementation_address ?? undefined,
abi: proxyInfo?.abi ?? undefined, abi: proxyInfo?.abi ?? undefined,
signerOrProvider: signer ?? provider, signerOrProvider: signer ?? provider,
}); });
const custom = useContract({
address: addressHash,
abi: customInfo ?? undefined,
signerOrProvider: signer ?? provider,
});
const value = React.useMemo(() => ({ const value = React.useMemo(() => ({
contract, contract,
proxy, proxy,
}), [ contract, proxy ]); custom,
}), [ contract, proxy, custom ]);
return ( return (
<ContractContext.Provider value={ value }> <ContractContext.Provider value={ value }>
......
...@@ -66,7 +66,7 @@ const AddressBalance = ({ data }: Props) => { ...@@ -66,7 +66,7 @@ const AddressBalance = ({ data }: Props) => {
return ( return (
<DetailsInfoItem <DetailsInfoItem
title="Balance" title="Balance"
hint={ `Address balance in ${ appConfig.network.currency.symbol }. Doesn't include ERC20, ERC721 and ERC1155 tokens.` } hint={ `Address balance in ${ appConfig.network.currency.symbol }. Doesn't include ERC20, ERC721 and ERC1155 tokens` }
flexWrap="nowrap" flexWrap="nowrap"
alignItems="flex-start" alignItems="flex-start"
> >
......
...@@ -86,7 +86,7 @@ const BlockDetails = () => { ...@@ -86,7 +86,7 @@ const BlockDetails = () => {
<Grid columnGap={ 8 } rowGap={{ base: 3, lg: 3 }} templateColumns={{ base: 'minmax(0, 1fr)', lg: 'auto minmax(0, 1fr)' }} overflow="hidden"> <Grid columnGap={ 8 } rowGap={{ base: 3, lg: 3 }} templateColumns={{ base: 'minmax(0, 1fr)', lg: 'auto minmax(0, 1fr)' }} overflow="hidden">
<DetailsInfoItem <DetailsInfoItem
title="Block height" title="Block height"
hint="The block height of a particular block is defined as the number of blocks preceding it in the blockchain." hint="The block height of a particular block is defined as the number of blocks preceding it in the blockchain"
> >
{ data.height } { data.height }
{ data.height === 0 && <Text whiteSpace="pre"> - Genesis Block</Text> } { data.height === 0 && <Text whiteSpace="pre"> - Genesis Block</Text> }
...@@ -100,7 +100,7 @@ const BlockDetails = () => { ...@@ -100,7 +100,7 @@ const BlockDetails = () => {
</DetailsInfoItem> </DetailsInfoItem>
<DetailsInfoItem <DetailsInfoItem
title="Size" title="Size"
hint="Size of the block in bytes." hint="Size of the block in bytes"
> >
{ data.size.toLocaleString('en') } { data.size.toLocaleString('en') }
</DetailsInfoItem> </DetailsInfoItem>
...@@ -115,7 +115,7 @@ const BlockDetails = () => { ...@@ -115,7 +115,7 @@ const BlockDetails = () => {
</DetailsInfoItem> </DetailsInfoItem>
<DetailsInfoItem <DetailsInfoItem
title="Transactions" title="Transactions"
hint="The number of transactions in the block." hint="The number of transactions in the block"
> >
<LinkInternal href={ route({ pathname: '/block/[height]', query: { height, tab: 'txs' } }) }> <LinkInternal href={ route({ pathname: '/block/[height]', query: { height, tab: 'txs' } }) }>
{ data.tx_count } transactions { data.tx_count } transactions
...@@ -123,7 +123,7 @@ const BlockDetails = () => { ...@@ -123,7 +123,7 @@ const BlockDetails = () => {
</DetailsInfoItem> </DetailsInfoItem>
<DetailsInfoItem <DetailsInfoItem
title={ appConfig.network.verificationType === 'validation' ? 'Validated by' : 'Mined by' } title={ appConfig.network.verificationType === 'validation' ? 'Validated by' : 'Mined by' }
hint="A block producer who successfully included the block onto the blockchain." hint="A block producer who successfully included the block onto the blockchain"
columnGap={ 1 } columnGap={ 1 }
> >
<AddressLink type="address" hash={ data.miner.hash }/> <AddressLink type="address" hash={ data.miner.hash }/>
...@@ -136,7 +136,7 @@ const BlockDetails = () => { ...@@ -136,7 +136,7 @@ const BlockDetails = () => {
title="Block reward" title="Block reward"
hint={ hint={
`For each block, the ${ validatorTitle } is rewarded with a finite amount of ${ appConfig.network.currency.symbol || 'native token' } `For each block, the ${ validatorTitle } is rewarded with a finite amount of ${ appConfig.network.currency.symbol || 'native token' }
on top of the fees paid for all transactions in the block.` on top of the fees paid for all transactions in the block`
} }
columnGap={ 1 } columnGap={ 1 }
> >
...@@ -173,7 +173,7 @@ const BlockDetails = () => { ...@@ -173,7 +173,7 @@ const BlockDetails = () => {
key={ type } key={ type }
title={ type } title={ type }
// is this text correct for validators? // is this text correct for validators?
hint={ `Amount of distributed reward. ${ capitalize(validatorTitle) }s receive a static block reward + Tx fees + uncle fees.` } hint={ `Amount of distributed reward. ${ capitalize(validatorTitle) }s receive a static block reward + Tx fees + uncle fees` }
> >
{ BigNumber(reward).dividedBy(WEI).toFixed() } { appConfig.network.currency.symbol } { BigNumber(reward).dividedBy(WEI).toFixed() } { appConfig.network.currency.symbol }
</DetailsInfoItem> </DetailsInfoItem>
...@@ -184,7 +184,7 @@ const BlockDetails = () => { ...@@ -184,7 +184,7 @@ const BlockDetails = () => {
<DetailsInfoItem <DetailsInfoItem
title="Gas used" title="Gas used"
hint="The total gas amount used in the block and its percentage of gas filled in the block." hint="The total gas amount used in the block and its percentage of gas filled in the block"
> >
<Text>{ BigNumber(data.gas_used || 0).toFormat() }</Text> <Text>{ BigNumber(data.gas_used || 0).toFormat() }</Text>
<Utilization <Utilization
...@@ -197,14 +197,14 @@ const BlockDetails = () => { ...@@ -197,14 +197,14 @@ const BlockDetails = () => {
</DetailsInfoItem> </DetailsInfoItem>
<DetailsInfoItem <DetailsInfoItem
title="Gas limit" title="Gas limit"
hint="Total gas limit provided by all transactions in the block." hint="Total gas limit provided by all transactions in the block"
> >
<Text>{ BigNumber(data.gas_limit).toFormat() }</Text> <Text>{ BigNumber(data.gas_limit).toFormat() }</Text>
</DetailsInfoItem> </DetailsInfoItem>
{ data.base_fee_per_gas && ( { data.base_fee_per_gas && (
<DetailsInfoItem <DetailsInfoItem
title="Base fee per gas" title="Base fee per gas"
hint="Minimum fee required per unit of gas. Fee adjusts based on network congestion." hint="Minimum fee required per unit of gas. Fee adjusts based on network congestion"
> >
<Text>{ BigNumber(data.base_fee_per_gas).dividedBy(WEI).toFixed() } { appConfig.network.currency.symbol } </Text> <Text>{ BigNumber(data.base_fee_per_gas).dividedBy(WEI).toFixed() } { appConfig.network.currency.symbol } </Text>
<Text variant="secondary" whiteSpace="pre"> <Text variant="secondary" whiteSpace="pre">
...@@ -217,7 +217,7 @@ const BlockDetails = () => { ...@@ -217,7 +217,7 @@ const BlockDetails = () => {
hint={ hint={
`Amount of ${ appConfig.network.currency.symbol || 'native token' } burned from transactions included in the block. `Amount of ${ appConfig.network.currency.symbol || 'native token' } burned from transactions included in the block.
Equals Block Base Fee per Gas * Gas Used.` Equals Block Base Fee per Gas * Gas Used`
} }
> >
<Icon as={ flameIcon } boxSize={ 5 } color="gray.500"/> <Icon as={ flameIcon } boxSize={ 5 } color="gray.500"/>
...@@ -236,7 +236,7 @@ const BlockDetails = () => { ...@@ -236,7 +236,7 @@ const BlockDetails = () => {
{ data.priority_fee !== null && BigNumber(data.priority_fee).gt(ZERO) && ( { data.priority_fee !== null && BigNumber(data.priority_fee).gt(ZERO) && (
<DetailsInfoItem <DetailsInfoItem
title="Priority fee / Tip" title="Priority fee / Tip"
hint="User-defined tips sent to validator for transaction priority/inclusion." hint="User-defined tips sent to validator for transaction priority/inclusion"
> >
{ BigNumber(data.priority_fee).dividedBy(WEI).toFixed() } { appConfig.network.currency.symbol } { BigNumber(data.priority_fee).dividedBy(WEI).toFixed() } { appConfig.network.currency.symbol }
</DetailsInfoItem> </DetailsInfoItem>
...@@ -244,7 +244,7 @@ const BlockDetails = () => { ...@@ -244,7 +244,7 @@ const BlockDetails = () => {
{ /* api doesn't support extra data yet */ } { /* api doesn't support extra data yet */ }
{ /* <DetailsInfoItem { /* <DetailsInfoItem
title="Extra data" title="Extra data"
hint={ `Any data that can be included by the ${ validatorTitle } in the block.` } hint={ `Any data that can be included by the ${ validatorTitle } in the block` }
> >
<Text whiteSpace="pre">{ data.extra_data } </Text> <Text whiteSpace="pre">{ data.extra_data } </Text>
<Text variant="secondary">(Hex: { data.extra_data })</Text> <Text variant="secondary">(Hex: { data.extra_data })</Text>
...@@ -273,7 +273,7 @@ const BlockDetails = () => { ...@@ -273,7 +273,7 @@ const BlockDetails = () => {
<DetailsInfoItem <DetailsInfoItem
title="Difficulty" title="Difficulty"
hint={ `Block difficulty for ${ validatorTitle }, used to calibrate block generation time.` } hint={ `Block difficulty for ${ validatorTitle }, used to calibrate block generation time` }
whiteSpace="normal" whiteSpace="normal"
wordBreak="break-all" wordBreak="break-all"
> >
...@@ -281,7 +281,7 @@ const BlockDetails = () => { ...@@ -281,7 +281,7 @@ const BlockDetails = () => {
</DetailsInfoItem> </DetailsInfoItem>
<DetailsInfoItem <DetailsInfoItem
title="Total difficulty" title="Total difficulty"
hint="Total difficulty of the chain until this block." hint="Total difficulty of the chain until this block"
whiteSpace="normal" whiteSpace="normal"
wordBreak="break-all" wordBreak="break-all"
> >
...@@ -292,7 +292,7 @@ const BlockDetails = () => { ...@@ -292,7 +292,7 @@ const BlockDetails = () => {
<DetailsInfoItem <DetailsInfoItem
title="Hash" title="Hash"
hint="The SHA256 hash of the block." hint="The SHA256 hash of the block"
flexWrap="nowrap" flexWrap="nowrap"
> >
<Box overflow="hidden"> <Box overflow="hidden">
...@@ -303,7 +303,7 @@ const BlockDetails = () => { ...@@ -303,7 +303,7 @@ const BlockDetails = () => {
{ data.height > 0 && ( { data.height > 0 && (
<DetailsInfoItem <DetailsInfoItem
title="Parent hash" title="Parent hash"
hint="The hash of the block from which this block was generated." hint="The hash of the block from which this block was generated"
flexWrap="nowrap" flexWrap="nowrap"
> >
<AddressLink hash={ data.parent_hash } type="block" id={ String(data.height - 1) }/> <AddressLink hash={ data.parent_hash } type="block" id={ String(data.height - 1) }/>
...@@ -313,13 +313,13 @@ const BlockDetails = () => { ...@@ -313,13 +313,13 @@ const BlockDetails = () => {
{ /* api doesn't support state root yet */ } { /* api doesn't support state root yet */ }
{ /* <DetailsInfoItem { /* <DetailsInfoItem
title="State root" title="State root"
hint="The root of the state trie." hint="The root of the state trie"
> >
<Text wordBreak="break-all" whiteSpace="break-spaces">{ data.state_root }</Text> <Text wordBreak="break-all" whiteSpace="break-spaces">{ data.state_root }</Text>
</DetailsInfoItem> */ } </DetailsInfoItem> */ }
<DetailsInfoItem <DetailsInfoItem
title="Nonce" title="Nonce"
hint="Block nonce is a value used during mining to demonstrate proof of work for a block." hint="Block nonce is a value used during mining to demonstrate proof of work for a block"
> >
{ data.nonce } { data.nonce }
</DetailsInfoItem> </DetailsInfoItem>
......
...@@ -84,7 +84,7 @@ const TokenPageContent = () => { ...@@ -84,7 +84,7 @@ const TokenPageContent = () => {
}); });
const tabs: Array<RoutedTab> = [ const tabs: Array<RoutedTab> = [
{ id: 'token_transfers', title: 'Token transfers', component: <TokenTransfer transfersQuery={ transfersQuery } token={ tokenQuery.data }/> }, { id: 'token_transfers', title: 'Token transfers', component: <TokenTransfer transfersQuery={ transfersQuery }/> },
{ id: 'holders', title: 'Holders', component: <TokenHolders tokenQuery={ tokenQuery } holdersQuery={ holdersQuery }/> }, { id: 'holders', title: 'Holders', component: <TokenHolders tokenQuery={ tokenQuery } holdersQuery={ holdersQuery }/> },
]; ];
......
import { Box, Icon, Link, chakra } from '@chakra-ui/react'; import { Box, Icon, chakra } from '@chakra-ui/react';
import { route } from 'nextjs-routes'; import { route } from 'nextjs-routes';
import React from 'react'; import React from 'react';
import nftPlaceholder from 'icons/nft_shield.svg'; import nftPlaceholder from 'icons/nft_shield.svg';
import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic'; import HashStringShortenDynamic from 'ui/shared/HashStringShortenDynamic';
import LinkInternal from 'ui/shared/LinkInternal';
interface Props { interface Props {
hash: string; hash: string;
id: string; id: string;
className?: string; className?: string;
isDisabled?: boolean;
} }
const TokenTransferNft = ({ hash, id, className }: Props) => { const TokenTransferNft = ({ hash, id, className, isDisabled }: Props) => {
const Component = isDisabled ? Box : LinkInternal;
return ( return (
<Link <Component
href={ route({ pathname: '/token/[hash]/instance/[id]', query: { hash, id } }) } href={ isDisabled ? undefined : route({ pathname: '/token/[hash]/instance/[id]', query: { hash, id } }) }
overflow="hidden" overflow="hidden"
whiteSpace="nowrap" whiteSpace="nowrap"
display="flex" display="flex"
...@@ -26,7 +30,7 @@ const TokenTransferNft = ({ hash, id, className }: Props) => { ...@@ -26,7 +30,7 @@ const TokenTransferNft = ({ hash, id, className }: Props) => {
<Box maxW="calc(100% - 34px)"> <Box maxW="calc(100% - 34px)">
<HashStringShortenDynamic hash={ id } fontWeight={ 500 }/> <HashStringShortenDynamic hash={ id } fontWeight={ 500 }/>
</Box> </Box>
</Link> </Component>
); );
}; };
......
...@@ -86,7 +86,7 @@ const TokenDetails = ({ tokenQuery }: Props) => { ...@@ -86,7 +86,7 @@ const TokenDetails = ({ tokenQuery }: Props) => {
{ exchangeRate && ( { exchangeRate && (
<DetailsInfoItem <DetailsInfoItem
title="Price" title="Price"
hint="Price per token on the exchanges." hint="Price per token on the exchanges"
alignSelf="center" alignSelf="center"
> >
{ `$${ exchangeRate }` } { `$${ exchangeRate }` }
...@@ -95,7 +95,7 @@ const TokenDetails = ({ tokenQuery }: Props) => { ...@@ -95,7 +95,7 @@ const TokenDetails = ({ tokenQuery }: Props) => {
{ totalValue?.usd && ( { totalValue?.usd && (
<DetailsInfoItem <DetailsInfoItem
title="Fully diluted market cap" title="Fully diluted market cap"
hint="Total supply * Price." hint="Total supply * Price"
alignSelf="center" alignSelf="center"
> >
{ `$${ totalValue?.usd }` } { `$${ totalValue?.usd }` }
...@@ -103,7 +103,7 @@ const TokenDetails = ({ tokenQuery }: Props) => { ...@@ -103,7 +103,7 @@ const TokenDetails = ({ tokenQuery }: Props) => {
) } ) }
<DetailsInfoItem <DetailsInfoItem
title="Max total supply" title="Max total supply"
hint="The total amount of tokens issued." hint="The total amount of tokens issued"
alignSelf="center" alignSelf="center"
wordBreak="break-word" wordBreak="break-word"
whiteSpace="pre-wrap" whiteSpace="pre-wrap"
...@@ -112,7 +112,7 @@ const TokenDetails = ({ tokenQuery }: Props) => { ...@@ -112,7 +112,7 @@ const TokenDetails = ({ tokenQuery }: Props) => {
</DetailsInfoItem> </DetailsInfoItem>
<DetailsInfoItem <DetailsInfoItem
title="Holders" title="Holders"
hint="Number of accounts holding the token." hint="Number of accounts holding the token"
alignSelf="center" alignSelf="center"
> >
{ tokenCountersQuery.isLoading && <Skeleton w={ 20 } h={ 4 }/> } { tokenCountersQuery.isLoading && <Skeleton w={ 20 } h={ 4 }/> }
...@@ -120,7 +120,7 @@ const TokenDetails = ({ tokenQuery }: Props) => { ...@@ -120,7 +120,7 @@ const TokenDetails = ({ tokenQuery }: Props) => {
</DetailsInfoItem> </DetailsInfoItem>
<DetailsInfoItem <DetailsInfoItem
title="Transfers" title="Transfers"
hint="Number of transfer for the token." hint="Number of transfer for the token"
alignSelf="center" alignSelf="center"
> >
{ tokenCountersQuery.isLoading && <Skeleton w={ 20 } h={ 4 }/> } { tokenCountersQuery.isLoading && <Skeleton w={ 20 } h={ 4 }/> }
...@@ -129,7 +129,7 @@ const TokenDetails = ({ tokenQuery }: Props) => { ...@@ -129,7 +129,7 @@ const TokenDetails = ({ tokenQuery }: Props) => {
{ decimals && ( { decimals && (
<DetailsInfoItem <DetailsInfoItem
title="Decimals" title="Decimals"
hint="Number of digits that come after the decimal place when displaying token value." hint="Number of digits that come after the decimal place when displaying token value"
alignSelf="center" alignSelf="center"
> >
{ decimals } { decimals }
......
...@@ -66,7 +66,7 @@ const TokenInventory = ({ inventoryQuery }: Props) => { ...@@ -66,7 +66,7 @@ const TokenInventory = ({ inventoryQuery }: Props) => {
rowGap={{ base: 3, lg: 6 }} rowGap={{ base: 3, lg: 6 }}
gridTemplateColumns={{ base: 'repeat(2, calc((100% - 12px)/2))', lg: 'repeat(auto-fill, minmax(210px, 1fr))' }} gridTemplateColumns={{ base: 'repeat(2, calc((100% - 12px)/2))', lg: 'repeat(auto-fill, minmax(210px, 1fr))' }}
> >
{ items.map((item) => <TokenInventoryItem key={ item.token.address } item={ item }/>) } { items.map((item) => <TokenInventoryItem key={ item.token.address + '_' + item.id } item={ item }/>) }
</Grid></> </Grid></>
); );
}; };
......
import { Flex, Link, Text, LinkBox, LinkOverlay, useColorModeValue, Hide } from '@chakra-ui/react'; import { Flex, Text, LinkBox, LinkOverlay, useColorModeValue, Hide } from '@chakra-ui/react';
import { route } from 'nextjs-routes'; import NextLink from 'next/link';
import React from 'react'; import React from 'react';
import type { TokenInstance } from 'types/api/token'; import type { TokenInstance } from 'types/api/token';
...@@ -7,14 +7,13 @@ import type { TokenInstance } from 'types/api/token'; ...@@ -7,14 +7,13 @@ import type { TokenInstance } from 'types/api/token';
import Address from 'ui/shared/address/Address'; import Address from 'ui/shared/address/Address';
import AddressIcon from 'ui/shared/address/AddressIcon'; import AddressIcon from 'ui/shared/address/AddressIcon';
import AddressLink from 'ui/shared/address/AddressLink'; import AddressLink from 'ui/shared/address/AddressLink';
import LinkInternal from 'ui/shared/LinkInternal';
import NftMedia from 'ui/shared/nft/NftMedia'; import NftMedia from 'ui/shared/nft/NftMedia';
import TruncatedTextTooltip from 'ui/shared/TruncatedTextTooltip'; import TruncatedTextTooltip from 'ui/shared/TruncatedTextTooltip';
type Props = { item: TokenInstance }; type Props = { item: TokenInstance };
const NFTItem = ({ item }: Props) => { const NFTItem = ({ item }: Props) => {
const tokenLink = route({ pathname: '/token/[hash]/instance/[id]', query: { hash: item.token.address, id: item.id } });
return ( return (
<LinkBox <LinkBox
w={{ base: '100%', lg: '210px' }} w={{ base: '100%', lg: '210px' }}
...@@ -27,24 +26,26 @@ const NFTItem = ({ item }: Props) => { ...@@ -27,24 +26,26 @@ const NFTItem = ({ item }: Props) => {
fontWeight={ 500 } fontWeight={ 500 }
lineHeight="20px" lineHeight="20px"
> >
<LinkOverlay href={ tokenLink }> <NextLink href={{ pathname: '/token/[hash]/instance/[id]', query: { hash: item.token.address, id: item.id } }} passHref>
<NftMedia <LinkOverlay>
mb="18px" <NftMedia
imageUrl={ item.image_url } mb="18px"
animationUrl={ item.animation_url } imageUrl={ item.image_url }
/> animationUrl={ item.animation_url }
</LinkOverlay> />
</LinkOverlay>
</NextLink>
{ item.id && ( { item.id && (
<Flex mb={ 2 } ml={ 1 }> <Flex mb={ 2 } ml={ 1 }>
<Text whiteSpace="pre" variant="secondary">ID# </Text> <Text whiteSpace="pre" variant="secondary">ID# </Text>
<TruncatedTextTooltip label={ item.id }> <TruncatedTextTooltip label={ item.id }>
<Link <LinkInternal
overflow="hidden" overflow="hidden"
whiteSpace="nowrap" whiteSpace="nowrap"
textOverflow="ellipsis" textOverflow="ellipsis"
> >
{ item.id } { item.id }
</Link> </LinkInternal>
</TruncatedTextTooltip> </TruncatedTextTooltip>
</Flex> </Flex>
) } ) }
......
...@@ -2,7 +2,6 @@ import { Box } from '@chakra-ui/react'; ...@@ -2,7 +2,6 @@ import { Box } from '@chakra-ui/react';
import { test, expect } from '@playwright/experimental-ct-react'; import { test, expect } from '@playwright/experimental-ct-react';
import React from 'react'; import React from 'react';
import { tokenInfo } from 'mocks/tokens/tokenInfo';
import * as tokenTransferMock from 'mocks/tokens/tokenTransfer'; import * as tokenTransferMock from 'mocks/tokens/tokenTransfer';
import TestApp from 'playwright/TestApp'; import TestApp from 'playwright/TestApp';
...@@ -13,7 +12,6 @@ test('erc20 +@mobile', async({ mount }) => { ...@@ -13,7 +12,6 @@ test('erc20 +@mobile', async({ mount }) => {
<TestApp> <TestApp>
<Box h={{ base: '134px', lg: '100px' }}/> <Box h={{ base: '134px', lg: '100px' }}/>
<TokenTransfer <TokenTransfer
token={ tokenInfo }
// eslint-disable-next-line @typescript-eslint/ban-ts-comment // eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: // @ts-ignore:
transfersQuery={{ transfersQuery={{
...@@ -38,7 +36,6 @@ test('erc721 +@mobile', async({ mount }) => { ...@@ -38,7 +36,6 @@ test('erc721 +@mobile', async({ mount }) => {
<TestApp> <TestApp>
<Box h={{ base: '134px', lg: '100px' }}/> <Box h={{ base: '134px', lg: '100px' }}/>
<TokenTransfer <TokenTransfer
token={{ ...tokenInfo, type: 'ERC-721' }}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment // eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: // @ts-ignore:
transfersQuery={{ transfersQuery={{
...@@ -63,7 +60,6 @@ test('erc1155 +@mobile', async({ mount }) => { ...@@ -63,7 +60,6 @@ test('erc1155 +@mobile', async({ mount }) => {
<TestApp> <TestApp>
<Box h={{ base: '134px', lg: '100px' }}/> <Box h={{ base: '134px', lg: '100px' }}/>
<TokenTransfer <TokenTransfer
token={{ ...tokenInfo, type: 'ERC-1155', symbol: tokenTransferMock.erc1155multiple.token.symbol }}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment // eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: // @ts-ignore:
transfersQuery={{ transfersQuery={{
......
...@@ -4,7 +4,6 @@ import { useRouter } from 'next/router'; ...@@ -4,7 +4,6 @@ import { useRouter } from 'next/router';
import React from 'react'; import React from 'react';
import type { SocketMessage } from 'lib/socket/types'; import type { SocketMessage } from 'lib/socket/types';
import type { TokenInfo } from 'types/api/token';
import type { TokenTransferResponse } from 'types/api/tokenTransfer'; import type { TokenTransferResponse } from 'types/api/tokenTransfer';
import useGradualIncrement from 'lib/hooks/useGradualIncrement'; import useGradualIncrement from 'lib/hooks/useGradualIncrement';
...@@ -23,14 +22,14 @@ import TokenTransferList from 'ui/token/TokenTransfer/TokenTransferList'; ...@@ -23,14 +22,14 @@ import TokenTransferList from 'ui/token/TokenTransfer/TokenTransferList';
import TokenTransferTable from 'ui/token/TokenTransfer/TokenTransferTable'; import TokenTransferTable from 'ui/token/TokenTransfer/TokenTransferTable';
type Props = { type Props = {
token?: TokenInfo;
transfersQuery: UseQueryResult<TokenTransferResponse> & { transfersQuery: UseQueryResult<TokenTransferResponse> & {
pagination: PaginationProps; pagination: PaginationProps;
isPaginationVisible: boolean; isPaginationVisible: boolean;
}; };
tokenId?: string;
} }
const TokenTransfer = ({ transfersQuery, token }: Props) => { const TokenTransfer = ({ transfersQuery, tokenId }: Props) => {
const isMobile = useIsMobile(); const isMobile = useIsMobile();
const router = useRouter(); const router = useRouter();
const { isError, isLoading, data, pagination, isPaginationVisible } = transfersQuery; const { isError, isLoading, data, pagination, isPaginationVisible } = transfersQuery;
...@@ -92,12 +91,10 @@ const TokenTransfer = ({ transfersQuery, token }: Props) => { ...@@ -92,12 +91,10 @@ const TokenTransfer = ({ transfersQuery, token }: Props) => {
<TokenTransferTable <TokenTransferTable
data={ items } data={ items }
top={ isPaginationVisible ? 80 : 0 } top={ isPaginationVisible ? 80 : 0 }
// token transfers query depends on token data
// so if we are here, we definitely have token data
token={ token as TokenInfo }
showSocketInfo={ pagination.page === 1 } showSocketInfo={ pagination.page === 1 }
socketInfoAlert={ socketAlert } socketInfoAlert={ socketAlert }
socketInfoNum={ newItemsCount } socketInfoNum={ newItemsCount }
tokenId={ tokenId }
/> />
</Hide> </Hide>
<Show below="lg" ssr={ false }> <Show below="lg" ssr={ false }>
...@@ -110,7 +107,7 @@ const TokenTransfer = ({ transfersQuery, token }: Props) => { ...@@ -110,7 +107,7 @@ const TokenTransfer = ({ transfersQuery, token }: Props) => {
borderBottomRadius={ 0 } borderBottomRadius={ 0 }
/> />
) } ) }
<TokenTransferList data={ items }/> <TokenTransferList data={ items } tokenId={ tokenId }/>
</Show> </Show>
</> </>
); );
......
...@@ -7,15 +7,17 @@ import TokenTransferListItem from 'ui/token/TokenTransfer/TokenTransferListItem' ...@@ -7,15 +7,17 @@ import TokenTransferListItem from 'ui/token/TokenTransfer/TokenTransferListItem'
interface Props { interface Props {
data: Array<TokenTransfer>; data: Array<TokenTransfer>;
tokenId?: string;
} }
const TokenTransferList = ({ data }: Props) => { const TokenTransferList = ({ data, tokenId }: Props) => {
return ( return (
<Box> <Box>
{ data.map((item, index) => ( { data.map((item, index) => (
<TokenTransferListItem <TokenTransferListItem
key={ index } key={ index }
{ ...item } { ...item }
tokenId={ tokenId }
/> />
)) } )) }
</Box> </Box>
......
...@@ -14,7 +14,7 @@ import AddressLink from 'ui/shared/address/AddressLink'; ...@@ -14,7 +14,7 @@ import AddressLink from 'ui/shared/address/AddressLink';
import ListItemMobile from 'ui/shared/ListItemMobile'; import ListItemMobile from 'ui/shared/ListItemMobile';
import TokenTransferNft from 'ui/shared/TokenTransfer/TokenTransferNft'; import TokenTransferNft from 'ui/shared/TokenTransfer/TokenTransferNft';
type Props = TokenTransfer; type Props = TokenTransfer & {tokenId?: string};
const TokenTransferListItem = ({ const TokenTransferListItem = ({
token, token,
...@@ -24,6 +24,7 @@ const TokenTransferListItem = ({ ...@@ -24,6 +24,7 @@ const TokenTransferListItem = ({
to, to,
method, method,
timestamp, timestamp,
tokenId,
}: Props) => { }: Props) => {
const value = (() => { const value = (() => {
if (!('value' in total)) { if (!('value' in total)) {
...@@ -78,7 +79,7 @@ const TokenTransferListItem = ({ ...@@ -78,7 +79,7 @@ const TokenTransferListItem = ({
</Flex> </Flex>
) } ) }
{ 'token_id' in total && (token.type === 'ERC-721' || token.type === 'ERC-1155') && { 'token_id' in total && (token.type === 'ERC-721' || token.type === 'ERC-1155') &&
<TokenTransferNft hash={ token.address } id={ total.token_id }/> } <TokenTransferNft hash={ token.address } id={ total.token_id } isDisabled={ Boolean(tokenId && tokenId === total.token_id) }/> }
</ListItemMobile> </ListItemMobile>
); );
}; };
......
import { Table, Tbody, Tr, Th, Td } from '@chakra-ui/react'; import { Table, Tbody, Tr, Th, Td } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
import type { TokenInfo } from 'types/api/token';
import type { TokenTransfer } from 'types/api/tokenTransfer'; import type { TokenTransfer } from 'types/api/tokenTransfer';
import trimTokenSymbol from 'lib/token/trimTokenSymbol'; import trimTokenSymbol from 'lib/token/trimTokenSymbol';
...@@ -12,24 +11,27 @@ import TokenTransferTableItem from 'ui/token/TokenTransfer/TokenTransferTableIte ...@@ -12,24 +11,27 @@ import TokenTransferTableItem from 'ui/token/TokenTransfer/TokenTransferTableIte
interface Props { interface Props {
data: Array<TokenTransfer>; data: Array<TokenTransfer>;
top: number; top: number;
token: TokenInfo;
showSocketInfo: boolean; showSocketInfo: boolean;
socketInfoAlert?: string; socketInfoAlert?: string;
socketInfoNum?: number; socketInfoNum?: number;
tokenId?: string;
} }
const TokenTransferTable = ({ data, top, token, showSocketInfo, socketInfoAlert, socketInfoNum }: Props) => { const TokenTransferTable = ({ data, top, showSocketInfo, socketInfoAlert, socketInfoNum, tokenId }: Props) => {
const tokenType = data[0].token.type;
const tokenSymbol = data[0].token.symbol;
return ( return (
<Table variant="simple" size="sm"> <Table variant="simple" size="sm">
<Thead top={ top }> <Thead top={ top }>
<Tr> <Tr>
<Th width={ token.type === 'ERC-1155' ? '60%' : '80%' }>Txn hash</Th> <Th width="60%">Txn hash</Th>
<Th width="164px">Method</Th> <Th width="164px">Method</Th>
<Th width="148px">From</Th> <Th width="148px">From</Th>
<Th width="36px" px={ 0 }/> <Th width="36px" px={ 0 }/>
<Th width="218px" >To</Th> <Th width="218px" >To</Th>
{ (token.type === 'ERC-721' || token.type === 'ERC-1155') && <Th width="20%" isNumeric={ token.type === 'ERC-721' }>Token ID</Th> } { (tokenType === 'ERC-721' || tokenType === 'ERC-1155') && <Th width="20%" isNumeric={ tokenType === 'ERC-721' }>Token ID</Th> }
{ (token.type === 'ERC-20' || token.type === 'ERC-1155') && <Th width="20%" isNumeric>Value { trimTokenSymbol(token.symbol) }</Th> } { (tokenType === 'ERC-20' || tokenType === 'ERC-1155') && <Th width="20%" isNumeric whiteSpace="nowrap">Value { trimTokenSymbol(tokenSymbol) }</Th> }
</Tr> </Tr>
</Thead> </Thead>
<Tbody> <Tbody>
...@@ -48,7 +50,7 @@ const TokenTransferTable = ({ data, top, token, showSocketInfo, socketInfoAlert, ...@@ -48,7 +50,7 @@ const TokenTransferTable = ({ data, top, token, showSocketInfo, socketInfoAlert,
</Tr> </Tr>
) } ) }
{ data.map((item, index) => ( { data.map((item, index) => (
<TokenTransferTableItem key={ index } { ...item }/> <TokenTransferTableItem key={ index } { ...item } tokenId={ tokenId }/>
)) } )) }
</Tbody> </Tbody>
</Table> </Table>
......
...@@ -11,7 +11,7 @@ import AddressIcon from 'ui/shared/address/AddressIcon'; ...@@ -11,7 +11,7 @@ import AddressIcon from 'ui/shared/address/AddressIcon';
import AddressLink from 'ui/shared/address/AddressLink'; import AddressLink from 'ui/shared/address/AddressLink';
import TokenTransferNft from 'ui/shared/TokenTransfer/TokenTransferNft'; import TokenTransferNft from 'ui/shared/TokenTransfer/TokenTransferNft';
type Props = TokenTransfer type Props = TokenTransfer & { tokenId?: string }
const TokenTransferTableItem = ({ const TokenTransferTableItem = ({
token, token,
...@@ -21,6 +21,7 @@ const TokenTransferTableItem = ({ ...@@ -21,6 +21,7 @@ const TokenTransferTableItem = ({
to, to,
method, method,
timestamp, timestamp,
tokenId,
}: Props) => { }: Props) => {
const value = (() => { const value = (() => {
if (!('value' in total)) { if (!('value' in total)) {
...@@ -84,7 +85,9 @@ const TokenTransferTableItem = ({ ...@@ -84,7 +85,9 @@ const TokenTransferTableItem = ({
<TokenTransferNft <TokenTransferNft
hash={ token.address } hash={ token.address }
id={ total.token_id } id={ total.token_id }
justifyContent={ token.type === 'ERC-721' ? 'end' : 'start' }/> justifyContent={ token.type === 'ERC-721' ? 'end' : 'start' }
isDisabled={ Boolean(tokenId && tokenId === total.token_id) }
/>
) : '-' ) : '-'
} }
</Td> </Td>
......
...@@ -7,14 +7,18 @@ import type { RoutedTab } from 'ui/shared/RoutedTabs/types'; ...@@ -7,14 +7,18 @@ import type { RoutedTab } from 'ui/shared/RoutedTabs/types';
import useApiQuery from 'lib/api/useApiQuery'; import useApiQuery from 'lib/api/useApiQuery';
import { useAppContext } from 'lib/appContext'; import { useAppContext } from 'lib/appContext';
import useIsMobile from 'lib/hooks/useIsMobile'; import useIsMobile from 'lib/hooks/useIsMobile';
import useQueryWithPages from 'lib/hooks/useQueryWithPages';
import AdBanner from 'ui/shared/ad/AdBanner'; import AdBanner from 'ui/shared/ad/AdBanner';
import TextAd from 'ui/shared/ad/TextAd'; import TextAd from 'ui/shared/ad/TextAd';
import AddressHeadingInfo from 'ui/shared/AddressHeadingInfo'; import AddressHeadingInfo from 'ui/shared/AddressHeadingInfo';
import PageTitle from 'ui/shared/Page/PageTitle'; import PageTitle from 'ui/shared/Page/PageTitle';
import Pagination from 'ui/shared/Pagination';
import RoutedTabs from 'ui/shared/RoutedTabs/RoutedTabs'; import RoutedTabs from 'ui/shared/RoutedTabs/RoutedTabs';
import TokenLogo from 'ui/shared/TokenLogo'; import TokenLogo from 'ui/shared/TokenLogo';
import TokenInstanceDetails from 'ui/tokenInstance/TokenInstanceDetails'; import TokenTransfer from 'ui/token/TokenTransfer/TokenTransfer';
import TokenInstanceSkeleton from 'ui/tokenInstance/TokenInstanceSkeleton';
import TokenInstanceDetails from './TokenInstanceDetails';
import TokenInstanceSkeleton from './TokenInstanceSkeleton';
export type TokenTabs = 'token_transfers' | 'holders' export type TokenTabs = 'token_transfers' | 'holders'
...@@ -25,6 +29,7 @@ const TokenInstanceContent = () => { ...@@ -25,6 +29,7 @@ const TokenInstanceContent = () => {
const hash = router.query.hash?.toString(); const hash = router.query.hash?.toString();
const id = router.query.id?.toString(); const id = router.query.id?.toString();
const tab = router.query.tab?.toString();
const hasGoBackLink = appProps.referrer && appProps.referrer.includes(`/token/${ hash }`) && !appProps.referrer.includes('instance'); const hasGoBackLink = appProps.referrer && appProps.referrer.includes(`/token/${ hash }`) && !appProps.referrer.includes('instance');
...@@ -35,9 +40,19 @@ const TokenInstanceContent = () => { ...@@ -35,9 +40,19 @@ const TokenInstanceContent = () => {
queryOptions: { enabled: Boolean(hash && id) }, queryOptions: { enabled: Boolean(hash && id) },
}); });
const transfersQuery = useQueryWithPages({
resourceName: 'token_instance_transfers',
pathParams: { hash, id },
scrollRef,
options: {
enabled: Boolean(hash && (!tab || tab === 'token_transfers') && tokenInstanceQuery.data),
},
});
const tabs: Array<RoutedTab> = [ const tabs: Array<RoutedTab> = [
{ id: 'token_transfers', title: 'Token transfers', component: <span>Token transfers</span> }, { id: 'token_transfers', title: 'Token transfers', component: <TokenTransfer transfersQuery={ transfersQuery } tokenId={ id }/> },
{ id: 'holders', title: 'Holders', component: <span>Holders</span> }, // there is no api for this tab yet
// { id: 'holders', title: 'Holders', component: <span>Holders</span> },
{ id: 'metadata', title: 'Metadata', component: <span>Metadata</span> }, { id: 'metadata', title: 'Metadata', component: <span>Metadata</span> },
]; ];
...@@ -81,6 +96,8 @@ const TokenInstanceContent = () => { ...@@ -81,6 +96,8 @@ const TokenInstanceContent = () => {
<RoutedTabs <RoutedTabs
tabs={ tabs } tabs={ tabs }
tabListProps={ isMobile ? { mt: 8 } : { mt: 3, py: 5, marginBottom: 0 } } tabListProps={ isMobile ? { mt: 8 } : { mt: 3, py: 5, marginBottom: 0 } }
rightSlot={ !isMobile && transfersQuery.isPaginationVisible ? <Pagination { ...transfersQuery.pagination }/> : null }
stickyEnabled={ !isMobile }
/> />
</> </>
); );
......
...@@ -38,13 +38,13 @@ const TokenInstanceDetails = ({ data, scrollRef }: Props) => { ...@@ -38,13 +38,13 @@ const TokenInstanceDetails = ({ data, scrollRef }: Props) => {
> >
<DetailsInfoItem <DetailsInfoItem
title="Token" title="Token"
hint="Token name." hint="Token name"
> >
<TokenSnippet hash={ data.token.address } name={ data.token.name }/> <TokenSnippet hash={ data.token.address } name={ data.token.name }/>
</DetailsInfoItem> </DetailsInfoItem>
<DetailsInfoItem <DetailsInfoItem
title="Owner" title="Owner"
hint="Current owner of this token instance." hint="Current owner of this token instance"
> >
<Address> <Address>
<AddressIcon address={ data.owner }/> <AddressIcon address={ data.owner }/>
...@@ -55,7 +55,7 @@ const TokenInstanceDetails = ({ data, scrollRef }: Props) => { ...@@ -55,7 +55,7 @@ const TokenInstanceDetails = ({ data, scrollRef }: Props) => {
<TokenInstanceCreatorAddress hash={ data.token.address }/> <TokenInstanceCreatorAddress hash={ data.token.address }/>
<DetailsInfoItem <DetailsInfoItem
title="Token ID" title="Token ID"
hint="This token instance unique token ID." hint="This token instance unique token ID"
> >
<Flex alignItems="center" overflow="hidden"> <Flex alignItems="center" overflow="hidden">
<Box overflow="hidden" display="inline-block" w="100%"> <Box overflow="hidden" display="inline-block" w="100%">
......
...@@ -36,7 +36,7 @@ const TokenInstanceTransfersCount = ({ hash, id, onClick }: Props) => { ...@@ -36,7 +36,7 @@ const TokenInstanceTransfersCount = ({ hash, id, onClick }: Props) => {
return ( return (
<DetailsInfoItem <DetailsInfoItem
title="Transfers" title="Transfers"
hint="Number of transfer for the token instance." hint="Number of transfer for the token instance"
> >
<LinkInternal <LinkInternal
href={ url } href={ url }
......
...@@ -126,7 +126,7 @@ const TxDetails = () => { ...@@ -126,7 +126,7 @@ const TxDetails = () => {
) } ) }
<DetailsInfoItem <DetailsInfoItem
title="Transaction hash" title="Transaction hash"
hint="Unique character string (TxID) assigned to every verified transaction." hint="Unique character string (TxID) assigned to every verified transaction"
flexWrap="nowrap" flexWrap="nowrap"
> >
{ data.status === null && <Spinner mr={ 2 } size="sm" flexShrink={ 0 }/> } { data.status === null && <Spinner mr={ 2 } size="sm" flexShrink={ 0 }/> }
...@@ -146,14 +146,14 @@ const TxDetails = () => { ...@@ -146,14 +146,14 @@ const TxDetails = () => {
{ data.revert_reason && ( { data.revert_reason && (
<DetailsInfoItem <DetailsInfoItem
title="Revert reason" title="Revert reason"
hint="The revert reason of the transaction." hint="The revert reason of the transaction"
> >
<TxRevertReason { ...data.revert_reason }/> <TxRevertReason { ...data.revert_reason }/>
</DetailsInfoItem> </DetailsInfoItem>
) } ) }
<DetailsInfoItem <DetailsInfoItem
title="Block" title="Block"
hint="Block number containing the transaction." hint="Block number containing the transaction"
> >
<Text>{ data.block === null ? 'Pending' : data.block }</Text> <Text>{ data.block === null ? 'Pending' : data.block }</Text>
{ Boolean(data.confirmations) && ( { Boolean(data.confirmations) && (
...@@ -168,7 +168,7 @@ const TxDetails = () => { ...@@ -168,7 +168,7 @@ const TxDetails = () => {
{ data.timestamp && ( { data.timestamp && (
<DetailsInfoItem <DetailsInfoItem
title="Timestamp" title="Timestamp"
hint="Date & time of transaction inclusion, including length of time for confirmation." hint="Date & time of transaction inclusion, including length of time for confirmation"
> >
<Icon as={ clockIcon } boxSize={ 5 } color="gray.500"/> <Icon as={ clockIcon } boxSize={ 5 } color="gray.500"/>
<Text ml={ 1 }>{ dayjs(data.timestamp).fromNow() }</Text> <Text ml={ 1 }>{ dayjs(data.timestamp).fromNow() }</Text>
...@@ -205,7 +205,7 @@ const TxDetails = () => { ...@@ -205,7 +205,7 @@ const TxDetails = () => {
<DetailsInfoItem <DetailsInfoItem
title="From" title="From"
hint="Address (external or contract) sending the transaction." hint="Address (external or contract) sending the transaction"
columnGap={ 3 } columnGap={ 3 }
> >
<Address> <Address>
...@@ -222,7 +222,7 @@ const TxDetails = () => { ...@@ -222,7 +222,7 @@ const TxDetails = () => {
</DetailsInfoItem> </DetailsInfoItem>
<DetailsInfoItem <DetailsInfoItem
title={ data.to?.is_contract ? 'Interacted with contract' : 'To' } title={ data.to?.is_contract ? 'Interacted with contract' : 'To' }
hint="Address (external or contract) receiving the transaction." hint="Address (external or contract) receiving the transaction"
flexWrap={{ base: 'wrap', lg: 'nowrap' }} flexWrap={{ base: 'wrap', lg: 'nowrap' }}
columnGap={ 3 } columnGap={ 3 }
> >
...@@ -257,13 +257,13 @@ const TxDetails = () => { ...@@ -257,13 +257,13 @@ const TxDetails = () => {
<DetailsInfoItem <DetailsInfoItem
title="Value" title="Value"
hint="Value sent in the native token (and USD) if applicable." hint="Value sent in the native token (and USD) if applicable"
> >
<CurrencyValue value={ data.value } currency={ appConfig.network.currency.symbol } exchangeRate={ data.exchange_rate }/> <CurrencyValue value={ data.value } currency={ appConfig.network.currency.symbol } exchangeRate={ data.exchange_rate }/>
</DetailsInfoItem> </DetailsInfoItem>
<DetailsInfoItem <DetailsInfoItem
title="Transaction fee" title="Transaction fee"
hint="Total transaction fee." hint="Total transaction fee"
> >
<CurrencyValue <CurrencyValue
value={ data.fee.value } value={ data.fee.value }
...@@ -274,14 +274,14 @@ const TxDetails = () => { ...@@ -274,14 +274,14 @@ const TxDetails = () => {
</DetailsInfoItem> </DetailsInfoItem>
<DetailsInfoItem <DetailsInfoItem
title="Gas price" title="Gas price"
hint="Price per unit of gas specified by the sender. Higher gas prices can prioritize transaction inclusion during times of high usage." hint="Price per unit of gas specified by the sender. Higher gas prices can prioritize transaction inclusion during times of high usage"
> >
<Text mr={ 1 }>{ BigNumber(data.gas_price).dividedBy(WEI).toFixed() } { appConfig.network.currency.symbol }</Text> <Text mr={ 1 }>{ BigNumber(data.gas_price).dividedBy(WEI).toFixed() } { appConfig.network.currency.symbol }</Text>
<Text variant="secondary">({ BigNumber(data.gas_price).dividedBy(WEI_IN_GWEI).toFixed() } Gwei)</Text> <Text variant="secondary">({ BigNumber(data.gas_price).dividedBy(WEI_IN_GWEI).toFixed() } Gwei)</Text>
</DetailsInfoItem> </DetailsInfoItem>
<DetailsInfoItem <DetailsInfoItem
title="Gas limit & usage by txn" title="Gas limit & usage by txn"
hint="Actual gas amount used by the transaction." hint="Actual gas amount used by the transaction"
> >
<Text>{ BigNumber(data.gas_used || 0).toFormat() }</Text> <Text>{ BigNumber(data.gas_used || 0).toFormat() }</Text>
<TextSeparator/> <TextSeparator/>
...@@ -292,7 +292,7 @@ const TxDetails = () => { ...@@ -292,7 +292,7 @@ const TxDetails = () => {
<DetailsInfoItem <DetailsInfoItem
title="Gas fees (Gwei)" title="Gas fees (Gwei)"
// eslint-disable-next-line max-len // eslint-disable-next-line max-len
hint="Base Fee refers to the network Base Fee at the time of the block, while Max Fee & Max Priority Fee refer to the max amount a user is willing to pay for their tx & to give to the miner respectively." hint="Base Fee refers to the network Base Fee at the time of the block, while Max Fee & Max Priority Fee refer to the max amount a user is willing to pay for their tx & to give to the miner respectively"
> >
{ data.base_fee_per_gas && ( { data.base_fee_per_gas && (
<Box> <Box>
...@@ -319,7 +319,7 @@ const TxDetails = () => { ...@@ -319,7 +319,7 @@ const TxDetails = () => {
{ data.tx_burnt_fee && ( { data.tx_burnt_fee && (
<DetailsInfoItem <DetailsInfoItem
title="Burnt fees" title="Burnt fees"
hint={ `Amount of ${ appConfig.network.currency.symbol } burned for this transaction. Equals Block Base Fee per Gas * Gas Used.` } hint={ `Amount of ${ appConfig.network.currency.symbol } burned for this transaction. Equals Block Base Fee per Gas * Gas Used` }
> >
<Icon as={ flameIcon } mr={ 1 } boxSize={ 5 } color="gray.500"/> <Icon as={ flameIcon } mr={ 1 } boxSize={ 5 } color="gray.500"/>
<CurrencyValue <CurrencyValue
...@@ -349,7 +349,7 @@ const TxDetails = () => { ...@@ -349,7 +349,7 @@ const TxDetails = () => {
<GridItem colSpan={{ base: undefined, lg: 2 }} mt={{ base: 1, lg: 4 }}/> <GridItem colSpan={{ base: undefined, lg: 2 }} mt={{ base: 1, lg: 4 }}/>
<DetailsInfoItem <DetailsInfoItem
title="Other" title="Other"
hint="Other data related to this transaction." hint="Other data related to this transaction"
> >
{ {
[ [
...@@ -382,7 +382,7 @@ const TxDetails = () => { ...@@ -382,7 +382,7 @@ const TxDetails = () => {
</DetailsInfoItem> </DetailsInfoItem>
<DetailsInfoItem <DetailsInfoItem
title="Raw input" title="Raw input"
hint="Binary data included with the transaction. See logs tab for additional info." hint="Binary data included with the transaction. See logs tab for additional info"
> >
<RawInputData hex={ data.raw_input }/> <RawInputData hex={ data.raw_input }/>
</DetailsInfoItem> </DetailsInfoItem>
......
...@@ -17,10 +17,10 @@ interface Props { ...@@ -17,10 +17,10 @@ interface Props {
} }
const TOKEN_TRANSFERS_TYPES = [ const TOKEN_TRANSFERS_TYPES = [
{ title: 'Tokens transferred', hint: 'List of tokens transferred in the transaction.', type: 'token_transfer' }, { title: 'Tokens transferred', hint: 'List of tokens transferred in the transaction', type: 'token_transfer' },
{ title: 'Tokens minted', hint: 'List of tokens minted in the transaction.', type: 'token_minting' }, { title: 'Tokens minted', hint: 'List of tokens minted in the transaction', type: 'token_minting' },
{ title: 'Tokens burnt', hint: 'List of tokens burnt in the transaction.', type: 'token_burning' }, { title: 'Tokens burnt', hint: 'List of tokens burnt in the transaction', type: 'token_burning' },
{ title: 'Tokens created', hint: 'List of tokens created in the transaction.', type: 'token_spawning' }, { title: 'Tokens created', hint: 'List of tokens created in the transaction', type: 'token_spawning' },
]; ];
const VISIBLE_ITEMS_NUM = 3; const VISIBLE_ITEMS_NUM = 3;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment