Commit 43feeff6 authored by tom's avatar tom

Merge branch 'main' of github.com:blockscout/frontend into search-bar

parents 0c3702ee c0f406be
......@@ -44,6 +44,8 @@ NEXT_PUBLIC_AD_ADBUTLER_ON=__PLACEHOLDER_FORNEXT_PUBLIC_AD_ADBUTLER_ON__
NEXT_PUBLIC_API_HOST=__PLACEHOLDER_FOR_NEXT_PUBLIC_API_HOST__
NEXT_PUBLIC_API_BASE_PATH=__PLACEHOLDER_FOR_NEXT_PUBLIC_API_BASE_PATH__
NEXT_PUBLIC_STATS_API_HOST=__PLACEHOLDER_FOR_NEXT_PUBLIC_STATS_API_HOST__
NEXT_PUBLIC_API_PROTOCOL=__PLACEHOLDER_FOR_NEXT_PUBLIC_API_PROTOCOL__
NEXT_PUBLIC_API_PORT=__PLACEHOLDER_FOR_NEXT_PUBLIC_API_PORT__
# external services config
NEXT_PUBLIC_SENTRY_DSN=__PLACEHOLDER_FOR_NEXT_PUBLIC_SENTRY_DSN__
......
......@@ -27,6 +27,14 @@ const baseUrl = [
].filter(Boolean).join('');
const authUrl = getEnvValue(process.env.NEXT_PUBLIC_AUTH_URL) || baseUrl;
const apiHost = getEnvValue(process.env.NEXT_PUBLIC_API_HOST);
const apiSchema = getEnvValue(process.env.NEXT_PUBLIC_API_PROTOCOL) || 'https';
const apiPort = getEnvValue(process.env.NEXT_PUBLIC_API_PORT);
const apiEndpoint = apiHost ? [
apiSchema || 'https',
'://',
apiHost,
apiPort && ':' + apiPort,
].filter(Boolean).join('') : 'https://blockscout.com';
const logoutUrl = (() => {
try {
......@@ -91,7 +99,7 @@ const config = Object.freeze({
},
api: {
host: apiHost,
endpoint: apiHost ? `https://${ apiHost }` : 'https://blockscout.com',
endpoint: apiEndpoint,
socket: apiHost ? `wss://${ apiHost }` : 'wss://blockscout.com',
basePath: stripTrailingSlash(getEnvValue(process.env.NEXT_PUBLIC_API_BASE_PATH) || ''),
},
......
# app config
NEXT_PUBLIC_APP_PROTOCOL=http
NEXT_PUBLIC_APP_HOST=localhost
NEXT_PUBLIC_APP_PORT=3000
NEXT_PUBLIC_APP_INSTANCE=local
NEXT_PUBLIC_APP_ENV=development
# ui config
NEXT_PUBLIC_BLOCKSCOUT_VERSION=v4.1.7-beta
NEXT_PUBLIC_FOOTER_TELEGRAM_LINK=https://t.me/poa_network
NEXT_PUBLIC_FOOTER_STAKING_LINK=https://duneanalytics.com/maxaleks/xdai-staking
NEXT_PUBLIC_FOOTER_GITHUB_LINK=https://github.com/blockscout/blockscout
NEXT_PUBLIC_FOOTER_TWITTER_LINK=https://www.twitter.com/blockscoutcom
NEXT_PUBLIC_FEATURED_NETWORKS=[{'title':'Gnosis Chain','url':'https://blockscout.com/xdai/mainnet','group':'mainnets','type':'xdai_mainnet'},{'title':'Optimism on Gnosis Chain','url':'https://blockscout.com/xdai/optimism','group':'mainnets','type':'xdai_optimism'},{'title':'Arbitrum on xDai','url':'https://blockscout.com/xdai/aox','group':'mainnets'},{'title':'Ethereum','url':'https://blockscout.com/eth/mainnet','group':'mainnets','type':'eth_mainnet'},{'title':'Ethereum Classic','url':'https://blockscout.com/etx/mainnet','group':'mainnets','type':'etc_mainnet'},{'title':'POA','url':'https://blockscout.com/poa/core','group':'mainnets','type':'poa_core'},{'title':'RSK','url':'https://blockscout.com/rsk/mainnet','group':'mainnets','type':'rsk_mainnet'},{'title':'Gnosis Chain Testnet','url':'https://blockscout.com/xdai/testnet','group':'testnets','type':'xdai_testnet'},{'title':'POA Sokol','url':'https://blockscout.com/poa/sokol','group':'testnets','type':'poa_sokol'},{'title':'ARTIS Σ1','url':'https://blockscout.com/artis/sigma1','group':'other','type':'artis_sigma1'},{'title':'LUKSO L14','url':'https://blockscout.com/lukso/l14','group':'other','type':'lukso_l14'},{'title':'Astar','url':'https://blockscout.com/astar','group':'other','type':'astar'}]
NEXT_PUBLIC_NETWORK_EXPLORERS=[{'title':'Anyblock','baseUrl':'https://explorer.anyblock.tools','paths':{'tx':'/ethereum/poa/core/transaction','address':'/ethereum/poa/core/address'}}]
NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs','coin_price','market_cup']
NEXT_PUBLIC_HOMEPAGE_PLATE_GRADIENT=radial-gradient(at 12% 37%, hsla(324,73%,67%,1) 0px, transparent 50%), radial-gradient(at 62% 14%, hsla(256,87%,73%,1) 0px, transparent 50%), radial-gradient(at 84% 80%, hsla(128,75%,73%,1) 0px, transparent 50%), radial-gradient(at 57% 46%, hsla(285,63%,72%,1) 0px, transparent 50%), radial-gradient(at 37% 30%, hsla(174,70%,61%,1) 0px, transparent 50%), radial-gradient(at 15% 86%, hsla(350,65%,70%,1) 0px, transparent 50%), radial-gradient(at 67% 57%, hsla(14,95%,76%,1) 0px, transparent 50%)
#NEXT_PUBLIC_NETWORK_LOGO=https://placekitten.com/300/60
#NEXT_PUBLIC_NETWORK_SMALL_LOGO=https://placekitten.com/300/300
# network config
NEXT_PUBLIC_NETWORK_NAME=POA
NEXT_PUBLIC_NETWORK_SHORT_NAME=POA
NEXT_PUBLIC_NETWORK_ASSETS_PATHNAME=poa
NEXT_PUBLIC_NETWORK_TYPE=poa_core
NEXT_PUBLIC_NETWORK_ID=99
NEXT_PUBLIC_NETWORK_CURRENCY_NAME=POA
NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=POA
NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18
NEXT_PUBLIC_NETWORK_TOKEN_ADDRESS=0x029a799563238d0e75e20be2f4bda0ea68d00172
NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true
NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE=validation
NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://airtable.com/shrqUAcjgGJ4jU88C
NEXT_PUBLIC_MARKETPLACE_APP_LIST=[{'author': 'Blockscout','id':'token-approval-tracker','title':'Token Approval Tracker','logo':'https://approval-tracker.vercel.app/icon-192.png','categories':['security','tools'],'shortDescription':'Token Approval Tracker shows all approvals for any ERC20-compliant tokens and NFTs and lets to revoke them or adjust the approved amount.','site':'https://docs.blockscout.com/for-users/blockscout-apps/token-approval-tracker','description':'Token Approval Tracker shows all approvals for any ERC20-compliant tokens and NFTs and lets to revoke them or adjust the approved amount.','url':'https://approval-tracker.vercel.app/'},{'author': 'Revoke','id':'revoke.cash','title':'Revoke.cash','logo':'https://www.gitbook.com/cdn-cgi/image/width=32,dpr=2.200000047683716,format=auto/https%3A%2F%2Ffiles.gitbook.com%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252F-Lq1XoWGmy8zggj_u2fM%252Fuploads%252FVBMGyUFnd6CScjfK7CYQ%252Frevoke_sing.png%3Falt%3Dmedia%26token%3D9ab94986-7ab1-41c8-bf7e-d9ce11d23182','categories':['security','tools'],'shortDescription': 'Revoke.cash comes in as a preventative tool to manage your token allowances and practice proper wallet hygiene. By regularly revoking active allowances you reduce the chances of becoming the victim of allowance exploits.','site': 'https://revoke.cash/about','description': 'Revoke.cash comes in as a preventative tool to manage your token allowances and practice proper wallet hygiene. By regularly revoking active allowances you reduce the chances of becoming the victim of allowance exploits.','url':'https://revoke.cash/'},{'author':'Aave','id': 'aave','title': 'Aave','logo': 'https://www.gitbook.com/cdn-cgi/image/width=32,dpr=2.200000047683716,format=auto/https%3A%2F%2Ffiles.gitbook.com%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252F-Lq1XoWGmy8zggj_u2fM%252Fuploads%252FrZkUTIUCG7Zx8BW6Em34%252FAave.png%3Falt%3Dmedia%26token%3D249797a4-4c1e-4372-9cd2-3e48e05e5f30','categories':['tools'],'shortDescription':'Aave is a decentralised non-custodial liquidity market protocol where users can participate as suppliers or borrowers. Suppliers provide liquidity to the market to earn a passive income, while borrowers are able to borrow in an overcollateralised (perpetually) or undercollateralised (one-block liquidity) fashion.','site': 'https://docs.aave.com/faq/','description': 'Aave is a decentralised non-custodial liquidity market protocol where users can participate as suppliers or borrowers. Suppliers provide liquidity to the market to earn a passive income, while borrowers are able to borrow in an overcollateralised (perpetually) or undercollateralised (one-block liquidity) fashion.','url': 'https://staging.aave.com/'},{'author':'LooksRare','id':'looksrare','external':true,'title':'LooksRare','logo': 'https://www.gitbook.com/cdn-cgi/image/width=32,dpr=2.200000047683716,format=auto/https%3A%2F%2Ffiles.gitbook.com%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252F-Lq1XoWGmy8zggj_u2fM%252Fuploads%252FeAI4gy3qPMt68mZOZHAx%252FLooksRare.png%3Falt%3Dmedia%26token%3D44c01439-ae09-40aa-b904-3a9ce5b2e002','categories':['tools'],'shortDescription': 'LooksRare is the web3 NFT Marketplace where traders and collectors have earned over $1.3 Billion in rewards.','site':'https://docs.looksrare.org/about/welcome-to-looksrare','description':'LooksRare is the web3 NFT Marketplace where traders and collectors have earned over $1.3 Billion in rewards.','url': 'https://goerli.looksrare.org/'},{'author':'zkSync Bridge','id':'zksync-bridge','external':true,'title':'zkSync Bridge','logo':'https://www.gitbook.com/cdn-cgi/image/width=32,dpr=2.200000047683716,format=auto/https%3A%2F%2Ffiles.gitbook.com%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252F-Lq1XoWGmy8zggj_u2fM%252Fuploads%252FrtQsaAz9BjGBc35tVAnq%252FzkSync.png%3Falt%3Dmedia%26token%3D5c18171c-8ccf-4a88-8f44-680cbf238115','categories':['security','tools'],'shortDescription':'zkSync 2.0 Goerli Bridge','site':'https://v2-docs.zksync.io/dev/','description':'zkSync 2.0 Goerli Bridge','url':'https://portal.zksync.io/bridge'},{'author':'dYdX','id':'dydx','external':true,'title':'dYdX','logo':'https://www.gitbook.com/cdn-cgi/image/width=32,dpr=2.200000047683716,format=auto/https%3A%2F%2Ffiles.gitbook.com%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252F-Lq1XoWGmy8zggj_u2fM%252Fuploads%252FCrOglR72wpi0UhEscwe4%252Fdxdy.png%3Falt%3Dmedia%26token%3D8811909e-93e3-487c-9614-dffce37223e9','categories': ['security','tools'],'shortDescription':'dYdX is a leading decentralized exchange that currently supports perpetual trading. dYdX runs on smart contracts on the Ethereum blockchain, and allows users to trade with no intermediaries.','site':'https://help.dydx.exchange/en/articles/3047379-introduction-and-overview','description':'dYdX is a leading decentralized exchange that currently supports perpetual trading. dYdX runs on smart contracts on the Ethereum blockchain, and allows users to trade with no intermediaries.','url':'https://trade.stage.dydx.exchange/portfolio/overview'},{'author':'MetalSwap','id':'metalswap','title':'MetalSwap','logo':'https://www.gitbook.com/cdn-cgi/image/width=32,dpr=2.200000047683716,format=auto/https%3A%2F%2Ffiles.gitbook.com%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252F-Lq1XoWGmy8zggj_u2fM%252Fuploads%252F8xqldTvxb6avrwVVc3rS%252FMetalSwap.png%3Falt%3Dmedia%26token%3D92d2db99-853a-487d-8d8c-8cdeaeaaf014','categories':['security','tools'],'shortDescription':'MetalSwap is a decentralised platform that enables hedging swaps in financial markets with the aim of providing a hedge for commodity traders and an investment opportunity for those who contribute to the shared liquidity of the project.','site':'https://docs.metalswap.finance/','description':'MetalSwap is a decentralised platform that enables hedging swaps in financial markets with the aim of providing a hedge for commodity traders and an investment opportunity for those who contribute to the shared liquidity of the project.','url':'https://demo.metalswap.finance/'},{'author':'FaucetDao','id':'faucetdao','title':'FaucetDao','logo':'https://www.gitbook.com/cdn-cgi/image/width=32,dpr=2.200000047683716,format=auto/https%3A%2F%2Ffiles.gitbook.com%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252F-Lq1XoWGmy8zggj_u2fM%252Fuploads%252Ffnnt3ZNZhzRwqMM5YYjD%252FPlaceholder.png%3Falt%3Dmedia%26token%3D507571bb-d76f-4d96-a35e-2b278608f7ca','categories':['tools'],'shortDescription':'FaucetDao is a decentralised community fund providing liquidity and support to early-stage well vetted blockchain projects.','site':'https://linktr.ee/faucet_dao','description':'FaucetDao is a decentralised community fund providing liquidity and support to early-stage well vetted blockchain projects.','url':'https://www.faucetdao.shop/swap?chain=goerli'},{'author':'Uniswap','id':'uniswap','title':'Uniswap','logo':'https://www.gitbook.com/cdn-cgi/image/width=32,dpr=2.200000047683716,format=auto/https%3A%2F%2Ffiles.gitbook.com%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Fspaces%252F-Lq1XoWGmy8zggj_u2fM%252Fuploads%252FJc0QAyeaBmFIL97tSmGv%252FUniswap.png%3Falt%3Dmedia%26token%3D5d25d796-c273-4e22-92fa-ff85206bec76','categories':['tools'],'shortDescription':'Uniswap is a cryptocurrency exchange which uses a decentralized network protocol.','site':'https://docs.uniswap.org/','description':'Uniswap is a cryptocurrency exchange which uses a decentralized network protocol.','url':'https://app.uniswap.org/swap'}]
# api config
NEXT_PUBLIC_API_BASE_PATH=/poa/core
NEXT_PUBLIC_API_HOST=localhost
NEXT_PUBLIC_API_PROTOCOL=http
NEXT_PUBLIC_API_PORT=3001
\ No newline at end of file
......@@ -2,10 +2,11 @@ import { compile } from 'path-to-regexp';
import appConfig from 'configs/app/config';
import type { ApiResource } from './resources';
import { RESOURCES } from './resources';
import type { ApiResource, ResourceName } from './resources';
export default function buildUrl(
resource: ApiResource,
_resource: ApiResource | ResourceName,
pathParams?: Record<string, string | undefined>,
queryParams?: Record<string, string | number | undefined>,
) {
......@@ -22,6 +23,7 @@ export default function buildUrl(
// will need to change the condition if there are more micro services that need authentication and DB state changes
const needProxy = appConfig.host !== appConfig.api.host;
const resource: ApiResource = typeof _resource === 'string' ? RESOURCES[_resource] : _resource;
const baseUrl = needProxy ? appConfig.baseUrl : (resource.endpoint || appConfig.api.endpoint);
const basePath = resource.basePath !== undefined ? resource.basePath : appConfig.api.basePath;
const path = needProxy ? '/node-api/proxy' + basePath + resource.path : basePath + resource.path;
......
import { compile } from 'path-to-regexp';
import appConfig from 'configs/app/config';
import { RESOURCES } from './resources';
import type { ApiResource, ResourceName } from './resources';
export default function buildUrlNode(
_resource: ApiResource | ResourceName,
pathParams?: Record<string, string | undefined>,
queryParams?: Record<string, string | number | undefined>,
) {
const resource: ApiResource = typeof _resource === 'string' ? RESOURCES[_resource] : _resource;
const baseUrl = resource.endpoint || appConfig.api.endpoint;
const basePath = resource.basePath !== undefined ? resource.basePath : appConfig.api.basePath;
const path = basePath + resource.path;
const url = new URL(compile(path)(pathParams), baseUrl);
queryParams && Object.entries(queryParams).forEach(([ key, value ]) => {
value && url.searchParams.append(key, String(value));
});
return url.toString();
}
import type { NextApiRequest } from 'next';
export default function getSearchParams(req: NextApiRequest) {
const searchParams: Record<string, string> = {};
Object.entries(req.query).forEach(([ key, value ]) => {
searchParams[key] = Array.isArray(value) ? value.join(',') : (value || '');
});
return new URLSearchParams(searchParams).toString();
}
import type { NextApiRequest } from 'next';
import appConfig from 'configs/app/config';
export default function getUrlWithNetwork(_req: NextApiRequest, path: string) {
return [
appConfig.api.basePath,
path,
]
.filter((segment) => segment !== '' && segment !== '/')
.join('');
}
import type { NextApiRequest, NextApiResponse } from 'next';
import fetchFactory from 'lib/api/fetch';
import getUrlWithNetwork from 'lib/api/getUrlWithNetwork';
import { httpLogger } from 'lib/api/logger';
type Methods = 'GET' | 'POST' | 'PUT' | 'DELETE';
export default function createHandler(getUrl: (_req: NextApiRequest) => string, allowedMethods: Array<Methods>, apiEndpoint?: string) {
const handler = async(_req: NextApiRequest, res: NextApiResponse) => {
httpLogger(_req, res);
if (!_req.method || !allowedMethods.includes(_req.method as Methods)) {
res.setHeader('Allow', allowedMethods);
res.status(405).end(`Method ${ _req.method } Not Allowed`);
return;
}
const isBodyDisallowed = _req.method === 'GET' || _req.method === 'HEAD';
const url = apiEndpoint ? `/api${ getUrl(_req) }` : getUrlWithNetwork(_req, `/api${ getUrl(_req) }`);
const fetch = fetchFactory(_req, apiEndpoint);
const response = await fetch(url, {
method: _req.method,
body: isBodyDisallowed ? undefined : _req.body,
});
if (response.status === 200) {
const data = await response.json();
res.status(200).json(data);
return;
}
let responseError;
const defaultError = { statusText: response.statusText, status: response.status };
try {
const error = await response.json() as { errors: unknown };
responseError = error?.errors || defaultError;
} catch (error) {
responseError = defaultError;
}
httpLogger.logger.error({ err: responseError, url: _req.url });
res.status(500).json(responseError);
};
return handler;
}
......@@ -2,25 +2,20 @@ import type { NextApiRequest } from 'next';
import type { RequestInit, Response } from 'node-fetch';
import nodeFetch from 'node-fetch';
import appConfig from 'configs/app/config';
import { httpLogger } from 'lib/api/logger';
import * as cookies from 'lib/cookies';
// first arg can be only a string
// FIXME migrate to RequestInfo later if needed
export default function fetchFactory(
_req: NextApiRequest,
apiEndpoint: string = appConfig.api.endpoint,
) {
return function fetch(path: string, init?: RequestInit): Promise<Response> {
const csrfToken = _req.headers['x-csrf-token']?.toString();
// first arg can be only a string
// FIXME migrate to RequestInfo later if needed
return function fetch(url: string, init?: RequestInit): Promise<Response> {
const headers = {
accept: 'application/json',
'content-type': 'application/json',
cookie: `${ cookies.NAMES.API_TOKEN }=${ _req.cookies[cookies.NAMES.API_TOKEN] }`,
...(csrfToken ? { 'x-csrf-token': csrfToken } : {}),
};
const url = new URL(path, apiEndpoint);
httpLogger.logger.info({
message: 'Trying to call API',
......@@ -28,7 +23,7 @@ export default function fetchFactory(
req: _req,
});
return nodeFetch(url.toString(), {
return nodeFetch(url, {
headers,
...init,
});
......
......@@ -36,6 +36,9 @@ export interface ApiResource {
export const RESOURCES = {
// ACCOUNT
csrf: {
path: '/api/account/v1/get_csrf',
},
user_info: {
path: '/api/account/v1/user/info',
},
......@@ -216,7 +219,6 @@ export type ResourcePaginationKey<R extends ResourceName> = typeof RESOURCES[R]
export const resourceKey = (x: keyof typeof RESOURCES) => x;
export interface ResourceError<T = unknown> {
error?: T;
payload?: T;
status: Response['status'];
statusText: Response['statusText'];
......
......@@ -5,6 +5,7 @@ import React from 'react';
import type { CsrfData } from 'types/client/account';
import type { ResourceError } from 'lib/api/resources';
import { getResourceKey } from 'lib/api/useApiQuery';
export interface Params {
method?: RequestInit['method'];
......@@ -16,17 +17,20 @@ export interface Params {
export default function useFetch() {
const queryClient = useQueryClient();
const { token } = queryClient.getQueryData<CsrfData>([ 'csrf' ]) || {};
const { token } = queryClient.getQueryData<CsrfData>(getResourceKey('csrf')) || {};
return React.useCallback(<Success, Error>(path: string, params?: Params): Promise<Success | ResourceError<Error>> => {
const reqParams = {
...params,
body: params?.method && params?.body && ![ 'GET', 'HEAD' ].includes(params.method) ?
JSON.stringify(params.body) :
JSON.stringify({
...params.body,
_csrf_token: token,
}) :
undefined,
headers: {
...params?.headers,
...(token ? { 'x-csrf-token': token } : {}),
// ...(token ? { 'x-csrf-token': token } : {}),
},
};
......@@ -40,8 +44,6 @@ export default function useFetch() {
return response.json().then(
(jsonError) => Promise.reject({
// DEPRECATED
error: jsonError as Error,
payload: jsonError as Error,
status: response.status,
statusText: response.statusText,
......
......@@ -9,6 +9,7 @@
},
"scripts": {
"dev": "next dev",
"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: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",
"build": "next build",
......
......@@ -23,7 +23,7 @@ function MyApp({ Component, pageProps }: AppProps) {
refetchOnWindowFocus: false,
retry: (failureCount, _error) => {
const error = _error as ResourceError<{ status: number }>;
const status = error?.status || error?.error?.status;
const status = error?.status || error?.payload?.status;
if (status && status >= 400 && status < 500) {
// don't do retry for client error responses
return false;
......
import type { NextApiRequest, NextApiResponse } from 'next';
import fetchFactory from 'lib/api/fetch';
import getUrlWithNetwork from 'lib/api/getUrlWithNetwork';
import buildUrlNode from 'lib/api/buildUrlNode';
import { httpLogger } from 'lib/api/logger';
import fetchFactory from 'lib/api/nodeFetch';
export default async function csrfHandler(_req: NextApiRequest, res: NextApiResponse) {
httpLogger(_req, res);
const url = getUrlWithNetwork(_req, `/api/account/v1/get_csrf`);
const fetch = fetchFactory(_req);
const response = await fetch(url);
const url = buildUrlNode('csrf');
const response = await fetchFactory(_req)(url);
if (response.status === 200) {
const token = response.headers.get('x-bs-account-csrf');
......
......@@ -2,7 +2,8 @@ import _pick from 'lodash/pick';
import _pickBy from 'lodash/pickBy';
import type { NextApiRequest, NextApiResponse } from 'next';
import fetchFactory from 'lib/api/fetch';
import appConfig from 'configs/app/config';
import fetchFactory from 'lib/api/nodeFetch';
const handler = async(_req: NextApiRequest, res: NextApiResponse) => {
if (!_req.url) {
......@@ -10,8 +11,12 @@ const handler = async(_req: NextApiRequest, res: NextApiResponse) => {
return;
}
const response = await fetchFactory(_req, _req.headers['x-endpoint']?.toString())(
const url = new URL(
_req.url.replace(/^\/node-api\/proxy/, ''),
_req.headers['x-endpoint']?.toString() || appConfig.api.endpoint,
);
const response = await fetchFactory(_req)(
url.toString(),
_pickBy(_pick(_req, [ 'body', 'method' ]), Boolean),
);
......
......@@ -19,7 +19,7 @@ const baseStylePopper = defineStyle({
const baseStyleContent = defineStyle((props) => {
const bg = mode('white', 'gray.900')(props);
const shadowColor = mode('gray.200', 'whiteAlpha.300')(props);
const shadowColor = mode('blackAlpha.200', 'whiteAlpha.300')(props);
return {
[$popperBg.variable]: `colors.${ bg }`,
......
......@@ -15,7 +15,7 @@ const variantSimple = definePartsStyle((props) => {
return {
th: {
border: 0,
color: mode('gray.600', 'whiteAlpha.700')(props),
color: mode('blackAlpha.700', 'whiteAlpha.700')(props),
backgroundColor: mode('blackAlpha.100', 'whiteAlpha.200')(props),
...transitionProps,
},
......
......@@ -24,7 +24,7 @@ export default function getOutlinedFieldStyles(props: StyleFunctionProps) {
},
_disabled: {
opacity: 1,
backgroundColor: mode('gray.200', 'whiteAlpha.200')(props),
backgroundColor: mode('blackAlpha.200', 'whiteAlpha.200')(props),
borderColor: 'transparent',
cursor: 'not-allowed',
_hover: {
......
import type { AddressParam } from './addressParams';
export interface AddressTag {
address_hash: string;
address: AddressParam;
name: string;
id: string;
}
......@@ -64,7 +65,7 @@ export interface WatchlistAddress {
notification_settings: NotificationSettings;
notification_methods: NotificationMethods;
id: string;
address?: AddressParam;
address: AddressParam;
}
export interface WatchlistTokensResponse {
......@@ -89,10 +90,11 @@ export interface PublicTag {
email: string;
company: string;
addresses: Array<string>;
addresses_with_info: Array<AddressParam>;
additional_comment: string;
}
export type PublicTagNew = Omit<PublicTag, 'id'>
export type PublicTagNew = Omit<PublicTag, 'id' | 'addresses_with_info'>
export type PublicTags = Array<PublicTag>;
......@@ -102,6 +104,7 @@ export interface CustomAbi {
name: string;
id: number;
contract_address_hash: string;
contract_address: AddressParam;
abi: Array<AbiItem>;
}
......
......@@ -21,11 +21,18 @@ export type GasPrices = {
export type Stats = {
counters: {
totalBlocks: string;
averageBlockTime: string;
totalTransactions: string;
completedTransactions: string;
totalAccounts: string;
totalBlocksAllTime: string;
totalTransactions: string;
totalTokens: string;
totalNativeCoinHolders: string;
totalNativeCoinTransfers: string;
};
}
......
......@@ -62,7 +62,7 @@ const AddressDetails = ({ addressQuery }: Props) => {
return (
<Box>
<Flex alignItems="center">
<AddressIcon hash={ addressQuery.data.hash }/>
<AddressIcon address={ addressQuery.data }/>
<Text ml={ 2 } fontFamily="heading" fontWeight={ 500 }>
{ isMobile ? <HashStringShorten hash={ addressQuery.data.hash }/> : addressQuery.data.hash }
</Text>
......
......@@ -53,7 +53,7 @@ const TxInternalsListItem = ({
</HStack>
<Box w="100%" display="flex" columnGap={ 3 }>
<Address width="calc((100% - 48px) / 2)">
<AddressIcon hash={ from.hash }/>
<AddressIcon address={ from }/>
<AddressLink ml={ 2 } fontWeight="500" hash={ from.hash }/>
</Address>
{ (isIn || isOut) ?
......@@ -61,7 +61,7 @@ const TxInternalsListItem = ({
<Icon as={ eastArrowIcon } boxSize={ 6 } color="gray.500"/>
}
<Address width="calc((100% - 48px) / 2)">
<AddressIcon hash={ toData.hash }/>
<AddressIcon address={ toData }/>
<AddressLink ml={ 2 } fontWeight="500" hash={ toData.hash }/>
</Address>
</Box>
......
......@@ -61,7 +61,7 @@ const AddressIntTxsTableItem = ({
</Td>
<Td verticalAlign="middle">
<Address display="inline-flex" maxW="100%">
<AddressIcon hash={ from.hash }/>
<AddressIcon address={ from }/>
<AddressLink ml={ 2 } fontWeight="500" hash={ from.hash } alias={ from.name } flexGrow={ 1 }/>
</Address>
</Td>
......@@ -73,7 +73,7 @@ const AddressIntTxsTableItem = ({
</Td>
<Td verticalAlign="middle">
<Address display="inline-flex" maxW="100%">
<AddressIcon hash={ toData.hash }/>
<AddressIcon address={ toData }/>
<AddressLink hash={ toData.hash } alias={ toData.name } fontWeight="500" ml={ 2 }/>
</Address>
</Td>
......
......@@ -57,7 +57,7 @@ const BlockDetails = () => {
}
if (isError) {
const is404 = error?.error?.status === 404;
const is404 = error?.payload?.status === 404;
return is404 ? <span>This block has not been processed yet.</span> : <DataFetchAlert/>;
}
......
......@@ -24,7 +24,7 @@ const CustomAbiListItem = ({ item, onEditClick, onDeleteClick }: Props) => {
return (
<ListItemMobile>
<AddressSnippet address={ item.contract_address_hash } subtitle={ item.name } isContract/>
<AddressSnippet address={ item.contract_address } subtitle={ item.name }/>
<TableItemActionButtons onDeleteClick={ onItemDeleteClick } onEditClick={ onItemEditClick }/>
</ListItemMobile>
);
......
......@@ -28,7 +28,7 @@ const CustomAbiTableItem = ({ item, onEditClick, onDeleteClick }: Props) => {
return (
<Tr alignItems="top" key={ item.id }>
<Td>
<AddressSnippet address={ item.contract_address_hash } subtitle={ item.name } isContract/>
<AddressSnippet address={ item.contract_address } subtitle={ item.name }/>
</Td>
<Td>
<TableItemActionButtons onDeleteClick={ onItemDeleteClick } onEditClick={ onItemEditClick }/>
......
......@@ -36,7 +36,7 @@ const LatestBlocksItem = ({ block, h }: Props) => {
transitionTimingFunction="linear"
borderRadius="12px"
border="1px solid"
borderColor={ useColorModeValue('gray.200', 'whiteAlpha.200') }
borderColor={ useColorModeValue('blackAlpha.200', 'whiteAlpha.200') }
p={ 6 }
h={ `${ h }px` }
minWidth={{ base: '100%', lg: '280px' }}
......
......@@ -15,7 +15,7 @@ const LatestBlocksItemSkeleton = () => {
minWidth={{ base: '100%', lg: '280px' }}
borderRadius="12px"
border="1px solid"
borderColor={ useColorModeValue('gray.200', 'whiteAlpha.200') }
borderColor={ useColorModeValue('blackAlpha.200', 'whiteAlpha.200') }
p={ 6 }
>
<Flex justifyContent="space-between" alignItems="center" mb={ 3 }>
......
......@@ -37,7 +37,7 @@ type Props = {
}
const LatestBlocksItem = ({ tx }: Props) => {
const borderColor = useColorModeValue('gray.200', 'whiteAlpha.200');
const borderColor = useColorModeValue('blackAlpha.200', 'whiteAlpha.200');
const iconColor = useColorModeValue('blue.600', 'blue.300');
const dataTo = tx.to ? tx.to : tx.created_contract;
......@@ -109,7 +109,7 @@ const LatestBlocksItem = ({ tx }: Props) => {
<Box width={{ base: '100%', lg: '50%' }}>
<Flex alignItems="center" mb={ 3 } justifyContent={{ base: 'start', lg: 'end' }}>
<Address>
<AddressIcon hash={ tx.from.hash }/>
<AddressIcon address={ tx.from }/>
<AddressLink
hash={ tx.from.hash }
alias={ tx.from.name }
......@@ -126,7 +126,7 @@ const LatestBlocksItem = ({ tx }: Props) => {
color="gray.500"
/>
<Address>
<AddressIcon hash={ dataTo.hash }/>
<AddressIcon address={ dataTo }/>
<AddressLink
hash={ dataTo.hash }
alias={ dataTo.name }
......
......@@ -9,7 +9,7 @@ import {
import React from 'react';
const LatestTxsItemSkeleton = () => {
const borderColor = useColorModeValue('gray.200', 'whiteAlpha.200');
const borderColor = useColorModeValue('blackAlpha.200', 'whiteAlpha.200');
return (
<Box
......
......@@ -25,7 +25,7 @@ const AddressTagListItem = ({ item, onEditClick, onDeleteClick }: Props) => {
return (
<ListItemMobile>
<Flex alignItems="flex-start" flexDirection="column" maxW="100%">
<AddressSnippet address={ item.address_hash }/>
<AddressSnippet address={ item.address }/>
<HStack spacing={ 3 } mt={ 4 }>
<Text fontSize="sm" fontWeight={ 500 }>Private tag</Text>
<Tag>
......
......@@ -29,7 +29,7 @@ const AddressTagTableItem = ({ item, onEditClick, onDeleteClick }: Props) => {
return (
<Tr alignItems="top" key={ item.id }>
<Td>
<AddressSnippet address={ item.address_hash }/>
<AddressSnippet address={ item.address }/>
</Td>
<Td whiteSpace="nowrap">
<TruncatedTextTooltip label={ item.name }>
......
......@@ -27,7 +27,7 @@ const PublicTagListItem = ({ item, onEditClick, onDeleteClick }: Props) => {
<ListItemMobile>
<VStack spacing={ 3 } alignItems="flex-start" maxW="100%">
<VStack spacing={ 4 } alignItems="unset" maxW="100%">
{ item.addresses.map((address) => <AddressSnippet key={ address } address={ address }/>) }
{ item.addresses_with_info.map((address) => <AddressSnippet key={ address.hash } address={ address }/>) }
</VStack>
<HStack spacing={ 3 }>
<Text fontSize="sm" fontWeight={ 500 }>Public tags</Text>
......
......@@ -32,7 +32,7 @@ const PublicTagTableItem = ({ item, onEditClick, onDeleteClick }: Props) => {
<Tr alignItems="top" key={ item.id }>
<Td>
<VStack spacing={ 4 } alignItems="unset">
{ item.addresses.map((address) => <AddressSnippet key={ address } address={ address }/>) }
{ item.addresses_with_info.map((address) => <AddressSnippet key={ address.hash } address={ address }/>) }
</VStack>
</Td>
<Td>
......
import { Text, Box } from '@chakra-ui/react';
import React from 'react';
import type { AddressParam } from 'types/api/addressParams';
import Address from 'ui/shared/address/Address';
import AddressIcon from 'ui/shared/address/AddressIcon';
import AddressLink from 'ui/shared/address/AddressLink';
import CopyToClipboard from 'ui/shared/CopyToClipboard';
import AddressContractIcon from './address/AddressContractIcon';
interface Props {
address: string;
address: AddressParam;
subtitle?: string;
// temporary solution for custom abis while we don't have address info on account pages
isContract?: boolean;
}
const AddressSnippet = ({ address, isContract, subtitle }: Props) => {
const AddressSnippet = ({ address, subtitle }: Props) => {
return (
<Box maxW="100%">
<Address>
{ isContract ? <AddressContractIcon/> : <AddressIcon hash={ address }/> }
<AddressLink hash={ address } fontWeight="600" ml={ 2 }/>
<CopyToClipboard text={ address } ml={ 1 }/>
<AddressIcon address={ address }/>
<AddressLink hash={ address.hash } fontWeight="600" ml={ 2 }/>
<CopyToClipboard text={ address.hash } ml={ 1 }/>
</Address>
{ subtitle && <Text fontSize="sm" variant="secondary" mt={ 0.5 } ml={ 8 }>{ subtitle }</Text> }
</Box>
......
......@@ -2,6 +2,9 @@ import { Flex } from '@chakra-ui/react';
import { useQuery } from '@tanstack/react-query';
import React from 'react';
import appConfig from 'configs/app/config';
import buildUrl from 'lib/api/buildUrl';
import { getResourceKey } from 'lib/api/useApiQuery';
import * as cookies from 'lib/cookies';
import useFetch from 'lib/hooks/useFetch';
import AppError from 'ui/shared/AppError/AppError';
......@@ -23,9 +26,26 @@ const Page = ({
isHomePage,
renderHeader,
}: Props) => {
const fetch = useFetch();
const nodeApiFetch = useFetch();
useQuery([ 'csrf' ], async() => await fetch('/node-api/csrf'), {
useQuery(getResourceKey('csrf'), async() => {
if (appConfig.host === appConfig.api.host) {
const url = buildUrl('csrf');
const apiResponse = await fetch(url, { credentials: 'include' });
const csrfFromHeader = apiResponse.headers.get('x-bs-account-csrf');
// eslint-disable-next-line no-console
console.log('>>> RESPONSE HEADERS <<<');
// eslint-disable-next-line no-console
console.table([ {
'content-length': apiResponse.headers.get('content-length'),
'x-bs-account-csrf': csrfFromHeader,
} ]);
return csrfFromHeader ? { token: csrfFromHeader } : undefined;
}
return nodeApiFetch('/node-api/csrf');
}, {
enabled: Boolean(cookies.get(cookies.NAMES.API_TOKEN)),
});
......
......@@ -80,7 +80,7 @@ const TokenTransferListItem = ({
) }
<Flex w="100%" columnGap={ 3 }>
<Address width={ addressWidth }>
<AddressIcon hash={ from.hash }/>
<AddressIcon address={ from }/>
<AddressLink ml={ 2 } fontWeight="500" hash={ from.hash }/>
</Address>
{ baseAddress ?
......@@ -88,7 +88,7 @@ const TokenTransferListItem = ({
<Icon as={ eastArrowIcon } boxSize={ 6 } color="gray.500"/>
}
<Address width={ addressWidth }>
<AddressIcon hash={ to.hash }/>
<AddressIcon address={ to }/>
<AddressLink ml={ 2 } fontWeight="500" hash={ to.hash }/>
</Address>
</Flex>
......
......@@ -69,7 +69,7 @@ const TokenTransferTableItem = ({
) }
<Td>
<Address display="inline-flex" maxW="100%" lineHeight="30px">
<AddressIcon hash={ from.hash }/>
<AddressIcon address={ from }/>
<AddressLink ml={ 2 } fontWeight="500" hash={ from.hash } alias={ from.name } flexGrow={ 1 }/>
</Address>
</Td>
......@@ -80,7 +80,7 @@ const TokenTransferTableItem = ({
) }
<Td>
<Address display="inline-flex" maxW="100%" lineHeight="30px">
<AddressIcon hash={ to.hash }/>
<AddressIcon address={ to }/>
<AddressLink ml={ 2 } fontWeight="500" hash={ to.hash } alias={ to.name } flexGrow={ 1 }/>
</Address>
</Td>
......
import { Box, chakra, Tooltip } from '@chakra-ui/react';
import { Box, chakra, Tooltip, useColorModeValue } from '@chakra-ui/react';
import React from 'react';
type Props = {
......@@ -6,6 +6,9 @@ type Props = {
}
const AddressContractIcon = ({ className }: Props) => {
const bgColor = useColorModeValue('gray.200', 'gray.600');
const color = useColorModeValue('gray.400', 'gray.200');
return (
<Tooltip label="Contract">
<Box
......@@ -13,8 +16,8 @@ const AddressContractIcon = ({ className }: Props) => {
width="24px"
height="24px"
borderRadius="12px"
backgroundColor="gray.200"
color="gray.400"
backgroundColor={ bgColor }
color={ color }
display="inline-flex"
alignItems="center"
justifyContent="center"
......
......@@ -2,10 +2,25 @@ import { Box, chakra } from '@chakra-ui/react';
import React from 'react';
import Jazzicon, { jsNumberForAddress } from 'react-jazzicon';
const AddressIcon = ({ hash, className }: {hash: string; className?: string}) => {
import type { AddressParam } from 'types/api/addressParams';
import AddressContractIcon from 'ui/shared/address/AddressContractIcon';
type Props = {
address: AddressParam;
className?: string;
}
const AddressIcon = ({ address, className }: Props) => {
if (address.is_contract) {
return (
<AddressContractIcon/>
);
}
return (
<Box className={ className } width="24px" display="inline-flex">
<Jazzicon diameter={ 24 } seed={ jsNumberForAddress(hash) }/>
<Jazzicon diameter={ 24 } seed={ jsNumberForAddress(address.hash) }/>
</Box>
);
};
......
......@@ -31,6 +31,7 @@ const ChartWidget = ({ items, title, description, isLoading, chartHeight }: Prop
const [ isFullscreen, setIsFullscreen ] = useState(false);
const [ isZoomResetInitial, setIsZoomResetInitial ] = React.useState(true);
const pngBackgroundColor = useColorModeValue('white', 'black');
const borderColor = useColorModeValue('gray.200', 'gray.600');
const handleZoom = useCallback(() => {
......@@ -54,11 +55,12 @@ const ChartWidget = ({ items, title, description, isLoading, chartHeight }: Prop
domToImage.toPng(ref.current,
{
quality: 100,
bgcolor: 'white',
bgcolor: pngBackgroundColor,
width: ref.current.offsetWidth * DOWNLOAD_IMAGE_SCALE,
height: ref.current.offsetHeight * DOWNLOAD_IMAGE_SCALE,
filter: (node) => node.nodeName !== 'BUTTON',
style: {
borderColor: 'transparent',
transform: `scale(${ DOWNLOAD_IMAGE_SCALE })`,
'transform-origin': 'top left',
},
......@@ -71,7 +73,7 @@ const ChartWidget = ({ items, title, description, isLoading, chartHeight }: Prop
link.remove();
});
}
}, [ title ]);
}, [ pngBackgroundColor, title ]);
const handleSVGSavingClick = useCallback(() => {
if (items) {
......
......@@ -12,7 +12,7 @@ export default function useColors() {
active: useColorModeValue('blue.50', 'gray.800'),
},
border: {
'default': useColorModeValue('gray.200', 'whiteAlpha.200'),
'default': useColorModeValue('blackAlpha.200', 'whiteAlpha.200'),
active: useColorModeValue('blue.50', 'gray.800'),
},
};
......
......@@ -7,7 +7,7 @@ export default function useColors({ hasIcon }: {hasIcon: boolean}) {
return {
text: {
'default': useColorModeValue('gray.600', 'gray.400'),
active: useColorModeValue('gray.700', 'whiteAlpha.900'),
active: useColorModeValue('blackAlpha.900', 'whiteAlpha.900'),
hover: useColorModeValue('blue.600', 'blue.400'),
},
icon: {
......@@ -19,7 +19,7 @@ export default function useColors({ hasIcon }: {hasIcon: boolean}) {
active: useColorModeValue('blue.50', 'gray.800'),
},
border: {
'default': useColorModeValue('gray.200', 'whiteAlpha.200'),
'default': useColorModeValue('blackAlpha.200', 'whiteAlpha.200'),
active: useColorModeValue('blue.50', 'gray.800'),
},
};
......
......@@ -12,8 +12,8 @@ type Props = UserInfo;
const ProfileMenuContent = ({ name, nickname, email }: Props) => {
const { accountNavItems, profileItem } = useNavItems();
const borderColor = useColorModeValue('gray.200', 'whiteAlpha.200');
const primaryTextColor = useColorModeValue('gray.600', 'whiteAlpha.800');
const borderColor = useColorModeValue('blackAlpha.200', 'whiteAlpha.200');
const primaryTextColor = useColorModeValue('blackAlpha.800', 'whiteAlpha.800');
return (
<Box>
......
......@@ -3,6 +3,7 @@ import React from 'react';
import useApiQuery from 'lib/api/useApiQuery';
import { numberWidgetsScheme } from './constants/number-widgets-scheme';
import NumberWidget from './NumberWidget';
import NumberWidgetSkeleton from './NumberWidgetSkeleton';
......@@ -18,30 +19,8 @@ const NumberWidgetsList = () => {
>
{ isLoading ? [ ...Array(skeletonsCount) ]
.map((e, i) => <NumberWidgetSkeleton key={ i }/>) :
(
<>
<NumberWidget
label="Total blocks"
value={ Number(data?.counters.totalBlocksAllTime).toLocaleString() }
/>
<NumberWidget
label="Average block time"
value={ Number(data?.counters.averageBlockTime).toLocaleString() }
/>
<NumberWidget
label="Completed transactions"
value={ Number(data?.counters.completedTransactions).toLocaleString() }
/>
<NumberWidget
label="Total transactions"
value={ Number(data?.counters.totalTransactions).toLocaleString() }
/>
<NumberWidget
label="Total accounts"
value={ Number(data?.counters.totalAccounts).toLocaleString() }
/>
</>
) }
numberWidgetsScheme.map(({ id, title }) =>
data?.counters[id] ? <NumberWidget key={ id } label={ title } value={ Number(data.counters[id]).toLocaleString() }/> : null) }
</Grid>
);
};
......
......@@ -6,57 +6,57 @@ export const statsChartsScheme: Array<StatsSection> = [
title: 'Blocks',
charts: [
{
id: 'new-blocks',
id: 'newBlocksPerDay',
title: 'New blocks',
description: 'New blocks number per day',
},
{
id: 'average-block-size',
title: 'Average block size',
description: 'Average size of blocks in bytes',
},
],
},
{
id: 'transactions',
title: 'Transactions',
charts: [
{
id: 'average-transaction-fee',
title: 'Average transaction fee',
description: 'The average amount in USD spent per transaction',
},
{
id: 'transactions-fees',
title: 'Transactions fees',
description: 'Amount of tokens paid as fees',
},
{
id: 'new-transactions',
title: 'Transactions fees',
description: 'New transactions number per period',
},
{
id: 'transactions-growth',
title: 'Transactions growth',
description: 'Cumulative transactions number per period',
},
],
},
{
id: 'accounts',
title: 'Accounts',
charts: [
{
id: 'active-accounts',
title: 'Active accounts',
description: 'Active accounts number per period',
},
{
id: 'accounts-growth',
title: 'Accounts growth',
description: 'Cumulative accounts number per period',
},
// {
// id: 'average-block-size',
// title: 'Average block size',
// description: 'Average size of blocks in bytes',
// },
],
},
// {
// id: 'transactions',
// title: 'Transactions',
// charts: [
// {
// id: 'average-transaction-fee',
// title: 'Average transaction fee',
// description: 'The average amount in USD spent per transaction',
// },
// {
// id: 'transactions-fees',
// title: 'Transactions fees',
// description: 'Amount of tokens paid as fees',
// },
// {
// id: 'new-transactions',
// title: 'Transactions fees',
// description: 'New transactions number per period',
// },
// {
// id: 'transactions-growth',
// title: 'Transactions growth',
// description: 'Cumulative transactions number per period',
// },
// ],
// },
// {
// id: 'accounts',
// title: 'Accounts',
// charts: [
// {
// id: 'active-accounts',
// title: 'Active accounts',
// description: 'Active accounts number per period',
// },
// {
// id: 'accounts-growth',
// title: 'Accounts growth',
// description: 'Cumulative accounts number per period',
// },
// ],
// },
];
import type { Stats } from 'types/api/stats';
type Key = keyof Stats['counters'];
export const numberWidgetsScheme: Array<{id: Key; title: string}> = [
{
id: 'totalBlocks',
title: 'Total blocks',
},
{
id: 'averageBlockTime',
title: 'Average block time',
},
{
id: 'totalTransactions',
title: 'Total transactions',
},
{
id: 'completedTransactions',
title: 'Completed transactions',
},
{
id: 'totalAccounts',
title: 'Total accounts',
},
{
id: 'totalTokens',
title: 'Total tokens',
},
{
id: 'totalNativeCoinHolders',
title: 'Total native coin holders',
},
{
id: 'totalNativeCoinTransfers',
title: 'Total native coin transfers',
},
{
id: 'totalAccounts',
title: 'Total accounts',
},
];
......@@ -27,7 +27,6 @@ test('between addresses +@mobile +@dark-mode', async({ mount, page }) => {
{ hooksConfig },
);
await page.waitForResponse(API_URL);
await page.getByText('View details').click();
await expect(component).toHaveScreenshot();
......@@ -45,7 +44,6 @@ test('creating contact', async({ mount, page }) => {
</TestApp>,
{ hooksConfig },
);
await page.waitForResponse(API_URL);
await expect(component).toHaveScreenshot();
});
......@@ -62,7 +60,6 @@ test('with token transfer +@mobile', async({ mount, page }) => {
</TestApp>,
{ hooksConfig },
);
await page.waitForResponse(API_URL);
await expect(component).toHaveScreenshot();
});
......@@ -79,7 +76,6 @@ test('with decoded revert reason', async({ mount, page }) => {
</TestApp>,
{ hooksConfig },
);
await page.waitForResponse(API_URL);
await expect(component).toHaveScreenshot();
});
......@@ -96,7 +92,6 @@ test('with decoded raw reason', async({ mount, page }) => {
</TestApp>,
{ hooksConfig },
);
await page.waitForResponse(API_URL);
await expect(component).toHaveScreenshot();
});
......@@ -113,7 +108,7 @@ test('pending', async({ mount, page }) => {
</TestApp>,
{ hooksConfig },
);
await page.waitForResponse(API_URL);
await page.getByText('View details').click();
await expect(component).toHaveScreenshot();
......
......@@ -147,7 +147,7 @@ const TxDetails = () => {
columnGap={ 3 }
>
<Address>
<AddressIcon hash={ data.from.hash }/>
<AddressIcon address={ data.from }/>
<AddressLink ml={ 2 } hash={ data.from.hash }/>
<CopyToClipboard text={ data.from.hash }/>
</Address>
......@@ -166,7 +166,7 @@ const TxDetails = () => {
>
{ data.to && data.to.hash ? (
<Address alignItems="center">
<AddressIcon hash={ toAddress.hash }/>
<AddressIcon address={ toAddress }/>
<AddressLink ml={ 2 } hash={ toAddress.hash }/>
{ executionSuccessBadge }
{ executionFailedBadge }
......
......@@ -27,12 +27,12 @@ const TxInternalsListItem = ({ type, from, to, value, success, error, gas_limit:
</Flex>
<Box w="100%" display="flex" columnGap={ 3 }>
<Address width="calc((100% - 48px) / 2)">
<AddressIcon hash={ from.hash }/>
<AddressIcon address={ from }/>
<AddressLink ml={ 2 } fontWeight="500" hash={ from.hash }/>
</Address>
<Icon as={ eastArrowIcon } boxSize={ 6 } color="gray.500"/>
<Address width="calc((100% - 48px) / 2)">
<AddressIcon hash={ toData.hash }/>
<AddressIcon address={ toData }/>
<AddressLink ml={ 2 } fontWeight="500" hash={ toData.hash }/>
</Address>
</Box>
......
......@@ -32,7 +32,7 @@ const TxInternalTableItem = ({ type, from, to, value, success, error, gas_limit:
</Td>
<Td verticalAlign="middle">
<Address display="inline-flex" maxW="100%">
<AddressIcon hash={ from.hash }/>
<AddressIcon address={ from }/>
<AddressLink ml={ 2 } fontWeight="500" hash={ from.hash } alias={ from.name } flexGrow={ 1 }/>
</Address>
</Td>
......@@ -41,7 +41,7 @@ const TxInternalTableItem = ({ type, from, to, value, success, error, gas_limit:
</Td>
<Td verticalAlign="middle">
<Address display="inline-flex" maxW="100%">
<AddressIcon hash={ toData.hash }/>
<AddressIcon address={ toData }/>
<AddressLink hash={ toData.hash } alias={ toData.name } fontWeight="500" ml={ 2 }/>
</Address>
</Td>
......
......@@ -50,7 +50,7 @@ const TxLogItem = ({ address, index, topics, data, decoded }: Props) => {
<RowHeader>Address</RowHeader>
<GridItem display="flex" alignItems="center">
<Address mr={{ base: 9, lg: 0 }}>
<AddressIcon hash={ address.hash }/>
<AddressIcon address={ address }/>
<AddressLink hash={ address.hash } alias={ address.name } ml={ 2 }/>
</Address>
{ /* api doesn't have find topic feature yet */ }
......
......@@ -9,7 +9,7 @@ import type { data } from 'data/txState';
import { nbsp } from 'lib/html-entities';
import getNetworkValidatorTitle from 'lib/networks/getNetworkValidatorTitle';
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 ListItemMobile from 'ui/shared/ListItemMobile';
......@@ -51,7 +51,8 @@ const TxStateListItem = ({ storage, address, miner, after, before, diff }: Props
<AccordionIcon color="blue.600" width="30px"/>
</AccordionButton>
<Address flexGrow={ 1 }>
<AddressIcon hash={ address }/>
{ /* ??? */ }
{ /* <AddressIcon hash={ address }/> */ }
<AddressLink hash={ address } ml={ 2 }/>
</Address>
</Flex>
......
......@@ -18,7 +18,7 @@ import React, { useRef } from 'react';
import type { TTxStateItem } from 'data/txState';
import { nbsp } from 'lib/html-entities';
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 TxStateStorageItem from './TxStateStorageItem';
......@@ -58,7 +58,8 @@ const TxStateTableItem = ({ txStateItem }: { txStateItem: TTxStateItem }) => {
</Td>
<Td border={ 0 }>
<Address height="30px">
<AddressIcon hash={ txStateItem.address }/>
{ /* ??? */ }
{ /* <AddressIcon hash={ txStateItem.address }/> */ }
<AddressLink hash={ txStateItem.address } fontWeight="500" truncation="constant" ml={ 2 }/>
</Address>
</Td>
......
......@@ -12,7 +12,7 @@ import TextSeparator from 'ui/shared/TextSeparator';
import Utilization from 'ui/shared/Utilization/Utilization';
const TxAdditionalInfo = ({ tx }: { tx: Transaction }) => {
const sectionBorderColor = useColorModeValue('gray.200', 'whiteAlpha.200');
const sectionBorderColor = useColorModeValue('blackAlpha.200', 'whiteAlpha.200');
const sectionProps = {
borderBottom: '1px solid',
borderColor: sectionBorderColor,
......
......@@ -72,7 +72,7 @@ const TxsFilters = ({ onFiltersChange, filters, appliedFiltersNum }: Props) => {
onClose();
}, [ onClose, onFiltersChange, typeFilter, methodFilter ]);
const borderColor = useColorModeValue('gray.200', 'whiteAlpha.200');
const borderColor = useColorModeValue('blackAlpha.200', 'whiteAlpha.200');
return (
<Popover isOpen={ isOpen } onClose={ onClose } placement="bottom-start" isLazy>
......
......@@ -102,7 +102,7 @@ const TxsListItem = ({ tx, showBlockInfo, currentAddress, enableTimeIncrement }:
) }
<Flex alignItems="center" height={ 6 } mt={ 6 }>
<Address width={ `calc((100%-${ currentAddress ? TAG_WIDTH : ARROW_WIDTH + 8 }px)/2)` }>
<AddressIcon hash={ tx.from.hash }/>
<AddressIcon address={ tx.from }/>
<AddressLink
hash={ tx.from.hash }
alias={ tx.from.name }
......@@ -120,7 +120,7 @@ const TxsListItem = ({ tx, showBlockInfo, currentAddress, enableTimeIncrement }:
/>
) }
<Address width="calc((100%-40px)/2)">
<AddressIcon hash={ dataTo.hash }/>
<AddressIcon address={ dataTo }/>
<AddressLink
hash={ dataTo.hash }
alias={ dataTo.name }
......
......@@ -51,7 +51,7 @@ const TxsTableItem = ({ tx, showBlockInfo, currentAddress, enableTimeIncrement }
const addressFrom = (
<Address>
<Tooltip label={ tx.from.implementation_name }>
<Box display="flex"><AddressIcon hash={ tx.from.hash }/></Box>
<Box display="flex"><AddressIcon address={ tx.from }/></Box>
</Tooltip>
<AddressLink hash={ tx.from.hash } alias={ tx.from.name } fontWeight="500" ml={ 2 } truncation="constant"/>
</Address>
......@@ -62,13 +62,13 @@ const TxsTableItem = ({ tx, showBlockInfo, currentAddress, enableTimeIncrement }
const addressTo = (
<Address>
<Tooltip label={ dataTo.implementation_name }>
<Box display="flex"><AddressIcon hash={ dataTo.hash }/></Box>
<Box display="flex"><AddressIcon address={ dataTo }/></Box>
</Tooltip>
<AddressLink hash={ dataTo.hash } alias={ dataTo.name } fontWeight="500" ml={ 2 } truncation="constant"/>
</Address>
);
const infoBorderColor = useColorModeValue('gray.200', 'whiteAlpha.200');
const infoBorderColor = useColorModeValue('blackAlpha.200', 'whiteAlpha.200');
return (
<Tr>
<Td pl={ 4 }>
......
......@@ -16,7 +16,7 @@ const WatchListAddressItem = ({ item }: {item: TWatchlistItem}) => {
return (
<VStack spacing={ 2 } align="stretch" fontWeight={ 500 }>
<AddressSnippet address={ item.address_hash }/>
<AddressSnippet address={ item.address }/>
<Flex fontSize="sm" h={ 6 } pl={ infoItemsPaddingLeft } flexWrap="wrap" alignItems="center" rowGap={ 1 }>
{ appConfig.network.currency.address && (
<TokenLogo
......
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