Commit a31baf08 authored by tom's avatar tom

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

parents 2f73b6b1 b50a0405
Dockerfile Dockerfile
.dockerignore .dockerignore
node_modules node_modules
node_modules_linux
npm-debug.log npm-debug.log
README.md README.md
.next .next
......
...@@ -4,10 +4,13 @@ NEXT_PUBLIC_APP_INSTANCE=__PLACEHOLDER_FOR_NEXT_PUBLIC_APP_INSTANCE__ ...@@ -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_PROTOCOL=__PLACEHOLDER_FOR_NEXT_PUBLIC_APP_PROTOCOL__
NEXT_PUBLIC_APP_HOST=__PLACEHOLDER_FOR_NEXT_PUBLIC_APP_HOST__ NEXT_PUBLIC_APP_HOST=__PLACEHOLDER_FOR_NEXT_PUBLIC_APP_HOST__
NEXT_PUBLIC_APP_PORT=__PLACEHOLDER_FOR_NEXT_PUBLIC_APP_PORT__ NEXT_PUBLIC_APP_PORT=__PLACEHOLDER_FOR_NEXT_PUBLIC_APP_PORT__
NEXT_PUBLIC_AUTH_URL=__PLACEHOLDER_FOR_NEXT_PUBLIC_AUTH_URL__
# network config # network config
NEXT_PUBLIC_NETWORK_NAME=__PLACEHOLDER_FOR_NEXT_PUBLIC_NETWORK_NAME__ 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_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_ASSETS_PATHNAME=__PLACEHOLDER_FOR_NEXT_PUBLIC_NETWORK_ASSETS_PATHNAME__
NEXT_PUBLIC_NETWORK_TYPE=__PLACEHOLDER_FOR_NEXT_PUBLIC_NETWORK_TYPE__ NEXT_PUBLIC_NETWORK_TYPE=__PLACEHOLDER_FOR_NEXT_PUBLIC_NETWORK_TYPE__
NEXT_PUBLIC_NETWORK_ID=__PLACEHOLDER_FOR_NEXT_PUBLIC_NETWORK_ID__ 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 ...@@ -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_MARKETPLACE_SUBMIT_FORM=__PLACEHOLDER_FOR_NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM__
NEXT_PUBLIC_LOGOUT_URL=__PLACEHOLDER_FOR_NEXT_PUBLIC_LOGOUT_URL__ NEXT_PUBLIC_LOGOUT_URL=__PLACEHOLDER_FOR_NEXT_PUBLIC_LOGOUT_URL__
NEXT_PUBLIC_LOGOUT_RETURN_URL=__PLACEHOLDER_FOR_NEXT_PUBLIC_LOGOUT_RETURN_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 # api config
NEXT_PUBLIC_API_HOST=__PLACEHOLDER_FOR_NEXT_PUBLIC_API_HOST__ NEXT_PUBLIC_API_HOST=__PLACEHOLDER_FOR_NEXT_PUBLIC_API_HOST__
NEXT_PUBLIC_API_BASE_PATH=__PLACEHOLDER_FOR_NEXT_PUBLIC_API_BASE_PATH__ 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 # external services config
NEXT_PUBLIC_SENTRY_DSN=__PLACEHOLDER_FOR_NEXT_PUBLIC_SENTRY_DSN__ NEXT_PUBLIC_SENTRY_DSN=__PLACEHOLDER_FOR_NEXT_PUBLIC_SENTRY_DSN__
......
node_modules node_modules
\ No newline at end of file node_modules_linux
playwright/envs.js
...@@ -18,6 +18,8 @@ module.exports = { ...@@ -18,6 +18,8 @@ module.exports = {
'plugin:regexp/recommended', 'plugin:regexp/recommended',
'plugin:@typescript-eslint/eslint-recommended', 'plugin:@typescript-eslint/eslint-recommended',
'plugin:@typescript-eslint/recommended', 'plugin:@typescript-eslint/recommended',
'plugin:jest/recommended',
'plugin:playwright/playwright-test',
], ],
plugins: [ plugins: [
'es5', 'es5',
...@@ -27,6 +29,7 @@ module.exports = { ...@@ -27,6 +29,7 @@ module.exports = {
'react-hooks', 'react-hooks',
'jsx-a11y', 'jsx-a11y',
'eslint-plugin-import-helpers', 'eslint-plugin-import-helpers',
'jest',
], ],
parser: '@typescript-eslint/parser', parser: '@typescript-eslint/parser',
parserOptions: { parserOptions: {
...@@ -195,7 +198,7 @@ module.exports = { ...@@ -195,7 +198,7 @@ module.exports = {
groups: [ groups: [
'module', 'module',
'/types/', '/types/',
[ '/^configs/', '/^data/', '/^deploy/', '/^icons/', '/^lib/', '/^pages/', '/^playwright/', '/^theme/', '/^ui/' ], [ '/^configs/', '/^data/', '/^deploy/', '/^icons/', '/^lib/', '/^mocks/', '/^pages/', '/^playwright/', '/^theme/', '/^ui/' ],
[ 'parent', 'sibling', 'index' ], [ 'parent', 'sibling', 'index' ],
], ],
alphabetize: { order: 'asc', ignoreCase: true }, alphabetize: { order: 'asc', ignoreCase: true },
......
...@@ -11,5 +11,6 @@ jobs: ...@@ -11,5 +11,6 @@ jobs:
cleanup: cleanup:
uses: blockscout/blockscout-ci-cd/.github/workflows/cleanup.yaml@master uses: blockscout/blockscout-ci-cd/.github/workflows/cleanup.yaml@master
with: with:
appNamespace: review-front-$GITHUB_HEAD_REF_SLUG appNamespace: review-front-$GITHUB_REF_NAME_SLUG
dockerImage: prerelease-$GITHUB_REF_NAME_SLUG
secrets: inherit secrets: inherit
...@@ -2,8 +2,8 @@ name: Run E2E tests k8s ...@@ -2,8 +2,8 @@ name: Run E2E tests k8s
on: on:
# push: # push:
# pull_request: pull_request:
workflow_dispatch workflow_dispatch:
env: env:
K8S_LOCAL_PORT: ${{ secrets.K8S_LOCAL_PORT }} K8S_LOCAL_PORT: ${{ secrets.K8S_LOCAL_PORT }}
......
...@@ -3,8 +3,6 @@ name: Deploy review environment ...@@ -3,8 +3,6 @@ name: Deploy review environment
on: on:
pull_request: pull_request:
# push: # push:
# branches-ignore:
# - 'main'
workflow_dispatch: workflow_dispatch:
env: env:
...@@ -25,6 +23,9 @@ jobs: ...@@ -25,6 +23,9 @@ jobs:
- name: Check out the repo - name: Check out the repo
uses: actions/checkout@v3 uses: actions/checkout@v3
- name: Inject slug/short variables
uses: rlespinasse/github-slug-action@v4
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2 uses: docker/setup-buildx-action@v2
...@@ -47,7 +48,7 @@ jobs: ...@@ -47,7 +48,7 @@ jobs:
- name: Add outputs - name: Add outputs
run: | 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 id: output-step
- name: Build and push - name: Build and push
...@@ -58,21 +59,22 @@ jobs: ...@@ -58,21 +59,22 @@ jobs:
push: true push: true
cache-from: type=gha cache-from: type=gha
cache-to: type=gha,mode=max 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 }} labels: ${{ steps.meta.outputs.labels }}
build-args: | build-args: |
GIT_COMMIT_SHA=${{ env.SHORT_SHA }} GIT_COMMIT_SHA=${{ env.GITHUB_EVENT_PULL_REQUEST_HEAD_SHA_SHORT }}
deploy_frontend: deploy_frontend:
name: Deploy frontend app name: Deploy frontend app
needs: push_to_registry needs: push_to_registry
uses: blockscout/blockscout-ci-cd/.github/workflows/deploy.yaml@master uses: blockscout/blockscout-ci-cd/.github/workflows/deploy.yaml@master
with: with:
env_vars: VALUES_DIR=deploy/values/review,APP_NAME=bs-stack env_vars: VALUES_DIR=deploy/values/review,APP_NAME=bs-stack,DOCKER_IMAGE=prerelease-$GITHUB_REF_NAME_SLUG
appNamespace: review-front-$GITHUB_HEAD_REF_SLUG globalEnv: review
appNamespace: review-front-$GITHUB_REF_NAME_SLUG
blockscoutIngressHost: blockscout blockscoutIngressHost: blockscout
frontendIngressHost: 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 gethIngressHost: geth
scVerifierIngressHost: sc-verifier scVerifierIngressHost: sc-verifier
secrets: inherit secrets: inherit
name: Playwright name: Unit tests
on: [pull_request] on:
pull_request:
push:
branches:
- main
jobs: jobs:
test: jest_tests:
name: Run components visual 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 runs-on: ubuntu-latest
container: container:
image: mcr.microsoft.com/playwright:v1.27.0-focal image: mcr.microsoft.com/playwright:v1.28.0-focal
steps: steps:
- run: apt-get update && apt-get install git-lfs - run: apt-get update && apt-get install git-lfs
...@@ -14,12 +33,12 @@ jobs: ...@@ -14,12 +33,12 @@ jobs:
lfs: 'true' lfs: 'true'
- uses: actions/setup-node@v3 - uses: actions/setup-node@v3
with: with:
node-version: '16' node-version: 16
cache: 'yarn' cache: 'yarn'
- name: Install dependencies - name: Install dependencies
run: yarn install --frozen-lockfile run: yarn install --frozen-lockfile
- name: Run your tests - name: Run your tests
run: HOME=/root yarn test-ct run: HOME=/root yarn test:pw
- name: Upload test results - name: Upload test results
if: always() if: always()
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v2
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
# dependencies # dependencies
/node_modules /node_modules
/node_modules_linux
/.pnp /.pnp
.pnp.js .pnp.js
...@@ -29,6 +30,7 @@ yarn-error.log* ...@@ -29,6 +30,7 @@ yarn-error.log*
# local env files # local env files
.env*.local .env*.local
/configs/envs/.env.secrets /configs/envs/.env.secrets
/configs/envs/.samples
# vercel # vercel
.vercel .vercel
...@@ -45,3 +47,5 @@ yarn-error.log* ...@@ -45,3 +47,5 @@ yarn-error.log*
/test-results/ /test-results/
/playwright-report/ /playwright-report/
/playwright/.cache/ /playwright/.cache/
/playwright/.browser/
/playwright/envs.js
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
"detail": "start local dev server", "detail": "start local dev server",
"presentation": { "presentation": {
"reveal": "silent", "reveal": "silent",
"panel": "new", "panel": "dedicated",
"close": true, "close": true,
"revealProblems": "onProblem", "revealProblems": "onProblem",
}, },
...@@ -31,7 +31,27 @@ ...@@ -31,7 +31,27 @@
"detail": "start local dev server for POA network", "detail": "start local dev server for POA network",
"presentation": { "presentation": {
"reveal": "silent", "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, "close": true,
"revealProblems": "onProblem", "revealProblems": "onProblem",
}, },
...@@ -57,7 +77,7 @@ ...@@ -57,7 +77,7 @@
}, },
"presentation": { "presentation": {
"reveal": "never", "reveal": "never",
"panel": "new", "panel": "dedicated",
"close": true, "close": true,
"revealProblems": "onProblem", "revealProblems": "onProblem",
}, },
...@@ -71,7 +91,7 @@ ...@@ -71,7 +91,7 @@
"detail": "run eslint", "detail": "run eslint",
"presentation": { "presentation": {
"reveal": "silent", "reveal": "silent",
"panel": "new", "panel": "dedicated",
"revealProblems": "onProblem", "revealProblems": "onProblem",
}, },
"icon": { "icon": {
...@@ -82,17 +102,97 @@ ...@@ -82,17 +102,97 @@
"instanceLimit": 1 "instanceLimit": 1
} }
}, },
// PW TESTS
{ {
"type": "npm", "type": "shell",
"script": "test-docker", "command": "${input:pwDebugFlag} yarn test:pw:local ${relativeFileDirname}/${fileBasename} ${input:pwArgs}",
"problemMatcher": [], "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", "detail": "run visual components tests",
"presentation": { "presentation": {
"reveal": "silent", "reveal": "always",
"panel": "new", "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, "close": true,
"revealProblems": "onProblem", "focus": true,
}, },
"icon": { "icon": {
"color": "terminal.ansiBlue", "color": "terminal.ansiBlue",
...@@ -102,6 +202,26 @@ ...@@ -102,6 +202,26 @@
"instanceLimit": 1 "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", "type": "npm",
"script": "build:docker", "script": "build:docker",
...@@ -110,9 +230,10 @@ ...@@ -110,9 +230,10 @@
"detail": "build docker image", "detail": "build docker image",
"presentation": { "presentation": {
"reveal": "always", "reveal": "always",
"panel": "new", "panel": "dedicated",
"close": true, "close": true,
"revealProblems": "onProblem", "revealProblems": "onProblem",
"focus": true,
}, },
"icon": { "icon": {
"color": "terminal.ansiRed", "color": "terminal.ansiRed",
...@@ -130,7 +251,7 @@ ...@@ -130,7 +251,7 @@
"detail": "run docker container for POA network", "detail": "run docker container for POA network",
"presentation": { "presentation": {
"reveal": "silent", "reveal": "silent",
"panel": "new", "panel": "dedicated",
"close": true, "close": true,
"revealProblems": "onProblem", "revealProblems": "onProblem",
}, },
...@@ -150,7 +271,7 @@ ...@@ -150,7 +271,7 @@
"detail": "format svg files with svgo", "detail": "format svg files with svgo",
"presentation": { "presentation": {
"reveal": "silent", "reveal": "silent",
"panel": "new", "panel": "dedicated",
"close": true, "close": true,
"revealProblems": "onProblem", "revealProblems": "onProblem",
}, },
...@@ -162,5 +283,29 @@ ...@@ -162,5 +283,29 @@
"instanceLimit": 1 "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: ...@@ -30,7 +30,7 @@ For local development please follow next steps:
## Components visual testing ## 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. 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 ## Environment variables
...@@ -52,6 +52,7 @@ The app instance could be customized by passing following variables to NodeJS en ...@@ -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_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_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_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` | | NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED | `boolean` *(optional)* | Set to true if network has account feature | `true` |
### UI configuration ### UI configuration
...@@ -64,13 +65,19 @@ The app instance could be customized by passing following variables to NodeJS en ...@@ -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_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_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_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_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_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_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_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_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 ### App configuration
| Variable | Type | Description | Default value | Variable | Type | Description | Default value
...@@ -87,6 +94,7 @@ The app instance could be customized by passing following variables to NodeJS en ...@@ -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_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_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 ### Featured network configuration properties
...@@ -122,6 +130,7 @@ The app instance could be customized by passing following variables to NodeJS en ...@@ -122,6 +130,7 @@ The app instance could be customized by passing following variables to NodeJS en
| Property | Type | Description | Example value | Property | Type | Description | Example value
| --- | --- | --- | --- | | --- | --- | --- | --- |
| id | `string` | Used as slug for the app. Must be unique in the app list. | `'app'` | | 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'` | | 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'` | | 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'` | | shortDescription | `string` | Displayed only in the app list. | `'Awesome app'` |
......
/// <reference types="next-react-svg" />
/* eslint-disable no-restricted-properties */ /* eslint-disable no-restricted-properties */
import type { AppItemOverview } from 'types/client/apps'; import type { AppItemOverview } from 'types/client/apps';
import type { FeaturedNetwork, NetworkExplorer, PreDefinedNetwork } from 'types/networks'; import type { FeaturedNetwork, NetworkExplorer, PreDefinedNetwork } from 'types/networks';
import type { ChainIndicatorId } from 'ui/home/indicators/types';
const getEnvValue = (env: string | undefined) => env?.replaceAll('\'', '"'); const getEnvValue = (env: string | undefined) => env?.replaceAll('\'', '"');
const parseEnvJson = <DataType>(env: string | undefined): DataType | null => { const parseEnvJson = <DataType>(env: string | undefined): DataType | null => {
...@@ -10,9 +11,9 @@ const parseEnvJson = <DataType>(env: string | undefined): DataType | null => { ...@@ -10,9 +11,9 @@ const parseEnvJson = <DataType>(env: string | undefined): DataType | null => {
return 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 isDev = env === 'development';
const appPort = getEnvValue(process.env.NEXT_PUBLIC_APP_PORT); const appPort = getEnvValue(process.env.NEXT_PUBLIC_APP_PORT);
...@@ -21,9 +22,10 @@ const appHost = getEnvValue(process.env.NEXT_PUBLIC_APP_HOST); ...@@ -21,9 +22,10 @@ const appHost = getEnvValue(process.env.NEXT_PUBLIC_APP_HOST);
const baseUrl = [ const baseUrl = [
appSchema || 'https', appSchema || 'https',
'://', '://',
process.env.NEXT_PUBLIC_VERCEL_URL || appHost, appHost,
appPort && ':' + appPort, appPort && ':' + appPort,
].filter(Boolean).join(''); ].filter(Boolean).join('');
const authUrl = getEnvValue(process.env.NEXT_PUBLIC_AUTH_URL) || baseUrl;
const apiHost = getEnvValue(process.env.NEXT_PUBLIC_API_HOST); const apiHost = getEnvValue(process.env.NEXT_PUBLIC_API_HOST);
const logoutUrl = (() => { const logoutUrl = (() => {
...@@ -52,6 +54,7 @@ const config = Object.freeze({ ...@@ -52,6 +54,7 @@ const config = Object.freeze({
network: { network: {
type: getEnvValue(process.env.NEXT_PUBLIC_NETWORK_TYPE) as PreDefinedNetwork | undefined, type: getEnvValue(process.env.NEXT_PUBLIC_NETWORK_TYPE) as PreDefinedNetwork | undefined,
logo: getEnvValue(process.env.NEXT_PUBLIC_NETWORK_LOGO), 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), name: getEnvValue(process.env.NEXT_PUBLIC_NETWORK_NAME),
id: getEnvValue(process.env.NEXT_PUBLIC_NETWORK_ID), id: getEnvValue(process.env.NEXT_PUBLIC_NETWORK_ID),
shortName: getEnvValue(process.env.NEXT_PUBLIC_NETWORK_SHORT_NAME), shortName: getEnvValue(process.env.NEXT_PUBLIC_NETWORK_SHORT_NAME),
...@@ -59,9 +62,9 @@ const config = Object.freeze({ ...@@ -59,9 +62,9 @@ const config = Object.freeze({
name: getEnvValue(process.env.NEXT_PUBLIC_NETWORK_CURRENCY_NAME), name: getEnvValue(process.env.NEXT_PUBLIC_NETWORK_CURRENCY_NAME),
symbol: getEnvValue(process.env.NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL), symbol: getEnvValue(process.env.NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL),
decimals: Number(getEnvValue(process.env.NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS)) || DEFAULT_CURRENCY_DECIMALS, 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), 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)) || [], explorers: parseEnvJson<Array<NetworkExplorer>>(getEnvValue(process.env.NEXT_PUBLIC_NETWORK_EXPLORERS)) || [],
verificationType: process.env.NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE || 'mining', verificationType: process.env.NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE || 'mining',
}, },
...@@ -80,12 +83,27 @@ const config = Object.freeze({ ...@@ -80,12 +83,27 @@ const config = Object.freeze({
host: appHost, host: appHost,
port: appPort, port: appPort,
baseUrl, baseUrl,
authUrl,
logoutUrl, 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: { api: {
endpoint: apiHost ? `https://${ apiHost }` : 'https://blockscout.com', endpoint: apiHost ? `https://${ apiHost }` : 'https://blockscout.com',
socket: apiHost ? `wss://${ apiHost }` : 'wss://blockscout.com', socket: apiHost ? `wss://${ apiHost }` : 'wss://blockscout.com',
basePath: stripTrailingSlash(getEnvValue(process.env.NEXT_PUBLIC_API_BASE_PATH) || ''), basePath: stripTrailingSlash(getEnvValue(process.env.NEXT_PUBLIC_API_BASE_PATH) || ''),
}, },
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; export default config;
...@@ -12,3 +12,4 @@ NEXT_PUBLIC_FOOTER_TWITTER_LINK=https://www.twitter.com/blockscoutcom ...@@ -12,3 +12,4 @@ NEXT_PUBLIC_FOOTER_TWITTER_LINK=https://www.twitter.com/blockscoutcom
# api config # api config
NEXT_PUBLIC_API_HOST=blockscout.com 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 = [ const oldUrls = [
{ {
......
import type { NextjsOptions } from '@sentry/nextjs/types/utils/nextjsOptions'; import type { NextjsOptions } from '@sentry/nextjs/types/utils/nextjsOptions';
const config: 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, 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 // We recommend adjusting this value in production, or using tracesSampler
// for finer control // for finer control
tracesSampleRate: 1.0, tracesSampleRate: 1.0,
......
...@@ -2,9 +2,9 @@ import type * as Sentry from '@sentry/react'; ...@@ -2,9 +2,9 @@ import type * as Sentry from '@sentry/react';
import { BrowserTracing } from '@sentry/tracing'; import { BrowserTracing } from '@sentry/tracing';
export const config: Sentry.BrowserOptions = { export const config: Sentry.BrowserOptions = {
environment: process.env.VERCEL_ENV || process.env.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, 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() ], integrations: [ new BrowserTracing() ],
// We recommend adjusting this value in production, or using tracesSampler // We recommend adjusting this value in production, or using tracesSampler
// for finer control // for finer control
......
This diff is collapsed.
This diff is collapsed.
...@@ -18,9 +18,9 @@ function replace_envs { ...@@ -18,9 +18,9 @@ function replace_envs {
# split # split
configName="$(cut -d'=' -f1 <<<"$line")" configName="$(cut -d'=' -f1 <<<"$line")"
configValue="$(cut -d'=' -f2 <<<"$line")" configValue="$(cut -d'=' -f2- <<<"$line")"
# get system env # get system env
envValue=$(env | grep "^$configName=" | grep -oe '[^=]*$'); envValue=$(env | grep "^$configName=" | sed "s/^$configName=//g");
# if config found # if config found
if [ -n "$configValue" ]; then if [ -n "$configValue" ]; then
......
This diff is collapsed.
This diff is collapsed.
...@@ -27,7 +27,7 @@ blockscout: ...@@ -27,7 +27,7 @@ blockscout:
ACCOUNT_SENDGRID_TEMPLATE: ACCOUNT_SENDGRID_TEMPLATE:
_default: ENC[AES256_GCM,data:teIknLFnYfGDD7drMUTvcoXBxR9rOjc8L8nOVtMYVOuzXQ==,iv:LoBlBjrWDf5/LsmBo58510evXBTxiIW1YekeLmB+Mow=,tag:dZ/YdphM5MxEygeFYlT0Nw==,type:str] _default: ENC[AES256_GCM,data:teIknLFnYfGDD7drMUTvcoXBxR9rOjc8L8nOVtMYVOuzXQ==,iv:LoBlBjrWDf5/LsmBo58510evXBTxiIW1YekeLmB+Mow=,tag:dZ/YdphM5MxEygeFYlT0Nw==,type:str]
ACCOUNT_PUBLIC_TAGS_AIRTABLE_URL: 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: ACCOUNT_PUBLIC_TAGS_AIRTABLE_API_KEY:
_default: ENC[AES256_GCM,data:WBxTPe+pQ2VzSPRmL3mzek0=,iv:0wYDv0BRmXrmz9EJg0cBclHpsPEQukO7tCnPAUlCKrs=,tag:3Zcyf/8kLU6ohUQ+97eONg==,type:str] _default: ENC[AES256_GCM,data:WBxTPe+pQ2VzSPRmL3mzek0=,iv:0wYDv0BRmXrmz9EJg0cBclHpsPEQukO7tCnPAUlCKrs=,tag:3Zcyf/8kLU6ohUQ+97eONg==,type:str]
ACCOUNT_CLOAK_KEY: ACCOUNT_CLOAK_KEY:
...@@ -60,12 +60,12 @@ geth: ...@@ -60,12 +60,12 @@ geth:
files: files:
list: 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] 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: "" password.txt: ""
frontend: frontend:
environment: environment:
NEXT_PUBLIC_NETWORK_TOKEN_ADDRESS: 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: NEXT_PUBLIC_SENTRY_DSN:
_default: ENC[AES256_GCM,data:n/H2AH2n9ovn265iFbbrqeOOWS3s7FXgDv5FXJ2Dz133GuhTaqIU5psWjTraZ/Vh+dVM8M9zBLFfUanCWOz7cerq/QUoSVZjKvIvcyp6F072DGU=,iv:Co/pSR+U0vfkmWR+LDpxcQKDJl0WNbaEZihpzrRJcbc=,tag:HUiCX8WGJXWCDB59Y6iIbw==,type:str] _default: ENC[AES256_GCM,data:n/H2AH2n9ovn265iFbbrqeOOWS3s7FXgDv5FXJ2Dz133GuhTaqIU5psWjTraZ/Vh+dVM8M9zBLFfUanCWOz7cerq/QUoSVZjKvIvcyp6F072DGU=,iv:Co/pSR+U0vfkmWR+LDpxcQKDJl0WNbaEZihpzrRJcbc=,tag:HUiCX8WGJXWCDB59Y6iIbw==,type:str]
SENTRY_CSP_REPORT_URI: SENTRY_CSP_REPORT_URI:
...@@ -78,8 +78,8 @@ sops: ...@@ -78,8 +78,8 @@ sops:
azure_kv: [] azure_kv: []
hc_vault: [] hc_vault: []
age: [] age: []
lastmodified: "2022-11-01T14:58:11Z" lastmodified: "2022-11-28T16:58:46Z"
mac: ENC[AES256_GCM,data:3AK4GRnUnAcQrdJ9JrdhSFqMYmYhE2RGiP+NPvO+mqBGDH26pRjN3lkNhHi/uObEQWiQZJLzLEOhSPl0/oDVYRTSGEeEiIlViEm/S5PuD57uFx6ogS9Iz88G/3hnc3HpTAIg2+NVwE7wF1/NK75WlivB1pUGk7OrazhZ+Fhyn5k=,iv:94YFEq/dmS07CqsKFZ2NAKMj3LqqyUB4+2XtHI3UiLg=,tag:4uti1G9eRoL2AwF0n7avdQ==,type:str] mac: ENC[AES256_GCM,data:QJvVfWWWVDk5mI66T9J8EnEyVwmJoGEsWO9Pr8vK7jyC3rhAYD2WdKYfpkbwwMKrJzcMBe7UeaOeEY6aApuMNdobeEjsJAvstXCOBzMe5H9XtAFiAY+oxf8r4ELNvQP/gIBZSja+ehSbXBcaP4DkLn4FboaBhkoE8A37W2R6/QA=,iv:FnIC6iGLEZNwRSrbF81vF6eQuyq0yQHNPRTPrx3FB+8=,tag:LRnZCwYkCh4o8lDUcG2m9A==,type:str]
pgp: pgp:
- created_at: "2022-09-14T13:42:28Z" - created_at: "2022-09-14T13:42:28Z"
enc: | 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"> <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>
<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"> <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 31 30">
<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"/> <g fill="currentColor" clip-path="url(#wallet_svg__a)">
<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"/> <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> </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 { ...@@ -13,7 +13,7 @@ interface Props extends ChakraProviderProps {
export function Chakra({ cookies, theme, children }: Props) { export function Chakra({ cookies, theme, children }: Props) {
const colorModeManager = const colorModeManager =
typeof cookies === 'string' ? typeof cookies === 'string' ?
cookieStorageManagerSSR(cookies) : cookieStorageManagerSSR(typeof document !== 'undefined' ? document.cookie : cookies) :
localStorageManager; localStorageManager;
return ( return (
......
...@@ -8,14 +8,17 @@ import * as cookies from 'lib/cookies'; ...@@ -8,14 +8,17 @@ import * as cookies from 'lib/cookies';
// first arg can be only a string // first arg can be only a string
// FIXME migrate to RequestInfo later if needed // 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> { return function fetch(path: string, init?: RequestInit): Promise<Response> {
const headers = { const headers = {
accept: 'application/json', accept: 'application/json',
'content-type': 'application/json', 'content-type': 'application/json',
cookie: `${ cookies.NAMES.API_TOKEN }=${ _req.cookies[cookies.NAMES.API_TOKEN] }`, 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({ httpLogger.logger.info({
message: 'Trying to call API', message: 'Trying to call API',
......
...@@ -6,7 +6,7 @@ import { httpLogger } from 'lib/api/logger'; ...@@ -6,7 +6,7 @@ import { httpLogger } from 'lib/api/logger';
type Methods = 'GET' | 'POST' | 'PUT' | 'DELETE'; 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) => { const handler = async(_req: NextApiRequest, res: NextApiResponse) => {
httpLogger(_req, res); httpLogger(_req, res);
...@@ -18,8 +18,8 @@ export default function createHandler(getUrl: (_req: NextApiRequest) => string, ...@@ -18,8 +18,8 @@ export default function createHandler(getUrl: (_req: NextApiRequest) => string,
const isBodyDisallowed = _req.method === 'GET' || _req.method === 'HEAD'; const isBodyDisallowed = _req.method === 'GET' || _req.method === 'HEAD';
const url = getUrlWithNetwork(_req, `/api${ getUrl(_req) }`); const url = apiEndpoint ? `/api${ getUrl(_req) }` : getUrlWithNetwork(_req, `/api${ getUrl(_req) }`);
const fetch = fetchFactory(_req); const fetch = fetchFactory(_req, apiEndpoint);
const response = await fetch(url, { const response = await fetch(url, {
method: _req.method, method: _req.method,
body: isBodyDisallowed ? undefined : _req.body, body: isBodyDisallowed ? undefined : _req.body,
......
...@@ -7,13 +7,11 @@ type Props = { ...@@ -7,13 +7,11 @@ type Props = {
pageProps: PageProps; pageProps: PageProps;
} }
const AppContext = createContext<PageProps>({ cookies: '' }); const AppContext = createContext<PageProps>({ cookies: '', referrer: '' });
export function AppWrapper({ children, pageProps }: Props) {
const appProps = { cookies: pageProps.cookies };
export function AppContextProvider({ children, pageProps }: Props) {
return ( return (
<AppContext.Provider value={ appProps }> <AppContext.Provider value={ pageProps }>
{ children } { children }
</AppContext.Provider> </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'; ...@@ -2,22 +2,27 @@ import clamp from 'lodash/clamp';
import throttle from 'lodash/throttle'; import throttle from 'lodash/throttle';
import React from 'react'; import React from 'react';
const ScrollDirectionContext = React.createContext<'up' | 'down' | null>(null);
import isBrowser from 'lib/isBrowser'; import isBrowser from 'lib/isBrowser';
const SCROLL_DIFF_THRESHOLD = 20; const SCROLL_DIFF_THRESHOLD = 20;
type Directions = 'up' | 'down'; 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 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 handleScroll = React.useCallback(() => {
const currentScrollPosition = clamp(window.pageYOffset, 0, window.document.body.scrollHeight - window.innerHeight); const currentScrollPosition = clamp(window.pageYOffset, 0, window.document.body.scrollHeight - window.innerHeight);
const scrollDiff = currentScrollPosition - prevScrollPosition.current; const scrollDiff = currentScrollPosition - prevScrollPosition.current;
if (window.pageYOffset === 0) { if (window.pageYOffset === 0) {
setDirection(undefined); setDirection(null);
} else if (Math.abs(scrollDiff) > SCROLL_DIFF_THRESHOLD) { } else if (Math.abs(scrollDiff) > SCROLL_DIFF_THRESHOLD) {
setDirection(scrollDiff < 0 ? 'up' : 'down'); setDirection(scrollDiff < 0 ? 'up' : 'down');
} }
...@@ -33,9 +38,21 @@ export default function useScrollDirection() { ...@@ -33,9 +38,21 @@ export default function useScrollDirection() {
return () => { return () => {
window.removeEventListener('scroll', throttledHandleScroll); window.removeEventListener('scroll', throttledHandleScroll);
}; };
// replicate componentDidMount // replicate componentDidMount
// eslint-disable-next-line react-hooks/exhaustive-deps // 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 { ...@@ -7,6 +7,7 @@ export enum NAMES {
NAV_BAR_COLLAPSED='nav_bar_collapsed', NAV_BAR_COLLAPSED='nav_bar_collapsed',
API_TOKEN='_explorer_key', API_TOKEN='_explorer_key',
TXS_SORT='txs_sort', TXS_SORT='txs_sort',
COLOR_MODE='chakra-ui-color-mode',
} }
export function get(name?: NAMES | undefined | null, serverCookie?: string) { export function get(name?: NAMES | undefined | null, serverCookie?: string) {
......
...@@ -64,6 +64,9 @@ function makePolicyMap() { ...@@ -64,6 +64,9 @@ function makePolicyMap() {
'sentry.io', '*.sentry.io', 'sentry.io', '*.sentry.io',
appConfig.api.socket, appConfig.api.socket,
// ad
'request-global.czilladx.com',
], ],
'script-src': [ 'script-src': [
...@@ -77,6 +80,12 @@ function makePolicyMap() { ...@@ -77,6 +80,12 @@ function makePolicyMap() {
// hash of ColorModeScript // hash of ColorModeScript
'\'sha256-e7MRMmTzLsLQvIy1iizO1lXf7VWYoQ6ysj5fuUzvRwE=\'', '\'sha256-e7MRMmTzLsLQvIy1iizO1lXf7VWYoQ6ysj5fuUzvRwE=\'',
// ad
'coinzillatag.com',
'servedbyadbutler.com',
'\'sha256-wMOeDjJaOTjCfNjluteV+tSqHW547T89sgxd8W6tQJM=\'',
'\'sha256-FcyIn1h7zra8TVnnRhYrwrplxJW7dpD5TV7kP2AG/kI=\'',
], ],
'style-src': [ 'style-src': [
...@@ -100,20 +109,23 @@ function makePolicyMap() { ...@@ -100,20 +109,23 @@ function makePolicyMap() {
...MAIN_DOMAINS, ...MAIN_DOMAINS,
// github avatars // github assets (e.g trustwallet token icons)
'avatars.githubusercontent.com',
// other github assets (e.g trustwallet token icons)
'raw.githubusercontent.com', 'raw.githubusercontent.com',
// auth0 assets // auth0 assets and avatars
's.gravatar.com', '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 // network assets
...networkExternalAssets.map((url) => url.host), ...networkExternalAssets.map((url) => url.host),
// marketplace apps logos // marketplace apps logos
...getMarketplaceAppsLogosOrigins().map((url) => url.host), ...getMarketplaceAppsLogosOrigins().map((url) => url.host),
// ad
'servedbyadbutler.com',
], ],
'font-src': [ 'font-src': [
...@@ -124,6 +136,10 @@ function makePolicyMap() { ...@@ -124,6 +136,10 @@ function makePolicyMap() {
'fonts.googleapis.com', 'fonts.googleapis.com',
], ],
'prefetch-src': [
...MAIN_DOMAINS,
],
'object-src': [ 'object-src': [
KEY_WORDS.NONE, KEY_WORDS.NONE,
], ],
...@@ -132,7 +148,12 @@ function makePolicyMap() { ...@@ -132,7 +148,12 @@ function makePolicyMap() {
KEY_WORDS.NONE, KEY_WORDS.NONE,
], ],
'frame-src': getMarketplaceAppsOrigins(), 'frame-src': [
...getMarketplaceAppsOrigins(),
// ad
'request-global.czilladx.com',
],
...(REPORT_URI ? { ...(REPORT_URI ? {
'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'; ...@@ -9,6 +9,7 @@ import blocksIcon from 'icons/block.svg';
import privateTagIcon from 'icons/privattags.svg'; import privateTagIcon from 'icons/privattags.svg';
import profileIcon from 'icons/profile.svg'; import profileIcon from 'icons/profile.svg';
import publicTagIcon from 'icons/publictags.svg'; import publicTagIcon from 'icons/publictags.svg';
import statsIcon from 'icons/stats.svg';
import tokensIcon from 'icons/token.svg'; import tokensIcon from 'icons/token.svg';
import transactionsIcon from 'icons/transactions.svg'; import transactionsIcon from 'icons/transactions.svg';
import watchlistIcon from 'icons/watchlist.svg'; import watchlistIcon from 'icons/watchlist.svg';
...@@ -28,6 +29,7 @@ export default function useNavItems() { ...@@ -28,6 +29,7 @@ export default function useNavItems() {
{ text: 'Tokens', url: link('tokens'), icon: tokensIcon, isActive: currentRoute === 'tokens', isNewUi: false }, { text: 'Tokens', url: link('tokens'), icon: tokensIcon, isActive: currentRoute === 'tokens', isNewUi: false },
isMarketplaceFilled ? isMarketplaceFilled ?
{ text: 'Apps', url: link('apps'), icon: appsIcon, isActive: currentRoute === 'apps', isNewUi: true } : null, { 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' // there should be custom site sections like Stats, Faucet, More, etc but never an 'other'
// examples https://explorer-edgenet.polygon.technology/ and https://explorer.celo.org/ // examples https://explorer-edgenet.polygon.technology/ and https://explorer.celo.org/
// at this stage custom menu items is under development, we will implement it later // at this stage custom menu items is under development, we will implement it later
......
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 { useQuery, useQueryClient } from '@tanstack/react-query';
import { pick, omit } from 'lodash'; import { pick, omit } from 'lodash';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import React, { useCallback } from 'react'; import React, { useCallback } from 'react';
import { animateScroll } from 'react-scroll'; import { animateScroll, scroller } from 'react-scroll';
import type { TransactionsResponse } from 'types/api/transaction'; import { PAGINATION_FIELDS, PAGINATION_FILTERS_FIELDS } from 'types/api/pagination';
import type { TTxsFilters } from 'types/api/txsFilters'; import type { PaginationParams, PaginatedResponse, PaginatedQueryKeys, PaginationFilters } from 'types/api/pagination';
import type { QueryKeys } from 'types/client/queries';
import useFetch from 'lib/hooks/useFetch'; 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 queryClient = useQueryClient();
const router = useRouter(); const router = useRouter();
const [ page, setPage ] = React.useState(1); const [ page, setPage ] = React.useState<number>(router.query.page && !Array.isArray(router.query.page) ? Number(router.query.page) : 1);
const currPageParams = pick(router.query, PAGINATION_FIELDS); const currPageParams = pick(router.query, paginationFields);
const [ pageParams, setPageParams ] = React.useState<Array<Partial<TransactionsResponse['next_page_params']>>>([ {} ]); const [ pageParams, setPageParams ] = React.useState<Array<PaginationParams<QueryName>>>([ ]);
const fetch = useFetch(); 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>( const scrollToTop = useCallback(() => {
[ queryName, { page, filters } ], scroll ? scroller.scrollTo(scroll.elem, { offset: scroll.offset }) : animateScroll.scrollToTop({ duration: 0 });
}, [ scroll ]);
const queryResult = useQuery<unknown, unknown, PaginatedResponse<QueryName>>(
queryKey,
async() => { async() => {
const params: Array<string> = []; const params: Array<string> = [];
...@@ -35,59 +58,86 @@ export default function useQueryWithPages(apiPath: string, queryName: QueryKeys, ...@@ -35,59 +58,86 @@ export default function useQueryWithPages(apiPath: string, queryName: QueryKeys,
return fetch(`${ apiPath }${ params.length ? '?' + params.join('&') : '' }`); return fetch(`${ apiPath }${ params.length ? '?' + params.join('&') : '' }`);
}, },
{ staleTime: Infinity }, { staleTime: page === 1 ? 0 : Infinity, ...options },
); );
const { data } = queryResult;
const onNextPageClick = useCallback(() => { const onNextPageClick = useCallback(() => {
if (!data?.next_page_params) { if (!data?.next_page_params) {
// we hide next page button if no next_page_params // we hide next page button if no next_page_params
return; return;
} }
// api adds filters into next-page-params now const nextPageParams = data.next_page_params;
// later filters will be removed from response
const nextPageParams = pick(data.next_page_params, PAGINATION_FIELDS);
if (page >= pageParams.length && data?.next_page_params) { if (page >= pageParams.length && data?.next_page_params) {
setPageParams(prev => [ ...prev, nextPageParams ]); setPageParams(prev => [ ...prev, nextPageParams ]);
} }
const nextPageQuery = { ...router.query }; const nextPageQuery = { ...router.query };
Object.entries(nextPageParams).forEach(([ key, val ]) => nextPageQuery[key] = val.toString()); 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 }) router.push({ pathname: router.pathname, query: nextPageQuery }, undefined, { shallow: true })
.then(() => { .then(() => {
animateScroll.scrollToTop({ duration: 0 }); scrollToTop();
setPage(prev => prev + 1); setPage(prev => prev + 1);
}); });
}, [ data, page, pageParams, router ]); }, [ data?.next_page_params, page, pageParams.length, router, scrollToTop ]);
const onPrevPageClick = useCallback(() => { const onPrevPageClick = useCallback(() => {
// returning to the first page // returning to the first page
// we dont have pagination params for 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) { if (page === 2) {
queryClient.clear(); nextPageQuery = omit(router.query, paginationFields, 'page');
nextPageQuery = omit(router.query, PAGINATION_FIELDS); canGoBackwards.current = true;
} else { } else {
const nextPageParams = { ...pageParams[page - 2] }; const nextPageParams = { ...pageParams[page - 2] };
nextPageQuery = { ...router.query }; nextPageParams && Object.entries(nextPageParams).forEach(([ key, val ]) => nextPageQuery[key] = val.toString());
Object.entries(nextPageParams).forEach(([ key, val ]) => nextPageQuery[key] = val.toString()); nextPageQuery.page = String(page - 1);
} }
router.query = nextPageQuery; router.query = nextPageQuery;
router.push({ pathname: router.pathname, query: nextPageQuery }, undefined, { shallow: true }) router.push({ pathname: router.pathname, query: nextPageQuery }, undefined, { shallow: true })
.then(() => { .then(() => {
animateScroll.scrollToTop({ duration: 0 }); scrollToTop();
setPage(prev => prev - 1); setPage(prev => prev - 1);
page === 2 && queryClient.clear();
}); });
}, [ router, page, pageParams, queryClient ]); }, [ router, page, paginationFields, pageParams, queryClient, scrollToTop ]);
const resetPage = useCallback(() => { const resetPage = useCallback(() => {
queryClient.clear(); router.push({ pathname: router.pathname, query: omit(router.query, paginationFields, 'page') }, undefined, { shallow: true }).then(() => {
router.push({ pathname: router.pathname, query: omit(router.query, PAGINATION_FIELDS) }, undefined, { shallow: true }).then(() => { queryClient.removeQueries({ queryKey: [ queryName ] });
animateScroll.scrollToTop({ duration: 0 }); 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); setPage(1);
setPageParams([ {} ]); setPageParams([ ]);
scrollToTop();
}); });
}, [ router, queryClient ]); }, [ queryName, router, scrollToTop, setPageParams, setPage ]);
const hasPaginationParams = Object.keys(currPageParams).length > 0; const hasPaginationParams = Object.keys(currPageParams).length > 0;
const nextPageParams = data?.next_page_params;
const pagination = { const pagination = {
page, page,
...@@ -95,7 +145,24 @@ export default function useQueryWithPages(apiPath: string, queryName: QueryKeys, ...@@ -95,7 +145,24 @@ export default function useQueryWithPages(apiPath: string, queryName: QueryKeys,
onPrevPageClick, onPrevPageClick,
hasPaginationParams, hasPaginationParams,
resetPage, 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'; ...@@ -3,7 +3,7 @@ import appConfig from 'configs/app/config';
import { ROUTES } from './routes'; import { ROUTES } from './routes';
import type { RouteName } from './routes'; import type { RouteName } from './routes';
const PATH_PARAM_REGEXP = /\/\[(\w+)\]/g; const PATH_PARAM_REGEXP = /\/:(\w+)/g;
export default function link( export default function link(
routeName: RouteName, routeName: RouteName,
...@@ -25,12 +25,13 @@ export default function link( ...@@ -25,12 +25,13 @@ export default function link(
return paramValue ? `/${ paramValue }` : ''; 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 ]) => { queryParams && Object.entries(queryParams).forEach(([ key, value ]) => {
url.searchParams.append(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 appConfig from 'configs/app/config';
import PATHS from './paths.js'; import PATHS from './paths.json';
export interface Route { export interface Route {
pattern: string; pattern: string;
...@@ -85,6 +85,10 @@ export const ROUTES = { ...@@ -85,6 +85,10 @@ export const ROUTES = {
pattern: PATHS.app_index, pattern: PATHS.app_index,
}, },
stats: {
pattern: PATHS.stats,
},
// SEARCH // SEARCH
search_results: { search_results: {
pattern: PATHS.search_results, pattern: PATHS.search_results,
......
...@@ -4,13 +4,18 @@ import React from 'react'; ...@@ -4,13 +4,18 @@ import React from 'react';
import type { RouteName } from 'lib/link/routes'; import type { RouteName } from 'lib/link/routes';
import { ROUTES } from 'lib/link/routes'; import { ROUTES } from 'lib/link/routes';
const PATH_PARAM_REGEXP = /\/:(\w+)/g;
export default function useCurrentRoute() { export default function useCurrentRoute() {
const { route: nextRoute } = useRouter(); const { route: nextRoute } = useRouter();
return React.useCallback((): RouteName => { return React.useCallback((): RouteName => {
for (const routeName in ROUTES) { for (const routeName in ROUTES) {
const route = ROUTES[routeName as RouteName]; 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; return routeName as RouteName;
} }
} }
......
import type { FeaturedNetwork, PreDefinedNetwork } from 'types/networks'; import type { FeaturedNetwork } from 'types/networks';
import appConfig from 'configs/app/config'; import appConfig from 'configs/app/config';
import arbitrumIcon from 'icons/networks/icons/arbitrum.svg'; import ASSETS from 'lib/networks/networkAssets';
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,
};
// for easy .env.example update // for easy .env.example update
// const FEATURED_NETWORKS = JSON.stringify([ // 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', // title: 'Gnosis Chain',
// url: 'https://blockscout.com/xdai/mainnet', // url: 'https://blockscout.com/xdai/mainnet',
// group: 'mainnets', // group: 'mainnets',
// type: 'xdai_mainnet', // type: 'xdai_mainnet',
// }, // },
// { // {
// title: 'Optimism on Gnosis Chain', // title: 'Astar (EVM)',
// url: 'https://blockscout.com/xdai/optimism', // url: 'https://blockscout.com/astar',
// group: 'mainnets', // group: 'mainnets',
// icon: 'https://www.fillmurray.com/60/60', // type: 'astar',
// type: 'xdai_optimism',
// }, // },
// { // {
// title: 'Arbitrum on xDai', // title: 'Shiden (EVM)',
// url: 'https://blockscout.com/xdai/aox', // url: 'https://blockscout.com/shiden',
// group: 'mainnets', // group: 'mainnets',
// type: 'astar',
// }, // },
// { // {
// title: 'Ethereum', // title: 'Klaytn Mainnet (Cypress)',
// url: 'https://blockscout.com/eth/mainnet', // url: 'https://klaytn-mainnet.aws-k8s.blockscout.com/',
// group: 'mainnets', // group: 'mainnets',
// type: 'eth_mainnet', // type: 'klaytn',
// }, // },
// { // {
// title: 'Ethereum Classic', // title: 'Goerli',
// url: 'https://blockscout.com/etx/mainnet', // url: 'https://blockscout.com/eth/goerli/',
// group: 'mainnets', // group: 'testnets',
// type: 'etc_mainnet', // type: 'goerli',
// }, // },
// { // {
// title: 'POA', // title: 'Optimism Goerli',
// url: 'https://blockscout.com/poa/core', // url: 'https://blockscout.com/optimism/goerli/',
// group: 'mainnets', // group: 'testnets',
// type: 'poa_core', // type: 'optimism_goerli',
// }, // },
// { // {
// title: 'RSK', // title: 'Optimism Bedrock Alpha',
// url: 'https://blockscout.com/rsk/mainnet', // url: 'https://blockscout.com/optimism/bedrock-alpha',
// group: 'mainnets', // group: 'testnets',
// type: 'rsk_mainnet', // type: 'optimism_bedrock_alpha',
// }, // },
// { // {
// title: 'Gnosis Chain Testnet', // title: 'Gnosis Chiado',
// url: 'https://blockscout.com/xdai/testnet', // url: 'https://blockscout.com/gnosis/chiado/',
// group: 'testnets', // group: 'testnets',
// type: 'xdai_testnet', // type: 'gnosis_chiado',
// }, // },
// { // {
// title: 'POA Sokol', // title: 'Shibuya (EVM)',
// url: 'https://blockscout.com/poa/sokol', // url: 'https://blockscout.com/shibuya',
// group: 'testnets', // 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', // url: 'https://blockscout.com/artis/sigma1',
// group: 'other', // group: 'other',
// type: 'artis_sigma1', // type: 'artis_sigma1',
...@@ -94,17 +98,23 @@ const ICONS: Partial<Record<PreDefinedNetwork, React.FunctionComponent<React.SVG ...@@ -94,17 +98,23 @@ const ICONS: Partial<Record<PreDefinedNetwork, React.FunctionComponent<React.SVG
// type: 'lukso_l14', // type: 'lukso_l14',
// }, // },
// { // {
// title: 'Astar', // title: 'POA',
// url: 'https://blockscout.com/astar', // url: 'https://blockscout.com/poa/core',
// group: 'other', // group: 'other',
// type: 'astar', // type: 'poa_core',
// },
// {
// title: 'POA Sokol',
// url: 'https://blockscout.com/poa/sokol',
// group: 'other',
// type: 'poa_sokol',
// }, // },
// ]).replaceAll('"', '\''); // ]).replaceAll('"', '\'');
const featuredNetworks: Array<FeaturedNetwork> = (() => { const featuredNetworks: Array<FeaturedNetwork> = (() => {
return appConfig.featuredNetworks.map((network) => ({ return appConfig.featuredNetworks.map((network) => ({
...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'; ...@@ -11,12 +11,21 @@ import appConfig from 'configs/app/config';
// title: 'Anyblock', // title: 'Anyblock',
// baseUrl: 'https://explorer.anyblock.tools', // baseUrl: 'https://explorer.anyblock.tools',
// paths: { // 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('"', '\''); // ]).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 addLeadingSlash = (str: string) => str.at(0) === '/' ? str : '/' + str;
const networkExplorers: Array<NetworkExplorer> = (() => { 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'; ...@@ -2,12 +2,16 @@ import type { GetServerSideProps, GetServerSidePropsResult } from 'next';
export type Props = { export type Props = {
cookies: string; 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 { return {
props: { props: {
cookies: req.headers.cookie || '', 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 type { Sort } from 'types/client/txs-sort';
import compareBns from 'lib/bigint/compareBns'; import compareBns from 'lib/bigint/compareBns';
export default function sortTxs(txs: TransactionsResponse['items'], sorting?: Sort) { const sortTxs = (sorting?: Sort) => (tx1: Transaction, tx2: Transaction) => {
let sortedTxs;
switch (sorting) { switch (sorting) {
case 'val-desc': case 'val-desc':
sortedTxs = [ ...txs ].sort((tx1, tx2) => compareBns(tx1.value, tx2.value)); return compareBns(tx1.value, tx2.value);
break;
case 'val-asc': case 'val-asc':
sortedTxs = [ ...txs ].sort((tx1, tx2) => compareBns(tx2.value, tx1.value)); return compareBns(tx2.value, tx1.value);
break;
case 'fee-desc': case 'fee-desc':
sortedTxs = [ ...txs ].sort((tx1, tx2) => compareBns(tx1.fee.value, tx2.fee.value)); return compareBns(tx1.fee.value, tx2.fee.value);
break;
case 'fee-asc': case 'fee-asc':
sortedTxs = [ ...txs ].sort((tx1, tx2) => compareBns(tx2.fee.value, tx1.fee.value)); return compareBns(tx2.fee.value, tx1.fee.value);
break;
default: default:
sortedTxs = txs; return 0;
} }
};
return sortedTxs; export default sortTxs;
}
...@@ -21,7 +21,7 @@ export function middleware(req: NextRequest) { ...@@ -21,7 +21,7 @@ export function middleware(req: NextRequest) {
const apiToken = req.cookies.get(NAMES.API_TOKEN); const apiToken = req.cookies.get(NAMES.API_TOKEN);
if ((isAccountRoute || isProfileRoute) && !apiToken && appConfig.isAccountSupported) { if ((isAccountRoute || isProfileRoute) && !apiToken && appConfig.isAccountSupported) {
const authUrl = link('auth'); const authUrl = link('auth', undefined, { path: req.nextUrl.pathname });
return NextResponse.redirect(authUrl); 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