Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .github/workflows/e2e-all.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
name: E2E Tests - All

on:
workflow_dispatch:

jobs:
e2e-eth-mainnet:
uses: ./.github/workflows/e2e-eth-mainnet.yml
secrets: inherit

e2e-evm-networks:
uses: ./.github/workflows/e2e-evm-networks.yml
secrets: inherit

e2e-bitcoin:
uses: ./.github/workflows/e2e-bitcoin.yml
secrets: inherit
3 changes: 2 additions & 1 deletion .github/workflows/e2e-bitcoin.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ name: E2E Tests - Bitcoin

on:
pull_request:
branches: [dev]
branches: [main]
workflow_call:
workflow_dispatch:

jobs:
Expand Down
17 changes: 13 additions & 4 deletions .github/workflows/e2e-eth-mainnet.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,22 @@ name: E2E Tests - Ethereum Mainnet

on:
pull_request:
branches: [dev]
branches: [main]
workflow_call:
workflow_dispatch:

jobs:
e2e-mainnet:
runs-on: ubuntu-latest
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
shard:
- name: shard-a
tests: "e2e/tests/eth-mainnet/block.spec.ts e2e/tests/eth-mainnet/blocks.spec.ts e2e/tests/eth-mainnet/transaction.spec.ts"
- name: shard-b
tests: "e2e/tests/eth-mainnet/txs.spec.ts e2e/tests/eth-mainnet/address.spec.ts e2e/tests/eth-mainnet/token.spec.ts"

steps:
- uses: actions/checkout@v4
Expand All @@ -27,8 +36,8 @@ jobs:
- name: Build application
run: ./scripts/build-production.sh

- name: Run Ethereum Mainnet E2E tests
run: bun run test:e2e:eth-mainnet
- name: Run Ethereum Mainnet E2E tests (${{ matrix.shard.name }})
run: bunx playwright test ${{ matrix.shard.tests }}
env:
CI: true
INFURA_API_KEY: ${{ secrets.INFURA_API_KEY }}
Expand All @@ -38,6 +47,6 @@ jobs:
uses: actions/upload-artifact@v4
if: failure()
with:
name: playwright-report-mainnet
name: playwright-report-mainnet-${{ matrix.shard.name }}
path: playwright-report/
retention-days: 7
17 changes: 12 additions & 5 deletions .github/workflows/e2e-evm-networks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ name: E2E Tests - EVM Networks

on:
pull_request:
branches: [dev]
branches: [main]
workflow_call:
workflow_dispatch:

jobs:
Expand All @@ -13,10 +14,16 @@ jobs:
fail-fast: false
matrix:
shard:
- name: shard-a
tests: "e2e/tests/evm-networks/arbitrum.spec.ts e2e/tests/evm-networks/base.spec.ts e2e/tests/evm-networks/optimism.spec.ts"
- name: shard-b
tests: "e2e/tests/evm-networks/bsc.spec.ts e2e/tests/evm-networks/polygon.spec.ts"
- name: arbitrum
tests: "e2e/tests/evm-networks/arbitrum.spec.ts"
- name: base
tests: "e2e/tests/evm-networks/base.spec.ts"
- name: optimism
tests: "e2e/tests/evm-networks/optimism.spec.ts"
- name: bsc
tests: "e2e/tests/evm-networks/bsc.spec.ts"
- name: polygon
tests: "e2e/tests/evm-networks/polygon.spec.ts"

steps:
- uses: actions/checkout@v4
Expand Down
23 changes: 23 additions & 0 deletions src/components/common/AIAnalysis/AIAnalysisPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { useEffect, useId, useState } from "react";
import { useTranslation } from "react-i18next";
import Markdown from "react-markdown";
import { Link } from "react-router-dom";
import { useSettings } from "../../../context/SettingsContext";
import OpenScanCubeLoader from "../../LoadingLogo";
import { useAIAnalysis } from "../../../hooks/useAIAnalysis";
import type { AIAnalysisType } from "../../../types";
Expand All @@ -22,6 +23,7 @@ const AIAnalysisPanel: React.FC<AIAnalysisProps> = ({
networkCurrency,
cacheKey,
}) => {
const { settings } = useSettings();
const { t, i18n } = useTranslation("common");
const [isOpen, setIsOpen] = useState(false);
const panelId = useId();
Expand All @@ -40,8 +42,18 @@ const AIAnalysisPanel: React.FC<AIAnalysisProps> = ({
}
}, [result, error]);

const hasAiApiKeys = !!(
settings.apiKeys?.groq ||
settings.apiKeys?.openai ||
settings.apiKeys?.anthropic ||
settings.apiKeys?.perplexity ||
settings.apiKeys?.gemini
);
const aiProxyDisabledNoKeys = settings.workerProxyAi === false && !hasAiApiKeys;

const handleAnalyze = () => {
setIsOpen(true);
if (aiProxyDisabledNoKeys) return;
void analyze();
};

