Commit 6760a274 authored by tom's avatar tom

Merge branch 'main' of github.com:blockscout/frontend into client-api-calls

parents 808184ca 4d8d344d
NEXT_PUBLIC_SUPPORTED_NETWORKS=APP_NEXT_NEXT_PUBLIC_SUPPORTED_NETWORKS # app config
NEXT_PUBLIC_BLOCKSCOUT_VERSION=APP_NEXT_NEXT_PUBLIC_BLOCKSCOUT_VERSION NEXT_PUBLIC_APP_ENV=__PLACEHOLDER_FOR_NEXT_PUBLIC_APP_ENV__
NEXT_PUBLIC_FOOTER_GITHUB_LINK=APP_NEXT_NEXT_PUBLIC_FOOTER_GITHUB_LINK NEXT_PUBLIC_APP_INSTANCE=__PLACEHOLDER_FOR_NEXT_PUBLIC_APP_INSTANCE__
NEXT_PUBLIC_FOOTER_TWITTER_LINK=APP_NEXT_NEXT_PUBLIC_FOOTER_TWITTER_LINK NEXT_PUBLIC_APP_PROTOCOL=__PLACEHOLDER_FOR_NEXT_PUBLIC_APP_PROTOCOL__
NEXT_PUBLIC_FOOTER_TELEGRAM_LINK=APP_NEXT_NEXT_PUBLIC_FOOTER_TELEGRAM_LINK NEXT_PUBLIC_APP_HOST=__PLACEHOLDER_FOR_NEXT_PUBLIC_APP_HOST__
NEXT_PUBLIC_FOOTER_STAKING_LINK=APP_NEXT_NEXT_PUBLIC_FOOTER_STAKING_LINK NEXT_PUBLIC_APP_PORT=__PLACEHOLDER_FOR_NEXT_PUBLIC_APP_PORT__
NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=APP_NEXT_NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM
NEXT_PUBLIC_SENTRY_DSN=APP_NEXT_NEXT_PUBLIC_SENTRY_DSN # network config
NEXT_PUBLIC_APP_INSTANCE=APP_NEXT_NEXT_PUBLIC_APP_INSTANCE NEXT_PUBLIC_NETWORK_NAME=__PLACEHOLDER_FOR_NEXT_PUBLIC_NETWORK_NAME__
NEXT_PUBLIC_NETWORK_NAME=APP_NEXT_NEXT_PUBLIC_NETWORK_NAME NEXT_PUBLIC_NETWORK_SHORT_NAME=__PLACEHOLDER_FOR_NEXT_PUBLIC_NETWORK_SHORT_NAME__
NEXT_PUBLIC_NETWORK_SHORT_NAME=APP_NEXT_NEXT_PUBLIC_NETWORK_SHORT_NAME NEXT_PUBLIC_NETWORK_ASSETS_PATHNAME=__PLACEHOLDER_FOR_NEXT_PUBLIC_NETWORK_ASSETS_PATHNAME__
NEXT_PUBLIC_NETWORK_ASSETS_PATHNAME=APP_NEXT_NEXT_PUBLIC_NETWORK_ASSETS_PATHNAME NEXT_PUBLIC_NETWORK_TYPE=__PLACEHOLDER_FOR_NEXT_PUBLIC_NETWORK_TYPE__
NEXT_PUBLIC_NETWORK_TYPE=APP_NEXT_NEXT_PUBLIC_NETWORK_TYPE NEXT_PUBLIC_NETWORK_ID=__PLACEHOLDER_FOR_NEXT_PUBLIC_NETWORK_ID__
NEXT_PUBLIC_NETWORK_SUBTYPE=APP_NEXT_NEXT_PUBLIC_NETWORK_SUBTYPE NEXT_PUBLIC_NETWORK_CURRENCY_NAME=__PLACEHOLDER_FOR_NEXT_PUBLIC_NETWORK_CURRENCY_NAME__
NEXT_PUBLIC_NETWORK_ID=APP_NEXT_NEXT_PUBLIC_NETWORK_ID NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=__PLACEHOLDER_FOR_NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL__
NEXT_PUBLIC_NETWORK_CURRENCY=APP_NEXT_NEXT_PUBLIC_NETWORK_CURRENCY NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=__PLACEHOLDER_FOR_NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS__
NEXT_PUBLIC_NETWORK_TOKEN_ADDRESS=APP_NEXT_NEXT_PUBLIC_NETWORK_TOKEN_ADDRESS NEXT_PUBLIC_NETWORK_TOKEN_ADDRESS=__PLACEHOLDER_FOR_NEXT_PUBLIC_NETWORK_TOKEN_ADDRESS__
NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=APP_NEXT_NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=__PLACEHOLDER_FOR_NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED__
NEXT_PUBLIC_FEATURED_NETWORKS=APP_NEXT_NEXT_PUBLIC_FEATURED_NETWORKS NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE=__PLACEHOLDER_FOR_NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE
NEXT_PUBLIC_APP_PROTOCOL=APP_NEXT_NEXT_PUBLIC_APP_PROTOCOL
NEXT_PUBLIC_APP_HOST=APP_NEXT_NEXT_PUBLIC_APP_HOST # ui config
NEXT_PUBLIC_APP_PORT=APP_NEXT_NEXT_PUBLIC_APP_PORT NEXT_PUBLIC_BLOCKSCOUT_VERSION=__PLACEHOLDER_FOR_NEXT_PUBLIC_BLOCKSCOUT_VERSION__
NEXT_PUBLIC_API_ENDPOINT=APP_NEXT_NEXT_PUBLIC_API_ENDPOINT NEXT_PUBLIC_FOOTER_GITHUB_LINK=__PLACEHOLDER_FOR_NEXT_PUBLIC_FOOTER_GITHUB_LINK__
NEXT_PUBLIC_API_BASE_PATH=APP_NEXT_NEXT_PUBLIC_API_BASE_PATH NEXT_PUBLIC_FOOTER_TWITTER_LINK=__PLACEHOLDER_FOR_NEXT_PUBLIC_FOOTER_TWITTER_LINK__
NEXT_PUBLIC_FOOTER_TELEGRAM_LINK=__PLACEHOLDER_FOR_NEXT_PUBLIC_FOOTER_TELEGRAM_LINK__
NEXT_PUBLIC_FOOTER_STAKING_LINK=__PLACEHOLDER_FOR_NEXT_PUBLIC_FOOTER_STAKING_LINK__
NEXT_PUBLIC_FEATURED_NETWORKS=__PLACEHOLDER_FOR_NEXT_PUBLIC_FEATURED_NETWORKS__
NEXT_PUBLIC_NETWORK_EXPLORERS=__PLACEHOLDER_FOR_NEXT_PUBLIC_NETWORK_EXPLORERS__
NEXT_PUBLIC_MARKETPLACE_APP_LIST=__PLACEHOLDER_FOR_NEXT_PUBLIC_MARKETPLACE_APP_LIST__
NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=__PLACEHOLDER_FOR_NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM__
NEXT_PUBLIC_LOGOUT_URL=__PLACEHOLDER_FOR_NEXT_PUBLIC_LOGOUT_URL__
NEXT_PUBLIC_LOGOUT_RETURN_URL=__PLACEHOLDER_FOR_NEXT_PUBLIC_LOGOUT_RETURN_URL__
# api config
NEXT_PUBLIC_API_HOST=__PLACEHOLDER_FOR_NEXT_PUBLIC_API_HOST__
NEXT_PUBLIC_API_BASE_PATH=__PLACEHOLDER_FOR_NEXT_PUBLIC_API_BASE_PATH__
# external services config
NEXT_PUBLIC_SENTRY_DSN=__PLACEHOLDER_FOR_NEXT_PUBLIC_SENTRY_DSN__
NEXT_PUBLIC_AUTH0_CLIENT_ID=__PLACEHOLDER_FOR_NEXT_PUBLIC_AUTH0_CLIENT_ID__
...@@ -59,21 +59,16 @@ jobs: ...@@ -59,21 +59,16 @@ jobs:
tags: ghcr.io/blockscout/frontend:prerelease-${{ env.SHORT_SHA }} tags: ghcr.io/blockscout/frontend:prerelease-${{ env.SHORT_SHA }}
labels: ${{ steps.meta.outputs.labels }} labels: ${{ steps.meta.outputs.labels }}
build-args: | build-args: |
SENTRY_DSN=${{ secrets.SENTRY_DSN }} GIT_COMMIT_SHA=${{ env.SHORT_SHA }}
NEXT_PUBLIC_SENTRY_DSN=${{ secrets.NEXT_PUBLIC_SENTRY_DSN }}
SENTRY_CSP_REPORT_URI=${{ secrets.SENTRY_CSP_REPORT_URI }}
SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }}
deploy_and_tests: deploy_and_tests:
needs: push_to_registry needs: push_to_registry
uses: blockscout/blockscout-ci-cd/.github/workflows/e2e_new.yaml@master uses: blockscout/blockscout-ci-cd/.github/workflows/e2e_new.yaml@master
with: with:
valuesDir: deploy/values/e2e
appName: e2e-front
appNamespace: e2e-front-$GITHUB_SHA_SHORT appNamespace: e2e-front-$GITHUB_SHA_SHORT
blockscoutIngressHost: blockscout
frontendIngressHost: frontend
frontendImage: ghcr.io/blockscout/frontend:prerelease-${{ needs.push_to_registry.outputs.shortSha }} frontendImage: ghcr.io/blockscout/frontend:prerelease-${{ needs.push_to_registry.outputs.shortSha }}
gethIngressHost: geth blockscoutIngressHost: e2e-blockscout-$GITHUB_SHA_SHORT
scVerifierIngressHost: sc-verifier frontendIngressHost: e2e-blockscout-$GITHUB_SHA_SHORT
gethIngressHost: e2e-geth-$GITHUB_SHA_SHORT
scVerifierIngressHost: e2e-sc-verifier-$GITHUB_SHA_SHORT
secrets: inherit secrets: inherit
name: Deploy review environment name: Deploy review environment
on: on:
# push:
pull_request: pull_request:
# push:
# branches-ignore:
# - 'main'
workflow_dispatch: workflow_dispatch:
env: env:
...@@ -66,10 +68,10 @@ jobs: ...@@ -66,10 +68,10 @@ jobs:
needs: push_to_registry needs: push_to_registry
uses: blockscout/blockscout-ci-cd/.github/workflows/deploy.yaml@master uses: blockscout/blockscout-ci-cd/.github/workflows/deploy.yaml@master
with: with:
valuesDir: deploy/values/review env_vars: VALUES_DIR=deploy/values/review,APP_NAME=bs-stack
appNamespace: review-front-$GITHUB_HEAD_REF_SLUG appNamespace: review-front-$GITHUB_HEAD_REF_SLUG
blockscoutIngressHost: blockscout blockscoutIngressHost: blockscout
frontendIngressHost: frontend frontendIngressHost: blockscout
frontendImage: ghcr.io/blockscout/frontend:prerelease-${{ needs.push_to_registry.outputs.shortSha }} frontendImage: ghcr.io/blockscout/frontend:prerelease-${{ needs.push_to_registry.outputs.shortSha }}
gethIngressHost: geth gethIngressHost: geth
scVerifierIngressHost: sc-verifier scVerifierIngressHost: sc-verifier
......
...@@ -56,10 +56,10 @@ jobs: ...@@ -56,10 +56,10 @@ jobs:
needs: push_to_registry needs: push_to_registry
uses: blockscout/blockscout-ci-cd/.github/workflows/deploy.yaml@master uses: blockscout/blockscout-ci-cd/.github/workflows/deploy.yaml@master
with: with:
valuesDir: deploy/values/review env_vars: VALUES_DIR=deploy/values/main,APP_NAME=bs-stack
appNamespace: front-main appNamespace: front-main
blockscoutIngressHost: blockscout blockscoutIngressHost: blockscout
frontendIngressHost: frontend frontendIngressHost: blockscout
frontendImage: ghcr.io/blockscout/frontend:main frontendImage: ghcr.io/blockscout/frontend:main
gethIngressHost: geth gethIngressHost: geth
scVerifierIngressHost: sc-verifier scVerifierIngressHost: sc-verifier
......
This diff is collapsed.
/* eslint-disable no-restricted-properties */ /* eslint-disable no-restricted-properties */
import type { AppItemOverview } from 'types/client/apps';
import type { FeaturedNetwork, NetworkExplorer, PreDefinedNetwork } from 'types/networks';
const getEnvValue = (env: string | undefined) => env?.replaceAll('\'', '"');
const parseEnvJson = <DataType>(env: string | undefined): DataType | null => {
try {
return JSON.parse(env || 'null');
} catch (error) {
return null;
}
};
const stripTrailingSlash = (str: string) => str.at(-1) === '/' ? str.slice(0, -1) : str;
const env = process.env.VERCEL_ENV || process.env.NODE_ENV; const env = process.env.VERCEL_ENV || process.env.NODE_ENV;
const isDev = env === 'development'; const isDev = env === 'development';
const appPort = getEnvValue(process.env.NEXT_PUBLIC_APP_PORT);
const appSchema = getEnvValue(process.env.NEXT_PUBLIC_APP_PROTOCOL);
const appHost = getEnvValue(process.env.NEXT_PUBLIC_APP_HOST);
const baseUrl = [ const baseUrl = [
process.env.NEXT_PUBLIC_APP_PROTOCOL || 'https', appSchema || 'https',
'://', '://',
process.env.NEXT_PUBLIC_VERCEL_URL || process.env.NEXT_PUBLIC_APP_HOST, process.env.NEXT_PUBLIC_VERCEL_URL || appHost,
process.env.NEXT_PUBLIC_APP_PORT ? ':' + process.env.NEXT_PUBLIC_APP_PORT : '', appPort && ':' + appPort,
].join(''); ].filter(Boolean).join('');
const apiHost = getEnvValue(process.env.NEXT_PUBLIC_API_HOST);
const logoutUrl = (() => {
try {
const envUrl = getEnvValue(process.env.NEXT_PUBLIC_LOGOUT_URL);
const auth0ClientId = getEnvValue(process.env.NEXT_PUBLIC_AUTH0_CLIENT_ID);
const returnUrl = getEnvValue(process.env.NEXT_PUBLIC_LOGOUT_RETURN_URL);
if (!envUrl || !auth0ClientId || !returnUrl) {
throw Error();
}
const url = new URL(envUrl);
url.searchParams.set('client_id', auth0ClientId);
url.searchParams.set('returnTo', returnUrl);
return url.toString();
} catch (error) {
return;
}
})();
const DEFAULT_CURRENCY_DECIMALS = 18;
const config = Object.freeze({ const config = Object.freeze({
env, env,
isDev, isDev,
network: { network: {
type: process.env.NEXT_PUBLIC_NETWORK_TYPE, type: getEnvValue(process.env.NEXT_PUBLIC_NETWORK_TYPE) as PreDefinedNetwork | undefined,
subtype: process.env.NEXT_PUBLIC_NETWORK_SUBTYPE, logo: getEnvValue(process.env.NEXT_PUBLIC_NETWORK_LOGO),
logo: process.env.NEXT_PUBLIC_NETWORK_LOGO, name: getEnvValue(process.env.NEXT_PUBLIC_NETWORK_NAME),
name: process.env.NEXT_PUBLIC_NETWORK_NAME, id: getEnvValue(process.env.NEXT_PUBLIC_NETWORK_ID),
id: process.env.NEXT_PUBLIC_NETWORK_ID, shortName: getEnvValue(process.env.NEXT_PUBLIC_NETWORK_SHORT_NAME),
shortName: process.env.NEXT_PUBLIC_NETWORK_SHORT_NAME, currency: {
currency: process.env.NEXT_PUBLIC_NETWORK_CURRENCY, name: getEnvValue(process.env.NEXT_PUBLIC_NETWORK_CURRENCY_NAME),
assetsPathname: process.env.NEXT_PUBLIC_NETWORK_ASSETS_PATHNAME, symbol: getEnvValue(process.env.NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL),
nativeTokenAddress: process.env.NEXT_PUBLIC_NETWORK_TOKEN_ADDRESS, decimals: Number(getEnvValue(process.env.NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS)) || DEFAULT_CURRENCY_DECIMALS,
basePath: '/' + [ process.env.NEXT_PUBLIC_NETWORK_TYPE, process.env.NEXT_PUBLIC_NETWORK_SUBTYPE ].filter(Boolean).join('/'), },
assetsPathname: getEnvValue(process.env.NEXT_PUBLIC_NETWORK_ASSETS_PATHNAME),
nativeTokenAddress: getEnvValue(process.env.NEXT_PUBLIC_NETWORK_TOKEN_ADDRESS),
explorers: parseEnvJson<Array<NetworkExplorer>>(getEnvValue(process.env.NEXT_PUBLIC_NETWORK_EXPLORERS)) || [],
verificationType: process.env.NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE || 'mining',
}, },
footerLinks: { footerLinks: {
github: process.env.NEXT_PUBLIC_FOOTER_GITHUB_LINK, github: getEnvValue(process.env.NEXT_PUBLIC_FOOTER_GITHUB_LINK),
twitter: process.env.NEXT_PUBLIC_FOOTER_TWITTER_LINK, twitter: getEnvValue(process.env.NEXT_PUBLIC_FOOTER_TWITTER_LINK),
telegram: process.env.NEXT_PUBLIC_FOOTER_TELEGRAM_LINK, telegram: getEnvValue(process.env.NEXT_PUBLIC_FOOTER_TELEGRAM_LINK),
staking: process.env.NEXT_PUBLIC_FOOTER_STAKING_LINK, staking: getEnvValue(process.env.NEXT_PUBLIC_FOOTER_STAKING_LINK),
}, },
featuredNetworks: process.env.NEXT_PUBLIC_FEATURED_NETWORKS?.replaceAll('\'', '"'), featuredNetworks: parseEnvJson<Array<FeaturedNetwork>>(getEnvValue(process.env.NEXT_PUBLIC_FEATURED_NETWORKS)) || [],
blockScoutVersion: process.env.NEXT_PUBLIC_BLOCKSCOUT_VERSION, blockScoutVersion: getEnvValue(process.env.NEXT_PUBLIC_BLOCKSCOUT_VERSION),
isAccountSupported: process.env.NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED?.replaceAll('\'', '"') === 'true', isAccountSupported: getEnvValue(process.env.NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED) === 'true',
marketplaceSubmitForm: process.env.NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM, marketplaceAppList: parseEnvJson<Array<AppItemOverview>>(getEnvValue(process.env.NEXT_PUBLIC_MARKETPLACE_APP_LIST)) || [],
protocol: process.env.NEXT_PUBLIC_APP_PROTOCOL, marketplaceSubmitForm: getEnvValue(process.env.NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM),
host: process.env.NEXT_PUBLIC_APP_HOST, protocol: appSchema,
port: process.env.NEXT_PUBLIC_APP_PORT, host: appHost,
port: appPort,
baseUrl, baseUrl,
logoutUrl,
api: { api: {
endpoint: process.env.NEXT_PUBLIC_API_ENDPOINT || 'https://blockscout.com', endpoint: apiHost ? `https://${ apiHost }` : 'https://blockscout.com',
basePath: process.env.NEXT_PUBLIC_API_BASE_PATH || '', socket: apiHost ? `wss://${ apiHost }` : 'wss://blockscout.com',
basePath: stripTrailingSlash(getEnvValue(process.env.NEXT_PUBLIC_API_BASE_PATH) || ''),
}, },
}); });
......
...@@ -3,15 +3,12 @@ NEXT_PUBLIC_APP_PROTOCOL=http ...@@ -3,15 +3,12 @@ NEXT_PUBLIC_APP_PROTOCOL=http
NEXT_PUBLIC_APP_HOST=localhost NEXT_PUBLIC_APP_HOST=localhost
NEXT_PUBLIC_APP_PORT=3000 NEXT_PUBLIC_APP_PORT=3000
NEXT_PUBLIC_APP_INSTANCE=local NEXT_PUBLIC_APP_INSTANCE=local
NEXT_PUBLIC_APP_ENV=development
# nav and footer config # ui config
NEXT_PUBLIC_BLOCKSCOUT_VERSION=v4.1.7-beta NEXT_PUBLIC_BLOCKSCOUT_VERSION=v4.1.7-beta
NEXT_PUBLIC_FOOTER_GITHUB_LINK=https://github.com/blockscout/blockscout NEXT_PUBLIC_FOOTER_GITHUB_LINK=https://github.com/blockscout/blockscout
NEXT_PUBLIC_FOOTER_TWITTER_LINK=https://www.twitter.com/blockscoutcom NEXT_PUBLIC_FOOTER_TWITTER_LINK=https://www.twitter.com/blockscoutcom
NEXT_PUBLIC_FEATURED_NETWORKS=[{'title':'Gnosis Chain','basePath':'/xdai/mainnet','group':'mainnets'}]
# marketplace config
NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://airtable.com/shrqUAcjgGJ4jU88C
# api config # api config
NEXT_PUBLIC_API_ENDPOINT=https://blockscout.com NEXT_PUBLIC_API_HOST=blockscout.com
\ No newline at end of file
# nav and footer config # ui config
NEXT_PUBLIC_FOOTER_TELEGRAM_LINK=https://t.me/poa_network NEXT_PUBLIC_FOOTER_TELEGRAM_LINK=https://t.me/poa_network
NEXT_PUBLIC_FOOTER_STAKING_LINK=https://duneanalytics.com/maxaleks/xdai-staking NEXT_PUBLIC_FOOTER_STAKING_LINK=https://duneanalytics.com/maxaleks/xdai-staking
NEXT_PUBLIC_FEATURED_NETWORKS=[{'title':'Gnosis Chain','basePath':'/xdai/mainnet','group':'mainnets'},{'title':'Optimism on Gnosis Chain','basePath':'/xdai/optimism','group':'mainnets','icon':'https://www.fillmurray.com/60/60'},{'title':'Arbitrum on xDai','basePath':'/xdai/aox','group':'mainnets'},{'title':'Ethereum','basePath':'/eth/mainnet','group':'mainnets'},{'title':'Ethereum Classic','basePath':'/etx/mainnet','group':'mainnets'},{'title':'POA','basePath':'/poa/core','group':'mainnets'},{'title':'RSK','basePath':'/rsk/mainnet','group':'mainnets'},{'title':'Gnosis Chain Testnet','basePath':'/xdai/testnet','group':'testnets'},{'title':'POA Sokol','basePath':'/poa/sokol','group':'testnets'},{'title':'ARTIS Σ1','basePath':'/artis/sigma1','group':'other'},{'title':'LUKSO L14','basePath':'/lukso/l14','group':'other'},{'title':'Astar','basePath':'/astar','group':'other'}] 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','icon':'https://www.fillmurray.com/60/60','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/tx'}}]
# current network config # network config
NEXT_PUBLIC_NETWORK_NAME=POA NEXT_PUBLIC_NETWORK_NAME=POA
NEXT_PUBLIC_NETWORK_SHORT_NAME=POA NEXT_PUBLIC_NETWORK_SHORT_NAME=POA
NEXT_PUBLIC_NETWORK_ASSETS_PATHNAME=poa NEXT_PUBLIC_NETWORK_ASSETS_PATHNAME=poa
NEXT_PUBLIC_NETWORK_TYPE=poa NEXT_PUBLIC_NETWORK_TYPE=poa_core
NEXT_PUBLIC_NETWORK_SUBTYPE=core
NEXT_PUBLIC_NETWORK_ID=99 NEXT_PUBLIC_NETWORK_ID=99
NEXT_PUBLIC_NETWORK_CURRENCY=POA 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_NETWORK_TOKEN_ADDRESS=0x029a799563238d0e75e20be2f4bda0ea68d00172
NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true 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/'}]
# api config # api config
NEXT_PUBLIC_API_BASE_PATH=/poa/core NEXT_PUBLIC_API_BASE_PATH=/poa/core
const BASE_PATH = require('../../lib/link/basePath.js'); const PATHS = require('../../lib/link/paths');
const PATHS = require('../../lib/link/paths.js');
const oldUrls = [ const oldUrls = [
{ {
...@@ -41,17 +40,10 @@ const oldUrls = [ ...@@ -41,17 +40,10 @@ const oldUrls = [
]; ];
async function redirects() { async function redirects() {
const homePagePath = '/' + [ process.env.NEXT_PUBLIC_NETWORK_TYPE, process.env.NEXT_PUBLIC_NETWORK_SUBTYPE ].filter(Boolean).join('/');
return [ return [
{
source: '/',
destination: homePagePath,
permanent: false,
},
...oldUrls.map(item => { ...oldUrls.map(item => {
const source = BASE_PATH.replaceAll('[', ':').replaceAll(']', '') + item.oldPath; const source = item.oldPath;
const destination = item.newPath.replaceAll('[', ':').replaceAll(']', ''); const destination = item.newPath;
return { source, destination, permanent: false }; return { source, destination, permanent: false };
}), }),
]; ];
......
async function rewrites() { async function rewrites() {
// there can be networks without subtype
// routing in nextjs allows optional params only at the end of the path
// if there are paths with subtype and subsubtype, we will change the routing
// but so far we think we're ok with this hack
//
// UPDATE: as for now I hardcoded all networks without subtype
// because we cannot do proper dynamic rewrites in middleware using runtime ENVs
// see issue - https://github.com/vercel/next.js/discussions/35231
// it seems like it's solved but it's not actually
return [ return [
{ source: '/astar/:slug*', destination: '/astar/mainnet/:slug*' }, { source: '/node-api/:slug*', destination: '/api/:slug*' },
{ source: '/shiden/:slug*', destination: '/shiden/mainnet/:slug*' }, ].filter(Boolean);
];
} }
module.exports = rewrites; module.exports = rewrites;
...@@ -2,7 +2,7 @@ import type * as Sentry from '@sentry/react'; ...@@ -2,7 +2,7 @@ import type * as Sentry from '@sentry/react';
import { BrowserTracing } from '@sentry/tracing'; import { BrowserTracing } from '@sentry/tracing';
export const config: Sentry.BrowserOptions = { export const config: Sentry.BrowserOptions = {
environment: process.env.VERCEL_ENV || process.env.NODE_ENV, environment: process.env.VERCEL_ENV || process.env.NEXT_PUBLIC_APP_ENV || process.env.NODE_ENV,
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN, dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
release: process.env.NEXT_PUBLIC_GIT_COMMIT_SHA || process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA, release: process.env.NEXT_PUBLIC_GIT_COMMIT_SHA || process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA,
integrations: [ new BrowserTracing() ], integrations: [ new BrowserTracing() ],
......
This diff is collapsed.
declare module 'react-identicons' declare module 'react-identicons'
declare module 'data/marketplaceApps.json' {
import type { AppItemOverview } from './types/client/apps';
const value: Array<AppItemOverview>;
export default value;
}
...@@ -15,9 +15,9 @@ blockscout: ...@@ -15,9 +15,9 @@ blockscout:
ACCOUNT_AUTH0_CLIENT_SECRET: ACCOUNT_AUTH0_CLIENT_SECRET:
_default: ENC[AES256_GCM,data:Nmajob/xrOfx3sZgGHWw4/VnSgoyehubNdhJSqasxLFInJeZgxCW/QXpu5KG6j1fDdALLOT89Es+m0IO9VrG8A==,iv:RxYWdOo5G6t0FFwHQfhvUTiCOsxRzJJT0Y1nc0F5sDc=,tag:6v6+yHaHgwnw0cDSD1hXwg==,type:str] _default: ENC[AES256_GCM,data:Nmajob/xrOfx3sZgGHWw4/VnSgoyehubNdhJSqasxLFInJeZgxCW/QXpu5KG6j1fDdALLOT89Es+m0IO9VrG8A==,iv:RxYWdOo5G6t0FFwHQfhvUTiCOsxRzJJT0Y1nc0F5sDc=,tag:6v6+yHaHgwnw0cDSD1hXwg==,type:str]
ACCOUNT_AUTH0_CALLBACK_URL: ACCOUNT_AUTH0_CALLBACK_URL:
_default: ENC[AES256_GCM,data:MkvI0EZaWmsd8tDMU+qiLLZ2anCl00PW3u3Og9GXNtnZ+a7CUyY6rdFBOfE3fSOh4haoNuBxMCyxsAhHk/x0MNRNVqKjqT9I3VBAfzUzM+Ft4g==,iv:RuHXRpEAYKlm2UwN3s6AofiGiSmuRkVyO531rnUIklc=,tag:L3NnykZU4WFOOfPGDCdMUg==,type:str] _default: ENC[AES256_GCM,data:v6LJLRwibjc6QgF1t6QNzy/FfL/i2G0mD2X6oxf0o4Q6A8cwovE03Wd5nXWTSyWrN95rJm0IQY7bjU3fXpDw0p/u4vu8dMpfM2bZMMOalXP4pA==,iv:VtNoyCEsesWn0PyWRXzLgbMcUa+voKa1sjsUY26RKnw=,tag:t02c9NVe4aGGo5feOP1OWQ==,type:str]
ACCOUNT_AUTH0_LOGOUT_RETURN_URL: ACCOUNT_AUTH0_LOGOUT_RETURN_URL:
_default: ENC[AES256_GCM,data:9cWXw4j6LAg9qUzKTGevJE4U1leMV+i9DD26VDyFqlin4M6O1r/xzoWQtHfrHzxP1RRBuGPIjBqV/WCM1u9cVmhgrs2FQvzZkHk=,iv:Qrxdp4CfnSkCXMxhzMZvvpFSkglzdviq4UYGhffLDNk=,tag:RJhq0+B9Sa5D8k8cAhZW5A==,type:str] _default: ENC[AES256_GCM,data:JQylcEDhJUOqXqBCycJ8XLwz6wpa3Uz3p5MhCEVolLnKkYoDFogGuFt1YkKt7EEtbZjqvSjIa0xQhpezX5hvypU9KxRxoVosUQ4=,iv:dbXG4N4t5jQgx68l0r5iLh5FGGg2O/vqbSDpyxzEauI=,tag:upkfr/3h35rZ93oBNJ3Y/w==,type:str]
ACCOUNT_AUTH0_LOGOUT_URL: ACCOUNT_AUTH0_LOGOUT_URL:
_default: ENC[AES256_GCM,data:6vj0qvxktXptFDX4Tq3kGIyhaJN7Adcwfgb3qWEYH78pWtmFprwfKNdPa/4=,iv:Eb3NcZP9oSmh0ZfY8vCWwR5Ciwe7Lr/QJmcNWXiir9E=,tag:uXoFnMfwo/GD9IPuvs/9Wg==,type:str] _default: ENC[AES256_GCM,data:6vj0qvxktXptFDX4Tq3kGIyhaJN7Adcwfgb3qWEYH78pWtmFprwfKNdPa/4=,iv:Eb3NcZP9oSmh0ZfY8vCWwR5Ciwe7Lr/QJmcNWXiir9E=,tag:uXoFnMfwo/GD9IPuvs/9Wg==,type:str]
ACCOUNT_SENDGRID_API_KEY: ACCOUNT_SENDGRID_API_KEY:
...@@ -65,21 +65,21 @@ geth: ...@@ -65,21 +65,21 @@ geth:
frontend: frontend:
environment: environment:
NEXT_PUBLIC_NETWORK_TOKEN_ADDRESS: NEXT_PUBLIC_NETWORK_TOKEN_ADDRESS:
_default: ENC[AES256_GCM,data:IVCGBLqP7IdmbDe9UbIFvJSBD7+g52chKzakELt2XuHDp9JvC4E+7xxp,iv:bDHp2llHAqhgI5N8swQALSDc6X3S0JCsXbJnEEDDJOc=,tag:Z1WsJXkqtq79WydIgUiDiA==,type:str] _default: ENC[AES256_GCM,data:1SAbzZhCs/vzdftIX0WVLtImH27NJ6SwENee4uTu2p+ZyUso3nQCLUUm,iv:apyLxt2dQ5RN33ra1Q1sAy2cyplG9FSryksQru2ghlA=,tag:PVcCNt0bz1TfQewUebV5LA==,type:str]
NEXT_PUBLIC_SENTRY_DSN: NEXT_PUBLIC_SENTRY_DSN:
_default: ENC[AES256_GCM,data:n/H2AH2n9ovn265iFbbrqeOOWS3s7FXgDv5FXJ2Dz133GuhTaqIU5psWjTraZ/Vh+dVM8M9zBLFfUanCWOz7cerq/QUoSVZjKvIvcyp6F072DGU=,iv:Co/pSR+U0vfkmWR+LDpxcQKDJl0WNbaEZihpzrRJcbc=,tag:HUiCX8WGJXWCDB59Y6iIbw==,type:str] _default: ENC[AES256_GCM,data:n/H2AH2n9ovn265iFbbrqeOOWS3s7FXgDv5FXJ2Dz133GuhTaqIU5psWjTraZ/Vh+dVM8M9zBLFfUanCWOz7cerq/QUoSVZjKvIvcyp6F072DGU=,iv:Co/pSR+U0vfkmWR+LDpxcQKDJl0WNbaEZihpzrRJcbc=,tag:HUiCX8WGJXWCDB59Y6iIbw==,type:str]
SENTRY_CSP_REPORT_URI: SENTRY_CSP_REPORT_URI:
_default: ENC[AES256_GCM,data:Hf4azYsGh2lysotK7afaHI85IaLBJeQmPlGE/lwokmX2eaQHVZJ/i5RsaoKoOCSiNyfAowr7P+6IBEC16BUTQMpbhYveBd7c/xjIsoomoHbTdoAdZA/QxNVpS4a2qVthruFudyT1BoZUHIGQ,iv:Mid+PfbslOyivrFSXopdeW96YvOcLP+g2RGcw1o7B98=,tag:IuPUsGdbZQodCMyi1DR04Q==,type:str] _default: ENC[AES256_GCM,data:Hf4azYsGh2lysotK7afaHI85IaLBJeQmPlGE/lwokmX2eaQHVZJ/i5RsaoKoOCSiNyfAowr7P+6IBEC16BUTQMpbhYveBd7c/xjIsoomoHbTdoAdZA/QxNVpS4a2qVthruFudyT1BoZUHIGQ,iv:Mid+PfbslOyivrFSXopdeW96YvOcLP+g2RGcw1o7B98=,tag:IuPUsGdbZQodCMyi1DR04Q==,type:str]
NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM: null NEXT_PUBLIC_AUTH0_CLIENT_ID:
_default: ENC[AES256_GCM,data:6MLOmBMJoB+dYoW8L4JYslO3F5tSFzhrkI+6rGo1a51s9sXZa9c=,iv:126unfUWSieigaq4Zne8321tSYoNy3EHk/qwEodqgH8=,tag:6BlrNkck5yANO2rqECjkuQ==,type:str] _default: ENC[AES256_GCM,data:cRcbMzOW2AFyDz/lqv4T9SpDCabXdBKLFN9dbq/rFg4=,iv:G9afggfvZ+BpuE5u31KDVfIxhlP38IE28Yt9pMQsd6E=,tag:t1kkjXPolc/58fuQVULLfA==,type:str]
sops: sops:
kms: [] kms: []
gcp_kms: [] gcp_kms: []
azure_kv: [] azure_kv: []
hc_vault: [] hc_vault: []
age: [] age: []
lastmodified: "2022-10-14T12:09:11Z" lastmodified: "2022-11-02T07:59:42Z"
mac: ENC[AES256_GCM,data:CnD+9hsC9ZyVhZPo+DXZfPH8svMuk50llaAm3JxgOlzhbJ4yp969WxLhZSORvj520b9geBPLZRU7ujLGiHKhrNzAK438LI2QttKQDt3WSbPwkIGDh/zuA201+gpT73awUNfMKCoHVjq4iQ6ty4KP/NCw1ZMcS/c1WVuRYE9RTl8=,iv:rl8eKiXwrBDjns2hiwJ6f28XyuhjH2soHeR1MBBu2Ig=,tag:vu5jGEPMkvmcl7m8huWl7g==,type:str] mac: ENC[AES256_GCM,data:OrV/dUWOtL23UFQLeIsKsGluTmse42d/4sgFMDs3UXdACsZu8twMt29Y/WaPHyq8Tpn5iYzhBLU6SCUmHxEhBNVzKBd5uCUbav1faS/zW6fSd9bEP7rmbUjaJGHliBkG3T4VCSZn53jR/OMNbSynIxZ0kRpVHr+RTcalaH7dLQ8=,iv:J3gXjFgFyZoPqL+VEnjkuKzA9UIIyK3UsvPWacBZKsY=,tag:/zY1jVjIm5BsDNJlfsg5uw==,type:str]
pgp: pgp:
- created_at: "2022-09-14T13:42:28Z" - created_at: "2022-09-14T13:42:28Z"
enc: | enc: |
......
global: global:
env: e2e env: main
# enable Blockscout deploy # enable Blockscout deploy
blockscout: blockscout:
app: blockscout
enabled: true enabled: true
image: image:
_default: blockscout/blockscout:latest _default: blockscout/blockscout:latest
...@@ -10,17 +11,28 @@ blockscout: ...@@ -10,17 +11,28 @@ blockscout:
docker: docker:
port: 80 port: 80
targetPort: 4000 targetPort: 4000
# init container
init:
enabled: true
image:
_default: blockscout/blockscout:latest
service: service:
# ClusterIP, NodePort or LoadBalancer # ClusterIP, NodePort or LoadBalancer
type: ClusterIP type: ClusterIP
# enable ingress # enable ingress
ingress: ingress:
enabled: true enabled: true
annotations: {}
# - 'nginx.ingress.kubernetes.io/rewrite-target: /$2'
host: host:
_default: blockscout.test.blockscout.aws-k8s.blockscout.com _default: blockscout.test.blockscout.aws-k8s.blockscout.com
# enable https # enable https
#
tls: tls:
enabled: true enabled: true
path:
# - "/poa/sokol(/|$)(.*)"
- "/"
# probes # probes
livenessProbe: livenessProbe:
enabled: true enabled: true
...@@ -41,7 +53,7 @@ blockscout: ...@@ -41,7 +53,7 @@ blockscout:
_default: "2" _default: "2"
# enable service to connect to RDS # enable service to connect to RDS
rds: rds:
enable: false enabled: false
endpoint: endpoint:
_default: <endpoint>.<region>.rds.amazonaws.com _default: <endpoint>.<region>.rds.amazonaws.com
# node label # node label
...@@ -81,15 +93,27 @@ blockscout: ...@@ -81,15 +93,27 @@ blockscout:
RUST_VERIFICATION_SERVICE_URL: RUST_VERIFICATION_SERVICE_URL:
_default: http://sc-verifier-svc:8043 _default: http://sc-verifier-svc:8043
ACCOUNT_ENABLED: ACCOUNT_ENABLED:
_default: true _default: 'true'
DISABLE_REALTIME_INDEXER: DISABLE_REALTIME_INDEXER:
_default: 'false' _default: 'false'
SOCKET_ROOT: SOCKET_ROOT:
_default: "/" _default: "/"
NETWORK_PATH: NETWORK_PATH:
_default: "/" _default: "/"
API_PATH:
_default: "/"
ETHEREUM_JSONRPC_DISABLE_ARCHIVE_BALANCES: ETHEREUM_JSONRPC_DISABLE_ARCHIVE_BALANCES:
_default: 'true' _default: 'true'
API_BASE_PATH:
_default: "/"
APPS_MENU:
_default: 'true'
EXTERNAL_APPS:
_default: '[{"title": "Marketplace", "url": "/apps"}]'
JSON_RPC:
_default: https://sokol.poa.network
API_V2_ENABLED:
_default: 'true'
postgres: postgres:
enabled: true enabled: true
...@@ -153,7 +177,7 @@ geth: ...@@ -153,7 +177,7 @@ geth:
ingress: ingress:
enabled: true enabled: true
host: host:
_default: asdfg-node.test.blockscout.aws-k8s.blockscout.com _default: node.test.blockscout.aws-k8s.blockscout.com
# enable https # enable https
tls: tls:
enabled: false enabled: false
...@@ -180,7 +204,6 @@ scVerifier: ...@@ -180,7 +204,6 @@ scVerifier:
enabled: true enabled: true
host: host:
_default: verifier.test.blockscout.aws-k8s.blockscout.com _default: verifier.test.blockscout.aws-k8s.blockscout.com
testnet: asdfg-verifier.test.blockscout.aws-k8s.blockscout.com
# enable https # enable https
tls: tls:
enabled: true enabled: true
...@@ -247,6 +270,7 @@ scVerifier: ...@@ -247,6 +270,7 @@ scVerifier:
SMART_CONTRACT_VERIFIER__JAEGER__ENABLED: SMART_CONTRACT_VERIFIER__JAEGER__ENABLED:
_default: 'false' _default: 'false'
frontend: frontend:
app: blockscout
enabled: true enabled: true
image: image:
_default: ghcr.io/blockscout/frontend:main _default: ghcr.io/blockscout/frontend:main
...@@ -257,12 +281,21 @@ frontend: ...@@ -257,12 +281,21 @@ frontend:
targetPort: 3000 targetPort: 3000
ingress: ingress:
enabled: true enabled: true
# annotations:
# - 'nginx.ingress.kubernetes.io/use-regex: "true"'
host: host:
_default: frontend.test.blockscout.aws-k8s.blockscout.com _default: frontend.test.blockscout.aws-k8s.blockscout.com
testnet: asdfg-frontend.test.blockscout.aws-k8s.blockscout.com
# enable https # enable https
tls: tls:
enabled: true enabled: true
path:
# - "/(apps|auth/profile|account)"
- "/apps"
- "/_next"
- "/node-api"
- "/static"
- "/auth/profile"
- "/account"
resources: resources:
limits: limits:
memory: memory:
...@@ -279,20 +312,16 @@ frontend: ...@@ -279,20 +312,16 @@ frontend:
enabled: true enabled: true
app: blockscout app: blockscout
environment: environment:
NEXT_PUBLIC_APP_PROTOCOL:
_default: http
NEXT_PUBLIC_APP_HOST:
_default: localhost
NEXT_PUBLIC_APP_PORT:
_default: 80
NEXT_PUBLIC_BLOCKSCOUT_VERSION: NEXT_PUBLIC_BLOCKSCOUT_VERSION:
_default: v4.1.8-beta _default: v4.1.8-beta
NEXT_PUBLIC_FOOTER_GITHUB_LINK: NEXT_PUBLIC_FOOTER_GITHUB_LINK:
_default: https://github.com/blockscout/blockscout _default: https://github.com/blockscout/blockscout
NEXT_PUBLIC_FOOTER_TWITTER_LINK: NEXT_PUBLIC_FOOTER_TWITTER_LINK:
_default: https://www.twitter.com/blockscoutcom _default: https://www.twitter.com/blockscoutcom
NEXT_PUBLIC_APP_ENV:
_default: preview
NEXT_PUBLIC_APP_INSTANCE: NEXT_PUBLIC_APP_INSTANCE:
_default: local _default: unknown
NEXT_PUBLIC_FOOTER_TELEGRAM_LINK: NEXT_PUBLIC_FOOTER_TELEGRAM_LINK:
_default: https://t.me/poa_network _default: https://t.me/poa_network
NEXT_PUBLIC_FOOTER_STAKING_LINK: NEXT_PUBLIC_FOOTER_STAKING_LINK:
...@@ -304,18 +333,30 @@ frontend: ...@@ -304,18 +333,30 @@ frontend:
NEXT_PUBLIC_NETWORK_ASSETS_PATHNAME: NEXT_PUBLIC_NETWORK_ASSETS_PATHNAME:
_default: poa _default: poa
NEXT_PUBLIC_NETWORK_TYPE: NEXT_PUBLIC_NETWORK_TYPE:
_default: poa _default: poa_core
NEXT_PUBLIC_NETWORK_SUBTYPE:
_default: sokol
NEXT_PUBLIC_NETWORK_ID: NEXT_PUBLIC_NETWORK_ID:
_default: 77 _default: 77
NEXT_PUBLIC_NETWORK_CURRENCY: NEXT_PUBLIC_NETWORK_CURRENCY_NAME:
_default: POA Network Sokol
NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL:
_default: SPOA _default: SPOA
NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS:
_default: 18
NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE:
_default: validation
NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED: NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED:
_default: 'true' _default: 'true'
NEXT_PUBLIC_FEATURED_NETWORKS: NEXT_PUBLIC_FEATURED_NETWORKS:
_default: "[{'title':'Gnosis Chain','basePath':'/xdai/mainnet','group':'mainnets'},{'title':'Optimism on Gnosis Chain','basePath':'/xdai/optimism','group':'mainnets','icon':'https://www.fillmurray.com/60/60'},{'title':'Arbitrum on xDai','basePath':'/xdai/aox','group':'mainnets'},{'title':'Ethereum','basePath':'/eth/mainnet','group':'mainnets'},{'title':'Ethereum Classic','basePath':'/etx/mainnet','group':'mainnets'},{'title':'POA','basePath':'/poa/core','group':'mainnets'},{'title':'RSK','basePath':'/rsk/mainnet','group':'mainnets'},{'title':'Gnosis Chain Testnet','basePath':'/xdai/testnet','group':'testnets'},{'title':'POA Sokol','basePath':'/poa/sokol','group':'testnets'},{'title':'ARTIS Σ1','basePath':'/artis/sigma1','group':'other'},{'title':'LUKSO L14','basePath':'/lukso/l14','group':'other'},{'title':'Astar','basePath':'/astar','group':'other'}]" _default: "[{'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','icon':'https://www.fillmurray.com/60/60','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_API_ENDPOINT: NEXT_PUBLIC_API_HOST:
_default: https://blockscout.com _default: blockscout.com
NEXT_PUBLIC_API_BASE_PATH: NEXT_PUBLIC_API_BASE_PATH:
_default: / _default: /
NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM:
_default: https://airtable.com/shrqUAcjgGJ4jU88C
NEXT_PUBLIC_MARKETPLACE_APP_LIST:
_default: "[{'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/'}]"
NEXT_PUBLIC_LOGOUT_URL:
_default: https://blockscoutcom.us.auth0.com/v2/logout
NEXT_PUBLIC_LOGOUT_RETURN_URL:
_default: https://blockscout.com/auth/logout
...@@ -15,9 +15,9 @@ blockscout: ...@@ -15,9 +15,9 @@ blockscout:
ACCOUNT_AUTH0_CLIENT_SECRET: ACCOUNT_AUTH0_CLIENT_SECRET:
_default: ENC[AES256_GCM,data:Nmajob/xrOfx3sZgGHWw4/VnSgoyehubNdhJSqasxLFInJeZgxCW/QXpu5KG6j1fDdALLOT89Es+m0IO9VrG8A==,iv:RxYWdOo5G6t0FFwHQfhvUTiCOsxRzJJT0Y1nc0F5sDc=,tag:6v6+yHaHgwnw0cDSD1hXwg==,type:str] _default: ENC[AES256_GCM,data:Nmajob/xrOfx3sZgGHWw4/VnSgoyehubNdhJSqasxLFInJeZgxCW/QXpu5KG6j1fDdALLOT89Es+m0IO9VrG8A==,iv:RxYWdOo5G6t0FFwHQfhvUTiCOsxRzJJT0Y1nc0F5sDc=,tag:6v6+yHaHgwnw0cDSD1hXwg==,type:str]
ACCOUNT_AUTH0_CALLBACK_URL: ACCOUNT_AUTH0_CALLBACK_URL:
_default: ENC[AES256_GCM,data:MkvI0EZaWmsd8tDMU+qiLLZ2anCl00PW3u3Og9GXNtnZ+a7CUyY6rdFBOfE3fSOh4haoNuBxMCyxsAhHk/x0MNRNVqKjqT9I3VBAfzUzM+Ft4g==,iv:RuHXRpEAYKlm2UwN3s6AofiGiSmuRkVyO531rnUIklc=,tag:L3NnykZU4WFOOfPGDCdMUg==,type:str] _default: ENC[AES256_GCM,data:dQMvW68eW5EokGgekC1HsozsMAq+eqWwqfocgF6QR9+1VA1z5efNbMjTuWJqaSzXq/yWwfAogP8GJSzpS8T7jKATOeVxZ71I4LmlFO7jR4wMKw==,iv:UA0DcnH7z6y5X9C9Qk3WvRrgI3LT/UvH4Fl7gb765/0=,tag:cn78k5f+L8ed0WV3mDKykg==,type:str]
ACCOUNT_AUTH0_LOGOUT_RETURN_URL: ACCOUNT_AUTH0_LOGOUT_RETURN_URL:
_default: ENC[AES256_GCM,data:9cWXw4j6LAg9qUzKTGevJE4U1leMV+i9DD26VDyFqlin4M6O1r/xzoWQtHfrHzxP1RRBuGPIjBqV/WCM1u9cVmhgrs2FQvzZkHk=,iv:Qrxdp4CfnSkCXMxhzMZvvpFSkglzdviq4UYGhffLDNk=,tag:RJhq0+B9Sa5D8k8cAhZW5A==,type:str] _default: ENC[AES256_GCM,data:y4px71aTwkmIcqC5T6ui1wHlhRExyy3Id/Dcy3AaKuGhIHUE0URbghm8niB1Seq08U5NeC4LvSmYsbWsx390lHP2dTuWwTaQ33s=,iv:JR3T7ZXxv3eAUn/XzMEz4/U58vJhB/pPWIutGN9XrSk=,tag:MiKgiyfzTHFjbQYdkFIJ8Q==,type:str]
ACCOUNT_AUTH0_LOGOUT_URL: ACCOUNT_AUTH0_LOGOUT_URL:
_default: ENC[AES256_GCM,data:6vj0qvxktXptFDX4Tq3kGIyhaJN7Adcwfgb3qWEYH78pWtmFprwfKNdPa/4=,iv:Eb3NcZP9oSmh0ZfY8vCWwR5Ciwe7Lr/QJmcNWXiir9E=,tag:uXoFnMfwo/GD9IPuvs/9Wg==,type:str] _default: ENC[AES256_GCM,data:6vj0qvxktXptFDX4Tq3kGIyhaJN7Adcwfgb3qWEYH78pWtmFprwfKNdPa/4=,iv:Eb3NcZP9oSmh0ZfY8vCWwR5Ciwe7Lr/QJmcNWXiir9E=,tag:uXoFnMfwo/GD9IPuvs/9Wg==,type:str]
ACCOUNT_SENDGRID_API_KEY: ACCOUNT_SENDGRID_API_KEY:
...@@ -70,16 +70,16 @@ frontend: ...@@ -70,16 +70,16 @@ frontend:
_default: ENC[AES256_GCM,data:n/H2AH2n9ovn265iFbbrqeOOWS3s7FXgDv5FXJ2Dz133GuhTaqIU5psWjTraZ/Vh+dVM8M9zBLFfUanCWOz7cerq/QUoSVZjKvIvcyp6F072DGU=,iv:Co/pSR+U0vfkmWR+LDpxcQKDJl0WNbaEZihpzrRJcbc=,tag:HUiCX8WGJXWCDB59Y6iIbw==,type:str] _default: ENC[AES256_GCM,data:n/H2AH2n9ovn265iFbbrqeOOWS3s7FXgDv5FXJ2Dz133GuhTaqIU5psWjTraZ/Vh+dVM8M9zBLFfUanCWOz7cerq/QUoSVZjKvIvcyp6F072DGU=,iv:Co/pSR+U0vfkmWR+LDpxcQKDJl0WNbaEZihpzrRJcbc=,tag:HUiCX8WGJXWCDB59Y6iIbw==,type:str]
SENTRY_CSP_REPORT_URI: SENTRY_CSP_REPORT_URI:
_default: ENC[AES256_GCM,data:Hf4azYsGh2lysotK7afaHI85IaLBJeQmPlGE/lwokmX2eaQHVZJ/i5RsaoKoOCSiNyfAowr7P+6IBEC16BUTQMpbhYveBd7c/xjIsoomoHbTdoAdZA/QxNVpS4a2qVthruFudyT1BoZUHIGQ,iv:Mid+PfbslOyivrFSXopdeW96YvOcLP+g2RGcw1o7B98=,tag:IuPUsGdbZQodCMyi1DR04Q==,type:str] _default: ENC[AES256_GCM,data:Hf4azYsGh2lysotK7afaHI85IaLBJeQmPlGE/lwokmX2eaQHVZJ/i5RsaoKoOCSiNyfAowr7P+6IBEC16BUTQMpbhYveBd7c/xjIsoomoHbTdoAdZA/QxNVpS4a2qVthruFudyT1BoZUHIGQ,iv:Mid+PfbslOyivrFSXopdeW96YvOcLP+g2RGcw1o7B98=,tag:IuPUsGdbZQodCMyi1DR04Q==,type:str]
NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM: null NEXT_PUBLIC_AUTH0_CLIENT_ID:
_default: ENC[AES256_GCM,data:KVzLMvMQc6f37c+OWu7vpAdDYwKI048XVY7EVijS2zz6jRRFacg=,iv:LeWSYZeaPdBsOxcGcca8L1Rp3ilsR+R13icX8Q/VUBA=,tag:Bj6NTTqb3R8oxAprZ+7CVQ==,type:str] _default: ENC[AES256_GCM,data:4cTeqxQnGcpzPK4bMqxZpLgMeFSSDbajN/fmb1UunH8=,iv:bPTQfahGfWF1OfArvYQeSQItMa0Ymkt6eUfDZFBQSOY=,tag:8xHe5AUkbH/rl6cOfkVAKg==,type:str]
sops: sops:
kms: [] kms: []
gcp_kms: [] gcp_kms: []
azure_kv: [] azure_kv: []
hc_vault: [] hc_vault: []
age: [] age: []
lastmodified: "2022-10-14T12:08:39Z" lastmodified: "2022-11-01T14:58:11Z"
mac: ENC[AES256_GCM,data:esHv2aUvW4lMmoD5yRWu4OJEpOMkCa7TOyPS0HDkQL25g4TOdE+AfVZBE5wLeL1rePaId4bHnX0sF2Tov0d8xhCH3mv6+Vvmgi+75Oqu8+logQ4LyZSI0yIcvmdGVHhaO6u3u1qwYXHrityIVmiXQdBck5oq67uyT+jtSh1pXpc=,iv:YNWhRxSY0WLRm+wbVURpfxU2K67MAB5dpSILSMy9oCE=,tag:TsCcsGmMG4id5ITR5LrDjg==,type:str] mac: ENC[AES256_GCM,data:3AK4GRnUnAcQrdJ9JrdhSFqMYmYhE2RGiP+NPvO+mqBGDH26pRjN3lkNhHi/uObEQWiQZJLzLEOhSPl0/oDVYRTSGEeEiIlViEm/S5PuD57uFx6ogS9Iz88G/3hnc3HpTAIg2+NVwE7wF1/NK75WlivB1pUGk7OrazhZ+Fhyn5k=,iv:94YFEq/dmS07CqsKFZ2NAKMj3LqqyUB4+2XtHI3UiLg=,tag:4uti1G9eRoL2AwF0n7avdQ==,type:str]
pgp: pgp:
- created_at: "2022-09-14T13:42:28Z" - created_at: "2022-09-14T13:42:28Z"
enc: | enc: |
......
global: global:
env: e2e env: review
# enable Blockscout deploy # enable Blockscout deploy
blockscout: blockscout:
app: blockscout
enabled: true enabled: true
image: image:
_default: blockscout/blockscout:latest _default: blockscout/blockscout:latest
...@@ -10,17 +11,28 @@ blockscout: ...@@ -10,17 +11,28 @@ blockscout:
docker: docker:
port: 80 port: 80
targetPort: 4000 targetPort: 4000
# init container
init:
enabled: true
image:
_default: blockscout/blockscout:latest
service: service:
# ClusterIP, NodePort or LoadBalancer # ClusterIP, NodePort or LoadBalancer
type: ClusterIP type: ClusterIP
# enable ingress # enable ingress
ingress: ingress:
enabled: true enabled: true
annotations: {}
# - 'nginx.ingress.kubernetes.io/rewrite-target: /$2'
host: host:
_default: blockscout.test.blockscout.aws-k8s.blockscout.com _default: blockscout.test.blockscout.aws-k8s.blockscout.com
# enable https # enable https
#
tls: tls:
enabled: true enabled: true
path:
# - "/poa/sokol(/|$)(.*)"
- "/"
# probes # probes
livenessProbe: livenessProbe:
enabled: true enabled: true
...@@ -41,7 +53,7 @@ blockscout: ...@@ -41,7 +53,7 @@ blockscout:
_default: "2" _default: "2"
# enable service to connect to RDS # enable service to connect to RDS
rds: rds:
enable: false enabled: false
endpoint: endpoint:
_default: <endpoint>.<region>.rds.amazonaws.com _default: <endpoint>.<region>.rds.amazonaws.com
# node label # node label
...@@ -81,15 +93,27 @@ blockscout: ...@@ -81,15 +93,27 @@ blockscout:
RUST_VERIFICATION_SERVICE_URL: RUST_VERIFICATION_SERVICE_URL:
_default: http://sc-verifier-svc:8043 _default: http://sc-verifier-svc:8043
ACCOUNT_ENABLED: ACCOUNT_ENABLED:
_default: true _default: 'true'
DISABLE_REALTIME_INDEXER: DISABLE_REALTIME_INDEXER:
_default: 'false' _default: 'false'
SOCKET_ROOT: SOCKET_ROOT:
_default: "/" _default: "/"
NETWORK_PATH: NETWORK_PATH:
_default: "/" _default: "/"
API_PATH:
_default: "/"
ETHEREUM_JSONRPC_DISABLE_ARCHIVE_BALANCES: ETHEREUM_JSONRPC_DISABLE_ARCHIVE_BALANCES:
_default: 'true' _default: 'true'
API_BASE_PATH:
_default: "/"
APPS_MENU:
_default: 'true'
EXTERNAL_APPS:
_default: '[{"title": "Marketplace", "url": "/apps"}]'
JSON_RPC:
_default: https://sokol.poa.network
API_V2_ENABLED:
_default: 'true'
postgres: postgres:
enabled: true enabled: true
...@@ -153,7 +177,7 @@ geth: ...@@ -153,7 +177,7 @@ geth:
ingress: ingress:
enabled: true enabled: true
host: host:
_default: asdfg-node.test.blockscout.aws-k8s.blockscout.com _default: node.test.blockscout.aws-k8s.blockscout.com
# enable https # enable https
tls: tls:
enabled: false enabled: false
...@@ -180,7 +204,6 @@ scVerifier: ...@@ -180,7 +204,6 @@ scVerifier:
enabled: true enabled: true
host: host:
_default: verifier.test.blockscout.aws-k8s.blockscout.com _default: verifier.test.blockscout.aws-k8s.blockscout.com
testnet: asdfg-verifier.test.blockscout.aws-k8s.blockscout.com
# enable https # enable https
tls: tls:
enabled: true enabled: true
...@@ -247,6 +270,7 @@ scVerifier: ...@@ -247,6 +270,7 @@ scVerifier:
SMART_CONTRACT_VERIFIER__JAEGER__ENABLED: SMART_CONTRACT_VERIFIER__JAEGER__ENABLED:
_default: 'false' _default: 'false'
frontend: frontend:
app: blockscout
enabled: true enabled: true
image: image:
_default: ghcr.io/blockscout/frontend:main _default: ghcr.io/blockscout/frontend:main
...@@ -257,12 +281,21 @@ frontend: ...@@ -257,12 +281,21 @@ frontend:
targetPort: 3000 targetPort: 3000
ingress: ingress:
enabled: true enabled: true
# annotations:
# - 'nginx.ingress.kubernetes.io/use-regex: "true"'
host: host:
_default: frontend.test.blockscout.aws-k8s.blockscout.com _default: frontend.test.blockscout.aws-k8s.blockscout.com
testnet: asdfg-frontend.test.blockscout.aws-k8s.blockscout.com
# enable https # enable https
tls: tls:
enabled: true enabled: true
path:
# - "/(apps|auth/profile|account)"
- "/apps"
- "/_next"
- "/node-api"
- "/static"
- "/auth/profile"
- "/account"
resources: resources:
limits: limits:
memory: memory:
...@@ -279,16 +312,16 @@ frontend: ...@@ -279,16 +312,16 @@ frontend:
enabled: true enabled: true
app: blockscout app: blockscout
environment: environment:
NEXT_PUBLIC_APP_PORT:
_default: 80
NEXT_PUBLIC_BLOCKSCOUT_VERSION: NEXT_PUBLIC_BLOCKSCOUT_VERSION:
_default: v4.1.8-beta _default: v4.1.8-beta
NEXT_PUBLIC_FOOTER_GITHUB_LINK: NEXT_PUBLIC_FOOTER_GITHUB_LINK:
_default: https://github.com/blockscout/blockscout _default: https://github.com/blockscout/blockscout
NEXT_PUBLIC_FOOTER_TWITTER_LINK: NEXT_PUBLIC_FOOTER_TWITTER_LINK:
_default: https://www.twitter.com/blockscoutcom _default: https://www.twitter.com/blockscoutcom
NEXT_PUBLIC_APP_ENV:
_default: preview
NEXT_PUBLIC_APP_INSTANCE: NEXT_PUBLIC_APP_INSTANCE:
_default: review _default: unknown
NEXT_PUBLIC_FOOTER_TELEGRAM_LINK: NEXT_PUBLIC_FOOTER_TELEGRAM_LINK:
_default: https://t.me/poa_network _default: https://t.me/poa_network
NEXT_PUBLIC_FOOTER_STAKING_LINK: NEXT_PUBLIC_FOOTER_STAKING_LINK:
...@@ -298,20 +331,32 @@ frontend: ...@@ -298,20 +331,32 @@ frontend:
NEXT_PUBLIC_NETWORK_SHORT_NAME: NEXT_PUBLIC_NETWORK_SHORT_NAME:
_default: POA _default: POA
NEXT_PUBLIC_NETWORK_ASSETS_PATHNAME: NEXT_PUBLIC_NETWORK_ASSETS_PATHNAME:
_default: sokol
NEXT_PUBLIC_NETWORK_TYPE:
_default: poa _default: poa
NEXT_PUBLIC_NETWORK_SUBTYPE: NEXT_PUBLIC_NETWORK_TYPE:
_default: sokol _default: poa_core
NEXT_PUBLIC_NETWORK_ID: NEXT_PUBLIC_NETWORK_ID:
_default: 77 _default: 77
NEXT_PUBLIC_NETWORK_CURRENCY: NEXT_PUBLIC_NETWORK_CURRENCY_NAME:
_default: POA Network Sokol
NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL:
_default: SPOA _default: SPOA
NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS:
_default: 18
NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE:
_default: validation
NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED: NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED:
_default: 'true' _default: 'true'
NEXT_PUBLIC_FEATURED_NETWORKS: NEXT_PUBLIC_FEATURED_NETWORKS:
_default: "[{'title':'Gnosis Chain','basePath':'/xdai/mainnet','group':'mainnets'},{'title':'Optimism on Gnosis Chain','basePath':'/xdai/optimism','group':'mainnets','icon':'https://www.fillmurray.com/60/60'},{'title':'Arbitrum on xDai','basePath':'/xdai/aox','group':'mainnets'},{'title':'Ethereum','basePath':'/eth/mainnet','group':'mainnets'},{'title':'Ethereum Classic','basePath':'/etx/mainnet','group':'mainnets'},{'title':'POA','basePath':'/poa/core','group':'mainnets'},{'title':'RSK','basePath':'/rsk/mainnet','group':'mainnets'},{'title':'Gnosis Chain Testnet','basePath':'/xdai/testnet','group':'testnets'},{'title':'POA Sokol','basePath':'/poa/sokol','group':'testnets'},{'title':'ARTIS Σ1','basePath':'/artis/sigma1','group':'other'},{'title':'LUKSO L14','basePath':'/lukso/l14','group':'other'},{'title':'Astar','basePath':'/astar','group':'other'}]" _default: "[{'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','icon':'https://www.fillmurray.com/60/60','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_API_ENDPOINT: NEXT_PUBLIC_API_HOST:
_default: https://blockscout.com _default: blockscout.com
NEXT_PUBLIC_API_BASE_PATH: NEXT_PUBLIC_API_BASE_PATH:
_default: / _default: /
NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM:
_default: https://airtable.com/shrqUAcjgGJ4jU88C
NEXT_PUBLIC_MARKETPLACE_APP_LIST:
_default: "[{'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/'}]"
NEXT_PUBLIC_LOGOUT_URL:
_default: https://blockscoutcom.us.auth0.com/v2/logout
NEXT_PUBLIC_LOGOUT_RETURN_URL:
_default: https://blockscout.com/auth/logout
import {
ChakraProvider,
cookieStorageManagerSSR,
localStorageManager,
} from '@chakra-ui/react';
import type { ChakraProviderProps } from '@chakra-ui/react';
import React from 'react';
interface Props extends ChakraProviderProps {
cookies?: string;
}
export function Chakra({ cookies, theme, children }: Props) {
const colorModeManager =
typeof cookies === 'string' ?
cookieStorageManagerSSR(cookies) :
localStorageManager;
return (
<ChakraProvider colorModeManager={ colorModeManager } theme={ theme }>
{ children }
</ChakraProvider>
);
}
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 * as Sentry from '@sentry/nextjs';
import type { NextApiRequest } from 'next'; import type { NextApiRequest } from 'next';
import appConfig from 'configs/app/config'; import appConfig from 'configs/app/config';
......
import React, { createContext, useContext } from 'react';
import type { Props as PageProps } from 'lib/next/getServerSideProps';
type Props = {
children: React.ReactNode;
pageProps: PageProps;
}
const AppContext = createContext<PageProps>({ cookies: '' });
export function AppWrapper({ children, pageProps }: Props) {
const appProps = { cookies: pageProps.cookies };
return (
<AppContext.Provider value={ appProps }>
{ children }
</AppContext.Provider>
);
}
export function useAppContext() {
return useContext(AppContext);
}
...@@ -4,3 +4,9 @@ export const WEI = new BigNumber(10 ** 18); ...@@ -4,3 +4,9 @@ export const WEI = new BigNumber(10 ** 18);
export const GWEI = new BigNumber(10 ** 9); export const GWEI = new BigNumber(10 ** 9);
export const WEI_IN_GWEI = WEI.dividedBy(GWEI); export const WEI_IN_GWEI = WEI.dividedBy(GWEI);
export const ZERO = new BigNumber(0); export const ZERO = new BigNumber(0);
export const SECOND = 1_000;
export const MINUTE = 60 * SECOND;
export const HOUR = 60 * MINUTE;
export const DAY = 24 * HOUR;
export const WEEK = 7 * DAY;
...@@ -6,11 +6,12 @@ import isBrowser from './isBrowser'; ...@@ -6,11 +6,12 @@ import isBrowser from './isBrowser';
export enum NAMES { export enum NAMES {
NAV_BAR_COLLAPSED='nav_bar_collapsed', NAV_BAR_COLLAPSED='nav_bar_collapsed',
API_TOKEN='_explorer_key', API_TOKEN='_explorer_key',
TXS_SORT='txs_sort',
} }
export function get(name?: string | undefined | null) { export function get(name?: NAMES | undefined | null, serverCookie?: string) {
if (!isBrowser()) { if (!isBrowser()) {
return undefined; return serverCookie ? getFromCookieString(serverCookie, name) : undefined;
} }
return Cookies.get(name); return Cookies.get(name);
} }
...@@ -20,3 +21,7 @@ export function set(name: string, value: string, attributes: Types.CookieAttribu ...@@ -20,3 +21,7 @@ export function set(name: string, value: string, attributes: Types.CookieAttribu
return Cookies.set(name, value, attributes); return Cookies.set(name, value, attributes);
} }
export function getFromCookieString(cookieString: string, name?: NAMES | undefined | null) {
return cookieString.split(`${ name }=`)[1]?.split(';')[0];
}
import appConfig from 'configs/app/config'; import appConfig from 'configs/app/config';
import featuredNetworks from 'lib/networks/featuredNetworks'; import featuredNetworks from 'lib/networks/featuredNetworks';
import getMarketplaceApps from '../getMarketplaceApps';
const KEY_WORDS = { const KEY_WORDS = {
BLOB: 'blob:', BLOB: 'blob:',
DATA: 'data:', DATA: 'data:',
...@@ -29,11 +27,22 @@ function getNetworksExternalAssets() { ...@@ -29,11 +27,22 @@ function getNetworksExternalAssets() {
} }
function getMarketplaceAppsOrigins() { function getMarketplaceAppsOrigins() {
return getMarketplaceApps().map(({ url }) => url); return appConfig.marketplaceAppList.map(({ url }) => url);
} }
function getMarketplaceAppsLogosOrigins() { function getMarketplaceAppsLogosOrigins() {
return getMarketplaceApps().map(({ logo }) => logo); return appConfig.marketplaceAppList.map(({ logo }) => new URL(logo));
}
// we cannot use lodash/uniq in middleware code since it calls new Set() and it'is causing an error in Nextjs
// "Dynamic Code Evaluation (e. g. 'eval', 'new Function', 'WebAssembly.compile') not allowed in Edge Runtime"
function unique(array: Array<string | undefined>) {
const set: Record<string, boolean> = {};
for (const item of array) {
item && (set[item] = true);
}
return Object.keys(set);
} }
function makePolicyMap() { function makePolicyMap() {
...@@ -53,6 +62,8 @@ function makePolicyMap() { ...@@ -53,6 +62,8 @@ function makePolicyMap() {
// client error monitoring // client error monitoring
'sentry.io', '*.sentry.io', 'sentry.io', '*.sentry.io',
appConfig.api.socket,
], ],
'script-src': [ 'script-src': [
...@@ -92,11 +103,17 @@ function makePolicyMap() { ...@@ -92,11 +103,17 @@ function makePolicyMap() {
// github avatars // github avatars
'avatars.githubusercontent.com', 'avatars.githubusercontent.com',
// other github assets (e.g trustwallet token icons)
'raw.githubusercontent.com',
// auth0 assets
's.gravatar.com',
// network assets // network assets
...networkExternalAssets.map((url) => url.host), ...networkExternalAssets.map((url) => url.host),
// marketplace apps logos // marketplace apps logos
...getMarketplaceAppsLogosOrigins(), ...getMarketplaceAppsLogosOrigins().map((url) => url.host),
], ],
'font-src': [ 'font-src': [
...@@ -134,7 +151,8 @@ function getCspPolicy() { ...@@ -134,7 +151,8 @@ function getCspPolicy() {
return; return;
} }
return [ key, value.join(' ') ].join(' '); const uniqueValues = unique(value);
return [ key, uniqueValues.join(' ') ].join(' ');
}) })
.filter(Boolean) .filter(Boolean)
.join(';'); .join(';');
......
import data from 'data/marketplaceApps.json';
export default function getMarketplaceApps() {
return data;
}
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import { useRouter } from 'next/router';
import type { UserInfo } from 'types/api/account'; import type { UserInfo } from 'types/api/account';
import { QueryKeys } from 'types/client/queries';
import appConfig from 'configs/app/config';
import * as cookies from 'lib/cookies';
import useFetch from 'lib/hooks/useFetch'; import useFetch from 'lib/hooks/useFetch';
interface Error { interface Error {
...@@ -14,14 +16,12 @@ interface Error { ...@@ -14,14 +16,12 @@ interface Error {
export default function useFetchProfileInfo() { export default function useFetchProfileInfo() {
const fetch = useFetch(); const fetch = useFetch();
const router = useRouter();
const url = new URL(`/${ router.query.network_type }/${ router.query.network_sub_type }/api/account/v1/user/info`, 'https://blockscout.com'); return useQuery<unknown, Error, UserInfo>([ QueryKeys.profile ], async() => {
const url = new URL(`${ appConfig.api.basePath }/api/account/v1/user/info`, appConfig.api.endpoint);
return useQuery<unknown, Error, UserInfo>([ 'profile' ], async() => { return fetch(url.toString(), { credentials: appConfig.isDev ? 'include' : 'same-origin' });
return fetch(url.toString(), { credentials: 'include' });
}, { }, {
refetchOnMount: false, refetchOnMount: false,
enabled: Boolean(router.query.network_type && router.query.network_sub_type), enabled: Boolean(cookies.get(cookies.NAMES.API_TOKEN)),
}); });
} }
import { useBreakpointValue } from '@chakra-ui/react'; import { useBreakpointValue } from '@chakra-ui/react';
export default function useIsMobile() { export default function useIsMobile(ssr = true) {
return useBreakpointValue({ base: true, lg: false }); return useBreakpointValue({ base: true, lg: false }, { ssr });
} }
import React, { useMemo } from 'react'; import React from 'react';
import appConfig from 'configs/app/config'; import appConfig from 'configs/app/config';
import marketplaceApps from 'data/marketplaceApps.json';
import abiIcon from 'icons/ABI.svg'; import abiIcon from 'icons/ABI.svg';
import apiKeysIcon from 'icons/API.svg'; import apiKeysIcon from 'icons/API.svg';
import appsIcon from 'icons/apps.svg'; import appsIcon from 'icons/apps.svg';
...@@ -18,20 +17,17 @@ import useCurrentRoute from 'lib/link/useCurrentRoute'; ...@@ -18,20 +17,17 @@ import useCurrentRoute from 'lib/link/useCurrentRoute';
import notEmpty from 'lib/notEmpty'; import notEmpty from 'lib/notEmpty';
export default function useNavItems() { export default function useNavItems() {
const isMarketplaceFilled = useMemo(() => const isMarketplaceFilled = appConfig.marketplaceAppList.length > 0;
marketplaceApps.filter(item => item.chainIds.includes(appConfig.network.id)),
[ ])
.length > 0;
const currentRoute = useCurrentRoute()(); const currentRoute = useCurrentRoute()();
return React.useMemo(() => { return React.useMemo(() => {
const mainNavItems = [ const mainNavItems = [
{ text: 'Blocks', url: link('blocks'), icon: blocksIcon, isActive: currentRoute.startsWith('block') }, { text: 'Blocks', url: link('blocks'), icon: blocksIcon, isActive: currentRoute.startsWith('block'), isNewUi: false },
{ text: 'Transactions', url: link('txs'), icon: transactionsIcon, isActive: currentRoute.startsWith('tx') }, { text: 'Transactions', url: link('txs'), icon: transactionsIcon, isActive: currentRoute.startsWith('tx'), isNewUi: false },
{ text: 'Tokens', url: link('tokens'), icon: tokensIcon, isActive: currentRoute === 'tokens' }, { text: 'Tokens', url: link('tokens'), icon: tokensIcon, isActive: currentRoute === 'tokens', isNewUi: false },
isMarketplaceFilled ? isMarketplaceFilled ?
{ text: 'Apps', url: link('apps'), icon: appsIcon, isActive: currentRoute === 'apps' } : null, { text: 'Apps', url: link('apps'), icon: appsIcon, isActive: currentRoute === 'apps', isNewUi: true } : null,
// there should be custom site sections like Stats, Faucet, More, etc but never an 'other' // there should be custom site sections like Stats, Faucet, More, etc but never an 'other'
// examples https://explorer-edgenet.polygon.technology/ and https://explorer.celo.org/ // examples https://explorer-edgenet.polygon.technology/ and https://explorer.celo.org/
// at this stage custom menu items is under development, we will implement it later // at this stage custom menu items is under development, we will implement it later
...@@ -39,14 +35,14 @@ export default function useNavItems() { ...@@ -39,14 +35,14 @@ export default function useNavItems() {
].filter(notEmpty); ].filter(notEmpty);
const accountNavItems = [ const accountNavItems = [
{ text: 'Watchlist', url: link('watchlist'), icon: watchlistIcon, isActive: currentRoute === 'watchlist' }, { text: 'Watchlist', url: link('watchlist'), icon: watchlistIcon, isActive: currentRoute === 'watchlist', isNewUi: true },
{ text: 'Private tags', url: link('private_tags'), icon: privateTagIcon, isActive: currentRoute.startsWith('private_tags') }, { text: 'Private tags', url: link('private_tags'), icon: privateTagIcon, isActive: currentRoute.startsWith('private_tags'), isNewUi: true },
{ text: 'Public tags', url: link('public_tags'), icon: publicTagIcon, isActive: currentRoute === 'public_tags' }, { text: 'Public tags', url: link('public_tags'), icon: publicTagIcon, isActive: currentRoute === 'public_tags', isNewUi: true },
{ text: 'API keys', url: link('api_keys'), icon: apiKeysIcon, isActive: currentRoute === 'api_keys' }, { text: 'API keys', url: link('api_keys'), icon: apiKeysIcon, isActive: currentRoute === 'api_keys', isNewUi: true },
{ text: 'Custom ABI', url: link('custom_abi'), icon: abiIcon, isActive: currentRoute === 'custom_abi' }, { text: 'Custom ABI', url: link('custom_abi'), icon: abiIcon, isActive: currentRoute === 'custom_abi', isNewUi: true },
]; ];
const profileItem = { text: 'My profile', url: link('profile'), icon: profileIcon, isActive: currentRoute === 'profile' }; const profileItem = { text: 'My profile', url: link('profile'), icon: profileIcon, isActive: currentRoute === 'profile', isNewUi: true };
return { mainNavItems, accountNavItems, profileItem }; return { mainNavItems, accountNavItems, profileItem };
}, [ isMarketplaceFilled, currentRoute ]); }, [ isMarketplaceFilled, currentRoute ]);
......
import clamp from 'lodash/clamp';
import throttle from 'lodash/throttle';
import React from 'react';
import isBrowser from 'lib/isBrowser';
const SCROLL_DIFF_THRESHOLD = 20;
type Directions = 'up' | 'down';
export default function useScrollDirection() {
const prevScrollPosition = React.useRef(isBrowser() ? window.pageYOffset : 0);
const [ scrollDirection, setDirection ] = React.useState<Directions>();
const handleScroll = React.useCallback(() => {
const currentScrollPosition = clamp(window.pageYOffset, 0, window.document.body.scrollHeight - window.innerHeight);
const scrollDiff = currentScrollPosition - prevScrollPosition.current;
if (window.pageYOffset === 0) {
setDirection(undefined);
} else if (Math.abs(scrollDiff) > SCROLL_DIFF_THRESHOLD) {
setDirection(scrollDiff < 0 ? 'up' : 'down');
}
prevScrollPosition.current = currentScrollPosition;
}, [ ]);
React.useEffect(() => {
const throttledHandleScroll = throttle(handleScroll, 300);
window.addEventListener('scroll', throttledHandleScroll);
return () => {
window.removeEventListener('scroll', throttledHandleScroll);
};
// replicate componentDidMount
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return scrollDirection;
}
import React from 'react';
import { DAY, HOUR, MINUTE, SECOND } from 'lib/consts';
import dayjs from 'lib/date/dayjs';
function getUnits(diff: number) {
if (diff < MINUTE) {
return [ SECOND, MINUTE ];
}
if (diff < HOUR) {
return [ MINUTE, HOUR ];
}
if (diff < DAY) {
return [ HOUR, DAY ];
}
return [ DAY, 2 * DAY ];
}
function getUpdateParams(ts: string) {
const timeDiff = Date.now() - new Date(ts).getTime();
const [ unit, higherUnit ] = getUnits(timeDiff);
if (unit === DAY) {
return { interval: DAY };
}
const leftover = unit - timeDiff % unit;
return {
startTimeout: unit === SECOND ?
0 :
// here we assume that in current dayjs locale time difference is rounded by Math.round function
// so we have to update displayed value whenever time comes over the middle of the unit interval
// since it will be rounded to the upper bound
(leftover < unit / 2 ? leftover + unit / 2 : leftover - unit / 2) + SECOND,
endTimeout: higherUnit - timeDiff + SECOND,
interval: unit,
};
}
export default function useTimeAgoIncrement(ts: string, isEnabled?: boolean) {
const [ value, setValue ] = React.useState(dayjs(ts).fromNow());
React.useEffect(() => {
const timeouts: Array<number> = [];
const intervals: Array<number> = [];
const startIncrement = () => {
const { startTimeout, interval, endTimeout } = getUpdateParams(ts);
if (!startTimeout && !endTimeout) {
return;
}
let intervalId: number;
const startTimeoutId = window.setTimeout(() => {
setValue(dayjs(ts).fromNow());
intervalId = window.setInterval(() => {
setValue(dayjs(ts).fromNow());
}, interval);
intervals.push(intervalId);
}, startTimeout);
const endTimeoutId = window.setTimeout(() => {
window.clearInterval(intervalId);
startIncrement();
}, endTimeout);
timeouts.push(startTimeoutId);
timeouts.push(endTimeoutId);
};
isEnabled && startIncrement();
return () => {
timeouts.forEach(window.clearTimeout);
intervals.forEach(window.clearInterval);
};
}, [ isEnabled, ts ]);
return value;
}
const BASE_PATH = '/[network_type]/[network_sub_type]';
module.exports = BASE_PATH;
...@@ -15,18 +15,8 @@ export default function link( ...@@ -15,18 +15,8 @@ export default function link(
return ''; return '';
} }
const refinedUrlParams: typeof urlParams = {
network_type: appConfig.network.type,
network_sub_type: appConfig.network.subtype,
...urlParams,
};
const path = route.pattern.replace(PATH_PARAM_REGEXP, (_, paramName: string) => { const path = route.pattern.replace(PATH_PARAM_REGEXP, (_, paramName: string) => {
if (paramName === 'network_sub_type' && !refinedUrlParams.network_sub_type) { let paramValue = urlParams?.[paramName];
return '';
}
let paramValue = refinedUrlParams?.[paramName];
if (Array.isArray(paramValue)) { if (Array.isArray(paramValue)) {
// FIXME we don't have yet params as array, but typescript says that we could // FIXME we don't have yet params as array, but typescript says that we could
// dunno know how to manage it, fix me if you find an issue // dunno know how to manage it, fix me if you find an issue
...@@ -42,5 +32,5 @@ export default function link( ...@@ -42,5 +32,5 @@ export default function link(
url.searchParams.append(key, value); url.searchParams.append(key, value);
}); });
return url.toString(); return url.pathname;
} }
const BASE_PATH = require('./basePath');
const paths = { const paths = {
network_index: `${ BASE_PATH }`, network_index: `/`,
watchlist: `${ BASE_PATH }/account/watchlist`, watchlist: `/account/watchlist`,
private_tags: `${ BASE_PATH }/account/tag_address`, private_tags: `/account/tag_address`,
public_tags: `${ BASE_PATH }/account/public_tags_request`, public_tags: `/account/public_tags_request`,
api_keys: `${ BASE_PATH }/account/api_key`, api_keys: `/account/api_key`,
custom_abi: `${ BASE_PATH }/account/custom_abi`, custom_abi: `/account/custom_abi`,
profile: `${ BASE_PATH }/auth/profile`, profile: `/auth/profile`,
txs: `${ BASE_PATH }/txs`, txs: `/txs`,
tx: `${ BASE_PATH }/tx/[id]`, tx: `/tx/[id]`,
blocks: `${ BASE_PATH }/blocks`, blocks: `/blocks`,
block: `${ BASE_PATH }/block/[id]`, block: `/block/[id]`,
tokens: `${ BASE_PATH }/tokens`, tokens: `/tokens`,
token_index: `${ BASE_PATH }/token/[hash]`, token_index: `/token/[hash]`,
token_instance_item: `${ BASE_PATH }/token/[hash]/instance/[id]`, token_instance_item: `/token/[hash]/instance/[id]`,
address_index: `${ BASE_PATH }/address/[id]`, address_index: `/address/[id]`,
address_contract_verification: `${ BASE_PATH }/address/[id]/contract_verifications/new`, address_contract_verification: `/address/[id]/contract_verifications/new`,
apps: `${ BASE_PATH }/apps`, apps: `/apps`,
app_index: `${ BASE_PATH }/apps/[id]`, app_index: `/apps/[id]`,
search_results: `${ BASE_PATH }/search-results`, search_results: `/search-results`,
other: `${ BASE_PATH }/search-results`, other: `/search-results`,
// no slash required, it is correct auth: `/auth/auth0`,
auth: `${ BASE_PATH }auth/auth0`,
}; };
module.exports = paths; module.exports = paths;
import type { FeaturedNetwork } from 'types/networks'; import type { FeaturedNetwork, PreDefinedNetwork } from 'types/networks';
import appConfig from 'configs/app/config'; import appConfig from 'configs/app/config';
import arbitrumIcon from 'icons/networks/icons/arbitrum.svg'; import arbitrumIcon from 'icons/networks/icons/arbitrum.svg';
...@@ -12,97 +12,99 @@ import poaIcon from 'icons/networks/icons/poa.svg'; ...@@ -12,97 +12,99 @@ import poaIcon from 'icons/networks/icons/poa.svg';
import rskIcon from 'icons/networks/icons/rsk.svg'; import rskIcon from 'icons/networks/icons/rsk.svg';
// predefined network icons // predefined network icons
const ICONS: Record<string, React.FunctionComponent<React.SVGAttributes<SVGElement>>> = { const ICONS: Partial<Record<PreDefinedNetwork, React.FunctionComponent<React.SVGAttributes<SVGElement>>>> = {
'/xdai/mainnet': gnosisIcon, xdai_mainnet: gnosisIcon,
'/xdai/optimism': optimismIcon, xdai_optimism: optimismIcon,
'/xdai/aox': arbitrumIcon, xdai_aox: arbitrumIcon,
'/eth/mainnet': ethereumIcon, eth_mainnet: ethereumIcon,
'/etc/mainnet': ethereumClassicIcon, etc_mainnet: ethereumClassicIcon,
'/poa/core': poaIcon, poa_core: poaIcon,
'/rsk/mainnet': rskIcon, rsk_mainnet: rskIcon,
'/xdai/testnet': arbitrumIcon, xdai_testnet: arbitrumIcon,
'/poa/sokol': poaSokolIcon, poa_sokol: poaSokolIcon,
'/artis/sigma1': artisIcon, artis_sigma1: artisIcon,
}; };
// for easy .env.example update // for easy .env.example update
// const FEATURED_NETWORKS = JSON.stringify([ // const FEATURED_NETWORKS = JSON.stringify([
// { // {
// title: 'Gnosis Chain', // title: 'Gnosis Chain',
// basePath: '/xdai/mainnet', // url: 'https://blockscout.com/xdai/mainnet',
// group: 'mainnets', // group: 'mainnets',
// type: 'xdai_mainnet',
// }, // },
// { // {
// title: 'Optimism on Gnosis Chain', // title: 'Optimism on Gnosis Chain',
// basePath: '/xdai/optimism', // url: 'https://blockscout.com/xdai/optimism',
// group: 'mainnets', // group: 'mainnets',
// icon: 'https://www.fillmurray.com/60/60', // icon: 'https://www.fillmurray.com/60/60',
// type: 'xdai_optimism',
// }, // },
// { // {
// title: 'Arbitrum on xDai', // title: 'Arbitrum on xDai',
// basePath: '/xdai/aox', // url: 'https://blockscout.com/xdai/aox',
// group: 'mainnets', // group: 'mainnets',
// }, // },
// { // {
// title: 'Ethereum', // title: 'Ethereum',
// basePath: '/eth/mainnet', // url: 'https://blockscout.com/eth/mainnet',
// group: 'mainnets', // group: 'mainnets',
// type: 'eth_mainnet',
// }, // },
// { // {
// title: 'Ethereum Classic', // title: 'Ethereum Classic',
// basePath: '/etx/mainnet', // url: 'https://blockscout.com/etx/mainnet',
// group: 'mainnets', // group: 'mainnets',
// type: 'etc_mainnet',
// }, // },
// { // {
// title: 'POA', // title: 'POA',
// basePath: '/poa/core', // url: 'https://blockscout.com/poa/core',
// group: 'mainnets', // group: 'mainnets',
// type: 'poa_core',
// }, // },
// { // {
// title: 'RSK', // title: 'RSK',
// basePath: '/rsk/mainnet', // url: 'https://blockscout.com/rsk/mainnet',
// group: 'mainnets', // group: 'mainnets',
// type: 'rsk_mainnet',
// }, // },
// { // {
// title: 'Gnosis Chain Testnet', // title: 'Gnosis Chain Testnet',
// basePath: '/xdai/testnet', // url: 'https://blockscout.com/xdai/testnet',
// group: 'testnets', // group: 'testnets',
// type: 'xdai_testnet',
// }, // },
// { // {
// title: 'POA Sokol', // title: 'POA Sokol',
// basePath: '/poa/sokol', // url: 'https://blockscout.com/poa/sokol',
// group: 'testnets', // group: 'testnets',
// type: 'poa_sokol',
// }, // },
// { // {
// title: 'ARTIS Σ1', // title: 'ARTIS Σ1',
// basePath: '/artis/sigma1', // url: 'https://blockscout.com/artis/sigma1',
// group: 'other', // group: 'other',
// type: 'artis_sigma1',
// }, // },
// { // {
// title: 'LUKSO L14', // title: 'LUKSO L14',
// basePath: '/lukso/l14', // url: 'https://blockscout.com/lukso/l14',
// group: 'other', // group: 'other',
// type: 'lukso_l14',
// }, // },
// { // {
// title: 'Astar', // title: 'Astar',
// basePath: '/astar', // url: 'https://blockscout.com/astar',
// group: 'other', // group: 'other',
// type: 'astar',
// }, // },
// ]).replaceAll('"', '\''); // ]).replaceAll('"', '\'');
function parseNetworkConfig() {
try {
return JSON.parse(appConfig.featuredNetworks || '[]');
} catch (error) {
return [];
}
}
const featuredNetworks: Array<FeaturedNetwork> = (() => { const featuredNetworks: Array<FeaturedNetwork> = (() => {
const networksFromConfig: Array<FeaturedNetwork> = parseNetworkConfig(); return appConfig.featuredNetworks.map((network) => ({
return networksFromConfig.map((network) => ({
...network, ...network,
icon: network.icon || ICONS[network.basePath], icon: network.icon || (network.type ? ICONS[network.type] : undefined),
})); }));
})(); })();
......
import appConfig from 'configs/app/config';
export default function getNetworkValidatorTitle() {
return appConfig.network.verificationType === 'validation' ? 'validator' : 'miner';
}
import _compose from 'lodash/fp/compose';
import _mapValues from 'lodash/mapValues';
import type { NetworkExplorer } from 'types/networks';
import appConfig from 'configs/app/config';
// for easy .env update
// const NETWORK_EXPLORERS = JSON.stringify([
// {
// title: 'Anyblock',
// baseUrl: 'https://explorer.anyblock.tools',
// paths: {
// tx: '/ethereum/poa/core/tx',
// },
// },
// ]).replaceAll('"', '\'');
const stripTrailingSlash = (str: string) => str.at(-1) === '/' ? str.slice(0, -1) : str;
const addLeadingSlash = (str: string) => str.at(0) === '/' ? str : '/' + str;
const networkExplorers: Array<NetworkExplorer> = (() => {
return appConfig.network.explorers.map((explorer) => ({
...explorer,
baseUrl: stripTrailingSlash(explorer.baseUrl),
paths: _mapValues(explorer.paths, _compose(stripTrailingSlash, addLeadingSlash)),
}));
})();
export default networkExplorers;
import { useRouter } from 'next/router';
import React from 'react'; import React from 'react';
import appConfig from 'configs/app/config'; import appConfig from 'configs/app/config';
import link from 'lib/link/link';
import { ROUTES } from 'lib/link/routes';
import useCurrentRoute from 'lib/link/useCurrentRoute';
import featuredNetworks from 'lib/networks/featuredNetworks'; import featuredNetworks from 'lib/networks/featuredNetworks';
export default function useNetworkNavigationItems() { export default function useNetworkNavigationItems() {
const currentRouteName = useCurrentRoute()();
const currentRoute = ROUTES[currentRouteName];
const router = useRouter();
return React.useMemo(() => { return React.useMemo(() => {
return featuredNetworks.map((network) => { return featuredNetworks.map((network) => {
const routeName = 'crossNetworkNavigation' in currentRoute && currentRoute.crossNetworkNavigation ? currentRouteName : 'network_index';
const [ , networkType, networkSubtype ] = network.basePath.split('/');
const url = link(routeName, { ...router.query, network_type: networkType, network_sub_type: networkSubtype });
return { return {
...network, ...network,
url: url, isActive: network.type ? appConfig.network.type === network.type : false,
isActive: appConfig.network.basePath === network.basePath,
}; };
}); });
}, [ currentRoute, currentRouteName, router.query ]); }, []);
} }
export type PageParams = { export type PageParams = {
network_type: string;
network_sub_type: string;
id: string; id: string;
} }
export type PageParams = { export type PageParams = unknown
network_type: string;
network_sub_type: string;
}
import type { GetServerSideProps, GetServerSidePropsResult } from 'next';
export type Props = {
cookies: string;
}
export const getServerSideProps: GetServerSideProps = async({ req }): Promise<GetServerSidePropsResult<Props>> => {
return {
props: {
cookies: req.headers.cookie || '',
},
};
};
import type { GetStaticPaths } from 'next';
export const getStaticPaths: GetStaticPaths = async() => {
return { paths: [], fallback: 'blocking' };
};
import type { GetStaticProps, GetStaticPropsResult } from 'next';
export const getStaticProps: GetStaticProps = async(context): Promise<GetStaticPropsResult<{ [key: string]: unknown }>> => {
return {
props: {
pageParams: context.params,
},
};
};
export type PageParams = { export type PageParams = {
network_type: string;
network_sub_type: string;
id: string; id: string;
} }
import type { TransactionsResponse } from 'types/api/transaction';
import type { Sort } from 'types/client/txs-sort';
import compareBns from 'lib/bigint/compareBns';
export default function sortTxs(txs: TransactionsResponse['items'], sorting?: Sort) {
let sortedTxs;
switch (sorting) {
case 'val-desc':
sortedTxs = [ ...txs ].sort((tx1, tx2) => compareBns(tx1.value, tx2.value));
break;
case 'val-asc':
sortedTxs = [ ...txs ].sort((tx1, tx2) => compareBns(tx2.value, tx1.value));
break;
case 'fee-desc':
sortedTxs = [ ...txs ].sort((tx1, tx2) => compareBns(tx1.fee.value, tx2.fee.value));
break;
case 'fee-asc':
sortedTxs = [ ...txs ].sort((tx1, tx2) => compareBns(tx2.fee.value, tx1.fee.value));
break;
default:
sortedTxs = txs;
}
return sortedTxs;
}
...@@ -15,24 +15,13 @@ export function middleware(req: NextRequest) { ...@@ -15,24 +15,13 @@ export function middleware(req: NextRequest) {
return; return;
} }
const [ , networkType, networkSubtype ] = req.nextUrl.pathname.split('/');
const networkParams = {
network_type: networkType,
network_sub_type: networkSubtype,
};
if (appConfig.network.type !== networkType && appConfig.network.subtype !== networkSubtype) {
const url = req.nextUrl.clone();
url.pathname = `/404`;
return NextResponse.rewrite(url);
}
// we don't have any info from router here, so just do straight forward sub-string search (sorry) // we don't have any info from router here, so just do straight forward sub-string search (sorry)
const isAccountRoute = req.nextUrl.pathname.includes('/account/'); const isAccountRoute = req.nextUrl.pathname.includes('/account/');
const isProfileRoute = req.nextUrl.pathname.includes('/auth/profile');
const apiToken = req.cookies.get(NAMES.API_TOKEN); const apiToken = req.cookies.get(NAMES.API_TOKEN);
if (isAccountRoute && !apiToken) { if ((isAccountRoute || isProfileRoute) && !apiToken && appConfig.isAccountSupported) {
const authUrl = link('auth', networkParams); const authUrl = link('auth');
return NextResponse.redirect(authUrl); return NextResponse.redirect(authUrl);
} }
......
import type { NextPage } from 'next';
import Head from 'next/head';
import React from 'react';
import Home from 'ui/pages/Home';
const HomePage: NextPage = () => {
return (
<>
<Head><title>Home Page</title></Head>
<Home/>
</>
);
};
export default HomePage;
export { getStaticPaths } from 'lib/next/getStaticPaths';
export { getStaticProps } from 'lib/next/getStaticProps';
import { ChakraProvider } from '@chakra-ui/react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import type { AppProps } from 'next/app'; import type { AppProps } from 'next/app';
import React, { useState } from 'react'; import React, { useState } from 'react';
import { AppWrapper } from 'lib/appContext';
import { Chakra } from 'lib/Chakra';
import useConfigSentry from 'lib/hooks/useConfigSentry'; import useConfigSentry from 'lib/hooks/useConfigSentry';
import type { ErrorType } from 'lib/hooks/useFetch'; import type { ErrorType } from 'lib/hooks/useFetch';
import theme from 'theme'; import theme from 'theme';
...@@ -30,12 +31,14 @@ function MyApp({ Component, pageProps }: AppProps) { ...@@ -30,12 +31,14 @@ function MyApp({ Component, pageProps }: AppProps) {
})); }));
return ( return (
<AppWrapper pageProps={ pageProps }>
<QueryClientProvider client={ queryClient }> <QueryClientProvider client={ queryClient }>
<ChakraProvider theme={ theme }> <Chakra theme={ theme } cookies={ pageProps.cookies }>
<Component { ...pageProps }/> <Component { ...pageProps }/>
</ChakraProvider> </Chakra>
<ReactQueryDevtools/> <ReactQueryDevtools/>
</QueryClientProvider> </QueryClientProvider>
</AppWrapper>
); );
} }
......
...@@ -13,10 +13,10 @@ class MyDocument extends Document { ...@@ -13,10 +13,10 @@ class MyDocument extends Document {
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap"
rel="stylesheet" rel="stylesheet"
/> />
<link rel="icon" sizes="32x32" type="image/png" href="/favicon-32x32.png"/> <link rel="icon" sizes="32x32" type="image/png" href="/static/favicon-32x32.png"/>
<link rel="icon" sizes="16x16" type="image/png"href="/favicon-16x16.png"/> <link rel="icon" sizes="16x16" type="image/png"href="/static/favicon-16x16.png"/>
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png"/> <link rel="apple-touch-icon" sizes="180x180" href="/static/apple-touch-icon.png"/>
<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5"/> <link rel="mask-icon" href="/static/safari-pinned-tab.svg" color="#5bbad5"/>
</Head> </Head>
<body> <body>
<ColorModeScript initialColorMode={ theme.config.initialColorMode }/> <ColorModeScript initialColorMode={ theme.config.initialColorMode }/>
......
...@@ -5,16 +5,7 @@ import React from 'react'; ...@@ -5,16 +5,7 @@ import React from 'react';
import getNetworkTitle from 'lib/networks/getNetworkTitle'; import getNetworkTitle from 'lib/networks/getNetworkTitle';
import ApiKeys from 'ui/pages/ApiKeys'; import ApiKeys from 'ui/pages/ApiKeys';
type PageParams = { const ApiKeysPage: NextPage = () => {
network_type: string;
network_sub_type: string;
}
type Props = {
pageParams: PageParams;
}
const ApiKeysPage: NextPage<Props> = () => {
const title = getNetworkTitle(); const title = getNetworkTitle();
return ( return (
<> <>
...@@ -26,5 +17,4 @@ const ApiKeysPage: NextPage<Props> = () => { ...@@ -26,5 +17,4 @@ const ApiKeysPage: NextPage<Props> = () => {
export default ApiKeysPage; export default ApiKeysPage;
export { getStaticPaths } from 'lib/next/getStaticPaths'; export { getServerSideProps } from 'lib/next/getServerSideProps';
export { getStaticProps } from 'lib/next/getStaticProps';
...@@ -5,16 +5,7 @@ import React from 'react'; ...@@ -5,16 +5,7 @@ import React from 'react';
import getNetworkTitle from 'lib/networks/getNetworkTitle'; import getNetworkTitle from 'lib/networks/getNetworkTitle';
import CustomAbi from 'ui/pages/CustomAbi'; import CustomAbi from 'ui/pages/CustomAbi';
type PageParams = { const CustomAbiPage: NextPage = () => {
network_type: string;
network_sub_type: string;
}
type Props = {
pageParams: PageParams;
}
const CustomAbiPage: NextPage<Props> = () => {
const title = getNetworkTitle(); const title = getNetworkTitle();
return ( return (
<> <>
...@@ -26,5 +17,4 @@ const CustomAbiPage: NextPage<Props> = () => { ...@@ -26,5 +17,4 @@ const CustomAbiPage: NextPage<Props> = () => {
export default CustomAbiPage; export default CustomAbiPage;
export { getStaticPaths } from 'lib/next/getStaticPaths'; export { getServerSideProps } from 'lib/next/getServerSideProps';
export { getStaticProps } from 'lib/next/getStaticProps';
...@@ -5,16 +5,7 @@ import React from 'react'; ...@@ -5,16 +5,7 @@ import React from 'react';
import getNetworkTitle from 'lib/networks/getNetworkTitle'; import getNetworkTitle from 'lib/networks/getNetworkTitle';
import PublicTags from 'ui/pages/PublicTags'; import PublicTags from 'ui/pages/PublicTags';
type PageParams = { const PublicTagsPage: NextPage = () => {
network_type: string;
network_sub_type: string;
}
type Props = {
pageParams: PageParams;
}
const PublicTagsPage: NextPage<Props> = () => {
const title = getNetworkTitle(); const title = getNetworkTitle();
return ( return (
<> <>
...@@ -26,5 +17,4 @@ const PublicTagsPage: NextPage<Props> = () => { ...@@ -26,5 +17,4 @@ const PublicTagsPage: NextPage<Props> = () => {
export default PublicTagsPage; export default PublicTagsPage;
export { getStaticPaths } from 'lib/next/getStaticPaths'; export { getServerSideProps } from 'lib/next/getServerSideProps';
export { getStaticProps } from 'lib/next/getStaticProps';
...@@ -5,16 +5,7 @@ import React from 'react'; ...@@ -5,16 +5,7 @@ import React from 'react';
import getNetworkTitle from 'lib/networks/getNetworkTitle'; import getNetworkTitle from 'lib/networks/getNetworkTitle';
import PrivateTags from 'ui/pages/PrivateTags'; import PrivateTags from 'ui/pages/PrivateTags';
type PageParams = { const AddressTagsPage: NextPage = () => {
network_type: string;
network_sub_type: string;
}
type Props = {
pageParams: PageParams;
}
const AddressTagsPage: NextPage<Props> = () => {
const title = getNetworkTitle(); const title = getNetworkTitle();
return ( return (
<> <>
...@@ -26,5 +17,4 @@ const AddressTagsPage: NextPage<Props> = () => { ...@@ -26,5 +17,4 @@ const AddressTagsPage: NextPage<Props> = () => {
export default AddressTagsPage; export default AddressTagsPage;
export { getStaticPaths } from 'lib/next/getStaticPaths'; export { getServerSideProps } from 'lib/next/getServerSideProps';
export { getStaticProps } from 'lib/next/getStaticProps';
...@@ -5,16 +5,7 @@ import React from 'react'; ...@@ -5,16 +5,7 @@ import React from 'react';
import getNetworkTitle from 'lib/networks/getNetworkTitle'; import getNetworkTitle from 'lib/networks/getNetworkTitle';
import WatchList from 'ui/pages/Watchlist'; import WatchList from 'ui/pages/Watchlist';
type PageParams = { const WatchListPage: NextPage = () => {
network_type: string;
network_sub_type: string;
}
type Props = {
pageParams: PageParams;
}
const WatchListPage: NextPage<Props> = () => {
const title = getNetworkTitle(); const title = getNetworkTitle();
return ( return (
<> <>
...@@ -28,5 +19,4 @@ const WatchListPage: NextPage<Props> = () => { ...@@ -28,5 +19,4 @@ const WatchListPage: NextPage<Props> = () => {
export default WatchListPage; export default WatchListPage;
export { getStaticPaths } from 'lib/next/getStaticPaths'; export { getServerSideProps } from 'lib/next/getServerSideProps';
export { getStaticProps } from 'lib/next/getStaticProps';
import type { NextApiRequest } from 'next';
import getSearchParams from 'lib/api/getSearchParams';
import handler from 'lib/api/handler';
const getUrl = (req: NextApiRequest) => {
const searchParamsStr = getSearchParams(req);
return `/v2/blocks/${ req.query.id }/transactions${ searchParamsStr ? '?' + searchParamsStr : '' }`;
};
const requestHandler = handler(getUrl, [ 'GET' ]);
export default requestHandler;
import handler from 'lib/api/handler';
const getUrl = () => {
return `/v2/config/json-rpc-url`;
};
const requestHandler = handler(getUrl, [ 'GET' ]);
export default requestHandler;
import type { NextApiRequest } from 'next'; import type { NextApiRequest } from 'next';
import getSearchParams from 'lib/api/getSearchParams';
import handler from 'lib/api/handler'; import handler from 'lib/api/handler';
const getUrl = (req: NextApiRequest) => { const getUrl = (req: NextApiRequest) => {
const searchParams: Record<string, string> = {}; const searchParamsStr = getSearchParams(req);
Object.entries(req.query).forEach(([ key, value ]) => {
searchParams[key] = Array.isArray(value) ? value.join(',') : (value || '');
});
const searchParamsStr = new URLSearchParams(searchParams).toString();
return `/v2/transactions/${ searchParamsStr ? '?' + searchParamsStr : '' }`; return `/v2/transactions/${ searchParamsStr ? '?' + searchParamsStr : '' }`;
}; };
......
import type { NextPage } from 'next';
import Head from 'next/head'; import Head from 'next/head';
import React from 'react'; import React from 'react';
...@@ -5,7 +6,7 @@ import Apps from 'ui/pages/Apps'; ...@@ -5,7 +6,7 @@ import Apps from 'ui/pages/Apps';
import Page from 'ui/shared/Page/Page'; import Page from 'ui/shared/Page/Page';
import PageTitle from 'ui/shared/Page/PageTitle'; import PageTitle from 'ui/shared/Page/PageTitle';
const AppsPage = () => { const AppsPage: NextPage = () => {
return ( return (
<Page> <Page>
<PageTitle text="Apps"/> <PageTitle text="Apps"/>
...@@ -18,5 +19,4 @@ const AppsPage = () => { ...@@ -18,5 +19,4 @@ const AppsPage = () => {
export default AppsPage; export default AppsPage;
export { getStaticPaths } from 'lib/next/getStaticPaths'; export { getServerSideProps } from 'lib/next/getServerSideProps';
export { getStaticProps } from 'lib/next/getStaticProps';
...@@ -5,7 +5,7 @@ import React, { useEffect, useState } from 'react'; ...@@ -5,7 +5,7 @@ import React, { useEffect, useState } from 'react';
import type { AppItemOverview } from 'types/client/apps'; import type { AppItemOverview } from 'types/client/apps';
import marketplaceApps from 'data/marketplaceApps.json'; import appConfig from 'configs/app/config';
import { apos } from 'lib/html-entities'; import { apos } from 'lib/html-entities';
import EmptySearchResult from 'ui/apps/EmptySearchResult'; import EmptySearchResult from 'ui/apps/EmptySearchResult';
import MarketplaceApp from 'ui/pages/MarketplaceApp'; import MarketplaceApp from 'ui/pages/MarketplaceApp';
...@@ -23,7 +23,7 @@ const AppPage: NextPage = () => { ...@@ -23,7 +23,7 @@ const AppPage: NextPage = () => {
return; return;
} }
const app = marketplaceApps.find((app) => app.id === id); const app = appConfig.marketplaceAppList.find((app) => app.id === id);
setApp(app); setApp(app);
setIsLoading(false); setIsLoading(false);
}, [ id ]); }, [ id ]);
...@@ -47,5 +47,4 @@ const AppPage: NextPage = () => { ...@@ -47,5 +47,4 @@ const AppPage: NextPage = () => {
export default AppPage; export default AppPage;
export { getStaticPaths } from 'lib/next/getStaticPaths'; export { getServerSideProps } from 'lib/next/getServerSideProps';
export { getStaticProps } from 'lib/next/getStaticProps';
...@@ -15,5 +15,4 @@ const MyProfilePage: NextPage = () => { ...@@ -15,5 +15,4 @@ const MyProfilePage: NextPage = () => {
export default MyProfilePage; export default MyProfilePage;
export { getStaticPaths } from 'lib/next/getStaticPaths'; export { getServerSideProps } from 'lib/next/getServerSideProps';
export { getStaticProps } from 'lib/next/getStaticProps';
...@@ -17,5 +17,4 @@ const BlockPage: NextPage<Props> = ({ pageParams }: Props) => { ...@@ -17,5 +17,4 @@ const BlockPage: NextPage<Props> = ({ pageParams }: Props) => {
export default BlockPage; export default BlockPage;
export { getStaticPaths } from 'lib/next/getStaticPaths'; export { getServerSideProps } from 'lib/next/getServerSideProps';
export { getStaticProps } from 'lib/next/getStaticProps';
import type { NextPage } from 'next'; import type { NextPage } from 'next';
import React from 'react'; import React from 'react';
import type { PageParams } from 'lib/next/tx/types';
import BlocksNextPage from 'lib/next/blocks/BlocksNextPage'; import BlocksNextPage from 'lib/next/blocks/BlocksNextPage';
type Props = { const BlockPage: NextPage = () => {
pageParams: PageParams;
}
const BlockPage: NextPage<Props> = () => {
return ( return (
<BlocksNextPage/> <BlocksNextPage/>
); );
...@@ -17,5 +11,4 @@ const BlockPage: NextPage<Props> = () => { ...@@ -17,5 +11,4 @@ const BlockPage: NextPage<Props> = () => {
export default BlockPage; export default BlockPage;
export { getStaticPaths } from 'lib/next/getStaticPaths'; export { getServerSideProps } from 'lib/next/getServerSideProps';
export { getStaticProps } from 'lib/next/getStaticProps';
import type { NextPage } from 'next'; import type { NextPage } from 'next';
import Head from 'next/head';
import React from 'react';
const Home: NextPage = () => { import Home from 'ui/pages/Home';
return null;
const HomePage: NextPage = () => {
return (
<>
<Head><title>Home Page</title></Head>
<Home/>
</>
);
}; };
export default Home; export default HomePage;
export { getServerSideProps } from 'lib/next/getServerSideProps';
...@@ -17,5 +17,4 @@ const TransactionPage: NextPage<Props> = ({ pageParams }: Props) => { ...@@ -17,5 +17,4 @@ const TransactionPage: NextPage<Props> = ({ pageParams }: Props) => {
export default TransactionPage; export default TransactionPage;
export { getStaticPaths } from 'lib/next/getStaticPaths'; export { getServerSideProps } from 'lib/next/getServerSideProps';
export { getStaticProps } from 'lib/next/getStaticProps';
...@@ -5,16 +5,7 @@ import React from 'react'; ...@@ -5,16 +5,7 @@ import React from 'react';
import getNetworkTitle from 'lib/networks/getNetworkTitle'; import getNetworkTitle from 'lib/networks/getNetworkTitle';
import Transactions from 'ui/pages/Transactions'; import Transactions from 'ui/pages/Transactions';
type PageParams = { const TxsPage: NextPage = () => {
network_type: string;
network_sub_type: string;
}
type Props = {
pageParams: PageParams;
}
const AddressTagsPage: NextPage<Props> = () => {
const title = getNetworkTitle(); const title = getNetworkTitle();
return ( return (
<> <>
...@@ -24,7 +15,6 @@ const AddressTagsPage: NextPage<Props> = () => { ...@@ -24,7 +15,6 @@ const AddressTagsPage: NextPage<Props> = () => {
); );
}; };
export default AddressTagsPage; export default TxsPage;
export { getStaticPaths } from 'lib/next/getStaticPaths'; export { getServerSideProps } from 'lib/next/getServerSideProps';
export { getStaticProps } from 'lib/next/getStaticProps';
...@@ -16,10 +16,10 @@ const variantSimple = definePartsStyle((props) => { ...@@ -16,10 +16,10 @@ const variantSimple = definePartsStyle((props) => {
th: { th: {
border: 0, border: 0,
color: mode('gray.600', 'whiteAlpha.700')(props), color: mode('gray.600', 'whiteAlpha.700')(props),
backgroundColor: mode('blackAlpha.100', 'whiteAlpha.200')(props),
...transitionProps, ...transitionProps,
}, },
thead: { thead: {
backgroundColor: mode('blackAlpha.100', 'whiteAlpha.200')(props),
...transitionProps, ...transitionProps,
}, },
td: { td: {
...@@ -73,9 +73,6 @@ const variants = { ...@@ -73,9 +73,6 @@ const variants = {
}; };
const baseStyle = definePartsStyle({ const baseStyle = definePartsStyle({
thead: {
backgroundColor: 'gray.50',
},
th: { th: {
textTransform: 'none', textTransform: 'none',
fontFamily: 'body', fontFamily: 'body',
...@@ -83,6 +80,12 @@ const baseStyle = definePartsStyle({ ...@@ -83,6 +80,12 @@ const baseStyle = definePartsStyle({
overflow: 'hidden', overflow: 'hidden',
color: 'gray.500', color: 'gray.500',
letterSpacing: 'none', letterSpacing: 'none',
_first: {
borderTopLeftRadius: '8px',
},
_last: {
borderTopRightRadius: '8px',
},
}, },
td: { td: {
fontSize: 'md', fontSize: 'md',
...@@ -92,7 +95,7 @@ const baseStyle = definePartsStyle({ ...@@ -92,7 +95,7 @@ const baseStyle = definePartsStyle({
tableLayout: 'fixed', tableLayout: 'fixed',
borderTopLeftRadius: 'base', borderTopLeftRadius: 'base',
borderTopRightRadius: 'base', borderTopRightRadius: 'base',
overflow: 'hidden', overflow: 'unset',
fontVariant: 'normal', fontVariant: 'normal',
}, },
}); });
......
const breakpoints = { const breakpoints = {
// maybe we need them in future // maybe we need them in future
sm: '414px', sm: '415px',
// md: '768px', // md: '768px',
lg: '1000px', lg: '1000px',
xl: '1440px', xl: '1440px',
......
const zIndices = {
hide: -1,
auto: 'auto',
base: 0,
docked: 10,
dropdown: 1000,
sticky: 1100,
sticky1: 1101,
sticky2: 1102,
banner: 1200,
overlay: 1300,
modal: 1400,
popover: 1500,
skipLink: 1600,
toast: 1700,
tooltip: 1800,
};
export default zIndices;
...@@ -7,6 +7,7 @@ import breakpoints from './foundations/breakpoints'; ...@@ -7,6 +7,7 @@ import breakpoints from './foundations/breakpoints';
import colors from './foundations/colors'; import colors from './foundations/colors';
import transition from './foundations/transition'; import transition from './foundations/transition';
import typography from './foundations/typography'; import typography from './foundations/typography';
import zIndices from './foundations/zIndices';
import global from './global'; import global from './global';
const overrides = { const overrides = {
...@@ -20,6 +21,7 @@ const overrides = { ...@@ -20,6 +21,7 @@ const overrides = {
}, },
breakpoints, breakpoints,
transition, transition,
zIndices,
}; };
export default extendTheme(overrides); export default extendTheme(overrides);
export interface AddressTag {
label: string;
display_name: string;
address_hash: string;
}
export interface WatchlistName {
label: string;
display_name: string;
}
export interface AddressParam { export interface AddressParam {
hash: string; hash: string;
implementation_name: string; implementation_name: string;
name: string; name: string | null;
is_contract: boolean; is_contract: boolean;
private_tags: Array<AddressTag> | null;
watchlist_names: Array<WatchlistName> | null;
public_tags: Array<AddressTag> | null;
} }
import type { AddressParam } from 'types/api/addressParams'; import type { AddressParam } from 'types/api/addressParams';
import type { Reward } from 'types/api/reward'; import type { Reward } from 'types/api/reward';
import type { Transaction } from 'types/api/transaction';
export type BlockType = 'block' | 'reorg' | 'uncle'; export type BlockType = 'block' | 'reorg' | 'uncle';
...@@ -37,3 +38,12 @@ export interface BlocksResponse { ...@@ -37,3 +38,12 @@ export interface BlocksResponse {
items_count: number; items_count: number;
}; };
} }
export interface BlockTransactionsResponse {
items: Array<Transaction>;
next_page_params: {
block_number: number;
index: number;
items_count: number;
} | null;
}
...@@ -10,10 +10,11 @@ export interface InternalTransaction { ...@@ -10,10 +10,11 @@ export interface InternalTransaction {
from: AddressParam; from: AddressParam;
to: AddressParam; to: AddressParam;
created_contract: AddressParam; created_contract: AddressParam;
value: number; value: string;
index: number; index: number;
block: number; block: number;
timestamp: string; timestamp: string;
gas_limit: string;
} }
export interface InternalTransactionsResponse { export interface InternalTransactionsResponse {
......
export type JsonRpcUrlResponse = {
json_rpc_url: string;
}
export interface Reward { export interface Reward {
reward: number; reward: string;
type: 'Miner Reward' | 'Validator Reward' | 'Emission Reward' | 'Chore Reward' | 'Uncle Reward'; type: 'Miner Reward' | 'Validator Reward' | 'Emission Reward' | 'Chore Reward' | 'Uncle Reward';
} }
export type TokenType = 'ERC-20' | 'ERC-721' | 'ERC-1155';
export interface TokenInfo {
address: string;
type: TokenType;
symbol: string | null;
name: string | null;
decimals: string | null;
holders: string | null;
exchange_rate: string | null;
}
export type TokenInfoGeneric<Type extends TokenType> = Omit<TokenInfo, 'type'> & { type: Type };
import type { AddressParam } from './addressParams'; import type { AddressParam } from './addressParams';
import type { TokenInfoGeneric } from './tokenInfo';
export type ERC1155TotalPayload = { export type Erc20TotalPayload = {
decimals: string | null;
value: string;
}
export type Erc721TotalPayload = {
token_id: string;
}
export type Erc1155TotalPayload = {
decimals: string | null;
value: string; value: string;
token_id: string; token_id: string;
} }
export type TokenTransfer = ( export type TokenTransfer = (
{ {
token_type: 'ERC-20'; token: TokenInfoGeneric<'ERC-20'>;
total: { total: Erc20TotalPayload;
value: string;
};
} | } |
{ {
token_type: 'ERC-721'; token: TokenInfoGeneric<'ERC-721'>;
total: { total: Erc721TotalPayload;
token_id: string;
};
} | } |
{ {
token_type: 'ERC-1155'; token: TokenInfoGeneric<'ERC-1155'>;
total: ERC1155TotalPayload | Array<ERC1155TotalPayload>; total: Erc1155TotalPayload | Array<Erc1155TotalPayload>;
} }
) & TokenTransferBase ) & TokenTransferBase
interface TokenTransferBase { interface TokenTransferBase {
type: 'token_transfer' | 'token_burning' | 'token_spawning' | 'token_minting'; type: 'token_transfer' | 'token_burning' | 'token_spawning' | 'token_minting';
txHash: string; tx_hash: string;
from: AddressParam; from: AddressParam;
to: AddressParam; to: AddressParam;
token_address: string;
token_symbol: string;
exchange_rate: string;
} }
...@@ -38,6 +38,9 @@ export interface Transaction { ...@@ -38,6 +38,9 @@ export interface Transaction {
token_transfers: Array<TokenTransfer> | null; token_transfers: Array<TokenTransfer> | null;
token_transfers_overflow: boolean; token_transfers_overflow: boolean;
exchange_rate: string; exchange_rate: string;
method: string;
tx_types: Array<TransactionType>;
tx_tag: string | null;
} }
export interface TransactionsResponse { export interface TransactionsResponse {
...@@ -46,5 +49,7 @@ export interface TransactionsResponse { ...@@ -46,5 +49,7 @@ export interface TransactionsResponse {
block_number: number; block_number: number;
index: number; index: number;
items_count: number; items_count: number;
}; } | null;
} }
export type TransactionType = 'token_transfer' | 'contract_creation' | 'contract_call' | 'token_creation' | 'coin_transfer'
export type TTxsFilters = {
filter: 'pending' | 'validated';
type?: Array<TypeFilter>;
method?: Array<MethodFilter>;
}
export type TypeFilter = 'token_transfer' | 'contract_creation' | 'contract_call' | 'coin_transfer' | 'token_creation';
export type MethodFilter = 'approve' | 'transfer' | 'multicall' | 'mint' | 'commit';
export enum QueryKeys {
addressTags = 'address-tags',
apiKeys = 'api-keys',
block = 'block',
blocks = 'blocks',
customAbis = 'custom-abis',
profile = 'profile',
publicTags = 'public-tags',
transactionTags = 'transaction-tags',
watchlist = 'watchlist',
}
...@@ -26,7 +26,6 @@ export type AppItemPreview = { ...@@ -26,7 +26,6 @@ export type AppItemPreview = {
} }
export type AppItemOverview = AppItemPreview & { export type AppItemOverview = AppItemPreview & {
chainIds: Array<string>;
author: string; author: string;
url: string; url: string;
description: string; description: string;
......
export enum QueryKeys {
csrf = 'csrf',
profile = 'profile',
transactions = 'transactions',
tx = 'tx',
txInternals = 'tx-internals',
txLog = 'tx-log',
txRawTrace = 'tx-raw-trace',
blockTxs = 'block-transactions',
}
export type Sort = 'val-desc' | 'val-asc' | 'fee-desc' | 'fee-asc' | undefined; export type Sort = 'val-desc' | 'val-asc' | 'fee-desc' | 'fee-asc' | '';
...@@ -2,9 +2,23 @@ import type { FunctionComponent, SVGAttributes } from 'react'; ...@@ -2,9 +2,23 @@ import type { FunctionComponent, SVGAttributes } from 'react';
export type NetworkGroup = 'mainnets' | 'testnets' | 'other'; export type NetworkGroup = 'mainnets' | 'testnets' | 'other';
export type PreDefinedNetwork = 'xdai_mainnet' | 'xdai_optimism' | 'xdai_aox' | 'eth_mainnet' | 'etc_mainnet' | 'poa_core' |
'rsk_mainnet' | 'xdai_testnet' | 'poa_sokol' | 'artis_sigma1' | 'lukso_l14' | 'astar' | 'shiden' | 'shibuya';
export interface FeaturedNetwork { export interface FeaturedNetwork {
title: string; title: string;
basePath: string; url: string;
group: NetworkGroup; group: NetworkGroup;
icon?: FunctionComponent<SVGAttributes<SVGElement>> | string; icon?: FunctionComponent<SVGAttributes<SVGElement>> | string;
type?: PreDefinedNetwork;
} }
export interface NetworkExplorer {
title: string;
baseUrl: string;
paths: {
tx: string;
};
}
export type NetworkVerificationType = 'mining' | 'validation';
import React from 'react';
const ScrollDirectionContext = React.createContext<'up' | 'down' | undefined>(undefined);
export default ScrollDirectionContext;
...@@ -12,6 +12,7 @@ import type { SubmitHandler, ControllerRenderProps } from 'react-hook-form'; ...@@ -12,6 +12,7 @@ import type { SubmitHandler, ControllerRenderProps } from 'react-hook-form';
import { useForm, Controller } from 'react-hook-form'; import { useForm, Controller } from 'react-hook-form';
import type { ApiKey, ApiKeys, ApiKeyErrors } from 'types/api/account'; import type { ApiKey, ApiKeys, ApiKeyErrors } from 'types/api/account';
import { QueryKeys } from 'types/client/accountQueries';
import getErrorMessage from 'lib/getErrorMessage'; import getErrorMessage from 'lib/getErrorMessage';
import getPlaceholderWithError from 'lib/getPlaceholderWithError'; import getPlaceholderWithError from 'lib/getPlaceholderWithError';
...@@ -47,17 +48,17 @@ const ApiKeyForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => { ...@@ -47,17 +48,17 @@ const ApiKeyForm: React.FC<Props> = ({ data, onClose, setAlertVisible }) => {
const body = { name: data.name }; const body = { name: data.name };
if (!data.token) { if (!data.token) {
return fetch('/api/account/api-keys', { method: 'POST', body }); return fetch('/node-api/account/api-keys', { method: 'POST', body });
} }
return fetch(`/api/account/api-keys/${ data.token }`, { method: 'PUT', body }); return fetch(`/node-api/account/api-keys/${ data.token }`, { method: 'PUT', body });
}; };
const mutation = useMutation(updateApiKey, { const mutation = useMutation(updateApiKey, {
onSuccess: async(data) => { onSuccess: async(data) => {
const response = data as unknown as ApiKey; const response = data as unknown as ApiKey;
queryClient.setQueryData([ 'api-keys' ], (prevData: ApiKeys | undefined) => { queryClient.setQueryData([ QueryKeys.apiKeys ], (prevData: ApiKeys | undefined) => {
const isExisting = prevData && prevData.some((item) => item.api_key === response.api_key); const isExisting = prevData && prevData.some((item) => item.api_key === response.api_key);
if (isExisting) { if (isExisting) {
......
...@@ -4,7 +4,6 @@ import { ...@@ -4,7 +4,6 @@ import {
Tbody, Tbody,
Tr, Tr,
Th, Th,
TableContainer,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import React from 'react'; import React from 'react';
...@@ -21,7 +20,6 @@ interface Props { ...@@ -21,7 +20,6 @@ interface Props {
const ApiKeyTable = ({ data, onDeleteClick, onEditClick, limit }: Props) => { const ApiKeyTable = ({ data, onDeleteClick, onEditClick, limit }: Props) => {
return ( return (
<TableContainer width="100%">
<Table variant="simple" minWidth="600px"> <Table variant="simple" minWidth="600px">
<Thead> <Thead>
<Tr> <Tr>
...@@ -40,7 +38,6 @@ const ApiKeyTable = ({ data, onDeleteClick, onEditClick, limit }: Props) => { ...@@ -40,7 +38,6 @@ const ApiKeyTable = ({ data, onDeleteClick, onEditClick, limit }: Props) => {
)) } )) }
</Tbody> </Tbody>
</Table> </Table>
</TableContainer>
); );
}; };
......
...@@ -3,6 +3,7 @@ import { useQueryClient } from '@tanstack/react-query'; ...@@ -3,6 +3,7 @@ import { useQueryClient } from '@tanstack/react-query';
import React, { useCallback } from 'react'; import React, { useCallback } from 'react';
import type { ApiKey, ApiKeys } from 'types/api/account'; import type { ApiKey, ApiKeys } from 'types/api/account';
import { QueryKeys } from 'types/client/accountQueries';
import useFetch from 'lib/hooks/useFetch'; import useFetch from 'lib/hooks/useFetch';
import DeleteModal from 'ui/shared/DeleteModal'; import DeleteModal from 'ui/shared/DeleteModal';
...@@ -18,11 +19,11 @@ const DeleteAddressModal: React.FC<Props> = ({ isOpen, onClose, data }) => { ...@@ -18,11 +19,11 @@ const DeleteAddressModal: React.FC<Props> = ({ isOpen, onClose, data }) => {
const fetch = useFetch(); const fetch = useFetch();
const mutationFn = useCallback(() => { const mutationFn = useCallback(() => {
return fetch(`/api/account/api-keys/${ data.api_key }`, { method: 'DELETE' }); return fetch(`/node-api/account/api-keys/${ data.api_key }`, { method: 'DELETE' });
}, [ data.api_key, fetch ]); }, [ data.api_key, fetch ]);
const onSuccess = useCallback(async() => { const onSuccess = useCallback(async() => {
queryClient.setQueryData([ 'api-keys' ], (prevData: ApiKeys | undefined) => { queryClient.setQueryData([ QueryKeys.apiKeys ], (prevData: ApiKeys | undefined) => {
return prevData?.filter((item) => item.api_key !== data.api_key); return prevData?.filter((item) => item.api_key !== data.api_key);
}); });
}, [ data, queryClient ]); }, [ data, queryClient ]);
......
...@@ -7,7 +7,7 @@ import React, { useCallback } from 'react'; ...@@ -7,7 +7,7 @@ import React, { useCallback } from 'react';
import type { AppItemOverview, MarketplaceCategoriesIds } from 'types/client/apps'; import type { AppItemOverview, MarketplaceCategoriesIds } from 'types/client/apps';
import marketplaceApps from 'data/marketplaceApps.json'; import appConfig from 'configs/app/config';
import linkIcon from 'icons/link.svg'; import linkIcon from 'icons/link.svg';
import ghIcon from 'icons/social/git.svg'; import ghIcon from 'icons/social/git.svg';
import tgIcon from 'icons/social/telega.svg'; import tgIcon from 'icons/social/telega.svg';
...@@ -43,7 +43,7 @@ const AppModal = ({ ...@@ -43,7 +43,7 @@ const AppModal = ({
twitter, twitter,
logo, logo,
categories, categories,
} = marketplaceApps.find(app => app.id === id) as AppItemOverview; } = appConfig.marketplaceAppList.find(app => app.id === id) as AppItemOverview;
const socialLinks = [ const socialLinks = [
telegram ? { telegram ? {
...@@ -206,7 +206,7 @@ const AppModal = ({ ...@@ -206,7 +206,7 @@ const AppModal = ({
</Link> </Link>
) } ) }
{ socialLinks.length && ( { socialLinks.length > 0 && (
<List <List
marginLeft={{ sm: 'auto' }} marginLeft={{ sm: 'auto' }}
display="grid" display="grid"
......
...@@ -3,6 +3,7 @@ import React from 'react'; ...@@ -3,6 +3,7 @@ import React from 'react';
import type { MarketplaceCategoriesIds, MarketplaceCategory } from 'types/client/apps'; import type { MarketplaceCategoriesIds, MarketplaceCategory } from 'types/client/apps';
import appConfig from 'configs/app/config';
import eastMiniArrowIcon from 'icons/arrows/east-mini.svg'; import eastMiniArrowIcon from 'icons/arrows/east-mini.svg';
import CategoriesMenuItem from './CategoriesMenuItem'; import CategoriesMenuItem from './CategoriesMenuItem';
...@@ -20,6 +21,10 @@ type Props = { ...@@ -20,6 +21,10 @@ type Props = {
const CategoriesMenu = ({ selectedCategoryId, onSelect }: Props) => { const CategoriesMenu = ({ selectedCategoryId, onSelect }: Props) => {
const selectedCategory = categoriesList.find(category => category.id === selectedCategoryId); const selectedCategory = categoriesList.find(category => category.id === selectedCategoryId);
const actualCategories = appConfig.marketplaceAppList.map(app => app.categories).flat();
const displayedCategories = categoriesList.filter(category => category.id === 'all' ||
category.id === 'favorites' ||
actualCategories.includes(category.id));
return ( return (
<Menu> <Menu>
...@@ -43,7 +48,7 @@ const CategoriesMenu = ({ selectedCategoryId, onSelect }: Props) => { ...@@ -43,7 +48,7 @@ const CategoriesMenu = ({ selectedCategoryId, onSelect }: Props) => {
</MenuButton> </MenuButton>
<MenuList zIndex={ 3 }> <MenuList zIndex={ 3 }>
{ categoriesList.map((category: MarketplaceCategory) => ( { displayedCategories.map((category: MarketplaceCategory) => (
<CategoriesMenuItem <CategoriesMenuItem
key={ category.id } key={ category.id }
id={ category.id } id={ category.id }
......
...@@ -4,7 +4,6 @@ import React, { useCallback, useEffect, useState } from 'react'; ...@@ -4,7 +4,6 @@ import React, { useCallback, useEffect, useState } from 'react';
import type { AppItemOverview, MarketplaceCategoriesIds } from 'types/client/apps'; import type { AppItemOverview, MarketplaceCategoriesIds } from 'types/client/apps';
import appConfig from 'configs/app/config'; import appConfig from 'configs/app/config';
import marketplaceApps from 'data/marketplaceApps.json';
const favoriteAppsLocalStorageKey = 'favoriteApps'; const favoriteAppsLocalStorageKey = 'favoriteApps';
...@@ -79,8 +78,7 @@ export default function useMarketplaceApps() { ...@@ -79,8 +78,7 @@ export default function useMarketplaceApps() {
}, [ filterQuery, category, filterApps ]); }, [ filterQuery, category, filterApps ]);
useEffect(() => { useEffect(() => {
const defaultDisplayedApps = [ ...marketplaceApps ] const defaultDisplayedApps = [ ...appConfig.marketplaceAppList ]
.filter(item => item.chainIds.includes(appConfig.network.id))
.sort((a, b) => a.title.localeCompare(b.title)); .sort((a, b) => a.title.localeCompare(b.title));
setDefaultAppList(defaultDisplayedApps); setDefaultAppList(defaultDisplayedApps);
......
import { Grid, GridItem, Text, Icon, Link, Box, Tooltip, Alert } from '@chakra-ui/react'; import { Grid, GridItem, Text, Icon, Link, Box, Tooltip, Alert } from '@chakra-ui/react';
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import BigNumber from 'bignumber.js'; import BigNumber from 'bignumber.js';
import capitalize from 'lodash/capitalize';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import React from 'react'; import React from 'react';
import { scroller, Element } from 'react-scroll'; import { scroller, Element } from 'react-scroll';
import type { Block } from 'types/api/block'; import type { Block } from 'types/api/block';
import { QueryKeys } from 'types/client/accountQueries';
import appConfig from 'configs/app/config'; import appConfig from 'configs/app/config';
import clockIcon from 'icons/clock.svg'; import clockIcon from 'icons/clock.svg';
...@@ -17,6 +19,7 @@ import type { ErrorType } from 'lib/hooks/useFetch'; ...@@ -17,6 +19,7 @@ import type { ErrorType } from 'lib/hooks/useFetch';
import useFetch from 'lib/hooks/useFetch'; import useFetch from 'lib/hooks/useFetch';
import { space } from 'lib/html-entities'; import { space } from 'lib/html-entities';
import link from 'lib/link/link'; import link from 'lib/link/link';
import getNetworkValidatorTitle from 'lib/networks/getNetworkValidatorTitle';
import BlockDetailsSkeleton from 'ui/block/details/BlockDetailsSkeleton'; import BlockDetailsSkeleton from 'ui/block/details/BlockDetailsSkeleton';
import AddressLink from 'ui/shared/address/AddressLink'; import AddressLink from 'ui/shared/address/AddressLink';
import CopyToClipboard from 'ui/shared/CopyToClipboard'; import CopyToClipboard from 'ui/shared/CopyToClipboard';
...@@ -34,8 +37,8 @@ const BlockDetails = () => { ...@@ -34,8 +37,8 @@ const BlockDetails = () => {
const fetch = useFetch(); const fetch = useFetch();
const { data, isLoading, isError, error } = useQuery<unknown, ErrorType<{ status: number }>, Block>( const { data, isLoading, isError, error } = useQuery<unknown, ErrorType<{ status: number }>, Block>(
[ 'block', router.query.id ], [ QueryKeys.block, router.query.id ],
async() => await fetch(`/api/blocks/${ router.query.id }`), async() => await fetch(`/node-api/blocks/${ router.query.id }`),
{ {
enabled: Boolean(router.query.id), enabled: Boolean(router.query.id),
}, },
...@@ -69,6 +72,8 @@ const BlockDetails = () => { ...@@ -69,6 +72,8 @@ const BlockDetails = () => {
const sectionGap = <GridItem colSpan={{ base: undefined, lg: 2 }} mt={{ base: 1, lg: 4 }}/>; const sectionGap = <GridItem colSpan={{ base: undefined, lg: 2 }} mt={{ base: 1, lg: 4 }}/>;
const { totalReward, staticReward, burntFees, txFees } = getBlockReward(data); const { totalReward, staticReward, burntFees, txFees } = getBlockReward(data);
const validatorTitle = getNetworkValidatorTitle();
return ( return (
<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
...@@ -104,17 +109,17 @@ const BlockDetails = () => { ...@@ -104,17 +109,17 @@ const BlockDetails = () => {
title="Transactions" title="Transactions"
hint="The number of transactions in the block." hint="The number of transactions in the block."
> >
<Link href={ link('block', { id: router.query.id }, { tab: 'transactions' }) }> <Link href={ link('block', { id: router.query.id }, { tab: 'txs' }) }>
{ data.tx_count } transactions { data.tx_count } transactions
</Link> </Link>
</DetailsInfoItem> </DetailsInfoItem>
<DetailsInfoItem <DetailsInfoItem
title="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 hash={ data.miner.hash }/> <AddressLink hash={ data.miner.hash }/>
{ data.miner.name && <Text>(Miner: { data.miner.name })</Text> } { data.miner.name && <Text>{ `(${ capitalize(validatorTitle) }: ${ data.miner.name })` }</Text> }
{ /* api doesn't return the block processing time yet */ } { /* api doesn't return the block processing time yet */ }
{ /* <Text>{ dayjs.duration(block.minedIn, 'second').humanize(true) }</Text> */ } { /* <Text>{ dayjs.duration(block.minedIn, 'second').humanize(true) }</Text> */ }
</DetailsInfoItem> </DetailsInfoItem>
...@@ -122,12 +127,12 @@ const BlockDetails = () => { ...@@ -122,12 +127,12 @@ const BlockDetails = () => {
<DetailsInfoItem <DetailsInfoItem
title="Block reward" title="Block reward"
hint={ hint={
`For each block, the miner is rewarded with a finite amount of ${ appConfig.network.currency || '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 }
> >
<Text>{ totalReward.dividedBy(WEI).toFixed() } { appConfig.network.currency }</Text> <Text>{ totalReward.dividedBy(WEI).toFixed() } { appConfig.network.currency.symbol }</Text>
{ (!txFees.isEqualTo(ZERO) || !burntFees.isEqualTo(ZERO)) && ( { (!txFees.isEqualTo(ZERO) || !burntFees.isEqualTo(ZERO)) && (
<Text variant="secondary" whiteSpace="break-spaces">( <Text variant="secondary" whiteSpace="break-spaces">(
<Tooltip label="Static block reward"> <Tooltip label="Static block reward">
...@@ -153,6 +158,19 @@ const BlockDetails = () => { ...@@ -153,6 +158,19 @@ const BlockDetails = () => {
) } ) }
</DetailsInfoItem> </DetailsInfoItem>
) } ) }
{ data.rewards
?.filter(({ type }) => type !== 'Validator Reward' && type !== 'Miner Reward')
.map(({ type, reward }) => (
<DetailsInfoItem
key={ type }
title={ type }
// is this text correct for validators?
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 }
</DetailsInfoItem>
))
}
{ sectionGap } { sectionGap }
...@@ -180,7 +198,7 @@ const BlockDetails = () => { ...@@ -180,7 +198,7 @@ const BlockDetails = () => {
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 } </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">
{ space }({ BigNumber(data.base_fee_per_gas).dividedBy(WEI_IN_GWEI).toFixed() } Gwei) { space }({ BigNumber(data.base_fee_per_gas).dividedBy(WEI_IN_GWEI).toFixed() } Gwei)
</Text> </Text>
...@@ -189,13 +207,13 @@ const BlockDetails = () => { ...@@ -189,13 +207,13 @@ const BlockDetails = () => {
<DetailsInfoItem <DetailsInfoItem
title="Burnt fees" title="Burnt fees"
hint={ hint={
`Amount of ${ appConfig.network.currency || '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"/>
<Text ml={ 1 }>{ burntFees.dividedBy(WEI).toFixed() } { appConfig.network.currency }</Text> <Text ml={ 1 }>{ burntFees.dividedBy(WEI).toFixed() } { appConfig.network.currency.symbol }</Text>
{ !txFees.isEqualTo(ZERO) && ( { !txFees.isEqualTo(ZERO) && (
<Tooltip label="Burnt fees / Txn fees * 100%"> <Tooltip label="Burnt fees / Txn fees * 100%">
<Box> <Box>
...@@ -212,13 +230,13 @@ const BlockDetails = () => { ...@@ -212,13 +230,13 @@ const BlockDetails = () => {
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 } { BigNumber(data.priority_fee).dividedBy(WEI).toFixed() } { appConfig.network.currency.symbol }
</DetailsInfoItem> </DetailsInfoItem>
) } ) }
{ /* 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 miner 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>
...@@ -247,7 +265,7 @@ const BlockDetails = () => { ...@@ -247,7 +265,7 @@ const BlockDetails = () => {
<DetailsInfoItem <DetailsInfoItem
title="Difficulty" title="Difficulty"
hint="Block difficulty for miner, used to calibrate block generation time." hint={ `Block difficulty for ${ validatorTitle }, used to calibrate block generation time.` }
> >
{ BigNumber(data.difficulty).toFormat() } { BigNumber(data.difficulty).toFormat() }
</DetailsInfoItem> </DetailsInfoItem>
...@@ -293,17 +311,6 @@ const BlockDetails = () => { ...@@ -293,17 +311,6 @@ const BlockDetails = () => {
> >
{ data.nonce } { data.nonce }
</DetailsInfoItem> </DetailsInfoItem>
{ data.rewards
?.filter(({ type }) => type !== 'Validator Reward' && type !== 'Miner Reward')
.map(({ type, reward }) => (
<DetailsInfoItem
key={ type }
title={ type }
hint="Amount of distributed reward. Miners receive a static block reward + Tx fees + uncle fees."
>
{ BigNumber(reward).dividedBy(WEI).toFixed() } { appConfig.network.currency }
</DetailsInfoItem>
)) }
</> </>
) } ) }
</Grid> </Grid>
......
import { useRouter } from 'next/router';
import React from 'react'; import React from 'react';
import { QueryKeys } from 'types/client/queries';
import TxsContent from 'ui/txs/TxsContent'; import TxsContent from 'ui/txs/TxsContent';
const BlockTxs = () => { const BlockTxs = () => {
return <TxsContent showDescription={ false } showSortButton={ false } txs={ [] }/>; const router = useRouter();
return (
<TxsContent
queryName={ QueryKeys.blockTxs }
apiPath={ `/api/blocks/${ router.query.id }/transactions` }
/>
);
}; };
export default BlockTxs; export default BlockTxs;
import { Text } from '@chakra-ui/react';
import React from 'react';
import useTimeAgoIncrement from 'lib/hooks/useTimeAgoIncrement';
interface Props {
ts: string;
isEnabled?: boolean;
}
const BlockTimestamp = ({ ts, isEnabled }: Props) => {
const timeAgo = useTimeAgoIncrement(ts, isEnabled);
return <Text variant="secondary" fontWeight={ 400 }>{ timeAgo }</Text>;
};
export default React.memo(BlockTimestamp);
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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