Commit 0de954b9 authored by tom's avatar tom

Merge branch 'main' of github.com:blockscout/frontend into block-details-api

parents 925f7fdd 72ae4ea7
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_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 ],
},
},
], ],
}; };
__snapshots__/** filter=lfs diff=lfs merge=lfs -text
name: Cleanup environments
on:
pull_request:
types:
- closed
- merged
workflow_dispatch:
jobs:
cleanup:
uses: blockscout/blockscout-ci-cd/.github/workflows/cleanup.yaml@master
with:
appNamespace: review-front-$GITHUB_HEAD_REF_SLUG
secrets: inherit
name: Run E2E tests k8s
on:
# push:
# pull_request:
workflow_dispatch
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@master
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: Linters
on: [pull_request] on:
pull_request:
push:
branches:
- main
jobs: jobs:
eslint: check_code:
name: Run code checks
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 16
cache: 'yarn'
- name: Install dependencies - name: Install dependencies
run: yarn run: yarn install --frozen-lockfile
- 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: Playwright
on: [pull_request]
jobs:
test:
name: Run components visual tests
runs-on: ubuntu-latest
container:
image: mcr.microsoft.com/playwright:v1.27.0-focal
steps:
- run: apt-get update && apt-get install git-lfs
- uses: actions/checkout@v3
with:
lfs: 'true'
- uses: actions/setup-node@v3
with:
node-version: '16'
cache: 'yarn'
- name: Install dependencies
run: yarn install --frozen-lockfile
- name: Run your tests
run: HOME=/root yarn test-ct
- name: Upload test results
if: always()
uses: actions/upload-artifact@v2
with:
name: playwright-report
path: playwright-report
\ No newline at end of file
name: Deploy review environment
on:
# push:
pull_request:
workflow_dispatch:
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@master
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,21 @@ jobs: ...@@ -50,29 +48,21 @@ 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_main:
needs: push_to_registry needs: push_to_registry
runs-on: ubuntu-latest uses: blockscout/blockscout-ci-cd/.github/workflows/deploy.yaml@master
steps: with:
- name: Check out the repo valuesDir: deploy/values/review
uses: actions/checkout@v3 appNamespace: front-main
- name: Set Kubernetes Context blockscoutIngressHost: blockscout
uses: azure/k8s-set-context@v1 frontendIngressHost: frontend
with: frontendImage: ghcr.io/blockscout/frontend:main
method: kubeconfig gethIngressHost: geth
kubeconfig: ${{ secrets.KUBE_CONFIG }} scVerifierIngressHost: sc-verifier
- name: Deploy to k8s secrets: inherit
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
...@@ -28,6 +28,7 @@ yarn-error.log* ...@@ -28,6 +28,7 @@ yarn-error.log*
# local env files # local env files
.env*.local .env*.local
/configs/envs/.env.secrets
# vercel # vercel
.vercel .vercel
...@@ -39,3 +40,8 @@ yarn-error.log* ...@@ -39,3 +40,8 @@ yarn-error.log*
# Sentry # Sentry
.sentryclirc .sentryclirc
**.decrypted~**
/test-results/
/playwright-report/
/playwright/.cache/
#!/bin/sh
command -v git-lfs >/dev/null 2>&1 || { echo >&2 "\nThis repository is configured for Git LFS but 'git-lfs' was not found on your path. If you no longer wish to use Git LFS, remove this hook by deleting '.git/hooks/post-checkout'.\n"; exit 2; }
git lfs post-checkout "$@"
#!/bin/sh
command -v git-lfs >/dev/null 2>&1 || { echo >&2 "\nThis repository is configured for Git LFS but 'git-lfs' was not found on your path. If you no longer wish to use Git LFS, remove this hook by deleting '.git/hooks/post-commit'.\n"; exit 2; }
git lfs post-commit "$@"
#!/bin/sh
command -v git-lfs >/dev/null 2>&1 || { echo >&2 "\nThis repository is configured for Git LFS but 'git-lfs' was not found on your path. If you no longer wish to use Git LFS, remove this hook by deleting '.git/hooks/post-merge'.\n"; exit 2; }
git lfs post-merge "$@"
#!/bin/sh
command -v git-lfs >/dev/null 2>&1 || { echo >&2 "\nThis repository is configured for Git LFS but 'git-lfs' was not found on your path. If you no longer wish to use Git LFS, remove this hook by deleting '.git/hooks/pre-push'.\n"; exit 2; }
git lfs pre-push "$@"
...@@ -23,10 +23,30 @@ ...@@ -23,10 +23,30 @@
"instanceLimit": 1 "instanceLimit": 1
} }
}, },
{
"type": "npm",
"script": "dev:poa_core",
"problemMatcher": [],
"label": "dev server: poa",
"detail": "start local dev server for POA network",
"presentation": {
"reveal": "silent",
"panel": "new",
"close": true,
"revealProblems": "onProblem",
},
"icon": {
"color": "terminal.ansiMagenta",
"id": "server-process"
},
"runOptions": {
"instanceLimit": 1
}
},
{ {
"type": "typescript", "type": "typescript",
"label": "tsc build", "label": "tsc build",
"detail": "run ts typechecking", "detail": "compile typescript",
"tsconfig": "tsconfig.json", "tsconfig": "tsconfig.json",
"problemMatcher": [ "problemMatcher": [
"$tsc" "$tsc"
...@@ -42,6 +62,106 @@ ...@@ -42,6 +62,106 @@
"revealProblems": "onProblem", "revealProblems": "onProblem",
}, },
"group": "build", "group": "build",
} },
{
"type": "npm",
"script": "lint:eslint",
"problemMatcher": [],
"label": "eslint",
"detail": "run eslint",
"presentation": {
"reveal": "silent",
"panel": "new",
"close": true,
"revealProblems": "onProblem",
},
"icon": {
"color": "terminal.ansiYellow",
"id": "zap"
},
"runOptions": {
"instanceLimit": 1
}
},
{
"type": "npm",
"script": "test-docker",
"problemMatcher": [],
"label": "test: playwright",
"detail": "run visual components tests",
"presentation": {
"reveal": "silent",
"panel": "new",
"close": true,
"revealProblems": "onProblem",
},
"icon": {
"color": "terminal.ansiBlue",
"id": "beaker"
},
"runOptions": {
"instanceLimit": 1
}
},
{
"type": "npm",
"script": "build:docker",
"problemMatcher": [],
"label": "docker: build",
"detail": "build docker image",
"presentation": {
"reveal": "silent",
"panel": "new",
"close": true,
"revealProblems": "onProblem",
},
"icon": {
"color": "terminal.ansiRed",
"id": "symbol-structure"
},
"runOptions": {
"instanceLimit": 1
}
},
{
"type": "npm",
"script": "start:docker:poa_core",
"problemMatcher": [],
"label": "docker: run poa",
"detail": "run docker container for POA network",
"presentation": {
"reveal": "silent",
"panel": "new",
"close": true,
"revealProblems": "onProblem",
},
"icon": {
"color": "terminal.ansiRed",
"id": "browser"
},
"runOptions": {
"instanceLimit": 1
}
},
{
"type": "npm",
"script": "format-svg",
"problemMatcher": [],
"label": "format svg",
"detail": "format svg files with svgo",
"presentation": {
"reveal": "silent",
"panel": "new",
"close": true,
"revealProblems": "onProblem",
},
"icon": {
"color": "terminal.ansiCyan",
"id": "combine"
},
"runOptions": {
"instanceLimit": 1
}
},
] ]
} }
\ No newline at end of file
...@@ -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
This diff is collapsed.
# 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_VERCEL_URL || 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;
# app config
NEXT_PUBLIC_APP_PROTOCOL=http
NEXT_PUBLIC_APP_HOST=localhost
NEXT_PUBLIC_APP_PORT=3000
NEXT_PUBLIC_APP_INSTANCE=local
# nav and footer config
NEXT_PUBLIC_BLOCKSCOUT_VERSION=v4.1.7-beta
NEXT_PUBLIC_FOOTER_GITHUB_LINK=https://github.com/blockscout/blockscout
NEXT_PUBLIC_FOOTER_TWITTER_LINK=https://www.twitter.com/blockscoutcom
NEXT_PUBLIC_FEATURED_NETWORKS=[{'title':'Gnosis Chain','basePath':'/xdai/mainnet','group':'mainnets'}]
# marketplace config
NEXT_PUBLIC_MARKETPLACE_SUBMIT_FORM=https://airtable.com/shrqUAcjgGJ4jU88C
# api config
NEXT_PUBLIC_API_ENDPOINT=https://blockscout.com
\ No newline at end of file
# nav and footer config
NEXT_PUBLIC_FOOTER_TELEGRAM_LINK=https://t.me/poa_network
NEXT_PUBLIC_FOOTER_STAKING_LINK=https://duneanalytics.com/maxaleks/xdai-staking
NEXT_PUBLIC_FEATURED_NETWORKS=[{'title':'Gnosis Chain','basePath':'/xdai/mainnet','group':'mainnets'},{'title':'Optimism on Gnosis Chain','basePath':'/xdai/optimism','group':'mainnets','icon':'https://www.fillmurray.com/60/60'},{'title':'Arbitrum on xDai','basePath':'/xdai/aox','group':'mainnets'},{'title':'Ethereum','basePath':'/eth/mainnet','group':'mainnets'},{'title':'Ethereum Classic','basePath':'/etx/mainnet','group':'mainnets'},{'title':'POA','basePath':'/poa/core','group':'mainnets'},{'title':'RSK','basePath':'/rsk/mainnet','group':'mainnets'},{'title':'Gnosis Chain Testnet','basePath':'/xdai/testnet','group':'testnets'},{'title':'POA Sokol','basePath':'/poa/sokol','group':'testnets'},{'title':'ARTIS Σ1','basePath':'/artis/sigma1','group':'other'},{'title':'LUKSO L14','basePath':'/lukso/l14','group':'other'},{'title':'Astar','basePath':'/astar','group':'other'}]
# current network config
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
# api config
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(),
},
], ],
}, },
]; ];
......
const BASE_PATH = require('../../lib/link/basePath.js');
const PATHS = require('../../lib/link/paths.js');
const oldUrls = [
{
oldPath: '/account/tag_address',
newPath: `${ PATHS.private_tags }?tab=address`,
},
{
oldPath: '/account/tag_transaction',
newPath: `${ PATHS.private_tags }?tab=tx`,
},
{
oldPath: '/pending-transactions',
newPath: `${ PATHS.txs }?tab=pending`,
},
{
oldPath: '/tx/:id/internal-transactions',
newPath: `${ PATHS.tx }?tab=internal`,
},
{
oldPath: '/tx/:id/logs',
newPath: `${ PATHS.tx }?tab=logs`,
},
{
oldPath: '/tx/:id/raw-trace',
newPath: `${ PATHS.tx }?tab=raw_trace`,
},
{
oldPath: '/tx/:id/state',
newPath: `${ PATHS.tx }?tab=state`,
},
{
oldPath: '/uncles',
newPath: `${ PATHS.blocks }?tab=uncles`,
},
{
oldPath: '/reorgs',
newPath: `${ PATHS.blocks }?tab=reorgs`,
},
{
oldPath: '/block/:id/transactions',
newPath: `${ PATHS.block }?tab=txs`,
},
];
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,
},
...oldUrls.map(item => {
const source = BASE_PATH.replaceAll('[', ':').replaceAll(']', '') + item.oldPath;
const destination = item.newPath.replaceAll('[', ':').replaceAll(']', '');
return { source, destination, 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);
}
[ [
{ {
"chainIds": [ "chainIds": [
1 "1"
], ],
"author": "xDaichain", "author": "xDaichain",
"id": "easy-staking", "id": "easy-staking",
...@@ -21,7 +21,7 @@ ...@@ -21,7 +21,7 @@
}, },
{ {
"chainIds": [ "chainIds": [
99 "99"
], ],
"author": "xDaichain", "author": "xDaichain",
"id": "curve", "id": "curve",
...@@ -41,7 +41,7 @@ ...@@ -41,7 +41,7 @@
}, },
{ {
"chainIds": [ "chainIds": [
1 "1"
], ],
"author": "xDaichain", "author": "xDaichain",
"id": "honwyswap", "id": "honwyswap",
...@@ -61,7 +61,7 @@ ...@@ -61,7 +61,7 @@
}, },
{ {
"chainIds": [ "chainIds": [
99 "99"
], ],
"author": "xDaichain", "author": "xDaichain",
"id": "sushi", "id": "sushi",
...@@ -81,7 +81,7 @@ ...@@ -81,7 +81,7 @@
}, },
{ {
"chainIds": [ "chainIds": [
100 "100"
], ],
"author": "xDaichain", "author": "xDaichain",
"id": "bao-finance", "id": "bao-finance",
...@@ -101,7 +101,7 @@ ...@@ -101,7 +101,7 @@
}, },
{ {
"chainIds": [ "chainIds": [
99 "99"
], ],
"author": "xDaichain", "author": "xDaichain",
"id": "component", "id": "component",
...@@ -121,7 +121,7 @@ ...@@ -121,7 +121,7 @@
}, },
{ {
"chainIds": [ "chainIds": [
1 "1"
], ],
"author": "xDaichain", "author": "xDaichain",
"id": "pooltogether", "id": "pooltogether",
...@@ -141,7 +141,7 @@ ...@@ -141,7 +141,7 @@
}, },
{ {
"chainIds": [ "chainIds": [
99 "99"
], ],
"author": "xDaichain", "author": "xDaichain",
"id": "swapr", "id": "swapr",
...@@ -161,7 +161,7 @@ ...@@ -161,7 +161,7 @@
}, },
{ {
"chainIds": [ "chainIds": [
1 "1"
], ],
"author": "xDaichain", "author": "xDaichain",
"id": "levinswap", "id": "levinswap",
...@@ -181,7 +181,7 @@ ...@@ -181,7 +181,7 @@
}, },
{ {
"chainIds": [ "chainIds": [
99 "99"
], ],
"author": "xDaichain", "author": "xDaichain",
"id": "omen", "id": "omen",
...@@ -201,7 +201,7 @@ ...@@ -201,7 +201,7 @@
}, },
{ {
"chainIds": [ "chainIds": [
1 "1"
], ],
"author": "xDaichain", "author": "xDaichain",
"id": "nifty-ink", "id": "nifty-ink",
...@@ -221,7 +221,7 @@ ...@@ -221,7 +221,7 @@
}, },
{ {
"chainIds": [ "chainIds": [
99 "99"
], ],
"author": "xDaichain", "author": "xDaichain",
"id": "treasure-chess", "id": "treasure-chess",
...@@ -241,7 +241,7 @@ ...@@ -241,7 +241,7 @@
}, },
{ {
"chainIds": [ "chainIds": [
1 "1"
], ],
"author": "xDaichain", "author": "xDaichain",
"id": "unique-one", "id": "unique-one",
...@@ -261,7 +261,7 @@ ...@@ -261,7 +261,7 @@
}, },
{ {
"chainIds": [ "chainIds": [
99 "99"
], ],
"author": "xDaichain", "author": "xDaichain",
"id": "cold-truth-culture", "id": "cold-truth-culture",
...@@ -281,7 +281,7 @@ ...@@ -281,7 +281,7 @@
}, },
{ {
"chainIds": [ "chainIds": [
1 "1"
], ],
"author": "xDaichain", "author": "xDaichain",
"id": "xdai-bridge", "id": "xdai-bridge",
...@@ -301,7 +301,7 @@ ...@@ -301,7 +301,7 @@
}, },
{ {
"chainIds": [ "chainIds": [
99 "99"
], ],
"author": "xDaichain", "author": "xDaichain",
"id": "omni-bridge", "id": "omni-bridge",
...@@ -321,7 +321,7 @@ ...@@ -321,7 +321,7 @@
}, },
{ {
"chainIds": [ "chainIds": [
1 "1"
], ],
"author": "xDaichain", "author": "xDaichain",
"id": "gnosis-safe", "id": "gnosis-safe",
...@@ -341,7 +341,7 @@ ...@@ -341,7 +341,7 @@
}, },
{ {
"chainIds": [ "chainIds": [
99 "99"
], ],
"author": "xDaichain", "author": "xDaichain",
"id": "multisender", "id": "multisender",
...@@ -361,7 +361,7 @@ ...@@ -361,7 +361,7 @@
}, },
{ {
"chainIds": [ "chainIds": [
1 "1"
], ],
"author": "xDaichain", "author": "xDaichain",
"id": "disperse", "id": "disperse",
...@@ -381,7 +381,7 @@ ...@@ -381,7 +381,7 @@
}, },
{ {
"chainIds": [ "chainIds": [
99 "99"
], ],
"author": "xDaichain", "author": "xDaichain",
"id": "symmetric", "id": "symmetric",
......
/* eslint-disable max-len */
export const tx = {
hash: '0x1ea365d2144796f793883534aa51bf20d23292b19478994eede23dfc599e7c34',
status: 'ok' as Transaction['status'],
block_num: 15006918,
confirmation_num: 283,
confirmation_duration: 30,
timestamp: 1662623567695,
address_from: {
hash: '0x97Aa2EfcF35c0f4c9AaDDCa8c2330fa7A9533830',
type: 'Address',
alias: '',
},
address_to: {
hash: '0x35317007D203b8a86CA727ad44E473E40450E378',
type: 'Contract',
alias: '',
},
amount: {
value: 0.03,
value_usd: 35.5,
},
fee: {
value: 0.002395904453623692,
value_usd: 2.84,
},
gas_price: 0.000000017716513811,
gas_limit: 208420,
gas_used: 159319,
gas_fees: {
base: 13.538410068,
max: 20.27657523,
max_priority: 1.5,
},
burnt_fees: {
value: 0.002156925953623692,
value_usd: 2.55,
},
type: {
value: '2',
eip: 'EIP-1559',
},
nonce: 4,
position: 342,
input_hex: '0x42842e0e0000000000000000000000007767dac225a233ea1055d79fb227b1696d538b75000000000000000000000000fc3017c31fe752fc48e904050ea5d6edfc38a1b00000000000000000000000000000000000000000000000000000000000000e3b',
transferred_tokens: [
{ from: '0x12E80C27BfFBB76b4A8d26FF2bfd3C9f310FFA01', to: '0xF7A558692dFB5F456e291791da7FAE8Dd046574e', token: { symbol: 'VIK', hash: '0xADFE00d92e5A16e773891F59780e6e54f40B532e', name: 'Viktor Coin' }, amount: 192.7, usd: 194.05 },
{ from: '0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45', to: '0x12E80C27BfFBB76b4A8d26FF2bfd3C9f310FFA01', token: { symbol: 'PAO', hash: '0xC98a06220239818B086CD96756d4E3bC41EC848f', name: 'POA Candy' }, amount: 76.1851851851846, usd: 194.05 },
],
txType: 'transaction' as TxType,
};
export type TxType = 'contract-call' | 'transaction' | 'token-transfer' | 'internal-tx' | 'multicall';
import type { Transaction } from 'types/api/transaction';
import type { Transaction } from 'types/api/transaction';
import { tx } from './tx';
import type { TxType } from './tx';
export const txs = [
{
...tx,
method: 'Withdraw',
txType: 'transaction' as TxType,
errorText: '',
},
{
...tx,
status: 'error' as Transaction['status'],
errorText: 'Error: (Awaiting internal transactions for reason)',
txType: 'contract-call' as TxType,
method: 'CommitHash CommitHash CommitHash CommitHash',
amount: {
value: 0.04,
value_usd: 35.5,
},
fee: {
value: 0.002295904453623692,
value_usd: 2.84,
},
},
{
...tx,
status: null,
txType: 'token-transfer' as TxType,
method: 'Multicall',
address_from: {
hash: '0x97Aa2EfcF35c0f4c9AaDDCa8c2330fa7A9533830',
alias: 'tkdkdkdkdkdkdkdkdkdkdkdkdkdkd.eth',
type: 'ENS name',
},
amount: {
value: 0.02,
value_usd: 35.5,
},
fee: {
value: 0.002495904453623692,
value_usd: 2.84,
},
errorText: '',
},
];
#!/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" ]; 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
This diff is collapsed.
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:main
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: 80
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: Sokol
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: sokol
NEXT_PUBLIC_NETWORK_ID:
_default: 77
NEXT_PUBLIC_NETWORK_CURRENCY:
_default: SPOA
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:
_default: /
---
creation_rules:
- path_regex: ^(.+/)?secrets\.yaml$
pgp: >-
99E83B7490B1A9F51781E6055317CE0D5CE1230B
This diff is collapsed.
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:main
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: 80
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: Sokol
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: sokol
NEXT_PUBLIC_NETWORK_ID:
_default: 77
NEXT_PUBLIC_NETWORK_CURRENCY:
_default: SPOA
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:
_default: /
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M11.868 1.55a.377.377 0 0 0-.378 0L3.188 6.363A.377.377 0 0 0 3 6.69v9.622c0 .135.072.26.188.327l8.302 4.811a.377.377 0 0 0 .378 0l8.302-4.811a.377.377 0 0 0 .188-.327V6.69a.377.377 0 0 0-.188-.327l-8.302-4.811ZM3.755 16.095V6.906l7.924-4.592 7.925 4.592v9.188l-7.925 4.592-7.924-4.592ZM8.66 6.972a.377.377 0 0 0-.754 0v6.354l-1.53-4.587a.377.377 0 0 0-.734.12v5.66a.377.377 0 0 0 .754 0v-3.335l1.529 4.586a.377.377 0 0 0 .735-.12V6.973Zm2.824-2.448a.377.377 0 0 1 .386-.003l2.262 1.32a.377.377 0 0 1-.38.652l-2.07-1.208-1.89 1.145v4.693h2.265a.377.377 0 0 1 0 .754H9.792v4.906a.377.377 0 0 1-.754 0V6.217c0-.132.069-.254.182-.323l2.264-1.37Zm3.195 2.06a.377.377 0 0 1 .515-.141l2.333 1.333a.377.377 0 1 1-.375.655l-.567-.324v7.544a.377.377 0 1 1-.755 0V7.676l-1.01-.578a.377.377 0 0 1-.141-.515Z" fill="currentColor"/>
</svg>
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;
} }
import BigNumber from 'bignumber.js';
export default function compareBns(value1: string | number, value2: string | number) {
const value1Bn = new BigNumber(value1);
const value2Bn = new BigNumber(value2);
if (value1Bn.isGreaterThan(value2Bn)) {
return 1;
}
if (value1Bn.isLessThan(value2Bn)) {
return -1;
}
return 0;
}
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 BigNumber from 'bignumber.js';
import { WEI, GWEI } from 'lib/consts';
export default function getValueWithUnit(value: string | number, unit: 'wei' | 'gwei' | 'ether' = 'wei') {
let unitBn: BigNumber.Value;
switch (unit) {
case 'wei':
unitBn = WEI;
break;
case 'gwei':
unitBn = GWEI;
break;
default:
unitBn = new BigNumber(1);
}
const valueBn = new BigNumber(value);
const valueCurr = valueBn.dividedBy(unitBn);
return valueCurr;
}
export default function hexToAddress(hex: string) {
const shortenHex = hex.slice(0, 66);
return shortenHex.slice(0, 2) + shortenHex.slice(26);
}
export default function hexToBytes(hex: string) {
const bytes = [];
for (let c = 0; c < hex.length; c += 2) {
bytes.push(parseInt(hex.substring(c, c + 2), 16));
}
return bytes;
}
import hexToBytes from 'lib/hexToBytes';
export default function hexToUtf8(hex: string) {
const utf8decoder = new TextDecoder();
const bytes = new Uint8Array(hexToBytes(hex));
return utf8decoder.decode(bytes);
}
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';
...@@ -12,27 +13,22 @@ import publicTagIcon from 'icons/publictags.svg'; ...@@ -12,27 +13,22 @@ import publicTagIcon from 'icons/publictags.svg';
import tokensIcon from 'icons/token.svg'; import tokensIcon from 'icons/token.svg';
import transactionsIcon from 'icons/transactions.svg'; import transactionsIcon from 'icons/transactions.svg';
import watchlistIcon from 'icons/watchlist.svg'; import watchlistIcon from 'icons/watchlist.svg';
import link from 'lib/link/link';
import useCurrentRoute from 'lib/link/useCurrentRoute'; import useCurrentRoute from 'lib/link/useCurrentRoute';
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 currentRoute = useCurrentRoute()(); const currentRoute = useCurrentRoute()();
return React.useMemo(() => { return React.useMemo(() => {
const mainNavItems = [ const mainNavItems = [
{ text: 'Blocks', url: link('blocks'), icon: blocksIcon, isActive: currentRoute.startsWith('block') }, { text: 'Blocks', url: link('blocks'), icon: blocksIcon, isActive: currentRoute.startsWith('block') },
{ text: 'Transactions', url: link('txs_validated'), icon: transactionsIcon, isActive: currentRoute.startsWith('tx') }, { text: 'Transactions', url: link('txs'), icon: transactionsIcon, isActive: currentRoute.startsWith('tx') },
{ text: 'Tokens', url: link('tokens'), icon: tokensIcon, isActive: currentRoute === 'tokens' }, { text: 'Tokens', url: link('tokens'), icon: tokensIcon, isActive: currentRoute === 'tokens' },
isMarketplaceFilled ? isMarketplaceFilled ?
{ text: 'Apps', url: link('apps'), icon: appsIcon, isActive: currentRoute === 'apps' } : null, { text: 'Apps', url: link('apps'), icon: appsIcon, isActive: currentRoute === 'apps' } : null,
...@@ -44,7 +40,7 @@ export default function useNavItems() { ...@@ -44,7 +40,7 @@ export default function useNavItems() {
const accountNavItems = [ const accountNavItems = [
{ text: 'Watchlist', url: link('watchlist'), icon: watchlistIcon, isActive: currentRoute === 'watchlist' }, { text: 'Watchlist', url: link('watchlist'), icon: watchlistIcon, isActive: currentRoute === 'watchlist' },
{ text: 'Private tags', url: link('private_tags_address'), icon: privateTagIcon, isActive: currentRoute.startsWith('private_tags') }, { text: 'Private tags', url: link('private_tags'), icon: privateTagIcon, isActive: currentRoute.startsWith('private_tags') },
{ text: 'Public tags', url: link('public_tags'), icon: publicTagIcon, isActive: currentRoute === 'public_tags' }, { text: 'Public tags', url: link('public_tags'), icon: publicTagIcon, isActive: currentRoute === 'public_tags' },
{ text: 'API keys', url: link('api_keys'), icon: apiKeysIcon, isActive: currentRoute === 'api_keys' }, { text: 'API keys', url: link('api_keys'), icon: apiKeysIcon, isActive: currentRoute === 'api_keys' },
{ text: 'Custom ABI', url: link('custom_abi'), icon: abiIcon, isActive: currentRoute === 'custom_abi' }, { text: 'Custom ABI', url: link('custom_abi'), icon: abiIcon, isActive: currentRoute === 'custom_abi' },
...@@ -53,5 +49,5 @@ export default function useNavItems() { ...@@ -53,5 +49,5 @@ export default function useNavItems() {
const profileItem = { text: 'My profile', url: link('profile'), icon: profileIcon, isActive: currentRoute === 'profile' }; const profileItem = { text: 'My profile', url: link('profile'), icon: profileIcon, isActive: currentRoute === 'profile' };
return { mainNavItems, accountNavItems, profileItem }; return { mainNavItems, accountNavItems, profileItem };
}, [ isMarketplaceFilled, link, currentRoute ]); }, [ isMarketplaceFilled, currentRoute ]);
} }
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;
}
const BASE_PATH = '/[network_type]/[network_sub_type]';
module.exports = BASE_PATH;
export const ACCOUNT_ROUTES: Array<RouteName> = [ 'watchlist', 'private_tags_address', 'private_tags_tx', 'public_tags', 'api_keys', 'custom_abi' ]; export const ACCOUNT_ROUTES: Array<RouteName> = [ 'watchlist', 'private_tags', 'public_tags', 'api_keys', 'custom_abi' ];
import type { RouteName } from 'lib/link/routes'; import type { RouteName } from 'lib/link/routes';
export default function isAccountRoute(route: RouteName) { export default function isAccountRoute(route: RouteName) {
......
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';
const PATH_PARAM_REGEXP = /\/\[(\w+)\]/g; const PATH_PARAM_REGEXP = /\/\[(\w+)\]/g;
export function link(routeName: RouteName, urlParams?: Record<string, Array<string> | string | undefined>, queryParams?: Record<string, string>): string { export default function link(
routeName: RouteName,
urlParams?: Record<string, Array<string> | string | undefined>,
queryParams?: Record<string, string>,
): string {
const route = ROUTES[routeName]; const route = ROUTES[routeName];
if (!route) { if (!route) {
return ''; return '';
} }
const network = findNetwork({ const refinedUrlParams: typeof urlParams = {
network_type: typeof urlParams?.network_type === 'string' ? urlParams?.network_type : '', network_type: appConfig.network.type,
network_sub_type: typeof urlParams?.network_sub_type === 'string' ? urlParams?.network_sub_type : undefined, network_sub_type: appConfig.network.subtype,
}); ...urlParams,
};
const path = route.pattern.replace(PATH_PARAM_REGEXP, (_, paramName: string) => { const path = route.pattern.replace(PATH_PARAM_REGEXP, (_, paramName: string) => {
if (paramName === 'network_sub_type' && !network?.subType) { if (paramName === 'network_sub_type' && !refinedUrlParams.network_sub_type) {
return ''; return '';
} }
let paramValue = urlParams?.[paramName]; let paramValue = refinedUrlParams?.[paramName];
if (Array.isArray(paramValue)) { if (Array.isArray(paramValue)) {
// FIXME we don't have yet params as array, but typescript says that we could // FIXME we don't have yet params as array, but typescript says that we could
// 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);
......
const BASE_PATH = require('./basePath');
const paths = {
network_index: `${ BASE_PATH }`,
watchlist: `${ BASE_PATH }/account/watchlist`,
private_tags: `${ BASE_PATH }/account/tag_address`,
public_tags: `${ BASE_PATH }/account/public_tags_request`,
api_keys: `${ BASE_PATH }/account/api_key`,
custom_abi: `${ BASE_PATH }/account/custom_abi`,
profile: `${ BASE_PATH }/auth/profile`,
txs: `${ BASE_PATH }/txs`,
tx: `${ BASE_PATH }/tx/[id]`,
blocks: `${ BASE_PATH }/blocks`,
block: `${ BASE_PATH }/block/[id]`,
tokens: `${ BASE_PATH }/tokens`,
token_index: `${ BASE_PATH }/token/[hash]`,
token_instance_item: `${ BASE_PATH }/token/[hash]/instance/[id]`,
address_index: `${ BASE_PATH }/address/[id]`,
address_contract_verification: `${ BASE_PATH }/address/[id]/contract_verifications/new`,
apps: `${ BASE_PATH }/apps`,
app_index: `${ BASE_PATH }/apps/[id]`,
search_results: `${ BASE_PATH }/search-results`,
other: `${ BASE_PATH }/search-results`,
// no slash required, it is correct
auth: `${ BASE_PATH }auth/auth0`,
};
module.exports = paths;
import appConfig from 'configs/app/config';
import PATHS from './paths.js';
export interface Route { export interface Route {
pattern: string; pattern: string;
crossNetworkNavigation?: boolean; // route will not change when switching networks crossNetworkNavigation?: boolean; // route will not change when switching networks
...@@ -5,128 +9,95 @@ export interface Route { ...@@ -5,128 +9,95 @@ export interface Route {
export type RouteName = keyof typeof ROUTES; export type RouteName = keyof typeof ROUTES;
const BASE_PATH = '/[network_type]/[network_sub_type]';
export const ROUTES = { export const ROUTES = {
// NETWORK MAIN PAGE // NETWORK MAIN PAGE
network_index: { network_index: {
pattern: `${ BASE_PATH }`, pattern: PATHS.network_index,
crossNetworkNavigation: true, crossNetworkNavigation: true,
}, },
// ACCOUNT // ACCOUNT
watchlist: { watchlist: {
pattern: `${ BASE_PATH }/account/watchlist`, pattern: PATHS.watchlist,
crossNetworkNavigation: true,
}, },
private_tags_address: { private_tags: {
pattern: `${ BASE_PATH }/account/tag_address`, pattern: PATHS.private_tags,
crossNetworkNavigation: true,
},
private_tags_tx: {
pattern: `${ BASE_PATH }/account/tag_transaction`,
crossNetworkNavigation: true,
}, },
public_tags: { public_tags: {
pattern: `${ BASE_PATH }/account/public_tags_request`, pattern: PATHS.public_tags,
crossNetworkNavigation: true,
}, },
api_keys: { api_keys: {
pattern: `${ BASE_PATH }/account/api_key`, pattern: PATHS.api_keys,
crossNetworkNavigation: true,
}, },
custom_abi: { custom_abi: {
pattern: `${ BASE_PATH }/account/custom_abi`, pattern: PATHS.custom_abi,
crossNetworkNavigation: true,
}, },
profile: { profile: {
pattern: `${ BASE_PATH }/auth/profile`, pattern: PATHS.profile,
crossNetworkNavigation: true,
}, },
// TRANSACTIONS // TRANSACTIONS
txs_validated: { txs: {
pattern: `${ BASE_PATH }/txs`, pattern: PATHS.txs,
crossNetworkNavigation: true,
},
txs_pending: {
pattern: `${ BASE_PATH }/pending-transactions`,
crossNetworkNavigation: true, crossNetworkNavigation: true,
}, },
tx_index: { tx: {
pattern: `${ BASE_PATH }/tx/[id]`, pattern: PATHS.tx,
},
tx_internal: {
pattern: `${ BASE_PATH }/tx/[id]/internal-transactions`,
},
tx_logs: {
pattern: `${ BASE_PATH }/tx/[id]/logs`,
},
tx_raw_trace: {
pattern: `${ BASE_PATH }/tx/[id]/raw-trace`,
},
tx_state: {
pattern: `${ BASE_PATH }/tx/[id]/state`,
}, },
// BLOCKS // BLOCKS
blocks: { blocks: {
pattern: `${ BASE_PATH }/blocks`, pattern: PATHS.blocks,
crossNetworkNavigation: true,
},
blocks_uncles: {
pattern: `${ BASE_PATH }/uncles`,
crossNetworkNavigation: true, crossNetworkNavigation: true,
}, },
blocks_reorgs: { block: {
pattern: `${ BASE_PATH }/reorgs`, pattern: PATHS.block,
crossNetworkNavigation: true,
},
block_index: {
pattern: `${ BASE_PATH }/block/[id]`,
},
block_txs: {
pattern: `${ BASE_PATH }/block/[id]/transactions`,
}, },
// TOKENS // TOKENS
tokens: { tokens: {
pattern: `${ BASE_PATH }/tokens`, pattern: PATHS.tokens,
crossNetworkNavigation: true, crossNetworkNavigation: true,
}, },
token_index: { token_index: {
pattern: `${ BASE_PATH }/token/[id]`, pattern: PATHS.token_index,
crossNetworkNavigation: true, crossNetworkNavigation: true,
}, },
token_instance_item: {
pattern: PATHS.token_instance_item,
},
// ADDRESSES // ADDRESSES
address_index: { address_index: {
pattern: `${ BASE_PATH }/address/[id]`, pattern: PATHS.address_index,
crossNetworkNavigation: true,
},
address_contract_verification: {
pattern: PATHS.address_contract_verification,
crossNetworkNavigation: true, crossNetworkNavigation: true,
}, },
// APPS // APPS
apps: { apps: {
pattern: `${ BASE_PATH }/apps`, pattern: PATHS.apps,
}, },
app_index: { app_index: {
pattern: `${ BASE_PATH }/apps/[id]`, pattern: PATHS.app_index,
}, },
// SEARCH // SEARCH
search_results: { search_results: {
pattern: `${ BASE_PATH }/apps`, pattern: PATHS.search_results,
}, },
// ??? what URL will be here // ??? what URL will be here
other: { other: {
pattern: `${ BASE_PATH }/search-results`, pattern: PATHS.other,
}, },
// AUTH // AUTH
auth: { auth: {
// no slash required, it is correct pattern: PATHS.auth,
pattern: `${ BASE_PATH }auth/auth0`,
}, },
}; };
...@@ -142,6 +113,6 @@ function checkRoutes(route: Record<string, Route>) { ...@@ -142,6 +113,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 { useRouter } from 'next/router';
import React from 'react';
import { link } from 'lib/link/link';
type LinkBuilderParams = Parameters<typeof link>;
export default function useLink() {
const router = useRouter();
const networkType = router.query.network_type;
const networkSubType = router.query.network_sub_type;
return React.useCallback((...args: LinkBuilderParams) => {
return link(args[0], { network_type: networkType, network_sub_type: networkSubType, ...args[1] }, args[2]);
}, [ networkType, networkSubType ]);
}
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 link from 'lib/link/link';
import isAccountRoute from 'lib/link/isAccountRoute';
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 };
};
...@@ -5,16 +5,14 @@ import React from 'react'; ...@@ -5,16 +5,14 @@ import React from 'react';
import type { PageParams } from './types'; import type { PageParams } from './types';
import Block from 'ui/pages/Block'; import Block from 'ui/pages/Block';
import type { Props as BlockProps } from 'ui/pages/Block';
import getSeo from './getSeo'; import getSeo from './getSeo';
type Props = { type Props = {
pageParams: PageParams; pageParams: PageParams;
tab: BlockProps['tab'];
} }
const BlockNextPage: NextPage<Props> = ({ pageParams, tab }: Props) => { const BlockNextPage: NextPage<Props> = ({ pageParams }: Props) => {
const { title, description } = getSeo(pageParams); const { title, description } = getSeo(pageParams);
return ( return (
<> <>
...@@ -22,7 +20,7 @@ const BlockNextPage: NextPage<Props> = ({ pageParams, tab }: Props) => { ...@@ -22,7 +20,7 @@ const BlockNextPage: NextPage<Props> = ({ pageParams, tab }: Props) => {
<title>{ title }</title> <title>{ title }</title>
<meta name="description" content={ description }/> <meta name="description" content={ description }/>
</Head> </Head>
<Block tab={ tab }/> <Block/>
</> </>
); );
}; };
......
...@@ -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,26 +2,18 @@ import type { NextPage } from 'next'; ...@@ -2,26 +2,18 @@ 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 type { Props as BlocksProps } from 'ui/pages/Blocks';
import getSeo from './getSeo'; import getSeo from './getSeo';
type Props = { const BlocksNextPage: NextPage = () => {
pageParams: PageParams; const { title } = getSeo();
tab: BlocksProps['tab'];
}
const BlocksNextPage: NextPage<Props> = ({ pageParams, tab }: Props) => {
const { title } = getSeo(pageParams);
return ( return (
<> <>
<Head> <Head>
<title>{ title }</title> <title>{ title }</title>
</Head> </Head>
<Blocks tab={ tab }/> <Blocks/>
</> </>
); );
}; };
......
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' };
}; };
...@@ -4,17 +4,15 @@ import React from 'react'; ...@@ -4,17 +4,15 @@ import React from 'react';
import type { PageParams } from './types'; import type { PageParams } from './types';
import type { Props as TransactionProps } from 'ui/pages/Transaction';
import Transaction from 'ui/pages/Transaction'; import Transaction from 'ui/pages/Transaction';
import getSeo from './getSeo'; import getSeo from './getSeo';
type Props = { type Props = {
pageParams: PageParams; pageParams: PageParams;
tab: TransactionProps['tab'];
} }
const TransactionNextPage: NextPage<Props> = ({ pageParams, tab }: Props) => { const TransactionNextPage: NextPage<Props> = ({ pageParams }: Props) => {
const { title, description } = getSeo(pageParams); const { title, description } = getSeo(pageParams);
return ( return (
<> <>
...@@ -22,7 +20,7 @@ const TransactionNextPage: NextPage<Props> = ({ pageParams, tab }: Props) => { ...@@ -22,7 +20,7 @@ const TransactionNextPage: NextPage<Props> = ({ pageParams, tab }: Props) => {
<title>{ title }</title> <title>{ title }</title>
<meta name="description" content={ description }/> <meta name="description" content={ description }/>
</Head> </Head>
<Transaction tab={ tab }/> <Transaction/>
</> </>
); );
}; };
......
...@@ -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,11 +9,18 @@ ...@@ -9,11 +9,18 @@
}, },
"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 next dev",
"build": "next build", "build": "next build",
"build:vercel": "./node_modules/.bin/dotenv -e ./configs/envs/.env.poa_core -e ./configs/envs/.env.common 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.common --env-file ./configs/envs/.env.poa_core --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-docker": "docker run --rm --network host -v $(pwd):/work/ -w /work/ -it mcr.microsoft.com/playwright:v1.27.0-focal ./run-tests.sh"
}, },
"dependencies": { "dependencies": {
"@chakra-ui/react": "2.3.1", "@chakra-ui/react": "2.3.1",
...@@ -21,6 +28,8 @@ ...@@ -21,6 +28,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",
...@@ -42,10 +51,12 @@ ...@@ -42,10 +51,12 @@
"use-font-face-observer": "^1.2.1" "use-font-face-observer": "^1.2.1"
}, },
"devDependencies": { "devDependencies": {
"@playwright/experimental-ct-react": "^1.26.1",
"@types/node": "17.0.36", "@types/node": "17.0.36",
"@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",
...@@ -53,6 +64,7 @@ ...@@ -53,6 +64,7 @@
"eslint-plugin-regexp": "^1.7.0", "eslint-plugin-regexp": "^1.7.0",
"husky": "^8.0.0", "husky": "^8.0.0",
"lint-staged": ">=10", "lint-staged": ">=10",
"playwright": "^1.26.1",
"svgo": "^2.8.0", "svgo": "^2.8.0",
"typescript": "4.7.2" "typescript": "4.7.2"
}, },
......
...@@ -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,17 +14,17 @@ type Props = { ...@@ -14,17 +14,17 @@ 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>
<PrivateTags tab="private_tags_address"/> <PrivateTags/>
</> </>
); );
}; };
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';
import type { NextPage } from 'next';
import Head from 'next/head';
import React from 'react';
import getNetworkTitle from 'lib/networks/getNetworkTitle';
import PrivateTags from 'ui/pages/PrivateTags';
type PageParams = {
network_type: string;
network_sub_type: string;
}
type Props = {
pageParams: PageParams;
}
const TransactionTagsPage: NextPage<Props> = ({ pageParams }: Props) => {
const title = getNetworkTitle(pageParams || {});
return (
<>
<Head><title>{ title }</title></Head>
<PrivateTags tab="private_tags_tx"/>
</>
);
};
export default TransactionTagsPage;
export { getStaticPaths } from 'lib/next/account/getStaticPaths';
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';
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment