Commit a31baf08 authored by tom's avatar tom

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

parents 2f73b6b1 b50a0405
Dockerfile
.dockerignore
node_modules
node_modules_linux
npm-debug.log
README.md
.next
......
......@@ -4,10 +4,13 @@ NEXT_PUBLIC_APP_INSTANCE=__PLACEHOLDER_FOR_NEXT_PUBLIC_APP_INSTANCE__
NEXT_PUBLIC_APP_PROTOCOL=__PLACEHOLDER_FOR_NEXT_PUBLIC_APP_PROTOCOL__
NEXT_PUBLIC_APP_HOST=__PLACEHOLDER_FOR_NEXT_PUBLIC_APP_HOST__
NEXT_PUBLIC_APP_PORT=__PLACEHOLDER_FOR_NEXT_PUBLIC_APP_PORT__
NEXT_PUBLIC_AUTH_URL=__PLACEHOLDER_FOR_NEXT_PUBLIC_AUTH_URL__
# network config
NEXT_PUBLIC_NETWORK_NAME=__PLACEHOLDER_FOR_NEXT_PUBLIC_NETWORK_NAME__
NEXT_PUBLIC_NETWORK_SHORT_NAME=__PLACEHOLDER_FOR_NEXT_PUBLIC_NETWORK_SHORT_NAME__
NEXT_PUBLIC_NETWORK_LOGO=__PLACEHOLDER_FOR_NEXT_PUBLIC_NETWORK_LOGO__
NEXT_PUBLIC_NETWORK_SMALL_LOGO=__PLACEHOLDER_FOR_NEXT_PUBLIC_NETWORK_SMALL_LOGO__
NEXT_PUBLIC_NETWORK_ASSETS_PATHNAME=__PLACEHOLDER_FOR_NEXT_PUBLIC_NETWORK_ASSETS_PATHNAME__
NEXT_PUBLIC_NETWORK_TYPE=__PLACEHOLDER_FOR_NEXT_PUBLIC_NETWORK_TYPE__
NEXT_PUBLIC_NETWORK_ID=__PLACEHOLDER_FOR_NEXT_PUBLIC_NETWORK_ID__
......@@ -30,10 +33,17 @@ NEXT_PUBLIC_MARKETPLACE_APP_LIST=__PLACEHOLDER_FOR_NEXT_PUBLIC_MARKETPLACE_APP_L
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__
NEXT_PUBLIC_HOMEPAGE_CHARTS=__PLACEHOLDER_FOR_NEXT_PUBLIC_HOMEPAGE_CHARTS__
NEXT_PUBLIC_HOMEPAGE_PLATE_GRADIENT=__PLACEHOLDER_FOR_NEXT_PUBLIC_HOMEPAGE_PLATE_GRADIENT__
NEXT_PUBLIC_HOMEPAGE_SHOW_GAS_TRACKER=__PLACEHOLDER_FOR_NEXT_PUBLIC_HOMEPAGE_SHOW_GAS_TRACKER__
NEXT_PUBLIC_HOMEPAGE_SHOW_AVG_BLOCK_TIME=__PLACEHOLDER_FOR_NEXT_PUBLIC_HOMEPAGE_SHOW_AVG_BLOCK_TIME__
NEXT_PUBLIC_AD_DOMAIN_WITH_AD=__PLACEHOLDER_FOR_NEXT_PUBLIC_DOMAIN_WITH_AD__
NEXT_PUBLIC_AD_ADBUTLER_ON=__PLACEHOLDER_FORNEXT_PUBLIC_AD_ADBUTLER_ON__
# api config
NEXT_PUBLIC_API_HOST=__PLACEHOLDER_FOR_NEXT_PUBLIC_API_HOST__
NEXT_PUBLIC_API_BASE_PATH=__PLACEHOLDER_FOR_NEXT_PUBLIC_API_BASE_PATH__
NEXT_PUBLIC_STATS_API_HOST=__PLACEHOLDER_FOR_NEXT_PUBLIC_STATS_API_HOST__
# external services config
NEXT_PUBLIC_SENTRY_DSN=__PLACEHOLDER_FOR_NEXT_PUBLIC_SENTRY_DSN__
......
node_modules
\ No newline at end of file
node_modules
node_modules_linux
playwright/envs.js
......@@ -18,6 +18,8 @@ module.exports = {
'plugin:regexp/recommended',
'plugin:@typescript-eslint/eslint-recommended',
'plugin:@typescript-eslint/recommended',
'plugin:jest/recommended',
'plugin:playwright/playwright-test',
],
plugins: [
'es5',
......@@ -27,6 +29,7 @@ module.exports = {
'react-hooks',
'jsx-a11y',
'eslint-plugin-import-helpers',
'jest',
],
parser: '@typescript-eslint/parser',
parserOptions: {
......@@ -195,7 +198,7 @@ module.exports = {
groups: [
'module',
'/types/',
[ '/^configs/', '/^data/', '/^deploy/', '/^icons/', '/^lib/', '/^pages/', '/^playwright/', '/^theme/', '/^ui/' ],
[ '/^configs/', '/^data/', '/^deploy/', '/^icons/', '/^lib/', '/^mocks/', '/^pages/', '/^playwright/', '/^theme/', '/^ui/' ],
[ 'parent', 'sibling', 'index' ],
],
alphabetize: { order: 'asc', ignoreCase: true },
......
......@@ -11,5 +11,6 @@ jobs:
cleanup:
uses: blockscout/blockscout-ci-cd/.github/workflows/cleanup.yaml@master
with:
appNamespace: review-front-$GITHUB_HEAD_REF_SLUG
appNamespace: review-front-$GITHUB_REF_NAME_SLUG
dockerImage: prerelease-$GITHUB_REF_NAME_SLUG
secrets: inherit
......@@ -2,8 +2,8 @@ name: Run E2E tests k8s
on:
# push:
# pull_request:
workflow_dispatch
pull_request:
workflow_dispatch:
env:
K8S_LOCAL_PORT: ${{ secrets.K8S_LOCAL_PORT }}
......
......@@ -3,8 +3,6 @@ name: Deploy review environment
on:
pull_request:
# push:
# branches-ignore:
# - 'main'
workflow_dispatch:
env:
......@@ -25,6 +23,9 @@ jobs:
- name: Check out the repo
uses: actions/checkout@v3
- name: Inject slug/short variables
uses: rlespinasse/github-slug-action@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
......@@ -47,7 +48,7 @@ jobs:
- name: Add outputs
run: |
echo "::set-output name=short-sha::${{ env.SHORT_SHA }}"
echo "::set-output name=short-sha::${{ env.GITHUB_EVENT_PULL_REQUEST_HEAD_SHA_SHORT }}"
id: output-step
- name: Build and push
......@@ -58,21 +59,22 @@ jobs:
push: true
cache-from: type=gha
cache-to: type=gha,mode=max
tags: ghcr.io/blockscout/frontend:prerelease-${{ env.SHORT_SHA }}
tags: ghcr.io/blockscout/frontend:prerelease-${{ env.GITHUB_REF_NAME_SLUG }}
labels: ${{ steps.meta.outputs.labels }}
build-args: |
GIT_COMMIT_SHA=${{ env.SHORT_SHA }}
GIT_COMMIT_SHA=${{ env.GITHUB_EVENT_PULL_REQUEST_HEAD_SHA_SHORT }}
deploy_frontend:
name: Deploy frontend app
needs: push_to_registry
uses: blockscout/blockscout-ci-cd/.github/workflows/deploy.yaml@master
with:
env_vars: VALUES_DIR=deploy/values/review,APP_NAME=bs-stack
appNamespace: review-front-$GITHUB_HEAD_REF_SLUG
env_vars: VALUES_DIR=deploy/values/review,APP_NAME=bs-stack,DOCKER_IMAGE=prerelease-$GITHUB_REF_NAME_SLUG
globalEnv: review
appNamespace: review-front-$GITHUB_REF_NAME_SLUG
blockscoutIngressHost: blockscout
frontendIngressHost: blockscout
frontendImage: ghcr.io/blockscout/frontend:prerelease-${{ needs.push_to_registry.outputs.shortSha }}
frontendImage: ghcr.io/blockscout/frontend:prerelease-$GITHUB_REF_NAME_SLUG
gethIngressHost: geth
scVerifierIngressHost: sc-verifier
secrets: inherit
name: Playwright
on: [pull_request]
name: Unit tests
on:
pull_request:
push:
branches:
- main
jobs:
test:
name: Run components visual tests
jest_tests:
name: Run tests with Jest
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 16
cache: 'yarn'
- name: Install dependencies
run: yarn install --frozen-lockfile
- name: Run tests
run: yarn test:jest
pw_tests:
name: Run components visual tests with PlayWright
runs-on: ubuntu-latest
container:
image: mcr.microsoft.com/playwright:v1.27.0-focal
image: mcr.microsoft.com/playwright:v1.28.0-focal
steps:
- run: apt-get update && apt-get install git-lfs
......@@ -14,12 +33,12 @@ jobs:
lfs: 'true'
- uses: actions/setup-node@v3
with:
node-version: '16'
node-version: 16
cache: 'yarn'
- name: Install dependencies
run: yarn install --frozen-lockfile
- name: Run your tests
run: HOME=/root yarn test-ct
run: HOME=/root yarn test:pw
- name: Upload test results
if: always()
uses: actions/upload-artifact@v2
......
......@@ -2,6 +2,7 @@
# dependencies
/node_modules
/node_modules_linux
/.pnp
.pnp.js
......@@ -29,6 +30,7 @@ yarn-error.log*
# local env files
.env*.local
/configs/envs/.env.secrets
/configs/envs/.samples
# vercel
.vercel
......@@ -45,3 +47,5 @@ yarn-error.log*
/test-results/
/playwright-report/
/playwright/.cache/
/playwright/.browser/
/playwright/envs.js
......@@ -11,7 +11,7 @@
"detail": "start local dev server",
"presentation": {
"reveal": "silent",
"panel": "new",
"panel": "dedicated",
"close": true,
"revealProblems": "onProblem",
},
......@@ -31,7 +31,27 @@
"detail": "start local dev server for POA network",
"presentation": {
"reveal": "silent",
"panel": "new",
"panel": "dedicated",
"close": true,
"revealProblems": "onProblem",
},
"icon": {
"color": "terminal.ansiMagenta",
"id": "server-process"
},
"runOptions": {
"instanceLimit": 1
}
},
{
"type": "npm",
"script": "dev:goerli",
"problemMatcher": [],
"label": "dev server: goerli",
"detail": "start local dev server for Goerli network",
"presentation": {
"reveal": "silent",
"panel": "dedicated",
"close": true,
"revealProblems": "onProblem",
},
......@@ -57,7 +77,7 @@
},
"presentation": {
"reveal": "never",
"panel": "new",
"panel": "dedicated",
"close": true,
"revealProblems": "onProblem",
},
......@@ -71,7 +91,7 @@
"detail": "run eslint",
"presentation": {
"reveal": "silent",
"panel": "new",
"panel": "dedicated",
"revealProblems": "onProblem",
},
"icon": {
......@@ -82,17 +102,97 @@
"instanceLimit": 1
}
},
// PW TESTS
{
"type": "npm",
"script": "test-docker",
"type": "shell",
"command": "${input:pwDebugFlag} yarn test:pw:local ${relativeFileDirname}/${fileBasename} ${input:pwArgs}",
"problemMatcher": [],
"label": "test: playwright",
"label": "pw: local",
"detail": "run visual components tests for current file",
"presentation": {
"reveal": "always",
"panel": "dedicated",
"focus": true,
},
"icon": {
"color": "terminal.ansiBlue",
"id": "beaker"
},
"runOptions": {
"instanceLimit": 1
},
},
{
"type": "shell",
"command": "yarn test:pw:docker ${relativeFileDirname}/${fileBasename} ${input:pwArgs}",
"problemMatcher": [],
"label": "pw: docker",
"detail": "run visual components tests for current file",
"presentation": {
"reveal": "always",
"panel": "dedicated",
"focus": true,
},
"icon": {
"color": "terminal.ansiBlue",
"id": "beaker"
},
"runOptions": {
"instanceLimit": 1
},
},
{
"type": "shell",
"command": "yarn test:pw:docker ${input:pwArgs}",
"problemMatcher": [],
"label": "pw: docker all",
"detail": "run visual components tests",
"presentation": {
"reveal": "silent",
"panel": "new",
"reveal": "always",
"panel": "dedicated",
"focus": true,
},
"icon": {
"color": "terminal.ansiBlue",
"id": "beaker"
},
"runOptions": {
"instanceLimit": 1
}
},
// JEST TESTS
{
"type": "npm",
"script": "test:jest",
"problemMatcher": [],
"label": "jest",
"detail": "run jest tests",
"presentation": {
"reveal": "always",
"panel": "dedicated",
"focus": true,
},
"icon": {
"color": "terminal.ansiBlue",
"id": "beaker"
},
"runOptions": {
"instanceLimit": 1
}
},
{
"type": "npm",
"script": "test:jest:watch",
"problemMatcher": [],
"label": "jest: watch all",
"detail": "run jest tests in watch mode",
"presentation": {
"reveal": "always",
"panel": "dedicated",
"close": true,
"revealProblems": "onProblem",
"focus": true,
},
"icon": {
"color": "terminal.ansiBlue",
......@@ -102,6 +202,26 @@
"instanceLimit": 1
}
},
{
"type": "shell",
"command": "yarn test:jest ${relativeFileDirname}/${fileBasename} --watch",
"problemMatcher": [],
"label": "jest: watch",
"detail": "run jest tests in watch mode for current file",
"presentation": {
"reveal": "always",
"panel": "dedicated",
"focus": true,
},
"icon": {
"color": "terminal.ansiBlue",
"id": "beaker"
},
"runOptions": {
"instanceLimit": 1
},
},
{
"type": "npm",
"script": "build:docker",
......@@ -110,9 +230,10 @@
"detail": "build docker image",
"presentation": {
"reveal": "always",
"panel": "new",
"panel": "dedicated",
"close": true,
"revealProblems": "onProblem",
"focus": true,
},
"icon": {
"color": "terminal.ansiRed",
......@@ -130,7 +251,7 @@
"detail": "run docker container for POA network",
"presentation": {
"reveal": "silent",
"panel": "new",
"panel": "dedicated",
"close": true,
"revealProblems": "onProblem",
},
......@@ -150,7 +271,7 @@
"detail": "format svg files with svgo",
"presentation": {
"reveal": "silent",
"panel": "new",
"panel": "dedicated",
"close": true,
"revealProblems": "onProblem",
},
......@@ -162,5 +283,29 @@
"instanceLimit": 1
}
},
]
],
"inputs": [
{
"type": "pickString",
"id": "pwDebugFlag",
"description": "What debug flag you want to use?",
"options": [
"",
"PWDEBUG=1",
"DEBUG=pw:browser,pw:api",
"DEBUG=*",
],
"default": ""
},
{
"type": "pickString",
"id": "pwArgs",
"description": "What args you want to pass?",
"options": [
"",
"--update-snapshots",
],
"default": ""
},
],
}
\ No newline at end of file
......@@ -30,7 +30,7 @@ For local development please follow next steps:
## Components visual testing
We use [playwright experimental components testing](https://playwright.dev/docs/test-components) for visual (screenshots) CI check. Test renders a single component in headless browser in docker, generates screenshots and then compares this screenshot with a reference one.
To perform testing locally you need to install docker and run `yarn test-docker`
To perform testing locally you need to install docker and run `yarn test:pw:docker`
## Environment variables
......@@ -52,6 +52,7 @@ The app instance could be customized by passing following variables to NodeJS en
| NEXT_PUBLIC_NETWORK_TOKEN_ADDRESS | `string` | Address of network's native token | `0x029a799563238d0e75e20be2f4bda0ea68d00172` |
| NEXT_PUBLIC_NETWORK_ASSETS_PATHNAME | `string` *(optional)* | Network name for constructing url of token logos according to template `https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/${assetsNamePath}/assets/${tokenAddress}/logo.png`. It should match network name in TrustWallet assets repo, see the full list [here](https://github.com/trustwallet/assets/tree/master/blockchains) | `ethereum` |
| NEXT_PUBLIC_NETWORK_LOGO | `string` *(optional)* | Network logo; if not provided, will fallback to logo predefined in the project; if the project doesn't have logo for such network then the common placeholder will be shown; *Note* that logo height should be 20px and width less than 120px | `https://www.fillmurray.com/240/40` |
| NEXT_PUBLIC_NETWORK_SMALL_LOGO | `string` *(optional)* | Small version of network logo; if not provided, will fallback to logo predefined in the project; if the project doesn't have logo for such network then the common placeholder will be shown; *Note* that logo should have square format (e.g 60px by 60px) | `https://www.fillmurray.com/60/60` |
| NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED | `boolean` *(optional)* | Set to true if network has account feature | `true` |
### UI configuration
......@@ -64,13 +65,19 @@ The app instance could be customized by passing following variables to NodeJS en
| NEXT_PUBLIC_FOOTER_TWITTER_LINK | `string` *(optional)* | Link to Twitter in the footer | `https://www.twitter.com/blockscoutcom` |
| NEXT_PUBLIC_FOOTER_TELEGRAM_LINK | `string` *(optional)* | Link to Telegram in the footer | `https://t.me/poa_network` |
| NEXT_PUBLIC_FOOTER_STAKING_LINK | `string` *(optional)* | Link to staking dashboard in the footer | `https://duneanalytics.com/maxaleks/xdai-staking` |
| NEXT_PUBLIC_MARKETPLACE_APP_LIST | `Array<MarketplaceApp>` where `MarketplaceApp` can have following [properties](#marketplace-app-configuration-properties) | List of apps that will be shown on the marketplace page | `[{'author': 'Bob', 'id': 'app', 'title': 'The App', 'logo': 'https://foo.app/icon.png', 'categories': ['security'], 'shortDescription': 'Awesome app', 'site': 'https://foo.app', 'description': 'The best app', 'url': 'https://foo.app/launch'}]` |
| NEXT_PUBLIC_MARKETPLACE_APP_LIST | `Array<MarketplaceApp>` where `MarketplaceApp` can have following [properties](#marketplace-app-configuration-properties) | List of apps that will be shown on the marketplace page | `[{'author': 'Bob', 'id': 'app', 'external': true, 'title': 'The App', 'logo': 'https://foo.app/icon.png', 'categories': ['security'], 'shortDescription': 'Awesome app', 'site': 'https://foo.app', 'description': 'The best app', 'url': 'https://foo.app/launch'}]` |
| NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM | `string` | Link to form where authors can submit their dapps to the marketplace | `https://airtable.com/shrqUAcjgGJ4jU88C` |
| NEXT_PUBLIC_NETWORK_EXPLORERS | `Array<NetworkExplorer>` where `NetworkExplorer` can have following [properties](#network-explorer-configuration-properties) | Used to build up links to transactions, blocks, addresses in other chain explorers. | `[{'title':'Anyblock','baseUrl':'https://explorer.anyblock.tools','paths':{'tx':'/ethereum/poa/core/tx'}}]` |
| NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE | `validation` or `mining` *(optional)* | Verification type in the network | `mining` |
| NEXT_PUBLIC_AUTH_URL | `string` *(optional)* | Account auth base url; the login path (`/auth/auth0`) will be added to it at execution time | `https://blockscout.com` |
| NEXT_PUBLIC_LOGOUT_URL | `string` *(optional)* | Account logout url | `https://blockscoutcom.us.auth0.com/v2/logout` |
| NEXT_PUBLIC_LOGOUT_RETURN_URL | `string` *(optional)* | Account logout return url | `https://blockscout.com/poa/core/auth/logout` |
| NEXT_PUBLIC_HOMEPAGE_CHARTS | `Array<'daily_txs' \| 'coin_price' \| 'market_cup'>` *(optional)* | List of charts displayed on the home page | `['daily_txs','coin_price','market_cup']` |
| NEXT_PUBLIC_HOMEPAGE_PLATE_GRADIENT | `string` *(optional)* | Gradient value for hero plate on the homepage | `radial-gradient(at 15% 86%, hsla(350,65%,70%,1) 0px, transparent 50%), radial-gradient(at 72% 57%, hsla(14,95%,76%,1) 0px, transparent 50%)` |
| NEXT_PUBLIC_HOMEPAGE_SHOW_GAS_TRACKER | `boolean` *(optional)* | Set to false if network doesn't have gas tracker | `true` |
| NEXT_PUBLIC_HOMEPAGE_SHOW_AVG_BLOCK_TIME | `boolean` *(optional)* | Set to false if average block time is useless for the network | `true` |
| NEXT_PUBLIC_DOMAIN_WITH_AD | `string` *(optional)* | The domain on which we display ads | `blockscout.com` |
| NEXT_PUBLIC_AD_ADBUTLER_ON | `boolean` *(optional)* | Set to true to show Adbutler banner instead of Coinzilla banner | `false` |
### App configuration
| Variable | Type | Description | Default value
......@@ -87,6 +94,7 @@ The app instance could be customized by passing following variables to NodeJS en
| --- | --- | --- | --- |
| NEXT_PUBLIC_API_HOST | `string` *(optional)* | By default the API endpoint base URL will be set as `https://blockscout.com`. If it is not the case, pass the API host in this variable | `my-host.com` |
| NEXT_PUBLIC_API_BASE_PATH | `string` *(optional)* | Base path for API endpoint url | `/poa/core` |
| NEXT_PUBLIC_STATS_API_HOST | `string` *(optional)* | Pass the Stats API host in this variable | `https://my-host.com` |
### Featured network configuration properties
......@@ -122,6 +130,7 @@ The app instance could be customized by passing following variables to NodeJS en
| Property | Type | Description | Example value
| --- | --- | --- | --- |
| id | `string` | Used as slug for the app. Must be unique in the app list. | `'app'` |
| external | `boolean` | If true means that the application opens in a new window, but not in an iframe. | `true` |
| title | `string` | Displayed title of the app. | `'The App'` |
| logo | `string` | URL to logo file. Should be at least 144x144. | `'https://foo.app/icon.png'` |
| shortDescription | `string` | Displayed only in the app list. | `'Awesome app'` |
......
/// <reference types="next-react-svg" />
/* eslint-disable no-restricted-properties */
import type { AppItemOverview } from 'types/client/apps';
import type { FeaturedNetwork, NetworkExplorer, PreDefinedNetwork } from 'types/networks';
import type { ChainIndicatorId } from 'ui/home/indicators/types';
const getEnvValue = (env: string | undefined) => env?.replaceAll('\'', '"');
const parseEnvJson = <DataType>(env: string | undefined): DataType | null => {
......@@ -10,9 +11,9 @@ const parseEnvJson = <DataType>(env: string | undefined): DataType | null => {
return null;
}
};
const stripTrailingSlash = (str: string) => str.at(-1) === '/' ? str.slice(0, -1) : str;
const stripTrailingSlash = (str: string) => str[str.length - 1] === '/' ? str.slice(0, -1) : str;
const env = process.env.VERCEL_ENV || process.env.NODE_ENV;
const env = process.env.NODE_ENV;
const isDev = env === 'development';
const appPort = getEnvValue(process.env.NEXT_PUBLIC_APP_PORT);
......@@ -21,9 +22,10 @@ const appHost = getEnvValue(process.env.NEXT_PUBLIC_APP_HOST);
const baseUrl = [
appSchema || 'https',
'://',
process.env.NEXT_PUBLIC_VERCEL_URL || appHost,
appHost,
appPort && ':' + appPort,
].filter(Boolean).join('');
const authUrl = getEnvValue(process.env.NEXT_PUBLIC_AUTH_URL) || baseUrl;
const apiHost = getEnvValue(process.env.NEXT_PUBLIC_API_HOST);
const logoutUrl = (() => {
......@@ -52,6 +54,7 @@ const config = Object.freeze({
network: {
type: getEnvValue(process.env.NEXT_PUBLIC_NETWORK_TYPE) as PreDefinedNetwork | undefined,
logo: getEnvValue(process.env.NEXT_PUBLIC_NETWORK_LOGO),
smallLogo: getEnvValue(process.env.NEXT_PUBLIC_NETWORK_SMALL_LOGO),
name: getEnvValue(process.env.NEXT_PUBLIC_NETWORK_NAME),
id: getEnvValue(process.env.NEXT_PUBLIC_NETWORK_ID),
shortName: getEnvValue(process.env.NEXT_PUBLIC_NETWORK_SHORT_NAME),
......@@ -59,9 +62,9 @@ const config = Object.freeze({
name: getEnvValue(process.env.NEXT_PUBLIC_NETWORK_CURRENCY_NAME),
symbol: getEnvValue(process.env.NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL),
decimals: Number(getEnvValue(process.env.NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS)) || DEFAULT_CURRENCY_DECIMALS,
address: getEnvValue(process.env.NEXT_PUBLIC_NETWORK_TOKEN_ADDRESS),
},
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',
},
......@@ -80,12 +83,27 @@ const config = Object.freeze({
host: appHost,
port: appPort,
baseUrl,
authUrl,
logoutUrl,
ad: {
domainWithAd: getEnvValue(process.env.NEXT_PUBLIC_AD_DOMAIN_WITH_AD) || 'blockscout.com',
adButlerOn: getEnvValue(process.env.NEXT_PUBLIC_AD_ADBUTLER_ON) === 'true',
},
api: {
endpoint: apiHost ? `https://${ apiHost }` : 'https://blockscout.com',
socket: apiHost ? `wss://${ apiHost }` : 'wss://blockscout.com',
basePath: stripTrailingSlash(getEnvValue(process.env.NEXT_PUBLIC_API_BASE_PATH) || ''),
},
statsApi: {
endpoint: getEnvValue(process.env.NEXT_PUBLIC_STATS_API_HOST),
},
homepage: {
charts: parseEnvJson<Array<ChainIndicatorId>>(getEnvValue(process.env.NEXT_PUBLIC_HOMEPAGE_CHARTS)) || [],
plateGradient: getEnvValue(process.env.NEXT_PUBLIC_HOMEPAGE_PLATE_GRADIENT) ||
'radial-gradient(103.03% 103.03% at 0% 0%, rgba(183, 148, 244, 0.8) 0%, rgba(0, 163, 196, 0.8) 100%)',
showGasTracker: getEnvValue(process.env.NEXT_PUBLIC_HOMEPAGE_SHOW_GAS_TRACKER) === 'false' ? false : true,
showAvgBlockTime: getEnvValue(process.env.NEXT_PUBLIC_HOMEPAGE_SHOW_AVG_BLOCK_TIME) === 'false' ? false : true,
},
});
export default config;
......@@ -12,3 +12,4 @@ NEXT_PUBLIC_FOOTER_TWITTER_LINK=https://www.twitter.com/blockscoutcom
# api config
NEXT_PUBLIC_API_HOST=blockscout.com
NEXT_PUBLIC_STATS_API_HOST=https://stats-test.aws-k8s.blockscout.com
This diff is collapsed.
# app config
NEXT_PUBLIC_APP_HOST=blockscout.com
NEXT_PUBLIC_APP_INSTANCE=jest
NEXT_PUBLIC_APP_ENV=testing
# ui config
NEXT_PUBLIC_BLOCKSCOUT_VERSION=v4.1.7-beta
NEXT_PUBLIC_FOOTER_GITHUB_LINK=https://github.com/blockscout/blockscout
NEXT_PUBLIC_FOOTER_TWITTER_LINK=https://www.twitter.com/blockscoutcom
# api config
NEXT_PUBLIC_API_HOST=blockscout.com
NEXT_PUBLIC_STATS_API_HOST=https://stats-test.aws-k8s.blockscout.com
This diff is collapsed.
# app config
NEXT_PUBLIC_APP_PROTOCOL=http
NEXT_PUBLIC_APP_HOST=localhost
NEXT_PUBLIC_APP_PORT=3100
NEXT_PUBLIC_APP_INSTANCE=pw
NEXT_PUBLIC_APP_ENV=testing
# ui config
NEXT_PUBLIC_BLOCKSCOUT_VERSION=v4.1.7-beta
NEXT_PUBLIC_FOOTER_GITHUB_LINK=https://github.com/blockscout/blockscout
NEXT_PUBLIC_FOOTER_TWITTER_LINK=https://www.twitter.com/blockscoutcom
NEXT_PUBLIC_HOMEPAGE_SHOW_AVG_BLOCK_TIME=true
NEXT_PUBLIC_HOMEPAGE_SHOW_GAS_TRACKER=true
# api config
NEXT_PUBLIC_API_HOST=blockscout.com
NEXT_PUBLIC_STATS_API_HOST=https://stats-test.aws-k8s.blockscout.com
import dotenv from 'dotenv';
export default async function globalSetup() {
dotenv.config({ path: './configs/envs/.env.jest' });
dotenv.config({ path: './configs/envs/.env.poa_core' });
}
const PATHS = require('../../lib/link/paths');
const PATHS = require('../../lib/link/paths.json');
const oldUrls = [
{
......
import type { NextjsOptions } from '@sentry/nextjs/types/utils/nextjsOptions';
const config: NextjsOptions = {
environment: process.env.VERCEL_ENV || process.env.NODE_ENV,
environment: process.env.NODE_ENV,
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,
// We recommend adjusting this value in production, or using tracesSampler
// for finer control
tracesSampleRate: 1.0,
......
......@@ -2,9 +2,9 @@ import type * as Sentry from '@sentry/react';
import { BrowserTracing } from '@sentry/tracing';
export const config: Sentry.BrowserOptions = {
environment: process.env.VERCEL_ENV || process.env.NEXT_PUBLIC_APP_ENV || process.env.NODE_ENV,
environment: process.env.NEXT_PUBLIC_APP_ENV || process.env.NODE_ENV,
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,
integrations: [ new BrowserTracing() ],
// We recommend adjusting this value in production, or using tracesSampler
// for finer control
......
This diff is collapsed.
This diff is collapsed.
......@@ -18,9 +18,9 @@ function replace_envs {
# split
configName="$(cut -d'=' -f1 <<<"$line")"
configValue="$(cut -d'=' -f2 <<<"$line")"
configValue="$(cut -d'=' -f2- <<<"$line")"
# get system env
envValue=$(env | grep "^$configName=" | grep -oe '[^=]*$');
envValue=$(env | grep "^$configName=" | sed "s/^$configName=//g");
# if config found
if [ -n "$configValue" ]; then
......
This diff is collapsed.
This diff is collapsed.
......@@ -27,7 +27,7 @@ blockscout:
ACCOUNT_SENDGRID_TEMPLATE:
_default: ENC[AES256_GCM,data:teIknLFnYfGDD7drMUTvcoXBxR9rOjc8L8nOVtMYVOuzXQ==,iv:LoBlBjrWDf5/LsmBo58510evXBTxiIW1YekeLmB+Mow=,tag:dZ/YdphM5MxEygeFYlT0Nw==,type:str]
ACCOUNT_PUBLIC_TAGS_AIRTABLE_URL:
_default: ENC[AES256_GCM,data:Y5Un1IwYz6luANlNDtbz6V9cdQ96sZFPbtS5MzDBPDFP8pBlQouhYeDBvB6irf2KiC3gYPHLea5lSWA=,iv:QH0A4zi/iWYka9n0SD2v+NXijNPyg0MPrGVawDzf5fU=,tag:wjUYXk943EXXF+++oNpLPw==,type:str]
_default: ENC[AES256_GCM,data:nKWi8/Fh2TpPuxx81CnKhGdVinL+paZ1oVmevCqVNfRFl7OK3mfOlGjzKINUHBf2+hvDTv0xqukwXUkOAkw=,iv:iWFeb7Px7RwKbhWWiwdi8SO8OqlEzfdebWLZXprNU64=,tag:CYLWnyAqTQhZxZJ0LGP7DA==,type:str]
ACCOUNT_PUBLIC_TAGS_AIRTABLE_API_KEY:
_default: ENC[AES256_GCM,data:WBxTPe+pQ2VzSPRmL3mzek0=,iv:0wYDv0BRmXrmz9EJg0cBclHpsPEQukO7tCnPAUlCKrs=,tag:3Zcyf/8kLU6ohUQ+97eONg==,type:str]
ACCOUNT_CLOAK_KEY:
......@@ -60,12 +60,12 @@ geth:
files:
list:
genesis.json: ENC[AES256_GCM,data:nSFncQhigaTtb7YSeXiUvua2LTOGlluVDywxQR5NnhvIBZCZniIxHsEhXKF9rBYceOMfBPKY5MTLIyxYisZeaJHL71A/uXQerDEKkECpzhwUSTdNEA9gUEqmpUjf7ChAWjybWw6NCM2jVhG3bnZwTwmuzeCSek85q4J503j9HVEkGXAU52A2+yoov41c+xoCEOj7W6Ka/f7yJYkyppoeS+4AIxBFcJ5hI0pIf1VqDcSrJf68JO1pPzcmp9eK3CK2CIpD8uxV6CseBFEqlbllmgAVwSUJ4i8XhYEO2hMyULm/LkW52U31/tq89yctSkp5TB62sZEHyDxXcq6jFSqd4840o9VedyX4YuEVtLBlXdzerc4Q2tHORQv5u2BLW7ktA/5vj8d9XnblQBDKrqxtUEgvSj9T/Xm4zyKjIhJBAAwUd5rF4j8dNbUGpsBm3G/Lx1+DqrS8cyxXgumtmfsklEVTWBra2dwt/tlTpF+utOwT7KtqXo2dVNvL6tUWrWGQqCt+fgeRKw8NuHWgU9Nm6baQ0yhWIM3XaCEuuJqHLdelYgQiKVEaTvlw7my7XwQsunJCQ98Q2eO61v7WFsg1m5s3Wyts8R2EnMWrnDlGro//eUHFlxDiIQGXmWgnz597lrLTXiuZKNI4pvPsAggBQxIeUcYzICy5uLRPWAiDpfshYt/fRQhIxmrVaJCufJrq4IcUdJYKjAcpnGHqZMB6quZiUPCPYg5Lvz7w1zoIVLn4ANWVuh7J1husNH13SwrDUXEm+k/WYCkDZm5B/K9IzkihY8nNXbvxHEXUClpb943Xw8hpvZZj0BfP/ye7ywUzHGlo3OmFAdE7Fbjc9eELxgYirONoaHy9GkjWPjinTVVlvitakqzx4dGJ+U2adSB01WAj63rn4ZidaC/eUpcPe7E2OeqmpkLc/kLM9g3gEe/MCiqsXXUYtm0Idpbrf+rRTAotoVOi3fKr1inlJY6n+9UzKAxNkTIkiGUQ1E9g0s3Kn4Z83apuy5+Us8gA9xNfWNGcOO105ZUYKyow4Xb5ggV8t+UTXPOZQp97Qz/6q0VC2kY9bx991SrJLoaK+ChLQ0/t/LcLjZxKRPDU2YGOfjrPDO3iscDkZgX/8IvH56jxUXAYmNrxOgbb9encYIrMDYrsKTaj5kXETp2M3sTdHwk6d78PAteYCvgQb44gu5JtknIq2LLXb/2Ue45O4HnrB+AsICPb+oGAxIqFkHvWuOIRZLqap8wbazszBlqsrX8ABegjvReXiG0rX1VeQIXNcG3/tKPHNKrDlFJykaiC1w==,iv:pyrxyy1bbv1UiGZ2y1jer9UqOwVkZGb4GwawJoCGJuM=,tag:aVt5dASuZglsUs9Yydzjaw==,type:str]
init.sh: ENC[AES256_GCM,data:2qHHfwC63yJlxqaqmQQ69CxtcYZiEgdLdr/Hcxk8eKEou16bXL1NxpxfW8MWsfZyB4S3QNj05V6WSpZAw9ITG/GnXOKocZqKbZPmmuwQpLoekC37EsdUETb+tFvIunh9xRmgst4/ByXEyjC9pZk8SN0Qr9cGQl3VMzeGSkRndaE+qnlFxHRO5ELbDyaLMcyLWaQncescCSvIDpyOPNN7NwS3PYW+Nq3rvh4y+2FBoHUPUMIq46q94+ROnFk5TQsUI5/sVdw/5fuJklcTeqxuo4pPFLAgtkXkab0k2Og0eMI4It1xIrwpfJnxKwbjd0zljKo8tNzjpPZKHMfYchPX6uGH9fnwvXcH7nKUUdtSWrWa8R9a5AyO7SJ0hfzewMzvOWspihJW3JXFTvfesoG/cMMBDg3icsD/0HyxVBQP2riSZsou3hzDFAOVoNLAQTNRVXckSxk9zbx0aOy3DlWefJhIFj2ZmF4sliwxMe+Oy/So6PfiRLsP9v6/OTv6sriExtHdAx6xeA+dbYCO9ruW+5gNB2UUi3DQvpvejaxysJygaHvYCVDGAUKCxDAvQPrUQ/r5+UaE+GmqflDzhd9lhAptL/xL8tW8R+v9RZdMO/G9kxWMhKWiHH6QqgdYGOvanwCxAGtSvKhqf6fiWnNMr6wKG4qAFk6cMNUsLHtnC1qLYhZAfBa1/aK5cG0IbygZKurC+9G8x4SmOqUpW8h23XRsNd8peCRuSZ2uSGqss/1XvngfW0avzGiXMHW9,iv:ldcn5mGmfGmSmq30mUen6diWK6EAEnYF6NQTXXwY8yA=,tag:kSuu7AGzzmSEM5AzjIw5RQ==,type:str]
init.sh: ENC[AES256_GCM,data:WtSrMR0dcOWU8aUtUqaRxHoG7k+821SrsBN/AgdQsDAwGJ21gP2iuT1hZn4t4qocv1qWnwtrrYwgDIX3MZUL5Ee0/SZ8oNEI49yaseA07OsPvs2AekHpOJBp4PJpPqN7C41Sb+pw9l3NUWnAG2LlCFwH8pfuszpKp/4sM5Vjn3KHSnEZ3VlddTfu+pa/Q65p6230ONemq0Fvp8r5ULznvcphNA63c4FK+xREVoPFXQQRvjnFvW4RdgUJriBxUc6r0LR6sWsfGZK+GeYID3N7J8J3FjSXl/yPTf5PjwtDsMJ9EjEiD1M9Ea5ssd32+Bx/XW2HR6oqApF2q9W2g55zC5o+72QREm1nDOt+/oB2uL5DUHB7FFgRwTHLTnjEF4LrmHD08kgzUghT1jks+XltIUt4MvPxSkhIN8QaNiLLkeJc2dxtcw2SZCTxrQmTgksqjf93gYZqbF7hrSwhyI9/yJOXBfLDCj/Ymzj7TaJDQgmabEgOAqtx+jTuAmJ3zKoLPw4+/9NX/IZi16x2gXg2zCWOlp6fH+4RRI6RHxcCRu0JQXO3q1TGVGZ8JC665oxcTDlXH7EFOiwo9e3r283WUrOHQ5CeOQTDV7SVVXmRwIUguOvH1BeU7penFeh+WirwquLykT5KQCt7zfbmmHpWSJ9uKVyX8d/LBydKCc1OAMMnp1zbE0cFX+4/Gyhlnn5JU6hvjOHohFvXn0b5hySY8seOvnOQCamQIOjDr85bHJGRRJRYJfMjghg3Sg==,iv:Q/dEbbeUwhkkTHZ8Ic5FHTZ4r9bCU1rDg6J9MyxzDiQ=,tag:XEPJ6r9+sQeGsh4VftLVVw==,type:str]
password.txt: ""
frontend:
environment:
NEXT_PUBLIC_NETWORK_TOKEN_ADDRESS:
_default: ENC[AES256_GCM,data:1SAbzZhCs/vzdftIX0WVLtImH27NJ6SwENee4uTu2p+ZyUso3nQCLUUm,iv:apyLxt2dQ5RN33ra1Q1sAy2cyplG9FSryksQru2ghlA=,tag:PVcCNt0bz1TfQewUebV5LA==,type:str]
_default: ENC[AES256_GCM,data:yShwsa6ajoFXg/6QSgEARkZRVVrwrdsR69NSmyvBH2O5EUQ0OvsWpW64,iv:K/HT6C9pYCK63LNyF3HERFc79vDS4cB0H4pINIlNhh0=,tag:X0HqeAP01diTvDOwoEP6lw==,type:str]
NEXT_PUBLIC_SENTRY_DSN:
_default: ENC[AES256_GCM,data:n/H2AH2n9ovn265iFbbrqeOOWS3s7FXgDv5FXJ2Dz133GuhTaqIU5psWjTraZ/Vh+dVM8M9zBLFfUanCWOz7cerq/QUoSVZjKvIvcyp6F072DGU=,iv:Co/pSR+U0vfkmWR+LDpxcQKDJl0WNbaEZihpzrRJcbc=,tag:HUiCX8WGJXWCDB59Y6iIbw==,type:str]
SENTRY_CSP_REPORT_URI:
......@@ -78,8 +78,8 @@ sops:
azure_kv: []
hc_vault: []
age: []
lastmodified: "2022-11-01T14:58:11Z"
mac: ENC[AES256_GCM,data:3AK4GRnUnAcQrdJ9JrdhSFqMYmYhE2RGiP+NPvO+mqBGDH26pRjN3lkNhHi/uObEQWiQZJLzLEOhSPl0/oDVYRTSGEeEiIlViEm/S5PuD57uFx6ogS9Iz88G/3hnc3HpTAIg2+NVwE7wF1/NK75WlivB1pUGk7OrazhZ+Fhyn5k=,iv:94YFEq/dmS07CqsKFZ2NAKMj3LqqyUB4+2XtHI3UiLg=,tag:4uti1G9eRoL2AwF0n7avdQ==,type:str]
lastmodified: "2022-11-28T16:58:46Z"
mac: ENC[AES256_GCM,data:QJvVfWWWVDk5mI66T9J8EnEyVwmJoGEsWO9Pr8vK7jyC3rhAYD2WdKYfpkbwwMKrJzcMBe7UeaOeEY6aApuMNdobeEjsJAvstXCOBzMe5H9XtAFiAY+oxf8r4ELNvQP/gIBZSja+ehSbXBcaP4DkLn4FboaBhkoE8A37W2R6/QA=,iv:FnIC6iGLEZNwRSrbF81vF6eQuyq0yQHNPRTPrx3FB+8=,tag:LRnZCwYkCh4o8lDUcG2m9A==,type:str]
pgp:
- created_at: "2022-09-14T13:42:28Z"
enc: |
......
This diff is collapsed.
import type { MetaMaskInpageProvider } from '@metamask/providers';
declare global {
interface Window {
ethereum: MetaMaskInpageProvider;
}
}
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 29 28">
<g fill="currentColor" clip-path="url(#clock-light_svg__a)">
<path d="M14.75 25.375a11.375 11.375 0 1 1 0-22.75 11.375 11.375 0 0 1 0 22.75Zm0-21a9.625 9.625 0 1 0 0 19.25 9.625 9.625 0 0 0 0-19.25Z"/>
<path d="M19.563 19.688a.875.875 0 0 1-.622-.254L14.13 14.62a.874.874 0 0 1-.254-.621V7a.875.875 0 0 1 1.75 0v6.641l4.559 4.55a.874.874 0 0 1-.622 1.497Z"/>
</g>
<defs>
<clipPath id="clock-light_svg__a">
<path fill="#fff" d="M.75 0h28v28h-28z"/>
</clipPath>
</defs>
</svg>
<svg viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="m6 5.293 2.475-2.475.707.707L6.707 6l2.475 2.475-.707.707L6 6.707 3.525 9.182l-.707-.707L5.293 6 2.818 3.525l.707-.707L6 5.293Z" fill="currentColor"/>
</svg>
<svg viewBox="0 0 201 100" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M126.092 85.414c1.267-2.895 1.9-5.993 1.9-9.295V23.881c0-3.302-.633-6.4-1.9-9.295-1.221-2.895-2.917-5.427-5.088-7.598-2.171-2.171-4.704-3.867-7.598-5.088-2.895-1.267-5.993-1.9-9.295-1.9h-4.138C96.67 0 93.55.633 90.61 1.9c-2.895 1.22-5.428 2.917-7.599 5.088-2.17 2.17-3.89 4.703-5.156 7.598-1.221 2.895-1.832 5.993-1.832 9.295v52.238c0 3.302.611 6.4 1.832 9.295 1.267 2.894 2.985 5.427 5.156 7.598 2.171 2.171 4.704 3.89 7.599 5.156C93.55 99.39 96.67 100 99.973 100h4.138c3.302 0 6.4-.61 9.295-1.832 2.894-1.266 5.427-2.985 7.598-5.156 2.171-2.17 3.867-4.704 5.088-7.598Zm-8.073-67.571c.814 1.854 1.221 3.867 1.221 6.038v52.238c0 2.171-.407 4.207-1.221 6.106a16.717 16.717 0 0 1-3.324 4.953c-1.402 1.402-3.053 2.51-4.953 3.324-1.854.814-3.867 1.221-6.038 1.221h-3.324c-2.171 0-4.206-.407-6.106-1.221a16.718 16.718 0 0 1-4.952-3.324 16.718 16.718 0 0 1-3.325-4.953c-.814-1.9-1.22-3.935-1.22-6.106V23.881c0-2.171.406-4.184 1.22-6.038.814-1.9 1.922-3.55 3.325-4.953a16.718 16.718 0 0 1 4.952-3.324c1.9-.814 3.935-1.221 6.106-1.221h3.324c2.171 0 4.184.407 6.038 1.22 1.9.815 3.551 1.923 4.953 3.325 1.402 1.402 2.51 3.053 3.324 4.953Zm-70.19 63.975v16.825a8.752 8.752 0 0 1-8.752-8.752v-8.073H3.275a3.275 3.275 0 0 1-2.85-4.89L42.38 2.836a2.914 2.914 0 0 1 5.45 1.436v69.203h8.345a8.345 8.345 0 0 1-8.345 8.344Zm-8.752-56.513L12.28 73.474h26.797V25.305Zm152.711 56.513v16.825a8.752 8.752 0 0 1-8.752-8.752v-8.073h-35.802a3.275 3.275 0 0 1-2.85-4.89l41.954-74.093a2.914 2.914 0 0 1 5.45 1.436v69.203h8.345a8.345 8.345 0 0 1-8.345 8.344Zm-8.752-56.513-26.798 48.169h26.798V25.305Z" fill="currentColor"/>
</svg>
<svg viewBox="0 0 197 101" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M124.363 76.7c0 3.326-.638 6.448-1.914 9.364-1.23 2.917-2.939 5.47-5.127 7.657-2.187 2.187-4.739 3.919-7.656 5.195-2.917 1.23-6.038 1.846-9.365 1.846h-4.17c-3.327 0-6.472-.616-9.434-1.846-2.916-1.276-5.469-3.008-7.656-5.195-2.188-2.188-3.92-4.74-5.195-7.657C72.616 83.148 72 80.026 72 76.7V24.063c0-3.327.615-6.45 1.846-9.366 1.276-2.916 3.007-5.468 5.195-7.656 2.188-2.187 4.74-3.896 7.656-5.127C89.66.638 92.804 0 96.131 0h4.17c3.327 0 6.448.638 9.365 1.914 2.917 1.23 5.469 2.94 7.656 5.127 2.188 2.188 3.897 4.74 5.127 7.656 1.276 2.917 1.914 6.039 1.914 9.366v52.636Zm-8.818-52.638c0-2.187-.41-4.215-1.231-6.084-.82-1.914-1.936-3.577-3.349-4.99-1.413-1.412-3.076-2.529-4.99-3.35-1.869-.82-3.897-1.23-6.084-1.23h-3.35c-2.188 0-4.238.41-6.152 1.23a16.848 16.848 0 0 0-4.99 3.35c-1.413 1.413-2.53 3.076-3.35 4.99-.82 1.869-1.23 3.897-1.23 6.084V76.7c0 2.188.41 4.239 1.23 6.153a16.847 16.847 0 0 0 3.35 4.99 16.847 16.847 0 0 0 4.99 3.35c1.914.82 3.965 1.23 6.152 1.23h3.35c2.187 0 4.215-.41 6.084-1.23 1.914-.82 3.577-1.937 4.99-3.35a16.834 16.834 0 0 0 3.349-4.99c.821-1.915 1.231-3.965 1.231-6.153V24.063ZM52.363 76.7c0 3.327-.638 6.449-1.914 9.365-1.23 2.917-2.94 5.47-5.127 7.657-2.187 2.187-4.74 3.919-7.656 5.195-2.917 1.23-6.038 1.846-9.365 1.846h-4.17c-3.327 0-6.471-.616-9.434-1.846-2.916-1.276-5.468-3.008-7.656-5.195-2.187-2.188-3.92-4.74-5.195-7.657C.616 83.148 0 80.026 0 76.7c0-.83.673-1.504 1.504-1.504h5.81c.831 0 1.504.674 1.504 1.504 0 2.188.41 4.239 1.23 6.153a16.847 16.847 0 0 0 3.35 4.99 16.847 16.847 0 0 0 4.99 3.35c1.915.82 3.965 1.23 6.153 1.23h3.35c2.187 0 4.215-.41 6.084-1.23 1.914-.82 3.577-1.937 4.99-3.35a16.847 16.847 0 0 0 3.35-4.99c.82-1.915 1.23-3.965 1.23-6.153V57.695c0-2.187-.41-4.215-1.23-6.084-.82-1.914-1.937-3.577-3.35-4.99a15.212 15.212 0 0 0-4.99-3.418c-1.869-.82-3.897-1.23-6.084-1.23h-3.35c-2.188 0-4.238.41-6.152 1.23a16.228 16.228 0 0 0-4.99 3.418c-1.413 1.413-2.53 3.076-3.35 4.99a14.485 14.485 0 0 0-1.112 4.09c-.134 1.096-1.014 1.994-2.119 1.994h-4.61a2 2 0 0 1-1.99-2.198l4.85-48.724a6 6 0 0 1 5.97-5.406H47.51a8.408 8.408 0 0 1-8.408 8.408H13.877l-3.213 30.147c2.096-2.005 4.535-3.555 7.315-4.649 2.78-1.139 5.765-1.709 8.955-1.709H28.3c3.327 0 6.448.639 9.365 1.914 2.917 1.231 5.469 2.94 7.656 5.127 2.188 2.188 3.897 4.763 5.127 7.725 1.276 2.917 1.914 6.038 1.914 9.365V76.7Zm142.086 9.365c1.276-2.916 1.914-6.038 1.914-9.365V24.063c0-3.327-.638-6.45-1.914-9.366-1.23-2.916-2.939-5.468-5.127-7.656-2.187-2.187-4.739-3.896-7.656-5.127C178.749.638 175.628 0 172.301 0h-4.17c-3.327 0-6.471.638-9.434 1.914-2.916 1.23-5.468 2.94-7.656 5.127-2.187 2.188-3.919 4.74-5.195 7.656-1.231 2.917-1.846 6.039-1.846 9.366v52.636c0 3.327.615 6.449 1.846 9.365 1.276 2.917 3.008 5.47 5.195 7.657 2.188 2.187 4.74 3.919 7.656 5.195 2.963 1.23 6.107 1.846 9.434 1.846h4.17c3.327 0 6.448-.616 9.365-1.846 2.917-1.276 5.469-3.008 7.656-5.195 2.188-2.188 3.897-4.74 5.127-7.657Zm-8.135-68.085c.821 1.868 1.231 3.896 1.231 6.084V76.7c0 2.188-.41 4.239-1.231 6.153a16.834 16.834 0 0 1-3.349 4.99c-1.413 1.413-3.076 2.53-4.99 3.35-1.869.82-3.897 1.23-6.084 1.23h-3.35c-2.187 0-4.238-.41-6.152-1.23a16.851 16.851 0 0 1-4.991-3.35 16.853 16.853 0 0 1-3.349-4.99c-.82-1.915-1.231-3.965-1.231-6.153V24.063c0-2.188.411-4.216 1.231-6.084.82-1.915 1.937-3.578 3.349-4.99a16.852 16.852 0 0 1 4.991-3.35c1.914-.82 3.965-1.23 6.152-1.23h3.35c2.187 0 4.215.41 6.084 1.23 1.914.82 3.577 1.937 4.99 3.35 1.413 1.412 2.529 3.075 3.349 4.99Z" fill="currentColor"/>
</svg>
<svg viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M17.5 23.75h1.25v2.5H2.5v-2.5h1.25V5A1.25 1.25 0 0 1 5 3.75h11.25A1.25 1.25 0 0 1 17.5 5v10H20a2.5 2.5 0 0 1 2.5 2.5v5a1.25 1.25 0 0 0 2.5 0v-8.75h-2.5a1.25 1.25 0 0 1-1.25-1.25V8.018l-2.071-2.072 1.767-1.767 6.188 6.187a1.244 1.244 0 0 1 .366.884V22.5a3.75 3.75 0 0 1-7.5 0v-5h-2.5v6.25Zm-11.25 0H15v-7.5H6.25v7.5Zm0-17.5v7.5H15v-7.5H6.25Z" fill="currentColor"/>
</svg>
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12.2 19.4c3.972 0 7.2-3.228 7.2-7.2S16.172 5 12.2 5A7.206 7.206 0 0 0 5 12.2c0 3.972 3.228 7.2 7.2 7.2Zm-5.574-4.332 2.926-.023a10.424 10.424 0 0 0 1.614 3.333 6.28 6.28 0 0 1-4.54-3.31Zm3.67-4.842h3.646a9.853 9.853 0 0 1 .023 3.867l-3.67.023a9.544 9.544 0 0 1 0-3.89Zm1.823 7.885a9.323 9.323 0 0 1-1.591-3.066l3.193-.023a9.813 9.813 0 0 1-1.602 3.089Zm.929.302a10.24 10.24 0 0 0 1.637-3.403l3.124-.023a6.286 6.286 0 0 1-4.761 3.426ZM18.47 12.2c0 .65-.105 1.277-.279 1.858l-3.286.023c.232-1.277.22-2.578-.023-3.855h3.263a6.17 6.17 0 0 1 .325 1.974Zm-.72-2.903h-3.09a10.675 10.675 0 0 0-1.613-3.31 6.244 6.244 0 0 1 4.703 3.31Zm-4.053 0H10.54a9.593 9.593 0 0 1 1.58-3.008 9.595 9.595 0 0 1 1.58 3.008Zm-2.532-3.275a10.317 10.317 0 0 0-1.59 3.275H6.648a6.249 6.249 0 0 1 4.517-3.275Zm-1.811 4.204a10.685 10.685 0 0 0-.012 3.89l-3.1.023a6.173 6.173 0 0 1 .012-3.914h3.1Z" fill="currentColor"/>
</svg>
<svg viewBox="0 0 143 26" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.513 1a1 1 0 0 0-1-1H6.068a1 1 0 0 0-1 1v3.417a1 1 0 0 1-1 1H1.49a1 1 0 0 0-1 1V25a1 1 0 0 0 1 1h3.445a1 1 0 0 0 1-1V6.417a1 1 0 0 1 1-1h2.578a1 1 0 0 0 1-1V1Zm10.926 0a1 1 0 0 0-1-1h-3.445a1 1 0 0 0-1 1v3.417a1 1 0 0 0 1 1h2.389a1 1 0 0 1 1 1V25a1 1 0 0 0 1 1h3.444a1 1 0 0 0 1-1V6.417a1 1 0 0 0-1-1H22.44a1 1 0 0 1-1-1V1Zm-5.52 10.369a1 1 0 0 0-1-1h-3.445a1 1 0 0 0-1 1v8.524a1 1 0 0 0 1 1h3.445a1 1 0 0 0 1-1v-8.524Z"/>
<path d="M35.113 5.893h6.228c2.638 0 4.065 1.434 4.065 3.498.02.645-.15 1.28-.486 1.825a3.12 3.12 0 0 1-1.395 1.221v.067a3.548 3.548 0 0 1 1.842 1.364c.453.652.687 1.44.666 2.242 0 2.308-1.544 4.122-4.346 4.122h-6.574V5.893Zm6.27 5.827c.996 0 1.71-.761 1.71-1.92 0-1.158-.714-1.92-1.71-1.92h-3.95v3.84h3.95Zm.282 6.54c1.19 0 2.054-.94 2.054-2.306 0-1.367-.865-2.285-2.054-2.285h-4.232v4.592h4.232ZM48.93 7.599h-1.303V5.436h3.59v14.786h-2.293L48.931 7.6Zm4.409 7.324c0-3.248 2.317-5.644 5.517-5.644s5.535 2.374 5.535 5.644-2.313 5.623-5.535 5.623-5.517-2.375-5.517-5.623Zm4.911 3.495h1.236c1.406 0 2.638-1.546 2.638-3.495 0-1.948-1.236-3.52-2.638-3.52H58.25c-1.405 0-2.638 1.6-2.638 3.52s1.233 3.495 2.638 3.495Zm7.63-3.518c0-3.314 2.21-5.621 5.388-5.621 2.552 0 4.562 1.366 4.887 4.16h-2.227c-.217-1.412-1.19-2.039-2.184-2.039h-1.082c-1.384 0-2.508 1.568-2.508 3.52s1.124 3.54 2.508 3.54h1.082a2.163 2.163 0 0 0 1.494-.597c.409-.386.662-.916.711-1.487h2.227c-.308 2.755-2.335 4.189-4.942 4.189-3.17-.02-5.353-2.349-5.353-5.664Zm12.348-9.464h2.27v8.156h1.647l3.07-4.01h2.595l-3.784 4.951 3.957 5.69h-2.725l-3.308-4.704h-1.452v4.703h-2.27V5.436ZM89.02 17.029h2.185c.108.94.67 1.546 1.853 1.546h1.43c1.017 0 1.493-.538 1.493-1.255 0-.717-.39-1.142-1.32-1.28l-2.403-.32c-2.032-.246-2.94-1.5-2.94-3.136 0-2.15 1.544-3.315 4.303-3.315 2.64 0 4.24 1.098 4.324 3.54h-2.184c-.108-.92-.475-1.569-1.6-1.569h-1.3c-.973 0-1.428.538-1.428 1.232 0 .695.433 1.165 1.363 1.3l2.425.32c1.968.246 2.876 1.343 2.876 3.046 0 2.195-1.406 3.404-4.52 3.404-3.012.004-4.473-1.184-4.556-3.513ZM99.524 14.9c0-3.314 2.205-5.621 5.387-5.621 2.552 0 4.563 1.366 4.887 4.16h-2.227c-.216-1.412-1.189-2.039-2.184-2.039h-1.081c-1.387 0-2.512 1.568-2.512 3.52s1.125 3.54 2.512 3.54h1.081a2.165 2.165 0 0 0 1.495-.597 2.32 2.32 0 0 0 .711-1.487h2.227c-.309 2.755-2.336 4.189-4.943 4.189-3.166-.02-5.353-2.349-5.353-5.664Zm11.738.023c0-3.248 2.314-5.644 5.514-5.644 3.201 0 5.539 2.374 5.539 5.644s-2.314 5.623-5.539 5.623c-3.225 0-5.514-2.375-5.514-5.623Zm4.909 3.495h1.236c1.405 0 2.641-1.546 2.641-3.495 0-1.948-1.236-3.52-2.641-3.52h-1.236c-1.406 0-2.638 1.6-2.638 3.52s1.239 3.495 2.644 3.495h-.006Zm8.029-2.128V9.595h2.27v6.295c0 1.545.757 2.464 1.752 2.464h1.235c1.06 0 2.011-1.008 2.011-2.464V9.595h2.274v10.64h-2.209V18.78c-.618 1.098-1.708 1.77-3.373 1.77-2.49-.003-3.96-1.504-3.96-4.26Zm13.32 1.456v-6.08h-1.99v-2.07h1.99V6.612h2.292v2.982h2.681v2.061h-2.681v5.42c0 .695.216 1.031.951 1.031h1.817v2.128h-2.472c-1.662-.003-2.588-.899-2.588-2.49Z"/>
</svg>
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M21.004 2.61 13.2 8.432l1.443-3.434 6.36-2.386Z" fill="#E2761B"/>
<path d="m2.988 2.61 7.741 5.876-1.372-3.489-6.369-2.386Zm15.208 13.492L16.117 19.3l4.447 1.229 1.279-4.356-3.647-.07Zm-16.032.071 1.271 4.356L7.882 19.3l-2.078-3.198-3.64.071Z" fill="#E4761B"/>
<path d="m7.631 10.7-1.24 1.882 4.417.197-.157-4.765L7.63 10.7Zm8.73 0L13.3 7.959l-.101 4.82 4.408-.197-1.248-1.883Zm-8.479 8.6 2.651-1.3-2.29-1.795-.36 3.095ZM13.46 18l2.658 1.3-.368-3.095L13.459 18Z" fill="#E4761B"/>
<path d="M16.117 19.3 13.458 18l.212 1.741-.023.733 2.47-1.174Zm-8.235 0 2.47 1.174-.015-.733.196-1.74-2.65 1.299Z" fill="#D7C1B3"/>
<path d="M10.392 15.055 8.18 14.4l1.561-.717.65 1.37Zm3.208 0 .65-1.37 1.57.716-2.22.654Z" fill="#233447"/>
<path d="m7.882 19.3.377-3.198-2.455.071L7.882 19.3Zm7.859-3.198.376 3.198 2.079-3.127-2.455-.07Zm1.867-3.52-4.408.197.408 2.276.65-1.37 1.57.716 1.78-1.82Zm-9.428 1.82 1.569-.718.643 1.37.416-2.275-4.416-.197 1.788 1.82Z" fill="#CD6116"/>
<path d="m6.392 12.582 1.85 3.623L8.18 14.4l-1.788-1.82Zm9.435 1.82-.078 1.803 1.859-3.623-1.78 1.82Zm-5.02-1.623-.415 2.276.518 2.686.117-3.537-.22-1.425Zm2.393 0-.212 1.417.094 3.545.526-2.686-.408-2.276Z" fill="#E4751F"/>
<path d="m13.608 15.055-.526 2.686.377.26 2.29-1.796.078-1.804-2.22.654ZM8.18 14.4l.063 1.804L10.533 18l.377-.26-.518-2.685L8.18 14.4Z" fill="#F6851B"/>
<path d="m13.647 20.474.023-.733-.196-.173h-2.957l-.18.173.016.733-2.47-1.174.862.709 1.749 1.22h3.004l1.757-1.22.862-.709-2.47 1.174Z" fill="#C0AD9E"/>
<path d="m13.459 18-.377-.26H10.91l-.377.26-.196 1.741.18-.173h2.957l.196.173-.211-1.74Z" fill="#161616"/>
<path d="M21.333 8.81 22 5.595l-.996-2.985-7.545 5.623L16.36 10.7l4.102 1.206.91-1.064-.392-.283.628-.575-.487-.378.628-.48-.416-.316ZM2 5.595l.666 3.213-.423.315.627.48-.478.379.627.575-.392.283.902 1.064 4.102-1.206 2.902-2.465L2.988 2.61 2 5.596Z" fill="#763D16"/>
<path d="M20.462 11.904 16.361 10.7l1.247 1.883-1.86 3.623 2.448-.032h3.647l-1.38-4.269ZM7.632 10.7l-4.103 1.205-1.365 4.27h3.64l2.439.03-1.851-3.622 1.24-1.883Zm5.568 2.08.259-4.545 1.192-3.237H9.357l1.176 3.237.275 4.545.094 1.433.008 3.529h2.172l.016-3.529.102-1.433Z" fill="#F6851B"/>
</svg>
<svg viewBox="0 0 30 30" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M13.822 15.052h-6.9v2.731h3.19l-.01 3.433c-.123.132-.269.24-.43.319-.21.108-.43.197-.656.267a5.653 5.653 0 0 1-1.656.247 3.012 3.012 0 0 1-2.715-1.424 7.746 7.746 0 0 1-.9-4.064v-2.6a9.44 9.44 0 0 1 .265-2.36 5.76 5.76 0 0 1 .715-1.71c.267-.427.63-.785 1.06-1.047.4-.237.858-.36 1.323-.357a2.922 2.922 0 0 1 2.191.735c.526.615.84 1.383.895 2.191h3.627a8.472 8.472 0 0 0-.615-2.458 5.315 5.315 0 0 0-1.304-1.857 5.626 5.626 0 0 0-2.047-1.164 9.016 9.016 0 0 0-2.84-.4 6.71 6.71 0 0 0-2.774.572A6.354 6.354 0 0 0 2.016 7.77a7.927 7.927 0 0 0-1.483 2.659 10.954 10.954 0 0 0-.532 3.557v2.575a11.475 11.475 0 0 0 .51 3.557c.3.97.793 1.871 1.45 2.646a6.251 6.251 0 0 0 2.264 1.65 7.406 7.406 0 0 0 2.966.573c.765.005 1.529-.07 2.278-.221a10.497 10.497 0 0 0 1.914-.58 7.636 7.636 0 0 0 1.477-.8 5.2 5.2 0 0 0 .98-.87l-.018-7.464Zm2.66 2.783c-.01.97.143 1.935.451 2.855.281.837.73 1.61 1.317 2.269a5.985 5.985 0 0 0 2.133 1.5 7.278 7.278 0 0 0 2.88.54 7.194 7.194 0 0 0 2.86-.54 5.935 5.935 0 0 0 2.118-1.5 6.602 6.602 0 0 0 1.311-2.27c.308-.92.46-1.884.45-2.854v-.274a8.673 8.673 0 0 0-.45-2.84 6.532 6.532 0 0 0-1.317-2.27 6.078 6.078 0 0 0-2.125-1.508 7.812 7.812 0 0 0-5.74 0 6.082 6.082 0 0 0-2.12 1.508 6.516 6.516 0 0 0-1.317 2.27 8.647 8.647 0 0 0-.45 2.84v.274Zm3.681-.273a7.394 7.394 0 0 1 .174-1.625c.1-.478.284-.936.541-1.352a2.633 2.633 0 0 1 2.357-1.26c.495-.016.984.1 1.418.337.387.224.712.542.947.923.253.417.435.874.535 1.352a7.4 7.4 0 0 1 .173 1.625v.273a7.613 7.613 0 0 1-.172 1.659c-.1.478-.281.935-.537 1.352a2.622 2.622 0 0 1-2.337 1.255 2.818 2.818 0 0 1-1.424-.338 2.759 2.759 0 0 1-.96-.917 4.143 4.143 0 0 1-.541-1.352 7.6 7.6 0 0 1-.174-1.659v-.274.001Zm-1.747-10.3c.086.196.212.37.37.514.167.147.36.262.57.338.472.164.985.164 1.456 0 .21-.076.404-.19.57-.338.16-.143.285-.318.37-.514a1.594 1.594 0 0 0 0-1.274 1.562 1.562 0 0 0-.37-.52 1.7 1.7 0 0 0-.57-.344 2.206 2.206 0 0 0-1.456 0 1.7 1.7 0 0 0-.57.345 1.562 1.562 0 0 0-.502 1.157c0 .219.045.436.133.637v-.001Zm6.357.013c.086.197.212.374.37.52a1.7 1.7 0 0 0 .57.345c.47.165.984.165 1.456 0a1.7 1.7 0 0 0 .57-.345c.157-.146.283-.323.37-.52.089-.205.134-.427.132-.65a1.561 1.561 0 0 0-.503-1.151 1.761 1.761 0 0 0-.57-.338 2.206 2.206 0 0 0-1.456 0c-.21.075-.403.19-.57.338a1.546 1.546 0 0 0-.503 1.151c-.002.224.043.446.134.65Z" fill="currentColor"/>
</svg>
<svg viewBox="0 0 114 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.21 0a1 1 0 0 1 1 1v2.167a1 1 0 0 1-1 1H5.687a1 1 0 0 0-1 1V19a1 1 0 0 1-1 1H1.5a1 1 0 0 1-1-1V5.167a1 1 0 0 1 1-1h1.521a1 1 0 0 0 1-1V1a1 1 0 0 1 1-1H7.21Zm8.404 0a1 1 0 0 1 1 1v2.167a1 1 0 0 0 1 1h1.376a1 1 0 0 1 1 1V19a1 1 0 0 1-1 1H16.8a1 1 0 0 1-1-1V5.167a1 1 0 0 0-1-1h-1.375a1 1 0 0 1-1-1V1a1 1 0 0 1 1-1h2.188Zm-4.246 7.976a1 1 0 0 1 1 1v6.095a1 1 0 0 1-1 1H9.18a1 1 0 0 1-1-1V8.976a1 1 0 0 1 1-1h2.188Zm26.776-2.131h1.035l-.005 9.71h1.821V4.181h-2.851v1.664ZM33.15 4.533h-4.948v11.03h5.223c2.225 0 3.452-1.396 3.452-3.17a2.825 2.825 0 0 0-.529-1.726 2.81 2.81 0 0 0-1.463-1.049v-.052a2.456 2.456 0 0 0 1.494-2.343c0-1.588-1.134-2.69-3.23-2.69Zm1.391 3.005c0 .891-.567 1.477-1.357 1.477h-3.139V6.061h3.139c.79 0 1.357.586 1.357 1.477Zm.498 4.734c0 1.05-.687 1.774-1.632 1.774h-3.362v-3.532h3.362c.945 0 1.632.707 1.632 1.758Zm7.642-.793c0-2.498 1.84-4.342 4.383-4.342s4.398 1.826 4.398 4.342c0 2.516-1.838 4.325-4.398 4.325-2.56 0-4.383-1.827-4.383-4.325Zm3.902 2.688h.982c1.117 0 2.096-1.189 2.096-2.688 0-1.5-.982-2.708-2.096-2.708h-.982c-1.116 0-2.095 1.231-2.095 2.708s.979 2.688 2.095 2.688Zm10.342-7.03c-2.525 0-4.28 1.775-4.28 4.325 0 2.55 1.735 4.342 4.253 4.357 2.071 0 3.681-1.103 3.927-3.223h-1.77a1.75 1.75 0 0 1-1.752 1.602h-.859c-1.1 0-1.993-1.22-1.993-2.722 0-1.501.893-2.707 1.993-2.707h.859c.79 0 1.563.482 1.735 1.568h1.77c-.258-2.149-1.856-3.2-3.883-3.2Zm5.53-2.956h1.803v6.274h1.308l2.44-3.084h2.06l-3.006 3.808 3.144 4.376H68.04l-2.629-3.618h-1.153v3.618h-1.804V4.181Zm10.309 8.918h-1.735c.066 1.792 1.227 2.705 3.62 2.702 2.473 0 3.59-.93 3.59-2.619 0-1.31-.722-2.154-2.285-2.343l-1.926-.246c-.74-.104-1.083-.465-1.083-1 0-.534.361-.947 1.134-.947h1.033c.894 0 1.186.5 1.272 1.206h1.735c-.067-1.878-1.338-2.722-3.436-2.722-2.192 0-3.419.896-3.419 2.55 0 1.257.722 2.222 2.337 2.412l1.909.246c.739.106 1.048.433 1.048.985 0 .551-.378.964-1.186.964h-1.136c-.94 0-1.386-.465-1.472-1.188Zm6.609-1.637c0-2.55 1.752-4.325 4.28-4.325 2.027 0 3.624 1.051 3.882 3.2h-1.77c-.171-1.086-.944-1.568-1.734-1.568h-.86c-1.101 0-1.995 1.206-1.995 2.707 0 1.502.894 2.723 1.996 2.723h.859a1.75 1.75 0 0 0 1.752-1.602h1.77c-.246 2.119-1.856 3.222-3.927 3.222-2.516-.015-4.253-1.807-4.253-4.357Zm13.706-4.325c-2.543 0-4.38 1.844-4.38 4.342 0 2.498 1.818 4.325 4.38 4.325s4.4-1.81 4.4-4.325c0-2.516-1.858-4.342-4.4-4.342Zm.5 7.03h-.976c-1.117 0-2.101-1.211-2.101-2.688s.98-2.708 2.096-2.708h.981c1.117 0 2.099 1.209 2.099 2.708s-.982 2.688-2.099 2.688Zm5.397-1.637V7.38h1.804v4.842c0 1.19.601 1.896 1.391 1.896h.982c.842 0 1.598-.776 1.598-1.896V7.381h1.806v8.184h-1.755v-1.12c-.491.844-1.357 1.361-2.68 1.361-1.978-.002-3.146-1.157-3.146-3.276Zm10.582-3.557v4.677c0 1.223.736 1.913 2.057 1.915h1.963v-1.637h-1.443c-.584 0-.756-.258-.756-.792v-4.17h2.13V7.38h-2.13V5.086h-1.821v2.295h-1.58v1.592h1.58Z" fill="#2B6CB0"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M7.21 0a1 1 0 0 1 1 1v2.167a1 1 0 0 1-1 1H5.687a1 1 0 0 0-1 1V19a1 1 0 0 1-1 1H1.5a1 1 0 0 1-1-1V5.167a1 1 0 0 1 1-1h1.521a1 1 0 0 0 1-1V1a1 1 0 0 1 1-1H7.21Zm8.404 0a1 1 0 0 1 1 1v2.167a1 1 0 0 0 1 1h1.376a1 1 0 0 1 1 1V19a1 1 0 0 1-1 1H16.8a1 1 0 0 1-1-1V5.167a1 1 0 0 0-1-1h-1.375a1 1 0 0 1-1-1V1a1 1 0 0 1 1-1h2.188Zm-4.246 7.976a1 1 0 0 1 1 1v6.095a1 1 0 0 1-1 1H9.18a1 1 0 0 1-1-1V8.976a1 1 0 0 1 1-1h2.188Zm26.776-2.131h1.035l-.005 9.71h1.821V4.181h-2.851v1.664ZM33.15 4.533h-4.948v11.03h5.223c2.225 0 3.452-1.396 3.452-3.17a2.825 2.825 0 0 0-.529-1.726 2.81 2.81 0 0 0-1.463-1.049v-.052a2.456 2.456 0 0 0 1.494-2.343c0-1.588-1.134-2.69-3.23-2.69Zm1.391 3.005c0 .891-.567 1.477-1.357 1.477h-3.139V6.061h3.139c.79 0 1.357.586 1.357 1.477Zm.498 4.734c0 1.05-.687 1.774-1.632 1.774h-3.362v-3.532h3.362c.945 0 1.632.707 1.632 1.758Zm7.642-.793c0-2.498 1.84-4.342 4.383-4.342s4.398 1.826 4.398 4.342c0 2.516-1.838 4.325-4.398 4.325-2.56 0-4.383-1.827-4.383-4.325Zm3.902 2.688h.982c1.117 0 2.096-1.189 2.096-2.688 0-1.5-.982-2.708-2.096-2.708h-.982c-1.116 0-2.095 1.231-2.095 2.708s.979 2.688 2.095 2.688Zm10.342-7.03c-2.525 0-4.28 1.775-4.28 4.325 0 2.55 1.735 4.342 4.253 4.357 2.071 0 3.681-1.103 3.927-3.223h-1.77a1.75 1.75 0 0 1-1.752 1.602h-.859c-1.1 0-1.993-1.22-1.993-2.722 0-1.501.893-2.707 1.993-2.707h.859c.79 0 1.563.482 1.735 1.568h1.77c-.258-2.149-1.856-3.2-3.883-3.2Zm5.53-2.956h1.803v6.274h1.308l2.44-3.084h2.06l-3.006 3.808 3.144 4.376H68.04l-2.629-3.618h-1.153v3.618h-1.804V4.181Zm10.309 8.918h-1.735c.066 1.792 1.227 2.705 3.62 2.702 2.473 0 3.59-.93 3.59-2.619 0-1.31-.722-2.154-2.285-2.343l-1.926-.246c-.74-.104-1.083-.465-1.083-1 0-.534.361-.947 1.134-.947h1.033c.894 0 1.186.5 1.272 1.206h1.735c-.067-1.878-1.338-2.722-3.436-2.722-2.192 0-3.419.896-3.419 2.55 0 1.257.722 2.222 2.337 2.412l1.909.246c.739.106 1.048.433 1.048.985 0 .551-.378.964-1.186.964h-1.136c-.94 0-1.386-.465-1.472-1.188Zm6.609-1.637c0-2.55 1.752-4.325 4.28-4.325 2.027 0 3.624 1.051 3.882 3.2h-1.77c-.171-1.086-.944-1.568-1.734-1.568h-.86c-1.101 0-1.995 1.206-1.995 2.707 0 1.502.894 2.723 1.996 2.723h.859a1.75 1.75 0 0 0 1.752-1.602h1.77c-.246 2.119-1.856 3.222-3.927 3.222-2.516-.015-4.253-1.807-4.253-4.357Zm13.706-4.325c-2.543 0-4.38 1.844-4.38 4.342 0 2.498 1.818 4.325 4.38 4.325s4.4-1.81 4.4-4.325c0-2.516-1.858-4.342-4.4-4.342Zm.5 7.03h-.976c-1.117 0-2.101-1.211-2.101-2.688s.98-2.708 2.096-2.708h.981c1.117 0 2.099 1.209 2.099 2.708s-.982 2.688-2.099 2.688Zm5.397-1.637V7.38h1.804v4.842c0 1.19.601 1.896 1.391 1.896h.982c.842 0 1.598-.776 1.598-1.896V7.381h1.806v8.184h-1.755v-1.12c-.491.844-1.357 1.361-2.68 1.361-1.978-.002-3.146-1.157-3.146-3.276Zm10.582-3.557v4.677c0 1.223.736 1.913 2.057 1.915h1.963v-1.637h-1.443c-.584 0-.756-.258-.756-.792v-4.17h2.13V7.38h-2.13V5.086h-1.821v2.295h-1.58v1.592h1.58Z" fill="currentColor"/>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 20">
<path data-name="-e-logo_top" d="M13.82 10.054h-6.9v2.731h3.191l-.011 3.432a1.456 1.456 0 0 1-.43.319 4 4 0 0 1-.655.267 5.306 5.306 0 0 1-.795.182 5.647 5.647 0 0 1-.861.065 3.012 3.012 0 0 1-2.715-1.424 7.745 7.745 0 0 1-.9-4.063v-2.6a9.439 9.439 0 0 1 .265-2.36 5.76 5.76 0 0 1 .715-1.71 3.229 3.229 0 0 1 1.059-1.047 2.57 2.57 0 0 1 1.324-.357 2.922 2.922 0 0 1 2.191.735 3.762 3.762 0 0 1 .894 2.191h3.628a8.468 8.468 0 0 0-.616-2.458A5.315 5.315 0 0 0 11.9 2.1 5.626 5.626 0 0 0 9.854.936a9.017 9.017 0 0 0-2.84-.4 6.711 6.711 0 0 0-2.774.572 6.354 6.354 0 0 0-2.225 1.664A7.926 7.926 0 0 0 .532 5.431 10.952 10.952 0 0 0 0 8.988v2.575a11.473 11.473 0 0 0 .51 3.556 7.5 7.5 0 0 0 1.45 2.646 6.25 6.25 0 0 0 2.264 1.651 7.406 7.406 0 0 0 2.966.572 11.15 11.15 0 0 0 2.278-.221 10.5 10.5 0 0 0 1.913-.579 7.641 7.641 0 0 0 1.477-.8 5.159 5.159 0 0 0 .98-.871zm2.661 2.783a8.686 8.686 0 0 0 .451 2.854 6.513 6.513 0 0 0 1.317 2.269 5.984 5.984 0 0 0 2.132 1.5 7.277 7.277 0 0 0 2.88.54 7.194 7.194 0 0 0 2.86-.54 5.934 5.934 0 0 0 2.119-1.5 6.6 6.6 0 0 0 1.311-2.269 8.713 8.713 0 0 0 .45-2.854v-.274a8.673 8.673 0 0 0-.45-2.841 6.532 6.532 0 0 0-1.318-2.269 6.078 6.078 0 0 0-2.125-1.508 7.813 7.813 0 0 0-5.74 0 6.082 6.082 0 0 0-2.119 1.508 6.515 6.515 0 0 0-1.317 2.269 8.647 8.647 0 0 0-.451 2.841v.273zm3.681-.273a7.394 7.394 0 0 1 .173-1.625 4.223 4.223 0 0 1 .542-1.352 2.633 2.633 0 0 1 2.357-1.261 2.777 2.777 0 0 1 1.417.338 2.693 2.693 0 0 1 .947.923 4.31 4.31 0 0 1 .536 1.352 7.394 7.394 0 0 1 .173 1.625v.273a7.606 7.606 0 0 1-.173 1.658 4.227 4.227 0 0 1-.536 1.352 2.621 2.621 0 0 1-2.337 1.255 2.818 2.818 0 0 1-1.424-.338 2.758 2.758 0 0 1-.96-.917 4.143 4.143 0 0 1-.542-1.352 7.606 7.606 0 0 1-.173-1.658v-.274zm-1.747-10.3a1.492 1.492 0 0 0 .37.514 1.761 1.761 0 0 0 .57.338 2.207 2.207 0 0 0 1.456 0 1.761 1.761 0 0 0 .57-.338 1.492 1.492 0 0 0 .37-.514 1.593 1.593 0 0 0 0-1.274 1.562 1.562 0 0 0-.37-.52 1.7 1.7 0 0 0-.57-.344 2.206 2.206 0 0 0-1.456 0 1.7 1.7 0 0 0-.57.345 1.562 1.562 0 0 0-.37.52 1.593 1.593 0 0 0 0 1.274zm6.356.013a1.558 1.558 0 0 0 .37.52 1.7 1.7 0 0 0 .57.345 2.207 2.207 0 0 0 1.456 0 1.7 1.7 0 0 0 .57-.345 1.558 1.558 0 0 0 .37-.52 1.593 1.593 0 0 0 .133-.65 1.562 1.562 0 0 0-.133-.637 1.492 1.492 0 0 0-.37-.514 1.762 1.762 0 0 0-.57-.338 2.206 2.206 0 0 0-1.456 0 1.762 1.762 0 0 0-.57.338 1.492 1.492 0 0 0-.37.514 1.546 1.546 0 0 0-.133.637 1.576 1.576 0 0 0 .133.651z" fill="currentColor" fill-rule="evenodd"/>
</svg>
<svg viewBox="0 0 20 20" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
<path d="M15.938 13.125h-2.5a.312.312 0 0 0-.313.313v2.5c0 .172.14.312.313.312h2.5c.172 0 .312-.14.312-.313v-2.5a.312.312 0 0 0-.313-.312Zm-3.125-2.5h-1.876a.312.312 0 0 0-.312.313v1.874c0 .173.14.313.313.313h1.874c.173 0 .313-.14.313-.313v-1.874a.312.312 0 0 0-.313-.313Zm5.625 5.625h-1.875a.312.312 0 0 0-.313.313v1.875c0 .172.14.312.313.312h1.875c.172 0 .312-.14.312-.313v-1.875a.312.312 0 0 0-.313-.312Zm0-5.625h-1.25a.312.312 0 0 0-.313.313v1.25c0 .172.14.312.313.312h1.25c.172 0 .312-.14.312-.313v-1.25a.312.312 0 0 0-.313-.312Zm-6.25 6.25h-1.25a.312.312 0 0 0-.313.313v1.25c0 .172.14.312.313.312h1.25c.172 0 .312-.14.312-.313v-1.25a.312.312 0 0 0-.313-.312ZM17.5 1.25h-5.625a1.25 1.25 0 0 0-1.25 1.25v5.625a1.25 1.25 0 0 0 1.25 1.25H17.5a1.25 1.25 0 0 0 1.25-1.25V2.5a1.25 1.25 0 0 0-1.25-1.25Zm-1.25 5.313a.312.312 0 0 1-.313.312h-2.5a.313.313 0 0 1-.312-.313v-2.5a.312.312 0 0 1 .313-.312h2.5a.313.313 0 0 1 .312.313v2.5ZM8.125 1.25H2.5A1.25 1.25 0 0 0 1.25 2.5v5.625a1.25 1.25 0 0 0 1.25 1.25h5.625a1.25 1.25 0 0 0 1.25-1.25V2.5a1.25 1.25 0 0 0-1.25-1.25Zm-1.25 5.313a.312.312 0 0 1-.313.312h-2.5a.312.312 0 0 1-.312-.313v-2.5a.312.312 0 0 1 .313-.312h2.5a.312.312 0 0 1 .312.313v2.5Zm1.25 4.062H2.5a1.25 1.25 0 0 0-1.25 1.25V17.5a1.25 1.25 0 0 0 1.25 1.25h5.625a1.25 1.25 0 0 0 1.25-1.25v-5.625a1.25 1.25 0 0 0-1.25-1.25Zm-1.25 5.313a.313.313 0 0 1-.313.312h-2.5a.312.312 0 0 1-.312-.313v-2.5a.313.313 0 0 1 .313-.312h2.5a.312.312 0 0 1 .312.313v2.5Z"/>
</svg>
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.485 7.012h1.446a.918.918 0 1 1 0 1.84H4.253a.919.919 0 0 1-.92-.92V4.254a.919.919 0 1 1 1.84 0v1.47l.505-.505a6.436 6.436 0 0 1 9.103 0 6.436 6.436 0 0 1 0 9.103 6.436 6.436 0 0 1-9.103 0 .92.92 0 0 1 1.302-1.301 4.597 4.597 0 1 0 0-6.503l-.495.494Z" fill="currentColor"/>
</svg>
<svg viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.615 5.192c0-.934.758-1.692 1.693-1.692h2.538c.935 0 1.692.758 1.692 1.692v18.616c0 .934-.757 1.692-1.692 1.692h-2.538a1.692 1.692 0 0 1-1.693-1.692V5.192Zm4.231.339a.339.339 0 0 0-.338-.339h-1.862a.339.339 0 0 0-.338.339v17.938c0 .187.151.339.338.339h1.862a.339.339 0 0 0 .338-.339V5.531ZM4 19.577c0-.935.758-1.692 1.692-1.692h2.539c.934 0 1.692.757 1.692 1.692v4.23c0 .935-.758 1.693-1.692 1.693H5.692A1.692 1.692 0 0 1 4 23.808v-4.231Zm4.23.338a.339.339 0 0 0-.338-.338H6.031a.339.339 0 0 0-.339.338v3.554a.34.34 0 0 0 .339.339h1.861a.338.338 0 0 0 .339-.339v-3.554Zm12.693-9.646c-.934 0-1.692.758-1.692 1.693v11.846c0 .934.757 1.692 1.692 1.692h2.539c.934 0 1.692-.758 1.692-1.692V11.962c0-.935-.758-1.693-1.692-1.693h-2.539Zm2.2 1.693c.187 0 .339.151.339.338v11.17a.338.338 0 0 1-.339.338h-1.861a.338.338 0 0 1-.339-.339V12.3c0-.187.152-.338.339-.338h1.861Z" fill="currentColor"/>
</svg>
<svg viewBox="2 2 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11.667 3.75c0-.688-.563-1.25-1.25-1.25-.688 0-1.25.563-1.25 1.25 0 .688.562 1.25 1.25 1.25.687 0 1.25-.563 1.25-1.25Zm0 12.5c0-.688-.563-1.25-1.25-1.25-.688 0-1.25.563-1.25 1.25 0 .688.562 1.25 1.25 1.25.687 0 1.25-.563 1.25-1.25Zm0-6.25c0-.688-.563-1.25-1.25-1.25-.688 0-1.25.563-1.25 1.25 0 .688.562 1.25 1.25 1.25.687 0 1.25-.563 1.25-1.25Z" fill="currentColor" fill-opacity=".8"/>
</svg>
<svg fill="currentColor" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
<path d="M12.923 16H3.077A3.078 3.078 0 0 1 0 12.924V4.312a.615.615 0 0 1 .615-.615h12.308A3.077 3.077 0 0 1 16 6.773v6.151A3.076 3.076 0 0 1 12.923 16ZM1.23 4.927v7.997a1.845 1.845 0 0 0 1.846 1.846h9.846a1.846 1.846 0 0 0 1.846-1.846V6.773a1.845 1.845 0 0 0-1.846-1.846H1.23Z"/>
<path d="M14.154 4.927a.616.616 0 0 1-.615-.615V2.62a1.433 1.433 0 0 0-.48-1.15 1.194 1.194 0 0 0-1.028-.197L1.71 3.617a.615.615 0 0 0-.48.615.615.615 0 0 1-1.23 0 1.845 1.845 0 0 1 1.433-1.815L11.76.073a2.401 2.401 0 0 1 2.068.437 2.67 2.67 0 0 1 .941 2.11v1.692a.615.615 0 0 1-.615.615Zm1.231 7.382h-4.308a2.462 2.462 0 1 1 0-4.921h4.308a.615.615 0 0 1 .615.615v3.69a.615.615 0 0 1-.615.616Zm-4.308-3.691a1.231 1.231 0 0 0 0 2.46h3.692v-2.46h-3.692Z"/>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 31 30">
<g fill="currentColor" clip-path="url(#wallet_svg__a)">
<path d="M22.75 27.188h-15A4.688 4.688 0 0 1 3.062 22.5V9.375A.937.937 0 0 1 4 8.437h18.75a4.688 4.688 0 0 1 4.688 4.688V22.5a4.688 4.688 0 0 1-4.688 4.688ZM4.937 10.313V22.5a2.812 2.812 0 0 0 2.813 2.813h15a2.812 2.812 0 0 0 2.813-2.813v-9.375a2.812 2.812 0 0 0-2.813-2.813H4.937Z"/>
<path d="M24.625 10.312a.937.937 0 0 1-.937-.937V6.797a2.184 2.184 0 0 0-.732-1.754 1.82 1.82 0 0 0-1.565-.3L5.669 8.315a.938.938 0 0 0-.731.938.938.938 0 0 1-1.875 0 2.813 2.813 0 0 1 2.184-2.766l15.731-3.572a3.656 3.656 0 0 1 3.15.666 4.069 4.069 0 0 1 1.435 3.216v2.578a.938.938 0 0 1-.938.937ZM26.5 21.563h-6.563a3.75 3.75 0 1 1 0-7.5H26.5a.938.938 0 0 1 .938.937v5.625a.938.938 0 0 1-.938.938Zm-6.563-5.625a1.875 1.875 0 1 0 0 3.75h5.625v-3.75h-5.625Z"/>
</g>
<defs>
<clipPath id="wallet_svg__a">
<path fill="#fff" d="M.25 0h30v30h-30z"/>
</clipPath>
</defs>
</svg>
/* eslint-disable max-len */
import type { JestConfigWithTsJest } from 'ts-jest';
/*
* For a detailed explanation regarding each configuration property and type check, visit:
* https://jestjs.io/docs/configuration
*/
const config: JestConfigWithTsJest = {
// All imported modules in your tests should be mocked automatically
// automock: false,
// Stop running tests after `n` failures
// bail: 0,
// The directory where Jest should store its cached dependency information
// cacheDirectory: "/private/var/folders/3v/ry82j1y52sqgz3yvbcbt1krw0000gn/T/jest_dx",
// Automatically clear mock calls, instances, contexts and results before every test
clearMocks: true,
// Indicates whether the coverage information should be collected while executing the test
// collectCoverage: false,
// An array of glob patterns indicating a set of files for which coverage information should be collected
// collectCoverageFrom: undefined,
// The directory where Jest should output its coverage files
// coverageDirectory: undefined,
// An array of regexp pattern strings used to skip coverage collection
// coveragePathIgnorePatterns: [
// "/node_modules/"
// ],
// Indicates which provider should be used to instrument code for coverage
coverageProvider: 'v8',
// A list of reporter names that Jest uses when writing coverage reports
// coverageReporters: [
// "json",
// "text",
// "lcov",
// "clover"
// ],
// An object that configures minimum threshold enforcement for coverage results
// coverageThreshold: undefined,
// A path to a custom dependency extractor
// dependencyExtractor: undefined,
// Make calling deprecated APIs throw helpful error messages
// errorOnDeprecated: false,
// The default configuration for fake timers
// fakeTimers: {
// "enableGlobally": false
// },
// Force coverage collection from ignored files using an array of glob patterns
// forceCoverageMatch: [],
// A path to a module which exports an async function that is triggered once before all test suites
globalSetup: '<rootDir>/configs/jest/globalSetup.ts',
// A path to a module which exports an async function that is triggered once after all test suites
// globalTeardown: undefined,
// A set of global variables that need to be available in all test environments
// globals: {},
// The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers.
// maxWorkers: "50%",
// An array of directory names to be searched recursively up from the requiring module's location
moduleDirectories: [
'node_modules',
__dirname,
],
// An array of file extensions your modules use
// moduleFileExtensions: [
// "js",
// "mjs",
// "cjs",
// "jsx",
// "ts",
// "tsx",
// "json",
// "node"
// ],
// A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module
// moduleNameMapper: {},
// An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
modulePathIgnorePatterns: [
'node_modules_linux',
],
// Activates notifications for test results
// notify: false,
// An enum that specifies notification mode. Requires { notify: true }
// notifyMode: "failure-change",
// A preset that is used as a base for Jest's configuration
preset: 'ts-jest',
// Run tests from one or more projects
// projects: undefined,
// Use this configuration option to add custom reporters to Jest
reporters: [ 'default', 'github-actions' ],
// Automatically reset mock state before every test
// resetMocks: false,
// Reset the module registry before running each individual test
// resetModules: false,
// A path to a custom resolver
// resolver: undefined,
// Automatically restore mock state and implementation before every test
// restoreMocks: false,
// The root directory that Jest should scan for tests and modules within
// rootDir: undefined,
// A list of paths to directories that Jest should use to search for files in
// roots: [
// "<rootDir>"
// ],
// Allows you to use a custom runner instead of Jest's default test runner
// runner: "jest-runner",
// The paths to modules that run some code to configure or set up the testing environment before each test
// setupFiles: undefined,
// A list of paths to modules that run some code to configure or set up the testing framework before each test
// setupFilesAfterEnv: [],
// The number of seconds after which a test is considered as slow and reported as such in the results.
// slowTestThreshold: 5,
// A list of paths to snapshot serializer modules Jest should use for snapshot testing
// snapshotSerializers: [],
// The test environment that will be used for testing
testEnvironment: 'jsdom',
// testEnvironment: 'node',
// Options that will be passed to the testEnvironment
// testEnvironmentOptions: {},
// Adds a location field to test results
// testLocationInResults: false,
// The glob patterns Jest uses to detect test files
// testMatch: [
// "**/__tests__/**/*.[jt]s?(x)",
// "**/?(*.)+(spec|test).[tj]s?(x)"
// ],
// An array of regexp pattern strings that are matched against all test paths, matched tests are skipped
// testPathIgnorePatterns: [
// "/node_modules/"
// ],
// The regexp pattern or array of patterns that Jest uses to detect test files
// testRegex: [],
// This option allows the use of a custom results processor
// testResultsProcessor: undefined,
// This option allows use of a custom test runner
// testRunner: "jest-circus/runner",
// A map from regular expressions to paths to transformers
// transform: undefined,
// An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
// transformIgnorePatterns: [
// "/node_modules/",
// "\\.pnp\\.[^\\/]+$"
// ],
// An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them
// unmockedModulePathPatterns: undefined,
// Indicates whether each individual test should be reported during the run
// verbose: undefined,
// An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode
// watchPathIgnorePatterns: [],
// Whether to use watchman for file crawling
// watchman: true,
};
export default config;
......@@ -13,7 +13,7 @@ interface Props extends ChakraProviderProps {
export function Chakra({ cookies, theme, children }: Props) {
const colorModeManager =
typeof cookies === 'string' ?
cookieStorageManagerSSR(cookies) :
cookieStorageManagerSSR(typeof document !== 'undefined' ? document.cookie : cookies) :
localStorageManager;
return (
......
......@@ -8,14 +8,17 @@ import * as cookies from 'lib/cookies';
// first arg can be only a string
// FIXME migrate to RequestInfo later if needed
export default function fetchFactory(_req: NextApiRequest) {
export default function fetchFactory(
_req: NextApiRequest,
apiEndpoint: string = appConfig.api.endpoint,
) {
return function fetch(path: string, init?: RequestInit): Promise<Response> {
const headers = {
accept: 'application/json',
'content-type': 'application/json',
cookie: `${ cookies.NAMES.API_TOKEN }=${ _req.cookies[cookies.NAMES.API_TOKEN] }`,
};
const url = new URL(path, appConfig.api.endpoint);
const url = new URL(path, apiEndpoint);
httpLogger.logger.info({
message: 'Trying to call API',
......
......@@ -6,7 +6,7 @@ import { httpLogger } from 'lib/api/logger';
type Methods = 'GET' | 'POST' | 'PUT' | 'DELETE';
export default function createHandler(getUrl: (_req: NextApiRequest) => string, allowedMethods: Array<Methods>) {
export default function createHandler(getUrl: (_req: NextApiRequest) => string, allowedMethods: Array<Methods>, apiEndpoint?: string) {
const handler = async(_req: NextApiRequest, res: NextApiResponse) => {
httpLogger(_req, res);
......@@ -18,8 +18,8 @@ export default function createHandler(getUrl: (_req: NextApiRequest) => string,
const isBodyDisallowed = _req.method === 'GET' || _req.method === 'HEAD';
const url = getUrlWithNetwork(_req, `/api${ getUrl(_req) }`);
const fetch = fetchFactory(_req);
const url = apiEndpoint ? `/api${ getUrl(_req) }` : getUrlWithNetwork(_req, `/api${ getUrl(_req) }`);
const fetch = fetchFactory(_req, apiEndpoint);
const response = await fetch(url, {
method: _req.method,
body: isBodyDisallowed ? undefined : _req.body,
......
......@@ -7,13 +7,11 @@ type Props = {
pageProps: PageProps;
}
const AppContext = createContext<PageProps>({ cookies: '' });
export function AppWrapper({ children, pageProps }: Props) {
const appProps = { cookies: pageProps.cookies };
const AppContext = createContext<PageProps>({ cookies: '', referrer: '' });
export function AppContextProvider({ children, pageProps }: Props) {
return (
<AppContext.Provider value={ appProps }>
<AppContext.Provider value={ pageProps }>
{ children }
</AppContext.Provider>
);
......
import BigNumber from 'bignumber.js';
import type { Block } from 'types/api/block';
import { WEI, ZERO } from 'lib/consts';
export default function getBlockTotalReward(block: Block) {
const totalReward = block.rewards
?.map(({ reward }) => BigNumber(reward))
.reduce((result, item) => result.plus(item), ZERO) || ZERO;
return totalReward.div(WEI).toFixed();
}
......@@ -2,22 +2,27 @@ import clamp from 'lodash/clamp';
import throttle from 'lodash/throttle';
import React from 'react';
const ScrollDirectionContext = React.createContext<'up' | 'down' | null>(null);
import isBrowser from 'lib/isBrowser';
const SCROLL_DIFF_THRESHOLD = 20;
type Directions = 'up' | 'down';
export default function useScrollDirection() {
interface Props {
children: React.ReactNode;
}
export function ScrollDirectionProvider({ children }: Props) {
const prevScrollPosition = React.useRef(isBrowser() ? window.pageYOffset : 0);
const [ scrollDirection, setDirection ] = React.useState<Directions>();
const [ scrollDirection, setDirection ] = React.useState<Directions | null>(null);
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);
setDirection(null);
} else if (Math.abs(scrollDiff) > SCROLL_DIFF_THRESHOLD) {
setDirection(scrollDiff < 0 ? 'up' : 'down');
}
......@@ -33,9 +38,21 @@ export default function useScrollDirection() {
return () => {
window.removeEventListener('scroll', throttledHandleScroll);
};
// replicate componentDidMount
// eslint-disable-next-line react-hooks/exhaustive-deps
// replicate componentDidMount
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return scrollDirection;
return (
<ScrollDirectionContext.Provider value={ scrollDirection }>
{ children }
</ScrollDirectionContext.Provider>
);
}
export function useScrollDirection() {
const context = React.useContext(ScrollDirectionContext);
if (context === undefined) {
throw new Error('useScrollDirection must be used within a ScrollDirectionProvider');
}
return context;
}
......@@ -7,6 +7,7 @@ export enum NAMES {
NAV_BAR_COLLAPSED='nav_bar_collapsed',
API_TOKEN='_explorer_key',
TXS_SORT='txs_sort',
COLOR_MODE='chakra-ui-color-mode',
}
export function get(name?: NAMES | undefined | null, serverCookie?: string) {
......
......@@ -64,6 +64,9 @@ function makePolicyMap() {
'sentry.io', '*.sentry.io',
appConfig.api.socket,
// ad
'request-global.czilladx.com',
],
'script-src': [
......@@ -77,6 +80,12 @@ function makePolicyMap() {
// hash of ColorModeScript
'\'sha256-e7MRMmTzLsLQvIy1iizO1lXf7VWYoQ6ysj5fuUzvRwE=\'',
// ad
'coinzillatag.com',
'servedbyadbutler.com',
'\'sha256-wMOeDjJaOTjCfNjluteV+tSqHW547T89sgxd8W6tQJM=\'',
'\'sha256-FcyIn1h7zra8TVnnRhYrwrplxJW7dpD5TV7kP2AG/kI=\'',
],
'style-src': [
......@@ -100,20 +109,23 @@ function makePolicyMap() {
...MAIN_DOMAINS,
// github avatars
'avatars.githubusercontent.com',
// other github assets (e.g trustwallet token icons)
// github assets (e.g trustwallet token icons)
'raw.githubusercontent.com',
// auth0 assets
// auth0 assets and avatars
's.gravatar.com',
'i0.wp.com', 'i1.wp.com', 'i2.wp.com', 'i3.wp.com',
'lh3.googleusercontent.com', // google avatars
'avatars.githubusercontent.com', // github avatars
// network assets
...networkExternalAssets.map((url) => url.host),
// marketplace apps logos
...getMarketplaceAppsLogosOrigins().map((url) => url.host),
// ad
'servedbyadbutler.com',
],
'font-src': [
......@@ -124,6 +136,10 @@ function makePolicyMap() {
'fonts.googleapis.com',
],
'prefetch-src': [
...MAIN_DOMAINS,
],
'object-src': [
KEY_WORDS.NONE,
],
......@@ -132,7 +148,12 @@ function makePolicyMap() {
KEY_WORDS.NONE,
],
'frame-src': getMarketplaceAppsOrigins(),
'frame-src': [
...getMarketplaceAppsOrigins(),
// ad
'request-global.czilladx.com',
],
...(REPORT_URI ? {
'report-uri': [
......
export function shortenNumberWithLetter(
x: number,
params?: {
unitSeparator: string;
},
_options?: Intl.NumberFormatOptions,
) {
const options = _options || { maximumFractionDigits: 2 };
const unitSeparator = params?.unitSeparator || '';
if (x > 1_000_000_000) {
return (x / 1_000_000_000).toLocaleString('en', options) + unitSeparator + 'B';
}
if (x > 1_000_000) {
return (x / 1_000_000).toLocaleString('en', options) + unitSeparator + 'M';
}
if (x > 1_000) {
return (x / 1_000).toLocaleString('en', options) + unitSeparator + 'K';
}
return x.toLocaleString('en', options);
}
import BigNumber from 'bignumber.js';
interface Params {
value: string;
exchangeRate?: string | null;
accuracy?: number;
accuracyUsd?: number;
decimals?: string | null;
}
export default function getCurrencyValue({ value, accuracy, accuracyUsd, decimals, exchangeRate }: Params) {
const valueCurr = BigNumber(value).div(BigNumber(10 ** Number(decimals || '18')));
const valueResult = accuracy ? valueCurr.dp(accuracy).toFormat() : valueCurr.toFormat();
let usdResult: string | undefined;
if (exchangeRate) {
const exchangeRateBn = new BigNumber(exchangeRate);
const usdBn = valueCurr.times(exchangeRateBn);
if (accuracyUsd && !usdBn.isEqualTo(0)) {
const usdBnDp = usdBn.dp(accuracyUsd);
usdResult = usdBnDp.isEqualTo(0) ? usdBn.precision(accuracyUsd).toFormat() : usdBnDp.toFormat();
} else {
usdResult = usdBn.toFormat();
}
}
return { valueStr: valueResult, usd: usdResult };
}
export default function getPlaceholderWithError(text: string, errorText?: string) {
return `${ text }${ errorText ? ' - ' + errorText : '' }`;
}
import React from 'react';
const DURATION = 300;
export default function useGradualIncrement(initialValue: number): [number, (inc: number) => void] {
const [ num, setNum ] = React.useState(initialValue);
const queue = React.useRef<number>(0);
const timeoutId = React.useRef(0);
const delay = React.useRef(0);
const incrementDelayed = React.useCallback(() => {
if (queue.current === 0) {
return;
}
queue.current--;
setNum(prev => prev + 1);
timeoutId.current = 0;
}, []);
const increment = React.useCallback((inc: number) => {
if (inc < 1) {
return;
}
queue.current += inc;
if (!timeoutId.current) {
timeoutId.current = window.setTimeout(incrementDelayed, 0);
}
}, [ incrementDelayed ]);
React.useEffect(() => {
if (queue.current > 0 && !timeoutId.current) {
if (!delay.current) {
delay.current = DURATION / queue.current;
} else if (delay.current > DURATION / queue.current) {
// in case if queue size is increased since last DOM update
delay.current = DURATION / queue.current;
}
timeoutId.current = window.setTimeout(incrementDelayed, delay.current);
}
}, [ incrementDelayed, num ]);
React.useEffect(() => {
return () => {
window.clearTimeout(timeoutId.current);
};
}, []);
return [ num, increment ];
}
import throttle from 'lodash/throttle';
import React from 'react';
export default function useIsSticky(ref: React.RefObject<HTMLDivElement>, offset = 0, isEnabled = true) {
const [ isSticky, setIsSticky ] = React.useState(false);
const handleScroll = React.useCallback(() => {
if (
Number(ref.current?.getBoundingClientRect().y) < offset
) {
setIsSticky(true);
} else {
setIsSticky(false);
}
}, [ ref, offset ]);
React.useEffect(() => {
if (!isEnabled) {
return;
}
const throttledHandleScroll = throttle(handleScroll, 300);
window.addEventListener('scroll', throttledHandleScroll);
return () => {
window.removeEventListener('scroll', throttledHandleScroll);
};
// replicate componentDidMount
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [ isEnabled ]);
return isSticky;
}
import { useRouter } from 'next/router';
import link from 'lib/link/link';
export default function useLoginUrl() {
const router = useRouter();
return link('auth', {}, { path: router.asPath });
}
......@@ -9,6 +9,7 @@ import blocksIcon from 'icons/block.svg';
import privateTagIcon from 'icons/privattags.svg';
import profileIcon from 'icons/profile.svg';
import publicTagIcon from 'icons/publictags.svg';
import statsIcon from 'icons/stats.svg';
import tokensIcon from 'icons/token.svg';
import transactionsIcon from 'icons/transactions.svg';
import watchlistIcon from 'icons/watchlist.svg';
......@@ -28,6 +29,7 @@ export default function useNavItems() {
{ text: 'Tokens', url: link('tokens'), icon: tokensIcon, isActive: currentRoute === 'tokens', isNewUi: false },
isMarketplaceFilled ?
{ text: 'Apps', url: link('apps'), icon: appsIcon, isActive: currentRoute === 'apps', isNewUi: true } : null,
{ text: 'Charts & stats', url: link('stats'), icon: statsIcon, isActive: currentRoute === 'stats', isNewUi: true },
// 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/
// at this stage custom menu items is under development, we will implement it later
......
import type { NextRouter } from 'next/router';
import { useRouter } from 'next/router';
import React from 'react';
import useGradualIncrement from 'lib/hooks/useGradualIncrement';
import { ROUTES } from 'lib/link/routes';
import useSocketChannel from 'lib/socket/useSocketChannel';
import useSocketMessage from 'lib/socket/useSocketMessage';
function getSocketParams(router: NextRouter) {
if (
router.pathname === ROUTES.txs.pattern &&
(router.query.tab === 'validated' || router.query.tab === undefined) &&
!router.query.block_number
) {
return { topic: 'transactions:new_transaction' as const, event: 'transaction' as const };
}
if (router.pathname === ROUTES.network_index.pattern) {
return { topic: 'transactions:new_transaction' as const, event: 'transaction' as const };
}
if (router.pathname === ROUTES.txs.pattern && router.query.tab === 'pending' && !router.query.block_number) {
return { topic: 'transactions:new_pending_transaction' as const, event: 'pending_transaction' as const };
}
return {};
}
function assertIsNewTxResponse(response: unknown): response is { transaction: number } {
return typeof response === 'object' && response !== null && 'transaction' in response;
}
function assertIsNewPendingTxResponse(response: unknown): response is { pending_transaction: number } {
return typeof response === 'object' && response !== null && 'pending_transaction' in response;
}
export default function useNewTxsSocket() {
const router = useRouter();
const [ num, setNum ] = useGradualIncrement(0);
const [ socketAlert, setSocketAlert ] = React.useState('');
const { topic, event } = getSocketParams(router);
const handleNewTxMessage = React.useCallback((response: { transaction: number } | { pending_transaction: number } | unknown) => {
if (assertIsNewTxResponse(response)) {
setNum(response.transaction);
}
if (assertIsNewPendingTxResponse(response)) {
setNum(response.pending_transaction);
}
}, [ setNum ]);
const handleSocketClose = React.useCallback(() => {
setSocketAlert('Connection is lost. Please reload page.');
}, []);
const handleSocketError = React.useCallback(() => {
setSocketAlert('An error has occurred while fetching new transactions. Please reload page.');
}, []);
const channel = useSocketChannel({
topic,
onSocketClose: handleSocketClose,
onSocketError: handleSocketError,
isDisabled: !topic,
});
useSocketMessage({
channel,
event,
handler: handleNewTxMessage,
});
if (!topic && !event) {
return {};
}
return { num, socketAlert };
}
import React from 'react';
// prevent set focus on button when closing modal
export default function usePreventFocusAfterModalClosing() {
return React.useCallback((e: React.SyntheticEvent) => e.stopPropagation(), []);
}
import type { UseQueryOptions } from '@tanstack/react-query';
import { useQuery, useQueryClient } from '@tanstack/react-query';
import { pick, omit } from 'lodash';
import { useRouter } from 'next/router';
import React, { useCallback } from 'react';
import { animateScroll } from 'react-scroll';
import { animateScroll, scroller } from 'react-scroll';
import type { TransactionsResponse } from 'types/api/transaction';
import type { TTxsFilters } from 'types/api/txsFilters';
import type { QueryKeys } from 'types/client/queries';
import { PAGINATION_FIELDS, PAGINATION_FILTERS_FIELDS } from 'types/api/pagination';
import type { PaginationParams, PaginatedResponse, PaginatedQueryKeys, PaginationFilters } from 'types/api/pagination';
import useFetch from 'lib/hooks/useFetch';
const PAGINATION_FIELDS = [ 'block_number', 'index', 'items_count' ];
interface Params<QueryName extends PaginatedQueryKeys> {
apiPath: string;
queryName: QueryName;
queryIds?: Array<string>;
filters?: PaginationFilters<QueryName>;
options?: Omit<UseQueryOptions<unknown, unknown, PaginatedResponse<QueryName>>, 'queryKey' | 'queryFn'>;
scroll?: { elem: string; offset: number };
}
export default function useQueryWithPages(apiPath: string, queryName: QueryKeys, filters?: TTxsFilters) {
export default function useQueryWithPages<QueryName extends PaginatedQueryKeys>({
queryName,
filters,
options,
apiPath,
queryIds,
scroll,
}: Params<QueryName>) {
const paginationFields = PAGINATION_FIELDS[queryName];
const queryClient = useQueryClient();
const router = useRouter();
const [ page, setPage ] = React.useState(1);
const currPageParams = pick(router.query, PAGINATION_FIELDS);
const [ pageParams, setPageParams ] = React.useState<Array<Partial<TransactionsResponse['next_page_params']>>>([ {} ]);
const [ page, setPage ] = React.useState<number>(router.query.page && !Array.isArray(router.query.page) ? Number(router.query.page) : 1);
const currPageParams = pick(router.query, paginationFields);
const [ pageParams, setPageParams ] = React.useState<Array<PaginationParams<QueryName>>>([ ]);
const fetch = useFetch();
const isMounted = React.useRef(false);
const canGoBackwards = React.useRef(!router.query.page);
const queryKey = [ queryName, ...(queryIds || []), { page, filters } ];
const { data, isLoading, isError } = useQuery<unknown, unknown, TransactionsResponse>(
[ queryName, { page, filters } ],
const scrollToTop = useCallback(() => {
scroll ? scroller.scrollTo(scroll.elem, { offset: scroll.offset }) : animateScroll.scrollToTop({ duration: 0 });
}, [ scroll ]);
const queryResult = useQuery<unknown, unknown, PaginatedResponse<QueryName>>(
queryKey,
async() => {
const params: Array<string> = [];
......@@ -35,59 +58,86 @@ export default function useQueryWithPages(apiPath: string, queryName: QueryKeys,
return fetch(`${ apiPath }${ params.length ? '?' + params.join('&') : '' }`);
},
{ staleTime: Infinity },
{ staleTime: page === 1 ? 0 : Infinity, ...options },
);
const { data } = queryResult;
const onNextPageClick = useCallback(() => {
if (!data?.next_page_params) {
// we hide next page button if no next_page_params
return;
}
// api adds filters into next-page-params now
// later filters will be removed from response
const nextPageParams = pick(data.next_page_params, PAGINATION_FIELDS);
const nextPageParams = data.next_page_params;
if (page >= pageParams.length && data?.next_page_params) {
setPageParams(prev => [ ...prev, nextPageParams ]);
}
const nextPageQuery = { ...router.query };
Object.entries(nextPageParams).forEach(([ key, val ]) => nextPageQuery[key] = val.toString());
nextPageQuery.page = String(page + 1);
router.push({ pathname: router.pathname, query: nextPageQuery }, undefined, { shallow: true })
.then(() => {
animateScroll.scrollToTop({ duration: 0 });
scrollToTop();
setPage(prev => prev + 1);
});
}, [ data, page, pageParams, router ]);
}, [ data?.next_page_params, page, pageParams.length, router, scrollToTop ]);
const onPrevPageClick = useCallback(() => {
// returning to the first page
// we dont have pagination params for the first page
let nextPageQuery: typeof router.query;
let nextPageQuery: typeof router.query = {};
if (page === 2) {
queryClient.clear();
nextPageQuery = omit(router.query, PAGINATION_FIELDS);
nextPageQuery = omit(router.query, paginationFields, 'page');
canGoBackwards.current = true;
} else {
const nextPageParams = { ...pageParams[page - 2] };
nextPageQuery = { ...router.query };
Object.entries(nextPageParams).forEach(([ key, val ]) => nextPageQuery[key] = val.toString());
nextPageParams && Object.entries(nextPageParams).forEach(([ key, val ]) => nextPageQuery[key] = val.toString());
nextPageQuery.page = String(page - 1);
}
router.query = nextPageQuery;
router.push({ pathname: router.pathname, query: nextPageQuery }, undefined, { shallow: true })
.then(() => {
animateScroll.scrollToTop({ duration: 0 });
scrollToTop();
setPage(prev => prev - 1);
page === 2 && queryClient.clear();
});
}, [ router, page, pageParams, queryClient ]);
}, [ router, page, paginationFields, pageParams, queryClient, scrollToTop ]);
const resetPage = useCallback(() => {
queryClient.clear();
router.push({ pathname: router.pathname, query: omit(router.query, PAGINATION_FIELDS) }, undefined, { shallow: true }).then(() => {
animateScroll.scrollToTop({ duration: 0 });
router.push({ pathname: router.pathname, query: omit(router.query, paginationFields, 'page') }, undefined, { shallow: true }).then(() => {
queryClient.removeQueries({ queryKey: [ queryName ] });
scrollToTop();
setPage(1);
setPageParams([ ]);
canGoBackwards.current = true;
});
}, [ queryClient, queryName, router, paginationFields, scrollToTop ]);
const onFilterChange = useCallback((newFilters: PaginationFilters<QueryName> | undefined) => {
const newQuery = omit(router.query, PAGINATION_FIELDS[queryName], 'page', PAGINATION_FILTERS_FIELDS[queryName]);
if (newFilters) {
Object.entries(newFilters).forEach(([ key, value ]) => {
if (value) {
newQuery[key] = Array.isArray(value) ? value.join(',') : (value || '');
}
});
}
router.push(
{
pathname: router.pathname,
query: newQuery,
},
undefined,
{ shallow: true },
).then(() => {
setPage(1);
setPageParams([ {} ]);
setPageParams([ ]);
scrollToTop();
});
}, [ router, queryClient ]);
}, [ queryName, router, scrollToTop, setPageParams, setPage ]);
const hasPaginationParams = Object.keys(currPageParams).length > 0;
const nextPageParams = data?.next_page_params;
const pagination = {
page,
......@@ -95,7 +145,24 @@ export default function useQueryWithPages(apiPath: string, queryName: QueryKeys,
onPrevPageClick,
hasPaginationParams,
resetPage,
hasNextPage: nextPageParams ? Object.keys(nextPageParams).length > 0 : false,
canGoBackwards: canGoBackwards.current,
};
return { data, isError, isLoading, pagination };
React.useEffect(() => {
if (page !== 1 && isMounted.current) {
queryClient.cancelQueries({ queryKey });
setPage(1);
}
// hook should run only when queryName has changed
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [ queryName ]);
React.useEffect(() => {
window.setTimeout(() => {
isMounted.current = true;
}, 0);
}, []);
return { ...queryResult, pagination, onFilterChange };
}
import * as Sentry from '@sentry/react';
import { useQueryClient } from '@tanstack/react-query';
import React from 'react';
import { QueryKeys } from 'types/client/accountQueries';
import * as cookies from 'lib/cookies';
import useLoginUrl from 'lib/hooks/useLoginUrl';
export interface ErrorType {
error?: {
status: Response['status'];
statusText: Response['statusText'];
};
}
export default function useRedirectForInvalidAuthToken() {
const queryClient = useQueryClient();
const state = queryClient.getQueryState<unknown, ErrorType>([ QueryKeys.profile ]);
const errorStatus = state?.error?.error?.status;
const loginUrl = useLoginUrl();
React.useEffect(() => {
if (errorStatus === 401) {
const apiToken = cookies.get(cookies.NAMES.API_TOKEN);
if (apiToken) {
Sentry.captureException(new Error('Invalid api token'), { tags: { source: 'fetch' } });
window.location.assign(loginUrl);
}
}
}, [ errorStatus, loginUrl ]);
}
import appConfig from 'configs/app/config';
export default function isSelfHosted() {
return appConfig.host?.endsWith(appConfig.ad.domainWithAd) || appConfig.host === 'localhost';
}
import link from './link';
it('makes correct link if there are no params in path', () => {
const result = link('api_keys');
expect(result).toBe('https://blockscout.com/account/api_key');
});
it('makes correct link if there are params in path', () => {
const result = link('token_instance_item', { id: '42', hash: '0x67e90a54AeEA85f21949c645082FE95d77BC1E70' });
expect(result).toBe('https://blockscout.com/token/0x67e90a54AeEA85f21949c645082FE95d77BC1E70/instance/42');
});
it('makes correct link with query params', () => {
const result = link('tx', { id: '0x4eb3b3b35d4c4757629bee32fc7a28b5dece693af8e7a383cf4cd6debe97ecf2' }, { tab: 'index', foo: 'bar' });
expect(result).toBe('https://blockscout.com/tx/0x4eb3b3b35d4c4757629bee32fc7a28b5dece693af8e7a383cf4cd6debe97ecf2?tab=index&foo=bar');
});
......@@ -3,7 +3,7 @@ import appConfig from 'configs/app/config';
import { ROUTES } from './routes';
import type { RouteName } from './routes';
const PATH_PARAM_REGEXP = /\/\[(\w+)\]/g;
const PATH_PARAM_REGEXP = /\/:(\w+)/g;
export default function link(
routeName: RouteName,
......@@ -25,12 +25,13 @@ export default function link(
return paramValue ? `/${ paramValue }` : '';
});
const baseUrl = routeName === 'auth' ? appConfig.authUrl : appConfig.baseUrl;
const url = new URL(path, appConfig.baseUrl);
const url = new URL(path, baseUrl);
queryParams && Object.entries(queryParams).forEach(([ key, value ]) => {
url.searchParams.append(key, value);
});
return url.pathname;
return url.toString();
}
const paths = {
network_index: `/`,
watchlist: `/account/watchlist`,
private_tags: `/account/tag_address`,
public_tags: `/account/public_tags_request`,
api_keys: `/account/api_key`,
custom_abi: `/account/custom_abi`,
profile: `/auth/profile`,
txs: `/txs`,
tx: `/tx/[id]`,
blocks: `/blocks`,
block: `/block/[id]`,
tokens: `/tokens`,
token_index: `/token/[hash]`,
token_instance_item: `/token/[hash]/instance/[id]`,
address_index: `/address/[id]`,
address_contract_verification: `/address/[id]/contract_verifications/new`,
apps: `/apps`,
app_index: `/apps/[id]`,
search_results: `/search-results`,
other: `/search-results`,
auth: `/auth/auth0`,
};
module.exports = paths;
{
"network_index": "/",
"watchlist": "/account/watchlist",
"private_tags": "/account/tag_address",
"public_tags": "/account/public_tags_request",
"api_keys": "/account/api_key",
"custom_abi": "/account/custom_abi",
"profile": "/auth/profile",
"txs": "/txs",
"tx": "/tx/:id",
"blocks": "/blocks",
"block": "/block/:id",
"tokens": "/tokens",
"token_index": "/token/:hash",
"token_instance_item": "/token/:hash/instance/:id",
"address_index": "/address/:id",
"address_contract_verification": "/address/:id/contract_verifications/new",
"apps": "/apps",
"app_index": "/apps/:id",
"search_results": "/search-results",
"other": "/search-results",
"auth": "/auth/auth0",
"stats": "/stats"
}
import appConfig from 'configs/app/config';
import PATHS from './paths.js';
import PATHS from './paths.json';
export interface Route {
pattern: string;
......@@ -85,6 +85,10 @@ export const ROUTES = {
pattern: PATHS.app_index,
},
stats: {
pattern: PATHS.stats,
},
// SEARCH
search_results: {
pattern: PATHS.search_results,
......
......@@ -4,13 +4,18 @@ import React from 'react';
import type { RouteName } from 'lib/link/routes';
import { ROUTES } from 'lib/link/routes';
const PATH_PARAM_REGEXP = /\/:(\w+)/g;
export default function useCurrentRoute() {
const { route: nextRoute } = useRouter();
return React.useCallback((): RouteName => {
for (const routeName in ROUTES) {
const route = ROUTES[routeName as RouteName];
if (route.pattern === nextRoute) {
const formattedRoute = route.pattern.replace(PATH_PARAM_REGEXP, (_, paramName: string) => {
return `/[${ paramName }]`;
});
if (formattedRoute === nextRoute) {
return routeName as RouteName;
}
}
......
import type { FeaturedNetwork, PreDefinedNetwork } from 'types/networks';
import type { FeaturedNetwork } from 'types/networks';
import appConfig from 'configs/app/config';
import arbitrumIcon from 'icons/networks/icons/arbitrum.svg';
import artisIcon from 'icons/networks/icons/artis.svg';
import ethereumClassicIcon from 'icons/networks/icons/ethereum-classic.svg';
import ethereumIcon from 'icons/networks/icons/ethereum.svg';
import gnosisIcon from 'icons/networks/icons/gnosis.svg';
import optimismIcon from 'icons/networks/icons/optimism.svg';
import poaSokolIcon from 'icons/networks/icons/poa-sokol.svg';
import poaIcon from 'icons/networks/icons/poa.svg';
import rskIcon from 'icons/networks/icons/rsk.svg';
// predefined network icons
const ICONS: Partial<Record<PreDefinedNetwork, React.FunctionComponent<React.SVGAttributes<SVGElement>>>> = {
xdai_mainnet: gnosisIcon,
xdai_optimism: optimismIcon,
xdai_aox: arbitrumIcon,
eth_mainnet: ethereumIcon,
etc_mainnet: ethereumClassicIcon,
poa_core: poaIcon,
rsk_mainnet: rskIcon,
xdai_testnet: arbitrumIcon,
poa_sokol: poaSokolIcon,
artis_sigma1: artisIcon,
};
import ASSETS from 'lib/networks/networkAssets';
// for easy .env.example update
// const FEATURED_NETWORKS = JSON.stringify([
// {
// 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: 'Gnosis Chain',
// url: 'https://blockscout.com/xdai/mainnet',
// group: 'mainnets',
// type: 'xdai_mainnet',
// },
// {
// title: 'Optimism on Gnosis Chain',
// url: 'https://blockscout.com/xdai/optimism',
// title: 'Astar (EVM)',
// url: 'https://blockscout.com/astar',
// group: 'mainnets',
// icon: 'https://www.fillmurray.com/60/60',
// type: 'xdai_optimism',
// type: 'astar',
// },
// {
// title: 'Arbitrum on xDai',
// url: 'https://blockscout.com/xdai/aox',
// title: 'Shiden (EVM)',
// url: 'https://blockscout.com/shiden',
// group: 'mainnets',
// type: 'astar',
// },
// {
// title: 'Ethereum',
// url: 'https://blockscout.com/eth/mainnet',
// title: 'Klaytn Mainnet (Cypress)',
// url: 'https://klaytn-mainnet.aws-k8s.blockscout.com/',
// group: 'mainnets',
// type: 'eth_mainnet',
// type: 'klaytn',
// },
// {
// title: 'Ethereum Classic',
// url: 'https://blockscout.com/etx/mainnet',
// group: 'mainnets',
// type: 'etc_mainnet',
// title: 'Goerli',
// url: 'https://blockscout.com/eth/goerli/',
// group: 'testnets',
// type: 'goerli',
// },
// {
// title: 'POA',
// url: 'https://blockscout.com/poa/core',
// group: 'mainnets',
// type: 'poa_core',
// title: 'Optimism Goerli',
// url: 'https://blockscout.com/optimism/goerli/',
// group: 'testnets',
// type: 'optimism_goerli',
// },
// {
// title: 'RSK',
// url: 'https://blockscout.com/rsk/mainnet',
// group: 'mainnets',
// type: 'rsk_mainnet',
// title: 'Optimism Bedrock Alpha',
// url: 'https://blockscout.com/optimism/bedrock-alpha',
// group: 'testnets',
// type: 'optimism_bedrock_alpha',
// },
// {
// title: 'Gnosis Chain Testnet',
// url: 'https://blockscout.com/xdai/testnet',
// title: 'Gnosis Chiado',
// url: 'https://blockscout.com/gnosis/chiado/',
// group: 'testnets',
// type: 'xdai_testnet',
// type: 'gnosis_chiado',
// },
// {
// title: 'POA Sokol',
// url: 'https://blockscout.com/poa/sokol',
// title: 'Shibuya (EVM)',
// url: 'https://blockscout.com/shibuya',
// group: 'testnets',
// type: 'poa_sokol',
// type: 'shibuya',
// },
// {
// title: 'Optimism Opcraft',
// url: 'https://blockscout.com/optimism/opcraft',
// group: 'other',
// type: 'optimism_opcraft',
// },
// {
// title: 'Optimism on Gnosis Chain',
// url: 'https://blockscout.com/xdai/optimism',
// group: 'other',
// type: 'optimism_gnosis',
// },
// {
// title: 'ARTIS Σ1',
// title: 'ARTIS-Σ1',
// url: 'https://blockscout.com/artis/sigma1',
// group: 'other',
// type: 'artis_sigma1',
......@@ -94,17 +98,23 @@ const ICONS: Partial<Record<PreDefinedNetwork, React.FunctionComponent<React.SVG
// type: 'lukso_l14',
// },
// {
// title: 'Astar',
// url: 'https://blockscout.com/astar',
// title: 'POA',
// url: 'https://blockscout.com/poa/core',
// group: 'other',
// type: 'astar',
// type: 'poa_core',
// },
// {
// title: 'POA Sokol',
// url: 'https://blockscout.com/poa/sokol',
// group: 'other',
// type: 'poa_sokol',
// },
// ]).replaceAll('"', '\'');
const featuredNetworks: Array<FeaturedNetwork> = (() => {
return appConfig.featuredNetworks.map((network) => ({
...network,
icon: network.icon || (network.type ? ICONS[network.type] : undefined),
icon: network.icon || (network.type ? ASSETS[network.type]?.icon : undefined),
}));
})();
......
import type React from 'react';
import type { PreDefinedNetwork } from 'types/networks';
import arbitrumIcon from 'icons/networks/icons/arbitrum.svg';
import artisIcon from 'icons/networks/icons/artis.svg';
import ethereumClassicIcon from 'icons/networks/icons/ethereum-classic.svg';
import ethereumIcon from 'icons/networks/icons/ethereum.svg';
import gnosisIcon from 'icons/networks/icons/gnosis.svg';
import goerliIcon from 'icons/networks/icons/goerli.svg';
import optimismIcon from 'icons/networks/icons/optimism.svg';
import poaSokolIcon from 'icons/networks/icons/poa-sokol.svg';
import poaIcon from 'icons/networks/icons/poa.svg';
import rskIcon from 'icons/networks/icons/rsk.svg';
import artisLogo from 'icons/networks/logos/artis.svg';
import astarLogo from 'icons/networks/logos/astar.svg';
import etcLogo from 'icons/networks/logos/etc.svg';
import ethLogo from 'icons/networks/logos/eth.svg';
import gnosisLogo from 'icons/networks/logos/gnosis.svg';
import goerliLogo from 'icons/networks/logos/goerli.svg';
import luksoLogo from 'icons/networks/logos/lukso.svg';
import poaLogo from 'icons/networks/logos/poa.svg';
import rskLogo from 'icons/networks/logos/rsk.svg';
import shibuyaLogo from 'icons/networks/logos/shibuya.svg';
import shidenLogo from 'icons/networks/logos/shiden.svg';
import sokolLogo from 'icons/networks/logos/sokol.svg';
interface NetworkAssets {
icon?: React.FunctionComponent<React.SVGAttributes<SVGElement>>;
logo?: React.FunctionComponent<React.SVGAttributes<SVGElement>>;
smallLogo?: React.FunctionComponent<React.SVGAttributes<SVGElement>>;
}
const networkAssets: Partial<Record<PreDefinedNetwork, NetworkAssets>> = {
xdai_mainnet: {
icon: gnosisIcon,
logo: gnosisLogo,
},
xdai_optimism: {
icon: optimismIcon,
},
xdai_aox: {
icon: arbitrumIcon,
},
eth_mainnet: {
icon: ethereumIcon,
logo: ethLogo,
},
etc_mainnet: {
icon: ethereumClassicIcon,
logo: etcLogo,
},
poa_core: {
icon: poaIcon,
logo: poaLogo,
},
rsk_mainnet: {
icon: rskIcon,
logo: rskLogo,
},
xdai_testnet: {
icon: arbitrumIcon,
logo: gnosisLogo,
},
poa_sokol: {
icon: poaSokolIcon,
logo: sokolLogo,
},
artis_sigma1: {
icon: artisIcon,
logo: artisLogo,
},
lukso_l14: {
logo: luksoLogo,
},
astar: {
logo: astarLogo,
},
shiden: {
logo: shidenLogo,
},
shibuya: {
logo: shibuyaLogo,
},
goerli: {
logo: goerliLogo,
icon: goerliIcon,
},
};
export default networkAssets;
......@@ -11,12 +11,21 @@ import appConfig from 'configs/app/config';
// title: 'Anyblock',
// baseUrl: 'https://explorer.anyblock.tools',
// paths: {
// tx: '/ethereum/poa/core/tx',
// tx: '/ethereum/ethereum/goerli/transaction',
// address: '/ethereum/ethereum/goerli/address'
// },
// },
// {
// title: 'Etherscan',
// baseUrl: 'https://goerli.etherscan.io/',
// paths: {
// tx: '/tx',
// address: '/address',
// },
// },
// ]).replaceAll('"', '\'');
const stripTrailingSlash = (str: string) => str.at(-1) === '/' ? str.slice(0, -1) : str;
const stripTrailingSlash = (str: string) => str[str.length - 1] === '/' ? str.slice(0, -1) : str;
const addLeadingSlash = (str: string) => str.at(0) === '/' ? str : '/' + str;
const networkExplorers: Array<NetworkExplorer> = (() => {
......
import type { PageParams } from './types';
import getNetworkTitle from 'lib/networks/getNetworkTitle';
export default function getSeo(params: PageParams) {
const networkTitle = getNetworkTitle();
return {
title: params ? `${ params.id } - ${ networkTitle }` : '',
description: params ?
`View the account balance, transactions, and other data for ${ params.id } on the ${ networkTitle }` :
'',
};
}
export type PageParams = {
id: string;
}
export type PageParams = unknown
......@@ -2,12 +2,16 @@ import type { GetServerSideProps, GetServerSidePropsResult } from 'next';
export type Props = {
cookies: string;
referrer: string;
id?: string;
}
export const getServerSideProps: GetServerSideProps = async({ req }): Promise<GetServerSidePropsResult<Props>> => {
export const getServerSideProps: GetServerSideProps = async({ req, query }): Promise<GetServerSidePropsResult<Props>> => {
return {
props: {
cookies: req.headers.cookie || '',
referrer: req.headers.referer || '',
id: query.id?.toString() || '',
},
};
};
import type { NextPage } from 'next';
import Head from 'next/head';
import React from 'react';
import type { PageParams } from './types';
import Transaction from 'ui/pages/Transaction';
import getSeo from './getSeo';
type Props = {
pageParams: PageParams;
}
const TransactionNextPage: NextPage<Props> = ({ pageParams }: Props) => {
const { title, description } = getSeo(pageParams);
return (
<>
<Head>
<title>{ title }</title>
<meta name="description" content={ description }/>
</Head>
<Transaction/>
</>
);
};
export default TransactionNextPage;
import type { SocketConnectOption } from 'phoenix';
import { Socket } from 'phoenix';
import React, { useEffect, useState } from 'react';
export const SocketContext = React.createContext<Socket | null>(null);
interface SocketProviderProps {
children: React.ReactNode;
url?: string;
options?: Partial<SocketConnectOption>;
}
export function SocketProvider({ children, options, url }: SocketProviderProps) {
const [ socket, setSocket ] = useState<Socket | null>(null);
useEffect(() => {
if (!url) {
return;
}
const socketInstance = new Socket(url, options);
socketInstance.connect();
setSocket(socketInstance);
return () => {
socketInstance.disconnect();
setSocket(null);
};
}, [ options, url ]);
return (
<SocketContext.Provider value={ socket }>
{ children }
</SocketContext.Provider>
);
}
export function useSocket() {
const context = React.useContext(SocketContext);
if (context === undefined) {
throw new Error('useSocket must be used within a SocketProvider');
}
return context;
}
import type { Channel } from 'phoenix';
import type { NewBlockSocketResponse } from 'types/api/block';
export type SocketMessageParams = SocketMessage.NewBlock |
SocketMessage.BlocksIndexStatus |
SocketMessage.TxStatusUpdate |
SocketMessage.NewTx |
SocketMessage.NewPendingTx |
SocketMessage.AddressBalance |
SocketMessage.AddressCurrentCoinBalance |
SocketMessage.AddressTokenBalance |
SocketMessage.AddressCoinBalance |
SocketMessage.Unknown;
interface SocketMessageParamsGeneric<Event extends string | undefined, Payload extends object | unknown> {
channel: Channel | undefined;
event: Event;
handler: (payload: Payload) => void;
}
export interface AddressCoinBalancePayload {
coin_balance: {
block_number: number;
block_timestamp?: string;
delta?: string;
transaction_hash?: string | null;
value?: string;
};
}
// eslint-disable-next-line @typescript-eslint/no-namespace
export namespace SocketMessage {
export type NewBlock = SocketMessageParamsGeneric<'new_block', NewBlockSocketResponse>;
export type BlocksIndexStatus = SocketMessageParamsGeneric<'index_status', {finished: boolean; ratio: string}>;
export type TxStatusUpdate = SocketMessageParamsGeneric<'collated', NewBlockSocketResponse>;
export type NewTx = SocketMessageParamsGeneric<'transaction', { transaction: number }>;
export type NewPendingTx = SocketMessageParamsGeneric<'pending_transaction', { pending_transaction: number }>;
export type AddressBalance = SocketMessageParamsGeneric<'balance', { balance: string; block_number: number; exchange_rate: string }>;
export type AddressCurrentCoinBalance =
SocketMessageParamsGeneric<'current_coin_balance', { coin_balance: string; block_number: number; exchange_rate: string }>;
export type AddressTokenBalance = SocketMessageParamsGeneric<'token_balance', { block_number: number }>;
export type AddressCoinBalance = SocketMessageParamsGeneric<'coin_balance', AddressCoinBalancePayload>;
export type Unknown = SocketMessageParamsGeneric<undefined, unknown>;
}
import type { Channel } from 'phoenix';
import { useEffect, useRef, useState } from 'react';
import notEmpty from 'lib/notEmpty';
import { useSocket } from './context';
const CHANNEL_REGISTRY: Record<string, Channel> = {};
interface Params {
topic: string | undefined;
params?: object;
isDisabled: boolean;
onJoin?: (channel: Channel, message: unknown) => void;
onSocketClose?: () => void;
onSocketError?: () => void;
}
export default function useSocketChannel({ topic, params, isDisabled, onJoin, onSocketClose, onSocketError }: Params) {
const socket = useSocket();
const [ channel, setChannel ] = useState<Channel>();
const onCloseRef = useRef<string>();
const onErrorRef = useRef<string>();
const onJoinRef = useRef(onJoin);
onJoinRef.current = onJoin;
useEffect(() => {
const cleanUpRefs = () => {
const refs = [ onCloseRef.current, onErrorRef.current ].filter(notEmpty);
refs.length > 0 && socket?.off(refs);
};
if (!isDisabled) {
onCloseRef.current = onSocketClose && socket?.onClose(onSocketClose);
onErrorRef.current = onSocketError && socket?.onError(onSocketError);
} else {
cleanUpRefs();
}
return cleanUpRefs;
}, [ onSocketClose, onSocketError, socket, isDisabled ]);
useEffect(() => {
if (isDisabled && channel) {
channel.leave();
setChannel(undefined);
}
}, [ channel, isDisabled ]);
useEffect(() => {
if (socket === null || isDisabled || !topic) {
return;
}
let ch: Channel;
if (CHANNEL_REGISTRY[topic]) {
ch = CHANNEL_REGISTRY[topic];
onJoinRef.current?.(ch, '');
} else {
ch = socket.channel(topic);
CHANNEL_REGISTRY[topic] = ch;
ch.join().receive('ok', (message) => onJoinRef.current?.(ch, message));
}
setChannel(ch);
return () => {
ch.leave();
delete CHANNEL_REGISTRY[topic];
setChannel(undefined);
};
}, [ socket, topic, params, isDisabled ]);
return channel;
}
import { useEffect, useRef } from 'react';
import type { SocketMessageParams } from 'lib/socket/types';
export default function useSocketMessage({ channel, event, handler }: SocketMessageParams) {
const handlerRef = useRef(handler);
handlerRef.current = handler;
useEffect(() => {
if (channel === undefined || event === undefined) {
return;
}
const ref = channel.on(event, (message) => {
handlerRef.current?.(message);
});
return () => {
channel.off(event, ref);
};
}, [ channel, event ]);
}
import type { TransactionsResponse } from 'types/api/transaction';
import type { Transaction } 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;
const sortTxs = (sorting?: Sort) => (tx1: Transaction, tx2: Transaction) => {
switch (sorting) {
case 'val-desc':
sortedTxs = [ ...txs ].sort((tx1, tx2) => compareBns(tx1.value, tx2.value));
break;
return compareBns(tx1.value, tx2.value);
case 'val-asc':
sortedTxs = [ ...txs ].sort((tx1, tx2) => compareBns(tx2.value, tx1.value));
break;
return compareBns(tx2.value, tx1.value);
case 'fee-desc':
sortedTxs = [ ...txs ].sort((tx1, tx2) => compareBns(tx1.fee.value, tx2.fee.value));
break;
return compareBns(tx1.fee.value, tx2.fee.value);
case 'fee-asc':
sortedTxs = [ ...txs ].sort((tx1, tx2) => compareBns(tx2.fee.value, tx1.fee.value));
break;
return compareBns(tx2.fee.value, tx1.fee.value);
default:
sortedTxs = txs;
return 0;
}
};
return sortedTxs;
}
export default sortTxs;
......@@ -21,7 +21,7 @@ export function middleware(req: NextRequest) {
const apiToken = req.cookies.get(NAMES.API_TOKEN);
if ((isAccountRoute || isProfileRoute) && !apiToken && appConfig.isAccountSupported) {
const authUrl = link('auth');
const authUrl = link('auth', undefined, { path: req.nextUrl.pathname });
return NextResponse.redirect(authUrl);
}
......
import type { AddressParam } from 'types/api/addressParams';
export const withName: AddressParam = {
hash: '0xd789a607CEac2f0E14867de4EB15b15C9FFB5859',
implementation_name: null,
is_contract: true,
is_verified: null,
name: 'ArianeeStore',
private_tags: [],
watchlist_names: [],
public_tags: [],
};
export const withoutName: AddressParam = {
hash: '0xd789a607CEac2f0E14867de4EB15b15C9FFB5859',
implementation_name: null,
is_contract: true,
is_verified: null,
name: null,
private_tags: [],
watchlist_names: [],
public_tags: [],
};
import type { AddressTag, WatchlistName } from 'types/api/addressParams';
export const privateTag: AddressTag = {
label: 'my-private-tag',
display_name: 'my private tag',
address_hash: '0x',
};
export const publicTag: AddressTag = {
label: 'some-public-tag',
display_name: 'some public tag',
address_hash: '0x',
};
export const watchlistName: WatchlistName = {
label: 'watchlist-name',
display_name: 'watchlist name',
};
import type { AddressTokenBalance } from 'types/api/address';
export const erc20a: AddressTokenBalance = {
token: {
address: '0xb2a90505dc6680a7a695f7975d0d32EeF610f456',
decimals: '18',
exchange_rate: null,
holders: '23',
name: 'hyfi.token',
symbol: 'HyFi',
total_supply: '369000000000000000000000000',
type: 'ERC-20',
},
token_id: null,
value: '1169320000000000000000000',
};
export const erc20b: AddressTokenBalance = {
token: {
address: '0xc1116c98ba622a6218433fF90a2E40DEa482d7A7',
decimals: '6',
exchange_rate: '0.982',
holders: '17',
name: 'USD Coin',
symbol: 'USDC',
total_supply: '900000000000000000000000000',
type: 'ERC-20',
},
token_id: null,
value: '872500000000',
};
export const erc20c: AddressTokenBalance = {
token: {
address: '0xc1116c98ba622a6218433fF90a2E40DEa482d7A7',
decimals: '18',
exchange_rate: '1328.89',
holders: '17',
name: 'Ethereum',
symbol: 'ETH',
total_supply: '1000000000000000000000000',
type: 'ERC-20',
},
token_id: null,
value: '9852000000000000000000',
};
export const erc20d: AddressTokenBalance = {
token: {
address: '0xCc7bb2D219A0FC08033E130629C2B854b7bA9195',
decimals: '18',
exchange_rate: null,
holders: '102625',
name: 'Zeta',
symbol: 'ZETA',
total_supply: '2100000000000000000000000000',
type: 'ERC-20',
},
token_id: null,
value: '39000000000000000000',
};
export const erc721a: AddressTokenBalance = {
token: {
address: '0xDe7cAc71E072FCBd4453E5FB3558C2684d1F88A0',
decimals: null,
exchange_rate: null,
holders: '7',
name: 'HyFi Athena',
symbol: 'HYFI_ATHENA',
total_supply: '105',
type: 'ERC-721',
},
token_id: null,
value: '51',
};
export const erc721b: AddressTokenBalance = {
token: {
address: '0xA8d5C7beEA8C9bB57f5fBa35fB638BF45550b11F',
decimals: null,
exchange_rate: null,
holders: '2',
name: 'World Of Women Galaxy',
symbol: 'WOWG',
total_supply: null,
type: 'ERC-721',
},
token_id: null,
value: '1',
};
export const erc721c: AddressTokenBalance = {
token: {
address: '0x47646F1d7dc4Dd2Db5a41D092e2Cf966e27A4992',
decimals: null,
exchange_rate: null,
holders: '12',
name: 'Puma',
symbol: 'PUMA',
total_supply: null,
type: 'ERC-721',
},
token_id: null,
value: '5',
};
export const erc1155a: AddressTokenBalance = {
token: {
address: '0x4b333DEd10c7ca855EA2C8D4D90A0a8b73788c8e',
decimals: null,
exchange_rate: null,
holders: '22',
name: 'HyFi Membership',
symbol: 'HYFI_MEMBERSHIP',
total_supply: '482',
type: 'ERC-1155',
},
token_id: '42',
value: '24',
};
export const erc1155b: AddressTokenBalance = {
token: {
address: '0xf4b71b179132ad457f6bcae2a55efa9e4b26eefc',
decimals: null,
exchange_rate: null,
holders: '100',
name: 'WinkyVerse Collections',
symbol: 'WVC',
total_supply: '4943',
type: 'ERC-1155',
},
token_id: '100010000000001',
value: '11',
};
export const erc1155withoutName: AddressTokenBalance = {
token: {
address: '0x4b333DEd10c7ca855EA2C8D4D90A0a8b73788c8e',
decimals: null,
exchange_rate: null,
holders: '22',
name: null,
symbol: null,
total_supply: '482',
type: 'ERC-1155',
},
token_id: '64532245',
value: '42',
};
export const baseList = [
erc20a,
erc20b,
erc20c,
erc721a,
erc721b,
erc721c,
erc1155withoutName,
erc1155a,
erc1155b,
];
import type { Block, BlocksResponse } from 'types/api/block';
export const base: Block = {
base_fee_per_gas: '10000000000',
burnt_fees: '5449200000000000',
burnt_fees_percentage: 20.292245650793845,
difficulty: '340282366920938463463374607431768211454',
extra_data: 'TODO',
gas_limit: '12500000',
gas_target_percentage: -91.28128,
gas_used: '544920',
gas_used_percentage: 4.35936,
hash: '0xccc75136de485434d578b73df66537c06b34c3c9b12d085daf95890c914fc2bc',
height: 30146364,
miner: {
hash: '0xdAd49e6CbDE849353ab27DeC6319E687BFc91A41',
implementation_name: null,
is_contract: false,
is_verified: null,
name: 'Alex Emelyanov',
private_tags: [],
public_tags: [],
watchlist_names: [],
},
nonce: '0x0000000000000000',
parent_hash: '0x44125f0eb36a9d942e0c23bb4e8117f7ba86a9537a69b59c0025986ed2b7500f',
priority_fee: '23211757500000000',
rewards: [
{
reward: '500000000000000000',
type: 'POA Mania Reward',
},
{
reward: '1026853607500000000',
type: 'Validator Reward',
},
{
reward: '500000000000000000',
type: 'Emission Reward',
},
],
size: 2448,
state_root: 'TODO',
timestamp: '2022-11-11T11:59:35Z',
total_difficulty: '10258276095980170141167591583995189665817672619',
tx_count: 5,
tx_fees: '26853607500000000',
type: 'block',
uncles_hashes: [],
};
export const genesis = {
base_fee_per_gas: null,
burnt_fees: null,
burnt_fees_percentage: null,
difficulty: '131072',
extra_data: 'TODO',
gas_limit: '6700000',
gas_target_percentage: -100,
gas_used: '0',
gas_used_percentage: 0,
hash: '0x39f02c003dde5b073b3f6e1700fc0b84b4877f6839bb23edadd3d2d82a488634',
height: 0,
miner: {
hash: '0x0000000000000000000000000000000000000000',
implementation_name: null,
is_contract: false,
is_verified: null,
name: null,
private_tags: [],
public_tags: [],
watchlist_names: [],
},
nonce: '0x0000000000000000',
parent_hash: '0x0000000000000000000000000000000000000000000000000000000000000000',
priority_fee: null,
rewards: [],
size: 533,
state_root: 'TODO',
timestamp: '2017-12-16T00:13:24.000000Z',
total_difficulty: '131072',
tx_count: 0,
tx_fees: '0',
type: 'block',
uncles_hashes: [],
};
export const base2: Block = {
...base,
height: base.height - 1,
size: 592,
miner: {
hash: '0xDfE10D55d9248B2ED66f1647df0b0A46dEb25165',
implementation_name: null,
is_contract: false,
is_verified: null,
name: 'Kiryl Ihnatsyeu',
private_tags: [],
public_tags: [],
watchlist_names: [],
},
timestamp: '2022-11-11T11:46:05Z',
tx_count: 253,
gas_target_percentage: 23.6433,
gas_used: '6333342',
gas_used_percentage: 87.859504,
burnt_fees: '232438000000000000',
burnt_fees_percentage: 65.3333333333334,
rewards: [
{
reward: '500000000000000000',
type: 'Chore Reward',
},
{
reward: '1017432850000000000',
type: 'Miner Reward',
},
{
reward: '500000000000000000',
type: 'Emission Reward',
},
],
};
export const baseListResponse: BlocksResponse = {
items: [
base,
base2,
],
next_page_params: null,
};
export const base = {
chart_data: [
{
date: '2022-11-28',
tx_count: 26815,
},
{
date: '2022-11-27',
tx_count: 34784,
},
{
date: '2022-11-26',
tx_count: 77527,
},
{
date: '2022-11-25',
tx_count: 39687,
},
{
date: '2022-11-24',
tx_count: 40752,
},
{
date: '2022-11-23',
tx_count: 32569,
},
{
date: '2022-11-22',
tx_count: 34449,
},
{
date: '2022-11-21',
tx_count: 106047,
},
{
date: '2022-11-20',
tx_count: 107713,
},
{
date: '2022-11-19',
tx_count: 96311,
},
{
date: '2022-11-18',
tx_count: 30828,
},
{
date: '2022-11-17',
tx_count: 27422,
},
{
date: '2022-11-16',
tx_count: 75898,
},
{
date: '2022-11-15',
tx_count: 84084,
},
{
date: '2022-11-14',
tx_count: 62266,
},
{
date: '2022-11-13',
tx_count: 22338,
},
{
date: '2022-11-12',
tx_count: 86764,
},
{
date: '2022-11-11',
tx_count: 79493,
},
{
date: '2022-11-10',
tx_count: 92887,
},
{
date: '2022-11-09',
tx_count: 43691,
},
{
date: '2022-11-08',
tx_count: 74197,
},
{
date: '2022-11-07',
tx_count: 58131,
},
{
date: '2022-11-06',
tx_count: 62477,
},
{
date: '2022-11-05',
tx_count: 82897,
},
{
date: '2022-11-04',
tx_count: 91725,
},
{
date: '2022-11-03',
tx_count: 83667,
},
{
date: '2022-11-02',
tx_count: 63743,
},
{
date: '2022-11-01',
tx_count: 152059,
},
{
date: '2022-10-31',
tx_count: 62519,
},
{
date: '2022-10-30',
tx_count: 48569,
},
{
date: '2022-10-29',
tx_count: 36789,
},
],
};
import type { HomeStats } from 'types/api/stats';
export const base: HomeStats = {
average_block_time: 6212.0,
coin_price: '0.00199678',
gas_prices: {
average: 48.0,
fast: 67.5,
slow: 48.0,
},
gas_used_today: '4108680603',
market_cap: '330809.96443288102524',
network_utilization_percentage: 1.55372064,
static_gas_price: '10',
total_addresses: '19667249',
total_blocks: '30215608',
total_gas_used: '0',
total_transactions: '82258122',
transactions_today: '26815',
};
import type { TokenTransfer, TokenTransferResponse } from 'types/api/tokenTransfer';
export const erc20: TokenTransfer = {
from: {
hash: '0xd789a607CEac2f0E14867de4EB15b15C9FFB5859',
implementation_name: null,
is_contract: true,
is_verified: true,
name: 'ArianeeStore',
private_tags: [],
public_tags: [],
watchlist_names: [],
},
to: {
hash: '0x7d20a8D54F955b4483A66aB335635ab66e151c51',
implementation_name: null,
is_contract: true,
is_verified: false,
name: null,
private_tags: [],
public_tags: [],
watchlist_names: [],
},
token: {
address: '0x55d536e4d6c1993d8ef2e2a4ef77f02088419420',
decimals: '18',
exchange_rate: null,
holders: '46554',
name: 'ARIANEE',
symbol: 'ARIA',
type: 'ERC-20',
total_supply: '0',
},
total: {
decimals: '18',
value: '31567373703130350',
},
tx_hash: '0x62d597ebcf3e8d60096dd0363bc2f0f5e2df27ba1dacd696c51aa7c9409f3193',
type: 'token_transfer',
};
export const erc721: TokenTransfer = {
from: {
hash: '0x621C2a125ec4A6D8A7C7A655A18a2868d35eb43C',
implementation_name: null,
is_contract: false,
is_verified: false,
name: null,
private_tags: [],
public_tags: [],
watchlist_names: [],
},
to: {
hash: '0x47eE48AEBc4ab9Ed908b805b8c8dAAa71B31Db1A',
implementation_name: null,
is_contract: false,
is_verified: false,
name: null,
private_tags: [],
public_tags: [],
watchlist_names: [],
},
token: {
address: '0x363574E6C5C71c343d7348093D84320c76d5Dd29',
decimals: null,
exchange_rate: null,
holders: '63090',
name: 'Arianee Smart-Asset',
symbol: 'AriaSA',
type: 'ERC-721',
total_supply: '0',
},
total: {
token_id: '875879856',
},
tx_hash: '0xf13bc7afe5e02b494dd2f22078381d36a4800ef94a0ccc147431db56c301e6cc',
type: 'token_transfer',
};
export const erc1155: TokenTransfer = {
from: {
hash: '0x0000000000000000000000000000000000000000',
implementation_name: null,
is_contract: false,
is_verified: false,
name: null,
private_tags: [],
public_tags: [],
watchlist_names: [],
},
to: {
hash: '0xBb36c792B9B45Aaf8b848A1392B0d6559202729E',
implementation_name: null,
is_contract: false,
is_verified: false,
name: null,
private_tags: [],
public_tags: [],
watchlist_names: [],
},
token: {
address: '0xF56b7693E4212C584de4a83117f805B8E89224CB',
decimals: null,
exchange_rate: null,
holders: '1',
name: null,
symbol: null,
type: 'ERC-1155',
total_supply: '0',
},
total: {
token_id: '123',
value: '42',
decimals: null,
},
tx_hash: '0x05d6589367633c032d757a69c5fb16c0e33e3994b0d9d1483f82aeee1f05d746',
type: 'token_minting',
};
export const erc1155multiple: TokenTransfer = {
...erc1155,
token: {
...erc1155.token,
name: 'OLYMPIC',
},
total: [
{ token_id: '456', value: '42', decimals: null },
{ token_id: '12345678', value: '142', decimals: null },
{ token_id: '1000006457499', value: '11', decimals: null },
],
};
export const mixTokens: TokenTransferResponse = {
items: [
erc20,
erc721,
erc1155,
erc1155multiple,
],
next_page_params: null,
};
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.
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.
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.
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.
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