Expand Down Expand Up @@ -91,6 +103,17 @@ const AIAnalysisPanel: React.FC<AIAnalysisProps> = ({
aria-hidden={!isOpen}
style={{ display: isOpen ? "flex" : "none" }}
>
{aiProxyDisabledNoKeys && (
<div className="ai-analysis-error">
<div className="ai-analysis-error-message">{t("aiAnalysis.errors.proxyDisabled")}</div>
<div className="ai-analysis-error-action">
<Link to="/settings" className="ai-analysis-settings-link">
{t("aiAnalysis.errors.goToSettings")}
</Link>
</div>
</div>
)}

{loading && !result && (
<div className="ai-analysis-loading">
<OpenScanCubeLoader size={60} />
Expand Down
71 changes: 61 additions & 10 deletions src/components/pages/evm/block/BlockAnalyser.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { Link } from "react-router-dom";
import { useBeaconBlobs } from "../../../../hooks/useBeaconBlobs";
import type { Block, BlockArbitrum } from "../../../../types";
import BlobDataDisplay from "../../../common/BlobDataDisplay";
import FieldLabel from "../../../common/FieldLabel";

type BlockAnalyserTab = "moreDetails" | "transactions" | "withdrawals" | "blobData";

Expand Down Expand Up @@ -92,7 +93,12 @@ const BlockAnalyser: React.FC<BlockAnalyserProps> = ({ block, networkId, isSuper
<button
type="button"
className="detail-panel-collapse-btn"
onClick={() => setCollapsed((c) => !c)}
onClick={() => {
setCollapsed((c) => {
if (c && activeTab === null) setActiveTab("moreDetails");
return !c;
});
}}
aria-label={collapsed ? t("analyser.expand") : t("analyser.collapse")}
>
{collapsed ? `▸ ${t("analyser.expand")}` : `▾ ${t("analyser.collapse")}`}
Expand All @@ -105,7 +111,12 @@ const BlockAnalyser: React.FC<BlockAnalyserProps> = ({ block, networkId, isSuper
{activeTab === "moreDetails" && (
<div className="detail-panel-tab-content">
<div className="detail-row">
<span className="detail-label">{t("analyser.parentHash")}</span>
<FieldLabel
label={t("analyser.parentHash")}
tooltipKey="block.parentHash"
visibleFor={["beginner", "intermediate", "advanced"]}
className="detail-label"
/>
<span className="detail-value tx-mono">
{networkId &&
block.parentHash !==
Expand All @@ -119,39 +130,79 @@ const BlockAnalyser: React.FC<BlockAnalyserProps> = ({ block, networkId, isSuper
</span>
</div>
<div className="detail-row">
<span className="detail-label">{t("analyser.stateRoot")}</span>
<FieldLabel
label={t("analyser.stateRoot")}
tooltipKey="block.stateRoot"
visibleFor={["beginner", "intermediate", "advanced"]}
className="detail-label"
/>
<span className="detail-value tx-mono">{block.stateRoot}</span>
</div>
<div className="detail-row">
<span className="detail-label">{t("analyser.transactionsRoot")}</span>
<FieldLabel
label={t("analyser.transactionsRoot")}
tooltipKey="block.transactionsRoot"
visibleFor={["beginner", "intermediate", "advanced"]}
className="detail-label"
/>
<span className="detail-value tx-mono">{block.transactionsRoot}</span>
</div>
<div className="detail-row">
<span className="detail-label">{t("analyser.receiptsRoot")}</span>
<FieldLabel
label={t("analyser.receiptsRoot")}
tooltipKey="block.receiptsRoot"
visibleFor={["beginner", "intermediate", "advanced"]}
className="detail-label"
/>
<span className="detail-value tx-mono">{block.receiptsRoot}</span>
</div>
{block.withdrawalsRoot && (
<div className="detail-row">
<span className="detail-label">{t("analyser.withdrawalsRoot")}</span>
<FieldLabel
label={t("analyser.withdrawalsRoot")}
tooltipKey="block.withdrawalsRoot"
visibleFor={["beginner", "intermediate", "advanced"]}
className="detail-label"
/>
<span className="detail-value tx-mono">{block.withdrawalsRoot}</span>
</div>
)}
<div className="detail-row">
<span className="detail-label">{t("analyser.logsBloom")}</span>
<FieldLabel
label={t("analyser.logsBloom")}
tooltipKey="block.logsBloom"
visibleFor={["beginner", "intermediate", "advanced"]}
className="detail-label"
/>
<div className="detail-value">
<code className="logs-bloom">{block.logsBloom}</code>
</div>
</div>
<div className="detail-row">
<span className="detail-label">{t("analyser.nonce")}</span>
<FieldLabel
label={t("analyser.nonce")}
tooltipKey="block.nonce"
visibleFor={["beginner", "intermediate"]}
className="detail-label"
/>
<span className="detail-value">{block.nonce}</span>
</div>
<div className="detail-row">
<span className="detail-label">{t("analyser.mixHash")}</span>
<FieldLabel
label={t("analyser.mixHash")}
tooltipKey="block.mixHash"
visibleFor={["beginner", "intermediate", "advanced"]}
className="detail-label"
/>
<span className="detail-value tx-mono">{block.mixHash}</span>
</div>
<div className="detail-row">
<span className="detail-label">{t("analyser.sha3Uncles")}</span>
<FieldLabel
label={t("analyser.sha3Uncles")}
tooltipKey="block.sha3Uncles"
visibleFor={["beginner", "intermediate", "advanced"]}
className="detail-label"
/>
<span className="detail-value tx-mono">{block.sha3Uncles}</span>
</div>
</div>
Expand Down
66 changes: 65 additions & 1 deletion src/components/pages/settings/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,11 @@ import { downloadConfigFile, exportConfig, importConfig } from "../../../utils/c
import { logger } from "../../../utils/logger";
import { getChainIdFromNetwork } from "../../../utils/networkResolver";
import { clearPersistentCache, getPersistentCacheSize } from "../../../utils/persistentCache";
import { clearMetadataRpcCache, getMetadataEndpointMap } from "../../../utils/rpcStorage";
import {
clearMetadataRpcCache,
getEffectiveRpcUrls,
getMetadataEndpointMap,
} from "../../../utils/rpcStorage";
import { sortRpcsByQuality } from "../../../utils/rpcAutoSync";
import { type RpcTestResult, testRpcEndpoint } from "../rpcs/useRpcLatencyTest";

Expand Down Expand Up @@ -1317,6 +1321,66 @@ const Settings: React.FC = () => {
</span>
</div>

<div className="settings-section no-margin-bottom">
<h2 className="settings-section-title">☁️ {t("workerProxy.title")}</h2>
<p className="settings-section-description">{t("workerProxy.description")}</p>

<div className="settings-item">
<div>
<div className="settings-item-label">{t("workerProxy.aiAnalysis.label")}</div>
<div className="settings-item-description">
{t("workerProxy.aiAnalysis.description")}
</div>
</div>
<label className="settings-toggle">
<input
type="checkbox"
checked={settings.workerProxyAi !== false}
onChange={(e) => updateSettings({ workerProxyAi: e.target.checked })}
className="settings-toggle-input"
/>
<span
className={`settings-toggle-slider ${settings.workerProxyAi !== false ? "active" : ""}`}
>
<span
className={`settings-toggle-knob ${settings.workerProxyAi !== false ? "active" : ""}`}
/>
</span>
</label>
</div>

<div className="settings-item">
<div>
<div className="settings-item-label">{t("workerProxy.rpcProxy.label")}</div>
<div className="settings-item-description">
{t("workerProxy.rpcProxy.description")}
</div>
</div>
<label className="settings-toggle">
<input
type="checkbox"
checked={settings.workerProxyRpc !== false}
onChange={(e) => {
updateSettings({ workerProxyRpc: e.target.checked });
setRpcUrls(
getEffectiveRpcUrls({
excludeWorkerProxy: !e.target.checked,
}),
);
}}
className="settings-toggle-input"
/>
<span
className={`settings-toggle-slider ${settings.workerProxyRpc !== false ? "active" : ""}`}
>
<span
className={`settings-toggle-knob ${settings.workerProxyRpc !== false ? "active" : ""}`}
/>
</span>
</label>
</div>
</div>

<div className="settings-section no-margin-bottom">
<h2 className="settings-section-title">⚡ {t("rpcStrategy.title")}</h2>
<p className="settings-section-description">{t("rpcStrategy.description")}</p>
Expand Down
5 changes: 2 additions & 3 deletions src/config/aiProviders.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { AIProvider, AIProviderConfig } from "../types";
import { OPENSCAN_WORKER_URL } from "./workerConfig";

/**
* Static configuration for supported AI providers.
Expand All @@ -9,9 +10,7 @@ export const AI_PROVIDERS: Record<AIProvider, AIProviderConfig> = {
"openscan-groq": {
id: "openscan-groq",
name: "OpenScan Groq",
baseUrl:
process.env.REACT_APP_OPENSCAN_GROQ_AI_URL ??
"https://openscan-groq-ai-proxy.openscan.workers.dev",
baseUrl: OPENSCAN_WORKER_URL,
defaultModel: "groq/compound",
keyUrl: "",
},
Expand Down
5 changes: 1 addition & 4 deletions src/config/rpcConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,5 @@ import type { RpcUrlsContextType } from "../types";
*/
export function getRPCUrls(networkId: string, rpcUrlsMap: RpcUrlsContextType): string[] {
const urls = rpcUrlsMap[networkId];
if (!urls || urls.length === 0) {
throw new Error(`No RPC endpoint configured for network ${networkId}`);
}
return urls;
return urls ?? [];
}
4 changes: 1 addition & 3 deletions src/config/workerConfig.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
/** Base URL for the OpenScan Cloudflare Worker proxy */
export const OPENSCAN_WORKER_URL =
// biome-ignore lint/complexity/useLiteralKeys: env var access
process.env["REACT_APP_OPENSCAN_WORKER_URL"] ??
"https://openscan-worker-proxy.openscan.workers.dev";
process.env.REACT_APP_OPENSCAN_WORKER_URL || "https://openscan-worker-proxy.openscan.workers.dev";
Loading
Loading