Commit 5e4c8864 authored by tom goriunov's avatar tom goriunov Committed by GitHub

Merge pull request #168 from blockscout/fix-ci-cd

Fix ci cd
parents c256c241 25305586
SENTRY_DSN=xxx
NEXT_PUBLIC_SENTRY_DSN=xxx NEXT_PUBLIC_SENTRY_DSN=xxx
SENTRY_ORG=block-scout
SENTRY_PROJECT=new-ui
SENTRY_AUTH_TOKEN=xxx
SENTRY_IGNORE_API_RESOLUTION_ERROR=1
SENTRY_CSP_REPORT_URI=xxx SENTRY_CSP_REPORT_URI=xxx
NEXT_PUBLIC_BLOCKSCOUT_VERSION=xxx
NEXT_PUBLIC_FOOTER_GITHUB_LINK=https://github.com/blockscout/blockscout
NEXT_PUBLIC_FOOTER_TWITTER_LINK=https://www.twitter.com/blockscoutcom
NEXT_PUBLIC_FOOTER_TELEGRAM_LINK=https://t.me/poa_network
NEXT_PUBLIC_FOOTER_STAKING_LINK=https://duneanalytics.com/maxaleks/xdai-staking
NEXT_PUBLIC_SUPPORTED_NETWORKS=[{"name":"Gnosis Chain","type":"xdai","subType":"mainnet","group":"mainnets","isAccountSupported":true,"chainId":100,"currency":"xDAI"},{"name":"Optimism on Gnosis Chain","shortName":"OoG","type":"xdai","subType":"optimism","group":"mainnets","icon":"https://www.fillmurray.com/60/60","chainId":300,"currency":"xDAI"},{"name":"Arbitrum on xDai","type":"xdai","subType":"aox","group":"mainnets","chainId":200,"currency":"xDAI"},{"name":"Ethereum","shortName":"ETH","type":"eth","subType":"mainnet","group":"mainnets","chainId":1,"currency":"ETH"},{"name":"Ethereum Classic","shortName":"ETC","type":"etc","subType":"mainnet","group":"mainnets","chainId":61,"currency":"ETC"},{"name":"POA","shortName":"POA","type":"poa","subType":"core","group":"mainnets","chainId":99,"currency":"POA","nativeTokenAddress": "0x029a799563238d0e75e20be2f4bda0ea68d00172"},{"name":"RSK","shortName":"RBTC","type":"rsk","subType":"mainnet","group":"mainnets","chainId":30,"currency":"RBTC"},{"name":"Gnosis Chain Testnet","type":"xdai","subType":"testnet","group":"testnets","isAccountSupported":true,"currency":"xDAI"},{"name":"POA Sokol","shortName":"POA","type":"poa","subType":"sokol","group":"testnets","chainId":77,"currency":"SPOA"},{"name":"ARTIS Σ1","type":"artis","subType":"sigma1","group":"other","chainId":246529,"currency":"ATS"},{"name":"LUKSO L14","shortName":"POA","type":"lukso","subType":"l14","group":"other","chainId":22,"currency":"LYX"},{"name":"Astar","type":"astar","group":"other","chainId":22,"currency":"ASTR"}]
NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://airtable.com/shrqUAcjgGJ4jU88C
NEXT_PUBLIC_SUPPORTED_NETWORKS=APP_NEXT_NEXT_PUBLIC_SUPPORTED_NETWORKS
NEXT_PUBLIC_BLOCKSCOUT_VERSION=APP_NEXT_NEXT_PUBLIC_BLOCKSCOUT_VERSION
NEXT_PUBLIC_FOOTER_GITHUB_LINK=APP_NEXT_NEXT_PUBLIC_FOOTER_GITHUB_LINK
NEXT_PUBLIC_FOOTER_TWITTER_LINK=APP_NEXT_NEXT_PUBLIC_FOOTER_TWITTER_LINK
NEXT_PUBLIC_FOOTER_TELEGRAM_LINK=APP_NEXT_NEXT_PUBLIC_FOOTER_TELEGRAM_LINK
NEXT_PUBLIC_FOOTER_STAKING_LINK=APP_NEXT_NEXT_PUBLIC_FOOTER_STAKING_LINK
NEXT_PUBLIC_FOOTER_STAKING_LINK=APP_NEXT_NEXT_PUBLIC_FOOTER_STAKING_LINK
NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=APP_NEXT_NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM
NEXT_PUBLIC_SENTRY_DSN=APP_NEXT_NEXT_PUBLIC_SENTRY_DSN
NEXT_PUBLIC_APP_INSTANCE=APP_NEXT_NEXT_PUBLIC_APP_INSTANCE
NEXT_PUBLIC_NETWORK_NAME=APP_NEXT_NEXT_PUBLIC_NETWORK_NAME
NEXT_PUBLIC_NETWORK_SHORT_NAME=APP_NEXT_NEXT_PUBLIC_NETWORK_SHORT_NAME
NEXT_PUBLIC_NETWORK_ASSETS_PATHNAME=APP_NEXT_NEXT_PUBLIC_NETWORK_ASSETS_PATHNAME
NEXT_PUBLIC_NETWORK_TYPE=APP_NEXT_NEXT_PUBLIC_NETWORK_TYPE
NEXT_PUBLIC_NETWORK_SUBTYPE=APP_NEXT_NEXT_PUBLIC_NETWORK_SUBTYPE
NEXT_PUBLIC_NETWORK_ID=APP_NEXT_NEXT_PUBLIC_NETWORK_ID
NEXT_PUBLIC_NETWORK_CURRENCY=APP_NEXT_NEXT_PUBLIC_NETWORK_CURRENCY
NEXT_PUBLIC_NETWORK_TOKEN_ADDRESS=APP_NEXT_NEXT_PUBLIC_NETWORK_TOKEN_ADDRESS
NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=APP_NEXT_NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED
NEXT_PUBLIC_FEATURED_NETWORKS=APP_NEXT_NEXT_PUBLIC_FEATURED_NETWORKS
NEXT_PUBLIC_APP_PROTOCOL=APP_NEXT_NEXT_PUBLIC_APP_PROTOCOL
NEXT_PUBLIC_APP_HOST=APP_NEXT_NEXT_PUBLIC_APP_HOST
NEXT_PUBLIC_APP_PORT=APP_NEXT_NEXT_PUBLIC_APP_PORT
NEXT_PUBLIC_API_ENDPOINT=APP_NEXT_NEXT_PUBLIC_API_ENDPOINT
NEXT_PUBLIC_API_BASE_PATH=APP_NEXT_NEXT_PUBLIC_API_BASE_PATH
...@@ -4,6 +4,7 @@ const RESTRICTED_MODULES = { ...@@ -4,6 +4,7 @@ const RESTRICTED_MODULES = {
{ name: '@chakra-ui/icons', message: 'Using @chakra-ui/icons is prohibited. Please use regular svg-icon instead (see examples in "icons/" folder)' }, { name: '@chakra-ui/icons', message: 'Using @chakra-ui/icons is prohibited. Please use regular svg-icon instead (see examples in "icons/" folder)' },
], ],
}; };
module.exports = { module.exports = {
env: { env: {
es6: true, es6: true,
...@@ -202,6 +203,12 @@ module.exports = { ...@@ -202,6 +203,12 @@ module.exports = {
], ],
'no-restricted-imports': [ 'error', RESTRICTED_MODULES ], 'no-restricted-imports': [ 'error', RESTRICTED_MODULES ],
'no-restricted-properties': [ 2, {
object: 'process',
property: 'env',
// FIXME: restrict the rule only NEXT_PUBLIC variables
message: 'Please use configs/app/config.ts to import any NEXT_PUBLIC environment variables. For other properties please disable this rule for a while.',
} ],
'react/jsx-key': 'error', 'react/jsx-key': 'error',
'react/jsx-no-bind': [ 'error', { 'react/jsx-no-bind': [ 'error', {
...@@ -275,5 +282,12 @@ module.exports = { ...@@ -275,5 +282,12 @@ module.exports = {
'@typescript-eslint/no-var-requires': 'off', '@typescript-eslint/no-var-requires': 'off',
}, },
}, },
{
files: [ 'configs/**/*.js', 'configs/**/*.ts', '*.config.ts' ],
rules: {
// for configs allow to consume env variables from process.env directly
'no-restricted-properties': [ 0 ],
},
},
], ],
}; };
name: Cleanup environments
on:
pull_request:
types:
- closed
jobs:
if_merged:
if: github.event.pull_request.merged == true
uses: blockscout/blockscout-ci-cd/.github/workflows/cleanup.yaml@fix-e2e-tests
with:
appNamespace: review-front-$GITHUB_HEAD_REF_SLUG
secrets: inherit
name: Run E2E tests k8s
on:
push:
# pull_request:
env:
K8S_LOCAL_PORT: ${{ secrets.K8S_LOCAL_PORT }}
K8S_HOST: ${{ secrets.K8S_HOST }}
BASTION_HOST: ${{ secrets.BASTION_HOST }}
K8S_PORT: ${{ secrets.K8S_PORT }}
USERNAME: ${{ secrets.USERNAME }}
BASTION_SSH_KEY: ${{secrets.BASTION_SSH_KEY}}
jobs:
push_to_registry:
name: Push Docker image to registry
runs-on: ubuntu-latest
outputs:
shortSha: ${{ steps.output-step.outputs.short-sha }}
steps:
- name: Check out the repo
uses: actions/checkout@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
# Will automatically make nice tags, see the table here https://github.com/docker/metadata-action#basic
- name: Docker meta
id: meta
uses: docker/metadata-action@v4
with:
images: ghcr.io/blockscout/frontend
- name: Login to GitHub Container Registry
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Add SHORT_SHA env property with commit short sha
run: echo "SHORT_SHA=`echo ${GITHUB_SHA} | cut -c1-8`" >> $GITHUB_ENV
- name: Add outputs
run: |
echo "::set-output name=short-sha::${{ env.SHORT_SHA }}"
id: output-step
- name: Build and push
uses: docker/build-push-action@v3
with:
context: .
file: ./Dockerfile
push: true
cache-from: type=gha
cache-to: type=gha,mode=max
tags: ghcr.io/blockscout/frontend:prerelease-${{ env.SHORT_SHA }}
labels: ${{ steps.meta.outputs.labels }}
build-args: |
SENTRY_DSN=${{ secrets.SENTRY_DSN }}
NEXT_PUBLIC_SENTRY_DSN=${{ secrets.NEXT_PUBLIC_SENTRY_DSN }}
SENTRY_CSP_REPORT_URI=${{ secrets.SENTRY_CSP_REPORT_URI }}
SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }}
deploy_and_tests:
needs: push_to_registry
uses: blockscout/blockscout-ci-cd/.github/workflows/e2e_new.yaml@fix-e2e-tests
with:
valuesDir: deploy/values/e2e
appName: e2e-front
appNamespace: e2e-front-$GITHUB_SHA_SHORT
blockscoutIngressHost: blockscout
frontendIngressHost: frontend
frontendImage: ghcr.io/blockscout/frontend:prerelease-${{ needs.push_to_registry.outputs.shortSha }}
gethIngressHost: geth
scVerifierIngressHost: sc-verifier
secrets: inherit
name: Linters name: Run code checks
on: [pull_request] on: [pull_request]
jobs: jobs:
eslint: Check code:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Install dependencies - name: Install dependencies
run: yarn run: yarn
- name: Run ESLint - name: Run ESLint
run: yarn lint:eslint run: yarn lint:eslint
\ No newline at end of file - name: Compile TypeScript
run: yarn lint:tsc
\ No newline at end of file
name: Deploy review environment
on:
# push:
pull_request:
env:
K8S_LOCAL_PORT: ${{ secrets.K8S_LOCAL_PORT }}
K8S_HOST: ${{ secrets.K8S_HOST }}
BASTION_HOST: ${{ secrets.BASTION_HOST }}
K8S_PORT: ${{ secrets.K8S_PORT }}
USERNAME: ${{ secrets.USERNAME }}
BASTION_SSH_KEY: ${{secrets.BASTION_SSH_KEY}}
jobs:
push_to_registry:
name: Push Docker image to registry
runs-on: ubuntu-latest
outputs:
shortSha: ${{ steps.output-step.outputs.short-sha }}
steps:
- name: Check out the repo
uses: actions/checkout@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
# Will automatically make nice tags, see the table here https://github.com/docker/metadata-action#basic
- name: Docker meta
id: meta
uses: docker/metadata-action@v4
with:
images: ghcr.io/blockscout/frontend
- name: Login to GitHub Container Registry
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Add SHORT_SHA env property with commit short sha
run: echo "SHORT_SHA=`echo ${GITHUB_SHA} | cut -c1-8`" >> $GITHUB_ENV
- name: Add outputs
run: |
echo "::set-output name=short-sha::${{ env.SHORT_SHA }}"
id: output-step
- name: Build and push
uses: docker/build-push-action@v3
with:
context: .
file: ./Dockerfile
push: true
cache-from: type=gha
cache-to: type=gha,mode=max
tags: ghcr.io/blockscout/frontend:prerelease-${{ env.SHORT_SHA }}
labels: ${{ steps.meta.outputs.labels }}
build-args: |
SENTRY_DSN=${{ secrets.SENTRY_DSN }}
NEXT_PUBLIC_SENTRY_DSN=${{ secrets.NEXT_PUBLIC_SENTRY_DSN }}
SENTRY_CSP_REPORT_URI=${{ secrets.SENTRY_CSP_REPORT_URI }}
SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }}
deploy_frontend:
needs: push_to_registry
uses: blockscout/blockscout-ci-cd/.github/workflows/deploy.yaml@fix-e2e-tests
with:
valuesDir: deploy/values/review
appNamespace: review-front-$GITHUB_HEAD_REF_SLUG
blockscoutIngressHost: blockscout
frontendIngressHost: frontend
frontendImage: ghcr.io/blockscout/frontend:prerelease-${{ needs.push_to_registry.outputs.shortSha }}
gethIngressHost: geth
scVerifierIngressHost: sc-verifier
secrets: inherit
...@@ -12,8 +12,6 @@ env: ...@@ -12,8 +12,6 @@ env:
K8S_PORT: ${{ secrets.K8S_PORT }} K8S_PORT: ${{ secrets.K8S_PORT }}
USERNAME: ${{ secrets.USERNAME }} USERNAME: ${{ secrets.USERNAME }}
BASTION_SSH_KEY: ${{secrets.BASTION_SSH_KEY}} BASTION_SSH_KEY: ${{secrets.BASTION_SSH_KEY}}
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
jobs: jobs:
push_to_registry: push_to_registry:
...@@ -50,29 +48,17 @@ jobs: ...@@ -50,29 +48,17 @@ jobs:
cache-to: type=gha,mode=max cache-to: type=gha,mode=max
tags: ${{ steps.meta.outputs.tags }} tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }} labels: ${{ steps.meta.outputs.labels }}
deploy: build-args: |
name: Deploy frontend to k8s SENTRY_DSN=${{ secrets.SENTRY_DSN }}
NEXT_PUBLIC_SENTRY_DSN=${{ secrets.NEXT_PUBLIC_SENTRY_DSN }}
SENTRY_CSP_REPORT_URI=${{ secrets.SENTRY_CSP_REPORT_URI }}
SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }}
deploy_frontend:
needs: push_to_registry needs: push_to_registry
runs-on: ubuntu-latest uses: blockscout/blockscout-ci-cd/.github/workflows/deploy.yaml@deploy-smart-contract-verifier
steps: with:
- name: Check out the repo valuesDir: deploy/values
uses: actions/checkout@v3 appNamespace: frontend-main
- name: Set Kubernetes Context appName: frontend
uses: azure/k8s-set-context@v1 secrets: inherit
with:
method: kubeconfig
kubeconfig: ${{ secrets.KUBE_CONFIG }}
- name: Deploy to k8s
shell: bash
working-directory: charts
# port forwarding works only inside the step, consider refactoring as TS action
env:
NAMESPACE_NAME: bs-frontend
run: |
mkdir ~/.ssh
ssh-keyscan -H $BASTION_HOST >> ~/.ssh/known_hosts
eval `ssh-agent -s`
ssh-add - <<< "$BASTION_SSH_KEY"
sudo echo "127.0.0.1 $K8S_HOST" | sudo tee -a /etc/hosts
ssh -fN -v -L $K8S_LOCAL_PORT:$K8S_HOST:$K8S_PORT $USERNAME@$BASTION_HOST
helm upgrade --install -n $NAMESPACE_NAME $NAMESPACE_NAME ./ -f values-frontend.yaml --create-namespace
...@@ -40,6 +40,8 @@ yarn-error.log* ...@@ -40,6 +40,8 @@ yarn-error.log*
# Sentry # Sentry
.sentryclirc .sentryclirc
**.decrypted~**
/test-results/ /test-results/
/playwright-report/ /playwright-report/
/playwright/.cache/ /playwright/.cache/
...@@ -8,18 +8,27 @@ WORKDIR /app ...@@ -8,18 +8,27 @@ WORKDIR /app
COPY package.json yarn.lock ./ COPY package.json yarn.lock ./
RUN yarn --frozen-lockfile RUN yarn --frozen-lockfile
# Rebuild the source code only when needed # Rebuild the source code only when needed
FROM node:16-alpine AS builder FROM node:16-alpine AS builder
WORKDIR /app WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules COPY --from=deps /app/node_modules ./node_modules
COPY . . COPY . .
COPY .env.template .env.production
# Next.js collects completely anonymous telemetry data about general usage. # Next.js collects completely anonymous telemetry data about general usage.
# Learn more here: https://nextjs.org/telemetry # Learn more here: https://nextjs.org/telemetry
# Uncomment the following line in case you want to disable telemetry during the build. # Uncomment the following line in case you want to disable telemetry during the build.
# ENV NEXT_TELEMETRY_DISABLED 1 # ENV NEXT_TELEMETRY_DISABLED 1
ARG SENTRY_DSN
ARG NEXT_PUBLIC_SENTRY_DSN
ARG SENTRY_CSP_REPORT_URI
ARG SENTRY_AUTH_TOKEN
# pass commit sha to the app (uses by sentry.io as release version)
ARG GIT_COMMIT_SHA
ENV NEXT_PUBLIC_GIT_COMMIT_SHA=$GIT_COMMIT_SHA
RUN yarn build RUN yarn build
# Production image, copy all the files and run next # Production image, copy all the files and run next
...@@ -37,15 +46,27 @@ COPY --from=builder /app/next.config.js ./ ...@@ -37,15 +46,27 @@ COPY --from=builder /app/next.config.js ./
COPY --from=builder /app/public ./public COPY --from=builder /app/public ./public
COPY --from=builder /app/package.json ./package.json COPY --from=builder /app/package.json ./package.json
# Copy scripts and ENV templates file
COPY ./deploy/scripts/entrypoint.sh .
COPY ./deploy/scripts/replace_envs.sh .
COPY .env.template .
# Automatically leverage output traces to reduce image size # Automatically leverage output traces to reduce image size
# https://nextjs.org/docs/advanced-features/output-file-tracing # https://nextjs.org/docs/advanced-features/output-file-tracing
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
# Execute script for replace build ENV with run ones
RUN apk add --no-cache --upgrade bash
RUN ["chmod", "+x", "./entrypoint.sh"]
RUN ["chmod", "+x", "./replace_envs.sh"]
ENTRYPOINT ["./entrypoint.sh"]
USER nextjs USER nextjs
EXPOSE 3000 EXPOSE 3000
ENV PORT 3000 ENV PORT 3000
CMD ["node", "server.js"] CMD ["node", "server.js"]
\ No newline at end of file
...@@ -16,11 +16,16 @@ And of course our premier language is [Typescript](https://www.typescriptlang.or ...@@ -16,11 +16,16 @@ And of course our premier language is [Typescript](https://www.typescriptlang.or
----- -----
## Local Development ## Local Development
**Pre-requisites** You should have installed Node.js v16. The best way to manage your local Node.js version is [nvm](https://github.com/nvm-sh/nvm)
For local development please follow next steps: For local development please follow next steps:
- clone repo - clone repo
- install dependencies with `yarn` - install dependencies with `yarn`
- create local env file `.env.local` according to `.env.example` snapshot (see list of used environment variables [below](#environment-variables)) - clone `.env.example` into `configs/envs/.env.secrets` and fill it with necessary secret values (see description [below](#environment-variables))
- run `yarn dev` to spin up local dev server and navigate to the host from logs output - to spin up local dev server
- for predefined networks configs (see full available list in `package.json`) you can just run `yarn dev:<app_name>`
- for custom network setup create `.env.local` file with all required environment variables from the [list](#environment-variables) and run `yarn dev`
- navigate to the host from logs output
## Components visual testing ## Components visual testing
...@@ -29,33 +34,49 @@ To perform testing locally you need to install docker and run `yarn test-docker` ...@@ -29,33 +34,49 @@ To perform testing locally you need to install docker and run `yarn test-docker`
## Environment variables ## Environment variables
### Variables list ### Variables list
The app instance could be customized by passing following variables to NodeJS environment. The app instance could be customized by passing following variables to NodeJS environment at runtime.
**IMPORTANT NOTE!** For _production_ build purposes all json-like values should be single-quoted
| Variable | Type | Description | Default value | Variable | Type | Description | Default value
| --- | --- | --- | --- | | --- | --- | --- | --- |
| NEXT_PUBLIC_BLOCKSCOUT_VERSION | `string` | Current running version of Blockscout (used to display link to release in the footer) | | NEXT_PUBLIC_NETWORK_NAME | `string` | Displayed name of the network | `Gnosis Chain` |
| NEXT_PUBLIC_FOOTER_GITHUB_LINK | `string` | Link to Github in the footer | `https://github.com/blockscout/blockscout` | | NEXT_PUBLIC_NETWORK_SHORT_NAME | `string` *(optional)* | Used for SEO attributes (page title and description) | `OoG` |
| NEXT_PUBLIC_FOOTER_TWITTER_LINK | `string` | Link to Twitter in the footer | `https://www.twitter.com/blockscoutcom` | | NEXT_PUBLIC_NETWORK_TYPE | `string` | Network type (used as first part of the base path) | `xdai` |
| NEXT_PUBLIC_FOOTER_TELEGRAM_LINK | `string` | Link to Telegram in the footer | `https://t.me/poa_network` | | NEXT_PUBLIC_NETWORK_SUBTYPE | `string` | Network subtype (used as second part of the base path) | `mainnet` |
| NEXT_PUBLIC_FOOTER_STAKING_LINK | `string` | Link to staking dashboard in the footer | `https://duneanalytics.com/maxaleks/xdai-staking` | | NEXT_PUBLIC_NETWORK_ID | `number` | Chain id, see [https://chainlist.org/](https://chainlist.org/) for the reference | `99` |
| NEXT_PUBLIC_SUPPORTED_NETWORKS | `Array<Network>` where `Network` can have following [properties](#network-configuration-properties) | Configuration of supported networks | `[{"name":"POA","type":"poa","subType":"core","group":"mainnets","isAccountSupported":true,"chainId":99,"currency":"POA"}]` | | NEXT_PUBLIC_NETWORK_CURRENCY | `string` | Network currency symbol | `xDAI` |
| 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). If not provided, the network type will be used as its assets path part | `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_IS_ACCOUNT_SUPPORTED | `boolean` *(optional)* | Set to true if network has account feature | `true` |
| NEXT_PUBLIC_FEATURED_NETWORKS | `Array<FeaturedNetwork>` where `FeaturedNetwork` can have following [properties](#network-configuration-properties) | Configuration of featured networks that will be shown in the app menu | `[{'title':'Gnosis Chain','basePath':'/xdai/mainnet','group':'mainnets'}]` |
| NEXT_PUBLIC_BLOCKSCOUT_VERSION | `string` *(optional)* | Current running version of Blockscout (used to display link to release in the footer) |
| NEXT_PUBLIC_FOOTER_GITHUB_LINK | `string` *(optional)* | Link to Github in the footer | `https://github.com/blockscout/blockscout` |
| NEXT_PUBLIC_FOOTER_TWITTER_LINK | `string` *(optional)* | Link to Twitter in the footer | `https://www.twitter.com/blockscoutcom` |
| NEXT_PUBLIC_FOOTER_TELEGRAM_LINK | `string` *(optional)* | Link to Telegram in the footer | `https://t.me/poa_network` |
| NEXT_PUBLIC_FOOTER_STAKING_LINK | `string` *(optional)* | Link to staking dashboard in the footer | `https://duneanalytics.com/maxaleks/xdai-staking` |
| NEXT_PUBLIC_MARKETPLACE_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_APP_INSTANCE | `string` *(optional)* | Name of app instance | `wonderful_kepler` |
| NEXT_PUBLIC_APP_PROTOCOL | `http \| https` *(optional)* | App protocol (`https` used as default value) | `https` |
| NEXT_PUBLIC_APP_HOST | `string` | App host | `blockscout.com` |
| NEXT_PUBLIC_APP_PORT | `number` *(optional)* | Port where app is running. Have to be provided if it is different to default port | `3000` |
| NEXT_PUBLIC_API_ENDPOINT | `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 endpoint base URL in this variable | `https://blockscout.com` |
| NEXT_PUBLIC_API_BASE_PATH | `string` *(optional)* | Base path for API endpoint url | `/poa/core` |
| NEXT_PUBLIC_SENTRY_DSN | `string` *(optional)* | Client key for your Senty.io app | `<secret>` |
| SENTRY_CSP_REPORT_URI | `string` *(optional)* | URL for sending CSP-reports to your Senty.io app | `<secret>` |
### Network configuration properties ### Featured network configuration properties
| Property | Type | Description | Example value | Property | Type | Description | Example value
| --- | --- | --- | --- | | --- | --- | --- | --- |
| name | `string` | Displayed name of the network | `"Gnosis Chain"` | | title | `string` | Displayed name of the network | `'Gnosis Chain'` |
| shortName | `string` | Used for SEO attributes (page title and description) | `"OoG"` | | basePath | `string` | Network explorer main page url | `'/xdai/mainnet'` |
| chainId | `number` | Id of the network. Could be found here – [https://chainlist.org/](https://chainlist.org/) | `1` | | group | `mainnets \| testnets \| other` | Indicates in which tab network appears in the menu | `'mainnets'` |
| currency | `string` | Network currency symbol. Could be found here – [https://chainlist.org/](https://chainlist.org/) | `"xDAI"` | | icon | `string` *(optional)* | Network icon; if not provided, will fallback to icon predefined in the project; if the project doesn't have icon for such network then the common placeholder will be shown; *Note* that icon size should be 30px by 30px | `'https://www.fillmurray.com/60/60'` |
| nativeTokenAddress | `string` | Address of network's native token | `"0x029a799563238d0e75e20be2f4bda0ea68d00172"` |
| type | `string` | Network type (used as first part of the base path) | `"xdai"` |
| subType | `string` | Network subtype (used as second part of the base path) | `"mainnet"` |
| group | `mainnets \| testnets \| other` | Indicates in which tab network appears in the menu | `"mainnets"` |
| isAccountSupported | `boolean` *(optional)* | Set to true if network has account feature | `true` |
| icon | `string` *(optional)* | Network icon; if not provided, will fallback to icon predefined in the project; if the project doesn't have icon for such network then the common placeholder will be shown; *Note* that icon size should be 30px by 30px | `"https://www.fillmurray.com/60/60"` |
| 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"` |
| assetsNamePath | `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). The project already has some pre-defined mapping for popular network, which is match assetsNamePath against provided network type and sub-type. So typically you don't need to provide this variable, if you network is in the list or its type in config is conformed to a name in TrustWallet repo. If it is not the case, pass value here | `"ethereum"` |
*Note* the base path for the network is built up from its `type` and `subType` like so `https://blockscout.com/<type>/<subType>` *Note* the base path for the network is built up from its `type` and `subType` like so `https://blockscout.com/<type>/<subType>`
### Sentry.io setup
TBD
\ No newline at end of file
# values-frontend.yaml
apiVersion: v1
appVersion: 0.0.1
version: 0.0.1
name: bs-frontend
description: '''
Helm chart for deploying blockscout frontend in K8S
Deploy command: `helm upgrade --install -n=<namespace> bs-frontend ./ -f values-<name>.yaml`
'''
{{- define "app_env" }}
{{- range $key, $value := .Values.environment }}
{{- $item := get $.Values.environment $key }}
{{- if or (kindIs "string" $item) (kindIs "int64" $item) (kindIs "bool" $item)}}
- name: {{ $key }}
value: {{ $value | quote }}
{{- else }}
- name: {{ $key }}
value: {{ pluck $.Values.global.env $item | first | default $item._default | quote }}
{{- end }}
{{- end }}
{{- end }}
{{- define "node_env" }}
{{- range $key, $value := .Values.node_environment }}
{{- $item := get $.Values.node_environment $key }}
{{- if or (kindIs "string" $item) (kindIs "int64" $item) (kindIs "bool" $item)}}
- name: {{ $key }}
value: {{ $value | quote }}
{{- else }}
- name: {{ $key }}
value: {{ pluck $.Values.global.env $item | first | default $item._default | quote }}
{{- end }}
{{- end }}
{{- end }}
kind: Deployment
apiVersion: apps/v1
metadata:
name: {{ .Release.Name }}
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "http-metrics"
spec:
replicas: {{ .Values.replicas.app }}
selector:
matchLabels:
app: {{ .Release.Name }}
template:
metadata:
labels:
app: {{ .Release.Name }}
spec:
# serviceAccountName: vault-auth
imagePullSecrets:
- name: regcred
containers:
- name: {{ .Release.Name }}
image: {{ .Values.image }}
resources:
{{- with .Values.resources }}
limits:
memory: {{ pluck $.Values.global.env .limits.memory | first | default .limits.memory._default | quote }}
cpu: {{ pluck $.Values.global.env .limits.cpu | first | default .limits.cpu._default | quote }}
requests:
memory: {{ pluck $.Values.global.env .requests.memory | first | default .requests.memory._default | quote }}
cpu: {{ pluck $.Values.global.env .requests.cpu | first | default .requests.cpu._default | quote }}
{{- end }}
imagePullPolicy: Always
ports:
- containerPort: {{ .Values.docker.targetPort }}
env:
{{- include "app_env" . | indent 10 }}
# volumeMounts:
# - name: smweb-logs
# mountPath: /usr/local/sm-web-server/log
# readinessProbe:
# httpGet:
# path: /appversion
# port: {{ .Values.docker.port }}
# scheme: HTTP
# initialDelaySeconds: 60
# periodSeconds: 10
# livenessProbe:
# httpGet:
# path: /appversion
# port: {{ .Values.docker.port }}
# scheme: HTTP
# initialDelaySeconds: 100
# periodSeconds: 100
# volumes:
# - name: smweb-logs
# emptyDir: { }
# - name: config
# configMap:
# name: {{ .Release.Name }}-promtail-configmap
restartPolicy: Always
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
kubernetes.io/ingress.class: internal-and-public
nginx.ingress.kubernetes.io/proxy-body-size: 500m
nginx.ingress.kubernetes.io/client-max-body-size: "500M"
nginx.ingress.kubernetes.io/proxy-buffering: "off"
nginx.ingress.kubernetes.io/proxy-connect-timeout: "15m"
nginx.ingress.kubernetes.io/proxy-send-timeout: "15m"
nginx.ingress.kubernetes.io/proxy-read-timeout: "15m"
# cert-manager.io/cluster-issuer: vault
name: {{ .Release.Name }}-ingress
spec:
rules:
- host: {{ .Values.ingress.host }}
http:
paths:
- path: "/"
pathType: Prefix
backend:
service:
name: {{ .Release.Name }}-svc
port:
number: {{ .Values.docker.port }}
# tls:
# - hosts:
# - {{ .Values.ingress.host }}
# secretName: xcloud-cert-srv
kind: Service
apiVersion: v1
metadata:
name: {{ .Release.Name }}-svc
spec:
type: ClusterIP
ports:
- port: {{ .Values.docker.port }}
targetPort: {{ .Values.docker.targetPort }}
protocol: TCP
name: http
selector:
app: {{ .Release.Name }}
---
image: ghcr.io/blockscout/frontend:main
replicas:
app: 1
docker:
port: 80
targetPort: 3000
ingress:
host: blockscout-frontend.aws-k8s.blockscout.com
resources:
limits:
memory:
_default: "0.3Gi"
cpu:
_default: "0.2"
requests:
memory:
_default: "0.3Gi"
cpu:
_default: "0.2"
environment: {}
NEXT_PUBLIC_BLOCKSCOUT_VERSION:
_default: v4.1.7-beta
NEXT_PUBLIC_FOOTER_GITHUB_LINK:
_default: https://github.com/blockscout/blockscout
NEXT_PUBLIC_FOOTER_TWITTER_LINK:
_default: https://www.twitter.com/blockscoutcom
NEXT_PUBLIC_FOOTER_TELEGRAM_LINK:
_default: https://t.me/poa_network
NEXT_PUBLIC_FOOTER_STAKING_LINK:
_default: https://duneanalytics.com/maxaleks/xdai-staking
NEXT_PUBLIC_SUPPORTED_NETWORKS:
_default: [{"name":"Gnosis Chain","type":"xdai","subType":"mainnet","group":"mainnets","isAccountSupported":true, "chainId": 100},{"name":"Optimism on Gnosis Chain","shortName":"OoG","type":"xdai","subType":"optimism","group":"mainnets","icon":"https://www.fillmurray.com/60/60", "chainId": 300},{"name":"Arbitrum on xDai","type":"xdai","subType":"aox","group":"mainnets", "chainId": 200},{"name":"Ethereum","shortName":"ETH","type":"eth","subType":"mainnet","group":"mainnets", "chainId": 1},{"name":"Ethereum Classic","shortName":"ETC","type":"etc","subType":"mainnet","group":"mainnets", "chainId": 61},{"name":"POA","shortName":"POA","type":"poa","subType":"core","group":"mainnets","isAccountSupported":true, "chainId": 99},{"name":"RSK","shortName":"RBTC","type":"rsk","subType":"mainnet","group":"mainnets", "chainId": 30},{"name":"Gnosis Chain Testnet","type":"xdai","subType":"testnet","group":"testnets"},{"name":"POA Sokol","shortName":"POA","type":"poa","subType":"sokol","group":"testnets", "chainId": 77},{"name":"ARTIS Σ1","type":"artis","subType":"sigma1","group":"other", "chainId": 246529},{"name":"LUKSO L14","shortName":"POA","type":"lukso","subType":"l14","group":"other", "chainId": 22}]
NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM:
_default: https://airtable.com/shrqUAcjgGJ4jU88C
global:
env: test
/* eslint-disable no-restricted-properties */
const env = process.env.VERCEL_ENV || process.env.NODE_ENV;
const isDev = env === 'development';
const baseUrl = [
process.env.NEXT_PUBLIC_APP_PROTOCOL || 'https',
'://',
process.env.NEXT_PUBLIC_APP_HOST,
process.env.NEXT_PUBLIC_APP_PORT ? ':' + process.env.NEXT_PUBLIC_APP_PORT : '',
].join('');
const apiUrl = [
process.env.NEXT_PUBLIC_API_ENDPOINT || 'https://blockscout.com',
process.env.NEXT_PUBLIC_API_BASE_PATH,
].filter(Boolean).join('');
const config = Object.freeze({
env,
isDev,
network: {
type: process.env.NEXT_PUBLIC_NETWORK_TYPE,
subtype: process.env.NEXT_PUBLIC_NETWORK_SUBTYPE,
logo: process.env.NEXT_PUBLIC_NETWORK_LOGO,
name: process.env.NEXT_PUBLIC_NETWORK_NAME,
id: process.env.NEXT_PUBLIC_NETWORK_ID,
shortName: process.env.NEXT_PUBLIC_NETWORK_SHORT_NAME,
currency: process.env.NEXT_PUBLIC_NETWORK_CURRENCY,
assetsPathname: process.env.NEXT_PUBLIC_NETWORK_ASSETS_PATHNAME,
nativeTokenAddress: process.env.NEXT_PUBLIC_NETWORK_TOKEN_ADDRESS,
basePath: '/' + [ process.env.NEXT_PUBLIC_NETWORK_TYPE, process.env.NEXT_PUBLIC_NETWORK_SUBTYPE ].filter(Boolean).join('/'),
},
footerLinks: {
github: process.env.NEXT_PUBLIC_FOOTER_GITHUB_LINK,
twitter: process.env.NEXT_PUBLIC_FOOTER_TWITTER_LINK,
telegram: process.env.NEXT_PUBLIC_FOOTER_TELEGRAM_LINK,
staking: process.env.NEXT_PUBLIC_FOOTER_STAKING_LINK,
},
featuredNetworks: process.env.NEXT_PUBLIC_FEATURED_NETWORKS?.replaceAll('\'', '"'),
blockScoutVersion: process.env.NEXT_PUBLIC_BLOCKSCOUT_VERSION,
isAccountSupported: process.env.NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED?.replaceAll('\'', '"') === 'true',
marketplaceSubmitForm: process.env.NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM,
protocol: process.env.NEXT_PUBLIC_APP_PROTOCOL,
host: process.env.NEXT_PUBLIC_APP_HOST,
port: process.env.NEXT_PUBLIC_APP_PORT,
baseUrl,
apiUrl,
});
export default config;
NEXT_PUBLIC_APP_PROTOCOL=http
NEXT_PUBLIC_APP_HOST=localhost
NEXT_PUBLIC_APP_PORT=3000
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_APP_INSTANCE=local
NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://airtable.com/shrqUAcjgGJ4jU88C
NEXT_PUBLIC_FEATURED_NETWORKS=[{'title':'Gnosis Chain','basePath':'/xdai/mainnet','group':'mainnets'}]
NEXT_PUBLIC_FOOTER_TELEGRAM_LINK=https://t.me/poa_network
NEXT_PUBLIC_FOOTER_STAKING_LINK=https://duneanalytics.com/maxaleks/xdai-staking
NEXT_PUBLIC_NETWORK_NAME=POA
NEXT_PUBLIC_NETWORK_SHORT_NAME=POA
NEXT_PUBLIC_NETWORK_ASSETS_PATHNAME=poa
NEXT_PUBLIC_NETWORK_TYPE=poa
NEXT_PUBLIC_NETWORK_SUBTYPE=core
NEXT_PUBLIC_NETWORK_ID=99
NEXT_PUBLIC_NETWORK_CURRENCY=POA
NEXT_PUBLIC_NETWORK_TOKEN_ADDRESS=0x029a799563238d0e75e20be2f4bda0ea68d00172
NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true
NEXT_PUBLIC_FEATURED_NETWORKS=[{'title':'Gnosis Chain','basePath':'/xdai/mainnet','group':'mainnets'},{'title':'Optimism on Gnosis Chain','basePath':'/xdai/optimism','group':'mainnets','icon':'https://www.fillmurray.com/60/60'},{'title':'Arbitrum on xDai','basePath':'/xdai/aox','group':'mainnets'},{'title':'Ethereum','basePath':'/eth/mainnet','group':'mainnets'},{'title':'Ethereum Classic','basePath':'/etx/mainnet','group':'mainnets'},{'title':'POA','basePath':'/poa/core','group':'mainnets'},{'title':'RSK','basePath':'/rsk/mainnet','group':'mainnets'},{'title':'Gnosis Chain Testnet','basePath':'/xdai/testnet','group':'testnets'},{'title':'POA Sokol','basePath':'/poa/sokol','group':'testnets'},{'title':'ARTIS Σ1','basePath':'/artis/sigma1','group':'other'},{'title':'LUKSO L14','basePath':'/lukso/l14','group':'other'},{'title':'Astar','basePath':'/astar','group':'other'}]
NEXT_PUBLIC_API_ENDPOINT=https://blockscout.com
NEXT_PUBLIC_API_BASE_PATH=/poa/core
const getCspPolicy = require('../../lib/csp/getCspPolicy');
async function headers() { async function headers() {
return [ return [
{ {
...@@ -14,10 +12,6 @@ async function headers() { ...@@ -14,10 +12,6 @@ async function headers() {
key: 'X-Content-Type-Options', key: 'X-Content-Type-Options',
value: 'nosniff', value: 'nosniff',
}, },
{
key: 'Content-Security-Policy-Report-Only',
value: getCspPolicy(),
},
], ],
}, },
]; ];
......
async function redirects() {
const homePagePath = '/' + [ process.env.NEXT_PUBLIC_NETWORK_TYPE, process.env.NEXT_PUBLIC_NETWORK_SUBTYPE ].filter(Boolean).join('/');
return [
{
source: '/',
destination: homePagePath,
permanent: false,
},
];
}
module.exports = redirects;
const parseNetworkConfig = require('../../lib/networks/parseNetworkConfig');
async function rewrites() { async function rewrites() {
// there can be networks without subtype // there can be networks without subtype
// routing in nextjs allows optional params only at the end of the path // routing in nextjs allows optional params only at the end of the path
// if there are paths with subtype and subsubtype, we will change the routing // if there are paths with subtype and subsubtype, we will change the routing
// but so far we think we're ok with this hack // but so far we think we're ok with this hack
const networksFromConfig = parseNetworkConfig(); //
return networksFromConfig.filter(n => !n.subType).map(n => ({ // UPDATE: as for now I hardcoded all networks without subtype
source: `/${ n.type }/:slug*`, // because we cannot do proper dynamic rewrites in middleware using runtime ENVs
destination: `/${ n.type }/mainnet/:slug*`, // see issue - https://github.com/vercel/next.js/discussions/35231
})); // it seems like it's solved but it's not actually
return [
{ source: '/astar/:slug*', destination: '/astar/mainnet/:slug*' },
{ source: '/shiden/:slug*', destination: '/shiden/mainnet/:slug*' },
];
} }
module.exports = rewrites; module.exports = rewrites;
import type { NextjsOptions } from '@sentry/nextjs/types/utils/nextjsOptions';
const config: NextjsOptions = {
environment: process.env.VERCEL_ENV || process.env.NODE_ENV,
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
release: process.env.NEXT_PUBLIC_GIT_COMMIT_SHA || process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA,
// We recommend adjusting this value in production, or using tracesSampler
// for finer control
tracesSampleRate: 1.0,
};
export default config;
import type * as Sentry from '@sentry/react';
import { BrowserTracing } from '@sentry/tracing';
export const config: Sentry.BrowserOptions = {
environment: process.env.VERCEL_ENV || process.env.NODE_ENV,
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
release: process.env.NEXT_PUBLIC_GIT_COMMIT_SHA || process.env.NEXT_PUBLIC_VERCEL_GIT_COMMIT_SHA,
integrations: [ new BrowserTracing() ],
// We recommend adjusting this value in production, or using tracesSampler
// for finer control
tracesSampleRate: 1.0,
};
export function configureScope(scope: Sentry.Scope) {
scope.setTag('app_instance', process.env.NEXT_PUBLIC_APP_INSTANCE);
}
#!/bin/bash
./replace_envs.sh
echo "starting Nextjs"
exec "$@"
\ No newline at end of file
#!/bin/bash
# no verbose
set +x
# config
envFilename='.env.template'
nextFolder='./.next/'
# replacing build-stage ENVs with run-stage ENVs
# https://raphaelpralat.medium.com/system-environment-variables-in-next-js-with-docker-1f0754e04cde
function replace_envs {
# read all config file
while read line; do
# no comment or not empty
if [ "${line:0:1}" == "#" ] || [ "${line}" == "" ]; then
continue
fi
# split
configName="$(cut -d'=' -f1 <<<"$line")"
configValue="$(cut -d'=' -f2 <<<"$line")"
# get system env
envValue=$(env | grep "^$configName=" | grep -oe '[^=]*$');
# if config found
if [ -n "$configValue" ] && [ -n "$envValue" ]; then
# replace all
echo "Replace: ${configValue} with: ${envValue}"
find $nextFolder \( -type d -name .git -prune \) -o -type f -print0 | xargs -0 sed -i "s#$configValue#$envValue#g"
fi
done < $envFilename
}
replace_envs
\ No newline at end of file
---
creation_rules:
- path_regex: ^(.+/)?secrets\.yaml$
pgp: >-
99E83B7490B1A9F51781E6055317CE0D5CE1230B
blockscout:
environment:
ACCOUNT_USERNAME:
_default: ENC[AES256_GCM,data:xJ0hxcuU8u+QwiXciZ8qe1sV1GuxZV8Z9iYgnoWCu0ueuEmNE80jwe+vqviNb/UbOQs=,iv:uRNP5RTG/oxUngEoBbvLg9GzS5gYiHlL42yttgYWPAc=,tag:plQfCpG9pN7d28ooZ3aHrQ==,type:str]
ACCOUNT_PASSWORD:
_default: ENC[AES256_GCM,data:aW52T8giY6Be3LMtiYJ7tJ0=,iv:vbHsPHzC/TI0wxwO1LgUuY7RWHUEpxP17b4NzcxH9Fc=,tag:kqqnsrIb+k8S+FUCAv2Y7Q==,type:str]
MAILSLURP_API_KEY:
_default: ENC[AES256_GCM,data:+q7GvdSeRQ0aY4grlQrdWUvxT9tzsRoii3W/ECwpVKs13lw84bGV+rbiEJXBYoOAw7SDz1Hr+KMy9SQzHvj12w==,iv:Xjo9+Nca6eDTZtUcDPAPfXYSAoP2gYmDX+JanCyPPkY=,tag:kQxLFsfN5YXft8ihgNvA+Q==,type:str]
MAILSLURP_EMAIL_ID:
_default: ENC[AES256_GCM,data:tGjukx3Q+NaWpvfXPXmY6dphdEdbO531q4NMqsOa4XCudfpt,iv:6S2jTCoJCfhkRHg4aA2UR1U4fMsDhNV44+r7ofnHLTA=,tag:nr+0zaM45+UBUwOpmVD1hg==,type:str]
ACCOUNT_AUTH0_DOMAIN:
_default: ENC[AES256_GCM,data:C6TAdc/RZ+qFOan+tWnTsfIDcYgxCNjuKOU=,iv:tEcHTumHxA9LreqLSwjcTTCP3igk/VQrJn/OWd+YQ4c=,tag:XOlWi3XHBZyENtLsv3afmw==,type:str]
ACCOUNT_AUTH0_CLIENT_ID:
_default: ENC[AES256_GCM,data:lojxfDVJBi1Sc6KCL28tln6RJ5leg0jmaTbSHbBd1H4=,iv:l1Iq/YsZKkFuorQgKd+KzAIkqbXHv6I4eid+7LDnyOA=,tag:/c7GBciDCruz+dAOfxncww==,type:str]
ACCOUNT_AUTH0_CLIENT_SECRET:
_default: ENC[AES256_GCM,data:Nmajob/xrOfx3sZgGHWw4/VnSgoyehubNdhJSqasxLFInJeZgxCW/QXpu5KG6j1fDdALLOT89Es+m0IO9VrG8A==,iv:RxYWdOo5G6t0FFwHQfhvUTiCOsxRzJJT0Y1nc0F5sDc=,tag:6v6+yHaHgwnw0cDSD1hXwg==,type:str]
ACCOUNT_AUTH0_CALLBACK_URL:
_default: ENC[AES256_GCM,data:MkvI0EZaWmsd8tDMU+qiLLZ2anCl00PW3u3Og9GXNtnZ+a7CUyY6rdFBOfE3fSOh4haoNuBxMCyxsAhHk/x0MNRNVqKjqT9I3VBAfzUzM+Ft4g==,iv:RuHXRpEAYKlm2UwN3s6AofiGiSmuRkVyO531rnUIklc=,tag:L3NnykZU4WFOOfPGDCdMUg==,type:str]
ACCOUNT_AUTH0_LOGOUT_RETURN_URL:
_default: ENC[AES256_GCM,data:9cWXw4j6LAg9qUzKTGevJE4U1leMV+i9DD26VDyFqlin4M6O1r/xzoWQtHfrHzxP1RRBuGPIjBqV/WCM1u9cVmhgrs2FQvzZkHk=,iv:Qrxdp4CfnSkCXMxhzMZvvpFSkglzdviq4UYGhffLDNk=,tag:RJhq0+B9Sa5D8k8cAhZW5A==,type:str]
ACCOUNT_AUTH0_LOGOUT_URL:
_default: ENC[AES256_GCM,data:6vj0qvxktXptFDX4Tq3kGIyhaJN7Adcwfgb3qWEYH78pWtmFprwfKNdPa/4=,iv:Eb3NcZP9oSmh0ZfY8vCWwR5Ciwe7Lr/QJmcNWXiir9E=,tag:uXoFnMfwo/GD9IPuvs/9Wg==,type:str]
ACCOUNT_SENDGRID_API_KEY:
_default: ENC[AES256_GCM,data:bNzimXY1TDUHgLijvgcJ8mw+9RXvCLchxhum77KY6ss5NUdoWuNuQjpC7M819XRPqhBmZkC2qBSX1ZRhcNQCB1OqGkRq,iv:JFmeCxsO2lkyScN8iVb8B/356SbP7KCMFffIPjGtN0o=,tag:A21hzDObJMAUhHQRWB7Z9w==,type:str]
ACCOUNT_SENDGRID_SENDER:
_default: ENC[AES256_GCM,data:68YVwUmudMo8n+1FENmPunPwLFSCgg==,iv:oALjuhqudA8zfOdse5Hd4IaLvEtY4VJ7exwUGsEPWFA=,tag:8F+rS2HZSfqCCXY4MkdTXw==,type:str]
ACCOUNT_SENDGRID_TEMPLATE:
_default: ENC[AES256_GCM,data:teIknLFnYfGDD7drMUTvcoXBxR9rOjc8L8nOVtMYVOuzXQ==,iv:LoBlBjrWDf5/LsmBo58510evXBTxiIW1YekeLmB+Mow=,tag:dZ/YdphM5MxEygeFYlT0Nw==,type:str]
ACCOUNT_PUBLIC_TAGS_AIRTABLE_URL:
_default: ENC[AES256_GCM,data:Y5Un1IwYz6luANlNDtbz6V9cdQ96sZFPbtS5MzDBPDFP8pBlQouhYeDBvB6irf2KiC3gYPHLea5lSWA=,iv:QH0A4zi/iWYka9n0SD2v+NXijNPyg0MPrGVawDzf5fU=,tag:wjUYXk943EXXF+++oNpLPw==,type:str]
ACCOUNT_PUBLIC_TAGS_AIRTABLE_API_KEY:
_default: ENC[AES256_GCM,data:WBxTPe+pQ2VzSPRmL3mzek0=,iv:0wYDv0BRmXrmz9EJg0cBclHpsPEQukO7tCnPAUlCKrs=,tag:3Zcyf/8kLU6ohUQ+97eONg==,type:str]
ACCOUNT_CLOAK_KEY:
_default: ENC[AES256_GCM,data:/+0GBBYW34fDHIAPpHCbNucu2FUH9VAI+wBV6dLHxvYk0whtXikiClentSk=,iv:ekOPgF0H/0/MG++cLi0gH+AzqWafkqFL9R7B8G8jPo4=,tag:QcI69GzicGVV96+EDOR81w==,type:str]
SECRET_KEY_BASE:
_default: ENC[AES256_GCM,data:Cqga1O4fbdtx5GfEQjJEPYWqeig7SBAdKiIif9sGCNrdy5FSHr43uskfdOdiS3uhaHm925jhPf+/nvs7VRzaqSAJCB2HBrVjJLOTQItVEw5rGus9Ma2plubDdrXh2CHO14mjE6f1/QOq2MLC1S79MqeHxpgqS0sgMflKopWa3/4=,iv:z1mBiInLa3kutmPFuX+R96rUSGcjFr/UH6cn/UbM0tg=,tag:8TRBcnTENdguj2BOs4Qrmw==,type:str]
SECRET_KEY_GUARDIAN:
_default: ENC[AES256_GCM,data:vpsqZh2sBKUiG4LtTVZyi3qBUU2EMPIrAp9k4X4iSYNvJM7iDGjq/du3EmsQCccvlr54x3opyypHYsdA7gL2Cg==,iv:9q8ePtwr2CDieE2ZU3UyxJ1wtKzIOY1oJ7rZmXCcWWM=,tag:BFO+PztjAOfQHh9mpC/ZJA==,type:str]
DATABASE_URL:
_default: ENC[AES256_GCM,data:r5JQJH+pq4zwo9ceFP4I8inttRFFxKKaw9+l9hDqeDMcEIPljAfJxvSF3noVlyfNtqO0ru+3GaTumDamdb9Hw2Y=,iv:OwCvrIgXpxVMI5QQfT7ZDGcsXBdzIcuv0wUSxtwkYrM=,tag:o0GFp3F+jTxw8Hg4Ce/IpQ==,type:str]
ACCOUNT_DATABASE_URL:
_default: ENC[AES256_GCM,data:48q2fFA3s1HGSm7YB5QV8B0eiI2PZHPgOsUS84R8x+8GecrFTenifbcHD6AivFNKovLuClUy2aFuEtEvJDOfelY=,iv:5ZLl62Yj/AXwyg+pdBLUv3EyYXSTOiSCqGYOv3rBx94=,tag:FUiy0haST4mdrqe2VlvrLg==,type:str]
ACCOUNT_REDIS_URL:
_default: ENC[AES256_GCM,data:MtAI4U/4mLdIW0d4ap5+0wTufO7hvOD/ETPnqpGJ7tGFXmirK2w/M5UdQd8M/EsH/4zk3rlM4WDGRVwMzCTiugG1FxEdIcKOA6127jXgSUrE,iv:zgrAoj7437b0TJTstU0S7UVHGt4fKnmP3wlc2qbURa0=,tag:vT3NL8PLLxB9z/dmkE/jaw==,type:str]
scVerifier:
environment:
SMART_CONTRACT_VERIFIER__SOLIDITY__FETCHER__S3__ACCESS_KEY:
_default: ENC[AES256_GCM,data:u9zFU9th8ShQ5HIaehYgMzOorx7Tfg7K,iv:ytd6IoU8PIMqbZ4W7kgkL5S4KV4MbvHfxMH2IthQnYE=,tag:0kgXuYSBZZp0LHG3m5EfYg==,type:str]
SMART_CONTRACT_VERIFIER__SOLIDITY__FETCHER__S3__SECRET_KEY:
_default: ENC[AES256_GCM,data:t78wGcNs4gqMav1TJaIyBUE4vxsaXloMSmPobix2zvpL2MfNl/6ERA==,iv:IdQot/B7QH9mJWFypTn9OIS+WOeq+B5KZYuYMkQ8+G4=,tag:xXX0WIIM5enPGaibiywtBg==,type:str]
SMART_CONTRACT_VERIFIER__SOLIDITY__FETCHER__S3__BUCKET:
_default: ENC[AES256_GCM,data:495QOb9qNhVugnOtaw==,iv:4h9S63+0P5qzeObiu5LJ3YKnFboSJ6WDE8tPnavySxg=,tag:ld3u6nYJH4qv3eN/pon5ow==,type:str]
postgres:
environment:
POSTGRES_PASSWORD:
_default: ENC[AES256_GCM,data:+DPLqOzpvlY=,iv:W6jbwxXI2NTb+kc7pGw0GPUpIPvgIfrNW9Uq5f64gS4=,tag:TV2bMILH/mYmUi0CxeYl2A==,type:str]
POSTGRES_DB:
_default: ENC[AES256_GCM,data:QRCvQVzKOfOApg==,iv:eOdxyjmv6Zj5xINGXO0dD/GcDgD1J8e4OzLMyOneoDs=,tag:EBnJ7OT3u7vcBIbviRKaHg==,type:str]
geth:
files:
list:
genesis.json: ENC[AES256_GCM,data:nSFncQhigaTtb7YSeXiUvua2LTOGlluVDywxQR5NnhvIBZCZniIxHsEhXKF9rBYceOMfBPKY5MTLIyxYisZeaJHL71A/uXQerDEKkECpzhwUSTdNEA9gUEqmpUjf7ChAWjybWw6NCM2jVhG3bnZwTwmuzeCSek85q4J503j9HVEkGXAU52A2+yoov41c+xoCEOj7W6Ka/f7yJYkyppoeS+4AIxBFcJ5hI0pIf1VqDcSrJf68JO1pPzcmp9eK3CK2CIpD8uxV6CseBFEqlbllmgAVwSUJ4i8XhYEO2hMyULm/LkW52U31/tq89yctSkp5TB62sZEHyDxXcq6jFSqd4840o9VedyX4YuEVtLBlXdzerc4Q2tHORQv5u2BLW7ktA/5vj8d9XnblQBDKrqxtUEgvSj9T/Xm4zyKjIhJBAAwUd5rF4j8dNbUGpsBm3G/Lx1+DqrS8cyxXgumtmfsklEVTWBra2dwt/tlTpF+utOwT7KtqXo2dVNvL6tUWrWGQqCt+fgeRKw8NuHWgU9Nm6baQ0yhWIM3XaCEuuJqHLdelYgQiKVEaTvlw7my7XwQsunJCQ98Q2eO61v7WFsg1m5s3Wyts8R2EnMWrnDlGro//eUHFlxDiIQGXmWgnz597lrLTXiuZKNI4pvPsAggBQxIeUcYzICy5uLRPWAiDpfshYt/fRQhIxmrVaJCufJrq4IcUdJYKjAcpnGHqZMB6quZiUPCPYg5Lvz7w1zoIVLn4ANWVuh7J1husNH13SwrDUXEm+k/WYCkDZm5B/K9IzkihY8nNXbvxHEXUClpb943Xw8hpvZZj0BfP/ye7ywUzHGlo3OmFAdE7Fbjc9eELxgYirONoaHy9GkjWPjinTVVlvitakqzx4dGJ+U2adSB01WAj63rn4ZidaC/eUpcPe7E2OeqmpkLc/kLM9g3gEe/MCiqsXXUYtm0Idpbrf+rRTAotoVOi3fKr1inlJY6n+9UzKAxNkTIkiGUQ1E9g0s3Kn4Z83apuy5+Us8gA9xNfWNGcOO105ZUYKyow4Xb5ggV8t+UTXPOZQp97Qz/6q0VC2kY9bx991SrJLoaK+ChLQ0/t/LcLjZxKRPDU2YGOfjrPDO3iscDkZgX/8IvH56jxUXAYmNrxOgbb9encYIrMDYrsKTaj5kXETp2M3sTdHwk6d78PAteYCvgQb44gu5JtknIq2LLXb/2Ue45O4HnrB+AsICPb+oGAxIqFkHvWuOIRZLqap8wbazszBlqsrX8ABegjvReXiG0rX1VeQIXNcG3/tKPHNKrDlFJykaiC1w==,iv:pyrxyy1bbv1UiGZ2y1jer9UqOwVkZGb4GwawJoCGJuM=,tag:aVt5dASuZglsUs9Yydzjaw==,type:str]
init.sh: ENC[AES256_GCM,data:2qHHfwC63yJlxqaqmQQ69CxtcYZiEgdLdr/Hcxk8eKEou16bXL1NxpxfW8MWsfZyB4S3QNj05V6WSpZAw9ITG/GnXOKocZqKbZPmmuwQpLoekC37EsdUETb+tFvIunh9xRmgst4/ByXEyjC9pZk8SN0Qr9cGQl3VMzeGSkRndaE+qnlFxHRO5ELbDyaLMcyLWaQncescCSvIDpyOPNN7NwS3PYW+Nq3rvh4y+2FBoHUPUMIq46q94+ROnFk5TQsUI5/sVdw/5fuJklcTeqxuo4pPFLAgtkXkab0k2Og0eMI4It1xIrwpfJnxKwbjd0zljKo8tNzjpPZKHMfYchPX6uGH9fnwvXcH7nKUUdtSWrWa8R9a5AyO7SJ0hfzewMzvOWspihJW3JXFTvfesoG/cMMBDg3icsD/0HyxVBQP2riSZsou3hzDFAOVoNLAQTNRVXckSxk9zbx0aOy3DlWefJhIFj2ZmF4sliwxMe+Oy/So6PfiRLsP9v6/OTv6sriExtHdAx6xeA+dbYCO9ruW+5gNB2UUi3DQvpvejaxysJygaHvYCVDGAUKCxDAvQPrUQ/r5+UaE+GmqflDzhd9lhAptL/xL8tW8R+v9RZdMO/G9kxWMhKWiHH6QqgdYGOvanwCxAGtSvKhqf6fiWnNMr6wKG4qAFk6cMNUsLHtnC1qLYhZAfBa1/aK5cG0IbygZKurC+9G8x4SmOqUpW8h23XRsNd8peCRuSZ2uSGqss/1XvngfW0avzGiXMHW9,iv:ldcn5mGmfGmSmq30mUen6diWK6EAEnYF6NQTXXwY8yA=,tag:kSuu7AGzzmSEM5AzjIw5RQ==,type:str]
password.txt: ""
frontend:
environment:
NEXT_PUBLIC_NETWORK_TOKEN_ADDRESS:
_default: ENC[AES256_GCM,data:fhH2QYodAkC/fIi5yU7Ur567buyIqQeTNUgserFNPs31yUeySk+BItpt,iv:7T7o5yX0Mvt1bYD9Cb6LxW9qHX1nzA99ykejJ4/KdpY=,tag:lfNUdrHGwU3BtsSvrnccYA==,type:str]
NEXT_PUBLIC_SENTRY_DSN:
_default: ENC[AES256_GCM,data:n/H2AH2n9ovn265iFbbrqeOOWS3s7FXgDv5FXJ2Dz133GuhTaqIU5psWjTraZ/Vh+dVM8M9zBLFfUanCWOz7cerq/QUoSVZjKvIvcyp6F072DGU=,iv:Co/pSR+U0vfkmWR+LDpxcQKDJl0WNbaEZihpzrRJcbc=,tag:HUiCX8WGJXWCDB59Y6iIbw==,type:str]
SENTRY_CSP_REPORT_URI:
_default: ENC[AES256_GCM,data:Hf4azYsGh2lysotK7afaHI85IaLBJeQmPlGE/lwokmX2eaQHVZJ/i5RsaoKoOCSiNyfAowr7P+6IBEC16BUTQMpbhYveBd7c/xjIsoomoHbTdoAdZA/QxNVpS4a2qVthruFudyT1BoZUHIGQ,iv:Mid+PfbslOyivrFSXopdeW96YvOcLP+g2RGcw1o7B98=,tag:IuPUsGdbZQodCMyi1DR04Q==,type:str]
sops:
kms: []
gcp_kms: []
azure_kv: []
hc_vault: []
age: []
lastmodified: "2022-10-10T10:14:07Z"
mac: ENC[AES256_GCM,data:eflai5xePeklnUiY+nkOHl0nEQ6UMrDS6COo6W24402u5w2S3A0XP7ZhgrWn4K+AogId2nxwwDt+CcsM9E2m8tIvWQeY7S1ywI9kY10NiTamQrho+b9J3ZPBQC1zfbskyrtLeb93h+utSXOs1lipKxBMiD609TboQNj6mzZWFMk=,iv:LR7XW6sDulFT102H5lr+e4+f6n1erBz8lgeyMOgrevk=,tag:2jVNlPm6dMckewseADrMiA==,type:str]
pgp:
- created_at: "2022-09-14T13:42:28Z"
enc: |
-----BEGIN PGP MESSAGE-----
hQEMA1MXzg1c4SMLAQgAoRceoDDpqXEbiz6DaMX7YS7j5mcp+xVoemU9qY4ln7dT
XtwAWiRVr7mf1ZDI77bQunbyWAU3zM/lFDsrlNUMAUGZzoOOtIGSnjCqCYB5JGiP
ZfdjG88RAJx+WMIgWl66PW1ceru8We+KauQyG7bD5g59g3b5RadEhH3VER4cYJof
VI0+NPpAcGciRsV3vxGGf4q1ppM3Pz0AnXcC+HB+hxWa5DeAhZlavFO2zYBCzw/o
ypyLCoOcuEDSj9AY+EYnjyXIS72DXPA953/8QSaMSZ9JVKG5imtXslGEdj9PIQOp
0rPqjZCxzydQaPZ68jXMg2Ci4gZT4ZPle9fRFxNGdtJeAScCUk+5L70orvNmWwgD
3fFnMY6tMDd/qSqSRFEJ0Vm0M4MYSg5mgW9M64zGfw1bZLLjsIGMe2ZqQC/sZh1O
Mrk3/xd4md/Ko8BQcaZ6lCi0olz6KWTzmQhXgNTx9A==
=mNH9
-----END PGP MESSAGE-----
fp: 99E83B7490B1A9F51781E6055317CE0D5CE1230B
unencrypted_suffix: _unencrypted
version: 3.7.3
global:
env: e2e
# enable Blockscout deploy
blockscout:
enabled: true
image:
_default: blockscout/blockscout:latest
replicas:
app: 1
docker:
port: 80
targetPort: 4000
service:
# ClusterIP, NodePort or LoadBalancer
type: ClusterIP
# enable ingress
ingress:
enabled: true
host:
_default: blockscout.test.blockscout.aws-k8s.blockscout.com
# enable https
tls:
enabled: true
# probes
livenessProbe:
enabled: true
path: /
readinessProbe:
enabled: true
path: /
resources:
limits:
memory:
_default: "1Gi"
cpu:
_default: "2"
requests:
memory:
_default: "1Gi"
cpu:
_default: "2"
# enable service to connect to RDS
rds:
enable: false
endpoint:
_default: <endpoint>.<region>.rds.amazonaws.com
# node label
nodeSelector:
enabled: true
app: blockscout
# Blockscout environment variables
environment:
ENV:
_default: test
RESOURCE_MODE:
_default: account
PUBLIC:
_default: 'false'
PORT:
_default: 4000
PORT_PG:
_default: 5432
PORT_NETWORK_HTTP:
_default: 8545
PORT_NETWORK_WS:
_default: 8546
ETHEREUM_JSONRPC_VARIANT:
_default: geth
ETHEREUM_JSONRPC_TRACE_URL:
_default: http://geth-svc:8545
ETHEREUM_JSONRPC_HTTP_URL:
_default: http://geth-svc:8545
ETHEREUM_JSONRPC_WS_URL:
_default: ws://geth-svc:8546
COIN:
_default: DAI
MIX_ENV:
_default: prod
ECTO_USE_SSL:
_default: 'false'
RUST_VERIFICATION_SERVICE_URL:
_default: http://sc-verifier-svc:8043
ACCOUNT_ENABLED:
_default: true
DISABLE_REALTIME_INDEXER:
_default: 'false'
SOCKET_ROOT:
_default: "/"
NETWORK_PATH:
_default: "/"
ETHEREUM_JSONRPC_DISABLE_ARCHIVE_BALANCES:
_default: 'true'
postgres:
enabled: true
image: postgres:13.8
port: 5432
command: '["docker-entrypoint.sh", "-c"]'
args: '["max_connections=300"]'
resources:
limits:
memory:
_default: "1Gi"
cpu:
_default: "1"
requests:
memory:
_default: "1Gi"
cpu:
_default: "1"
environment:
POSTGRES_USER:
_default: 'postgres'
POSTGRES_HOST_AUTH_METHOD:
_default: 'trust'
# enable geth deploy
geth:
enabled: true
image:
_default: ethereum/client-go:stable
replicas:
app: 1
portHttp: 8545
portWs: 8546
portAuth: 8551
command: '["sh","./root/init.sh"]'
args: '["--fakepow", "--dev", "--dev.period=1", "--datadir=/root/.ethereum/devnet", "--keystore=/root/.ethereum/devnet/keystore", "--password=/root/password.txt", "--unlock=0", "--unlock=1", "--mine", "--miner.threads=1", "--miner.etherbase=0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", "--ipcpath=/root/geth.ipc", "--http", "--http.vhosts=*", "--http.addr=0.0.0.0", "--http.port=8545", "--http.api=eth,net,web3,debug,txpool", "--ws", "--ws.origins=*", "--ws.addr=0.0.0.0", "--ws.port=8546", "--ws.api=eth,net,web3,debug,txpool", "--graphql", "--graphql.corsdomain=*", "--allow-insecure-unlock", "--rpc.allow-unprotected-txs", "--http.corsdomain=*", "--vmdebug", "--networkid=1337", "--rpc.txfeecap=0"]'
environment: {}
persistence:
enabled: false
resources:
limits:
memory:
_default: "2Gi"
cpu:
_default: "0.2"
requests:
memory:
_default: "2Gi"
cpu:
_default: "0.2"
# node label
nodeSelector:
enabled: true
app: blockscout
service:
# ClusterIP, NodePort or LoadBalancer
type: ClusterIP
# enable ingress
ingress:
enabled: true
host:
_default: asdfg-node.test.blockscout.aws-k8s.blockscout.com
# enable https
tls:
enabled: false
jwt:
enabled: false
files:
enabled: true
# enable Smart-contract-verifier deploy
scVerifier:
enabled: true
image:
_default: ghcr.io/blockscout/smart-contract-verifier:latest
replicas:
app: 1
docker:
port: 80
targetPort: 8043
metricsPort: 6060
service:
# ClusterIP, NodePort or LoadBalancer
type: ClusterIP
# enable ingress
ingress:
enabled: true
host:
_default: verifier.test.blockscout.aws-k8s.blockscout.com
testnet: asdfg-verifier.test.blockscout.aws-k8s.blockscout.com
# enable https
tls:
enabled: true
resources:
limits:
memory:
_default: "0.5Gi"
cpu:
_default: "0.25"
requests:
memory:
_default: "0.5Gi"
cpu:
_default: "0.25"
# node label
nodeSelector:
enabled: true
app: blockscout
# probes
livenessProbe:
enabled: true
path: /health
readinessProbe:
enabled: true
path: /health
# enable Horizontal Pod Autoscaler
hpa:
enabled: true
minReplicas: 1
maxReplicas: 10
cpuTarget: 90
environment:
SMART_CONTRACT_VERIFIER__SERVER__ADDR:
_default: 0.0.0.0:8043
# SMART_CONTRACT_VERIFIER__SOLIDITY__ENABLED:
# _default: 'true'
SMART_CONTRACT_VERIFIER__SOLIDITY__COMPILERS_DIR:
_default: /tmp/solidity-compilers
SMART_CONTRACT_VERIFIER__SOLIDITY__REFRESH_VERSIONS_SCHEDULE:
_default: 0 0 * * * * *
# It depends on the OS you are running the service on
# SMART_CONTRACT_VERIFIER__SOLIDITY__FETCHER__LIST__LIST_URL:
# _default: https://solc-bin.ethereum.org/linux-amd64/list.json
#SMART_CONTRACT_VERIFIER__SOLIDITY__FETCHER__LIST__LIST_URL=https://solc-bin.ethereum.org/macosx-amd64/list.json
#SMART_CONTRACT_VERIFIER__SOLIDITY__FETCHER__LIST__LIST_URL=https://solc-bin.ethereum.org/windows-amd64/list.json
SMART_CONTRACT_VERIFIER__SOLIDITY__FETCHER__S3__REGION:
_default: ""
SMART_CONTRACT_VERIFIER__SOLIDITY__FETCHER__S3__ENDPOINT:
_default: https://storage.googleapis.com
SMART_CONTRACT_VERIFIER__SOURCIFY__ENABLED:
_default: 'true'
SMART_CONTRACT_VERIFIER__SOURCIFY__API_URL:
_default: https://sourcify.dev/server/
SMART_CONTRACT_VERIFIER__SOURCIFY__VERIFICATION_ATTEMPTS:
_default: 3
SMART_CONTRACT_VERIFIER__SOURCIFY__REQUEST_TIMEOUT:
_default: 10
SMART_CONTRACT_VERIFIER__METRICS__ENABLED:
_default: 'true'
SMART_CONTRACT_VERIFIER__METRICS__ADDR:
_default: 0.0.0.0:6060
SMART_CONTRACT_VERIFIER__METRICS__ROUTE:
_default: /metrics
SMART_CONTRACT_VERIFIER__JAEGER__ENABLED:
_default: 'false'
frontend:
enabled: true
image:
_default: ghcr.io/blockscout/frontend:prerelease-5ca79e55
replicas:
app: 1
docker:
port: 80
targetPort: 3000
ingress:
enabled: true
host:
_default: frontend.test.blockscout.aws-k8s.blockscout.com
testnet: asdfg-frontend.test.blockscout.aws-k8s.blockscout.com
# enable https
tls:
enabled: true
resources:
limits:
memory:
_default: "0.3Gi"
cpu:
_default: "0.2"
requests:
memory:
_default: "0.3Gi"
cpu:
_default: "0.2"
# node label
nodeSelector:
enabled: true
app: blockscout
environment:
NEXT_PUBLIC_APP_PROTOCOL:
_default: http
NEXT_PUBLIC_APP_HOST:
_default: localhost
NEXT_PUBLIC_APP_PORT:
_default: 3000
NEXT_PUBLIC_BLOCKSCOUT_VERSION:
_default: v4.1.7-beta
NEXT_PUBLIC_FOOTER_GITHUB_LINK:
_default: https://github.com/blockscout/blockscout
NEXT_PUBLIC_FOOTER_TWITTER_LINK:
_default: https://www.twitter.com/blockscoutcom
NEXT_PUBLIC_APP_INSTANCE:
_default: local
NEXT_PUBLIC_FOOTER_TELEGRAM_LINK:
_default: https://t.me/poa_network
NEXT_PUBLIC_FOOTER_STAKING_LINK:
_default: https://duneanalytics.com/maxaleks/xdai-staking
NEXT_PUBLIC_NETWORK_NAME:
_default: POA
NEXT_PUBLIC_NETWORK_SHORT_NAME:
_default: POA
NEXT_PUBLIC_NETWORK_ASSETS_PATHNAME:
_default: poa
NEXT_PUBLIC_NETWORK_TYPE:
_default: poa
NEXT_PUBLIC_NETWORK_SUBTYPE:
_default: core
NEXT_PUBLIC_NETWORK_ID:
_default: 99
NEXT_PUBLIC_NETWORK_CURRENCY:
_default: POA
NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED:
_default: 'true'
NEXT_PUBLIC_FEATURED_NETWORKS:
_default: "[{'title':'Gnosis Chain','basePath':'/xdai/mainnet','group':'mainnets'},{'title':'Optimism on Gnosis Chain','basePath':'/xdai/optimism','group':'mainnets','icon':'https://www.fillmurray.com/60/60'},{'title':'Arbitrum on xDai','basePath':'/xdai/aox','group':'mainnets'},{'title':'Ethereum','basePath':'/eth/mainnet','group':'mainnets'},{'title':'Ethereum Classic','basePath':'/etx/mainnet','group':'mainnets'},{'title':'POA','basePath':'/poa/core','group':'mainnets'},{'title':'RSK','basePath':'/rsk/mainnet','group':'mainnets'},{'title':'Gnosis Chain Testnet','basePath':'/xdai/testnet','group':'testnets'},{'title':'POA Sokol','basePath':'/poa/sokol','group':'testnets'},{'title':'ARTIS Σ1','basePath':'/artis/sigma1','group':'other'},{'title':'LUKSO L14','basePath':'/lukso/l14','group':'other'},{'title':'Astar','basePath':'/astar','group':'other'}]"
NEXT_PUBLIC_API_ENDPOINT:
_default: https://blockscout.com
NEXT_PUBLIC_API_BASE_PATH: /
---
creation_rules:
- path_regex: ^(.+/)?secrets\.yaml$
pgp: >-
99E83B7490B1A9F51781E6055317CE0D5CE1230B
blockscout:
environment:
ACCOUNT_USERNAME:
_default: ENC[AES256_GCM,data:xJ0hxcuU8u+QwiXciZ8qe1sV1GuxZV8Z9iYgnoWCu0ueuEmNE80jwe+vqviNb/UbOQs=,iv:uRNP5RTG/oxUngEoBbvLg9GzS5gYiHlL42yttgYWPAc=,tag:plQfCpG9pN7d28ooZ3aHrQ==,type:str]
ACCOUNT_PASSWORD:
_default: ENC[AES256_GCM,data:aW52T8giY6Be3LMtiYJ7tJ0=,iv:vbHsPHzC/TI0wxwO1LgUuY7RWHUEpxP17b4NzcxH9Fc=,tag:kqqnsrIb+k8S+FUCAv2Y7Q==,type:str]
MAILSLURP_API_KEY:
_default: ENC[AES256_GCM,data:+q7GvdSeRQ0aY4grlQrdWUvxT9tzsRoii3W/ECwpVKs13lw84bGV+rbiEJXBYoOAw7SDz1Hr+KMy9SQzHvj12w==,iv:Xjo9+Nca6eDTZtUcDPAPfXYSAoP2gYmDX+JanCyPPkY=,tag:kQxLFsfN5YXft8ihgNvA+Q==,type:str]
MAILSLURP_EMAIL_ID:
_default: ENC[AES256_GCM,data:tGjukx3Q+NaWpvfXPXmY6dphdEdbO531q4NMqsOa4XCudfpt,iv:6S2jTCoJCfhkRHg4aA2UR1U4fMsDhNV44+r7ofnHLTA=,tag:nr+0zaM45+UBUwOpmVD1hg==,type:str]
ACCOUNT_AUTH0_DOMAIN:
_default: ENC[AES256_GCM,data:C6TAdc/RZ+qFOan+tWnTsfIDcYgxCNjuKOU=,iv:tEcHTumHxA9LreqLSwjcTTCP3igk/VQrJn/OWd+YQ4c=,tag:XOlWi3XHBZyENtLsv3afmw==,type:str]
ACCOUNT_AUTH0_CLIENT_ID:
_default: ENC[AES256_GCM,data:lojxfDVJBi1Sc6KCL28tln6RJ5leg0jmaTbSHbBd1H4=,iv:l1Iq/YsZKkFuorQgKd+KzAIkqbXHv6I4eid+7LDnyOA=,tag:/c7GBciDCruz+dAOfxncww==,type:str]
ACCOUNT_AUTH0_CLIENT_SECRET:
_default: ENC[AES256_GCM,data:Nmajob/xrOfx3sZgGHWw4/VnSgoyehubNdhJSqasxLFInJeZgxCW/QXpu5KG6j1fDdALLOT89Es+m0IO9VrG8A==,iv:RxYWdOo5G6t0FFwHQfhvUTiCOsxRzJJT0Y1nc0F5sDc=,tag:6v6+yHaHgwnw0cDSD1hXwg==,type:str]
ACCOUNT_AUTH0_CALLBACK_URL:
_default: ENC[AES256_GCM,data:MkvI0EZaWmsd8tDMU+qiLLZ2anCl00PW3u3Og9GXNtnZ+a7CUyY6rdFBOfE3fSOh4haoNuBxMCyxsAhHk/x0MNRNVqKjqT9I3VBAfzUzM+Ft4g==,iv:RuHXRpEAYKlm2UwN3s6AofiGiSmuRkVyO531rnUIklc=,tag:L3NnykZU4WFOOfPGDCdMUg==,type:str]
ACCOUNT_AUTH0_LOGOUT_RETURN_URL:
_default: ENC[AES256_GCM,data:9cWXw4j6LAg9qUzKTGevJE4U1leMV+i9DD26VDyFqlin4M6O1r/xzoWQtHfrHzxP1RRBuGPIjBqV/WCM1u9cVmhgrs2FQvzZkHk=,iv:Qrxdp4CfnSkCXMxhzMZvvpFSkglzdviq4UYGhffLDNk=,tag:RJhq0+B9Sa5D8k8cAhZW5A==,type:str]
ACCOUNT_AUTH0_LOGOUT_URL:
_default: ENC[AES256_GCM,data:6vj0qvxktXptFDX4Tq3kGIyhaJN7Adcwfgb3qWEYH78pWtmFprwfKNdPa/4=,iv:Eb3NcZP9oSmh0ZfY8vCWwR5Ciwe7Lr/QJmcNWXiir9E=,tag:uXoFnMfwo/GD9IPuvs/9Wg==,type:str]
ACCOUNT_SENDGRID_API_KEY:
_default: ENC[AES256_GCM,data:bNzimXY1TDUHgLijvgcJ8mw+9RXvCLchxhum77KY6ss5NUdoWuNuQjpC7M819XRPqhBmZkC2qBSX1ZRhcNQCB1OqGkRq,iv:JFmeCxsO2lkyScN8iVb8B/356SbP7KCMFffIPjGtN0o=,tag:A21hzDObJMAUhHQRWB7Z9w==,type:str]
ACCOUNT_SENDGRID_SENDER:
_default: ENC[AES256_GCM,data:68YVwUmudMo8n+1FENmPunPwLFSCgg==,iv:oALjuhqudA8zfOdse5Hd4IaLvEtY4VJ7exwUGsEPWFA=,tag:8F+rS2HZSfqCCXY4MkdTXw==,type:str]
ACCOUNT_SENDGRID_TEMPLATE:
_default: ENC[AES256_GCM,data:teIknLFnYfGDD7drMUTvcoXBxR9rOjc8L8nOVtMYVOuzXQ==,iv:LoBlBjrWDf5/LsmBo58510evXBTxiIW1YekeLmB+Mow=,tag:dZ/YdphM5MxEygeFYlT0Nw==,type:str]
ACCOUNT_PUBLIC_TAGS_AIRTABLE_URL:
_default: ENC[AES256_GCM,data:Y5Un1IwYz6luANlNDtbz6V9cdQ96sZFPbtS5MzDBPDFP8pBlQouhYeDBvB6irf2KiC3gYPHLea5lSWA=,iv:QH0A4zi/iWYka9n0SD2v+NXijNPyg0MPrGVawDzf5fU=,tag:wjUYXk943EXXF+++oNpLPw==,type:str]
ACCOUNT_PUBLIC_TAGS_AIRTABLE_API_KEY:
_default: ENC[AES256_GCM,data:WBxTPe+pQ2VzSPRmL3mzek0=,iv:0wYDv0BRmXrmz9EJg0cBclHpsPEQukO7tCnPAUlCKrs=,tag:3Zcyf/8kLU6ohUQ+97eONg==,type:str]
ACCOUNT_CLOAK_KEY:
_default: ENC[AES256_GCM,data:/+0GBBYW34fDHIAPpHCbNucu2FUH9VAI+wBV6dLHxvYk0whtXikiClentSk=,iv:ekOPgF0H/0/MG++cLi0gH+AzqWafkqFL9R7B8G8jPo4=,tag:QcI69GzicGVV96+EDOR81w==,type:str]
SECRET_KEY_BASE:
_default: ENC[AES256_GCM,data:Cqga1O4fbdtx5GfEQjJEPYWqeig7SBAdKiIif9sGCNrdy5FSHr43uskfdOdiS3uhaHm925jhPf+/nvs7VRzaqSAJCB2HBrVjJLOTQItVEw5rGus9Ma2plubDdrXh2CHO14mjE6f1/QOq2MLC1S79MqeHxpgqS0sgMflKopWa3/4=,iv:z1mBiInLa3kutmPFuX+R96rUSGcjFr/UH6cn/UbM0tg=,tag:8TRBcnTENdguj2BOs4Qrmw==,type:str]
SECRET_KEY_GUARDIAN:
_default: ENC[AES256_GCM,data:vpsqZh2sBKUiG4LtTVZyi3qBUU2EMPIrAp9k4X4iSYNvJM7iDGjq/du3EmsQCccvlr54x3opyypHYsdA7gL2Cg==,iv:9q8ePtwr2CDieE2ZU3UyxJ1wtKzIOY1oJ7rZmXCcWWM=,tag:BFO+PztjAOfQHh9mpC/ZJA==,type:str]
DATABASE_URL:
_default: ENC[AES256_GCM,data:r5JQJH+pq4zwo9ceFP4I8inttRFFxKKaw9+l9hDqeDMcEIPljAfJxvSF3noVlyfNtqO0ru+3GaTumDamdb9Hw2Y=,iv:OwCvrIgXpxVMI5QQfT7ZDGcsXBdzIcuv0wUSxtwkYrM=,tag:o0GFp3F+jTxw8Hg4Ce/IpQ==,type:str]
ACCOUNT_DATABASE_URL:
_default: ENC[AES256_GCM,data:48q2fFA3s1HGSm7YB5QV8B0eiI2PZHPgOsUS84R8x+8GecrFTenifbcHD6AivFNKovLuClUy2aFuEtEvJDOfelY=,iv:5ZLl62Yj/AXwyg+pdBLUv3EyYXSTOiSCqGYOv3rBx94=,tag:FUiy0haST4mdrqe2VlvrLg==,type:str]
ACCOUNT_REDIS_URL:
_default: ENC[AES256_GCM,data:MtAI4U/4mLdIW0d4ap5+0wTufO7hvOD/ETPnqpGJ7tGFXmirK2w/M5UdQd8M/EsH/4zk3rlM4WDGRVwMzCTiugG1FxEdIcKOA6127jXgSUrE,iv:zgrAoj7437b0TJTstU0S7UVHGt4fKnmP3wlc2qbURa0=,tag:vT3NL8PLLxB9z/dmkE/jaw==,type:str]
scVerifier:
environment:
SMART_CONTRACT_VERIFIER__SOLIDITY__FETCHER__S3__ACCESS_KEY:
_default: ENC[AES256_GCM,data:u9zFU9th8ShQ5HIaehYgMzOorx7Tfg7K,iv:ytd6IoU8PIMqbZ4W7kgkL5S4KV4MbvHfxMH2IthQnYE=,tag:0kgXuYSBZZp0LHG3m5EfYg==,type:str]
SMART_CONTRACT_VERIFIER__SOLIDITY__FETCHER__S3__SECRET_KEY:
_default: ENC[AES256_GCM,data:t78wGcNs4gqMav1TJaIyBUE4vxsaXloMSmPobix2zvpL2MfNl/6ERA==,iv:IdQot/B7QH9mJWFypTn9OIS+WOeq+B5KZYuYMkQ8+G4=,tag:xXX0WIIM5enPGaibiywtBg==,type:str]
SMART_CONTRACT_VERIFIER__SOLIDITY__FETCHER__S3__BUCKET:
_default: ENC[AES256_GCM,data:495QOb9qNhVugnOtaw==,iv:4h9S63+0P5qzeObiu5LJ3YKnFboSJ6WDE8tPnavySxg=,tag:ld3u6nYJH4qv3eN/pon5ow==,type:str]
postgres:
environment:
POSTGRES_PASSWORD:
_default: ENC[AES256_GCM,data:+DPLqOzpvlY=,iv:W6jbwxXI2NTb+kc7pGw0GPUpIPvgIfrNW9Uq5f64gS4=,tag:TV2bMILH/mYmUi0CxeYl2A==,type:str]
POSTGRES_DB:
_default: ENC[AES256_GCM,data:QRCvQVzKOfOApg==,iv:eOdxyjmv6Zj5xINGXO0dD/GcDgD1J8e4OzLMyOneoDs=,tag:EBnJ7OT3u7vcBIbviRKaHg==,type:str]
geth:
files:
list:
genesis.json: ENC[AES256_GCM,data:nSFncQhigaTtb7YSeXiUvua2LTOGlluVDywxQR5NnhvIBZCZniIxHsEhXKF9rBYceOMfBPKY5MTLIyxYisZeaJHL71A/uXQerDEKkECpzhwUSTdNEA9gUEqmpUjf7ChAWjybWw6NCM2jVhG3bnZwTwmuzeCSek85q4J503j9HVEkGXAU52A2+yoov41c+xoCEOj7W6Ka/f7yJYkyppoeS+4AIxBFcJ5hI0pIf1VqDcSrJf68JO1pPzcmp9eK3CK2CIpD8uxV6CseBFEqlbllmgAVwSUJ4i8XhYEO2hMyULm/LkW52U31/tq89yctSkp5TB62sZEHyDxXcq6jFSqd4840o9VedyX4YuEVtLBlXdzerc4Q2tHORQv5u2BLW7ktA/5vj8d9XnblQBDKrqxtUEgvSj9T/Xm4zyKjIhJBAAwUd5rF4j8dNbUGpsBm3G/Lx1+DqrS8cyxXgumtmfsklEVTWBra2dwt/tlTpF+utOwT7KtqXo2dVNvL6tUWrWGQqCt+fgeRKw8NuHWgU9Nm6baQ0yhWIM3XaCEuuJqHLdelYgQiKVEaTvlw7my7XwQsunJCQ98Q2eO61v7WFsg1m5s3Wyts8R2EnMWrnDlGro//eUHFlxDiIQGXmWgnz597lrLTXiuZKNI4pvPsAggBQxIeUcYzICy5uLRPWAiDpfshYt/fRQhIxmrVaJCufJrq4IcUdJYKjAcpnGHqZMB6quZiUPCPYg5Lvz7w1zoIVLn4ANWVuh7J1husNH13SwrDUXEm+k/WYCkDZm5B/K9IzkihY8nNXbvxHEXUClpb943Xw8hpvZZj0BfP/ye7ywUzHGlo3OmFAdE7Fbjc9eELxgYirONoaHy9GkjWPjinTVVlvitakqzx4dGJ+U2adSB01WAj63rn4ZidaC/eUpcPe7E2OeqmpkLc/kLM9g3gEe/MCiqsXXUYtm0Idpbrf+rRTAotoVOi3fKr1inlJY6n+9UzKAxNkTIkiGUQ1E9g0s3Kn4Z83apuy5+Us8gA9xNfWNGcOO105ZUYKyow4Xb5ggV8t+UTXPOZQp97Qz/6q0VC2kY9bx991SrJLoaK+ChLQ0/t/LcLjZxKRPDU2YGOfjrPDO3iscDkZgX/8IvH56jxUXAYmNrxOgbb9encYIrMDYrsKTaj5kXETp2M3sTdHwk6d78PAteYCvgQb44gu5JtknIq2LLXb/2Ue45O4HnrB+AsICPb+oGAxIqFkHvWuOIRZLqap8wbazszBlqsrX8ABegjvReXiG0rX1VeQIXNcG3/tKPHNKrDlFJykaiC1w==,iv:pyrxyy1bbv1UiGZ2y1jer9UqOwVkZGb4GwawJoCGJuM=,tag:aVt5dASuZglsUs9Yydzjaw==,type:str]
init.sh: ENC[AES256_GCM,data:2qHHfwC63yJlxqaqmQQ69CxtcYZiEgdLdr/Hcxk8eKEou16bXL1NxpxfW8MWsfZyB4S3QNj05V6WSpZAw9ITG/GnXOKocZqKbZPmmuwQpLoekC37EsdUETb+tFvIunh9xRmgst4/ByXEyjC9pZk8SN0Qr9cGQl3VMzeGSkRndaE+qnlFxHRO5ELbDyaLMcyLWaQncescCSvIDpyOPNN7NwS3PYW+Nq3rvh4y+2FBoHUPUMIq46q94+ROnFk5TQsUI5/sVdw/5fuJklcTeqxuo4pPFLAgtkXkab0k2Og0eMI4It1xIrwpfJnxKwbjd0zljKo8tNzjpPZKHMfYchPX6uGH9fnwvXcH7nKUUdtSWrWa8R9a5AyO7SJ0hfzewMzvOWspihJW3JXFTvfesoG/cMMBDg3icsD/0HyxVBQP2riSZsou3hzDFAOVoNLAQTNRVXckSxk9zbx0aOy3DlWefJhIFj2ZmF4sliwxMe+Oy/So6PfiRLsP9v6/OTv6sriExtHdAx6xeA+dbYCO9ruW+5gNB2UUi3DQvpvejaxysJygaHvYCVDGAUKCxDAvQPrUQ/r5+UaE+GmqflDzhd9lhAptL/xL8tW8R+v9RZdMO/G9kxWMhKWiHH6QqgdYGOvanwCxAGtSvKhqf6fiWnNMr6wKG4qAFk6cMNUsLHtnC1qLYhZAfBa1/aK5cG0IbygZKurC+9G8x4SmOqUpW8h23XRsNd8peCRuSZ2uSGqss/1XvngfW0avzGiXMHW9,iv:ldcn5mGmfGmSmq30mUen6diWK6EAEnYF6NQTXXwY8yA=,tag:kSuu7AGzzmSEM5AzjIw5RQ==,type:str]
password.txt: ""
frontend:
environment:
NEXT_PUBLIC_NETWORK_TOKEN_ADDRESS:
_default: ENC[AES256_GCM,data:fhH2QYodAkC/fIi5yU7Ur567buyIqQeTNUgserFNPs31yUeySk+BItpt,iv:7T7o5yX0Mvt1bYD9Cb6LxW9qHX1nzA99ykejJ4/KdpY=,tag:lfNUdrHGwU3BtsSvrnccYA==,type:str]
NEXT_PUBLIC_SENTRY_DSN:
_default: ENC[AES256_GCM,data:n/H2AH2n9ovn265iFbbrqeOOWS3s7FXgDv5FXJ2Dz133GuhTaqIU5psWjTraZ/Vh+dVM8M9zBLFfUanCWOz7cerq/QUoSVZjKvIvcyp6F072DGU=,iv:Co/pSR+U0vfkmWR+LDpxcQKDJl0WNbaEZihpzrRJcbc=,tag:HUiCX8WGJXWCDB59Y6iIbw==,type:str]
SENTRY_CSP_REPORT_URI:
_default: ENC[AES256_GCM,data:Hf4azYsGh2lysotK7afaHI85IaLBJeQmPlGE/lwokmX2eaQHVZJ/i5RsaoKoOCSiNyfAowr7P+6IBEC16BUTQMpbhYveBd7c/xjIsoomoHbTdoAdZA/QxNVpS4a2qVthruFudyT1BoZUHIGQ,iv:Mid+PfbslOyivrFSXopdeW96YvOcLP+g2RGcw1o7B98=,tag:IuPUsGdbZQodCMyi1DR04Q==,type:str]
sops:
kms: []
gcp_kms: []
azure_kv: []
hc_vault: []
age: []
lastmodified: "2022-10-10T10:14:07Z"
mac: ENC[AES256_GCM,data:eflai5xePeklnUiY+nkOHl0nEQ6UMrDS6COo6W24402u5w2S3A0XP7ZhgrWn4K+AogId2nxwwDt+CcsM9E2m8tIvWQeY7S1ywI9kY10NiTamQrho+b9J3ZPBQC1zfbskyrtLeb93h+utSXOs1lipKxBMiD609TboQNj6mzZWFMk=,iv:LR7XW6sDulFT102H5lr+e4+f6n1erBz8lgeyMOgrevk=,tag:2jVNlPm6dMckewseADrMiA==,type:str]
pgp:
- created_at: "2022-09-14T13:42:28Z"
enc: |
-----BEGIN PGP MESSAGE-----
hQEMA1MXzg1c4SMLAQgAoRceoDDpqXEbiz6DaMX7YS7j5mcp+xVoemU9qY4ln7dT
XtwAWiRVr7mf1ZDI77bQunbyWAU3zM/lFDsrlNUMAUGZzoOOtIGSnjCqCYB5JGiP
ZfdjG88RAJx+WMIgWl66PW1ceru8We+KauQyG7bD5g59g3b5RadEhH3VER4cYJof
VI0+NPpAcGciRsV3vxGGf4q1ppM3Pz0AnXcC+HB+hxWa5DeAhZlavFO2zYBCzw/o
ypyLCoOcuEDSj9AY+EYnjyXIS72DXPA953/8QSaMSZ9JVKG5imtXslGEdj9PIQOp
0rPqjZCxzydQaPZ68jXMg2Ci4gZT4ZPle9fRFxNGdtJeAScCUk+5L70orvNmWwgD
3fFnMY6tMDd/qSqSRFEJ0Vm0M4MYSg5mgW9M64zGfw1bZLLjsIGMe2ZqQC/sZh1O
Mrk3/xd4md/Ko8BQcaZ6lCi0olz6KWTzmQhXgNTx9A==
=mNH9
-----END PGP MESSAGE-----
fp: 99E83B7490B1A9F51781E6055317CE0D5CE1230B
unencrypted_suffix: _unencrypted
version: 3.7.3
global:
env: e2e
# enable Blockscout deploy
blockscout:
enabled: true
image:
_default: blockscout/blockscout:latest
replicas:
app: 1
docker:
port: 80
targetPort: 4000
service:
# ClusterIP, NodePort or LoadBalancer
type: ClusterIP
# enable ingress
ingress:
enabled: true
host:
_default: blockscout.test.blockscout.aws-k8s.blockscout.com
# enable https
tls:
enabled: true
# probes
livenessProbe:
enabled: true
path: /
readinessProbe:
enabled: true
path: /
resources:
limits:
memory:
_default: "1Gi"
cpu:
_default: "2"
requests:
memory:
_default: "1Gi"
cpu:
_default: "2"
# enable service to connect to RDS
rds:
enable: false
endpoint:
_default: <endpoint>.<region>.rds.amazonaws.com
# node label
nodeSelector:
enabled: true
app: blockscout
# Blockscout environment variables
environment:
ENV:
_default: test
RESOURCE_MODE:
_default: account
PUBLIC:
_default: 'false'
PORT:
_default: 4000
PORT_PG:
_default: 5432
PORT_NETWORK_HTTP:
_default: 8545
PORT_NETWORK_WS:
_default: 8546
ETHEREUM_JSONRPC_VARIANT:
_default: geth
ETHEREUM_JSONRPC_TRACE_URL:
_default: http://geth-svc:8545
ETHEREUM_JSONRPC_HTTP_URL:
_default: http://geth-svc:8545
ETHEREUM_JSONRPC_WS_URL:
_default: ws://geth-svc:8546
COIN:
_default: DAI
MIX_ENV:
_default: prod
ECTO_USE_SSL:
_default: 'false'
RUST_VERIFICATION_SERVICE_URL:
_default: http://sc-verifier-svc:8043
ACCOUNT_ENABLED:
_default: true
DISABLE_REALTIME_INDEXER:
_default: 'false'
SOCKET_ROOT:
_default: "/"
NETWORK_PATH:
_default: "/"
ETHEREUM_JSONRPC_DISABLE_ARCHIVE_BALANCES:
_default: 'true'
postgres:
enabled: true
image: postgres:13.8
port: 5432
command: '["docker-entrypoint.sh", "-c"]'
args: '["max_connections=300"]'
resources:
limits:
memory:
_default: "1Gi"
cpu:
_default: "1"
requests:
memory:
_default: "1Gi"
cpu:
_default: "1"
environment:
POSTGRES_USER:
_default: 'postgres'
POSTGRES_HOST_AUTH_METHOD:
_default: 'trust'
# enable geth deploy
geth:
enabled: true
image:
_default: ethereum/client-go:stable
replicas:
app: 1
portHttp: 8545
portWs: 8546
portAuth: 8551
command: '["sh","./root/init.sh"]'
args: '["--fakepow", "--dev", "--dev.period=1", "--datadir=/root/.ethereum/devnet", "--keystore=/root/.ethereum/devnet/keystore", "--password=/root/password.txt", "--unlock=0", "--unlock=1", "--mine", "--miner.threads=1", "--miner.etherbase=0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266", "--ipcpath=/root/geth.ipc", "--http", "--http.vhosts=*", "--http.addr=0.0.0.0", "--http.port=8545", "--http.api=eth,net,web3,debug,txpool", "--ws", "--ws.origins=*", "--ws.addr=0.0.0.0", "--ws.port=8546", "--ws.api=eth,net,web3,debug,txpool", "--graphql", "--graphql.corsdomain=*", "--allow-insecure-unlock", "--rpc.allow-unprotected-txs", "--http.corsdomain=*", "--vmdebug", "--networkid=1337", "--rpc.txfeecap=0"]'
environment: {}
persistence:
enabled: false
resources:
limits:
memory:
_default: "2Gi"
cpu:
_default: "0.2"
requests:
memory:
_default: "2Gi"
cpu:
_default: "0.2"
# node label
nodeSelector:
enabled: true
app: blockscout
service:
# ClusterIP, NodePort or LoadBalancer
type: ClusterIP
# enable ingress
ingress:
enabled: true
host:
_default: asdfg-node.test.blockscout.aws-k8s.blockscout.com
# enable https
tls:
enabled: false
jwt:
enabled: false
files:
enabled: true
# enable Smart-contract-verifier deploy
scVerifier:
enabled: true
image:
_default: ghcr.io/blockscout/smart-contract-verifier:latest
replicas:
app: 1
docker:
port: 80
targetPort: 8043
metricsPort: 6060
service:
# ClusterIP, NodePort or LoadBalancer
type: ClusterIP
# enable ingress
ingress:
enabled: true
host:
_default: verifier.test.blockscout.aws-k8s.blockscout.com
testnet: asdfg-verifier.test.blockscout.aws-k8s.blockscout.com
# enable https
tls:
enabled: true
resources:
limits:
memory:
_default: "0.5Gi"
cpu:
_default: "0.25"
requests:
memory:
_default: "0.5Gi"
cpu:
_default: "0.25"
# node label
nodeSelector:
enabled: true
app: blockscout
# probes
livenessProbe:
enabled: true
path: /health
readinessProbe:
enabled: true
path: /health
# enable Horizontal Pod Autoscaler
hpa:
enabled: true
minReplicas: 1
maxReplicas: 10
cpuTarget: 90
environment:
SMART_CONTRACT_VERIFIER__SERVER__ADDR:
_default: 0.0.0.0:8043
# SMART_CONTRACT_VERIFIER__SOLIDITY__ENABLED:
# _default: 'true'
SMART_CONTRACT_VERIFIER__SOLIDITY__COMPILERS_DIR:
_default: /tmp/solidity-compilers
SMART_CONTRACT_VERIFIER__SOLIDITY__REFRESH_VERSIONS_SCHEDULE:
_default: 0 0 * * * * *
# It depends on the OS you are running the service on
# SMART_CONTRACT_VERIFIER__SOLIDITY__FETCHER__LIST__LIST_URL:
# _default: https://solc-bin.ethereum.org/linux-amd64/list.json
#SMART_CONTRACT_VERIFIER__SOLIDITY__FETCHER__LIST__LIST_URL=https://solc-bin.ethereum.org/macosx-amd64/list.json
#SMART_CONTRACT_VERIFIER__SOLIDITY__FETCHER__LIST__LIST_URL=https://solc-bin.ethereum.org/windows-amd64/list.json
SMART_CONTRACT_VERIFIER__SOLIDITY__FETCHER__S3__REGION:
_default: ""
SMART_CONTRACT_VERIFIER__SOLIDITY__FETCHER__S3__ENDPOINT:
_default: https://storage.googleapis.com
SMART_CONTRACT_VERIFIER__SOURCIFY__ENABLED:
_default: 'true'
SMART_CONTRACT_VERIFIER__SOURCIFY__API_URL:
_default: https://sourcify.dev/server/
SMART_CONTRACT_VERIFIER__SOURCIFY__VERIFICATION_ATTEMPTS:
_default: 3
SMART_CONTRACT_VERIFIER__SOURCIFY__REQUEST_TIMEOUT:
_default: 10
SMART_CONTRACT_VERIFIER__METRICS__ENABLED:
_default: 'true'
SMART_CONTRACT_VERIFIER__METRICS__ADDR:
_default: 0.0.0.0:6060
SMART_CONTRACT_VERIFIER__METRICS__ROUTE:
_default: /metrics
SMART_CONTRACT_VERIFIER__JAEGER__ENABLED:
_default: 'false'
frontend:
enabled: true
image:
_default: ghcr.io/blockscout/frontend:prerelease-5ca79e55
replicas:
app: 1
docker:
port: 80
targetPort: 3000
ingress:
enabled: true
host:
_default: frontend.test.blockscout.aws-k8s.blockscout.com
testnet: asdfg-frontend.test.blockscout.aws-k8s.blockscout.com
# enable https
tls:
enabled: true
resources:
limits:
memory:
_default: "0.3Gi"
cpu:
_default: "0.2"
requests:
memory:
_default: "0.3Gi"
cpu:
_default: "0.2"
# node label
nodeSelector:
enabled: true
app: blockscout
environment:
NEXT_PUBLIC_APP_PROTOCOL:
_default: http
NEXT_PUBLIC_APP_HOST:
_default: localhost
NEXT_PUBLIC_APP_PORT:
_default: 3000
NEXT_PUBLIC_BLOCKSCOUT_VERSION:
_default: v4.1.8-beta
NEXT_PUBLIC_FOOTER_GITHUB_LINK:
_default: https://github.com/blockscout/blockscout
NEXT_PUBLIC_FOOTER_TWITTER_LINK:
_default: https://www.twitter.com/blockscoutcom
NEXT_PUBLIC_APP_INSTANCE:
_default: local
NEXT_PUBLIC_FOOTER_TELEGRAM_LINK:
_default: https://t.me/poa_network
NEXT_PUBLIC_FOOTER_STAKING_LINK:
_default: https://duneanalytics.com/maxaleks/xdai-staking
NEXT_PUBLIC_NETWORK_NAME:
_default: POA
NEXT_PUBLIC_NETWORK_SHORT_NAME:
_default: POA
NEXT_PUBLIC_NETWORK_ASSETS_PATHNAME:
_default: poa
NEXT_PUBLIC_NETWORK_TYPE:
_default: poa
NEXT_PUBLIC_NETWORK_SUBTYPE:
_default: core
NEXT_PUBLIC_NETWORK_ID:
_default: 99
NEXT_PUBLIC_NETWORK_CURRENCY:
_default: POA
NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED:
_default: 'true'
NEXT_PUBLIC_FEATURED_NETWORKS:
_default: "[{'title':'Gnosis Chain','basePath':'/xdai/mainnet','group':'mainnets'},{'title':'Optimism on Gnosis Chain','basePath':'/xdai/optimism','group':'mainnets','icon':'https://www.fillmurray.com/60/60'},{'title':'Arbitrum on xDai','basePath':'/xdai/aox','group':'mainnets'},{'title':'Ethereum','basePath':'/eth/mainnet','group':'mainnets'},{'title':'Ethereum Classic','basePath':'/etx/mainnet','group':'mainnets'},{'title':'POA','basePath':'/poa/core','group':'mainnets'},{'title':'RSK','basePath':'/rsk/mainnet','group':'mainnets'},{'title':'Gnosis Chain Testnet','basePath':'/xdai/testnet','group':'testnets'},{'title':'POA Sokol','basePath':'/poa/sokol','group':'testnets'},{'title':'ARTIS Σ1','basePath':'/artis/sigma1','group':'other'},{'title':'LUKSO L14','basePath':'/lukso/l14','group':'other'},{'title':'Astar','basePath':'/astar','group':'other'}]"
NEXT_PUBLIC_API_ENDPOINT:
_default: https://blockscout.com
NEXT_PUBLIC_API_BASE_PATH: /
import appConfig from 'configs/app/config';
import type { NextApiRequest } from 'next'; import type { NextApiRequest } from 'next';
import type { RequestInit, Response } from 'node-fetch'; import type { RequestInit, Response } from 'node-fetch';
import nodeFetch from 'node-fetch'; import nodeFetch from 'node-fetch';
...@@ -13,9 +14,9 @@ export default function fetchFactory(_req: NextApiRequest) { ...@@ -13,9 +14,9 @@ export default function fetchFactory(_req: NextApiRequest) {
'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 = `https://blockscout.com${ path }`; const url = new URL(path, appConfig.apiUrl);
return nodeFetch(url, { return nodeFetch(url.toString(), {
headers, headers,
...init, ...init,
}); });
......
import * as Sentry from '@sentry/nextjs'; // import * as Sentry from '@sentry/nextjs';
import type { NextApiRequest } from 'next'; import type { NextApiRequest } from 'next';
import * as cookies from 'lib/cookies'; import * as cookies from 'lib/cookies';
...@@ -7,9 +7,11 @@ export default function getUrlWithNetwork(_req: NextApiRequest, path: string) { ...@@ -7,9 +7,11 @@ export default function getUrlWithNetwork(_req: NextApiRequest, path: string) {
const networkType = _req.cookies[cookies.NAMES.NETWORK_TYPE]; const networkType = _req.cookies[cookies.NAMES.NETWORK_TYPE];
const networkSubType = _req.cookies[cookies.NAMES.NETWORK_SUB_TYPE]; const networkSubType = _req.cookies[cookies.NAMES.NETWORK_SUB_TYPE];
if (!networkType) { // if (!networkType) {
Sentry.captureException(new Error('Incorrect network'), { extra: { networkType, networkSubType } }); // TODO setup sentry for NodeJS
} // we probably do not need to if we will do api request from client directly
// Sentry.captureException(new Error('Incorrect network'), { extra: { networkType, networkSubType } });
// }
return `/${ networkType }${ networkSubType ? '/' + networkSubType : '' }/${ path }`; return `/${ networkType }${ networkSubType ? '/' + networkSubType : '' }/${ path }`;
} }
import { withSentry } from '@sentry/nextjs';
import type { NextApiRequest, NextApiResponse } from 'next'; import type { NextApiRequest, NextApiResponse } from 'next';
import fetchFactory from 'lib/api/fetch'; import fetchFactory from 'lib/api/fetch';
...@@ -42,5 +41,5 @@ export default function createHandler(getUrl: (_req: NextApiRequest) => string, ...@@ -42,5 +41,5 @@ export default function createHandler(getUrl: (_req: NextApiRequest) => string,
res.status(500).json(responseError); res.status(500).json(responseError);
}; };
return withSentry(handler); return handler;
} }
const getMarketplaceApps = require('../getMarketplaceApps'); import appConfig from 'configs/app/config';
const parseNetworkConfig = require('../networks/parseNetworkConfig');
import featuredNetworks from 'lib/networks/featuredNetworks';
import getMarketplaceApps from '../getMarketplaceApps';
const KEY_WORDS = { const KEY_WORDS = {
BLOB: 'blob:', BLOB: 'blob:',
...@@ -12,16 +15,18 @@ const KEY_WORDS = { ...@@ -12,16 +15,18 @@ const KEY_WORDS = {
UNSAFE_EVAL: '\'unsafe-eval\'', UNSAFE_EVAL: '\'unsafe-eval\'',
}; };
const MAIN_DOMAINS = [ '*.blockscout.com', 'blockscout.com' ]; const MAIN_DOMAINS = [ `*.${ appConfig.host }`, appConfig.host ];
// eslint-disable-next-line no-restricted-properties
const isDev = process.env.NODE_ENV === 'development'; const REPORT_URI = process.env.SENTRY_CSP_REPORT_URI;
function getNetworksExternalAssets() { function getNetworksExternalAssets() {
const icons = parseNetworkConfig() const icons = featuredNetworks
.filter(({ icon }) => typeof icon === 'string') .filter(({ icon }) => typeof icon === 'string')
.map(({ icon }) => new URL(icon)); .map(({ icon }) => new URL(icon as string));
const logo = appConfig.network.logo ? new URL(appConfig.network.logo) : undefined;
return icons; return logo ? icons.concat(logo) : icons;
} }
function getMarketplaceAppsOrigins() { function getMarketplaceAppsOrigins() {
...@@ -44,7 +49,7 @@ function makePolicyMap() { ...@@ -44,7 +49,7 @@ function makePolicyMap() {
KEY_WORDS.SELF, KEY_WORDS.SELF,
// webpack hmr in safari doesn't recognize localhost as 'self' for some reason // webpack hmr in safari doesn't recognize localhost as 'self' for some reason
isDev ? 'ws://localhost:3000/_next/webpack-hmr' : '', appConfig.isDev ? 'ws://localhost:3000/_next/webpack-hmr' : '',
// client error monitoring // client error monitoring
'sentry.io', '*.sentry.io', 'sentry.io', '*.sentry.io',
...@@ -55,7 +60,7 @@ function makePolicyMap() { ...@@ -55,7 +60,7 @@ function makePolicyMap() {
// next.js generates and rebuilds source maps in dev using eval() // next.js generates and rebuilds source maps in dev using eval()
// https://github.com/vercel/next.js/issues/14221#issuecomment-657258278 // https://github.com/vercel/next.js/issues/14221#issuecomment-657258278
isDev ? KEY_WORDS.UNSAFE_EVAL : '', appConfig.isDev ? KEY_WORDS.UNSAFE_EVAL : '',
...MAIN_DOMAINS, ...MAIN_DOMAINS,
...@@ -110,11 +115,13 @@ function makePolicyMap() { ...@@ -110,11 +115,13 @@ function makePolicyMap() {
KEY_WORDS.NONE, KEY_WORDS.NONE,
], ],
'report-uri': [
process.env.SENTRY_CSP_REPORT_URI,
],
'frame-src': getMarketplaceAppsOrigins(), 'frame-src': getMarketplaceAppsOrigins(),
...(REPORT_URI ? {
'report-uri': [
REPORT_URI,
],
} : {}),
}; };
} }
...@@ -135,4 +142,4 @@ function getCspPolicy() { ...@@ -135,4 +142,4 @@ function getCspPolicy() {
return policyString; return policyString;
} }
module.exports = getCspPolicy; export default getCspPolicy;
// should be CommonJS module since it used for next.config.js
const data = require('../data/marketplaceApps.json');
function getMarketplaceApps() {
return data;
}
module.exports = getMarketplaceApps;
import data from 'data/marketplaceApps.json';
export default function getMarketplaceApps() {
return data;
}
import * as Sentry from '@sentry/react';
import { config, configureScope } from 'configs/sentry/react';
import React from 'react';
export default function useConfigSentry() {
React.useEffect(() => {
// gotta init sentry in browser
Sentry.init(config);
Sentry.configureScope(configureScope);
}, []);
}
import * as Sentry from '@sentry/nextjs'; import * as Sentry from '@sentry/react';
import { useQueryClient } from '@tanstack/react-query'; import { useQueryClient } from '@tanstack/react-query';
import React from 'react'; import React from 'react';
...@@ -29,6 +29,12 @@ export default function useFetch() { ...@@ -29,6 +29,12 @@ export default function useFetch() {
return fetch(path, reqParams).then(response => { return fetch(path, reqParams).then(response => {
if (!response.ok) { if (!response.ok) {
const error = {
status: response.status,
statusText: response.statusText,
};
Sentry.captureException(new Error('Client fetch failed'), { extra: error, tags: { source: 'fetch' } });
return response.json().then( return response.json().then(
(jsonError) => Promise.reject({ (jsonError) => Promise.reject({
error: jsonError as Error, error: jsonError as Error,
...@@ -36,12 +42,6 @@ export default function useFetch() { ...@@ -36,12 +42,6 @@ export default function useFetch() {
statusText: response.statusText, statusText: response.statusText,
}), }),
() => { () => {
const error = {
status: response.status,
statusText: response.statusText,
};
Sentry.captureException(new Error('Client fetch failed'), { extra: error, tags: { source: 'fetch' } });
return Promise.reject(error); return Promise.reject(error);
}, },
); );
......
import appConfig from 'configs/app/config';
import React, { useMemo } from 'react'; import React, { useMemo } from 'react';
import marketplaceApps from 'data/marketplaceApps.json'; import marketplaceApps from 'data/marketplaceApps.json';
...@@ -16,14 +17,10 @@ import useCurrentRoute from 'lib/link/useCurrentRoute'; ...@@ -16,14 +17,10 @@ import useCurrentRoute from 'lib/link/useCurrentRoute';
import useLink from 'lib/link/useLink'; import useLink from 'lib/link/useLink';
import notEmpty from 'lib/notEmpty'; import notEmpty from 'lib/notEmpty';
import useNetwork from './useNetwork';
export default function useNavItems() { export default function useNavItems() {
const selectedNetwork = useNetwork();
const isMarketplaceFilled = useMemo(() => const isMarketplaceFilled = useMemo(() =>
marketplaceApps.filter(item => item.chainIds.includes(selectedNetwork?.chainId)), marketplaceApps.filter(item => item.chainIds.includes(appConfig.network.id)),
[ selectedNetwork?.chainId ]) [ ])
.length > 0; .length > 0;
const link = useLink(); const link = useLink();
......
import { useRouter } from 'next/router';
import findNetwork from 'lib/networks/findNetwork';
export default function useNetwork() {
const router = useRouter();
const selectedNetwork = findNetwork({
network_type: typeof router.query.network_type === 'string' ? router.query.network_type : '',
network_sub_type: typeof router.query.network_sub_type === 'string' ? router.query.network_sub_type : undefined,
});
return selectedNetwork;
}
import isBrowser from 'lib/isBrowser'; import appConfig from 'configs/app/config';
import findNetwork from 'lib/networks/findNetwork';
import { ROUTES } from './routes'; import { ROUTES } from './routes';
import type { RouteName } from './routes'; import type { RouteName } from './routes';
...@@ -12,27 +11,26 @@ export function link(routeName: RouteName, urlParams?: Record<string, Array<stri ...@@ -12,27 +11,26 @@ export function link(routeName: RouteName, urlParams?: Record<string, Array<stri
return ''; return '';
} }
const network = findNetwork({ // if we pass network type, we have to get subtype from params too
network_type: typeof urlParams?.network_type === 'string' ? urlParams?.network_type : '', // otherwise getting it from config since it is not cross-chain link
network_sub_type: typeof urlParams?.network_sub_type === 'string' ? urlParams?.network_sub_type : undefined, const networkSubType = typeof urlParams?.network_type === 'string' ? urlParams?.network_sub_type : appConfig.network.subtype;
});
const path = route.pattern.replace(PATH_PARAM_REGEXP, (_, paramName: string) => { const path = route.pattern.replace(PATH_PARAM_REGEXP, (_, paramName: string) => {
if (paramName === 'network_sub_type' && !network?.subType) { if (paramName === 'network_sub_type' && !networkSubType) {
return ''; return '';
} }
let paramValue = urlParams?.[paramName]; let paramValue = urlParams?.[paramName];
if (Array.isArray(paramValue)) { if (Array.isArray(paramValue)) {
// FIXME we don't have yet params as array, but typescript says that we could // FIXME we don't have yet params as array, but typescript says that we could
// dun't know how to manage it, fix me if you find an issue // dunno know how to manage it, fix me if you find an issue
paramValue = paramValue.join(','); paramValue = paramValue.join(',');
} }
return paramValue ? `/${ paramValue }` : ''; return paramValue ? `/${ paramValue }` : '';
}); });
const url = new URL(path, isBrowser() ? window.location.origin : 'https://blockscout.com'); const url = new URL(path, appConfig.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);
......
...@@ -3,6 +3,8 @@ export interface Route { ...@@ -3,6 +3,8 @@ export interface Route {
crossNetworkNavigation?: boolean; // route will not change when switching networks crossNetworkNavigation?: boolean; // route will not change when switching networks
} }
import appConfig from 'configs/app/config';
export type RouteName = keyof typeof ROUTES; export type RouteName = keyof typeof ROUTES;
const BASE_PATH = '/[network_type]/[network_sub_type]'; const BASE_PATH = '/[network_type]/[network_sub_type]';
...@@ -17,27 +19,21 @@ export const ROUTES = { ...@@ -17,27 +19,21 @@ export const ROUTES = {
// ACCOUNT // ACCOUNT
watchlist: { watchlist: {
pattern: `${ BASE_PATH }/account/watchlist`, pattern: `${ BASE_PATH }/account/watchlist`,
crossNetworkNavigation: true,
}, },
private_tags: { private_tags: {
pattern: `${ BASE_PATH }/account/tag_address`, pattern: `${ BASE_PATH }/account/tag_address`,
crossNetworkNavigation: true,
}, },
public_tags: { public_tags: {
pattern: `${ BASE_PATH }/account/public_tags_request`, pattern: `${ BASE_PATH }/account/public_tags_request`,
crossNetworkNavigation: true,
}, },
api_keys: { api_keys: {
pattern: `${ BASE_PATH }/account/api_key`, pattern: `${ BASE_PATH }/account/api_key`,
crossNetworkNavigation: true,
}, },
custom_abi: { custom_abi: {
pattern: `${ BASE_PATH }/account/custom_abi`, pattern: `${ BASE_PATH }/account/custom_abi`,
crossNetworkNavigation: true,
}, },
profile: { profile: {
pattern: `${ BASE_PATH }/auth/profile`, pattern: `${ BASE_PATH }/auth/profile`,
crossNetworkNavigation: true,
}, },
// TRANSACTIONS // TRANSACTIONS
...@@ -115,6 +111,6 @@ function checkRoutes(route: Record<string, Route>) { ...@@ -115,6 +111,6 @@ function checkRoutes(route: Record<string, Route>) {
return route; return route;
} }
if (process.env.NODE_ENV === 'development') { if (appConfig.isDev) {
checkRoutes(ROUTES); checkRoutes(ROUTES);
} }
import type { Network } 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 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 parseNetworkConfig from './parseNetworkConfig';
// will change later when we agree how to host network icons
const ICONS: Record<string, 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,
};
const LOGOS: Record<string, React.FunctionComponent<React.SVGAttributes<SVGElement>>> = {
'xdai/mainnet': require('icons/networks/logos/gnosis.svg'),
'eth/mainnet': require('icons/networks/logos/eth.svg'),
'etc/mainnet': require('icons/networks/logos/etc.svg'),
'poa/core': require('icons/networks/logos/poa.svg'),
'rsk/mainnet': require('icons/networks/logos/rsk.svg'),
'xdai/testnet': require('icons/networks/logos/gnosis.svg'),
'poa/sokol': require('icons/networks/logos/sokol.svg'),
'artis/sigma1': require('icons/networks/logos/artis.svg'),
'lukso/l14': require('icons/networks/logos/lukso.svg'),
astar: require('icons/networks/logos/astar.svg'),
shiden: require('icons/networks/logos/shiden.svg'),
shibuya: require('icons/networks/logos/shibuya.svg'),
};
const NETWORKS: Array<Network> = (() => {
const networksFromConfig: Array<Network> = parseNetworkConfig();
return networksFromConfig.map((network) => ({
...network,
logo: network.logo || LOGOS[network.type + (network.subType ? `/${ network.subType }` : '')],
icon: network.icon || ICONS[network.type + (network.subType ? `/${ network.subType }` : '')],
}));
})();
export default NETWORKS;
// for easy env creation
// const FOR_CONFIG = [
// {
// name: 'Gnosis Chain',
// type: 'xdai',
// subType: 'mainnet',
// group: 'mainnets',
// isAccountSupported: true,
// chainId: 100,
// currency: 'xDAI',
// },
// {
// name: 'Optimism on Gnosis Chain',
// shortName: 'OoG',
// type: 'xdai',
// subType: 'optimism',
// group: 'mainnets',
// icon: 'https://www.fillmurray.com/60/60',
// chainId: 300,
// currency: 'xDAI',
// },
// {
// name: 'Arbitrum on xDai',
// type: 'xdai',
// subType: 'aox',
// group: 'mainnets',
// chainId: 200,
// currency: 'xDAI',
// },
// {
// name: 'Ethereum',
// shortName: 'ETH',
// type: 'eth',
// subType: 'mainnet',
// group: 'mainnets',
// chainId: 1,
// currency: 'ETH',
// },
// {
// name: 'Ethereum Classic',
// shortName: 'ETC',
// type: 'etc',
// subType: 'mainnet',
// group: 'mainnets',
// chainId: 61,
// currency: 'ETC',
// },
// {
// name: 'POA',
// shortName: 'POA',
// type: 'poa',
// subType: 'core',
// group: 'mainnets',
// chainId: 99,
// currency: 'POA',
// isAccountSupported: true,
// nativeTokenAddress: '0x029a799563238d0e75e20be2f4bda0ea68d00172',
// },
// {
// name: 'RSK',
// shortName: 'RBTC',
// type: 'rsk',
// subType: 'mainnet',
// group: 'mainnets',
// chainId: 30,
// currency: 'RBTC',
// },
// {
// name: 'Gnosis Chain Testnet',
// type: 'xdai',
// subType: 'testnet',
// group: 'testnets',
// isAccountSupported: true,
// currency: 'xDAI',
// },
// {
// name: 'POA Sokol',
// shortName: 'POA',
// type: 'poa',
// subType: 'sokol',
// group: 'testnets',
// chainId: 77,
// currency: 'SPOA',
// },
// {
// name: 'ARTIS Σ1',
// type: 'artis',
// subType: 'sigma1',
// group: 'other',
// chainId: 246529,
// currency: 'ATS',
// },
// {
// name: 'LUKSO L14',
// shortName: 'POA',
// type: 'lukso',
// subType: 'l14',
// group: 'other',
// chainId: 22,
// currency: 'LYX',
// },
// {
// name: 'Astar',
// type: 'astar',
// group: 'other',
// chainId: 22,
// currency: 'ASTR',
// },
// ];
import appConfig from 'configs/app/config';
import type { FeaturedNetwork } 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 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: Record<string, 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
// const FEATURED_NETWORKS = JSON.stringify([
// {
// title: 'Gnosis Chain',
// basePath: '/xdai/mainnet',
// group: 'mainnets',
// },
// {
// title: 'Optimism on Gnosis Chain',
// basePath: '/xdai/optimism',
// group: 'mainnets',
// icon: 'https://www.fillmurray.com/60/60',
// },
// {
// title: 'Arbitrum on xDai',
// basePath: '/xdai/aox',
// group: 'mainnets',
// },
// {
// title: 'Ethereum',
// basePath: '/eth/mainnet',
// group: 'mainnets',
// },
// {
// title: 'Ethereum Classic',
// basePath: '/etx/mainnet',
// group: 'mainnets',
// },
// {
// title: 'POA',
// basePath: '/poa/core',
// group: 'mainnets',
// },
// {
// title: 'RSK',
// basePath: '/rsk/mainnet',
// group: 'mainnets',
// },
// {
// title: 'Gnosis Chain Testnet',
// basePath: '/xdai/testnet',
// group: 'testnets',
// },
// {
// title: 'POA Sokol',
// basePath: '/poa/sokol',
// group: 'testnets',
// },
// {
// title: 'ARTIS Σ1',
// basePath: '/artis/sigma1',
// group: 'other',
// },
// {
// title: 'LUKSO L14',
// basePath: '/lukso/l14',
// group: 'other',
// },
// {
// title: 'Astar',
// basePath: '/astar',
// group: 'other',
// },
// ]).replaceAll('"', '\'');
function parseNetworkConfig() {
try {
return JSON.parse(appConfig.featuredNetworks || '[]');
} catch (error) {
return [];
}
}
const featuredNetworks: Array<FeaturedNetwork> = (() => {
const networksFromConfig: Array<FeaturedNetwork> = parseNetworkConfig();
return networksFromConfig.map((network) => ({
...network,
icon: network.icon || ICONS[network.basePath],
}));
})();
export default featuredNetworks;
import availableNetworks from 'lib/networks/availableNetworks';
interface Params {
network_type: string;
network_sub_type?: string;
}
export default function findNetwork(params: Params) {
return availableNetworks.find((network) =>
network.type === params.network_type &&
network.subType ? network.subType === params.network_sub_type : network.type === params.network_type,
);
}
import NETWORKS from './availableNetworks';
export default function getAvailablePaths() {
return NETWORKS.map(({ type, subType }) => ({ params: { network_type: type, network_sub_type: subType || 'mainnet' } }));
}
import findNetwork from './findNetwork'; import appConfig from 'configs/app/config';
export default function getNetworkTitle({ network_type: type, network_sub_type: subType }: {network_type?: string; network_sub_type?: string}) { export default function getNetworkTitle() {
const currentNetwork = findNetwork({ network_type: type || '', network_sub_type: subType }); return appConfig.network.name + (appConfig.network.shortName ? ` (${ appConfig.network.shortName })` : '') + ' Explorer';
if (currentNetwork) {
return currentNetwork.name + (currentNetwork.shortName ? ` (${ currentNetwork.shortName })` : '') + ' Explorer';
}
return '';
} }
// should be CommonJS module since it used for next.config.js
function parseNetworkConfig() {
try {
return JSON.parse(process.env.NEXT_PUBLIC_SUPPORTED_NETWORKS || '[]');
} catch (error) {
return [];
}
}
module.exports = parseNetworkConfig;
import appConfig from 'configs/app/config';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import React from 'react'; import React from 'react';
import useNetwork from 'lib/hooks/useNetwork';
import isAccountRoute from 'lib/link/isAccountRoute';
import { link } from 'lib/link/link'; import { link } from 'lib/link/link';
import { ROUTES } from 'lib/link/routes'; import { ROUTES } from 'lib/link/routes';
import useCurrentRoute from 'lib/link/useCurrentRoute'; import useCurrentRoute from 'lib/link/useCurrentRoute';
import NETWORKS from 'lib/networks/availableNetworks'; import featuredNetworks from 'lib/networks/featuredNetworks';
export default function useNetworkNavigationItems() { export default function useNetworkNavigationItems() {
const selectedNetwork = useNetwork();
const currentRouteName = useCurrentRoute()(); const currentRouteName = useCurrentRoute()();
const currentRoute = ROUTES[currentRouteName]; const currentRoute = ROUTES[currentRouteName];
const router = useRouter(); const router = useRouter();
const isAccount = isAccountRoute(currentRouteName);
return React.useMemo(() => { return React.useMemo(() => {
return NETWORKS.map((network) => { return featuredNetworks.map((network) => {
const routeName = 'crossNetworkNavigation' in currentRoute && currentRoute.crossNetworkNavigation ? currentRouteName : 'network_index';
const routeName = (() => { const [ , networkType, networkSubtype ] = network.basePath.split('/');
if ('crossNetworkNavigation' in currentRoute && currentRoute.crossNetworkNavigation) { const url = link(routeName, { ...router.query, network_type: networkType, network_sub_type: networkSubtype });
if ((isAccount && network.isAccountSupported) || !isAccount) {
return currentRouteName;
}
}
return 'network_index';
})();
const url = link(routeName, { ...router.query, network_type: network.type, network_sub_type: network.subType });
return { return {
...network, ...network,
url: url, url: url,
isActive: selectedNetwork?.type === network.type && selectedNetwork?.subType === network?.subType, isActive: appConfig.network.basePath === network.basePath,
}; };
}); });
}, [ currentRoute, currentRouteName, isAccount, router.query, selectedNetwork?.subType, selectedNetwork?.type ]); }, [ currentRoute, currentRouteName, router.query ]);
} }
import type { GetStaticPaths } from 'next';
export const getStaticPaths: GetStaticPaths = async() => {
return { paths: [], fallback: true };
};
import type { GetStaticPaths } from 'next';
export const getStaticPaths: GetStaticPaths = async() => {
return { paths: [], fallback: true };
};
...@@ -3,7 +3,7 @@ import type { PageParams } from './types'; ...@@ -3,7 +3,7 @@ import type { PageParams } from './types';
import getNetworkTitle from 'lib/networks/getNetworkTitle'; import getNetworkTitle from 'lib/networks/getNetworkTitle';
export default function getSeo(params?: PageParams) { export default function getSeo(params?: PageParams) {
const networkTitle = getNetworkTitle(params || {}); const networkTitle = getNetworkTitle();
return { return {
title: params ? `Block ${ params.id } - ${ networkTitle }` : '', title: params ? `Block ${ params.id } - ${ networkTitle }` : '',
......
import type { GetStaticPaths } from 'next';
export const getStaticPaths: GetStaticPaths = async() => {
return { paths: [], fallback: true };
};
...@@ -2,18 +2,12 @@ import type { NextPage } from 'next'; ...@@ -2,18 +2,12 @@ import type { NextPage } from 'next';
import Head from 'next/head'; import Head from 'next/head';
import React from 'react'; import React from 'react';
import type { PageParams } from './types';
import Blocks from 'ui/pages/Blocks'; import Blocks from 'ui/pages/Blocks';
import getSeo from './getSeo'; import getSeo from './getSeo';
type Props = { const BlocksNextPage: NextPage = () => {
pageParams: PageParams; const { title } = getSeo();
}
const BlocksNextPage: NextPage<Props> = ({ pageParams }: Props) => {
const { title } = getSeo(pageParams);
return ( return (
<> <>
<Head> <Head>
......
import type { PageParams } from './types';
import getNetworkTitle from 'lib/networks/getNetworkTitle'; import getNetworkTitle from 'lib/networks/getNetworkTitle';
export default function getSeo(params?: PageParams) { export default function getSeo() {
const networkTitle = getNetworkTitle(params || {});
return { return {
title: params ? `${ networkTitle } - BlockScout` : '', title: getNetworkTitle(),
}; };
} }
import type { GetStaticPaths } from 'next'; import type { GetStaticPaths } from 'next';
export const getStaticPaths: GetStaticPaths = async() => { export const getStaticPaths: GetStaticPaths = async() => {
return { paths: [], fallback: true }; return { paths: [], fallback: 'blocking' };
}; };
...@@ -3,7 +3,7 @@ import type { PageParams } from './types'; ...@@ -3,7 +3,7 @@ import type { PageParams } from './types';
import getNetworkTitle from 'lib/networks/getNetworkTitle'; import getNetworkTitle from 'lib/networks/getNetworkTitle';
export default function getSeo(params?: PageParams) { export default function getSeo(params?: PageParams) {
const networkTitle = getNetworkTitle(params || {}); const networkTitle = getNetworkTitle();
return { return {
title: params ? `Transaction ${ params.id } - ${ networkTitle }` : '', title: params ? `Transaction ${ params.id } - ${ networkTitle }` : '',
......
const { NextResponse } = require('next/server');
const { NAMES } = require('lib/cookies');
const { link } = require('lib/link/link');
const findNetwork = require('lib/networks/findNetwork').default;
export function middleware(req) {
const [ , networkType, networkSubtype ] = req.nextUrl.pathname.split('/');
const networkParams = {
network_type: networkType,
network_sub_type: networkSubtype,
};
const selectedNetwork = findNetwork(networkParams);
if (selectedNetwork) {
const apiToken = req.cookies.get(NAMES.API_TOKEN);
if (!apiToken) {
const authUrl = link('auth', networkParams);
return NextResponse.redirect(authUrl);
}
}
}
export const config = {
matcher: '/:network_type/:network_sub_type/account/:path*',
};
import appConfig from 'configs/app/config';
import type { NextRequest } from 'next/server';
import { NextResponse } from 'next/server';
import { NAMES } from 'lib/cookies';
import getCspPolicy from 'lib/csp/getCspPolicy';
import { link } from 'lib/link/link';
const cspPolicy = getCspPolicy();
export function middleware(req: NextRequest) {
const isPageRequest = req.headers.get('accept')?.includes('text/html');
if (!isPageRequest) {
return;
}
const [ , networkType, networkSubtype ] = req.nextUrl.pathname.split('/');
const networkParams = {
network_type: networkType,
network_sub_type: networkSubtype,
};
if (appConfig.network.type !== networkType && appConfig.network.subtype !== networkSubtype) {
const url = req.nextUrl.clone();
url.pathname = `/404`;
return NextResponse.rewrite(url);
}
// we don't have any info from router here, so just do straight forward sub-string search (sorry)
const isAccountRoute = req.nextUrl.pathname.includes('/account/');
const apiToken = req.cookies.get(NAMES.API_TOKEN);
if (isAccountRoute && !apiToken) {
const authUrl = link('auth', networkParams);
return NextResponse.redirect(authUrl);
}
const res = NextResponse.next();
res.headers.append('Content-Security-Policy-Report-Only', cspPolicy);
return res;
}
/**
* Configure which routes should pass through the Middleware.
* Exclude all `_next` urls.
*/
export const config = {
matcher: [ '/', '/:notunderscore((?!_next).+)' ],
};
const { withSentryConfig } = require('@sentry/nextjs');
const withReactSvg = require('next-react-svg'); const withReactSvg = require('next-react-svg');
const path = require('path'); const path = require('path');
const headers = require('./configs/nextjs/headers'); const headers = require('./configs/nextjs/headers');
const redirects = require('./configs/nextjs/redirects');
const rewrites = require('./configs/nextjs/rewrites'); const rewrites = require('./configs/nextjs/rewrites');
const moduleExports = { const moduleExports = {
...@@ -18,36 +18,14 @@ const moduleExports = { ...@@ -18,36 +18,14 @@ const moduleExports = {
return config; return config;
}, },
async redirects() { // NOTE: all config functions should be static and not depend on any environment variables
return [ // since all variables will be passed to the app only at runtime and there is now way to change Next.js config at this time
{ // if you are stuck and strongly believe what you need some sort of flexibility here please fill free to join the discussion
source: '/', // https://github.com/blockscout/frontend/discussions/167
destination: '/poa/core',
permanent: false,
},
];
},
headers,
rewrites, rewrites,
redirects,
headers,
output: 'standalone', output: 'standalone',
sentry: {
hideSourceMaps: true,
},
};
const sentryWebpackPluginOptions = {
// Additional config options for the Sentry Webpack plugin. Keep in mind that
// the following options are set automatically, and overriding them is not
// recommended:
// release, url, org, project, authToken, configFile, stripPrefix,
// urlPrefix, include, ignore
silent: true, // Suppresses all logs
// For all available options, see:
// https://github.com/getsentry/sentry-webpack-plugin#options.
deploy: {
env: process.env.VERCEL_ENV || process.env.NODE_ENV,
},
}; };
module.exports = withReactSvg(withSentryConfig(moduleExports, sentryWebpackPluginOptions)); module.exports = withReactSvg(moduleExports);
...@@ -9,9 +9,13 @@ ...@@ -9,9 +9,13 @@
}, },
"scripts": { "scripts": {
"dev": "next dev", "dev": "next dev",
"dev:poa_core": "./node_modules/.bin/dotenv -e ./configs/envs/.env.poa_core -e ./configs/envs/.env.common -e ./configs/envs/.env.secrets yarn dev",
"build": "next build", "build": "next build",
"build:docker": "docker build --build-arg GIT_COMMIT_SHA=$(git rev-parse HEAD) -t blockscout ./",
"start": "next start", "start": "next start",
"start:docker:poa_core": "docker run -p 3000:3000 --env-file ./configs/envs/.env.poa_core --env-file ./configs/envs/.env.common --env-file ./configs/envs/.env.secrets blockscout",
"lint:eslint": "./node_modules/.bin/eslint . --ext .js,.jsx,.ts,.tsx", "lint:eslint": "./node_modules/.bin/eslint . --ext .js,.jsx,.ts,.tsx",
"lint:tsc": "./node_modules/.bin/tsc -p ./tsconfig.json",
"prepare": "husky install", "prepare": "husky install",
"format-svg": "./node_modules/.bin/svgo -r ./icons", "format-svg": "./node_modules/.bin/svgo -r ./icons",
"test-ct": "playwright test -c playwright-ct.config.ts", "test-ct": "playwright test -c playwright-ct.config.ts",
...@@ -23,6 +27,8 @@ ...@@ -23,6 +27,8 @@
"@emotion/react": "^11", "@emotion/react": "^11",
"@emotion/styled": "^11", "@emotion/styled": "^11",
"@sentry/nextjs": "^7.12.1", "@sentry/nextjs": "^7.12.1",
"@sentry/react": "^7.13.0",
"@sentry/tracing": "^7.13.0",
"@tanstack/react-query": "^4.0.10", "@tanstack/react-query": "^4.0.10",
"@tanstack/react-query-devtools": "^4.0.10", "@tanstack/react-query-devtools": "^4.0.10",
"@types/react-scroll": "^1.8.4", "@types/react-scroll": "^1.8.4",
...@@ -49,6 +55,7 @@ ...@@ -49,6 +55,7 @@
"@types/react": "18.0.9", "@types/react": "18.0.9",
"@types/react-dom": "18.0.5", "@types/react-dom": "18.0.5",
"@typescript-eslint/eslint-plugin": "^5.27.0", "@typescript-eslint/eslint-plugin": "^5.27.0",
"dotenv-cli": "^6.0.0",
"eslint": "8.16.0", "eslint": "8.16.0",
"eslint-config-next": "^12.3.0", "eslint-config-next": "^12.3.0",
"eslint-plugin-es5": "^1.5.0", "eslint-plugin-es5": "^1.5.0",
......
...@@ -14,8 +14,8 @@ type Props = { ...@@ -14,8 +14,8 @@ type Props = {
pageParams: PageParams; pageParams: PageParams;
} }
const ApiKeysPage: NextPage<Props> = ({ pageParams }: Props) => { const ApiKeysPage: NextPage<Props> = () => {
const title = getNetworkTitle(pageParams || {}); const title = getNetworkTitle();
return ( return (
<> <>
<Head><title>{ title }</title></Head> <Head><title>{ title }</title></Head>
...@@ -26,5 +26,5 @@ const ApiKeysPage: NextPage<Props> = ({ pageParams }: Props) => { ...@@ -26,5 +26,5 @@ const ApiKeysPage: NextPage<Props> = ({ pageParams }: Props) => {
export default ApiKeysPage; export default ApiKeysPage;
export { getStaticPaths } from 'lib/next/account/getStaticPaths'; export { getStaticPaths } from 'lib/next/getStaticPaths';
export { getStaticProps } from 'lib/next/getStaticProps'; export { getStaticProps } from 'lib/next/getStaticProps';
...@@ -14,8 +14,8 @@ type Props = { ...@@ -14,8 +14,8 @@ type Props = {
pageParams: PageParams; pageParams: PageParams;
} }
const CustomAbiPage: NextPage<Props> = ({ pageParams }: Props) => { const CustomAbiPage: NextPage<Props> = () => {
const title = getNetworkTitle(pageParams || {}); const title = getNetworkTitle();
return ( return (
<> <>
<Head><title>{ title }</title></Head> <Head><title>{ title }</title></Head>
...@@ -26,5 +26,5 @@ const CustomAbiPage: NextPage<Props> = ({ pageParams }: Props) => { ...@@ -26,5 +26,5 @@ const CustomAbiPage: NextPage<Props> = ({ pageParams }: Props) => {
export default CustomAbiPage; export default CustomAbiPage;
export { getStaticPaths } from 'lib/next/account/getStaticPaths'; export { getStaticPaths } from 'lib/next/getStaticPaths';
export { getStaticProps } from 'lib/next/getStaticProps'; export { getStaticProps } from 'lib/next/getStaticProps';
...@@ -14,8 +14,8 @@ type Props = { ...@@ -14,8 +14,8 @@ type Props = {
pageParams: PageParams; pageParams: PageParams;
} }
const PublicTagsPage: NextPage<Props> = ({ pageParams }: Props) => { const PublicTagsPage: NextPage<Props> = () => {
const title = getNetworkTitle(pageParams || {}); const title = getNetworkTitle();
return ( return (
<> <>
<Head><title>{ title }</title></Head> <Head><title>{ title }</title></Head>
...@@ -26,5 +26,5 @@ const PublicTagsPage: NextPage<Props> = ({ pageParams }: Props) => { ...@@ -26,5 +26,5 @@ const PublicTagsPage: NextPage<Props> = ({ pageParams }: Props) => {
export default PublicTagsPage; export default PublicTagsPage;
export { getStaticPaths } from 'lib/next/account/getStaticPaths'; export { getStaticPaths } from 'lib/next/getStaticPaths';
export { getStaticProps } from 'lib/next/getStaticProps'; export { getStaticProps } from 'lib/next/getStaticProps';
...@@ -14,8 +14,8 @@ type Props = { ...@@ -14,8 +14,8 @@ type Props = {
pageParams: PageParams; pageParams: PageParams;
} }
const AddressTagsPage: NextPage<Props> = ({ pageParams }: Props) => { const AddressTagsPage: NextPage<Props> = () => {
const title = getNetworkTitle(pageParams || {}); const title = getNetworkTitle();
return ( return (
<> <>
<Head><title>{ title }</title></Head> <Head><title>{ title }</title></Head>
...@@ -26,5 +26,5 @@ const AddressTagsPage: NextPage<Props> = ({ pageParams }: Props) => { ...@@ -26,5 +26,5 @@ const AddressTagsPage: NextPage<Props> = ({ pageParams }: Props) => {
export default AddressTagsPage; export default AddressTagsPage;
export { getStaticPaths } from 'lib/next/account/getStaticPaths'; export { getStaticPaths } from 'lib/next/getStaticPaths';
export { getStaticProps } from 'lib/next/getStaticProps'; export { getStaticProps } from 'lib/next/getStaticProps';
...@@ -14,8 +14,8 @@ type Props = { ...@@ -14,8 +14,8 @@ type Props = {
pageParams: PageParams; pageParams: PageParams;
} }
const WatchListPage: NextPage<Props> = ({ pageParams }: Props) => { const WatchListPage: NextPage<Props> = () => {
const title = getNetworkTitle(pageParams || {}); const title = getNetworkTitle();
return ( return (
<> <>
<Head> <Head>
...@@ -28,5 +28,5 @@ const WatchListPage: NextPage<Props> = ({ pageParams }: Props) => { ...@@ -28,5 +28,5 @@ const WatchListPage: NextPage<Props> = ({ pageParams }: Props) => {
export default WatchListPage; export default WatchListPage;
export { getStaticPaths } from 'lib/next/account/getStaticPaths'; export { getStaticPaths } from 'lib/next/getStaticPaths';
export { getStaticProps } from 'lib/next/getStaticProps'; export { getStaticProps } from 'lib/next/getStaticProps';
...@@ -17,3 +17,6 @@ const AppsPage = () => { ...@@ -17,3 +17,6 @@ const AppsPage = () => {
}; };
export default AppsPage; export default AppsPage;
export { getStaticPaths } from 'lib/next/getStaticPaths';
export { getStaticProps } from 'lib/next/getStaticProps';
...@@ -47,5 +47,5 @@ const AppPage: NextPage = () => { ...@@ -47,5 +47,5 @@ const AppPage: NextPage = () => {
export default AppPage; export default AppPage;
export { getStaticPaths } from 'lib/next/apps/getStaticPaths'; export { getStaticPaths } from 'lib/next/getStaticPaths';
export { getStaticProps } from 'lib/next/getStaticProps'; export { getStaticProps } from 'lib/next/getStaticProps';
...@@ -15,5 +15,5 @@ const MyProfilePage: NextPage = () => { ...@@ -15,5 +15,5 @@ const MyProfilePage: NextPage = () => {
export default MyProfilePage; export default MyProfilePage;
export { getStaticPaths } from 'lib/next/account/getStaticPaths'; export { getStaticPaths } from 'lib/next/getStaticPaths';
export { getStaticProps } from 'lib/next/getStaticProps'; export { getStaticProps } from 'lib/next/getStaticProps';
...@@ -17,5 +17,5 @@ const BlockPage: NextPage<Props> = ({ pageParams }: Props) => { ...@@ -17,5 +17,5 @@ const BlockPage: NextPage<Props> = ({ pageParams }: Props) => {
export default BlockPage; export default BlockPage;
export { getStaticPaths } from 'lib/next/block/getStaticPaths'; export { getStaticPaths } from 'lib/next/getStaticPaths';
export { getStaticProps } from 'lib/next/getStaticProps'; export { getStaticProps } from 'lib/next/getStaticProps';
...@@ -9,13 +9,13 @@ type Props = { ...@@ -9,13 +9,13 @@ type Props = {
pageParams: PageParams; pageParams: PageParams;
} }
const BlockPage: NextPage<Props> = ({ pageParams }: Props) => { const BlockPage: NextPage<Props> = () => {
return ( return (
<BlocksNextPage pageParams={ pageParams }/> <BlocksNextPage/>
); );
}; };
export default BlockPage; export default BlockPage;
export { getStaticPaths } from 'lib/next/block/getStaticPaths'; export { getStaticPaths } from 'lib/next/getStaticPaths';
export { getStaticProps } from 'lib/next/getStaticProps'; export { getStaticProps } from 'lib/next/getStaticProps';
...@@ -15,5 +15,5 @@ const HomePage: NextPage = () => { ...@@ -15,5 +15,5 @@ const HomePage: NextPage = () => {
export default HomePage; export default HomePage;
export { getStaticPaths } from 'lib/next/account/getStaticPaths'; export { getStaticPaths } from 'lib/next/getStaticPaths';
export { getStaticProps } from 'lib/next/getStaticProps'; export { getStaticProps } from 'lib/next/getStaticProps';
...@@ -17,5 +17,5 @@ const TransactionPage: NextPage<Props> = ({ pageParams }: Props) => { ...@@ -17,5 +17,5 @@ const TransactionPage: NextPage<Props> = ({ pageParams }: Props) => {
export default TransactionPage; export default TransactionPage;
export { getStaticPaths } from 'lib/next/tx/getStaticPaths'; export { getStaticPaths } from 'lib/next/getStaticPaths';
export { getStaticProps } from 'lib/next/getStaticProps'; export { getStaticProps } from 'lib/next/getStaticProps';
...@@ -14,8 +14,8 @@ type Props = { ...@@ -14,8 +14,8 @@ type Props = {
pageParams: PageParams; pageParams: PageParams;
} }
const AddressTagsPage: NextPage<Props> = ({ pageParams }: Props) => { const AddressTagsPage: NextPage<Props> = () => {
const title = getNetworkTitle(pageParams || {}); const title = getNetworkTitle();
return ( return (
<> <>
<Head><title>{ title }</title></Head> <Head><title>{ title }</title></Head>
...@@ -26,5 +26,5 @@ const AddressTagsPage: NextPage<Props> = ({ pageParams }: Props) => { ...@@ -26,5 +26,5 @@ const AddressTagsPage: NextPage<Props> = ({ pageParams }: Props) => {
export default AddressTagsPage; export default AddressTagsPage;
export { getStaticPaths } from 'lib/next/account/getStaticPaths'; export { getStaticPaths } from 'lib/next/getStaticPaths';
export { getStaticProps } from 'lib/next/getStaticProps'; export { getStaticProps } from 'lib/next/getStaticProps';
...@@ -4,10 +4,13 @@ import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; ...@@ -4,10 +4,13 @@ import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import type { AppProps } from 'next/app'; import type { AppProps } from 'next/app';
import React, { useState } from 'react'; import React, { useState } from 'react';
import useConfigSentry from 'lib/hooks/useConfigSentry';
import type { ErrorType } from 'lib/hooks/useFetch'; import type { ErrorType } from 'lib/hooks/useFetch';
import theme from 'theme'; import theme from 'theme';
function MyApp({ Component, pageProps }: AppProps) { function MyApp({ Component, pageProps }: AppProps) {
useConfigSentry();
const [ queryClient ] = useState(() => new QueryClient({ const [ queryClient ] = useState(() => new QueryClient({
defaultOptions: { defaultOptions: {
queries: { queries: {
......
...@@ -17,6 +17,7 @@ ...@@ -17,6 +17,7 @@
*/ */
import * as Sentry from '@sentry/nextjs'; import * as Sentry from '@sentry/nextjs';
import sentryConfig from 'configs/sentry/nextjs';
import type { NextPageContext } from 'next'; import type { NextPageContext } from 'next';
import NextErrorComponent from 'next/error'; import NextErrorComponent from 'next/error';
import React from 'react'; import React from 'react';
...@@ -34,6 +35,8 @@ const CustomErrorComponent = (props: { statusCode: number }) => { ...@@ -34,6 +35,8 @@ const CustomErrorComponent = (props: { statusCode: number }) => {
}; };
CustomErrorComponent.getInitialProps = async(contextData: ContextOrProps) => { CustomErrorComponent.getInitialProps = async(contextData: ContextOrProps) => {
Sentry.init(sentryConfig);
// In case this is running in a serverless function, await this in order to give Sentry // In case this is running in a serverless function, await this in order to give Sentry
// time to send the error before the lambda exits // time to send the error before the lambda exits
await Sentry.captureUnderscoreErrorException(contextData); await Sentry.captureUnderscoreErrorException(contextData);
......
// This file configures the initialization of Sentry on the browser.
// The config you add here will be used whenever a page is visited.
// https://docs.sentry.io/platforms/javascript/guides/nextjs/
import * as Sentry from '@sentry/nextjs';
const SENTRY_DSN = process.env.SENTRY_DSN || process.env.NEXT_PUBLIC_SENTRY_DSN;
const ENV = process.env.NEXT_PUBLIC_VERCEL_ENV || process.env.NODE_ENV;
Sentry.init({
environment: ENV,
dsn: SENTRY_DSN,
// Adjust this value in production, or use tracesSampler for greater control
tracesSampleRate: 1.0,
// ...
// Note: if you want to override the automatic release value, do not set a
// `release` value here - use the environment variable `SENTRY_RELEASE`, so
// that it will also get attached to your source maps
});
defaults.url=https://sentry.io/
defaults.org=block-scout
defaults.project=new-ui
cli.executable=../../.npm/_npx/a8388072043b4cbc/node_modules/@sentry/cli/bin/sentry-cli
// This file configures the initialization of Sentry on the server.
// The config you add here will be used whenever the server handles a request.
// https://docs.sentry.io/platforms/javascript/guides/nextjs/
import * as Sentry from '@sentry/nextjs';
const SENTRY_DSN = process.env.SENTRY_DSN || process.env.NEXT_PUBLIC_SENTRY_DSN;
const ENV = process.env.VERCEL_ENV || process.env.NODE_ENV;
Sentry.init({
environment: ENV,
dsn: SENTRY_DSN,
// Adjust this value in production, or use tracesSampler for greater control
tracesSampleRate: 1.0,
// ...
// Note: if you want to override the automatic release value, do not set a
// `release` value here - use the environment variable `SENTRY_RELEASE`, so
// that it will also get attached to your source maps
});
...@@ -2,18 +2,9 @@ import type { FunctionComponent, SVGAttributes } from 'react'; ...@@ -2,18 +2,9 @@ import type { FunctionComponent, SVGAttributes } from 'react';
export type NetworkGroup = 'mainnets' | 'testnets' | 'other'; export type NetworkGroup = 'mainnets' | 'testnets' | 'other';
export interface Network { export interface FeaturedNetwork {
name: string; title: string;
chainId: number; // https://chainlist.org/ basePath: string;
currency: string; group: NetworkGroup;
nativeTokenAddress: string;
shortName?: string;
// basePath = /<type>/<subType>, e.g. /xdai/mainnet
type: string;
subType?: string;
group: 'mainnets' | 'testnets' | 'other';
icon?: FunctionComponent<SVGAttributes<SVGElement>> | string; icon?: FunctionComponent<SVGAttributes<SVGElement>> | string;
logo?: FunctionComponent<SVGAttributes<SVGElement>> | string;
isAccountSupported?: boolean;
assetsNamePath?: string;
} }
import appConfig from 'configs/app/config';
import debounce from 'lodash/debounce'; import debounce from 'lodash/debounce';
import React, { useCallback, useEffect, useState } from 'react'; import React, { useCallback, useEffect, useState } from 'react';
import type { AppItemOverview, MarketplaceCategoriesIds } from 'types/client/apps'; import type { AppItemOverview, MarketplaceCategoriesIds } from 'types/client/apps';
import marketplaceApps from 'data/marketplaceApps.json'; import marketplaceApps from 'data/marketplaceApps.json';
import useNetwork from 'lib/hooks/useNetwork';
const favoriteAppsLocalStorageKey = 'favoriteApps'; const favoriteAppsLocalStorageKey = 'favoriteApps';
...@@ -27,7 +27,6 @@ function isAppCategoryMatches(category: MarketplaceCategoriesIds, app: AppItemOv ...@@ -27,7 +27,6 @@ function isAppCategoryMatches(category: MarketplaceCategoriesIds, app: AppItemOv
} }
export default function useMarketplaceApps() { export default function useMarketplaceApps() {
const selectedNetwork = useNetwork();
const [ isLoading, setIsLoading ] = useState(true); const [ isLoading, setIsLoading ] = useState(true);
const [ defaultAppList, setDefaultAppList ] = useState<Array<AppItemOverview>>(); const [ defaultAppList, setDefaultAppList ] = useState<Array<AppItemOverview>>();
const [ displayedApps, setDisplayedApps ] = useState<Array<AppItemOverview>>([]); const [ displayedApps, setDisplayedApps ] = useState<Array<AppItemOverview>>([]);
...@@ -80,18 +79,14 @@ export default function useMarketplaceApps() { ...@@ -80,18 +79,14 @@ export default function useMarketplaceApps() {
}, [ filterQuery, category, filterApps ]); }, [ filterQuery, category, filterApps ]);
useEffect(() => { useEffect(() => {
if (!selectedNetwork) {
return;
}
const defaultDisplayedApps = [ ...marketplaceApps ] const defaultDisplayedApps = [ ...marketplaceApps ]
.filter(item => item.chainIds.includes(selectedNetwork?.chainId)) .filter(item => item.chainIds.includes(appConfig.network.id))
.sort((a, b) => a.title.localeCompare(b.title)); .sort((a, b) => a.title.localeCompare(b.title));
setDefaultAppList(defaultDisplayedApps); setDefaultAppList(defaultDisplayedApps);
setDisplayedApps(defaultDisplayedApps); setDisplayedApps(defaultDisplayedApps);
setIsLoading(false); setIsLoading(false);
}, [ selectedNetwork ]); }, [ ]);
return React.useMemo(() => ({ return React.useMemo(() => ({
category, category,
......
import { Grid, GridItem, Text, Icon, Link, Box, Tooltip } from '@chakra-ui/react'; import { Grid, GridItem, Text, Icon, Link, Box, Tooltip } from '@chakra-ui/react';
import appConfig from 'configs/app/config';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import React from 'react'; import React from 'react';
import { scroller, Element } from 'react-scroll'; import { scroller, Element } from 'react-scroll';
...@@ -7,7 +8,6 @@ import { block } from 'data/block'; ...@@ -7,7 +8,6 @@ import { block } from 'data/block';
import clockIcon from 'icons/clock.svg'; import clockIcon from 'icons/clock.svg';
import flameIcon from 'icons/flame.svg'; import flameIcon from 'icons/flame.svg';
import dayjs from 'lib/date/dayjs'; import dayjs from 'lib/date/dayjs';
import useNetwork from 'lib/hooks/useNetwork';
import { space } from 'lib/html-entities'; import { space } from 'lib/html-entities';
import useLink from 'lib/link/useLink'; import useLink from 'lib/link/useLink';
import AddressLink from 'ui/shared/address/AddressLink'; import AddressLink from 'ui/shared/address/AddressLink';
...@@ -23,7 +23,6 @@ const BlockDetails = () => { ...@@ -23,7 +23,6 @@ const BlockDetails = () => {
const [ isExpanded, setIsExpanded ] = React.useState(false); const [ isExpanded, setIsExpanded ] = React.useState(false);
const link = useLink(); const link = useLink();
const router = useRouter(); const router = useRouter();
const network = useNetwork();
const handleCutClick = React.useCallback(() => { const handleCutClick = React.useCallback(() => {
setIsExpanded((flag) => !flag); setIsExpanded((flag) => !flag);
...@@ -79,7 +78,7 @@ const BlockDetails = () => { ...@@ -79,7 +78,7 @@ const BlockDetails = () => {
<DetailsInfoItem <DetailsInfoItem
title="Block reward" title="Block reward"
hint={ hint={
`For each block, the miner is rewarded with a finite amount of ${ network?.currency || 'native token' } `For each block, the miner is rewarded with a finite amount of ${ appConfig.network.currency || 'native token' }
on top of the fees paid for all transactions in the block.` on top of the fees paid for all transactions in the block.`
} }
columnGap={ 1 } columnGap={ 1 }
...@@ -120,15 +119,16 @@ const BlockDetails = () => { ...@@ -120,15 +119,16 @@ const BlockDetails = () => {
title="Base fee per gas" title="Base fee per gas"
hint="Minimum fee required per unit of gas. Fee adjusts based on network congestion." hint="Minimum fee required per unit of gas. Fee adjusts based on network congestion."
> >
<Text>{ (block.base_fee_per_gas / 10 ** 9).toLocaleString('en', { minimumFractionDigits: 18 }) } { network?.currency } </Text> <Text>{ (block.base_fee_per_gas / 10 ** 9).toLocaleString('en', { minimumFractionDigits: 18 }) } { appConfig.network.currency } </Text>
<Text variant="secondary" whiteSpace="pre">{ space }({ block.base_fee_per_gas.toLocaleString('en', { minimumFractionDigits: 9 }) } Gwei)</Text> <Text variant="secondary" whiteSpace="pre">{ space }({ block.base_fee_per_gas.toLocaleString('en', { minimumFractionDigits: 9 }) } Gwei)</Text>
</DetailsInfoItem> </DetailsInfoItem>
<DetailsInfoItem <DetailsInfoItem
title="Burnt fees" title="Burnt fees"
hint={ `Amount of ${ network?.currency || 'native token' } burned from transactions included in the block. Equals Block Base Fee per Gas * Gas Used.` } hint={ `Amount of ${ appConfig.network.currency || 'native token' } burned from transactions included in the block.
Equals Block Base Fee per Gas * Gas Used.` }
> >
<Icon as={ flameIcon } boxSize={ 5 } color="gray.500"/> <Icon as={ flameIcon } boxSize={ 5 } color="gray.500"/>
<Text ml={ 1 }>{ block.burnt_fees.toLocaleString('en', { minimumFractionDigits: 18 }) } { network?.currency }</Text> <Text ml={ 1 }>{ block.burnt_fees.toLocaleString('en', { minimumFractionDigits: 18 }) } { appConfig.network.currency }</Text>
<Tooltip label="Burnt fees / Txn fees * 100%"> <Tooltip label="Burnt fees / Txn fees * 100%">
<Box> <Box>
<Utilization ml={ 4 } value={ block.burnt_fees / block.reward.tx_fee }/> <Utilization ml={ 4 } value={ block.burnt_fees / block.reward.tx_fee }/>
......
import { Flex, Link, Spinner, Text, Box, Icon, useColorModeValue } from '@chakra-ui/react'; import { Flex, Link, Spinner, Text, Box, Icon, useColorModeValue } from '@chakra-ui/react';
import appConfig from 'configs/app/config';
import React from 'react'; import React from 'react';
import type ArrayElement from 'types/utils/ArrayElement'; import type ArrayElement from 'types/utils/ArrayElement';
...@@ -6,7 +7,6 @@ import type ArrayElement from 'types/utils/ArrayElement'; ...@@ -6,7 +7,6 @@ import type ArrayElement from 'types/utils/ArrayElement';
import type { blocks } from 'data/blocks'; import type { blocks } from 'data/blocks';
import flameIcon from 'icons/flame.svg'; import flameIcon from 'icons/flame.svg';
import dayjs from 'lib/date/dayjs'; import dayjs from 'lib/date/dayjs';
import useNetwork from 'lib/hooks/useNetwork';
import useLink from 'lib/link/useLink'; import useLink from 'lib/link/useLink';
import AccountListItemMobile from 'ui/shared/AccountListItemMobile'; import AccountListItemMobile from 'ui/shared/AccountListItemMobile';
import AddressLink from 'ui/shared/address/AddressLink'; import AddressLink from 'ui/shared/address/AddressLink';
...@@ -21,7 +21,6 @@ interface Props { ...@@ -21,7 +21,6 @@ interface Props {
const BlocksListItem = ({ data, isPending }: Props) => { const BlocksListItem = ({ data, isPending }: Props) => {
const spinnerEmptyColor = useColorModeValue('blackAlpha.200', 'whiteAlpha.200'); const spinnerEmptyColor = useColorModeValue('blackAlpha.200', 'whiteAlpha.200');
const link = useLink(); const link = useLink();
const network = useNetwork();
return ( return (
<AccountListItemMobile rowGap={ 3 }> <AccountListItemMobile rowGap={ 3 }>
...@@ -58,7 +57,7 @@ const BlocksListItem = ({ data, isPending }: Props) => { ...@@ -58,7 +57,7 @@ const BlocksListItem = ({ data, isPending }: Props) => {
</Flex> </Flex>
</Box> </Box>
<Flex columnGap={ 2 }> <Flex columnGap={ 2 }>
<Text fontWeight={ 500 }>Reward { network?.currency }</Text> <Text fontWeight={ 500 }>Reward { appConfig.network.currency }</Text>
<Text variant="secondary">{ (data.reward.static + data.reward.tx_fee - data.burnt_fees).toLocaleString('en', { maximumFractionDigits: 5 }) }</Text> <Text variant="secondary">{ (data.reward.static + data.reward.tx_fee - data.burnt_fees).toLocaleString('en', { maximumFractionDigits: 5 }) }</Text>
</Flex> </Flex>
<Flex> <Flex>
......
import { Table, Thead, Tbody, Tr, Th, TableContainer } from '@chakra-ui/react'; import { Table, Thead, Tbody, Tr, Th, TableContainer } from '@chakra-ui/react';
import appConfig from 'configs/app/config';
import React from 'react'; import React from 'react';
import { blocks } from 'data/blocks'; import { blocks } from 'data/blocks';
import useNetwork from 'lib/hooks/useNetwork';
import BlocksTableItem from 'ui/blocks/BlocksTableItem'; import BlocksTableItem from 'ui/blocks/BlocksTableItem';
const BlocksTable = () => { const BlocksTable = () => {
const network = useNetwork();
return ( return (
<TableContainer width="100%" mt={ 8 }> <TableContainer width="100%" mt={ 8 }>
...@@ -18,8 +17,8 @@ const BlocksTable = () => { ...@@ -18,8 +17,8 @@ const BlocksTable = () => {
<Th width="144px">Miner</Th> <Th width="144px">Miner</Th>
<Th width="64px" isNumeric>Txn</Th> <Th width="64px" isNumeric>Txn</Th>
<Th width="40%">Gas used</Th> <Th width="40%">Gas used</Th>
<Th width="30%">Reward { network?.currency }</Th> <Th width="30%">Reward { appConfig.network.currency }</Th>
<Th width="30%">Burnt fees { network?.currency }</Th> <Th width="30%">Burnt fees { appConfig.network.currency }</Th>
</Tr> </Tr>
</Thead> </Thead>
<Tbody> <Tbody>
......
import { Box, Icon, Link } from '@chakra-ui/react'; import { Box, Icon, Link } from '@chakra-ui/react';
import config from 'configs/app/config';
import React from 'react'; import React from 'react';
import PlusIcon from 'icons/plus.svg'; import PlusIcon from 'icons/plus.svg';
...@@ -48,13 +49,13 @@ const Apps = () => { ...@@ -48,13 +49,13 @@ const Apps = () => {
/> />
) } ) }
{ process.env.NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM && ( { config.marketplaceSubmitForm && (
<Link <Link
fontWeight="bold" fontWeight="bold"
display="inline-flex" display="inline-flex"
alignItems="baseline" alignItems="baseline"
marginTop={{ base: 8, sm: 16 }} marginTop={{ base: 8, sm: 16 }}
href={ process.env.NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM } href={ config.marketplaceSubmitForm }
isExternal isExternal
> >
<Icon <Icon
......
import { VStack, Textarea, Button, Alert, AlertTitle, AlertDescription, Link, Code } from '@chakra-ui/react'; import { VStack, Textarea, Button, Alert, AlertTitle, AlertDescription, Link, Code } from '@chakra-ui/react';
import * as Sentry from '@sentry/react';
import appConfig from 'configs/app/config';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import type { ChangeEvent } from 'react'; import type { ChangeEvent } from 'react';
import React from 'react'; import React from 'react';
import * as cookies from 'lib/cookies'; import * as cookies from 'lib/cookies';
import useNetwork from 'lib/hooks/useNetwork';
import useToast from 'lib/hooks/useToast'; import useToast from 'lib/hooks/useToast';
import Page from 'ui/shared/Page/Page'; import Page from 'ui/shared/Page/Page';
import PageTitle from 'ui/shared/Page/PageTitle'; import PageTitle from 'ui/shared/Page/PageTitle';
const Home = () => { const Home = () => {
const router = useRouter(); const router = useRouter();
const selectedNetwork = useNetwork();
const toast = useToast(); const toast = useToast();
const [ isFormVisible, setFormVisibility ] = React.useState(false); const [ isFormVisible, setFormVisibility ] = React.useState(false);
...@@ -19,8 +19,12 @@ const Home = () => { ...@@ -19,8 +19,12 @@ const Home = () => {
React.useEffect(() => { React.useEffect(() => {
const token = cookies.get(cookies.NAMES.API_TOKEN); const token = cookies.get(cookies.NAMES.API_TOKEN);
setFormVisibility(Boolean(!token && selectedNetwork?.isAccountSupported)); setFormVisibility(Boolean(!token && appConfig.isAccountSupported));
}, [ selectedNetwork?.isAccountSupported ]); }, []);
const checkSentry = React.useCallback(() => {
Sentry.captureException(new Error('Test error'), { extra: { foo: 'bar' }, tags: { source: 'test' } });
}, []);
const handleTokenChange = React.useCallback((event: ChangeEvent<HTMLTextAreaElement>) => { const handleTokenChange = React.useCallback((event: ChangeEvent<HTMLTextAreaElement>) => {
setToken(event.target.value); setToken(event.target.value);
...@@ -48,8 +52,9 @@ const Home = () => { ...@@ -48,8 +52,9 @@ const Home = () => {
<Page> <Page>
<VStack gap={ 4 } alignItems="flex-start" maxW="800px"> <VStack gap={ 4 } alignItems="flex-start" maxW="800px">
<PageTitle text={ <PageTitle text={
`Home Page for ${ selectedNetwork?.name } network` `Home Page for ${ appConfig.network.name } network`
}/> }/>
<Button colorScheme="red" onClick={ checkSentry }>Check Sentry</Button>
{ /* will be deleted when we move to new CI */ } { /* will be deleted when we move to new CI */ }
{ isFormVisible && ( { isFormVisible && (
<> <>
......
import { Box, Center, useColorMode } from '@chakra-ui/react'; import { Box, Center, useColorMode } from '@chakra-ui/react';
import appConfig from 'configs/app/config';
import React, { useCallback, useEffect, useRef, useState } from 'react'; import React, { useCallback, useEffect, useRef, useState } from 'react';
import type { AppItemOverview } from 'types/client/apps'; import type { AppItemOverview } from 'types/client/apps';
import useNetwork from 'lib/hooks/useNetwork';
import ContentLoader from 'ui/shared/ContentLoader'; import ContentLoader from 'ui/shared/ContentLoader';
import Page from 'ui/shared/Page/Page'; import Page from 'ui/shared/Page/Page';
...@@ -16,7 +16,6 @@ const MarketplaceApp = ({ app, isLoading }: Props) => { ...@@ -16,7 +16,6 @@ const MarketplaceApp = ({ app, isLoading }: Props) => {
const [ isFrameLoading, setIsFrameLoading ] = useState(isLoading); const [ isFrameLoading, setIsFrameLoading ] = useState(isLoading);
const ref = useRef<HTMLIFrameElement>(null); const ref = useRef<HTMLIFrameElement>(null);
const { colorMode } = useColorMode(); const { colorMode } = useColorMode();
const network = useNetwork();
const handleIframeLoad = useCallback(() => { const handleIframeLoad = useCallback(() => {
setIsFrameLoading(false); setIsFrameLoading(false);
...@@ -31,9 +30,9 @@ const MarketplaceApp = ({ app, isLoading }: Props) => { ...@@ -31,9 +30,9 @@ const MarketplaceApp = ({ app, isLoading }: Props) => {
useEffect(() => { useEffect(() => {
if (app && !isFrameLoading) { if (app && !isFrameLoading) {
ref?.current?.contentWindow?.postMessage({ blockscoutColorMode: colorMode, blockscoutChainId: network?.chainId }, app.url); ref?.current?.contentWindow?.postMessage({ blockscoutColorMode: colorMode, blockscoutChainId: appConfig.network.id }, app.url);
} }
}, [ isFrameLoading, app, colorMode, network, ref ]); }, [ isFrameLoading, app, colorMode, ref ]);
return ( return (
<Page wrapChildren={ false }> <Page wrapChildren={ false }>
......
import { Image, chakra } from '@chakra-ui/react'; import { Image, chakra } from '@chakra-ui/react';
import appConfig from 'configs/app/config';
import React from 'react'; import React from 'react';
import type { Network } from 'types/networks';
import useNetwork from 'lib/hooks/useNetwork';
const EmptyElement = () => null; const EmptyElement = () => null;
const ASSETS_PATH_MAP: Record<string, string> = {
'xdai/mainnet': 'xdai',
'xdai/testnet': 'xdai',
'xdai/optimism': 'optimism',
'xdai/aox': 'arbitrum',
'eth/mainnet': 'ethereum',
'etc/mainnet': 'classic',
'poa/core': 'poa',
};
const getAssetsPath = (network: Network) => {
if (network.assetsNamePath) {
return network.assetsNamePath;
}
const key = [ network.type, network.subType ].filter(Boolean).join('/');
const nameFromMap = ASSETS_PATH_MAP[key];
return nameFromMap || network.type;
};
interface Props { interface Props {
hash: string; hash: string;
name: string; name?: string;
className?: string; className?: string;
} }
const TokenLogo = ({ hash, name, className }: Props) => { const TokenLogo = ({ hash, name, className }: Props) => {
const network = useNetwork(); const logoSrc = `
https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/
if (!network) { ${ appConfig.network.assetsPathname || appConfig.network.type }
return null; /assets/
} ${ hash }
/logo.png
const assetsPath = getAssetsPath(network); `;
const logoSrc = `https://raw.githubusercontent.com/trustwallet/assets/master/blockchains/${ assetsPath }/assets/${ hash }/logo.png`;
return <Image className={ className } src={ logoSrc } alt={ `${ name || 'token' } logo` } fallback={ <EmptyElement/> }/>;
return <Image className={ className } src={ logoSrc } alt={ `${ name } logo` } fallback={ <EmptyElement/> }/>;
}; };
export default React.memo(chakra(TokenLogo)); export default React.memo(chakra(TokenLogo));
import { Box, VStack, Text, Stack, Icon, Link, useColorModeValue } from '@chakra-ui/react'; import { Box, VStack, Text, Stack, Icon, Link, useColorModeValue } from '@chakra-ui/react';
import appConfig from 'configs/app/config';
import React from 'react'; import React from 'react';
import ghIcon from 'icons/social/git.svg'; import ghIcon from 'icons/social/git.svg';
...@@ -8,14 +9,13 @@ import twIcon from 'icons/social/tweet.svg'; ...@@ -8,14 +9,13 @@ import twIcon from 'icons/social/tweet.svg';
import getDefaultTransitionProps from 'theme/utils/getDefaultTransitionProps'; import getDefaultTransitionProps from 'theme/utils/getDefaultTransitionProps';
const SOCIAL_LINKS = [ const SOCIAL_LINKS = [
{ link: process.env.NEXT_PUBLIC_FOOTER_GITHUB_LINK, icon: ghIcon, label: 'Github link' }, { link: appConfig.footerLinks.github, icon: ghIcon, label: 'Github link' },
{ link: process.env.NEXT_PUBLIC_FOOTER_TWITTER_LINK, icon: twIcon, label: 'Twitter link' }, { link: appConfig.footerLinks.twitter, icon: twIcon, label: 'Twitter link' },
{ link: process.env.NEXT_PUBLIC_FOOTER_TELEGRAM_LINK, icon: tgIcon, label: 'Telegram link' }, { link: appConfig.footerLinks.telegram, icon: tgIcon, label: 'Telegram link' },
{ link: process.env.NEXT_PUBLIC_FOOTER_STAKING_LINK, icon: statsIcon, label: 'Staking analytic link' }, { link: appConfig.footerLinks.staking, icon: statsIcon, label: 'Staking analytic link' },
].filter(({ link }) => link !== undefined); ].filter(({ link }) => link !== undefined);
const BLOCKSCOUT_VERSION = process.env.NEXT_PUBLIC_BLOCKSCOUT_VERSION; const VERSION_URL = `https://github.com/blockscout/blockscout/tree/${ appConfig.blockScoutVersion }`;
const VERSION_URL = `https://github.com/blockscout/blockscout/tree/${ BLOCKSCOUT_VERSION }`;
interface Props { interface Props {
isCollapsed?: boolean; isCollapsed?: boolean;
...@@ -60,7 +60,7 @@ const NavFooter = ({ isCollapsed, hasAccount }: Props) => { ...@@ -60,7 +60,7 @@ const NavFooter = ({ isCollapsed, hasAccount }: Props) => {
<Text variant="secondary" mb={ 8 }> <Text variant="secondary" mb={ 8 }>
Blockscout is a tool for inspecting and analyzing EVM based blockchains. Blockchain explorer for Ethereum Networks. Blockscout is a tool for inspecting and analyzing EVM based blockchains. Blockchain explorer for Ethereum Networks.
</Text> </Text>
<Text variant="secondary">Version: <Link href={ VERSION_URL } target="_blank">{ BLOCKSCOUT_VERSION }</Link></Text> <Text variant="secondary">Version: <Link href={ VERSION_URL } target="_blank">{ appConfig.blockScoutVersion }</Link></Text>
</Box> </Box>
</VStack> </VStack>
); );
......
import { Flex, Box, VStack, Icon, useColorModeValue } from '@chakra-ui/react'; import { Flex, Box, VStack, Icon, useColorModeValue } from '@chakra-ui/react';
import appConfig from 'configs/app/config';
import React from 'react'; import React from 'react';
import chevronIcon from 'icons/arrows/east-mini.svg'; import chevronIcon from 'icons/arrows/east-mini.svg';
import * as cookies from 'lib/cookies'; import * as cookies from 'lib/cookies';
import useNavItems from 'lib/hooks/useNavItems'; import useNavItems from 'lib/hooks/useNavItems';
import useNetwork from 'lib/hooks/useNetwork';
import isBrowser from 'lib/isBrowser'; import isBrowser from 'lib/isBrowser';
import getDefaultTransitionProps from 'theme/utils/getDefaultTransitionProps'; import getDefaultTransitionProps from 'theme/utils/getDefaultTransitionProps';
import NetworkLogo from 'ui/snippets/networkMenu/NetworkLogo'; import NetworkLogo from 'ui/snippets/networkMenu/NetworkLogo';
...@@ -15,7 +15,6 @@ import NavLink from './NavLink'; ...@@ -15,7 +15,6 @@ import NavLink from './NavLink';
const NavigationDesktop = () => { const NavigationDesktop = () => {
const { mainNavItems, accountNavItems } = useNavItems(); const { mainNavItems, accountNavItems } = useNavItems();
const selectedNetwork = useNetwork();
const isInBrowser = isBrowser(); const isInBrowser = isBrowser();
const [ hasAccount, setHasAccount ] = React.useState(false); const [ hasAccount, setHasAccount ] = React.useState(false);
...@@ -31,9 +30,9 @@ const NavigationDesktop = () => { ...@@ -31,9 +30,9 @@ const NavigationDesktop = () => {
if (navBarCollapsedCookie === 'false') { if (navBarCollapsedCookie === 'false') {
setCollapsedState(false); setCollapsedState(false);
} }
setHasAccount(Boolean(selectedNetwork?.isAccountSupported && isAuth && isInBrowser)); setHasAccount(Boolean(appConfig.isAccountSupported && isAuth && isInBrowser));
} }
}, [ isInBrowser, selectedNetwork?.isAccountSupported ]); }, [ isInBrowser ]);
const handleTogglerClick = React.useCallback(() => { const handleTogglerClick = React.useCallback(() => {
setCollapsedState((flag) => !flag); setCollapsedState((flag) => !flag);
......
import { Box, VStack } from '@chakra-ui/react'; import { Box, VStack } from '@chakra-ui/react';
import appConfig from 'configs/app/config';
import React from 'react'; import React from 'react';
import * as cookies from 'lib/cookies'; import * as cookies from 'lib/cookies';
import useNavItems from 'lib/hooks/useNavItems'; import useNavItems from 'lib/hooks/useNavItems';
import useNetwork from 'lib/hooks/useNetwork';
import NavFooter from 'ui/snippets/navigation/NavFooter'; import NavFooter from 'ui/snippets/navigation/NavFooter';
import NavLink from 'ui/snippets/navigation/NavLink'; import NavLink from 'ui/snippets/navigation/NavLink';
const NavigationMobile = () => { const NavigationMobile = () => {
const { mainNavItems, accountNavItems } = useNavItems(); const { mainNavItems, accountNavItems } = useNavItems();
const selectedNetwork = useNetwork();
const isAuth = Boolean(cookies.get(cookies.NAMES.API_TOKEN)); const isAuth = Boolean(cookies.get(cookies.NAMES.API_TOKEN));
const hasAccount = selectedNetwork?.isAccountSupported && isAuth; const hasAccount = appConfig.isAccountSupported && isAuth;
return ( return (
<> <>
......
import { Icon, Box, Image, useColorModeValue } from '@chakra-ui/react'; import { Icon, Box, Image, useColorModeValue } from '@chakra-ui/react';
import appConfig from 'configs/app/config';
import NextLink from 'next/link'; import NextLink from 'next/link';
import React from 'react'; import React from 'react';
import type { FunctionComponent, SVGAttributes } from 'react'; import type { FunctionComponent, SVGAttributes } from 'react';
import blockscoutLogo from 'icons/logo.svg'; import blockscoutLogo from 'icons/logo.svg';
import useNetwork from 'lib/hooks/useNetwork';
import useLink from 'lib/link/useLink'; import useLink from 'lib/link/useLink';
import getDefaultTransitionProps from 'theme/utils/getDefaultTransitionProps'; import getDefaultTransitionProps from 'theme/utils/getDefaultTransitionProps';
// predefined network logos
const LOGOS: Record<string, React.FunctionComponent<React.SVGAttributes<SVGElement>>> = {
'/xdai/mainnet': require('icons/networks/logos/gnosis.svg'),
'/eth/mainnet': require('icons/networks/logos/eth.svg'),
'/etc/mainnet': require('icons/networks/logos/etc.svg'),
'/poa/core': require('icons/networks/logos/poa.svg'),
'/rsk/mainnet': require('icons/networks/logos/rsk.svg'),
'/xdai/testnet': require('icons/networks/logos/gnosis.svg'),
'/poa/sokol': require('icons/networks/logos/sokol.svg'),
'/artis/sigma1': require('icons/networks/logos/artis.svg'),
'/lukso/l14': require('icons/networks/logos/lukso.svg'),
'/astar': require('icons/networks/logos/astar.svg'),
'/shiden': require('icons/networks/logos/shiden.svg'),
'/shibuya': require('icons/networks/logos/shibuya.svg'),
};
interface Props { interface Props {
isCollapsed?: boolean; isCollapsed?: boolean;
onClick?: (event: React.SyntheticEvent) => void; onClick?: (event: React.SyntheticEvent) => void;
...@@ -17,8 +33,7 @@ const NetworkLogo = ({ isCollapsed, onClick }: Props) => { ...@@ -17,8 +33,7 @@ const NetworkLogo = ({ isCollapsed, onClick }: Props) => {
const logoColor = useColorModeValue('blue.600', 'white'); const logoColor = useColorModeValue('blue.600', 'white');
const link = useLink(); const link = useLink();
const href = link('network_index'); const href = link('network_index');
const network = useNetwork(); const logo = appConfig.network.logo || LOGOS[appConfig.network.basePath];
const logo = network?.logo;
const style = useColorModeValue({}, { filter: 'brightness(0) invert(1)' }); const style = useColorModeValue({}, { filter: 'brightness(0) invert(1)' });
...@@ -29,7 +44,7 @@ const NetworkLogo = ({ isCollapsed, onClick }: Props) => { ...@@ -29,7 +44,7 @@ const NetworkLogo = ({ isCollapsed, onClick }: Props) => {
<Image <Image
h="20px" h="20px"
src={ logo } src={ logo }
alt={ `${ network.type } ${ network.subType ? network.subType : '' } network icon` } alt={ `${ appConfig.network.name } network icon` }
/> />
); );
} else if (typeof logo !== undefined) { } else if (typeof logo !== undefined) {
......
...@@ -3,18 +3,17 @@ import React from 'react'; ...@@ -3,18 +3,17 @@ import React from 'react';
import type { NetworkGroup } from 'types/networks'; import type { NetworkGroup } from 'types/networks';
import useNetwork from 'lib/hooks/useNetwork'; import featuredNetworks from 'lib/networks/featuredNetworks';
import NETWORKS from 'lib/networks/availableNetworks';
import useNetworkNavigationItems from 'lib/networks/useNetworkNavigationItems'; import useNetworkNavigationItems from 'lib/networks/useNetworkNavigationItems';
import NetworkMenuLink from './NetworkMenuLink'; import NetworkMenuLink from './NetworkMenuLink';
const TABS: Array<NetworkGroup> = [ 'mainnets', 'testnets', 'other' ]; const TABS: Array<NetworkGroup> = [ 'mainnets', 'testnets', 'other' ];
const availableTabs = TABS.filter((tab) => NETWORKS.some(({ group }) => group === tab)); const availableTabs = TABS.filter((tab) => featuredNetworks.some(({ group }) => group === tab));
const NetworkMenuPopup = () => { const NetworkMenuPopup = () => {
const selectedNetwork = useNetwork();
const items = useNetworkNavigationItems(); const items = useNetworkNavigationItems();
const selectedNetwork = items.find(({ isActive }) => isActive);
const selectedTab = availableTabs.findIndex((tab) => selectedNetwork?.group === tab); const selectedTab = availableTabs.findIndex((tab) => selectedNetwork?.group === tab);
return ( return (
...@@ -35,7 +34,7 @@ const NetworkMenuPopup = () => { ...@@ -35,7 +34,7 @@ const NetworkMenuPopup = () => {
.filter((network) => network.group === tab) .filter((network) => network.group === tab)
.map((network) => ( .map((network) => (
<NetworkMenuLink <NetworkMenuLink
key={ network.name } key={ network.title }
{ ...network } { ...network }
/> />
)) } )) }
......
...@@ -4,7 +4,6 @@ import React from 'react'; ...@@ -4,7 +4,6 @@ import React from 'react';
import type { NetworkGroup } from 'types/networks'; import type { NetworkGroup } from 'types/networks';
import useNetwork from 'lib/hooks/useNetwork';
import useNetworkNavigationItems from 'lib/networks/useNetworkNavigationItems'; import useNetworkNavigationItems from 'lib/networks/useNetworkNavigationItems';
import NetworkMenuLink from './NetworkMenuLink'; import NetworkMenuLink from './NetworkMenuLink';
...@@ -12,9 +11,9 @@ import NetworkMenuLink from './NetworkMenuLink'; ...@@ -12,9 +11,9 @@ import NetworkMenuLink from './NetworkMenuLink';
const TABS: Array<NetworkGroup> = [ 'mainnets', 'testnets', 'other' ]; const TABS: Array<NetworkGroup> = [ 'mainnets', 'testnets', 'other' ];
const NetworkMenuContentMobile = () => { const NetworkMenuContentMobile = () => {
const selectedNetwork = useNetwork();
const [ selectedTab, setSelectedTab ] = React.useState<NetworkGroup>(TABS.find((tab) => selectedNetwork?.group === tab) || 'mainnets');
const items = useNetworkNavigationItems(); const items = useNetworkNavigationItems();
const selectedNetwork = items.find(({ isActive }) => isActive);
const [ selectedTab, setSelectedTab ] = React.useState<NetworkGroup>(TABS.find((tab) => selectedNetwork?.group === tab) || 'mainnets');
const handleSelectChange = React.useCallback((event: React.ChangeEvent<HTMLSelectElement>) => { const handleSelectChange = React.useCallback((event: React.ChangeEvent<HTMLSelectElement>) => {
setSelectedTab(event.target.value as NetworkGroup); setSelectedTab(event.target.value as NetworkGroup);
...@@ -30,7 +29,7 @@ const NetworkMenuContentMobile = () => { ...@@ -30,7 +29,7 @@ const NetworkMenuContentMobile = () => {
.filter(({ group }) => group === selectedTab) .filter(({ group }) => group === selectedTab)
.map((network) => ( .map((network) => (
<NetworkMenuLink <NetworkMenuLink
key={ network.name } key={ network.title }
{ ...network } { ...network }
isMobile isMobile
/> />
......
...@@ -2,25 +2,25 @@ import { Box, Flex, Icon, Text, Image } from '@chakra-ui/react'; ...@@ -2,25 +2,25 @@ import { Box, Flex, Icon, Text, Image } from '@chakra-ui/react';
import NextLink from 'next/link'; import NextLink from 'next/link';
import React from 'react'; import React from 'react';
import type { Network } from 'types/networks'; import type { FeaturedNetwork } from 'types/networks';
import checkIcon from 'icons/check.svg'; import checkIcon from 'icons/check.svg';
import placeholderIcon from 'icons/networks/icons/placeholder.svg'; import placeholderIcon from 'icons/networks/icons/placeholder.svg';
import useColors from './useColors'; import useColors from './useColors';
interface Props extends Network { interface Props extends FeaturedNetwork {
isActive: boolean; isActive: boolean;
isMobile?: boolean; isMobile?: boolean;
url: string; url: string;
} }
const NetworkMenuLink = ({ name, type, subType, icon, isActive, isMobile, url }: Props) => { const NetworkMenuLink = ({ title, icon, isActive, isMobile, url }: Props) => {
const hasIcon = Boolean(icon); const hasIcon = Boolean(icon);
const colors = useColors({ hasIcon }); const colors = useColors({ hasIcon });
const iconEl = typeof icon === 'string' ? ( const iconEl = typeof icon === 'string' ? (
<Image w="30px" h="30px" src={ icon } alt={ `${ type } ${ subType ? subType : '' } network icon` }/> <Image w="30px" h="30px" src={ icon } alt={ `${ title } network icon` }/>
) : ( ) : (
<Icon <Icon
as={ hasIcon ? icon : placeholderIcon } as={ hasIcon ? icon : placeholderIcon }
...@@ -52,7 +52,7 @@ const NetworkMenuLink = ({ name, type, subType, icon, isActive, isMobile, url }: ...@@ -52,7 +52,7 @@ const NetworkMenuLink = ({ name, type, subType, icon, isActive, isMobile, url }:
fontSize={ isMobile ? 'sm' : 'md' } fontSize={ isMobile ? 'sm' : 'md' }
lineHeight={ isMobile ? '20px' : '24px' } lineHeight={ isMobile ? '20px' : '24px' }
> >
{ name } { title }
</Text> </Text>
{ isActive && ( { isActive && (
<Icon <Icon
......
import { Grid, GridItem, Text, Box, Icon, Link, Flex } from '@chakra-ui/react'; import { Grid, GridItem, Text, Box, Icon, Link, Flex } from '@chakra-ui/react';
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import BigNumber from 'bignumber.js'; import BigNumber from 'bignumber.js';
import appConfig from 'configs/app/config';
import { useRouter } from 'next/router'; import { useRouter } from 'next/router';
import React from 'react'; import React from 'react';
import { scroller, Element } from 'react-scroll'; import { scroller, Element } from 'react-scroll';
...@@ -14,7 +15,6 @@ import { WEI, WEI_IN_GWEI } from 'lib/consts'; ...@@ -14,7 +15,6 @@ import { WEI, WEI_IN_GWEI } from 'lib/consts';
// import successIcon from 'icons/status/success.svg'; // import successIcon from 'icons/status/success.svg';
import dayjs from 'lib/date/dayjs'; import dayjs from 'lib/date/dayjs';
import useFetch from 'lib/hooks/useFetch'; import useFetch from 'lib/hooks/useFetch';
import useNetwork from 'lib/hooks/useNetwork';
import getConfirmationDuration from 'lib/tx/getConfirmationDuration'; import getConfirmationDuration from 'lib/tx/getConfirmationDuration';
import Address from 'ui/shared/address/Address'; import Address from 'ui/shared/address/Address';
import AddressIcon from 'ui/shared/address/AddressIcon'; import AddressIcon from 'ui/shared/address/AddressIcon';
...@@ -35,7 +35,6 @@ import TokenTransfer from 'ui/tx/TokenTransfer'; ...@@ -35,7 +35,6 @@ import TokenTransfer from 'ui/tx/TokenTransfer';
import TxDecodedInputData from 'ui/tx/TxDecodedInputData'; import TxDecodedInputData from 'ui/tx/TxDecodedInputData';
const TxDetails = () => { const TxDetails = () => {
const selectedNetwork = useNetwork();
const router = useRouter(); const router = useRouter();
const fetch = useFetch(); const fetch = useFetch();
...@@ -154,19 +153,19 @@ const TxDetails = () => { ...@@ -154,19 +153,19 @@ const TxDetails = () => {
title="Value" title="Value"
hint="Value sent in the native token (and USD) if applicable." hint="Value sent in the native token (and USD) if applicable."
> >
<CurrencyValue value={ String(data.value) } currency={ selectedNetwork?.currency } exchangeRate={ data.exchange_rate }/> <CurrencyValue value={ String(data.value) } currency={ appConfig.network.currency } exchangeRate={ data.exchange_rate }/>
</DetailsInfoItem> </DetailsInfoItem>
<DetailsInfoItem <DetailsInfoItem
title="Transaction fee" title="Transaction fee"
hint="Total transaction fee." hint="Total transaction fee."
> >
<CurrencyValue value={ String(data.fee.value) } currency={ selectedNetwork?.currency } exchangeRate={ data.exchange_rate }/> <CurrencyValue value={ String(data.fee.value) } currency={ appConfig.network.currency } exchangeRate={ data.exchange_rate }/>
</DetailsInfoItem> </DetailsInfoItem>
<DetailsInfoItem <DetailsInfoItem
title="Gas price" title="Gas price"
hint="Price per unit of gas specified by the sender. Higher gas prices can prioritize transaction inclusion during times of high usage." hint="Price per unit of gas specified by the sender. Higher gas prices can prioritize transaction inclusion during times of high usage."
> >
<Text mr={ 1 }>{ BigNumber(data.gas_price).dividedBy(WEI).toFixed() } { selectedNetwork?.currency }</Text> <Text mr={ 1 }>{ BigNumber(data.gas_price).dividedBy(WEI).toFixed() } { appConfig.network.currency }</Text>
<Text variant="secondary">({ BigNumber(data.gas_price).dividedBy(WEI_IN_GWEI).toFixed() } Gwei)</Text> <Text variant="secondary">({ BigNumber(data.gas_price).dividedBy(WEI_IN_GWEI).toFixed() } Gwei)</Text>
</DetailsInfoItem> </DetailsInfoItem>
<DetailsInfoItem <DetailsInfoItem
...@@ -209,10 +208,10 @@ const TxDetails = () => { ...@@ -209,10 +208,10 @@ const TxDetails = () => {
{ data.tx_burnt_fee && ( { data.tx_burnt_fee && (
<DetailsInfoItem <DetailsInfoItem
title="Burnt fees" title="Burnt fees"
hint={ `Amount of ${ selectedNetwork?.currency } burned for this transaction. Equals Block Base Fee per Gas * Gas Used.` } hint={ `Amount of ${ appConfig.network.currency } burned for this transaction. Equals Block Base Fee per Gas * Gas Used.` }
> >
<Icon as={ flameIcon } mr={ 1 } boxSize={ 5 } color="gray.500"/> <Icon as={ flameIcon } mr={ 1 } boxSize={ 5 } color="gray.500"/>
<CurrencyValue value={ String(data.tx_burnt_fee) } currency={ selectedNetwork?.currency } exchangeRate={ data.exchange_rate }/> <CurrencyValue value={ String(data.tx_burnt_fee) } currency={ appConfig.network.currency } exchangeRate={ data.exchange_rate }/>
</DetailsInfoItem> </DetailsInfoItem>
) } ) }
<GridItem colSpan={{ base: undefined, lg: 2 }}> <GridItem colSpan={{ base: undefined, lg: 2 }}>
......
...@@ -3,15 +3,12 @@ import React from 'react'; ...@@ -3,15 +3,12 @@ import React from 'react';
import type { InternalTransaction } from 'types/api/internalTransaction'; import type { InternalTransaction } from 'types/api/internalTransaction';
import useNetwork from 'lib/hooks/useNetwork';
import TxInternalsListItem from 'ui/tx/internals/TxInternalsListItem'; import TxInternalsListItem from 'ui/tx/internals/TxInternalsListItem';
const TxInternalsList = ({ data }: { data: Array<InternalTransaction>}) => { const TxInternalsList = ({ data }: { data: Array<InternalTransaction>}) => {
const selectedNetwork = useNetwork();
return ( return (
<Box mt={ 6 }> <Box mt={ 6 }>
{ data.map((item) => <TxInternalsListItem key={ item.transaction_hash } { ...item } currency={ selectedNetwork?.currency }/>) } { data.map((item) => <TxInternalsListItem key={ item.transaction_hash } { ...item }/>) }
</Box> </Box>
); );
}; };
......
import { Flex, Tag, Icon, Box, HStack, Text } from '@chakra-ui/react'; import { Flex, Tag, Icon, Box, HStack, Text } from '@chakra-ui/react';
import appConfig from 'configs/app/config';
import React from 'react'; import React from 'react';
import type { InternalTransaction } from 'types/api/internalTransaction'; import type { InternalTransaction } from 'types/api/internalTransaction';
...@@ -11,9 +12,9 @@ import AddressLink from 'ui/shared/address/AddressLink'; ...@@ -11,9 +12,9 @@ import AddressLink from 'ui/shared/address/AddressLink';
import TxStatus from 'ui/shared/TxStatus'; import TxStatus from 'ui/shared/TxStatus';
import { TX_INTERNALS_ITEMS } from 'ui/tx/internals/utils'; import { TX_INTERNALS_ITEMS } from 'ui/tx/internals/utils';
type Props = InternalTransaction & { currency?: string }; type Props = InternalTransaction;
const TxInternalsListItem = ({ type, from, to, value, currency, success, error }: Props) => { const TxInternalsListItem = ({ type, from, to, value, success, error }: Props) => {
const typeTitle = TX_INTERNALS_ITEMS.find(({ id }) => id === type)?.title; const typeTitle = TX_INTERNALS_ITEMS.find(({ id }) => id === type)?.title;
return ( return (
...@@ -34,7 +35,7 @@ const TxInternalsListItem = ({ type, from, to, value, currency, success, error } ...@@ -34,7 +35,7 @@ const TxInternalsListItem = ({ type, from, to, value, currency, success, error }
</Address> </Address>
</Box> </Box>
<HStack spacing={ 3 }> <HStack spacing={ 3 }>
<Text fontSize="sm" fontWeight={ 500 }>Value { currency }</Text> <Text fontSize="sm" fontWeight={ 500 }>Value { appConfig.network.currency }</Text>
<Text fontSize="sm" variant="secondary">{ value }</Text> <Text fontSize="sm" variant="secondary">{ value }</Text>
</HStack> </HStack>
{ /* no gas limit in api yet */ } { /* no gas limit in api yet */ }
......
import { Table, Thead, Tbody, Tr, Th, TableContainer, Link, Icon } from '@chakra-ui/react'; import { Table, Thead, Tbody, Tr, Th, TableContainer, Link, Icon } from '@chakra-ui/react';
import appConfig from 'configs/app/config';
import React from 'react'; import React from 'react';
import type { InternalTransaction } from 'types/api/internalTransaction'; import type { InternalTransaction } from 'types/api/internalTransaction';
import arrowIcon from 'icons/arrows/east.svg'; import arrowIcon from 'icons/arrows/east.svg';
import useNetwork from 'lib/hooks/useNetwork';
import TxInternalsTableItem from 'ui/tx/internals/TxInternalsTableItem'; import TxInternalsTableItem from 'ui/tx/internals/TxInternalsTableItem';
import type { Sort, SortField } from 'ui/tx/internals/utils'; import type { Sort, SortField } from 'ui/tx/internals/utils';
...@@ -15,7 +15,6 @@ interface Props { ...@@ -15,7 +15,6 @@ interface Props {
} }
const TxInternalsTable = ({ data, sort, onSortToggle }: Props) => { const TxInternalsTable = ({ data, sort, onSortToggle }: Props) => {
const selectedNetwork = useNetwork();
const sortIconTransform = sort?.includes('asc') ? 'rotate(-90deg)' : 'rotate(90deg)'; const sortIconTransform = sort?.includes('asc') ? 'rotate(-90deg)' : 'rotate(90deg)';
return ( return (
...@@ -30,7 +29,7 @@ const TxInternalsTable = ({ data, sort, onSortToggle }: Props) => { ...@@ -30,7 +29,7 @@ const TxInternalsTable = ({ data, sort, onSortToggle }: Props) => {
<Th width="16%" isNumeric> <Th width="16%" isNumeric>
<Link display="flex" alignItems="center" justifyContent="flex-end" onClick={ onSortToggle('value') } columnGap={ 1 }> <Link display="flex" alignItems="center" justifyContent="flex-end" onClick={ onSortToggle('value') } columnGap={ 1 }>
{ sort?.includes('value') && <Icon as={ arrowIcon } boxSize={ 4 } transform={ sortIconTransform }/> } { sort?.includes('value') && <Icon as={ arrowIcon } boxSize={ 4 } transform={ sortIconTransform }/> }
Value { selectedNetwork?.currency } Value { appConfig.network.currency }
</Link> </Link>
</Th> </Th>
{ /* no gas limit in api yet */ } { /* no gas limit in api yet */ }
......
import { AccordionItem, AccordionButton, AccordionIcon, Button, Box, Flex, Text, Link, StatArrow, Stat, AccordionPanel } from '@chakra-ui/react'; import { AccordionItem, AccordionButton, AccordionIcon, Button, Box, Flex, Text, Link, StatArrow, Stat, AccordionPanel } from '@chakra-ui/react';
import appConfig from 'configs/app/config';
import React from 'react'; import React from 'react';
import type ArrayElement from 'types/utils/ArrayElement'; import type ArrayElement from 'types/utils/ArrayElement';
import type { data } from 'data/txState'; import type { data } from 'data/txState';
import useNetwork from 'lib/hooks/useNetwork';
import { nbsp } from 'lib/html-entities'; import { nbsp } from 'lib/html-entities';
import AccountListItemMobile from 'ui/shared/AccountListItemMobile'; import AccountListItemMobile from 'ui/shared/AccountListItemMobile';
import Address from 'ui/shared/address/Address'; import Address from 'ui/shared/address/Address';
...@@ -16,7 +16,6 @@ import TxStateStorageItem from './TxStateStorageItem'; ...@@ -16,7 +16,6 @@ import TxStateStorageItem from './TxStateStorageItem';
type Props = ArrayElement<typeof data>; type Props = ArrayElement<typeof data>;
const TxStateListItem = ({ storage, address, miner, after, before, diff }: Props) => { const TxStateListItem = ({ storage, address, miner, after, before, diff }: Props) => {
const selectedNetwork = useNetwork();
const hasStorageData = Boolean(storage?.length); const hasStorageData = Boolean(storage?.length);
...@@ -65,7 +64,7 @@ const TxStateListItem = ({ storage, address, miner, after, before, diff }: Props ...@@ -65,7 +64,7 @@ const TxStateListItem = ({ storage, address, miner, after, before, diff }: Props
<Link>{ miner }</Link> <Link>{ miner }</Link>
</Box> </Box>
<Box> <Box>
<Text as="span">Before { selectedNetwork?.currency } </Text> <Text as="span">Before { appConfig.network.currency } </Text>
<Text as="span" variant="secondary">{ before.balance }</Text> <Text as="span" variant="secondary">{ before.balance }</Text>
</Box> </Box>
{ typeof before.nonce !== 'undefined' && ( { typeof before.nonce !== 'undefined' && (
...@@ -75,7 +74,7 @@ const TxStateListItem = ({ storage, address, miner, after, before, diff }: Props ...@@ -75,7 +74,7 @@ const TxStateListItem = ({ storage, address, miner, after, before, diff }: Props
</Box> </Box>
) } ) }
<Box> <Box>
<Text as="span">After { selectedNetwork?.currency } </Text> <Text as="span">After { appConfig.network.currency } </Text>
<Text as="span" variant="secondary">{ after.balance }</Text> <Text as="span" variant="secondary">{ after.balance }</Text>
</Box> </Box>
{ typeof after.nonce !== 'undefined' && ( { typeof after.nonce !== 'undefined' && (
...@@ -84,7 +83,7 @@ const TxStateListItem = ({ storage, address, miner, after, before, diff }: Props ...@@ -84,7 +83,7 @@ const TxStateListItem = ({ storage, address, miner, after, before, diff }: Props
<Text as="span" fontWeight={ 600 }>{ nbsp }{ after.nonce }</Text> <Text as="span" fontWeight={ 600 }>{ nbsp }{ after.nonce }</Text>
</Box> </Box>
) } ) }
<Text>State difference { selectedNetwork?.currency }</Text> <Text>State difference { appConfig.network.currency }</Text>
<Stat> <Stat>
{ diff } { diff }
<StatArrow ml={ 2 } type={ Number(diff) > 0 ? 'increase' : 'decrease' }/> <StatArrow ml={ 2 } type={ Number(diff) > 0 ? 'increase' : 'decrease' }/>
......
...@@ -6,15 +6,13 @@ import { ...@@ -6,15 +6,13 @@ import {
Th, Th,
TableContainer, TableContainer,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import appConfig from 'configs/app/config';
import React from 'react'; import React from 'react';
import { data } from 'data/txState'; import { data } from 'data/txState';
import useNetwork from 'lib/hooks/useNetwork';
import TxStateTableItem from 'ui/tx/state/TxStateTableItem'; import TxStateTableItem from 'ui/tx/state/TxStateTableItem';
const TxStateTable = () => { const TxStateTable = () => {
const selectedNetwork = useNetwork();
return ( return (
<TableContainer width="100%" mt={ 6 }> <TableContainer width="100%" mt={ 6 }>
<Table variant="simple" minWidth="950px" size="sm" w="auto"> <Table variant="simple" minWidth="950px" size="sm" w="auto">
...@@ -23,9 +21,9 @@ const TxStateTable = () => { ...@@ -23,9 +21,9 @@ const TxStateTable = () => {
<Th width="92px">Storage</Th> <Th width="92px">Storage</Th>
<Th width="146px">Address</Th> <Th width="146px">Address</Th>
<Th width="120px">Miner</Th> <Th width="120px">Miner</Th>
<Th width="33%" isNumeric>{ `After ${ selectedNetwork?.currency }` }</Th> <Th width="33%" isNumeric>{ `After ${ appConfig.network.currency }` }</Th>
<Th width="33%" isNumeric>{ `Before ${ selectedNetwork?.currency }` }</Th> <Th width="33%" isNumeric>{ `Before ${ appConfig.network.currency }` }</Th>
<Th width="33%" isNumeric>{ `State difference ${ selectedNetwork?.currency }` }</Th> <Th width="33%" isNumeric>{ `State difference ${ appConfig.network.currency }` }</Th>
</Tr> </Tr>
</Thead> </Thead>
<Tbody> <Tbody>
......
import { Box, Heading, Text, Flex, Link, useColorModeValue } from '@chakra-ui/react'; import { Box, Heading, Text, Flex, Link, useColorModeValue } from '@chakra-ui/react';
import appConfig from 'configs/app/config';
import React from 'react'; import React from 'react';
import type ArrayElement from 'types/utils/ArrayElement'; import type ArrayElement from 'types/utils/ArrayElement';
import type { txs } from 'data/txs'; import type { txs } from 'data/txs';
import useNetwork from 'lib/hooks/useNetwork';
import { nbsp } from 'lib/html-entities'; import { nbsp } from 'lib/html-entities';
import useLink from 'lib/link/useLink'; import useLink from 'lib/link/useLink';
import TextSeparator from 'ui/shared/TextSeparator'; import TextSeparator from 'ui/shared/TextSeparator';
import Utilization from 'ui/shared/Utilization'; import Utilization from 'ui/shared/Utilization';
const TxAdditionalInfo = ({ tx }: { tx: ArrayElement<typeof txs> }) => { const TxAdditionalInfo = ({ tx }: { tx: ArrayElement<typeof txs> }) => {
const selectedNetwork = useNetwork();
const sectionBorderColor = useColorModeValue('gray.200', 'whiteAlpha.200'); const sectionBorderColor = useColorModeValue('gray.200', 'whiteAlpha.200');
const sectionProps = { const sectionProps = {
borderBottom: '1px solid', borderBottom: '1px solid',
...@@ -35,7 +33,7 @@ const TxAdditionalInfo = ({ tx }: { tx: ArrayElement<typeof txs> }) => { ...@@ -35,7 +33,7 @@ const TxAdditionalInfo = ({ tx }: { tx: ArrayElement<typeof txs> }) => {
<Box { ...sectionProps } mb={ 4 }> <Box { ...sectionProps } mb={ 4 }>
<Text { ...sectionTitleProps }>Transaction fee</Text> <Text { ...sectionTitleProps }>Transaction fee</Text>
<Flex> <Flex>
<Text>{ tx.fee.value }{ nbsp }{ selectedNetwork?.currency }</Text> <Text>{ tx.fee.value }{ nbsp }{ appConfig.network.currency }</Text>
<Text variant="secondary" ml={ 1 }>(${ tx.fee.value_usd.toFixed(2) })</Text> <Text variant="secondary" ml={ 1 }>(${ tx.fee.value_usd.toFixed(2) })</Text>
</Flex> </Flex>
</Box> </Box>
......
...@@ -10,6 +10,7 @@ import { ...@@ -10,6 +10,7 @@ import {
Text, Text,
useColorModeValue, useColorModeValue,
useDisclosure } from '@chakra-ui/react'; useDisclosure } from '@chakra-ui/react';
import appConfig from 'configs/app/config';
import React from 'react'; import React from 'react';
import type ArrayElement from 'types/utils/ArrayElement'; import type ArrayElement from 'types/utils/ArrayElement';
...@@ -18,7 +19,6 @@ import type { txs } from 'data/txs'; ...@@ -18,7 +19,6 @@ import type { txs } from 'data/txs';
import rightArrowIcon from 'icons/arrows/east.svg'; import rightArrowIcon from 'icons/arrows/east.svg';
import transactionIcon from 'icons/transactions.svg'; import transactionIcon from 'icons/transactions.svg';
import dayjs from 'lib/date/dayjs'; import dayjs from 'lib/date/dayjs';
import useNetwork from 'lib/hooks/useNetwork';
import useLink from 'lib/link/useLink'; import useLink from 'lib/link/useLink';
import Address from 'ui/shared/address/Address'; import Address from 'ui/shared/address/Address';
import AddressIcon from 'ui/shared/address/AddressIcon'; import AddressIcon from 'ui/shared/address/AddressIcon';
...@@ -30,7 +30,6 @@ import TxType from 'ui/txs/TxType'; ...@@ -30,7 +30,6 @@ import TxType from 'ui/txs/TxType';
const TxsListItem = ({ tx }: {tx: ArrayElement<typeof txs>}) => { const TxsListItem = ({ tx }: {tx: ArrayElement<typeof txs>}) => {
const { isOpen, onOpen, onClose } = useDisclosure(); const { isOpen, onOpen, onClose } = useDisclosure();
const selectedNetwork = useNetwork();
const iconColor = useColorModeValue('blue.600', 'blue.300'); const iconColor = useColorModeValue('blue.600', 'blue.300');
const borderColor = useColorModeValue('blackAlpha.200', 'whiteAlpha.200'); const borderColor = useColorModeValue('blackAlpha.200', 'whiteAlpha.200');
...@@ -108,11 +107,11 @@ const TxsListItem = ({ tx }: {tx: ArrayElement<typeof txs>}) => { ...@@ -108,11 +107,11 @@ const TxsListItem = ({ tx }: {tx: ArrayElement<typeof txs>}) => {
</Address> </Address>
</Flex> </Flex>
<Box mt={ 2 }> <Box mt={ 2 }>
<Text as="span">Value { selectedNetwork?.currency } </Text> <Text as="span">Value { appConfig.network.currency } </Text>
<Text as="span" variant="secondary">{ tx.amount.value.toFixed(8) }</Text> <Text as="span" variant="secondary">{ tx.amount.value.toFixed(8) }</Text>
</Box> </Box>
<Box mt={ 2 } mb={ 3 }> <Box mt={ 2 } mb={ 3 }>
<Text as="span">Fee { selectedNetwork?.currency } </Text> <Text as="span">Fee { appConfig.network.currency } </Text>
<Text as="span" variant="secondary">{ tx.fee.value.toFixed(8) }</Text> <Text as="span" variant="secondary">{ tx.fee.value.toFixed(8) }</Text>
</Box> </Box>
</Box> </Box>
......
import { Link, Table, Thead, Tbody, Tr, Th, TableContainer, Icon } from '@chakra-ui/react'; import { Link, Table, Thead, Tbody, Tr, Th, TableContainer, Icon } from '@chakra-ui/react';
import appConfig from 'configs/app/config';
import React from 'react'; import React from 'react';
import type { Sort } from 'types/client/txs-sort'; import type { Sort } from 'types/client/txs-sort';
import type { txs as data } from 'data/txs'; import type { txs as data } from 'data/txs';
import rightArrowIcon from 'icons/arrows/east.svg'; import rightArrowIcon from 'icons/arrows/east.svg';
import useNetwork from 'lib/hooks/useNetwork';
import TxsTableItem from './TxsTableItem'; import TxsTableItem from './TxsTableItem';
...@@ -16,8 +16,6 @@ type Props = { ...@@ -16,8 +16,6 @@ type Props = {
} }
const TxsTable = ({ txs, sort, sorting }: Props) => { const TxsTable = ({ txs, sort, sorting }: Props) => {
const selectedNetwork = useNetwork();
return ( return (
<TableContainer width="100%" mt={ 6 }> <TableContainer width="100%" mt={ 6 }>
<Table variant="simple" minWidth="810px" size="xs"> <Table variant="simple" minWidth="810px" size="xs">
...@@ -35,14 +33,14 @@ const TxsTable = ({ txs, sort, sorting }: Props) => { ...@@ -35,14 +33,14 @@ const TxsTable = ({ txs, sort, sorting }: Props) => {
<Link onClick={ sort('val') } display="flex" justifyContent="end"> <Link onClick={ sort('val') } display="flex" justifyContent="end">
{ sorting === 'val-asc' && <Icon boxSize={ 5 } as={ rightArrowIcon } transform="rotate(-90deg)"/> } { sorting === 'val-asc' && <Icon boxSize={ 5 } as={ rightArrowIcon } transform="rotate(-90deg)"/> }
{ sorting === 'val-desc' && <Icon boxSize={ 5 } as={ rightArrowIcon } transform="rotate(90deg)"/> } { sorting === 'val-desc' && <Icon boxSize={ 5 } as={ rightArrowIcon } transform="rotate(90deg)"/> }
{ `Value ${ selectedNetwork?.currency }` } { `Value ${ appConfig.network.currency }` }
</Link> </Link>
</Th> </Th>
<Th width="18%" isNumeric pr={ 5 }> <Th width="18%" isNumeric pr={ 5 }>
<Link onClick={ sort('fee') } display="flex" justifyContent="end"> <Link onClick={ sort('fee') } display="flex" justifyContent="end">
{ sorting === 'fee-asc' && <Icon boxSize={ 5 } as={ rightArrowIcon } transform="rotate(-90deg)"/> } { sorting === 'fee-asc' && <Icon boxSize={ 5 } as={ rightArrowIcon } transform="rotate(-90deg)"/> }
{ sorting === 'fee-desc' && <Icon boxSize={ 5 } as={ rightArrowIcon } transform="rotate(90deg)"/> } { sorting === 'fee-desc' && <Icon boxSize={ 5 } as={ rightArrowIcon } transform="rotate(90deg)"/> }
{ `Fee ${ selectedNetwork?.currency }` } { `Fee ${ appConfig.network.currency }` }
</Link> </Link>
</Th> </Th>
</Tr> </Tr>
......
import { Grid, GridItem } from '@chakra-ui/react'; import { Grid, GridItem } from '@chakra-ui/react';
import appConfig from 'configs/app/config';
import React, { useCallback } from 'react'; import React, { useCallback } from 'react';
import { Controller } from 'react-hook-form'; import { Controller } from 'react-hook-form';
import type { Path, ControllerRenderProps, FieldValues, Control } from 'react-hook-form'; import type { Path, ControllerRenderProps, FieldValues, Control } from 'react-hook-form';
import useNetwork from 'lib/hooks/useNetwork';
import CheckboxInput from 'ui/shared/CheckboxInput'; import CheckboxInput from 'ui/shared/CheckboxInput';
// does it depend on the network? // does it depend on the network?
const NOTIFICATIONS = [ 'native', 'ERC-20', 'ERC-721' ] as const; const NOTIFICATIONS = [ 'native', 'ERC-20', 'ERC-721' ] as const;
const NOTIFICATIONS_NAMES = [ appConfig.network.currency, 'ERC-20', 'ERC-721, ERC-1155 (NFT)' ];
type Props<Inputs extends FieldValues> = { type Props<Inputs extends FieldValues> = {
control: Control<Inputs>; control: Control<Inputs>;
} }
export default function AddressFormNotifications<Inputs extends FieldValues, Checkboxes extends Path<Inputs>>({ control }: Props<Inputs>) { export default function AddressFormNotifications<Inputs extends FieldValues, Checkboxes extends Path<Inputs>>({ control }: Props<Inputs>) {
const selectedNetwork = useNetwork();
const NOTIFICATIONS_NAMES = React.useMemo(() => {
return [ selectedNetwork?.currency, 'ERC-20', 'ERC-721, ERC-1155 (NFT)' ];
}, [ selectedNetwork?.currency ]);
// eslint-disable-next-line react/display-name // eslint-disable-next-line react/display-name
const renderCheckbox = useCallback((text: string) => ({ field }: {field: ControllerRenderProps<Inputs, Checkboxes>}) => ( const renderCheckbox = useCallback((text: string) => ({ field }: {field: ControllerRenderProps<Inputs, Checkboxes>}) => (
<CheckboxInput<Inputs, Checkboxes> text={ text } field={ field }/> <CheckboxInput<Inputs, Checkboxes> text={ text } field={ field }/>
......
import { HStack, VStack, Text, Icon, useColorModeValue } from '@chakra-ui/react'; import { HStack, VStack, Text, Icon, useColorModeValue } from '@chakra-ui/react';
import appConfig from 'configs/app/config';
import React from 'react'; import React from 'react';
import type { TWatchlistItem } from 'types/client/account'; import type { TWatchlistItem } from 'types/client/account';
import TokensIcon from 'icons/tokens.svg'; import TokensIcon from 'icons/tokens.svg';
// import WalletIcon from 'icons/wallet.svg'; // import WalletIcon from 'icons/wallet.svg';
import useNetwork from 'lib/hooks/useNetwork';
import { nbsp } from 'lib/html-entities'; import { nbsp } from 'lib/html-entities';
import AddressSnippet from 'ui/shared/AddressSnippet'; import AddressSnippet from 'ui/shared/AddressSnippet';
import TokenLogo from 'ui/shared/TokenLogo'; import TokenLogo from 'ui/shared/TokenLogo';
...@@ -14,7 +14,6 @@ const DECIMALS = 18; ...@@ -14,7 +14,6 @@ const DECIMALS = 18;
const WatchListAddressItem = ({ item }: {item: TWatchlistItem}) => { const WatchListAddressItem = ({ item }: {item: TWatchlistItem}) => {
const mainTextColor = useColorModeValue('gray.700', 'gray.50'); const mainTextColor = useColorModeValue('gray.700', 'gray.50');
const selectedNetwork = useNetwork();
const nativeBalance = ((item.address_balance || 0) / 10 ** DECIMALS).toFixed(1); const nativeBalance = ((item.address_balance || 0) / 10 ** DECIMALS).toFixed(1);
const nativeBalanceUSD = item.exchange_rate ? `$${ Number(nativeBalance) * item.exchange_rate } USD` : 'N/A'; const nativeBalanceUSD = item.exchange_rate ? `$${ Number(nativeBalance) * item.exchange_rate } USD` : 'N/A';
...@@ -24,8 +23,9 @@ const WatchListAddressItem = ({ item }: {item: TWatchlistItem}) => { ...@@ -24,8 +23,9 @@ const WatchListAddressItem = ({ item }: {item: TWatchlistItem}) => {
<VStack spacing={ 2 } align="stretch" overflow="hidden" fontWeight={ 500 } color="gray.700"> <VStack spacing={ 2 } align="stretch" overflow="hidden" fontWeight={ 500 } color="gray.700">
<AddressSnippet address={ item.address_hash }/> <AddressSnippet address={ item.address_hash }/>
<HStack spacing={ 0 } fontSize="sm" h={ 6 } pl={ infoItemsPaddingLeft }> <HStack spacing={ 0 } fontSize="sm" h={ 6 } pl={ infoItemsPaddingLeft }>
{ selectedNetwork && <TokenLogo hash={ selectedNetwork.nativeTokenAddress } name={ selectedNetwork.name } boxSize={ 4 } mr="10px"/> } { appConfig.network.nativeTokenAddress &&
<Text color={ mainTextColor }>{ `${ selectedNetwork?.currency } balance:${ nbsp }` + nativeBalance }</Text> <TokenLogo hash={ appConfig.network.nativeTokenAddress } name={ appConfig.network.name } boxSize={ 4 } mr="10px"/> }
<Text color={ mainTextColor }>{ `${ appConfig.network.currency } balance:${ nbsp }` + nativeBalance }</Text>
<Text variant="secondary">{ `${ nbsp }(${ nativeBalanceUSD })` }</Text> <Text variant="secondary">{ `${ nbsp }(${ nativeBalanceUSD })` }</Text>
</HStack> </HStack>
{ item.tokens_count && ( { item.tokens_count && (
......
...@@ -1797,7 +1797,7 @@ ...@@ -1797,7 +1797,7 @@
lru_map "^0.3.3" lru_map "^0.3.3"
tslib "^1.9.3" tslib "^1.9.3"
"@sentry/react@7.15.0": "@sentry/react@7.15.0", "@sentry/react@^7.13.0":
version "7.15.0" version "7.15.0"
resolved "https://registry.yarnpkg.com/@sentry/react/-/react-7.15.0.tgz#441ed851ca64afeef10abcb00302e0c95846404e" resolved "https://registry.yarnpkg.com/@sentry/react/-/react-7.15.0.tgz#441ed851ca64afeef10abcb00302e0c95846404e"
integrity sha512-a+5+Og93YPtWSCmOFYa/qzrbvfgIZXShJk1bsIaEI0KdltTOVJBdwvLQc8OiIOBe/CMDVCmK1t2DqiWfOWj41w== integrity sha512-a+5+Og93YPtWSCmOFYa/qzrbvfgIZXShJk1bsIaEI0KdltTOVJBdwvLQc8OiIOBe/CMDVCmK1t2DqiWfOWj41w==
...@@ -1808,7 +1808,7 @@ ...@@ -1808,7 +1808,7 @@
hoist-non-react-statics "^3.3.2" hoist-non-react-statics "^3.3.2"
tslib "^1.9.3" tslib "^1.9.3"
"@sentry/tracing@7.15.0": "@sentry/tracing@7.15.0", "@sentry/tracing@^7.13.0":
version "7.15.0" version "7.15.0"
resolved "https://registry.yarnpkg.com/@sentry/tracing/-/tracing-7.15.0.tgz#ea516957b2ed39f389c21132f433b6470d54b465" resolved "https://registry.yarnpkg.com/@sentry/tracing/-/tracing-7.15.0.tgz#ea516957b2ed39f389c21132f433b6470d54b465"
integrity sha512-c0Y3+z6EWsc+EJsfBcRtc58ugkWYa6+6KTu3ceMkx2ZgZTCmRUuzAb7yodMt/gwezBsxzq706fnQivx1lQgzlQ== integrity sha512-c0Y3+z6EWsc+EJsfBcRtc58ugkWYa6+6KTu3ceMkx2ZgZTCmRUuzAb7yodMt/gwezBsxzq706fnQivx1lQgzlQ==
...@@ -2678,6 +2678,26 @@ domutils@^2.8.0: ...@@ -2678,6 +2678,26 @@ domutils@^2.8.0:
domelementtype "^2.2.0" domelementtype "^2.2.0"
domhandler "^4.2.0" domhandler "^4.2.0"
dotenv-cli@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/dotenv-cli/-/dotenv-cli-6.0.0.tgz#8a30cbc59d0a8aaa166b2fee0a9a55e23a1223ab"
integrity sha512-qXlCOi3UMDhCWFKe0yq5sg3X+pJAz+RQDiFN38AMSbUrnY3uZshSfDJUAge951OS7J9gwLZGfsBlWRSOYz/TRg==
dependencies:
cross-spawn "^7.0.3"
dotenv "^16.0.0"
dotenv-expand "^8.0.1"
minimist "^1.2.5"
dotenv-expand@^8.0.1:
version "8.0.3"
resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-8.0.3.tgz#29016757455bcc748469c83a19b36aaf2b83dd6e"
integrity sha512-SErOMvge0ZUyWd5B0NXMQlDkN+8r+HhVUsxgOO7IoPDOdDRD2JjExpN6y3KnFR66jsJMwSn1pqIivhU5rcJiNg==
dotenv@^16.0.0:
version "16.0.3"
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.0.3.tgz#115aec42bac5053db3c456db30cc243a5a836a07"
integrity sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==
eastasianwidth@^0.2.0: eastasianwidth@^0.2.0:
version "0.2.0" version "0.2.0"
resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb"
...@@ -4048,7 +4068,7 @@ minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2: ...@@ -4048,7 +4068,7 @@ minimatch@^3.0.4, minimatch@^3.1.1, minimatch@^3.1.2:
dependencies: dependencies:
brace-expansion "^1.1.7" brace-expansion "^1.1.7"
minimist@^1.2.0, minimist@^1.2.6: minimist@^1.2.0, minimist@^1.2.5, minimist@^1.2.6:
version "1.2.7" version "1.2.7"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.7.tgz#daa1c4d91f507390437c6a8bc01078e7000c4d18" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.7.tgz#daa1c4d91f507390437c6a8bc01078e7000c4d18"
integrity sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g== integrity sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==
......
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