Merge upstream blockscout/frontend (29 commits)
Sync with upstream blockscout frontend, keeping Lux Network branding in footer and documentation links. Added llms.txt link from upstream.
@@ -0,0 +1,53 @@
|
||||
# Bugbot PR Review Rules
|
||||
|
||||
In addition to the regular bug-finding process, check the following aspects in the code changes introduced by the Pull Request.
|
||||
|
||||
## 1. Design System & Theming
|
||||
|
||||
- Verify that all color values use **semantic color tokens** from the project's design system (`text.secondary`, `border.divider`, `icon.secondary`, `button.solid.bg`, `global.body.bg`, etc.). Flag any hardcoded color values such as `rgb(...)`, `#hex`. The full list of available tokens can be found in `toolkit/theme/foundations/semanticTokens.ts` and `toolkit/theme/foundations/colors.ts`. In rare cases the author may use hardcoded color values, but they must leave a comment explaining why.
|
||||
- Flag any custom `box-shadow` values. Advise the author to use the design system's shadow tokens instead.
|
||||
- Check for custom margins or paddings that override defaults of **internal parts** of compound components (e.g., `DialogHeader` inside a `Dialog`). This rule does not apply to the root component itself. If a part component already has default spacing from the design system, it should not be overridden with custom values. This applies to all components from the toolkit (see `toolkit/chakra`).
|
||||
- Look for duplicated style props — check whether a property is already set by an inherited style prop and flag any redundant re-declarations.
|
||||
- Refer to the Chakra documentation at [llms.txt](https://chakra-ui.com/llms.txt) to understand what components Chakra UI provides and how the styling system works.
|
||||
|
||||
## 2. TypeScript & Code Quality
|
||||
|
||||
- Check for usage of the `any` type. If found, advise the author to use `unknown` instead and narrow it properly in the code.
|
||||
- Look for `eslint-disable` comments. If any exist without an accompanying explanation, ask the author to add a comment describing why the rule was disabled. Example: `// eslint-disable-next-line @typescript-eslint/no-explicit-any -- API response shape is dynamic and validated at runtime`.
|
||||
- Identify magic numbers in the code. Advise the author to extract them into named constants using `UPPER_UNDERSCORE_CASE` and place the constants above the component definition. For example, `const MAX_VISIBLE_ITEMS = 4;` is preferable to an inline `4`.
|
||||
- Evaluate naming of hooks, features, components, functions, and variables. Names should be specific and self-documenting. Flag vague names like `useWidgets` and suggest more descriptive alternatives such as `useAddress3rdPartyWidgets`.
|
||||
- Where an `as MyType[]` assertion is used, suggest using `satisfies Array<MyType>` instead, if applicable.
|
||||
- Check for inline empty arrays or objects used as default values on every render (e.g., `?? []`). Advise the author to define a static constant outside the component (e.g., `const EMPTY_ARRAY: Array<T> = [];`) and reference it instead.
|
||||
- Check whether `.filter()`, `.map()`, or `.reduce()` calls produce new array references that are passed as props or used as hook dependencies. If so, advise the author to wrap them in `useMemo`.
|
||||
- Flag unnecessary string interpolation in file paths. Explicit file names (e.g., `streak_30.png`) are preferable to template literals (e.g., `` `streak_${days}.png` ``), as they are easier to locate with search tools.
|
||||
|
||||
## 3. Environment Variables
|
||||
|
||||
When a PR adds, changes, renames, or removes an environment variable, verify that **all** of the following steps have been completed:
|
||||
|
||||
- The variable is documented in `docs/ENVS.md` with its name, expected type, compulsoriness, default value, and an example value.
|
||||
- The variable is added to the app config in the appropriate section of `configs/app/` (`features/`, `ui.ts`, `api.ts`, etc.).
|
||||
- A validation schema is added or updated in `deploy/tools/envs-validator/schema.ts`. For complex configs, a **separate sub-schema** should be created (similar to `tacScheme` or `beaconChainSchema`).
|
||||
- The variable is added to the test presets in `deploy/tools/envs-validator/test/.env.base`. If the variable supports alternative configurations, examples should also be added to `.env.alt`.
|
||||
- If the variable contains an external URL that is **not** an asset URL (i.e., not an image or JSON file), the appropriate CSP policy in `nextjs/csp/policies/` is updated. Policies should only be added when the relevant config option is actually enabled.
|
||||
- If the variable links to an external resource (image or JSON config), the `ASSETS_ENVS` array in `deploy/scripts/download_assets.sh` is extended with the variable name.
|
||||
- If the variable stores a JSON config URL, an example file is added to `deploy/tools/envs-validator/test/assets/configs/` and the `envsWithJsonConfig` array in `deploy/tools/envs-validator/index.ts` is extended.
|
||||
- The PR description clearly states that a new ENV variable was added and includes example values.
|
||||
|
||||
## 4. Component Architecture & Reuse
|
||||
|
||||
- When links to **application pages** are constructed, verify that `nextjs-routes` or `nextjs/routes` utilities are used instead of string concatenation or template literals. The full list of application routes is available in `nextjs/nextjs-routes.d.ts`.
|
||||
|
||||
## 5. Testing with Playwright
|
||||
|
||||
- Flag any tests that rely on CSS class selectors (`.className`). These will break when the implementation changes. Advise the author to use roles, test IDs, text content, or other semantic selectors instead.
|
||||
- Flag unnecessary use of the `testFn` pattern (i.e., `const testFn: TestFixture<...> = async (...)`). This pattern makes refactoring harder and can bypass Playwright lint rules. It should only be used when genuinely sharing test logic across multiple test suites.
|
||||
- Check for unexplained magic values in test data. If a test uses a specific number or string, its meaning should be clear. Advise the author to use named constants (e.g., `const BLOCK_HEIGHT = 3947;` instead of a bare `3947`).
|
||||
- Check for hardcoded URLs or values that already exist in mock files. Advise the author to import and reference them from the existing mocks to keep tests DRY and consistent.
|
||||
|
||||
## 6. Utilities & Best Practices
|
||||
|
||||
- Check whether any utility logic could be replaced by an `es-toolkit` function (e.g., `clamp`, `get`). Advise the author to prefer existing utilities over manual implementations.
|
||||
- Flag unsafe type coercions such as `as unknown as MyType`. If coercion is necessary, the author should add runtime validation or a comment explaining why the cast is safe.
|
||||
- Check that date and time formatting uses the shared project utilities (`Time` or `TimeWithTooltip` components). The project maintains a single consistent date format.
|
||||
- Flag any commented-out code blocks. Advise the author to either implement the feature or remove the dead code entirely. TODOs are acceptable only when there is a clear follow-up plan.
|
||||
@@ -21,7 +21,7 @@ _Note_ in the command output, format all URLs as clickable Markdown links: `[Lin
|
||||
- If found, fetch the issue details using `gh issue view [issue_number]`
|
||||
- Include "Resolves #[issue_number]" at the very beginning of the description (in the "Description and Related Issue(s)" section)
|
||||
- Summarize the changes clearly and concisely, using no more than two paragraphs. If necessary, use bullet points to highlight the main changes in the codebase. Be precise, this description should not be very long.
|
||||
- List any changes in the enviroment variables (look at the `./docs/ENVS.md` file) in a separate section, describe purpose of each variable change
|
||||
- List any changes in the environment variables (look at the `./docs/ENVS.md` file) in a separate section, describe purpose of each variable change
|
||||
- Bad example: "Added `NEXT_PUBLIC_VIEWS_TX_GROUPED_FEES` environment variable to the documentation"
|
||||
- Good example: "Added `NEXT_PUBLIC_VIEWS_TX_GROUPED_FEES` to group transaction fees into one section on the transaction page"
|
||||
- Good example: "Extended possible values for `NEXT_PUBLIC_VIEWS_TX_ADDITIONAL_FIELDS` with set_max_gas_limit to display the maximum gas price set by the transaction sender"
|
||||
|
||||
@@ -183,7 +183,7 @@ type FetchingState<TData> =
|
||||
|
||||
Do not introduce new enums into the codebase. Retain existing enums.
|
||||
|
||||
If you require enum-like behaviour, use an `as const` object:
|
||||
If you require enum-like behavior, use an `as const` object:
|
||||
|
||||
```ts
|
||||
const backendToFrontendEnum = {
|
||||
@@ -267,7 +267,7 @@ interface C extends A, B {
|
||||
|
||||
Use JSDoc comments to annotate functions and types.
|
||||
|
||||
Be concise in JSDoc comments, and only provide JSDoc comments if the function's behaviour is not self-evident.
|
||||
Be concise in JSDoc comments, and only provide JSDoc comments if the function's behavior is not self-evident.
|
||||
|
||||
Use the JSDoc inline `@link` tag to link to other functions and types within the same file.
|
||||
|
||||
|
||||
@@ -10,4 +10,5 @@ README.md
|
||||
*.tsbuildinfo
|
||||
.eslintcache
|
||||
/test-results/
|
||||
/playwright-report/
|
||||
/playwright-report/
|
||||
/blob-report/
|
||||
@@ -13,7 +13,7 @@ on:
|
||||
- 'public/**'
|
||||
- 'stub/**'
|
||||
- 'tools/**'
|
||||
|
||||
|
||||
# concurrency:
|
||||
# group: ${{ github.workflow }}__${{ github.job }}__${{ github.ref }}
|
||||
# cancel-in-progress: true
|
||||
@@ -44,7 +44,7 @@ jobs:
|
||||
path: |
|
||||
node_modules
|
||||
key: node_modules-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
|
||||
|
||||
|
||||
- name: Install dependencies
|
||||
if: steps.cache-node-modules.outputs.cache-hit != 'true'
|
||||
run: yarn --frozen-lockfile
|
||||
@@ -59,6 +59,12 @@ jobs:
|
||||
- name: Compile TypeScript
|
||||
run: yarn lint:tsc
|
||||
|
||||
- name: Check licenses
|
||||
run: yarn lint:license:check
|
||||
|
||||
- name: Check spelling
|
||||
run: yarn lint:cspell --no-progress
|
||||
|
||||
toolkit_build_check:
|
||||
name: Toolkit build check
|
||||
needs: [ code_quality ]
|
||||
@@ -80,7 +86,7 @@ jobs:
|
||||
path: |
|
||||
node_modules
|
||||
key: node_modules-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
|
||||
|
||||
|
||||
- name: Install project dependencies
|
||||
if: steps.cache-node-modules.outputs.cache-hit != 'true'
|
||||
run: yarn --frozen-lockfile
|
||||
@@ -111,12 +117,12 @@ jobs:
|
||||
echo "Build failed: dist directory not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
if [ ! -f "dist/index.js" ]; then
|
||||
echo "Build failed: dist/index.js not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
if [ ! -f "dist/index.d.ts" ]; then
|
||||
echo "Build failed: dist/index.d.ts not found"
|
||||
exit 1
|
||||
@@ -143,7 +149,7 @@ jobs:
|
||||
path: |
|
||||
node_modules
|
||||
key: node_modules-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
|
||||
|
||||
|
||||
- name: Install dependencies
|
||||
if: steps.cache-node-modules.outputs.cache-hit != 'true'
|
||||
run: yarn --frozen-lockfile
|
||||
@@ -186,7 +192,7 @@ jobs:
|
||||
path: |
|
||||
node_modules
|
||||
key: node_modules-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
|
||||
|
||||
|
||||
- name: Install dependencies
|
||||
if: steps.cache-node-modules.outputs.cache-hit != 'true'
|
||||
run: yarn --frozen-lockfile
|
||||
@@ -222,7 +228,7 @@ jobs:
|
||||
path: |
|
||||
node_modules
|
||||
key: node_modules-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
|
||||
|
||||
|
||||
- name: Install dependencies
|
||||
if: steps.cache-node-modules.outputs.cache-hit != 'true'
|
||||
run: yarn --frozen-lockfile
|
||||
@@ -231,14 +237,14 @@ jobs:
|
||||
if: steps.cache-node-modules.outputs.cache-hit == 'true'
|
||||
run: yarn chakra:typegen
|
||||
|
||||
- name: Install script dependencies
|
||||
- name: Install script dependencies
|
||||
run: cd ./deploy/tools/affected-tests && yarn --frozen-lockfile
|
||||
|
||||
|
||||
- name: Run script
|
||||
run: yarn test:pw:detect-affected
|
||||
|
||||
- name: Upload result file
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: playwright-affected-tests
|
||||
path: ./playwright/affected-tests.txt
|
||||
@@ -249,9 +255,9 @@ jobs:
|
||||
needs: [ code_quality, envs_validation, pw_affected_tests ]
|
||||
if: |
|
||||
always() &&
|
||||
needs.code_quality.result == 'success' &&
|
||||
needs.envs_validation.result == 'success' &&
|
||||
(needs.pw_affected_tests.result == 'success' || needs.pw_affected_tests.result == 'skipped')
|
||||
needs.code_quality.result == 'success' &&
|
||||
needs.envs_validation.result == 'success' &&
|
||||
(needs.pw_affected_tests.result == 'success' || needs.pw_affected_tests.result == 'skipped')
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: mcr.microsoft.com/playwright:v1.57.0-noble
|
||||
@@ -283,7 +289,7 @@ jobs:
|
||||
path: |
|
||||
node_modules
|
||||
key: node_modules-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
|
||||
|
||||
|
||||
- name: Install dependencies
|
||||
if: steps.cache-node-modules.outputs.cache-hit != 'true'
|
||||
run: yarn --frozen-lockfile
|
||||
@@ -294,7 +300,7 @@ jobs:
|
||||
|
||||
- name: Download affected tests list
|
||||
if: ${{ needs.pw_affected_tests.result == 'success' }}
|
||||
uses: actions/download-artifact@v4
|
||||
uses: actions/download-artifact@v5
|
||||
continue-on-error: true
|
||||
with:
|
||||
name: playwright-affected-tests
|
||||
@@ -306,10 +312,99 @@ jobs:
|
||||
HOME: /root
|
||||
PW_PROJECT: ${{ matrix.project }}
|
||||
|
||||
- name: Upload test results
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
- name: Upload blob report to GitHub Actions Artifacts
|
||||
if: ${{ !cancelled() }}
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: playwright-report-${{ matrix.project }}
|
||||
name: blob-report-${{ matrix.project }}
|
||||
path: blob-report
|
||||
retention-days: 1
|
||||
|
||||
pw_report:
|
||||
name: Generate Playwright report
|
||||
# Merge reports after pw_tests job, even if some shards have failed
|
||||
if: ${{ !cancelled() }}
|
||||
runs-on: ubuntu-latest
|
||||
needs: [ pw_tests ]
|
||||
steps:
|
||||
- name: Download blob reports from GitHub Actions Artifacts
|
||||
id: download-reports
|
||||
uses: actions/download-artifact@v5
|
||||
continue-on-error: true
|
||||
with:
|
||||
path: ${{ runner.temp }}/all-blob-reports
|
||||
pattern: blob-report-*
|
||||
merge-multiple: true
|
||||
|
||||
- name: Check for downloaded reports
|
||||
id: check-reports
|
||||
run: |
|
||||
if [ -d "$RUNNER_TEMP/all-blob-reports" ] && [ -n "$(ls -A "$RUNNER_TEMP/all-blob-reports" 2>/dev/null)" ]; then
|
||||
echo "has_reports=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "has_reports=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Checkout repo
|
||||
if: steps.check-reports.outputs.has_reports == 'true'
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup node
|
||||
if: steps.check-reports.outputs.has_reports == 'true'
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22.14.0
|
||||
cache: 'yarn'
|
||||
|
||||
- name: Cache node_modules
|
||||
if: steps.check-reports.outputs.has_reports == 'true'
|
||||
uses: actions/cache@v4
|
||||
id: cache-node-modules
|
||||
with:
|
||||
path: |
|
||||
node_modules
|
||||
key: node_modules-${{ runner.os }}-${{ hashFiles('yarn.lock') }}
|
||||
|
||||
- name: Install dependencies
|
||||
if: steps.check-reports.outputs.has_reports == 'true' && steps.cache-node-modules.outputs.cache-hit != 'true'
|
||||
run: yarn --frozen-lockfile
|
||||
|
||||
- name: Merge into HTML Report
|
||||
if: steps.check-reports.outputs.has_reports == 'true'
|
||||
run: npx playwright merge-reports --reporter html "$RUNNER_TEMP/all-blob-reports"
|
||||
|
||||
- name: Upload HTML report artifact
|
||||
# Upload HTML report to GitHub Actions Artifacts only for pull requests from forks
|
||||
if: steps.check-reports.outputs.has_reports == 'true' && (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository)
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: playwright-html-report
|
||||
path: playwright-report
|
||||
retention-days: 10
|
||||
retention-days: 7
|
||||
|
||||
- name: Configure AWS credentials
|
||||
if: steps.check-reports.outputs.has_reports == 'true' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository)
|
||||
uses: aws-actions/configure-aws-credentials@v1
|
||||
with:
|
||||
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
aws-region: ${{ vars.AWS_REGION }}
|
||||
|
||||
- name: Upload HTML report to S3
|
||||
# Upload HTML report to S3 only for pull requests from the main repository or for non-pull requests events
|
||||
if: steps.check-reports.outputs.has_reports == 'true' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository)
|
||||
run: |
|
||||
export "SHORT_SHA=`echo ${GITHUB_SHA} | cut -c1-8`"
|
||||
aws s3 cp playwright-report/ s3://${{ vars.AWS_BUCKET_NAME }}/report-${SHORT_SHA} --recursive
|
||||
echo "PLAYWRIGHT_URL=https://${{ vars.AWS_BUCKET_NAME }}.s3.${{ vars.AWS_REGION }}.amazonaws.com/report-${SHORT_SHA}/index.html" >> "$GITHUB_ENV"
|
||||
|
||||
- name: Add link to job summary
|
||||
if: steps.check-reports.outputs.has_reports == 'true'
|
||||
run: |
|
||||
echo "### Playwright report" >> $GITHUB_STEP_SUMMARY
|
||||
if [ -n "${{ env.PLAYWRIGHT_URL }}" ]; then
|
||||
echo "[View report (S3)](${{ env.PLAYWRIGHT_URL }})" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo "Download the **playwright-html-report** artifact below to view the report (open \`index.html\` in a browser)." >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
echo "[Read about ducks](https://animalkingdom.org/animals/ducks/)" >> $GITHUB_STEP_SUMMARY
|
||||
@@ -1,19 +1,20 @@
|
||||
name: Deploy from main branch
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths-ignore:
|
||||
- '.github/ISSUE_TEMPLATE/**'
|
||||
- '.husky/**'
|
||||
- '.vscode/**'
|
||||
- 'docs/**'
|
||||
- 'vitest/**'
|
||||
- 'mocks/**'
|
||||
- 'playwright/**'
|
||||
- 'stubs/**'
|
||||
- 'tools/**'
|
||||
# NOTE: Currently disabled to avoid unnecessary deployments since we only publish private images and do not use unstable builds in our internal pipelines.
|
||||
# push:
|
||||
# branches:
|
||||
# - main
|
||||
# paths-ignore:
|
||||
# - '.github/ISSUE_TEMPLATE/**'
|
||||
# - '.husky/**'
|
||||
# - '.vscode/**'
|
||||
# - 'docs/**'
|
||||
# - 'vitest/**'
|
||||
# - 'mocks/**'
|
||||
# - 'playwright/**'
|
||||
# - 'stubs/**'
|
||||
# - 'tools/**'
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
|
||||
@@ -24,6 +24,7 @@ on:
|
||||
- immutable
|
||||
- mega_eth
|
||||
- neon_devnet
|
||||
- numine
|
||||
- optimism
|
||||
- optimism_sepolia
|
||||
- optimism_superchain
|
||||
|
||||
@@ -26,6 +26,7 @@ on:
|
||||
- mega_eth
|
||||
- mekong
|
||||
- neon_devnet
|
||||
- numine
|
||||
- optimism
|
||||
- optimism_sepolia
|
||||
- optimism_superchain
|
||||
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
with:
|
||||
project_name: ${{ vars.PROJECT_NAME }}
|
||||
field_name: Status
|
||||
field_value: Ready For Realease
|
||||
field_value: Ready For Release
|
||||
issues: "[${{ github.event.issue.number }}]"
|
||||
secrets: inherit
|
||||
permissions:
|
||||
|
||||
@@ -55,6 +55,7 @@ yarn-error.log*
|
||||
**.decrypted~**
|
||||
/test-results/
|
||||
/playwright-report/
|
||||
/blob-report/
|
||||
/playwright/.cache/
|
||||
/playwright/.browser/
|
||||
/playwright/envs.js
|
||||
@@ -65,6 +66,7 @@ yarn-error.log*
|
||||
# build outputs
|
||||
/tools/preset-sync/index.js
|
||||
/toolkit/package/dist
|
||||
license.json
|
||||
|
||||
AGENTS.md
|
||||
CLAUDE.md
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
echo 🧿 Running file linter...
|
||||
npx lint-staged
|
||||
|
||||
echo 🧿 Running spelling checker...
|
||||
git diff --diff-filter=ACMRT --cached --name-only | npx cspell --config cspell.jsonc --file-list stdin --no-must-find-files
|
||||
|
||||
# format svg
|
||||
echo 🧿 Running svg formatter...
|
||||
for file in `git diff --diff-filter=ACMRT --cached --name-only | grep ".svg\$"`
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"exclude": [
|
||||
// It currently has two licenses (MIT and Apache-2.0), which are not correctly recognized by license-report-check.
|
||||
"@helia/verified-fetch"
|
||||
]
|
||||
}
|
||||
@@ -30,7 +30,7 @@
|
||||
"command": "yarn dev:preset:sync --name=${input:dev_config_preset}",
|
||||
"problemMatcher": [],
|
||||
"label": "dev preset sync",
|
||||
"detail": "syncronize dev preset",
|
||||
"detail": "synchronize dev preset",
|
||||
"presentation": {
|
||||
"reveal": "always",
|
||||
"panel": "shared",
|
||||
@@ -355,6 +355,7 @@
|
||||
"mega_eth",
|
||||
"mekong",
|
||||
"neon_devnet",
|
||||
"numine",
|
||||
"optimism",
|
||||
"optimism_interop_0",
|
||||
"optimism_sepolia",
|
||||
|
||||
@@ -98,7 +98,7 @@ RUN set -a && \
|
||||
# ENV NEXT_TELEMETRY_DISABLED 1
|
||||
|
||||
# Build app for production
|
||||
ENV NODE_OPTIONS="--max-old-space-size=4096"
|
||||
ENV NODE_OPTIONS="--max-old-space-size=8192"
|
||||
RUN yarn build
|
||||
|
||||
|
||||
|
||||
@@ -83,6 +83,17 @@ const contractInfoApi = (() => {
|
||||
});
|
||||
})();
|
||||
|
||||
const interchainIndexerApi = (() => {
|
||||
const apiHost = getEnvValue('NEXT_PUBLIC_INTERCHAIN_INDEXER_API_HOST');
|
||||
if (!apiHost) {
|
||||
return;
|
||||
}
|
||||
|
||||
return Object.freeze({
|
||||
endpoint: apiHost,
|
||||
});
|
||||
})();
|
||||
|
||||
const metadataApi = (() => {
|
||||
const apiHost = getEnvValue('NEXT_PUBLIC_METADATA_SERVICE_API_HOST');
|
||||
if (!apiHost) {
|
||||
@@ -221,6 +232,7 @@ const apis: Apis = Object.freeze({
|
||||
bens: bensApi,
|
||||
clusters: clustersApi,
|
||||
contractInfo: contractInfoApi,
|
||||
interchainIndexer: interchainIndexerApi,
|
||||
metadata: metadataApi,
|
||||
multichainAggregator: multichainAggregatorApi,
|
||||
multichainStats: multichainStatsApi,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { Feature } from './types';
|
||||
import type { AuthProvider } from 'types/client/account';
|
||||
|
||||
import app from '../app';
|
||||
import services from '../services';
|
||||
@@ -6,18 +7,39 @@ import { getEnvValue } from '../utils';
|
||||
|
||||
const title = 'My account';
|
||||
|
||||
const config: Feature<{ isEnabled: true; recaptchaSiteKey: string }> = (() => {
|
||||
const config: Feature<{
|
||||
isEnabled: true;
|
||||
authProvider: AuthProvider;
|
||||
dynamic?: {
|
||||
environmentId: string;
|
||||
};
|
||||
}> = (() => {
|
||||
|
||||
if (
|
||||
!app.isPrivateMode &&
|
||||
getEnvValue('NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED') === 'true' &&
|
||||
services.reCaptchaV2.siteKey
|
||||
getEnvValue('NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED') === 'true'
|
||||
) {
|
||||
return Object.freeze({
|
||||
title,
|
||||
isEnabled: true,
|
||||
recaptchaSiteKey: services.reCaptchaV2.siteKey,
|
||||
});
|
||||
const authProvider = getEnvValue('NEXT_PUBLIC_ACCOUNT_AUTH_PROVIDER');
|
||||
const dynamicEnvironmentId = getEnvValue('NEXT_PUBLIC_ACCOUNT_DYNAMIC_ENVIRONMENT_ID');
|
||||
|
||||
if (authProvider === 'dynamic' && dynamicEnvironmentId) {
|
||||
return Object.freeze({
|
||||
title,
|
||||
isEnabled: true,
|
||||
authProvider: 'dynamic',
|
||||
dynamic: {
|
||||
environmentId: dynamicEnvironmentId,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (services.reCaptchaV2.siteKey) {
|
||||
return Object.freeze({
|
||||
title,
|
||||
isEnabled: true,
|
||||
authProvider: 'auth0',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return Object.freeze({
|
||||
|
||||
@@ -3,13 +3,22 @@ import type { Feature } from './types';
|
||||
import app from '../app';
|
||||
import chain from '../chain';
|
||||
import { getEnvValue, parseEnvJson } from '../utils';
|
||||
import accountFeature from './account';
|
||||
import opSuperchain from './opSuperchain';
|
||||
|
||||
const walletConnectProjectId = getEnvValue('NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID');
|
||||
|
||||
const title = 'Blockchain interaction (writing to contract, etc.)';
|
||||
|
||||
const config: Feature<{ walletConnect: { projectId: string; featuredWalletIds: Array<string> } }> = (() => {
|
||||
type FeaturePayload = {
|
||||
connectorType: 'reown';
|
||||
reown: { projectId: string; featuredWalletIds: Array<string> };
|
||||
} | {
|
||||
connectorType: 'dynamic';
|
||||
dynamic: { environmentId: string };
|
||||
};
|
||||
|
||||
const config: Feature<FeaturePayload> = (() => {
|
||||
|
||||
// all chain parameters are required for wagmi provider
|
||||
// @wagmi/chains/dist/index.d.ts
|
||||
@@ -26,17 +35,28 @@ const config: Feature<{ walletConnect: { projectId: string; featuredWalletIds: A
|
||||
|
||||
if (
|
||||
!app.isPrivateMode &&
|
||||
(isSingleChain || isOpSuperchain) &&
|
||||
walletConnectProjectId
|
||||
(isSingleChain || isOpSuperchain)
|
||||
) {
|
||||
return Object.freeze({
|
||||
title,
|
||||
isEnabled: true,
|
||||
walletConnect: {
|
||||
projectId: walletConnectProjectId,
|
||||
featuredWalletIds: parseEnvJson<Array<string>>(getEnvValue('NEXT_PUBLIC_WALLET_CONNECT_FEATURED_WALLET_IDS')) ?? [],
|
||||
},
|
||||
});
|
||||
if (accountFeature.isEnabled && accountFeature.authProvider === 'dynamic' && accountFeature.dynamic?.environmentId) {
|
||||
return Object.freeze({
|
||||
title,
|
||||
isEnabled: true,
|
||||
connectorType: 'dynamic',
|
||||
dynamic: {
|
||||
environmentId: accountFeature.dynamic.environmentId,
|
||||
},
|
||||
});
|
||||
} else if (walletConnectProjectId) {
|
||||
return Object.freeze({
|
||||
title,
|
||||
isEnabled: true,
|
||||
connectorType: 'reown',
|
||||
reown: {
|
||||
projectId: walletConnectProjectId,
|
||||
featuredWalletIds: parseEnvJson<Array<string>>(getEnvValue('NEXT_PUBLIC_WALLET_CONNECT_FEATURED_WALLET_IDS')) ?? [],
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return Object.freeze({
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import type { Feature } from './types';
|
||||
|
||||
import apis from '../apis';
|
||||
import { getEnvValue } from '../utils';
|
||||
|
||||
const title = 'Cross-chain transactions';
|
||||
|
||||
const config: Feature<{}> = (() => {
|
||||
if (getEnvValue('NEXT_PUBLIC_CROSS_CHAIN_TXS_ENABLED') === 'true' && apis.interchainIndexer) {
|
||||
return Object.freeze({
|
||||
title,
|
||||
isEnabled: true,
|
||||
});
|
||||
}
|
||||
|
||||
return Object.freeze({
|
||||
title,
|
||||
isEnabled: false,
|
||||
});
|
||||
})();
|
||||
|
||||
export default config;
|
||||
@@ -10,6 +10,7 @@ export { default as beaconChain } from './beaconChain';
|
||||
export { default as bridgedTokens } from './bridgedTokens';
|
||||
export { default as blockchainInteraction } from './blockchainInteraction';
|
||||
export { default as celo } from './celo';
|
||||
export { default as crossChainTxs } from './crossChainTxs';
|
||||
export { default as csvExport } from './csvExport';
|
||||
export { default as dataAvailability } from './dataAvailability';
|
||||
export { default as deFiDropdown } from './deFiDropdown';
|
||||
|
||||
@@ -8,7 +8,8 @@ import blockchainInteraction from './blockchainInteraction';
|
||||
const title = 'Rewards service integration';
|
||||
|
||||
const config: Feature<{}> = (() => {
|
||||
if (!app.isPrivateMode && apis.rewards && account.isEnabled && blockchainInteraction.isEnabled) {
|
||||
// @0xdeval: as of now, we won't support rewards programs with dynamic auth provider
|
||||
if (!app.isPrivateMode && apis.rewards && account.isEnabled && account.authProvider === 'auth0' && blockchainInteraction.isEnabled) {
|
||||
return Object.freeze({
|
||||
title,
|
||||
isEnabled: true,
|
||||
|
||||
@@ -85,6 +85,9 @@ const UI = Object.freeze({
|
||||
maintenanceAlert: {
|
||||
message: getEnvValue('NEXT_PUBLIC_MAINTENANCE_ALERT_MESSAGE'),
|
||||
},
|
||||
apiKeysAlert: {
|
||||
message: getEnvValue('NEXT_PUBLIC_API_KEYS_ALERT_MESSAGE'),
|
||||
},
|
||||
explorers: {
|
||||
items: parseEnvJson<Array<NetworkExplorer>>(getEnvValue('NEXT_PUBLIC_NETWORK_EXPLORERS')) || [],
|
||||
},
|
||||
|
||||
@@ -76,3 +76,4 @@ NEXT_PUBLIC_VIEWS_NFT_MARKETPLACES=[{'name':'OpenSea','collection_url':'https://
|
||||
NEXT_PUBLIC_VIEWS_TOKEN_SCAM_TOGGLE_ENABLED=true
|
||||
NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com
|
||||
NEXT_PUBLIC_XSTAR_SCORE_URL=https://docs.xname.app/the-solution-adaptive-proof-of-humanity-on-blockchain/xhs-scoring-algorithm?utm_source=blockscout&utm_medium=address
|
||||
NEXT_PUBLIC_API_KEYS_ALERT_MESSAGE='<strong>Chain-specific API keys are being deprecated.</strong> Please migrate to <a href="https://docs.blockscout.com/for-developers/api-keys/pro-api" target="_blank" rel="noopener noreferrer">Blockscout's PRO API</a> for new multichain access. Existing API keys will become invalid on 1st of Jan 2027'
|
||||
@@ -10,6 +10,7 @@ NEXT_PUBLIC_APP_ENV=development
|
||||
NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL=ws
|
||||
|
||||
NEXT_PUBLIC_HOMEPAGE_HIGHLIGHTS_CONFIG=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/homepage-highlights/test.json
|
||||
# NEXT_PUBLIC_ACCOUNT_AUTH_PROVIDER=dynamic
|
||||
|
||||
# Instance ENVs
|
||||
NEXT_PUBLIC_AD_BANNER_ENABLE_SPECIFY=true
|
||||
@@ -64,7 +65,7 @@ NEXT_PUBLIC_NETWORK_ID=11155111
|
||||
NEXT_PUBLIC_NETWORK_LOGO=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/sepolia.svg
|
||||
NEXT_PUBLIC_NETWORK_LOGO_DARK=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/network-logos/sepolia.svg
|
||||
NEXT_PUBLIC_NETWORK_NAME=Sepolia
|
||||
NEXT_PUBLIC_NETWORK_RPC_URL=https://sepolia.drpc.org
|
||||
NEXT_PUBLIC_NETWORK_RPC_URL=https://ethereum-sepolia-rpc.publicnode.com
|
||||
NEXT_PUBLIC_NETWORK_VERIFICATION_TYPE=validation
|
||||
NEXT_PUBLIC_OG_ENHANCED_DATA_ENABLED=true
|
||||
NEXT_PUBLIC_OG_IMAGE_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/og-images/sepolia-testnet.png
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
# Set of ENVs for Numine network explorer
|
||||
# https://numine.blockscout.com
|
||||
# This is an auto-generated file. To update all values, run "yarn dev:preset:sync --name=numine"
|
||||
|
||||
# Local ENVs
|
||||
NEXT_PUBLIC_APP_PROTOCOL=http
|
||||
NEXT_PUBLIC_APP_HOST=localhost
|
||||
NEXT_PUBLIC_APP_PORT=3000
|
||||
NEXT_PUBLIC_APP_ENV=development
|
||||
NEXT_PUBLIC_API_WEBSOCKET_PROTOCOL=ws
|
||||
|
||||
NEXT_PUBLIC_CROSS_CHAIN_TXS_ENABLED=true
|
||||
NEXT_PUBLIC_INTERCHAIN_INDEXER_API_HOST=https://interchain-indexer.k8s-dev.blockscout.com
|
||||
|
||||
# Instance ENVs
|
||||
NEXT_PUBLIC_AD_BANNER_ENABLE_SPECIFY=true
|
||||
NEXT_PUBLIC_ADDRESS_3RD_PARTY_WIDGETS=['talentprotocol','drops','blockscoutbadges','gitpoap','efp','etherscore','webacy','humanpassport','trustblock','smartmuv','humanode']
|
||||
NEXT_PUBLIC_ADDRESS_3RD_PARTY_WIDGETS_CONFIG_URL=https://raw.githubusercontent.com/blockscout/frontend-configs/main/configs/widgets/config.json
|
||||
NEXT_PUBLIC_ADMIN_SERVICE_API_HOST=https://admin-rs.services.blockscout.com
|
||||
NEXT_PUBLIC_API_BASE_PATH=/
|
||||
NEXT_PUBLIC_API_HOST=numine.blockscout.com
|
||||
NEXT_PUBLIC_API_SPEC_URL=https://raw.githubusercontent.com/blockscout/blockscout-api-v2-swagger/main/swagger.yaml
|
||||
NEXT_PUBLIC_CONTRACT_INFO_API_HOST=https://contracts-info.services.blockscout.com
|
||||
NEXT_PUBLIC_GAME_BADGE_CLAIM_LINK=https://badges.blockscout.com/mint/sherblockHolmesBadge
|
||||
NEXT_PUBLIC_HOMEPAGE_CHARTS=['daily_txs']
|
||||
NEXT_PUBLIC_METADATA_SERVICE_API_HOST=https://metadata.services.blockscout.com
|
||||
NEXT_PUBLIC_MIXPANEL_CONFIG_OVERRIDES={"record_sessions_percent": 0.5,"record_heatmap_data": true}
|
||||
NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS=18
|
||||
NEXT_PUBLIC_NETWORK_CURRENCY_NAME=NUMINE
|
||||
NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL=NUMINE
|
||||
NEXT_PUBLIC_NETWORK_ID=8021
|
||||
NEXT_PUBLIC_NETWORK_NAME=Numine
|
||||
NEXT_PUBLIC_NETWORK_RPC_URL=https://subnets.avax.network/numi/mainnet/rpc
|
||||
NEXT_PUBLIC_OG_ENHANCED_DATA_ENABLED=true
|
||||
NEXT_PUBLIC_PUZZLE_GAME_BADGE_CLAIM_LINK=https://badges.blockscout.com/mint/capyPuzzleBadge
|
||||
NEXT_PUBLIC_STATS_API_BASE_PATH=/stats-service
|
||||
NEXT_PUBLIC_STATS_API_HOST=https://numine.blockscout.com
|
||||
NEXT_PUBLIC_TRANSACTION_INTERPRETATION_PROVIDER=blockscout
|
||||
NEXT_PUBLIC_VIEWS_TOKEN_SCAM_TOGGLE_ENABLED=true
|
||||
NEXT_PUBLIC_VISUALIZE_API_HOST=https://visualizer.services.blockscout.com
|
||||
@@ -53,8 +53,9 @@ NEXT_PUBLIC_TAC_OPERATION_LIFECYCLE_API_HOST=http://localhost:3100
|
||||
NEXT_PUBLIC_USER_OPS_INDEXER_API_HOST=http://localhost:3110
|
||||
NEXT_PUBLIC_ZETACHAIN_SERVICE_API_HOST=http://localhost:3111
|
||||
NEXT_PUBLIC_MULTICHAIN_AGGREGATOR_API_HOST=http://localhost:3012
|
||||
NEXT_PUBLIC_MULTICHAIN_CLUSTER=test
|
||||
NEXT_PUBLIC_MULTICHAIN_STATS_API_HOST=http://localhost:3013
|
||||
NEXT_PUBLIC_INTERCHAIN_INDEXER_API_HOST=http://localhost:3014
|
||||
NEXT_PUBLIC_MULTICHAIN_CLUSTER=test
|
||||
NEXT_PUBLIC_RE_CAPTCHA_APP_SITE_KEY=xxx
|
||||
NEXT_PUBLIC_WALLET_CONNECT_PROJECT_ID=xxx
|
||||
NEXT_PUBLIC_VIEWS_ADDRESS_FORMAT=['base16','bech32']
|
||||
|
||||
@@ -22,10 +22,14 @@ async function fetchConfig() {
|
||||
|
||||
const url = baseUrl + '/assets/essential-dapps/chains.json';
|
||||
const response = await fetch(url);
|
||||
const json = await response.json();
|
||||
|
||||
value = json as MultichainConfig;
|
||||
return value;
|
||||
if (response.ok) {
|
||||
const contentType = response.headers.get('content-type');
|
||||
if (contentType?.includes('application/json')) {
|
||||
const json = await response.json();
|
||||
value = json as MultichainConfig;
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function load() {
|
||||
|
||||
@@ -22,10 +22,14 @@ async function fetchConfig() {
|
||||
|
||||
const url = baseUrl + '/assets/multichain/config.json';
|
||||
const response = await fetch(url);
|
||||
const json = await response.json();
|
||||
|
||||
value = json as MultichainConfig;
|
||||
return value;
|
||||
if (response.ok) {
|
||||
const contentType = response.headers.get('content-type');
|
||||
if (contentType?.includes('application/json')) {
|
||||
const json = await response.json();
|
||||
value = json as MultichainConfig;
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function load() {
|
||||
|
||||
@@ -0,0 +1,286 @@
|
||||
{
|
||||
// Version of the setting file. Always 0.2
|
||||
"version": "0.2",
|
||||
// language - current active spelling language
|
||||
"language": "en",
|
||||
"useGitignore": true,
|
||||
"ignorePaths": [
|
||||
"**/*.svg",
|
||||
"**/*.sfd",
|
||||
".git",
|
||||
"mocks/zetaChain/zetaChainCCTX.ts",
|
||||
"mocks/metadata/address.ts",
|
||||
"playwright/mocks/file_mock_with_very_long_name.json",
|
||||
"playwright/fixtures/rewards.ts",
|
||||
"public/static/capybara/index.js",
|
||||
"ui/showcases/utils.ts",
|
||||
"ui/tx/TxExternalTxs.pw.tsx"
|
||||
],
|
||||
"enableGlobDot": true,
|
||||
"ignoreRandomStrings": true,
|
||||
"allowCompoundWords": true,
|
||||
"ignoreRegExpList": [
|
||||
// Ignore filecoin f410f-like native addresses
|
||||
"f410f[a-z2-7]{39}",
|
||||
// Specify publisher key
|
||||
"spk_\\w+",
|
||||
// Posthog project key
|
||||
"phc_\\w+",
|
||||
// GitHub account names
|
||||
"@(\\w|-)+"
|
||||
],
|
||||
// words - list of words to be always considered correct
|
||||
"words": [
|
||||
"aatx",
|
||||
"abfnrtv",
|
||||
"abitype",
|
||||
"abkw",
|
||||
"ACMRT",
|
||||
"adbutler",
|
||||
"addrs",
|
||||
"adsbyslise",
|
||||
"aeiou",
|
||||
"Ahrefs",
|
||||
"airtable",
|
||||
"Alexa",
|
||||
"alfajores",
|
||||
"andb",
|
||||
"anyblock",
|
||||
"anytrust",
|
||||
"apng",
|
||||
"apos",
|
||||
"appkit",
|
||||
"Applebot",
|
||||
"Arianee",
|
||||
"ARTIS",
|
||||
"astar",
|
||||
"asymp",
|
||||
"autoscout",
|
||||
"Baiduspider",
|
||||
"barooumba",
|
||||
"basehead",
|
||||
"bech",
|
||||
"bedag",
|
||||
"bingbot",
|
||||
"bitquery",
|
||||
"blackfort",
|
||||
"blockie",
|
||||
"blockies",
|
||||
"blockscout",
|
||||
"buildx",
|
||||
"callvalue",
|
||||
"campnetwork",
|
||||
"CCIP",
|
||||
"cctx",
|
||||
"cctxs",
|
||||
"celenium",
|
||||
"celestia",
|
||||
"CERC",
|
||||
"cfasync",
|
||||
"chainid",
|
||||
"chainscout",
|
||||
"chakra",
|
||||
"clstr",
|
||||
"coinzilla",
|
||||
"coinzillatag",
|
||||
"Computor",
|
||||
"contentscript",
|
||||
"contractname",
|
||||
"convictional",
|
||||
"Couldn",
|
||||
"crios",
|
||||
"dappscout",
|
||||
"dbaeumer",
|
||||
"deepdao",
|
||||
"defi",
|
||||
"devnet",
|
||||
"didn",
|
||||
"doesn",
|
||||
"dotenv",
|
||||
"DRPC",
|
||||
"DTEND",
|
||||
"duckduckgo",
|
||||
"eamodio",
|
||||
"Eigenda",
|
||||
"Emelyanov",
|
||||
"Enkrypt",
|
||||
"explorable",
|
||||
"facebookexternalhit",
|
||||
"favicons",
|
||||
"filecoin",
|
||||
"flashblock",
|
||||
"flashblocks",
|
||||
"foos",
|
||||
"fxios",
|
||||
"geas",
|
||||
"giga",
|
||||
"gitpoap",
|
||||
"Googlebot",
|
||||
"goriunov",
|
||||
"gotmpl",
|
||||
"grecaptcha",
|
||||
"growthbook",
|
||||
"gstatic",
|
||||
"hairsp",
|
||||
"healthz",
|
||||
"helia",
|
||||
"hyfi",
|
||||
"ICTT",
|
||||
"identicons",
|
||||
"IERC",
|
||||
"Ihnatsyeu",
|
||||
"Inde",
|
||||
"inpage",
|
||||
"internaltx",
|
||||
"ipfs",
|
||||
"iszero",
|
||||
"jazzicon",
|
||||
"jfif",
|
||||
"jumpdest",
|
||||
"jumpi",
|
||||
"keccak",
|
||||
"Kiryl",
|
||||
"labelable",
|
||||
"laquo",
|
||||
"libc",
|
||||
"libp",
|
||||
"Liquality",
|
||||
"llms",
|
||||
"lokijs",
|
||||
"LUKSO",
|
||||
"mainnets",
|
||||
"megaeth",
|
||||
"merkle",
|
||||
"metasuites",
|
||||
"mgas",
|
||||
"mload",
|
||||
"mmss",
|
||||
"msize",
|
||||
"mulmod",
|
||||
"multiarch",
|
||||
"multicall",
|
||||
"multichain",
|
||||
"multisend",
|
||||
"nbdash",
|
||||
"NCAABB",
|
||||
"ndash",
|
||||
"negb",
|
||||
"nextjs",
|
||||
"noopener",
|
||||
"noreferrer",
|
||||
"noves",
|
||||
"numine",
|
||||
"nums",
|
||||
"okex",
|
||||
"okhttp",
|
||||
"opblock",
|
||||
"opengraph",
|
||||
"paych",
|
||||
"peekers",
|
||||
"pino",
|
||||
"pjpeg",
|
||||
"posthog",
|
||||
"PWDEBUG",
|
||||
"pwstory",
|
||||
"pyftsubset",
|
||||
"qrcode",
|
||||
"rabby",
|
||||
"raleway",
|
||||
"randao",
|
||||
"raquo",
|
||||
"rari",
|
||||
"Rarible",
|
||||
"RBTC",
|
||||
"rdns",
|
||||
"rdparty",
|
||||
"regcred",
|
||||
"remasc",
|
||||
"reown",
|
||||
"resizer",
|
||||
"rgba",
|
||||
"RIPEMD",
|
||||
"rlespinasse",
|
||||
"rogerbot",
|
||||
"rollbar",
|
||||
"ROLLUP",
|
||||
"rollups",
|
||||
"rubic",
|
||||
"Sastana",
|
||||
"schnorr",
|
||||
"screencasts",
|
||||
"scure",
|
||||
"sdiv",
|
||||
"SEMRESATTRS",
|
||||
"Semrush",
|
||||
"servedbyadbutler",
|
||||
"Shavuha",
|
||||
"Shavukha",
|
||||
"shibarium",
|
||||
"shibariumscan",
|
||||
"SHVKH",
|
||||
"sidechain",
|
||||
"Signatur",
|
||||
"simonsiefke",
|
||||
"siwe",
|
||||
"slise",
|
||||
"sload",
|
||||
"smartmuv",
|
||||
"smod",
|
||||
"smol",
|
||||
"sokol",
|
||||
"solidityscan",
|
||||
"Sourcify",
|
||||
"Succ",
|
||||
"superchain",
|
||||
"tabler",
|
||||
"TBXN",
|
||||
"testnetv",
|
||||
"thinsp",
|
||||
"Tokenary",
|
||||
"tokenpocket",
|
||||
"tokentxns",
|
||||
"tonumber",
|
||||
"trustwallet",
|
||||
"Twitterbot",
|
||||
"txns",
|
||||
"typegen",
|
||||
"uidotdev",
|
||||
"unfinalized",
|
||||
"UNKN",
|
||||
"unparse",
|
||||
"unstaked",
|
||||
"usehooks",
|
||||
"utia",
|
||||
"utka",
|
||||
"utko",
|
||||
"UUPS",
|
||||
"VCALENDAR",
|
||||
"vercel",
|
||||
"verifreg",
|
||||
"VEVENT",
|
||||
"viem",
|
||||
"vitalik",
|
||||
"vitest",
|
||||
"wagmi",
|
||||
"warpcast",
|
||||
"watchlist",
|
||||
"webacy",
|
||||
"Winky",
|
||||
"WOWG",
|
||||
"WPOA",
|
||||
"xdai",
|
||||
"XDEFI",
|
||||
"xname",
|
||||
"xstar",
|
||||
"yatki",
|
||||
"zerion",
|
||||
"zerossl",
|
||||
"zetachain",
|
||||
"zetavaloper",
|
||||
"zilliqa",
|
||||
"zkevm",
|
||||
"zksolc",
|
||||
"zksync",
|
||||
"zora"
|
||||
]
|
||||
}
|
||||
@@ -2,7 +2,6 @@
|
||||
"name": "affected-tests",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"author": "Vasilii (tom) Goriunov <tom@ohhhh.me>",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"dependency-tree": "10.0.9"
|
||||
|
||||
@@ -103,7 +103,6 @@ const schema = yup
|
||||
NEXT_PUBLIC_DATA_AVAILABILITY_ENABLED: yup.boolean(),
|
||||
NEXT_PUBLIC_ADVANCED_FILTER_ENABLED: yup.boolean(),
|
||||
NEXT_PUBLIC_CELO_ENABLED: yup.boolean(),
|
||||
NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED: yup.boolean(),
|
||||
NEXT_PUBLIC_DEX_POOLS_ENABLED: yup.boolean()
|
||||
.when('NEXT_PUBLIC_CONTRACT_INFO_API_HOST', {
|
||||
is: (value: string) => Boolean(value),
|
||||
@@ -150,6 +149,7 @@ const schema = yup
|
||||
|
||||
// Misc
|
||||
NEXT_PUBLIC_USE_NEXT_JS_PROXY: yup.boolean(),
|
||||
NEXT_PUBLIC_API_KEYS_ALERT_MESSAGE: yup.string(),
|
||||
})
|
||||
.concat(apisSchema)
|
||||
.concat(chainSchema)
|
||||
@@ -159,11 +159,13 @@ const schema = yup
|
||||
.concat(uiSchemas.footerSchema)
|
||||
.concat(uiSchemas.miscSchema)
|
||||
.concat(uiSchemas.viewsSchema)
|
||||
.concat(featuresSchemas.accountSchema)
|
||||
.concat(featuresSchemas.address3rdPartyWidgetsConfigSchema)
|
||||
.concat(featuresSchemas.adsSchema)
|
||||
.concat(featuresSchemas.apiDocsSchema)
|
||||
.concat(featuresSchemas.beaconChainSchema)
|
||||
.concat(featuresSchemas.bridgedTokensSchema)
|
||||
.concat(featuresSchemas.crossChainTxsSchema)
|
||||
.concat(featuresSchemas.defiDropdownSchema)
|
||||
.concat(featuresSchemas.highlightsConfigSchema)
|
||||
.concat(featuresSchemas.marketplaceSchema)
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import { AuthProvider } from 'types/client/account';
|
||||
import * as yup from 'yup';
|
||||
|
||||
export const accountSchema = yup
|
||||
.object()
|
||||
.shape({
|
||||
NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED: yup.boolean(),
|
||||
NEXT_PUBLIC_ACCOUNT_AUTH_PROVIDER: yup
|
||||
.string<AuthProvider>()
|
||||
.when('NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED', {
|
||||
is: (value: boolean) => value === true,
|
||||
then: (schema) => schema.oneOf([ 'auth0', 'dynamic' ]),
|
||||
otherwise: (schema) => schema.max(-1, 'NEXT_PUBLIC_ACCOUNT_AUTH_PROVIDER cannot not be used if NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED is not defined'),
|
||||
}),
|
||||
NEXT_PUBLIC_ACCOUNT_DYNAMIC_ENVIRONMENT_ID: yup
|
||||
.string()
|
||||
.when('NEXT_PUBLIC_ACCOUNT_AUTH_PROVIDER', {
|
||||
is: (value: AuthProvider) => value === 'dynamic',
|
||||
then: (schema) => schema.required(),
|
||||
otherwise: (schema) => schema.max(-1, 'NEXT_PUBLIC_ACCOUNT_DYNAMIC_ENVIRONMENT_ID can only be used if NEXT_PUBLIC_ACCOUNT_AUTH_PROVIDER is set to \'dynamic\' '),
|
||||
}),
|
||||
});
|
||||
@@ -0,0 +1,19 @@
|
||||
import * as yup from 'yup';
|
||||
import { urlTest } from '../../utils';
|
||||
|
||||
export const crossChainTxsSchema = yup
|
||||
.object()
|
||||
.shape({
|
||||
NEXT_PUBLIC_CROSS_CHAIN_TXS_ENABLED: yup.boolean(),
|
||||
NEXT_PUBLIC_INTERCHAIN_INDEXER_API_HOST: yup
|
||||
.string()
|
||||
.when('NEXT_PUBLIC_CROSS_CHAIN_TXS_ENABLED', {
|
||||
is: (value: boolean) => value,
|
||||
then: (schema) => schema.test(urlTest),
|
||||
otherwise: (schema) => schema.test(
|
||||
'not-exist',
|
||||
'NEXT_PUBLIC_INTERCHAIN_INDEXER_API_HOST can only be used with NEXT_PUBLIC_CROSS_CHAIN_TXS_ENABLED',
|
||||
value => value === undefined,
|
||||
),
|
||||
}),
|
||||
});
|
||||
@@ -1,8 +1,10 @@
|
||||
export * from './account';
|
||||
export * from './address3rdPartyWidgets';
|
||||
export * from './ads';
|
||||
export * from './apiDocs';
|
||||
export * from './beaconChain';
|
||||
export * from './bridgedToken';
|
||||
export * from './crossChainTxs';
|
||||
export * from './defiDropdown';
|
||||
export * from './highlights';
|
||||
export * from './marketplace';
|
||||
|
||||
@@ -10,3 +10,6 @@ NEXT_PUBLIC_METADATA_ADDRESS_TAGS_UPDATE_ENABLED=false
|
||||
NEXT_PUBLIC_HAS_USER_OPS=true
|
||||
NEXT_PUBLIC_USER_OPS_INDEXER_API_HOST=https://example.com
|
||||
NEXT_PUBLIC_NAVIGATION_PROMO_BANNER_CONFIG={'img_url': {'small': 'https://example.com/promo-sm.png', 'large': 'https://example.com/promo-lg.png'}, 'link_url': 'https://example.com'}
|
||||
NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED=true
|
||||
NEXT_PUBLIC_ACCOUNT_AUTH_PROVIDER=dynamic
|
||||
NEXT_PUBLIC_ACCOUNT_DYNAMIC_ENVIRONMENT_ID=xxx
|
||||
@@ -0,0 +1,2 @@
|
||||
NEXT_PUBLIC_CROSS_CHAIN_TXS_ENABLED=true
|
||||
NEXT_PUBLIC_INTERCHAIN_INDEXER_API_HOST=https://api.example.com
|
||||
@@ -0,0 +1,18 @@
|
||||
[
|
||||
{
|
||||
"id": "8021",
|
||||
"name": "Numine",
|
||||
"explorer_url": "https://example.com/blockchain/numine",
|
||||
"route_templates": {
|
||||
"tx": "/transaction/{hash}",
|
||||
"address": "/wallet/{hash}",
|
||||
"token": "/address/{hash}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "43114",
|
||||
"name": "C-Chain",
|
||||
"logo": "https://example.com/logo.svg",
|
||||
"explorer_url": "https://example.com/blockchain/c"
|
||||
}
|
||||
]
|
||||
@@ -4,11 +4,10 @@ import { dirname, resolve as resolvePath } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { Worker } from 'node:worker_threads';
|
||||
import * as viemChains from 'viem/chains';
|
||||
import { pick } from 'es-toolkit';
|
||||
import { pick, uniq, delay } from 'es-toolkit';
|
||||
|
||||
import { EssentialDappsConfig } from 'types/client/marketplace';
|
||||
import { getEnvValue, parseEnvJson } from 'configs/app/utils';
|
||||
import { uniq } from 'es-toolkit';
|
||||
import currentChainConfig from 'configs/app';
|
||||
import appConfig from 'configs/app';
|
||||
import { EssentialDappsChainConfig } from 'types/client/marketplace';
|
||||
@@ -61,14 +60,15 @@ function trimChainConfig(config: typeof appConfig, logoUrl: string | undefined)
|
||||
}
|
||||
|
||||
async function computeChainConfig(url: string): Promise<unknown> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const workerPath = resolvePath(currentDir, 'worker.js');
|
||||
const workerPath = resolvePath(currentDir, 'worker.js');
|
||||
|
||||
const worker = new Worker(workerPath, {
|
||||
workerData: { url },
|
||||
env: {} // Start with empty environment
|
||||
});
|
||||
const worker = new Worker(workerPath, {
|
||||
workerData: { url },
|
||||
env: {} // Start with empty environment
|
||||
});
|
||||
const controller = new AbortController();
|
||||
|
||||
const configPromise = new Promise((resolve, reject) => {
|
||||
worker.on('message', (config) => {
|
||||
resolve(config);
|
||||
});
|
||||
@@ -79,11 +79,17 @@ async function computeChainConfig(url: string): Promise<unknown> {
|
||||
});
|
||||
|
||||
worker.on('exit', (code) => {
|
||||
if (code !== 0) {
|
||||
reject(new Error(`Worker stopped with exit code ${ code }`));
|
||||
}
|
||||
controller.abort();
|
||||
reject(new Error(`Worker stopped with exit code ${ code }`));
|
||||
});
|
||||
});
|
||||
|
||||
return Promise.race([
|
||||
configPromise,
|
||||
delay(30_000, { signal: controller.signal })
|
||||
]).finally(() => {
|
||||
worker.terminate();
|
||||
})
|
||||
}
|
||||
|
||||
async function run() {
|
||||
@@ -116,7 +122,15 @@ async function run() {
|
||||
const explorerUrls = chainscoutInfo.externals.map(({ explorerUrl }) => explorerUrl).filter(Boolean);
|
||||
console.log(`ℹ️ For ${ explorerUrls.length } chains explorer url was found in static config. Fetching parameters for each chain...`);
|
||||
|
||||
const chainConfigs = await Promise.all(explorerUrls.map(computeChainConfig)) as Array<typeof appConfig>;
|
||||
const chainConfigs: Array<typeof appConfig> = [];
|
||||
|
||||
for (const explorerUrl of explorerUrls) {
|
||||
const chainConfig = (await computeChainConfig(explorerUrl)) as typeof appConfig | undefined;
|
||||
if (!chainConfig) {
|
||||
throw new Error(`Failed to fetch chain config for ${ explorerUrl }`);
|
||||
}
|
||||
chainConfigs.push(chainConfig);
|
||||
}
|
||||
|
||||
const result = {
|
||||
chains: [ currentChainConfig, ...chainConfigs ].map((config) => {
|
||||
|
||||
@@ -2,8 +2,10 @@ import { mkdirSync, writeFileSync } from 'node:fs';
|
||||
import { dirname, resolve as resolvePath } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { Worker } from 'node:worker_threads';
|
||||
import { delay } from 'es-toolkit';
|
||||
|
||||
import { ClusterChainConfig } from 'types/multichain';
|
||||
import appConfig from 'configs/app';
|
||||
|
||||
const currentFilePath = fileURLToPath(import.meta.url);
|
||||
const currentDir = dirname(currentFilePath);
|
||||
@@ -48,14 +50,15 @@ async function getChainscoutInfo(chainIds: Array<string>) {
|
||||
}
|
||||
|
||||
async function computeChainConfig(url: string): Promise<unknown> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const workerPath = resolvePath(currentDir, 'worker.js');
|
||||
const workerPath = resolvePath(currentDir, 'worker.js');
|
||||
|
||||
const worker = new Worker(workerPath, {
|
||||
workerData: { url },
|
||||
env: {} // Start with empty environment
|
||||
});
|
||||
const worker = new Worker(workerPath, {
|
||||
workerData: { url },
|
||||
env: {} // Start with empty environment
|
||||
});
|
||||
const controller = new AbortController();
|
||||
|
||||
const configPromise = new Promise((resolve, reject) => {
|
||||
worker.on('message', (config) => {
|
||||
resolve(config);
|
||||
});
|
||||
@@ -66,11 +69,18 @@ async function computeChainConfig(url: string): Promise<unknown> {
|
||||
});
|
||||
|
||||
worker.on('exit', (code) => {
|
||||
if (code !== 0) {
|
||||
reject(new Error(`Worker stopped with exit code ${ code }`));
|
||||
}
|
||||
controller.abort();
|
||||
reject(new Error(`Worker stopped with exit code ${ code }`));
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
return Promise.race([
|
||||
configPromise,
|
||||
delay(30_000, { signal: controller.signal })
|
||||
]).finally(() => {
|
||||
worker.terminate();
|
||||
})
|
||||
}
|
||||
|
||||
async function getExplorerUrls() {
|
||||
@@ -105,15 +115,27 @@ async function run() {
|
||||
throw new Error('No chains found in the cluster.');
|
||||
}
|
||||
|
||||
const configs = await Promise.all(explorerUrls.map(computeChainConfig));
|
||||
const chainscoutInfo = await getChainscoutInfo(configs.map((config) => config.chain.id));
|
||||
const configs: Array<typeof appConfig> = [];
|
||||
for (const url of explorerUrls) {
|
||||
const chainConfig = (await computeChainConfig(url)) as typeof appConfig | undefined;
|
||||
if (!chainConfig) {
|
||||
throw new Error(`Failed to fetch chain config for ${ url }`);
|
||||
}
|
||||
configs.push(chainConfig);
|
||||
}
|
||||
|
||||
const chainscoutInfo = await getChainscoutInfo(
|
||||
configs
|
||||
.map((config) => config.chain.id)
|
||||
.filter((chainId) => chainId !== undefined)
|
||||
);
|
||||
|
||||
const config = {
|
||||
chains: configs.map((config, index) => {
|
||||
const chainId = config.chain.id;
|
||||
const chainName = (config as { chain: { name: string } })?.chain?.name ?? `Chain ${ chainId }`;
|
||||
return {
|
||||
id: chainId,
|
||||
id: chainId || '',
|
||||
name: chainName,
|
||||
logo: chainscoutInfo.find((chain) => chain.id === chainId)?.logoUrl,
|
||||
explorer_url: explorerUrls[index],
|
||||
|
||||
@@ -112,7 +112,7 @@ Also, be aware that if you customize the name of the currency or any of its deno
|
||||
| NEXT_PUBLIC_NETWORK_RPC_URL | `string \| Array<string>` | Chain public RPC server url, see [https://chainlist.org](https://chainlist.org) for the reference. Can contain a single string value, or an array of urls. | - | - | `https://core.poa.network` | v1.0.x+ |
|
||||
| NEXT_PUBLIC_NETWORK_CURRENCY_NAME | `string` | Network currency name | - | - | `Ether` | v1.0.x+ |
|
||||
| NEXT_PUBLIC_NETWORK_CURRENCY_WEI_NAME | `string` | Name of the smallest unit of the native currency (e.g., 'wei' for Ethereum, where 1 ETH = 10^18 wei). Used for displaying gas prices and transaction fees in the smallest denomination. | - | `wei` | `duck` | v1.23.0+ |
|
||||
| NEXT_PUBLIC_NETWORK_CURRENCY_GWEI_NAME | `string` | Name of the giga-unit of the native currency (e.g., 'gwei' for Ethereum, where 1 gwei = 10^9 of the smallest unit). Used for displaying gas prices in a more readable format throughout the UI. | - | `gwei` | `gduck` | v2.5.0+ |
|
||||
| NEXT_PUBLIC_NETWORK_CURRENCY_GWEI_NAME | `string` | Name of the giga-unit of the native currency (e.g., 'gwei' for Ethereum, where 1 gwei = 10^9 of the smallest unit). Used for displaying gas prices in a more readable format throughout the UI. | - | `gwei` | `gDuck` | v2.5.0+ |
|
||||
| NEXT_PUBLIC_NETWORK_CURRENCY_SYMBOL | `string` | Network currency symbol | - | - | `ETH` | v1.0.x+ |
|
||||
| NEXT_PUBLIC_NETWORK_CURRENCY_DECIMALS | `string` | Network currency decimals | - | `18` | `6` | v1.0.x+ |
|
||||
| NEXT_PUBLIC_NETWORK_SECONDARY_COIN_SYMBOL | `string` | Network secondary coin symbol. | - | - | `GNO` | v1.29.0+ |
|
||||
@@ -380,6 +380,7 @@ Settings for meta tags, OG tags and SEO
|
||||
| NEXT_PUBLIC_HIDE_INDEXING_ALERT_INT_TXS | `boolean` | Set to `true` to hide indexing alert in the page footer about indexing block's internal transactions | - | `false` | `true` | v1.17.0+ |
|
||||
| NEXT_PUBLIC_HIDE_NATIVE_COIN_PRICE | `boolean` | Set to `true` to hide the native coin price in the top bar | - | `false` | `true` | v2.4.0+ |
|
||||
| NEXT_PUBLIC_MAINTENANCE_ALERT_MESSAGE | `string` | Used for displaying custom announcements or alerts in the header of the site. Could be a regular string or a HTML code. | - | - | `Hello world! 🤪` | v1.13.0+ |
|
||||
| NEXT_PUBLIC_API_KEYS_ALERT_MESSAGE | `string` | Used for displaying custom alerts on the API keys page. Could be a regular string or a HTML code. | - | - | `Hello world! 🤪` | v2.7.0+ |
|
||||
| NEXT_PUBLIC_COLOR_THEME_DEFAULT | `'light' \| 'dim' \| 'midnight' \| 'dark'` | Preferred color theme of the app | - | - | `midnight` | v1.30.0+ |
|
||||
| NEXT_PUBLIC_COLOR_THEME_OVERRIDES | `string` | Color overrides for the default theme; pass a JSON-like string that represents a subset of the `DEFAULT_THEME_COLORS` object (see `toolkit/theme/foundations/colors.ts`) to customize the app's main colors. See [here](https://www.figma.com/design/4In0X8UADoZaTfZ34HaZ3K/Blockscout-design-system?node-id=29124-23813&t=XOv4ahHUSsTDlNkN-4) the Figma worksheet with description of available color tokens. | - | - | `{'text':{'primary':{'_light':{'value':'rgba(16,17,18,0.80)'},'_dark':{'value':'rgba(222,217,217)'}}}}` | v2.3.0+ |
|
||||
| NEXT_PUBLIC_FONT_FAMILY_HEADING | `FontFamily`, see full description [below](#font-family-configuration-properties) | Special typeface to use in page headings (`<h1>`, `<h2>`, etc.) | - | - | `{'name':'Montserrat','url':'https://fonts.googleapis.com/css2?family=Montserrat:wght@400;500;600;700&display=swap'}` | v1.35.0+ |
|
||||
@@ -427,7 +428,9 @@ Settings for meta tags, OG tags and SEO
|
||||
| Variable | Type| Description | Compulsoriness | Default value | Example value | Version |
|
||||
| --- | --- | --- | --- | --- | --- | --- |
|
||||
| NEXT_PUBLIC_IS_ACCOUNT_SUPPORTED | `boolean` | Set to true if network has account feature | Required | - | `true` | v1.0.x+ |
|
||||
| NEXT_PUBLIC_RE_CAPTCHA_APP_SITE_KEY | `boolean` | See [below](#google-recaptcha) | Required | - | `<your-secret>` | v1.0.x+ |
|
||||
| NEXT_PUBLIC_ACCOUNT_AUTH_PROVIDER | `auth0 \| dynamic` | Auth provider that enables basic user authentication. | - | `auth0` | `dynamic` | upcoming |
|
||||
| NEXT_PUBLIC_ACCOUNT_DYNAMIC_ENVIRONMENT_ID | `string` | Environment ID of the Dynamic project. | Required, if provider is `dynamic` | - | `<your-secret>` | upcoming |
|
||||
| NEXT_PUBLIC_RE_CAPTCHA_APP_SITE_KEY | `boolean` | See [below](#google-recaptcha) | Required, if provided is `auth0` | - | `<your-secret>` | v1.0.x+ |
|
||||
|
||||
|
||||
|
||||
@@ -536,7 +539,7 @@ Ads are enabled by default on all self-hosted instances. If you would like to di
|
||||
| NEXT_PUBLIC_ROLLUP_HOMEPAGE_SHOW_LATEST_BLOCKS | `boolean` | Set to `true` to display "Latest blocks" widget instead of "Latest batches" on the home page | - | - | `true` | v1.36.0+ |
|
||||
| NEXT_PUBLIC_ROLLUP_OUTPUT_ROOTS_ENABLED | `boolean` | Enables "Output roots" page (Optimistic stack only) | - | `false` | `true` | v1.37.0+ |
|
||||
| NEXT_PUBLIC_ROLLUP_PARENT_CHAIN | `ParentChain`, see details [below](#parent-chain-configuration-properties) | Configuration parameters for the parent chain. | - | - | `{'baseUrl':'https://explorer.duckchain.io'}` | v1.38.0+ |
|
||||
| NEXT_PUBLIC_ROLLUP_DA_CELESTIA_NAMESPACE | `string` | Hex-string for creating a link to the transaction batch on the Seleneium explorer. "0x"-format and 60 symbol length. Available only for Arbitrum roll-ups. | - | - | `0x00000000000000000000000000000000000000ca1de12a9905be97beaf` | v1.38.0+ |
|
||||
| NEXT_PUBLIC_ROLLUP_DA_CELESTIA_NAMESPACE | `string` | Hex-string for creating a link to the transaction batch on the [Celenium explorer](https://celenium.io). "0x"-format and 60 symbol length. Available only for Arbitrum roll-ups. | - | - | `0x00000000000000000000000000000000000000ca1de12a9905be97beaf` | v1.38.0+ |
|
||||
| NEXT_PUBLIC_ROLLUP_DA_CELESTIA_CELENIUM_URL | `string` | URL for the Selenium explorer. It is used to create links to the Data Availability Blobs page. The URL should contain the full path without any search parameters related to the blob, as these will be constructed at runtime for each blob separately. Available only for Optimistic or Arbitrum roll-ups. | - | - | `https://mocha.celenium.io/blob` | v2.0.2+ |
|
||||
|
||||
#### Parent chain configuration properties
|
||||
@@ -1070,6 +1073,18 @@ This feature enables the application to act as an explorer of multiple blockchai
|
||||
| NEXT_PUBLIC_MULTICHAIN_AGGREGATOR_API_HOST | `string` | Multichain aggregator API service host | Required | - | `https://multichain-aggregator.k8s-dev.blockscout.com` | v2.5.0+ |
|
||||
| NEXT_PUBLIC_MULTICHAIN_STATS_API_HOST | `string` | Multichain statistics API service host | Required | - | `http://multichain-search-stats.k8s-dev.blockscout.com` | v2.5.0+ |
|
||||
|
||||
|
||||
|
||||
### Cross-chain transactions
|
||||
|
||||
This feature enables cross-chain transaction tracking and visualization, allowing users to view transactions that span multiple blockchains. It provides detailed information about cross-chain operations and links to related transactions on different chains.
|
||||
|
||||
| Variable | Type| Description | Compulsoriness | Default value | Example value | Version |
|
||||
| --- | --- | --- | --- | --- | --- | --- |
|
||||
| NEXT_PUBLIC_CROSS_CHAIN_TXS_ENABLED | `boolean` | The flag that enables the feature | Required | - | `true` | upcoming |
|
||||
| NEXT_PUBLIC_INTERCHAIN_INDEXER_API_HOST | `string` | Interchain indexer API service host used to fetch cross-chain transaction data and metadata | Required | - | `https://interchain-indexer.k8s-dev.blockscout.com` | upcoming |
|
||||
|
||||
|
||||
|
||||
|
||||
### Badge claim link
|
||||
|
||||
@@ -7,7 +7,6 @@ import consistentDefaultExportNamePlugin from 'eslint-plugin-consistent-default-
|
||||
import importPlugin from 'eslint-plugin-import';
|
||||
import importHelpersPlugin from 'eslint-plugin-import-helpers';
|
||||
import jsxA11yPlugin from 'eslint-plugin-jsx-a11y';
|
||||
import noCyrillicStringPlugin from 'eslint-plugin-no-cyrillic-string';
|
||||
import playwrightPlugin from 'eslint-plugin-playwright';
|
||||
import reactPlugin from 'eslint-plugin-react';
|
||||
import reactHooksPlugin from 'eslint-plugin-react-hooks';
|
||||
@@ -325,15 +324,6 @@ export default tseslint.config(
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
plugins: {
|
||||
'no-cyrillic-string': noCyrillicStringPlugin,
|
||||
},
|
||||
rules: {
|
||||
'no-cyrillic-string/no-cyrillic-string': 'error',
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
plugins: {
|
||||
'jsx-a11y': jsxA11yPlugin,
|
||||
|
||||
@@ -22,8 +22,8 @@ declare global {
|
||||
};
|
||||
abkw: string;
|
||||
__envs: Record<string, string>;
|
||||
__multichainConfig: MultichainConfig;
|
||||
__essentialDappsChains: { chains: Array<EssentialDappsChainConfig> };
|
||||
__multichainConfig?: MultichainConfig;
|
||||
__essentialDappsChains?: { chains: Array<EssentialDappsChainConfig> };
|
||||
}
|
||||
|
||||
namespace NodeJS {
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M10 3.177H4.6a2.34 2.34 0 0 0-2.34 2.34v9.9a2.34 2.34 0 0 0 2.34 2.34h10.8a2.34 2.34 0 0 0 2.34-2.34v-9.9H19v9.9l-.005.186a3.6 3.6 0 0 1-3.41 3.41l-.185.004H4.6a3.6 3.6 0 0 1-3.6-3.6v-9.9a3.6 3.6 0 0 1 3.6-3.6H10v1.26ZM8.987 6.814c.098 0 .186.061.219.153l2.614 7.2a.234.234 0 0 1-.22.314h-.89a.235.235 0 0 1-.22-.158l-.638-1.828H7.015l-.639 1.828a.235.235 0 0 1-.22.158h-.892a.234.234 0 0 1-.22-.314l2.615-7.2a.233.233 0 0 1 .219-.153h1.11Zm4.913 0c.129 0 .233.104.233.233v7.2a.233.233 0 0 1-.233.234h-.841a.234.234 0 0 1-.234-.234v-7.2c0-.129.105-.233.234-.233h.841Zm-6.495 4.562h2.057L8.434 8.428l-1.029 2.948Zm7.9-9.324a.129.129 0 0 1 .24 0l.556 1.5a.13.13 0 0 0 .076.076l1.5.555a.13.13 0 0 1 0 .242l-1.5.555a.13.13 0 0 0-.076.076l-.555 1.5a.129.129 0 0 1-.241 0l-.556-1.5a.13.13 0 0 0-.076-.076l-1.5-.555a.13.13 0 0 1 0-.242l1.5-.555a.13.13 0 0 0 .076-.076l.556-1.5Zm-2.58-.645a.13.13 0 0 1 .243 0l.206.559a.13.13 0 0 0 .076.076l.56.207a.13.13 0 0 1 0 .242l-.56.206a.13.13 0 0 0-.076.077l-.206.558a.13.13 0 0 1-.242 0l-.207-.558a.13.13 0 0 0-.077-.077l-.558-.206a.13.13 0 0 1 0-.242l.558-.207a.13.13 0 0 0 .077-.076l.207-.559Z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -1,4 +1,3 @@
|
||||
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M12.26 17.5H4.027a.47.47 0 0 1-.47-.469V5.312c0-.26.21-.469.47-.469h2.137V2.969c0-.259.21-.469.468-.469h5.89l.015.003.014.003a.449.449 0 0 1 .14.03.463.463 0 0 1 .156.095l3.452 3.31-.002.002a.466.466 0 0 1 .146.337v7.3c0 .87-.708 1.577-1.577 1.577h-1.029v.766c0 .87-.707 1.577-1.577 1.577Zm2.823-11.69L12.99 3.794v1.379a.64.64 0 0 0 .638.639h1.454Zm-2.755-2.648H6.826v11.333h8.316a.64.64 0 0 0 .639-.64V6.474H13.63c-.87 0-1.302-.432-1.302-1.301v-2.01ZM6.164 5.505H4.22v11.333h8.316c.352 0 .639-.563.639-.915v-.766H6.632a.469.469 0 0 1-.468-.469V5.505ZM8.85 8.5a.25.25 0 1 0 0 .5h4.7a.25.25 0 1 0 0-.5h-4.7Zm-.25 1.95a.25.25 0 0 1 .25-.25h4.7a.25.25 0 1 1 0 .5h-4.7a.25.25 0 0 1-.25-.25ZM8.85 12a.25.25 0 1 0 0 .5h4.7a.25.25 0 1 0 0-.5h-4.7Z" fill="currentColor"/>
|
||||
<path d="M6.164 4.843v.35h.35v-.35h-.35Zm6.373-2.34-.098.336h.003l.095-.336Zm0 0 .099-.336h-.003l-.095.336Zm.014.003.02-.35-.02.35Zm.123.025.12-.33h-.004l-.115.33Zm.017.005.101-.335-.101.335Zm.025.009.145-.319h-.002l-.143.319Zm.13.086.243-.253-.243.252ZM16.3 5.94l.247.248.253-.253-.258-.247-.242.252Zm-.002.002-.247-.248-.252.252.257.248.242-.252Zm-2.46 9.214v-.35h-.35v.35h.35Zm-.846-11.364.242-.252-.593-.572v.824h.35Zm2.092 2.018v.35h.867l-.624-.602-.243.252ZM6.826 3.162v-.35h-.35v.35h.35Zm5.502 0h.35v-.35h-.35v.35ZM6.826 14.495h-.35v.35h.35v-.35Zm8.955-8.022h.35v-.35h-.35v.35ZM4.22 5.505v-.35h-.35v.35h.35Zm1.944 0h.35v-.35h-.35v.35ZM4.22 16.838h-.35v.35h.35v-.35Zm8.955-1.68h.35v-.35h-.35v.35ZM4.027 17.85h8.233v-.7H4.027v.7Zm-.82-.819a.82.82 0 0 0 .82.819v-.7a.12.12 0 0 1-.12-.119h-.7Zm0-11.719v11.719h.7V5.312h-.7Zm.82-.819a.82.82 0 0 0-.82.819h.7a.12.12 0 0 1 .12-.119v-.7Zm2.137 0H4.027v.7h2.137v-.7Zm-.35-1.524v1.874h.7V2.969h-.7Zm.818-.819a.819.819 0 0 0-.818.819h.7c0-.066.053-.119.118-.119v-.7Zm5.89 0h-5.89v.7h5.89v-.7Zm.114.017s-.015-.005-.033-.008a.389.389 0 0 0-.081-.009v.7a.313.313 0 0 1-.065-.007c-.013-.002-.022-.006-.018-.004l.197-.672Zm-.003 0-.191.673.191-.674Zm-.063-.01a.308.308 0 0 1 .053.007l.013.003-.197.672.027.007a.39.39 0 0 0 .066.01l.038-.7Zm.22.043a.8.8 0 0 0-.218-.044l-.041.7a.1.1 0 0 1 .028.005l.231-.66Zm.002 0 .001.001-.237.659c.015.005.029.01.033.01l.203-.67Zm.067.025c-.032-.014-.061-.022-.067-.024l-.203.67h.004-.002a.208.208 0 0 1-.018-.007l.286-.639Zm.23.153a.812.812 0 0 0-.228-.152l-.29.637a.11.11 0 0 1 .033.02l.485-.505Zm3.452 3.31-3.452-3.31-.485.505 3.453 3.311.484-.505Zm.004.502.001-.001-.495-.495-.001.001.495.495Zm.248.09a.818.818 0 0 0-.253-.59l-.485.505a.117.117 0 0 1 .038.085h.7Zm0 7.3v-7.3h-.7v7.3h.7Zm-1.927 1.927a1.929 1.929 0 0 0 1.927-1.927h-.7c0 .677-.551 1.227-1.227 1.227v.7Zm-1.029 0h1.029v-.7h-1.029v.7Zm.35.416v-.766h-.7v.766h.7ZM12.26 17.85a1.929 1.929 0 0 0 1.927-1.927h-.7c0 .676-.55 1.227-1.227 1.227v.7Zm.488-13.805 2.092 2.018.486-.504-2.093-2.018-.486.504Zm.593 1.127v-1.38h-.7v1.38h.7Zm.288.289a.29.29 0 0 1-.288-.29h-.7a.99.99 0 0 0 .988.99v-.7Zm1.454 0h-1.454v.7h1.454v-.7ZM6.826 3.512h5.502v-.7H6.826v.7Zm.35 10.983V3.162h-.7v11.333h.7Zm7.966-.35H6.826v.7h8.316v-.7Zm.289-.29a.29.29 0 0 1-.29.29v.7a.99.99 0 0 0 .99-.99h-.7Zm0-7.382v7.383h.7V6.473h-.7Zm-1.801.35h2.15v-.7h-2.15v.7Zm-1.652-1.651c0 .488.122.918.427 1.224.306.306.737.427 1.225.427v-.7c-.382 0-.602-.095-.73-.222-.127-.127-.222-.348-.222-.73h-.7Zm0-2.01v2.01h.7v-2.01h-.7ZM4.22 5.855h1.944v-.7H4.22v.7Zm.35 10.983V5.505h-.7v11.333h.7Zm7.966-.35H4.22v.7h8.316v-.7Zm.289-.565a.862.862 0 0 1-.134.404c-.099.156-.167.16-.155.16v.7c.364 0 .615-.276.748-.489.143-.229.24-.52.24-.775h-.7Zm0-.766v.766h.7v-.766h-.7Zm-6.193.35h6.543v-.7H6.632v.7Zm-.818-.819c0 .452.365.82.818.82v-.7a.119.119 0 0 1-.118-.12h-.7Zm0-9.183v9.183h.7V5.505h-.7ZM8.95 8.75a.1.1 0 0 1-.1.1v-.7a.6.6 0 0 0-.6.6h.7Zm-.1-.1a.1.1 0 0 1 .1.1h-.7a.6.6 0 0 0 .6.6v-.7Zm4.7 0h-4.7v.7h4.7v-.7Zm-.1.1a.1.1 0 0 1 .1-.1v.7a.6.6 0 0 0 .6-.6h-.7Zm.1.1a.1.1 0 0 1-.1-.1h.7a.6.6 0 0 0-.6-.6v.7Zm-4.7 0h4.7v-.7h-4.7v.7Zm0 1a.6.6 0 0 0-.6.6h.7a.1.1 0 0 1-.1.1v-.7Zm4.7 0h-4.7v.7h4.7v-.7Zm.6.6a.6.6 0 0 0-.6-.6v.7a.1.1 0 0 1-.1-.1h.7Zm-.6.6a.6.6 0 0 0 .6-.6h-.7a.1.1 0 0 1 .1-.1v.7Zm-4.7 0h4.7v-.7h-4.7v.7Zm-.6-.6a.6.6 0 0 0 .6.6v-.7a.1.1 0 0 1 .1.1h-.7Zm.7 1.8a.1.1 0 0 1-.1.1v-.7a.6.6 0 0 0-.6.6h.7Zm-.1-.1a.1.1 0 0 1 .1.1h-.7a.6.6 0 0 0 .6.6v-.7Zm4.7 0h-4.7v.7h4.7v-.7Zm-.1.1a.1.1 0 0 1 .1-.1v.7a.6.6 0 0 0 .6-.6h-.7Zm.1.1a.1.1 0 0 1-.1-.1h.7a.6.6 0 0 0-.6-.6v.7Zm-4.7 0h4.7v-.7h-4.7v.7Z" fill="currentColor"/>
|
||||
<path d="M18.942 4.505c-.045-.107-1.15-2.617-4.799-2.617A5.336 5.336 0 0 0 10 3.546a5.333 5.333 0 0 0-4.144-1.658c-3.649 0-4.755 2.51-4.8 2.617L1 4.78v12.674l1.36.284c.032-.07.82-1.758 3.496-1.758 2.674 0 3.462 1.686 3.491 1.751l1.304.006c.032-.07.82-1.757 3.494-1.757 2.674 0 3.463 1.686 3.492 1.751L19 17.455V4.781l-.058-.276ZM17.58 15.58a5.677 5.677 0 0 0-3.436-1.018A5.347 5.347 0 0 0 10 16.216a5.347 5.347 0 0 0-4.144-1.654 5.677 5.677 0 0 0-3.437 1.018V4.953c.226-.391 1.13-1.646 3.437-1.646 2.306 0 3.214 1.261 3.434 1.645v8.955h1.42V4.952c.226-.39 1.129-1.645 3.435-1.645 2.307 0 3.216 1.261 3.436 1.645V15.58Z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 724 B |
@@ -1,10 +0,0 @@
|
||||
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M19.111 21H4.891a.674.674 0 0 1-.673-.674v-9.698a.674.674 0 0 1 .674-.674h14.22a.674.674 0 0 1 .673.674v9.698a.674.674 0 0 1-.674.674ZM5.566 19.652h12.871v-8.35H5.566v8.35Z" fill="currentColor"/>
|
||||
<path d="M19.111 21H4.891a.674.674 0 0 1-.673-.674v-9.698a.674.674 0 0 1 .674-.674h14.22a.674.674 0 0 1 .673.674v9.698a.674.674 0 0 1-.674.674ZM5.566 19.652h12.871v-8.35H5.566v8.35Z" fill="currentColor"/>
|
||||
<path d="M20.328 11.302H3.674A.674.674 0 0 1 3 10.628V7.006a.674.674 0 0 1 .674-.674h16.654a.674.674 0 0 1 .675.674v3.622a.674.674 0 0 1-.675.674ZM4.348 9.954h15.307V7.68H4.348v2.274Z" fill="currentColor"/>
|
||||
<path d="M20.328 11.302H3.674A.674.674 0 0 1 3 10.628V7.006a.674.674 0 0 1 .674-.674h16.654a.674.674 0 0 1 .675.674v3.622a.674.674 0 0 1-.675.674ZM4.348 9.954h15.307V7.68H4.348v2.274Z" fill="currentColor"/>
|
||||
<path d="M12 7.68a.674.674 0 0 1-.415-.144l-3.903-3.07a.55.55 0 0 0-.89.432v2.108a.674.674 0 1 1-1.348 0V4.898a1.898 1.898 0 0 1 3.071-1.491l3.902 3.07A.674.674 0 0 1 12 7.68Z" fill="currentColor"/>
|
||||
<path d="M12 7.68a.674.674 0 0 1-.415-.144l-3.903-3.07a.55.55 0 0 0-.89.432v2.108a.674.674 0 1 1-1.348 0V4.898a1.898 1.898 0 0 1 3.071-1.491l3.902 3.07A.674.674 0 0 1 12 7.68Z" fill="currentColor"/>
|
||||
<path d="M12.001 7.68a.674.674 0 0 1-.416-1.204l3.901-3.07a1.898 1.898 0 0 1 3.072 1.492v2.108a.674.674 0 1 1-1.348 0V4.898a.55.55 0 0 0-.89-.432l-3.902 3.07A.674.674 0 0 1 12 7.68Z" fill="currentColor"/>
|
||||
<path d="M12.001 7.68a.674.674 0 0 1-.416-1.204l3.901-3.07a1.898 1.898 0 0 1 3.072 1.492v2.108a.674.674 0 1 1-1.348 0V4.898a.55.55 0 0 0-.89-.432l-3.902 3.07A.674.674 0 0 1 12 7.68Z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.7 KiB |
@@ -0,0 +1,7 @@
|
||||
<svg viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M16 1a3 3 0 0 1 3 3v12a3 3 0 0 1-3 3H4a3 3 0 0 1-3-3V4a3 3 0 0 1 3-3h12ZM4.2 2.2a2 2 0 0 0-2 2v11.6a2 2 0 0 0 2 2h11.6a2 2 0 0 0 2-2V4.2a2 2 0 0 0-2-2H4.2Z" fill="currentColor"/>
|
||||
<rect x="4" y="5" width="12" height="1.4" rx=".7" fill="currentColor"/>
|
||||
<rect x="4" y="8" width="8" height="1.4" rx=".7" fill="currentColor"/>
|
||||
<rect x="4" y="10.801" width="12" height="1.4" rx=".7" fill="currentColor"/>
|
||||
<rect x="4" y="13.6" width="9" height="1.4" rx=".7" fill="currentColor"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 572 B |
@@ -10,6 +10,12 @@ import { CONTRACT_INFO_API_RESOURCES } from './services/contractInfo';
|
||||
import type { ContractInfoApiPaginationFilters, ContractInfoApiResourceName, ContractInfoApiResourcePayload } from './services/contractInfo';
|
||||
import { GENERAL_API_RESOURCES } from './services/general';
|
||||
import type { GeneralApiResourceName, GeneralApiResourcePayload, GeneralApiPaginationFilters, GeneralApiPaginationSorting } from './services/general';
|
||||
import type {
|
||||
InterchainIndexerApiPaginationFilters,
|
||||
InterchainIndexerApiResourceName,
|
||||
InterchainIndexerApiResourcePayload,
|
||||
} from './services/interchainIndexer';
|
||||
import { INTERCHAIN_INDEXER_API_RESOURCES } from './services/interchainIndexer';
|
||||
import type { MetadataApiResourceName, MetadataApiResourcePayload } from './services/metadata';
|
||||
import { METADATA_API_RESOURCES } from './services/metadata';
|
||||
import type {
|
||||
@@ -43,6 +49,7 @@ export const RESOURCES = {
|
||||
clusters: CLUSTERS_API_RESOURCES,
|
||||
contractInfo: CONTRACT_INFO_API_RESOURCES,
|
||||
general: GENERAL_API_RESOURCES,
|
||||
interchainIndexer: INTERCHAIN_INDEXER_API_RESOURCES,
|
||||
metadata: METADATA_API_RESOURCES,
|
||||
multichainAggregator: MULTICHAIN_AGGREGATOR_API_RESOURCES,
|
||||
multichainStats: MULTICHAIN_STATS_API_RESOURCES,
|
||||
@@ -79,6 +86,7 @@ R extends BensApiResourceName ? BensApiResourcePayload<R> :
|
||||
R extends ClustersApiResourceName ? ClustersApiResourcePayload<R> :
|
||||
R extends ContractInfoApiResourceName ? ContractInfoApiResourcePayload<R> :
|
||||
R extends GeneralApiResourceName ? GeneralApiResourcePayload<R> :
|
||||
R extends InterchainIndexerApiResourceName ? InterchainIndexerApiResourcePayload<R> :
|
||||
R extends MetadataApiResourceName ? MetadataApiResourcePayload<R> :
|
||||
R extends MultichainAggregatorApiResourceName ? MultichainAggregatorApiResourcePayload<R> :
|
||||
R extends MultichainStatsApiResourceName ? MultichainStatsApiResourcePayload<R> :
|
||||
@@ -118,6 +126,7 @@ R extends BensApiResourceName ? BensApiPaginationFilters<R> :
|
||||
R extends ClustersApiResourceName ? ClustersApiPaginationFilters :
|
||||
R extends GeneralApiResourceName ? GeneralApiPaginationFilters<R> :
|
||||
R extends ContractInfoApiResourceName ? ContractInfoApiPaginationFilters<R> :
|
||||
R extends InterchainIndexerApiResourceName ? InterchainIndexerApiPaginationFilters<R> :
|
||||
R extends MultichainAggregatorApiResourceName ? MultichainAggregatorApiPaginationFilters<R> :
|
||||
R extends TacOperationLifecycleApiResourceName ? TacOperationLifecycleApiPaginationFilters<R> :
|
||||
R extends ZetaChainApiResourceName ? ZetaChainApiPaginationFilters<R> :
|
||||
|
||||
@@ -58,6 +58,9 @@ export const GENERAL_API_ACCOUNT_RESOURCES = {
|
||||
auth_logout: {
|
||||
path: '/api/account/auth/logout',
|
||||
},
|
||||
auth_dynamic: {
|
||||
path: '/api/account/v2/authenticate_via_dynamic',
|
||||
},
|
||||
} satisfies Record<string, ApiResource>;
|
||||
|
||||
export type GeneralApiAccountResourceName = `general:${ keyof typeof GENERAL_API_ACCOUNT_RESOURCES }`;
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
import type { ApiResource } from '../types';
|
||||
import type * as interchainIndexer from '@blockscout/interchain-indexer-types';
|
||||
import type { CrossChainMessageFilters, CrossChainTransferFilters } from 'types/api/interchainIndexer';
|
||||
|
||||
export const INTERCHAIN_INDEXER_API_RESOURCES = {
|
||||
messages: {
|
||||
path: '/api/v1/interchain/messages',
|
||||
filterFields: [ 'q' as const ],
|
||||
paginated: true,
|
||||
},
|
||||
message: {
|
||||
path: '/api/v1/interchain/messages/:id',
|
||||
pathParams: [ 'id' as const ],
|
||||
},
|
||||
tx_messages: {
|
||||
path: '/api/v1/interchain/messages\\:byTx/:hash',
|
||||
pathParams: [ 'hash' as const ],
|
||||
filterFields: [ 'q' as const ],
|
||||
paginated: true,
|
||||
},
|
||||
address_messages: {
|
||||
path: '/api/v1/interchain/messages\\:byAddress/:hash',
|
||||
pathParams: [ 'hash' as const ],
|
||||
paginated: true,
|
||||
},
|
||||
transfers: {
|
||||
path: '/api/v1/interchain/transfers',
|
||||
filterFields: [ 'q' as const ],
|
||||
paginated: true,
|
||||
},
|
||||
tx_transfers: {
|
||||
path: '/api/v1/interchain/transfers\\:byTx/:hash',
|
||||
pathParams: [ 'hash' as const ],
|
||||
filterFields: [ 'q' as const ],
|
||||
paginated: true,
|
||||
},
|
||||
address_transfers: {
|
||||
path: '/api/v1/interchain/transfers\\:byAddress/:hash',
|
||||
pathParams: [ 'hash' as const ],
|
||||
paginated: true,
|
||||
},
|
||||
stats_daily: {
|
||||
path: '/api/v1/stats/daily',
|
||||
},
|
||||
stats_common: {
|
||||
path: '/api/v1/stats/common',
|
||||
},
|
||||
} satisfies Record<string, ApiResource>;
|
||||
|
||||
export type InterchainIndexerApiResourceName = `interchainIndexer:${ keyof typeof INTERCHAIN_INDEXER_API_RESOURCES }`;
|
||||
|
||||
/* eslint-disable @stylistic/indent */
|
||||
export type InterchainIndexerApiResourcePayload<R extends InterchainIndexerApiResourceName> =
|
||||
R extends 'interchainIndexer:messages' ? interchainIndexer.GetMessagesResponse :
|
||||
R extends 'interchainIndexer:message' ? interchainIndexer.InterchainMessage :
|
||||
R extends 'interchainIndexer:tx_messages' ? interchainIndexer.GetMessagesResponse :
|
||||
R extends 'interchainIndexer:address_messages' ? interchainIndexer.GetMessagesResponse :
|
||||
R extends 'interchainIndexer:transfers' ? interchainIndexer.GetTransfersResponse :
|
||||
R extends 'interchainIndexer:tx_transfers' ? interchainIndexer.GetTransfersResponse :
|
||||
R extends 'interchainIndexer:address_transfers' ? interchainIndexer.GetTransfersResponse :
|
||||
R extends 'interchainIndexer:stats_daily' ? interchainIndexer.GetDailyStatisticsResponse :
|
||||
R extends 'interchainIndexer:stats_common' ? interchainIndexer.GetCommonStatisticsResponse :
|
||||
never;
|
||||
/* eslint-enable @stylistic/indent */
|
||||
|
||||
/* eslint-disable @stylistic/indent */
|
||||
export type InterchainIndexerApiPaginationFilters<R extends InterchainIndexerApiResourceName> =
|
||||
R extends 'interchainIndexer:messages' ? CrossChainMessageFilters :
|
||||
R extends 'interchainIndexer:transfers' ? CrossChainTransferFilters :
|
||||
never;
|
||||
/* eslint-enable @stylistic/indent */
|
||||
@@ -1,5 +1,5 @@
|
||||
export type ApiName =
|
||||
'general' | 'admin' | 'bens' | 'contractInfo' | 'clusters' | 'external' |
|
||||
'general' | 'admin' | 'bens' | 'contractInfo' | 'clusters' | 'external' | 'interchainIndexer' |
|
||||
'metadata' | 'multichainAggregator' | 'multichainStats' | 'rewards' | 'stats' | 'tac' |
|
||||
'userOps' | 'visualize' | 'zetachain';
|
||||
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
import type React from 'react';
|
||||
|
||||
export const FallbackProvider = ({ children }: { children: React.ReactNode }) => {
|
||||
return children;
|
||||
};
|
||||
@@ -26,6 +26,7 @@ export enum NAMES {
|
||||
UUID = 'uuid',
|
||||
SHOW_SCAM_TOKENS = 'show_scam_tokens',
|
||||
APP_PROFILE = 'app_profile',
|
||||
TABLE_VIEW_ON_MOBILE = 'table_view_on_mobile',
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -39,8 +39,8 @@ dayjs.extend(utc);
|
||||
|
||||
dayjs.updateLocale('en', {
|
||||
formats: {
|
||||
llll: `MMM DD YYYY HH:mm:ss A (Z${ nbsp }UTC)`,
|
||||
lll: 'MMM D, YYYY h:mm A',
|
||||
llll: `MMM DD YYYY HH:mm:ss (Z${ nbsp }UTC)`,
|
||||
lll: 'MMM D, YYYY H:mm',
|
||||
},
|
||||
relativeTime: {
|
||||
s: '1s',
|
||||
@@ -68,5 +68,5 @@ export default dayjs;
|
||||
|
||||
export const FORMATS = {
|
||||
// the "lll" format with seconds
|
||||
lll_s: 'MMM D, YYYY h:mm:ss A',
|
||||
lll_s: 'MMM D, YYYY H:mm:ss',
|
||||
};
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
export default function getErrorStack(error: Error | undefined): string | undefined {
|
||||
return (
|
||||
error && 'stack' in error &&
|
||||
typeof error.stack === 'string' && error.stack !== null &&
|
||||
error.stack
|
||||
) ||
|
||||
undefined;
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import { STORAGE_KEY, STORAGE_LIMIT } from './consts';
|
||||
|
||||
export interface GrowthBookFeatures {
|
||||
test_value: string;
|
||||
txns_view_exp: 'table_view' | 'list_view';
|
||||
}
|
||||
|
||||
export const initGrowthBook = (uuid: string) => {
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import React from 'react';
|
||||
|
||||
import * as cookies from 'lib/cookies';
|
||||
import useFeatureValue from 'lib/growthbook/useFeatureValue';
|
||||
import * as mixpanel from 'lib/mixpanel';
|
||||
|
||||
export default function useTableViewValue() {
|
||||
const cookieValue = cookies.get(cookies.NAMES.TABLE_VIEW_ON_MOBILE);
|
||||
const [ value, setValue ] = React.useState<boolean | undefined>(cookieValue ? cookieValue === 'true' : undefined);
|
||||
const { value: featureFlag, isLoading: isFeatureLoading } = useFeatureValue('txns_view_exp', 'list_view');
|
||||
|
||||
const onToggle = React.useCallback(() => {
|
||||
setValue((prev) => {
|
||||
const nextValue = !prev;
|
||||
cookies.set(cookies.NAMES.TABLE_VIEW_ON_MOBILE, nextValue ? 'true' : 'false');
|
||||
mixpanel.logEvent(mixpanel.EventTypes.PAGE_WIDGET, {
|
||||
Type: 'Txn view switch',
|
||||
Info: nextValue ? 'Table view' : 'List view',
|
||||
Source: 'Address page',
|
||||
});
|
||||
return nextValue;
|
||||
});
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!isFeatureLoading) {
|
||||
setValue((prev) => {
|
||||
if (prev === undefined) {
|
||||
return featureFlag === 'table_view';
|
||||
}
|
||||
return prev;
|
||||
});
|
||||
}
|
||||
}, [ featureFlag, isFeatureLoading ]);
|
||||
|
||||
return React.useMemo(() => {
|
||||
if (value !== undefined) {
|
||||
return {
|
||||
value,
|
||||
isLoading: false,
|
||||
onToggle,
|
||||
};
|
||||
}
|
||||
|
||||
return { value: featureFlag === 'table_view', isLoading: isFeatureLoading, onToggle };
|
||||
}, [ featureFlag, isFeatureLoading, onToggle, value ]);
|
||||
}
|
||||
@@ -69,6 +69,7 @@ const OG_TYPE_DICT: Record<Route['pathname'], OGPageType> = {
|
||||
'/operations': 'Root page',
|
||||
'/operation/[id]': 'Regular page',
|
||||
'/cc/tx/[hash]': 'Regular page',
|
||||
'/cross-chain-tx/[id]': 'Regular page',
|
||||
|
||||
// multichain routes
|
||||
'/chain/[chain_slug]/accounts/label/[slug]': 'Root page',
|
||||
|
||||
@@ -72,6 +72,7 @@ const TEMPLATE_MAP: Record<Route['pathname'], string> = {
|
||||
'/operations': DEFAULT_TEMPLATE,
|
||||
'/operation/[id]': DEFAULT_TEMPLATE,
|
||||
'/cc/tx/[hash]': DEFAULT_TEMPLATE,
|
||||
'/cross-chain-tx/[id]': DEFAULT_TEMPLATE,
|
||||
|
||||
// multichain routes
|
||||
'/chain/[chain_slug]/accounts/label/[slug]': DEFAULT_TEMPLATE,
|
||||
|
||||
@@ -73,6 +73,7 @@ const TEMPLATE_MAP: Record<Route['pathname'], string> = {
|
||||
'/operations': '%network_name% operations',
|
||||
'/operation/[id]': '%network_name% operation %id%',
|
||||
'/cc/tx/[hash]': '%network_name% cross-chain transaction %hash% details',
|
||||
'/cross-chain-tx/[id]': '%network_name% cross-chain transaction %id% details',
|
||||
|
||||
// multichain routes
|
||||
'/chain/[chain_slug]/accounts/label/[slug]': '%network_name% addresses search by label',
|
||||
|
||||
@@ -67,6 +67,7 @@ export const PAGE_TYPE_DICT: Record<Route['pathname'], string> = {
|
||||
'/operations': 'Operations',
|
||||
'/operation/[id]': 'Operation details',
|
||||
'/cc/tx/[hash]': 'Cross-chain transaction details',
|
||||
'/cross-chain-tx/[id]': 'Cross-chain transaction details',
|
||||
|
||||
// multichain routes
|
||||
'/chain/[chain_slug]/accounts/label/[slug]': 'Chain addresses search by label',
|
||||
|
||||
@@ -15,7 +15,7 @@ import * as userProfile from './userProfile';
|
||||
const opSuperchainFeature = config.features.opSuperchain;
|
||||
|
||||
export default function useMixpanelInit() {
|
||||
const [ isInited, setIsInited ] = React.useState(false);
|
||||
const [ isInitialized, setIsInitialized ] = React.useState(false);
|
||||
const router = useRouter();
|
||||
const debugFlagQuery = React.useRef(getQueryParamString(router.query._mixpanel_debug));
|
||||
|
||||
@@ -57,11 +57,11 @@ export default function useMixpanelInit() {
|
||||
'First Time Join': dayjs().toISOString(),
|
||||
});
|
||||
|
||||
setIsInited(true);
|
||||
setIsInitialized(true);
|
||||
if (debugFlagQuery.current && !debugFlagCookie) {
|
||||
cookies.set(cookies.NAMES.MIXPANEL_DEBUG, 'true');
|
||||
}
|
||||
}, [ ]);
|
||||
|
||||
return isInited;
|
||||
return isInitialized;
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ import getTabName from './getTabName';
|
||||
import logEvent from './logEvent';
|
||||
import { EventTypes } from './utils';
|
||||
|
||||
export default function useLogPageView(isInited: boolean) {
|
||||
export default function useLogPageView(isInitialized: boolean) {
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
|
||||
@@ -25,7 +25,7 @@ export default function useLogPageView(isInited: boolean) {
|
||||
const { colorMode } = useColorMode();
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!config.features.mixpanel.isEnabled || !isInited) {
|
||||
if (!config.features.mixpanel.isEnabled || !isInitialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -44,5 +44,5 @@ export default function useLogPageView(isInited: boolean) {
|
||||
// but we still want to log page view
|
||||
// so we use pathname from 'next/navigation' instead of router.pathname from 'next/router' as deps
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [ isInited, page, pathname, tab, colorMode ]);
|
||||
}, [ isInitialized, page, pathname, tab, colorMode ]);
|
||||
}
|
||||
|
||||
@@ -73,7 +73,7 @@ Type extends EventTypes.LOGIN ? (
|
||||
Source: 'Email';
|
||||
} | {
|
||||
Action: 'Success';
|
||||
Source: 'Email' | 'Wallet';
|
||||
Source: 'Email' | 'Wallet' | 'Dynamic';
|
||||
}
|
||||
) :
|
||||
Type extends EventTypes.ACCOUNT_LINK_INFO ? {
|
||||
@@ -147,6 +147,10 @@ Type extends EventTypes.PAGE_WIDGET ? (
|
||||
Type: 'Chain switch';
|
||||
Info: string;
|
||||
Source: 'Revoke essential dapp';
|
||||
} | {
|
||||
Type: 'Txn view switch';
|
||||
Info: 'Table view' | 'List view';
|
||||
Source: 'Address page';
|
||||
}
|
||||
) :
|
||||
Type extends EventTypes.TX_INTERPRETATION_INTERACTION ? {
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import { useDynamicContext, useUserWallets } from '@dynamic-labs/sdk-react-core';
|
||||
import React from 'react';
|
||||
import type { UseAccountReturnType } from 'wagmi';
|
||||
|
||||
export default function useAccountDynamic(): UseAccountReturnType {
|
||||
const userWallets = useUserWallets();
|
||||
const { primaryWallet } = useDynamicContext();
|
||||
|
||||
const address = (primaryWallet?.address || userWallets[0]?.address) as `0x${ string }` | undefined;
|
||||
|
||||
return React.useMemo(() => {
|
||||
if (!address) {
|
||||
return {
|
||||
address: undefined,
|
||||
addresses: undefined,
|
||||
chain: undefined,
|
||||
chainId: undefined,
|
||||
connector: undefined,
|
||||
isConnected: false,
|
||||
isConnecting: false,
|
||||
isDisconnected: true,
|
||||
isReconnecting: false,
|
||||
status: 'disconnected',
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
address,
|
||||
addresses: [ address ],
|
||||
chain: undefined,
|
||||
chainId: undefined,
|
||||
connector: undefined,
|
||||
isConnected: true,
|
||||
isConnecting: false,
|
||||
isDisconnected: false,
|
||||
isReconnecting: false,
|
||||
status: 'connected',
|
||||
} as unknown as UseAccountReturnType;
|
||||
}, [ address ]);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import React from 'react';
|
||||
import type { UseAccountReturnType } from 'wagmi';
|
||||
|
||||
export default function useAccountFallback(): UseAccountReturnType {
|
||||
return React.useMemo(() => ({
|
||||
address: undefined,
|
||||
addresses: undefined,
|
||||
chain: undefined,
|
||||
chainId: undefined,
|
||||
connector: undefined,
|
||||
isConnected: false,
|
||||
isConnecting: false,
|
||||
isDisconnected: true,
|
||||
isReconnecting: false,
|
||||
status: 'disconnected',
|
||||
}), []);
|
||||
}
|
||||
@@ -4,7 +4,11 @@ import appConfig from 'configs/app';
|
||||
import essentialDappsChainsConfig from 'configs/essential-dapps-chains';
|
||||
import multichainConfig from 'configs/multichain';
|
||||
|
||||
const getChainInfo = (config: Partial<typeof appConfig> = appConfig, contracts?: Chain['contracts']): Chain | undefined => {
|
||||
const getChainInfo = (
|
||||
config: Partial<typeof appConfig> = appConfig,
|
||||
contracts?: Chain['contracts'],
|
||||
logoUrl?: string,
|
||||
): Chain | undefined => {
|
||||
if (!config.chain || !config.app) {
|
||||
return;
|
||||
}
|
||||
@@ -30,6 +34,9 @@ const getChainInfo = (config: Partial<typeof appConfig> = appConfig, contracts?:
|
||||
},
|
||||
testnet: config.chain.isTestnet,
|
||||
contracts,
|
||||
custom: {
|
||||
logoUrl: logoUrl ?? config.UI?.navigation.icon.default,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@@ -74,7 +81,7 @@ export const clusterChains: Array<Chain> | undefined = (() => {
|
||||
return;
|
||||
}
|
||||
|
||||
return config.chains.map(({ app_config: config }) => getChainInfo(config)).filter(Boolean);
|
||||
return config.chains.map(({ app_config: config, logo }) => getChainInfo(config, undefined, logo)).filter(Boolean);
|
||||
})();
|
||||
|
||||
export const essentialDappsChains: Array<Chain> | undefined = (() => {
|
||||
@@ -84,7 +91,7 @@ export const essentialDappsChains: Array<Chain> | undefined = (() => {
|
||||
return;
|
||||
}
|
||||
|
||||
return config.chains.map(({ app_config: config, contracts }) => getChainInfo(config, contracts)).filter(Boolean);
|
||||
return config.chains.map(({ app_config: config, contracts, logo }) => getChainInfo(config, contracts, logo)).filter(Boolean);
|
||||
})();
|
||||
|
||||
export const chains = (() => {
|
||||
|
||||
@@ -1,23 +1,12 @@
|
||||
import type { UseAccountReturnType } from 'wagmi';
|
||||
import { useAccount } from 'wagmi';
|
||||
|
||||
import config from 'configs/app';
|
||||
|
||||
function useAccountFallback(): UseAccountReturnType {
|
||||
return {
|
||||
address: undefined,
|
||||
addresses: undefined,
|
||||
chain: undefined,
|
||||
chainId: undefined,
|
||||
connector: undefined,
|
||||
isConnected: false,
|
||||
isConnecting: false,
|
||||
isDisconnected: true,
|
||||
isReconnecting: false,
|
||||
status: 'disconnected',
|
||||
};
|
||||
}
|
||||
const feature = config.features.blockchainInteraction;
|
||||
|
||||
const hook = config.features.blockchainInteraction.isEnabled ? useAccount : useAccountFallback;
|
||||
// eslint-disable-next-line no-nested-ternary
|
||||
const useAccount = (feature.isEnabled && feature.connectorType === 'dynamic') ?
|
||||
(await import('./account/useAccountDynamic')).default :
|
||||
(feature.isEnabled && feature.connectorType === 'reown') ?
|
||||
(await import('wagmi')).useAccount :
|
||||
(await import('./account/useAccountFallback')).default;
|
||||
|
||||
export default hook;
|
||||
export default useAccount;
|
||||
|
||||
@@ -1,63 +1,12 @@
|
||||
import { useAppKit, useAppKitState } from '@reown/appkit/react';
|
||||
import React from 'react';
|
||||
import { useDisconnect, useAccountEffect } from 'wagmi';
|
||||
import config from 'configs/app';
|
||||
|
||||
import * as mixpanel from 'lib/mixpanel/index';
|
||||
import useAccount from 'lib/web3/useAccount';
|
||||
const feature = config.features.blockchainInteraction;
|
||||
|
||||
interface Params {
|
||||
source: mixpanel.EventPayload<mixpanel.EventTypes.WALLET_CONNECT>['Source'];
|
||||
onConnect?: () => void;
|
||||
}
|
||||
// eslint-disable-next-line no-nested-ternary
|
||||
const useWallet = (feature.isEnabled && feature.connectorType === 'dynamic') ?
|
||||
(await import('./wallet/useWalletDynamic')).default :
|
||||
(feature.isEnabled && feature.connectorType === 'reown') ?
|
||||
(await import('./wallet/useWalletReown')).default :
|
||||
(await import('./wallet/useWalletFallback')).default;
|
||||
|
||||
export default function useWeb3Wallet({ source, onConnect }: Params) {
|
||||
const { open: openModal } = useAppKit();
|
||||
const { open: isOpen } = useAppKitState();
|
||||
const { disconnect } = useDisconnect();
|
||||
const [ isOpening, setIsOpening ] = React.useState(false);
|
||||
const [ isClientLoaded, setIsClientLoaded ] = React.useState(false);
|
||||
const isConnectionStarted = React.useRef(false);
|
||||
|
||||
React.useEffect(() => {
|
||||
setIsClientLoaded(true);
|
||||
}, []);
|
||||
|
||||
const handleConnect = React.useCallback(async() => {
|
||||
setIsOpening(true);
|
||||
await openModal();
|
||||
setIsOpening(false);
|
||||
mixpanel.logEvent(mixpanel.EventTypes.WALLET_CONNECT, { Source: source, Status: 'Started' });
|
||||
isConnectionStarted.current = true;
|
||||
}, [ openModal, source ]);
|
||||
|
||||
const handleAccountConnected = React.useCallback(({ isReconnected }: { isReconnected: boolean }) => {
|
||||
if (!isReconnected && isConnectionStarted.current) {
|
||||
mixpanel.logEvent(mixpanel.EventTypes.WALLET_CONNECT, { Source: source, Status: 'Connected' });
|
||||
mixpanel.userProfile.setOnce({
|
||||
'With Connected Wallet': true,
|
||||
});
|
||||
onConnect?.();
|
||||
}
|
||||
isConnectionStarted.current = false;
|
||||
}, [ source, onConnect ]);
|
||||
|
||||
const handleDisconnect = React.useCallback(() => {
|
||||
disconnect();
|
||||
}, [ disconnect ]);
|
||||
|
||||
useAccountEffect({ onConnect: handleAccountConnected });
|
||||
|
||||
const account = useAccount();
|
||||
const address = account.address;
|
||||
const isConnected = isClientLoaded && !account.isDisconnected && account.address !== undefined;
|
||||
|
||||
return React.useMemo(() => ({
|
||||
connect: handleConnect,
|
||||
disconnect: handleDisconnect,
|
||||
isOpen: isOpening || isOpen,
|
||||
isConnected,
|
||||
isReconnecting: account.isReconnecting,
|
||||
address,
|
||||
openModal,
|
||||
}), [ handleConnect, handleDisconnect, isOpening, isOpen, isConnected, account.isReconnecting, address, openModal ]);
|
||||
}
|
||||
export default useWallet;
|
||||
|
||||
@@ -19,7 +19,7 @@ const getChainTransportFromConfig = (config: Partial<typeof appConfig> | undefin
|
||||
return {
|
||||
[config.chain.id]: fallback(
|
||||
config.chain.rpcUrls
|
||||
.concat(readOnly && config.apis?.general ? `${ config.apis.general.endpoint }/api/eth-rpc` : '')
|
||||
.concat(readOnly && config.apis?.general ? `${ config.apis.general.endpoint }${ config.apis.general.basePath ?? '' }/api/eth-rpc` : '')
|
||||
.filter(Boolean)
|
||||
.map((url) => http(url, { batch: { wait: 100, batchSize: 5 } })),
|
||||
),
|
||||
@@ -47,7 +47,7 @@ const reduceExternalChainsToTransportConfig = (readOnly: boolean): Record<string
|
||||
|
||||
const wagmi = (() => {
|
||||
|
||||
if (!feature.isEnabled) {
|
||||
if (!feature.isEnabled || feature.connectorType === 'dynamic') {
|
||||
const wagmiConfig = createConfig({
|
||||
chains: chains as [Chain, ...Array<Chain>],
|
||||
transports: {
|
||||
@@ -57,6 +57,7 @@ const wagmi = (() => {
|
||||
},
|
||||
ssr: true,
|
||||
batch: { multicall: { wait: 100, batchSize: 5 } },
|
||||
multiInjectedProviderDiscovery: feature.isEnabled && feature.connectorType === 'dynamic' ? false : true,
|
||||
});
|
||||
|
||||
return { config: wagmiConfig, adapter: null };
|
||||
@@ -70,7 +71,7 @@ const wagmi = (() => {
|
||||
...(parentChain ? { [parentChain.id]: http() } : {}),
|
||||
...reduceExternalChainsToTransportConfig(false),
|
||||
},
|
||||
projectId: feature.walletConnect.projectId,
|
||||
projectId: feature.reown.projectId,
|
||||
ssr: true,
|
||||
batch: { multicall: { wait: 100, batchSize: 5 } },
|
||||
syncConnectedChain: false,
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import type * as mixpanel from 'lib/mixpanel/index';
|
||||
|
||||
export interface Params {
|
||||
source: mixpanel.EventPayload<mixpanel.EventTypes.WALLET_CONNECT>['Source'];
|
||||
onConnect?: () => void;
|
||||
}
|
||||
|
||||
export interface Result {
|
||||
connect: () => void;
|
||||
disconnect: () => void;
|
||||
isOpen: boolean;
|
||||
isConnected: boolean;
|
||||
isReconnecting: boolean;
|
||||
address: string | undefined;
|
||||
openModal: () => void;
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
import { useDynamicContext, useDynamicEvents, useUserWallets } from '@dynamic-labs/sdk-react-core';
|
||||
import React from 'react';
|
||||
import { useAccountEffect } from 'wagmi';
|
||||
|
||||
import type { Params, Result } from './types';
|
||||
|
||||
import * as mixpanel from 'lib/mixpanel/index';
|
||||
|
||||
import useAccountDynamic from '../account/useAccountDynamic';
|
||||
|
||||
export default function useWalletDynamic({ source, onConnect }: Params): Result {
|
||||
const isConnectionStarted = React.useRef(false);
|
||||
const [ isOpen ] = React.useState(false);
|
||||
const [ isClientLoaded, setIsClientLoaded ] = React.useState(false);
|
||||
|
||||
const { setShowDynamicUserProfile, setShowAuthFlow, setAuthMode, removeWallet } = useDynamicContext();
|
||||
|
||||
const openModal = React.useCallback(() => {
|
||||
setShowDynamicUserProfile(true);
|
||||
mixpanel.logEvent(mixpanel.EventTypes.ACCOUNT_ACCESS, { Action: 'Dropdown open' });
|
||||
}, [ setShowDynamicUserProfile ]);
|
||||
|
||||
useDynamicEvents('authFlowOpen', async() => {
|
||||
if (!isConnectionStarted.current) {
|
||||
mixpanel.logEvent(mixpanel.EventTypes.WALLET_CONNECT, { Source: source, Status: 'Started' });
|
||||
isConnectionStarted.current = true;
|
||||
}
|
||||
});
|
||||
|
||||
const handleAccountConnected = React.useCallback(({ isReconnected }: { isReconnected: boolean }) => {
|
||||
if (!isReconnected && isConnectionStarted.current) {
|
||||
mixpanel.logEvent(mixpanel.EventTypes.WALLET_CONNECT, { Source: source, Status: 'Connected' });
|
||||
mixpanel.userProfile.setOnce({
|
||||
'With Connected Wallet': true,
|
||||
});
|
||||
onConnect?.();
|
||||
}
|
||||
isConnectionStarted.current = false;
|
||||
}, [ source, onConnect ]);
|
||||
|
||||
const handleConnect = React.useCallback(() => {
|
||||
setAuthMode('connect-only');
|
||||
setShowAuthFlow(true);
|
||||
}, [ setAuthMode, setShowAuthFlow ]);
|
||||
|
||||
const userWallets = useUserWallets();
|
||||
const primaryWalletId = userWallets[0]?.id;
|
||||
|
||||
const handleDisconnect = React.useCallback(() => {
|
||||
primaryWalletId && removeWallet(primaryWalletId);
|
||||
}, [ primaryWalletId, removeWallet ]);
|
||||
|
||||
useAccountEffect({ onConnect: handleAccountConnected });
|
||||
|
||||
React.useEffect(() => {
|
||||
setIsClientLoaded(true);
|
||||
}, []);
|
||||
|
||||
const account = useAccountDynamic();
|
||||
const address = account.address;
|
||||
const isConnected = isClientLoaded && !account.isDisconnected && account.address !== undefined;
|
||||
|
||||
return React.useMemo(() => ({
|
||||
connect: handleConnect,
|
||||
disconnect: handleDisconnect,
|
||||
isOpen,
|
||||
isConnected,
|
||||
isReconnecting: false,
|
||||
address,
|
||||
openModal,
|
||||
}), [ handleConnect, handleDisconnect, isOpen, isConnected, address, openModal ]);
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import React from 'react';
|
||||
|
||||
import type { Result } from './types';
|
||||
|
||||
export default function useWalletFallback(): Result {
|
||||
return React.useMemo(() => ({
|
||||
connect: () => {},
|
||||
disconnect: () => {},
|
||||
isOpen: false,
|
||||
isConnected: false,
|
||||
isReconnecting: false,
|
||||
address: undefined,
|
||||
openModal: () => {},
|
||||
}), []);
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import { useAppKit, useAppKitState } from '@reown/appkit/react';
|
||||
import React from 'react';
|
||||
import { useAccountEffect, useAccount, useDisconnect } from 'wagmi';
|
||||
|
||||
import type { Params, Result } from './types';
|
||||
|
||||
import * as mixpanel from 'lib/mixpanel/index';
|
||||
|
||||
export default function useWalletReown({ source, onConnect }: Params): Result {
|
||||
const { open: openModal } = useAppKit();
|
||||
const { open: isOpen } = useAppKitState();
|
||||
const { disconnect } = useDisconnect();
|
||||
const [ isOpening, setIsOpening ] = React.useState(false);
|
||||
const [ isClientLoaded, setIsClientLoaded ] = React.useState(false);
|
||||
const isConnectionStarted = React.useRef(false);
|
||||
|
||||
React.useEffect(() => {
|
||||
setIsClientLoaded(true);
|
||||
}, []);
|
||||
|
||||
const handleConnect = React.useCallback(async() => {
|
||||
setIsOpening(true);
|
||||
await openModal();
|
||||
setIsOpening(false);
|
||||
mixpanel.logEvent(mixpanel.EventTypes.WALLET_CONNECT, { Source: source, Status: 'Started' });
|
||||
isConnectionStarted.current = true;
|
||||
}, [ openModal, source ]);
|
||||
|
||||
const handleAccountConnected = React.useCallback(({ isReconnected }: { isReconnected: boolean }) => {
|
||||
if (!isReconnected && isConnectionStarted.current) {
|
||||
mixpanel.logEvent(mixpanel.EventTypes.WALLET_CONNECT, { Source: source, Status: 'Connected' });
|
||||
mixpanel.userProfile.setOnce({
|
||||
'With Connected Wallet': true,
|
||||
});
|
||||
onConnect?.();
|
||||
}
|
||||
isConnectionStarted.current = false;
|
||||
}, [ source, onConnect ]);
|
||||
|
||||
const handleDisconnect = React.useCallback(() => {
|
||||
disconnect();
|
||||
}, [ disconnect ]);
|
||||
|
||||
useAccountEffect({ onConnect: handleAccountConnected });
|
||||
|
||||
const account = useAccount();
|
||||
const address = account.address;
|
||||
const isConnected = isClientLoaded && !account.isDisconnected && account.address !== undefined;
|
||||
|
||||
return React.useMemo(() => ({
|
||||
connect: handleConnect,
|
||||
disconnect: handleDisconnect,
|
||||
isOpen: isOpening || isOpen,
|
||||
isConnected,
|
||||
isReconnecting: account.isReconnecting,
|
||||
address,
|
||||
openModal,
|
||||
}), [ handleConnect, handleDisconnect, isOpening, isOpen, isConnected, account.isReconnecting, address, openModal ]);
|
||||
}
|
||||
@@ -218,7 +218,7 @@ export const withBlobTxs: Block = {
|
||||
blob_gas_used: '393216',
|
||||
burnt_blob_fees: '8461393325064192',
|
||||
excess_blob_gas: '79429632',
|
||||
blob_transaction_count: 1,
|
||||
blob_transactions_count: 1,
|
||||
};
|
||||
|
||||
export const withWithdrawals: Block = {
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import type { ExternalChain } from 'types/externalChains';
|
||||
|
||||
export const config: Array<ExternalChain> = [
|
||||
{
|
||||
id: '43114',
|
||||
name: 'C-Chain',
|
||||
logo: 'https://c-chain.com/logo.svg',
|
||||
explorer_url: 'https://c-chain.com/explorer',
|
||||
route_templates: {
|
||||
tx: '/transaction/{hash}',
|
||||
address: '/wallet/{hash}',
|
||||
token: '/address/{hash}',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: '8021',
|
||||
name: 'Numine',
|
||||
logo: 'https://numine.com/logo.svg',
|
||||
explorer_url: 'https://numine.com',
|
||||
},
|
||||
{
|
||||
id: '4444',
|
||||
name: 'Duck chain',
|
||||
logo: undefined,
|
||||
explorer_url: 'https://duck.com',
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,73 @@
|
||||
import type { GetTransfersResponse, InterchainTransfer } from '@blockscout/interchain-indexer-types';
|
||||
import { MessageStatus } from '@blockscout/interchain-indexer-types';
|
||||
|
||||
import { config } from './config';
|
||||
|
||||
export const transferA = {
|
||||
bridge: {
|
||||
name: 'Avalanche ICTT',
|
||||
ui_url: 'https://app.avax.network/',
|
||||
},
|
||||
message_id: '0x057b42bbbfbb4900e155a554ae67632cb21e6f5a64d815fcad7f33abe552c059',
|
||||
status: MessageStatus.MESSAGE_STATUS_COMPLETED,
|
||||
source_chain: config[0],
|
||||
destination_chain: config[1],
|
||||
source_token: {
|
||||
address_hash: '0x33a31e0f62c0ddf25090b61ef21a70d5f48725b7',
|
||||
name: 'Wrapped AVAX',
|
||||
symbol: 'WAVAX',
|
||||
decimals: '18',
|
||||
icon_url: 'https://app.avax.network/logo.svg',
|
||||
},
|
||||
source_amount: '509700000000000000',
|
||||
source_transaction_hash: '0x866a70cb1c8c33d259c819473d7b419c0de67770755bf07dee14dd2d0c6dc8ab',
|
||||
sender: {
|
||||
hash: '0xd7e63822d0e386fb65f28f41e8c1aa8d844ba018',
|
||||
ens_domain_name: 'kitty.kitty.kitty.kitty.cat.eth',
|
||||
},
|
||||
send_timestamp: '2022-01-13T12:06:24.000Z',
|
||||
destination_token: {
|
||||
address_hash: '0x012cb6651cb29c7d5dc96173756a773f7fb87cfb',
|
||||
name: 'Wrapped AVAX',
|
||||
symbol: 'WAVAX',
|
||||
decimals: '18',
|
||||
},
|
||||
destination_amount: '509700000000000000',
|
||||
destination_transaction_hash: '0xdbdf690cfde8af2ee855bb90bfa9977a2d8ba36c9ae1a2010c67fe3774832213',
|
||||
recipient: {
|
||||
hash: '0xd7e63822d0e386fb65f28f41e8c1aa8d844ba018',
|
||||
},
|
||||
receive_timestamp: '2022-01-13T12:06:30.000Z',
|
||||
} satisfies InterchainTransfer;
|
||||
|
||||
export const transferB = {
|
||||
...transferA,
|
||||
source_chain: {
|
||||
id: '420',
|
||||
name: 'Unknown chain',
|
||||
logo: undefined,
|
||||
explorer_url: undefined,
|
||||
},
|
||||
source_transaction_hash: '0x866a70cb1c8c33d259c819473d7b419c0de67770755bf07dee14dd2d0c6dc800',
|
||||
source_token: {
|
||||
address_hash: '0x33a31e0f62c0ddf25090b61ef21a70d5f48725b7',
|
||||
name: 'Circle USD',
|
||||
symbol: 'USDC',
|
||||
decimals: '6',
|
||||
},
|
||||
destination_transaction_hash: '0xdbdf690cfde8af2ee855bb90bfa9977a2d8ba36c9ae1a2010c67fe3774832214',
|
||||
status: MessageStatus.MESSAGE_STATUS_FAILED,
|
||||
bridge: {
|
||||
name: 'Optimism Superchain',
|
||||
},
|
||||
} satisfies InterchainTransfer;
|
||||
|
||||
export const listResponse = {
|
||||
items: [
|
||||
transferA,
|
||||
transferB,
|
||||
],
|
||||
next_page_params: {
|
||||
page_token: 'token',
|
||||
},
|
||||
} satisfies GetTransfersResponse;
|
||||
@@ -0,0 +1,75 @@
|
||||
import type { GetMessagesResponse, InterchainMessage } from '@blockscout/interchain-indexer-types';
|
||||
import { MessageStatus } from '@blockscout/interchain-indexer-types';
|
||||
|
||||
import { config } from './config';
|
||||
import { transferA, transferB } from './transfers';
|
||||
|
||||
/* eslint-disable max-len */
|
||||
export const base = {
|
||||
bridge: {
|
||||
name: 'Avalanche ICTT',
|
||||
ui_url: 'https://app.avax.network/',
|
||||
},
|
||||
message_id: '0x057b42bbbfbb4900e155a554ae67632cb21e6f5a64d815fcad7f33abe552c059',
|
||||
status: MessageStatus.MESSAGE_STATUS_COMPLETED,
|
||||
source_chain: config[0],
|
||||
send_timestamp: '2022-01-13T12:06:24.000Z',
|
||||
sender: {
|
||||
hash: '0x33a31e0f62c0ddf25090b61ef21a70d5f48725b7',
|
||||
},
|
||||
source_transaction_hash: '0x866a70cb1c8c33d259c819473d7b419c0de67770755bf07dee14dd2d0c6dc8ab',
|
||||
destination_chain: config[1],
|
||||
receive_timestamp: '2022-02-13T12:06:30.000Z',
|
||||
recipient: {
|
||||
hash: '0x012cb6651cb29c7d5dc96173756a773f7fb87cfb',
|
||||
ens_domain_name: 'duck-duck.eth',
|
||||
},
|
||||
destination_transaction_hash: '0xdbdf690cfde8af2ee855bb90bfa9977a2d8ba36c9ae1a2010c67fe3774832213',
|
||||
payload: '0x0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000040000000000000000000000000d7e63822d0e386fb65f28f41e8c1aa8d844ba0180000000000000000000000000000000000000000000000000712d17312044000',
|
||||
extra: {},
|
||||
transfers: [
|
||||
transferA,
|
||||
transferB,
|
||||
],
|
||||
} satisfies InterchainMessage;
|
||||
|
||||
export const pending = {
|
||||
...base,
|
||||
message_id: '0x057b42bbbfbb4900e155a554ae67632cb21e6f5a64d815fcad7f33abe552c05a',
|
||||
status: MessageStatus.MESSAGE_STATUS_INITIATED,
|
||||
source_chain: config[1],
|
||||
destination_chain: config[2],
|
||||
destination_transaction_hash: undefined,
|
||||
transfers: [
|
||||
transferA,
|
||||
],
|
||||
} satisfies InterchainMessage;
|
||||
|
||||
export const failed = {
|
||||
...base,
|
||||
message_id: '0x057b42bbbfbb4900e155a554ae67632cb21e6f5a64d815fcad7f33abe552c05b',
|
||||
status: MessageStatus.MESSAGE_STATUS_FAILED,
|
||||
source_chain: {
|
||||
id: '420',
|
||||
name: 'Unknown chain',
|
||||
logo: undefined,
|
||||
explorer_url: undefined,
|
||||
},
|
||||
transfers: [
|
||||
transferB,
|
||||
],
|
||||
bridge: {
|
||||
name: 'Goose bridge',
|
||||
},
|
||||
} satisfies InterchainMessage;
|
||||
|
||||
export const listResponse = {
|
||||
items: [
|
||||
base,
|
||||
pending,
|
||||
failed,
|
||||
],
|
||||
next_page_params: {
|
||||
page_token: '1',
|
||||
},
|
||||
} satisfies GetMessagesResponse;
|
||||
@@ -27,6 +27,7 @@ export const token1: SearchResultToken = {
|
||||
exchange_rate: null,
|
||||
is_verified_via_admin_panel: true,
|
||||
is_smart_contract_verified: true,
|
||||
is_smart_contract_address: true,
|
||||
reputation: 'ok',
|
||||
};
|
||||
|
||||
@@ -43,6 +44,7 @@ export const token2: SearchResultToken = {
|
||||
exchange_rate: '1.11',
|
||||
is_verified_via_admin_panel: false,
|
||||
is_smart_contract_verified: false,
|
||||
is_smart_contract_address: false,
|
||||
reputation: 'ok',
|
||||
};
|
||||
|
||||
@@ -77,6 +79,7 @@ export const address1: SearchResultAddressOrContract = {
|
||||
name: null,
|
||||
type: 'address' as const,
|
||||
is_smart_contract_verified: false,
|
||||
is_smart_contract_address: false,
|
||||
url: '/address/0xb64a30399f7F6b0C154c2E7Af0a3ec7B0A5b131a',
|
||||
};
|
||||
|
||||
@@ -85,6 +88,7 @@ export const address2: SearchResultAddressOrContract = {
|
||||
name: null,
|
||||
type: 'address' as const,
|
||||
is_smart_contract_verified: false,
|
||||
is_smart_contract_address: false,
|
||||
url: '/address/0xb64a30399f7F6b0C154c2E7Af0a3ec7B0A5b131b',
|
||||
ens_info: {
|
||||
address_hash: '0x1234567890123456789012345678901234567890',
|
||||
@@ -99,6 +103,7 @@ export const contract1: SearchResultAddressOrContract = {
|
||||
name: 'Unknown contract in this network',
|
||||
type: 'contract' as const,
|
||||
is_smart_contract_verified: true,
|
||||
is_smart_contract_address: true,
|
||||
url: '/address/0xb64a30399f7F6b0C154c2E7Af0a3ec7B0A5b131a',
|
||||
};
|
||||
|
||||
@@ -108,6 +113,7 @@ export const contract2: SearchResultAddressOrContract = {
|
||||
type: 'contract' as const,
|
||||
is_smart_contract_verified: true,
|
||||
certified: true,
|
||||
is_smart_contract_address: true,
|
||||
url: '/address/0xb64a30399f7F6b0C154c2E7Af0a3ec7B0A5b131a',
|
||||
};
|
||||
|
||||
@@ -116,6 +122,7 @@ export const label1: SearchResultLabel = {
|
||||
name: 'utko',
|
||||
type: 'label' as const,
|
||||
is_smart_contract_verified: true,
|
||||
is_smart_contract_address: true,
|
||||
url: '/address/0xb64a30399f7F6b0C154c2E7Af0a3ec7B0A5b131a',
|
||||
};
|
||||
|
||||
@@ -148,6 +155,7 @@ export const domain1: SearchResultDomain = {
|
||||
names_count: 1,
|
||||
},
|
||||
is_smart_contract_verified: false,
|
||||
is_smart_contract_address: false,
|
||||
name: null,
|
||||
type: 'ens_domain',
|
||||
url: '/address/0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045',
|
||||
|
||||
@@ -25,6 +25,14 @@ const moduleExports = {
|
||||
);
|
||||
config.resolve.fallback = { fs: false, net: false, tls: false };
|
||||
config.externals.push('pino-pretty', 'lokijs', 'encoding');
|
||||
|
||||
config.experiments = { ...config.experiments, topLevelAwait: true };
|
||||
// Tell webpack the target supports async/await so it stops warning about top-level await
|
||||
// Top-level await is belong to ES2017 specification that is adopted by all major browsers and Node.js.
|
||||
config.output.environment = {
|
||||
...config.output.environment,
|
||||
asyncFunction: true,
|
||||
};
|
||||
|
||||
return config;
|
||||
},
|
||||
@@ -36,7 +44,7 @@ const moduleExports = {
|
||||
redirects,
|
||||
headers,
|
||||
output: 'standalone',
|
||||
productionBrowserSourceMaps: true,
|
||||
productionBrowserSourceMaps: false,
|
||||
serverExternalPackages: ["@opentelemetry/sdk-node", "@opentelemetry/auto-instrumentations-node"],
|
||||
experimental: {
|
||||
staleTimes: {
|
||||
|
||||
@@ -24,8 +24,8 @@ const PageNextJs = <Pathname extends Route['pathname']>(props: Props<Pathname>)
|
||||
useAdblockDetect();
|
||||
useNotifyOnNavigation();
|
||||
|
||||
const isMixpanelInited = mixpanel.useInit();
|
||||
mixpanel.useLogPageView(isMixpanelInited);
|
||||
const isMixPanelInitialized = mixpanel.useInit();
|
||||
mixpanel.useLogPageView(isMixPanelInitialized);
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -6,6 +6,7 @@ function generateCspPolicy(isPrivateMode = false) {
|
||||
descriptors.app(isPrivateMode),
|
||||
// Exclude tracking/analytics sources in private mode
|
||||
isPrivateMode ? {} : descriptors.ad(),
|
||||
isPrivateMode ? {} : descriptors.blockchainInteraction(),
|
||||
descriptors.cloudFlare(),
|
||||
descriptors.flashblocks(),
|
||||
descriptors.gasHawk(),
|
||||
@@ -23,7 +24,6 @@ function generateCspPolicy(isPrivateMode = false) {
|
||||
descriptors.rollup(),
|
||||
descriptors.safe(),
|
||||
descriptors.usernameApi(),
|
||||
isPrivateMode ? {} : descriptors.walletConnect(),
|
||||
descriptors.zetachain(),
|
||||
);
|
||||
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
import type CspDev from 'csp-dev';
|
||||
|
||||
import config from 'configs/app';
|
||||
|
||||
import { KEY_WORDS } from '../utils';
|
||||
|
||||
const feature = config.features.blockchainInteraction;
|
||||
|
||||
export function blockchainInteraction(): CspDev.DirectiveDescriptor {
|
||||
if (!feature.isEnabled) {
|
||||
return {};
|
||||
}
|
||||
|
||||
switch (feature.connectorType) {
|
||||
case 'reown': {
|
||||
return {
|
||||
'connect-src': [
|
||||
'*.web3modal.com',
|
||||
'*.web3modal.org',
|
||||
'*.walletconnect.com',
|
||||
'*.walletconnect.org',
|
||||
'wss://relay.walletconnect.com',
|
||||
'wss://relay.walletconnect.org',
|
||||
'wss://www.walletlink.org',
|
||||
],
|
||||
'frame-ancestors': [
|
||||
'*.walletconnect.org',
|
||||
'*.walletconnect.com',
|
||||
],
|
||||
'img-src': [
|
||||
KEY_WORDS.BLOB,
|
||||
'*.walletconnect.com',
|
||||
],
|
||||
};
|
||||
}
|
||||
case 'dynamic': {
|
||||
return {
|
||||
'connect-src': [
|
||||
'https://dynamic-static-assets.com',
|
||||
'https://app.dynamicauth.com',
|
||||
'https://logs.dynamicauth.com',
|
||||
],
|
||||
'font-src': [
|
||||
'https://cdn.jsdelivr.net/npm/@fontsource/dm-sans/files/dm-sans-latin-400-normal.woff2',
|
||||
'https://cdn.jsdelivr.net/npm/@fontsource/dm-sans/files/dm-sans-latin-400-normal.woff',
|
||||
'https://cdn.jsdelivr.net/npm/@fontsource/dm-sans/files/dm-sans-latin-400-italic.woff2',
|
||||
'https://cdn.jsdelivr.net/npm/@fontsource/dm-sans/files/dm-sans-latin-400-italic.woff',
|
||||
'https://cdn.jsdelivr.net/npm/@fontsource/dm-sans/files/dm-sans-latin-500-normal.woff2',
|
||||
'https://cdn.jsdelivr.net/npm/@fontsource/dm-sans/files/dm-sans-latin-500-normal.woff',
|
||||
'https://cdn.jsdelivr.net/npm/@fontsource/dm-sans/files/dm-sans-latin-500-italic.woff2',
|
||||
'https://cdn.jsdelivr.net/npm/@fontsource/dm-sans/files/dm-sans-latin-500-italic.woff',
|
||||
'https://cdn.jsdelivr.net/npm/@fontsource/dm-sans/files/dm-sans-latin-700-normal.woff2',
|
||||
'https://cdn.jsdelivr.net/npm/@fontsource/dm-sans/files/dm-sans-latin-700-normal.woff',
|
||||
'https://cdn.jsdelivr.net/npm/@fontsource/dm-sans/files/dm-sans-latin-700-italic.woff2',
|
||||
'https://cdn.jsdelivr.net/npm/@fontsource/dm-sans/files/dm-sans-latin-700-italic.woff',
|
||||
],
|
||||
'style-src': [
|
||||
'https://app.dynamic.xyz',
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
export { ad } from './ad';
|
||||
export { app } from './app';
|
||||
export { blockchainInteraction } from './blockchainInteraction';
|
||||
export { cloudFlare } from './cloudFlare';
|
||||
export { flashblocks } from './flashblocks';
|
||||
export { gasHawk } from './gasHawk';
|
||||
@@ -17,5 +18,4 @@ export { rollbar } from './rollbar';
|
||||
export { rollup } from './rollup';
|
||||
export { safe } from './safe';
|
||||
export { usernameApi } from './usernameApi';
|
||||
export { walletConnect } from './walletConnect';
|
||||
export { zetachain } from './zetachain';
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
import type CspDev from 'csp-dev';
|
||||
|
||||
import config from 'configs/app';
|
||||
|
||||
import { KEY_WORDS } from '../utils';
|
||||
|
||||
export function walletConnect(): CspDev.DirectiveDescriptor {
|
||||
if (!config.features.blockchainInteraction.isEnabled) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return {
|
||||
'connect-src': [
|
||||
'*.web3modal.com',
|
||||
'*.web3modal.org',
|
||||
'*.walletconnect.com',
|
||||
'*.walletconnect.org',
|
||||
'wss://relay.walletconnect.com',
|
||||
'wss://relay.walletconnect.org',
|
||||
'wss://www.walletlink.org',
|
||||
],
|
||||
'frame-ancestors': [
|
||||
'*.walletconnect.org',
|
||||
'*.walletconnect.com',
|
||||
],
|
||||
'img-src': [
|
||||
KEY_WORDS.BLOB,
|
||||
'*.walletconnect.com',
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -18,6 +18,15 @@ export const account: Guard = (chainConfig: typeof config) => async() => {
|
||||
}
|
||||
};
|
||||
|
||||
export const accountAuth0: Guard = (chainConfig: typeof config) => async() => {
|
||||
const feature = chainConfig.features.account;
|
||||
if (!feature.isEnabled || feature.authProvider !== 'auth0') {
|
||||
return {
|
||||
notFound: true,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export const verifiedAddresses: Guard = (chainConfig: typeof config) => async() => {
|
||||
if (!chainConfig.features.addressVerification.isEnabled) {
|
||||
return {
|
||||
@@ -319,6 +328,14 @@ export const interopMessages: Guard = (chainConfig: typeof config) => async() =>
|
||||
}
|
||||
};
|
||||
|
||||
export const crossChainTxs: Guard = (chainConfig: typeof config) => async() => {
|
||||
if (!chainConfig.features.crossChainTxs.isEnabled) {
|
||||
return {
|
||||
notFound: true,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export const opSuperchain: Guard = () => async() => {
|
||||
if (!config.features.opSuperchain.isEnabled) {
|
||||
return {
|
||||
|
||||
@@ -6,6 +6,7 @@ export const block = factory([ guards.notOpSuperchain ]);
|
||||
export const tx = factory([ guards.notOpSuperchain ]);
|
||||
export const token = factory([ guards.notOpSuperchain ]);
|
||||
export const account = factory([ guards.account ]);
|
||||
export const accountAuth0 = factory([ guards.accountAuth0 ]);
|
||||
export const verifiedAddresses = factory([ guards.account, guards.verifiedAddresses ]);
|
||||
export const userOps = factory([ guards.userOps ]);
|
||||
export const marketplace = factory([ guards.marketplace ]);
|
||||
@@ -31,6 +32,7 @@ export const publicTagsSubmit = factory([ guards.publicTagsSubmit ]);
|
||||
export const pools = factory([ guards.pools ]);
|
||||
export const megaEth = factory([ guards.megaEth ]);
|
||||
export const zetaChainCCTX = factory([ guards.zetaChainCCTX ]);
|
||||
export const crossChainTxs = factory([ guards.notOpSuperchain, guards.crossChainTxs ]);
|
||||
|
||||
// ROLLUPS
|
||||
export const rollup = factory([ guards.rollup ]);
|
||||
|
||||
@@ -52,6 +52,7 @@ declare module "nextjs-routes" {
|
||||
| DynamicRoute<"/chain/[chain_slug]/visualize/sol2uml", { "chain_slug": string }>
|
||||
| StaticRoute<"/chakra">
|
||||
| StaticRoute<"/contract-verification">
|
||||
| DynamicRoute<"/cross-chain-tx/[id]", { "id": string }>
|
||||
| StaticRoute<"/csv-export">
|
||||
| StaticRoute<"/deposits">
|
||||
| StaticRoute<"/dispute-games">
|
||||
|
||||
@@ -13,7 +13,7 @@ export interface RouteParams {
|
||||
export const route = (route: Route, params?: RouteParams | null) => {
|
||||
const generatedRoute = nextjsRoute(routeParams(route, params));
|
||||
|
||||
if (params && params.chain && params.external) {
|
||||
if (params && params.chain && params.external && params.chain.explorer_url) {
|
||||
return stripTrailingSlash(params.chain.explorer_url) + generatedRoute;
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,8 @@
|
||||
"lint:eslint": "eslint .",
|
||||
"lint:eslint:fix": "eslint . --fix",
|
||||
"lint:tsc": "tsc -p ./tsconfig.json",
|
||||
"lint:cspell": "cspell . --no-must-find-files",
|
||||
"lint:license:check": "license-report > ./license.json && license-report-check --source=./license.json --output=table --forbidden=n/a",
|
||||
"lint:envs-validator:test": "cd ./deploy/tools/envs-validator && ./test.sh",
|
||||
"prepare": "husky install",
|
||||
"svg:format": "svgo -r --config svgo.config.format.js ./icons ./toolkit/chakra/assets",
|
||||
@@ -42,6 +44,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@blockscout/bens-types": "1.4.1",
|
||||
"@blockscout/interchain-indexer-types": "0.0.10",
|
||||
"@blockscout/multichain-aggregator-types": "1.6.3-alpha.3",
|
||||
"@blockscout/points-types": "1.4.0-alpha.1",
|
||||
"@blockscout/stats-types": "2.11.1",
|
||||
@@ -49,6 +52,9 @@
|
||||
"@blockscout/visualizer-types": "0.2.0",
|
||||
"@blockscout/zetachain-cctx-types": "^1.0.0-rc.6",
|
||||
"@chakra-ui/react": "3.15.0",
|
||||
"@dynamic-labs/ethereum": "4.52.2",
|
||||
"@dynamic-labs/sdk-react-core": "4.52.2",
|
||||
"@dynamic-labs/wagmi-connector": "4.52.2",
|
||||
"@emotion/react": "11.14.0",
|
||||
"@growthbook/growthbook-react": "0.21.0",
|
||||
"@helia/verified-fetch": "2.6.12",
|
||||
@@ -85,6 +91,7 @@
|
||||
"blo": "^1.1.1",
|
||||
"brotli-compress": "1.3.3",
|
||||
"crypto-js": "^4.2.0",
|
||||
"cspell": "9.6.4",
|
||||
"d3": "^7.6.1",
|
||||
"dappscout-iframe": "0.4.0",
|
||||
"dayjs": "^1.11.5",
|
||||
@@ -100,7 +107,7 @@
|
||||
"magic-bytes.js": "1.8.0",
|
||||
"mixpanel-browser": "2.67.0",
|
||||
"monaco-editor": "^0.34.1",
|
||||
"next": "15.5.9",
|
||||
"next": "15.5.10",
|
||||
"next-themes": "0.4.4",
|
||||
"nextjs-routes": "^1.0.8",
|
||||
"node-fetch": "^3.2.9",
|
||||
@@ -166,7 +173,6 @@
|
||||
"eslint-plugin-import": "2.31.0",
|
||||
"eslint-plugin-import-helpers": "2.0.1",
|
||||
"eslint-plugin-jsx-a11y": "6.10.2",
|
||||
"eslint-plugin-no-cyrillic-string": "^1.0.5",
|
||||
"eslint-plugin-playwright": "2.0.1",
|
||||
"eslint-plugin-react": "7.37.5",
|
||||
"eslint-plugin-react-hooks": "5.0.0",
|
||||
@@ -175,6 +181,8 @@
|
||||
"globals": "15.12.0",
|
||||
"husky": "^8.0.0",
|
||||
"jsdom": "27.3.0",
|
||||
"license-report": "6.8.1",
|
||||
"license-report-check": "0.1.2",
|
||||
"lint-staged": ">=10",
|
||||
"mockdate": "^3.0.5",
|
||||
"style-loader": "^3.3.1",
|
||||
@@ -204,7 +212,9 @@
|
||||
"@reown/appkit-adapter-wagmi/**/node-forge": "1.3.2",
|
||||
"eslint/**/brace-expansion": "1.1.12",
|
||||
"swagger-ui-react/**/js-yaml": "4.1.1",
|
||||
"dappscout-iframe/**/ws": "8.17.1"
|
||||
"dappscout-iframe/**/ws": "8.17.1",
|
||||
"viem/**/@noble/hashes": "1.8.0",
|
||||
"@walletconnect/ethereum-provider/**/@reown/appkit/**/@noble/hashes": "1.8.0"
|
||||
},
|
||||
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { GrowthBookProvider } from '@growthbook/growthbook-react';
|
||||
import { QueryClientProvider } from '@tanstack/react-query';
|
||||
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
|
||||
import type { AppProps } from 'next/app';
|
||||
import dynamic from 'next/dynamic';
|
||||
import React from 'react';
|
||||
|
||||
import type { NextPageWithLayout } from 'nextjs/types';
|
||||
@@ -11,8 +12,8 @@ import config from 'configs/app';
|
||||
import getSocketUrl from 'lib/api/getSocketUrl';
|
||||
import useQueryClientConfig from 'lib/api/useQueryClientConfig';
|
||||
import { AppContextProvider } from 'lib/contexts/app';
|
||||
import { FallbackProvider } from 'lib/contexts/fallback';
|
||||
import { MarketplaceContextProvider } from 'lib/contexts/marketplace';
|
||||
import { RewardsContextProvider } from 'lib/contexts/rewards';
|
||||
import { SettingsContextProvider } from 'lib/contexts/settings';
|
||||
import { initGrowthBook } from 'lib/growthbook/init';
|
||||
import useLoadFeatures from 'lib/growthbook/useLoadFeatures';
|
||||
@@ -20,13 +21,15 @@ import { clientConfig as rollbarConfig, Provider as RollbarProvider } from 'lib/
|
||||
import { SocketProvider } from 'lib/socket/context';
|
||||
import { Provider as ChakraProvider } from 'toolkit/chakra/provider';
|
||||
import { Toaster } from 'toolkit/chakra/toaster';
|
||||
import RewardsLoginModal from 'ui/rewards/login/RewardsLoginModal';
|
||||
import RewardsActivityTracker from 'ui/rewards/RewardsActivityTracker';
|
||||
import AppErrorBoundary from 'ui/shared/AppError/AppErrorBoundary';
|
||||
import AppErrorGlobalContainer from 'ui/shared/AppError/AppErrorGlobalContainer';
|
||||
import GoogleAnalytics from 'ui/shared/GoogleAnalytics';
|
||||
import Layout from 'ui/shared/layout/Layout';
|
||||
import Web3ModalProvider from 'ui/shared/Web3ModalProvider';
|
||||
import Web3Provider from 'ui/shared/web3/Web3Provider';
|
||||
|
||||
const RewardsContextProvider = dynamic(() => import('lib/contexts/rewards').then(module => module.RewardsContextProvider), { ssr: false });
|
||||
const RewardsLoginModal = dynamic(() => import('ui/rewards/login/RewardsLoginModal'), { ssr: false });
|
||||
const RewardsActivityTracker = dynamic(() => import('ui/rewards/RewardsActivityTracker'), { ssr: false });
|
||||
|
||||
import 'lib/setLocale';
|
||||
// import 'focus-visible/dist/focus-visible';
|
||||
@@ -88,6 +91,8 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) {
|
||||
);
|
||||
})();
|
||||
|
||||
const RewardsProvider = config.features.rewards.isEnabled ? RewardsContextProvider : FallbackProvider;
|
||||
|
||||
const socketUrl = !config.features.opSuperchain.isEnabled ? getSocketUrl() : undefined;
|
||||
|
||||
return (
|
||||
@@ -97,25 +102,25 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) {
|
||||
{ ...ERROR_SCREEN_STYLES }
|
||||
Container={ AppErrorGlobalContainer }
|
||||
>
|
||||
<Web3ModalProvider>
|
||||
<AppContextProvider pageProps={ pageProps }>
|
||||
<QueryClientProvider client={ queryClient }>
|
||||
<QueryClientProvider client={ queryClient }>
|
||||
<Web3Provider>
|
||||
<AppContextProvider pageProps={ pageProps }>
|
||||
<GrowthBookProvider growthbook={ growthBook }>
|
||||
<SocketProvider url={ socketUrl }>
|
||||
<RewardsContextProvider>
|
||||
<RewardsProvider>
|
||||
<MarketplaceContextProvider>
|
||||
<SettingsContextProvider>
|
||||
{ content }
|
||||
</SettingsContextProvider>
|
||||
</MarketplaceContextProvider>
|
||||
</RewardsContextProvider>
|
||||
</RewardsProvider>
|
||||
</SocketProvider>
|
||||
</GrowthBookProvider>
|
||||
<ReactQueryDevtools buttonPosition="bottom-left" position="left"/>
|
||||
<GoogleAnalytics/>
|
||||
</QueryClientProvider>
|
||||
</AppContextProvider>
|
||||
</Web3ModalProvider>
|
||||
</AppContextProvider>
|
||||
</Web3Provider>
|
||||
</QueryClientProvider>
|
||||
</AppErrorBoundary>
|
||||
</RollbarProvider>
|
||||
</ChakraProvider>
|
||||
|
||||
@@ -1,10 +1,26 @@
|
||||
import type { NextPageContext } from 'next';
|
||||
import NextErrorComponent from 'next/error';
|
||||
import React from 'react';
|
||||
import Rollbar from 'rollbar';
|
||||
|
||||
import type { Props as ServerSidePropsCommon } from 'nextjs/getServerSideProps/handlers';
|
||||
|
||||
import config from 'configs/app';
|
||||
import * as cookies from 'lib/cookies';
|
||||
|
||||
const rollbarFeature = config.features.rollbar;
|
||||
const rollbar = rollbarFeature.isEnabled ? new Rollbar({
|
||||
accessToken: rollbarFeature.clientToken,
|
||||
environment: rollbarFeature.environment,
|
||||
payload: {
|
||||
code_version: rollbarFeature.codeVersion,
|
||||
app_instance: rollbarFeature.instance,
|
||||
},
|
||||
maxItems: 10,
|
||||
captureUncaught: true,
|
||||
captureUnhandledRejections: true,
|
||||
}) : undefined;
|
||||
|
||||
type Props = ServerSidePropsCommon & {
|
||||
statusCode: number;
|
||||
};
|
||||
@@ -14,4 +30,25 @@ const CustomErrorComponent = (props: Props) => {
|
||||
return <NextErrorComponent statusCode={ props.statusCode } withDarkMode={ colorModeCookie === 'dark' }/>;
|
||||
};
|
||||
|
||||
CustomErrorComponent.getInitialProps = async(context: NextPageContext) => {
|
||||
const { res, err, req } = context;
|
||||
|
||||
const baseProps = await NextErrorComponent.getInitialProps(context); // Extract cookies from the request headers
|
||||
const statusCode = res?.statusCode ?? err?.statusCode;
|
||||
const cookies = req?.headers?.cookie || '';
|
||||
|
||||
if (rollbar) {
|
||||
rollbar.error(err?.message ?? 'Unknown error', {
|
||||
cause: err?.cause,
|
||||
stack: err?.stack,
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
...baseProps,
|
||||
statusCode,
|
||||
cookies,
|
||||
};
|
||||
};
|
||||
|
||||
export default CustomErrorComponent;
|
||||
|
||||
@@ -15,4 +15,4 @@ const Page: NextPage = () => {
|
||||
|
||||
export default Page;
|
||||
|
||||
export { account as getServerSideProps } from 'nextjs/getServerSideProps/main';
|
||||
export { accountAuth0 as getServerSideProps } from 'nextjs/getServerSideProps/main';
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import type { NextPage } from 'next';
|
||||
import dynamic from 'next/dynamic';
|
||||
import React from 'react';
|
||||
|
||||
import type { Props } from 'nextjs/getServerSideProps/handlers';
|
||||
import PageNextJs from 'nextjs/PageNextJs';
|
||||
|
||||
const TxCrossChain = dynamic(() => import('ui/crossChain/tx/TxCrossChain'), { ssr: false });
|
||||
|
||||
const Page: NextPage<Props> = (props: Props) => {
|
||||
return (
|
||||
<PageNextJs pathname="/cross-chain-tx/[id]" query={ props.query }>
|
||||
<TxCrossChain/>
|
||||
</PageNextJs>
|
||||
);
|
||||
};
|
||||
|
||||
export default Page;
|
||||
|
||||
export { crossChainTxs as getServerSideProps } from 'nextjs/getServerSideProps/main';
|
||||
@@ -15,6 +15,10 @@ const Transactions = dynamic(() => {
|
||||
return import('ui/pages/TransactionsZetaChain');
|
||||
}
|
||||
|
||||
if (config.features.crossChainTxs.isEnabled) {
|
||||
return import('ui/crossChain/txs/Transactions');
|
||||
}
|
||||
|
||||
return import('ui/pages/Transactions');
|
||||
}, { ssr: false });
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ const config: PlaywrightTestConfig = defineConfig({
|
||||
workers: 1,
|
||||
|
||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||
reporter: 'html',
|
||||
reporter: process.env.CI ? 'blob' : 'html',
|
||||
|
||||
expect: {
|
||||
toHaveScreenshot: {
|
||||
@@ -68,6 +68,21 @@ const config: PlaywrightTestConfig = defineConfig({
|
||||
// https://github.com/storybookjs/builder-vite/issues/409#issuecomment-1152848986
|
||||
sourcemap: false,
|
||||
minify: false,
|
||||
rollupOptions: {
|
||||
output: {
|
||||
// Ensure __envs exists in every chunk so code (e.g. next/router, configs/app) that
|
||||
// runs before index.ts/envs.js doesn't throw. Real envs are set when envs.js runs.
|
||||
//
|
||||
// Explanation:
|
||||
// With await import(...) in useAccount/useWallet, Vite turns those into separate chunks.
|
||||
// In Playwright CT, the component’s chunk (and its dependency graph, including useAccount → config → getEnvValue → __envs)
|
||||
// can load and run before the main entry that runs envs.js.
|
||||
// So when that chunk runs, window.__envs isn’t set yet and you get ReferenceError: __envs is not defined.
|
||||
//
|
||||
// https://rollupjs.org/configuration-options/#output-banner-output-footer
|
||||
banner: '(function(){if(typeof window!==\'undefined\')window.__envs=window.__envs||{};})();',
|
||||
},
|
||||
},
|
||||
},
|
||||
resolve: {
|
||||
alias: [
|
||||
|
||||
@@ -136,4 +136,8 @@ export const ENVS_MAP: Record<string, Array<[string, string]>> = {
|
||||
colorThemeOverrides: [
|
||||
[ 'NEXT_PUBLIC_COLOR_THEME_OVERRIDES', '{"bg":{"primary":{"_light":{"value":"rgba(254,253,253)"},"_dark":{"value":"rgba(25,25,26)"}}},"text":{"primary":{"_light":{"value":"rgba(16,17,18,0.80)"},"_dark":{"value":"rgba(222,217,217)"}},"secondary":{"_light":{"value":"rgba(138,136,136)"},"_dark":{"value":"rgba(133,133,133)"}}},"hover":{"_light":{"value":"rgba(104,200,158)"},"_dark":{"value":"rgba(104,200,158)"}},"selected":{"control":{"text":{"_light":{"value":"rgba(25,25,26)"},"_dark":{"value":"rgba(247,250,252)"}},"bg":{"_light":{"value":"rgba(242,239,239)"},"_dark":{"value":"rgba(255,255,255,0.06)"}}},"option":{"bg":{"_light":{"value":"rgba(84,75,75)"},"_dark":{"value":"rgba(87,87,87)"}}}},"icon":{"primary":{"_light":{"value":"rgba(138,136,136)"},"_dark":{"value":"rgba(133,133,133)"}},"secondary":{"_light":{"value":"rgba(176,176,176)"},"_dark":{"value":"rgba(105,103,103)"}}},"button":{"primary":{"_light":{"value":"rgba(105,103,103)"},"_dark":{"value":"rgba(133,133,133)"}}},"link":{"primary":{"_light":{"value":"rgba(57,146,108)"},"_dark":{"value":"rgba(57,146,108)"}}},"graph":{"line":{"_light":{"value":"rgba(105,103,103)"},"_dark":{"value":"rgba(57,146,108)"}},"gradient":{"start":{"_light":{"value":"rgba(105,103,103,0.3)"},"_dark":{"value":"rgba(57,146,108,0.3)"}},"stop":{"_light":{"value":"rgba(105,103,103,0)"},"_dark":{"value":"rgba(57,146,108,0)"}}}},"stats":{"bg":{"_light":{"value":"rgba(242,239,239)"},"_dark":{"value":"rgba(255,255,255,0.06)"}}},"topbar":{"bg":{"_light":{"value":"rgba(242,239,239)"},"_dark":{"value":"rgba(255,255,255,0.06)"}}},"navigation":{"text":{"selected":{"_light":{"value":"rgba(25,25,26)"},"_dark":{"value":"rgba(247,250,252)"}}},"bg":{"selected":{"_light":{"value":"rgba(242,239,239)"},"_dark":{"value":"rgba(255,255,255,0.06)"}}}},"tabs":{"text":{"primary":{"_light":{"value":"rgba(25,25,26)"},"_dark":{"value":"rgba(222,217,217)"}}}}}' ],
|
||||
],
|
||||
crossChainTxs: [
|
||||
[ 'NEXT_PUBLIC_INTERCHAIN_INDEXER_API_HOST', 'http://localhost:3014' ],
|
||||
[ 'NEXT_PUBLIC_CROSS_CHAIN_TXS_ENABLED', 'true' ],
|
||||
],
|
||||
};
|
||||
|
||||
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 34 KiB |
@@ -3,6 +3,7 @@
|
||||
export type IconName =
|
||||
| "ABI"
|
||||
| "advanced-filter"
|
||||
| "AI"
|
||||
| "API"
|
||||
| "apps_list"
|
||||
| "apps"
|
||||
@@ -47,7 +48,6 @@
|
||||
| "cross"
|
||||
| "delete"
|
||||
| "docs"
|
||||
| "donate"
|
||||
| "dots"
|
||||
| "edit"
|
||||
| "email"
|
||||
@@ -85,6 +85,7 @@
|
||||
| "lightning"
|
||||
| "link_external"
|
||||
| "link"
|
||||
| "list_view"
|
||||
| "lock"
|
||||
| "merits_colored"
|
||||
| "merits_with_dot"
|
||||
|
||||
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 17 KiB |