diff --git a/.github/workflows/e2e-all.yml b/.github/workflows/e2e-all.yml new file mode 100644 index 00000000..ddad65dd --- /dev/null +++ b/.github/workflows/e2e-all.yml @@ -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 diff --git a/.github/workflows/e2e-bitcoin.yml b/.github/workflows/e2e-bitcoin.yml index 8100456e..6290c347 100644 --- a/.github/workflows/e2e-bitcoin.yml +++ b/.github/workflows/e2e-bitcoin.yml @@ -2,7 +2,8 @@ name: E2E Tests - Bitcoin on: pull_request: - branches: [dev] + branches: [main] + workflow_call: workflow_dispatch: jobs: diff --git a/.github/workflows/e2e-eth-mainnet.yml b/.github/workflows/e2e-eth-mainnet.yml index 0f23f2eb..98eb0e88 100644 --- a/.github/workflows/e2e-eth-mainnet.yml +++ b/.github/workflows/e2e-eth-mainnet.yml @@ -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 @@ -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 }} @@ -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 diff --git a/.github/workflows/e2e-evm-networks.yml b/.github/workflows/e2e-evm-networks.yml index c2bd05b0..c309fca8 100644 --- a/.github/workflows/e2e-evm-networks.yml +++ b/.github/workflows/e2e-evm-networks.yml @@ -2,7 +2,8 @@ name: E2E Tests - EVM Networks on: pull_request: - branches: [dev] + branches: [main] + workflow_call: workflow_dispatch: jobs: @@ -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 diff --git a/src/components/common/AIAnalysis/AIAnalysisPanel.tsx b/src/components/common/AIAnalysis/AIAnalysisPanel.tsx index e80182c5..68d1e993 100644 --- a/src/components/common/AIAnalysis/AIAnalysisPanel.tsx +++ b/src/components/common/AIAnalysis/AIAnalysisPanel.tsx @@ -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"; @@ -22,6 +23,7 @@ const AIAnalysisPanel: React.FC = ({ networkCurrency, cacheKey, }) => { + const { settings } = useSettings(); const { t, i18n } = useTranslation("common"); const [isOpen, setIsOpen] = useState(false); const panelId = useId(); @@ -40,8 +42,18 @@ const AIAnalysisPanel: React.FC = ({ } }, [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(); }; @@ -91,6 +103,17 @@ const AIAnalysisPanel: React.FC = ({ aria-hidden={!isOpen} style={{ display: isOpen ? "flex" : "none" }} > + {aiProxyDisabledNoKeys && ( +
+
{t("aiAnalysis.errors.proxyDisabled")}
+
+ + {t("aiAnalysis.errors.goToSettings")} + +
+
+ )} + {loading && !result && (
diff --git a/src/components/pages/evm/block/BlockAnalyser.tsx b/src/components/pages/evm/block/BlockAnalyser.tsx index acd7ca0b..62902795 100644 --- a/src/components/pages/evm/block/BlockAnalyser.tsx +++ b/src/components/pages/evm/block/BlockAnalyser.tsx @@ -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"; @@ -92,7 +93,12 @@ const BlockAnalyser: React.FC = ({ block, networkId, isSuper
+
+

☁️ {t("workerProxy.title")}

+

{t("workerProxy.description")}

+ +
+
+
{t("workerProxy.aiAnalysis.label")}
+
+ {t("workerProxy.aiAnalysis.description")} +
+
+ +
+ +
+
+
{t("workerProxy.rpcProxy.label")}
+
+ {t("workerProxy.rpcProxy.description")} +
+
+ +
+
+

⚡ {t("rpcStrategy.title")}

{t("rpcStrategy.description")}

diff --git a/src/config/aiProviders.ts b/src/config/aiProviders.ts index 00b5aa1e..c398f7d6 100644 --- a/src/config/aiProviders.ts +++ b/src/config/aiProviders.ts @@ -1,4 +1,5 @@ import type { AIProvider, AIProviderConfig } from "../types"; +import { OPENSCAN_WORKER_URL } from "./workerConfig"; /** * Static configuration for supported AI providers. @@ -9,9 +10,7 @@ export const AI_PROVIDERS: Record = { "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: "", }, diff --git a/src/config/rpcConfig.ts b/src/config/rpcConfig.ts index f120055c..6aa20b7c 100644 --- a/src/config/rpcConfig.ts +++ b/src/config/rpcConfig.ts @@ -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 ?? []; } diff --git a/src/config/workerConfig.ts b/src/config/workerConfig.ts index 4ac4653f..41558d4c 100644 --- a/src/config/workerConfig.ts +++ b/src/config/workerConfig.ts @@ -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"; diff --git a/src/context/AppContext.tsx b/src/context/AppContext.tsx index 32d8ab9c..f5159bd6 100644 --- a/src/context/AppContext.tsx +++ b/src/context/AppContext.tsx @@ -29,6 +29,16 @@ import { // Alias exported for use across the app where a shorter/consistent name is preferred export type tRpcUrlsContextType = RpcUrlsContextType; +function readWorkerProxyRpcSetting(): boolean { + try { + const raw = localStorage.getItem("openScan_user_settings"); + if (raw) return (JSON.parse(raw) as { workerProxyRpc?: boolean }).workerProxyRpc !== false; + } catch { + /* ignore */ + } + return true; +} + export const AppContext = createContext({ appReady: false, resourcesLoaded: false, @@ -50,7 +60,9 @@ export const AppContextProvider = ({ children }: { children: ReactNode }) => { const [appReady, setAppReady] = useState(false); const [resourcesLoaded, setResourcesLoaded] = useState(false); const [isHydrated, setIsHydrated] = useState(false); - const [rpcUrls, setRpcUrlsState] = useState(() => getEffectiveRpcUrls()); + const [rpcUrls, setRpcUrlsState] = useState(() => + getEffectiveRpcUrls({ excludeWorkerProxy: !readWorkerProxyRpcSetting() }), + ); // biome-ignore lint/suspicious/noExplicitAny: const [jsonFiles, setJsonFilesState] = useState>(() => loadJsonFilesFromStorage(), @@ -102,7 +114,7 @@ export const AppContextProvider = ({ children }: { children: ReactNode }) => { } } - setRpcUrlsState(getEffectiveRpcUrls()); + setRpcUrlsState(getEffectiveRpcUrls({ excludeWorkerProxy: !readWorkerProxyRpcSetting() })); } catch (err) { setNetworksError(err instanceof Error ? err.message : "Failed to load networks"); setNetworks(getAllNetworks()); diff --git a/src/hooks/useAIAnalysis.ts b/src/hooks/useAIAnalysis.ts index a37447b7..d282777e 100644 --- a/src/hooks/useAIAnalysis.ts +++ b/src/hooks/useAIAnalysis.ts @@ -48,8 +48,9 @@ export function useAIAnalysis( const apiKeys = settings.apiKeys ?? {}; for (const id of AI_PROVIDER_ORDER) { - // openscan-groq is a free proxy — no API key needed + // openscan-groq is a free proxy — skip when AI worker proxy is disabled if (id === "openscan-groq") { + if (settings.workerProxyAi === false) continue; return { provider: AI_PROVIDERS[id], apiKey: "" }; } const key = apiKeys[id]; @@ -58,7 +59,7 @@ export function useAIAnalysis( } } return null; - }, [settings.apiKeys]); + }, [settings.apiKeys, settings.workerProxyAi]); // Augment cache key with version and mode so switching invalidates cache const augmentedCacheKey = `${cacheKey}_v${promptVersion}_${userMode}`; diff --git a/src/locales/en/common.json b/src/locales/en/common.json index b3b3757c..42ee1dd9 100644 --- a/src/locales/en/common.json +++ b/src/locales/en/common.json @@ -299,7 +299,8 @@ "parseError": "Failed to parse AI response. Try again.", "generic": "Analysis failed. Please try again.", "tryAgain": "Try Again", - "goToSettings": "Go to Settings" + "goToSettings": "Go to Settings", + "proxyDisabled": "The free OpenScan AI proxy is disabled. Enable it in Settings → Network → Worker Proxy, or configure your own AI provider API keys in Settings → Providers." } }, "search": { diff --git a/src/locales/en/settings.json b/src/locales/en/settings.json index d73321aa..24edd808 100644 --- a/src/locales/en/settings.json +++ b/src/locales/en/settings.json @@ -149,6 +149,18 @@ } } }, + "workerProxy": { + "title": "Worker Proxy", + "description": "Control the OpenScan worker proxy used for AI analysis and RPC routing. Disabling these will require your own API keys or endpoints.", + "aiAnalysis": { + "label": "AI Analysis Proxy", + "description": "Enable the OpenScan AI analysis proxy. When disabled, the AI analysis panel will be hidden across all pages." + }, + "rpcProxy": { + "label": "RPC Proxy", + "description": "Enable OpenScan worker proxy RPC endpoints. When disabled, only metadata and your personal RPC endpoints will be used." + } + }, "beaconApi": { "title": "Beacon API", "description": "Configure Beacon Chain API endpoints to view EIP-4844 blob data. Public endpoints are provided by default.", diff --git a/src/locales/es/common.json b/src/locales/es/common.json index 87fadd57..4067115f 100644 --- a/src/locales/es/common.json +++ b/src/locales/es/common.json @@ -291,7 +291,8 @@ "parseError": "No se pudo procesar la respuesta de IA. Intentá de nuevo.", "generic": "El análisis falló. Intentá de nuevo.", "tryAgain": "Intentar de nuevo", - "goToSettings": "Ir a Configuración" + "goToSettings": "Ir a Configuración", + "proxyDisabled": "El proxy de IA gratuito de OpenScan está desactivado. Activalo en Configuración → Red → Proxy Worker, o configurá tus propias claves de API en Configuración → Proveedores." } }, "search": { diff --git a/src/locales/es/settings.json b/src/locales/es/settings.json index d731268a..1936a346 100644 --- a/src/locales/es/settings.json +++ b/src/locales/es/settings.json @@ -149,6 +149,18 @@ } } }, + "workerProxy": { + "title": "Proxy Worker", + "description": "Controlá el proxy worker de OpenScan usado para análisis de IA y enrutamiento RPC. Desactivarlos requiere tus propias claves de API o endpoints.", + "aiAnalysis": { + "label": "Proxy de Análisis IA", + "description": "Habilitar el proxy de análisis IA de OpenScan. Cuando está desactivado, el panel de análisis IA se oculta en todas las páginas." + }, + "rpcProxy": { + "label": "Proxy RPC", + "description": "Habilitar los endpoints RPC del proxy worker de OpenScan. Cuando está desactivado, solo se usarán los endpoints de metadatos y personales." + } + }, "beaconApi": { "title": "Beacon API", "description": "Configurá endpoints de Beacon Chain API para ver datos de blobs EIP-4844. Se proporcionan endpoints públicos por defecto.", diff --git a/src/locales/ja/common.json b/src/locales/ja/common.json index b11fc25c..843ccf18 100644 --- a/src/locales/ja/common.json +++ b/src/locales/ja/common.json @@ -289,7 +289,8 @@ "parseError": "AIレスポンスの解析に失敗しました。再試行してください。", "generic": "分析に失敗しました。もう一度お試しください。", "tryAgain": "再試行", - "goToSettings": "設定へ" + "goToSettings": "設定へ", + "proxyDisabled": "OpenScanの無料AIプロキシが無効です。設定 → ネットワーク → ワーカープロキシで有効にするか、設定 → プロバイダーで独自のAIプロバイダーAPIキーを設定してください。" } }, "search": { diff --git a/src/locales/ja/settings.json b/src/locales/ja/settings.json index 6a1f1c0e..17c4d4ab 100644 --- a/src/locales/ja/settings.json +++ b/src/locales/ja/settings.json @@ -149,6 +149,18 @@ } } }, + "workerProxy": { + "title": "ワーカープロキシ", + "description": "AI分析とRPCルーティングに使用されるOpenScanワーカープロキシを制御します。無効にすると、独自のAPIキーまたはエンドポイントが必要です。", + "aiAnalysis": { + "label": "AI分析プロキシ", + "description": "OpenScan AI分析プロキシを有効にします。無効にすると、すべてのページでAI分析パネルが非表示になります。" + }, + "rpcProxy": { + "label": "RPCプロキシ", + "description": "OpenScanワーカープロキシRPCエンドポイントを有効にします。無効にすると、メタデータと個人のRPCエンドポイントのみが使用されます。" + } + }, "beaconApi": { "title": "Beacon API", "description": "EIP-4844 Blob データを表示するための Beacon Chain API エンドポイントを設定します。デフォルトで公開エンドポイントが提供されます。", diff --git a/src/locales/pt-BR/common.json b/src/locales/pt-BR/common.json index e73d46f5..c441c67f 100644 --- a/src/locales/pt-BR/common.json +++ b/src/locales/pt-BR/common.json @@ -292,7 +292,8 @@ "parseError": "Falha ao analisar resposta da IA. Tente novamente.", "generic": "Análise falhou. Por favor, tente novamente.", "tryAgain": "Tentar Novamente", - "goToSettings": "Ir para Configurações" + "goToSettings": "Ir para Configurações", + "proxyDisabled": "O proxy de IA gratuito do OpenScan está desativado. Ative em Configurações → Rede → Proxy Worker, ou configure suas próprias chaves de API em Configurações → Provedores." } }, "search": { diff --git a/src/locales/pt-BR/settings.json b/src/locales/pt-BR/settings.json index 8571ef2e..eba67fd1 100644 --- a/src/locales/pt-BR/settings.json +++ b/src/locales/pt-BR/settings.json @@ -149,6 +149,18 @@ } } }, + "workerProxy": { + "title": "Proxy Worker", + "description": "Controle o proxy worker do OpenScan usado para análise de IA e roteamento RPC. Desativar requer suas próprias chaves de API ou endpoints.", + "aiAnalysis": { + "label": "Proxy de Análise IA", + "description": "Ativar o proxy de análise IA do OpenScan. Quando desativado, o painel de análise IA será ocultado em todas as páginas." + }, + "rpcProxy": { + "label": "Proxy RPC", + "description": "Ativar os endpoints RPC do proxy worker do OpenScan. Quando desativado, apenas endpoints de metadados e pessoais serão usados." + } + }, "beaconApi": { "title": "Beacon API", "description": "Configure endpoints de Beacon Chain API para visualizar dados de blobs EIP-4844. Endpoints públicos são fornecidos por padrão.", diff --git a/src/locales/zh/common.json b/src/locales/zh/common.json index 201b44af..47e84319 100644 --- a/src/locales/zh/common.json +++ b/src/locales/zh/common.json @@ -274,7 +274,8 @@ "parseError": "解析 AI 响应失败。请重试。", "generic": "分析失败。请重试。", "tryAgain": "重试", - "goToSettings": "前往设置" + "goToSettings": "前往设置", + "proxyDisabled": "OpenScan免费AI代理已禁用。请在设置 → 网络 → 工作代理中启用,或在设置 → 提供商中配置您自己的AI提供商API密钥。" } }, "search": { diff --git a/src/locales/zh/settings.json b/src/locales/zh/settings.json index be6e0353..67908294 100644 --- a/src/locales/zh/settings.json +++ b/src/locales/zh/settings.json @@ -149,6 +149,18 @@ } } }, + "workerProxy": { + "title": "工作代理", + "description": "控制用于AI分析和RPC路由的OpenScan工作代理。禁用后需要您自己的API密钥或端点。", + "aiAnalysis": { + "label": "AI分析代理", + "description": "启用OpenScan AI分析代理。禁用后,所有页面上的AI分析面板将被隐藏。" + }, + "rpcProxy": { + "label": "RPC代理", + "description": "启用OpenScan工作代理RPC端点。禁用后,仅使用元数据和您个人的RPC端点。" + } + }, "beaconApi": { "title": "Beacon API", "description": "配置 Beacon Chain API 端点以查看 EIP-4844 Blob 数据。默认提供公共端点。", diff --git a/src/styles/styles.css b/src/styles/styles.css index 84c804e8..1af9f89c 100644 --- a/src/styles/styles.css +++ b/src/styles/styles.css @@ -5627,6 +5627,8 @@ code { position: relative; display: inline-block; width: 50px; + min-width: 50px; + flex-shrink: 0; height: 26px; cursor: pointer; } diff --git a/src/types/index.ts b/src/types/index.ts index 73ecc2a6..edc8e45b 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -511,6 +511,8 @@ export interface UserSettings { beaconUrls?: Record; knowledgeLevel?: KnowledgeLevel; showHelperTooltips?: boolean; + workerProxyAi?: boolean; + workerProxyRpc?: boolean; } // ==================== BEACON/BLOB TYPES ==================== @@ -540,6 +542,8 @@ export const DEFAULT_SETTINGS: UserSettings = { persistentCacheSizeMB: 10, knowledgeLevel: "beginner", showHelperTooltips: true, + workerProxyAi: true, + workerProxyRpc: true, }; /** diff --git a/src/utils/rpcStorage.ts b/src/utils/rpcStorage.ts index a61d1a8b..647498ff 100644 --- a/src/utils/rpcStorage.ts +++ b/src/utils/rpcStorage.ts @@ -20,8 +20,8 @@ const BUILTIN_RPC_DEFAULTS: RpcUrlsContextType = { `${OPENSCAN_WORKER_URL}/btc/ankr`, `${OPENSCAN_WORKER_URL}/btc/onfinality/bip122:000000000019d6689c085ae165831e93`, ], - "bip122:000000000933ea01ad0ee984209779ba": [ - `${OPENSCAN_WORKER_URL}/btc/onfinality/bip122:000000000933ea01ad0ee984209779ba`, + "bip122:00000000da84f2bafbbc53dee25a72ae": [ + `${OPENSCAN_WORKER_URL}/btc/onfinality/bip122:00000000da84f2bafbbc53dee25a72ae`, ], "eip155:1": [ `${OPENSCAN_WORKER_URL}/evm/alchemy/eip155:1`, @@ -189,19 +189,49 @@ export function saveRpcUrlsToStorage(map: RpcUrlsContextType): void { * Stored values override default for a network; missing networks fall back to defaults. * Keys are networkId strings (CAIP-2 format) */ -export function getEffectiveRpcUrls(): RpcUrlsContextType { - // Merge builtin defaults first, then metadata defaults, so stored user values win - const defaults = { ...BUILTIN_RPC_DEFAULTS, ...getDefaultRpcEndpoints() }; +/** + * Check whether a URL points to the OpenScan worker proxy. + */ +export function isWorkerProxyUrl(url: string): boolean { + return OPENSCAN_WORKER_URL.length > 0 && url.startsWith(OPENSCAN_WORKER_URL); +} + +export function getEffectiveRpcUrls(options?: { + excludeWorkerProxy?: boolean; +}): RpcUrlsContextType { + // Merge metadata and builtin worker URLs per-network (concatenate arrays, deduplicate) + const metadataDefaults = getDefaultRpcEndpoints(); + const allNetworkIds = new Set([ + ...Object.keys(metadataDefaults), + ...Object.keys(BUILTIN_RPC_DEFAULTS), + ]); + const defaults: RpcUrlsContextType = {}; + for (const networkId of allNetworkIds) { + const metadataUrls = metadataDefaults[networkId] ?? []; + const builtinUrls = BUILTIN_RPC_DEFAULTS[networkId] ?? []; + defaults[networkId] = [...new Set([...metadataUrls, ...builtinUrls])]; + } const stored = loadRpcUrlsFromStorage(); - if (!stored) return defaults; - // Merge: stored values override defaults const merged: RpcUrlsContextType = { ...defaults }; - for (const k of Object.keys(stored)) { - const val = stored[k]; - if (!val || !Array.isArray(val) || val.length === 0) continue; - merged[k] = val; + if (stored) { + for (const k of Object.keys(stored)) { + const val = stored[k]; + if (!val || !Array.isArray(val) || val.length === 0) continue; + // Merge stored URLs with defaults so builtin worker URLs are always present + const defaultUrls = defaults[k] ?? []; + merged[k] = [...new Set([...val, ...defaultUrls])]; + } } + + if (options?.excludeWorkerProxy) { + const filtered: RpcUrlsContextType = {}; + for (const [networkId, urls] of Object.entries(merged)) { + filtered[networkId] = urls.filter((url) => !isWorkerProxyUrl(url)); + } + return filtered; + } + return merged; } diff --git a/vite.config.ts b/vite.config.ts index 7ec37b31..7fc220a4 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -58,8 +58,8 @@ export default defineConfig({ "process.env.REACT_APP_OPENSCAN_NETWORKS": JSON.stringify( process.env.REACT_APP_OPENSCAN_NETWORKS || "" ), - "process.env.REACT_APP_OPENSCAN_GROQ_AI_URL": JSON.stringify( - process.env.REACT_APP_OPENSCAN_GROQ_AI_URL || "https://openscan-groq-ai-proxy.openscan.workers.dev" + "process.env.REACT_APP_OPENSCAN_WORKER_URL": JSON.stringify( + process.env.REACT_APP_OPENSCAN_WORKER_URL || "https://openscan-worker-proxy.openscan.workers.dev" ), "import.meta.env.VITE_ENVIRONMENT": JSON.stringify( process.env.NODE_ENV || "development" diff --git a/worker/src/routes/onfinalityRpc.ts b/worker/src/routes/onfinalityRpc.ts index c67d375c..ef156c4c 100644 --- a/worker/src/routes/onfinalityRpc.ts +++ b/worker/src/routes/onfinalityRpc.ts @@ -3,7 +3,7 @@ import type { BtcRpcRequestBody, Env } from "../types"; const ONFINALITY_BTC_HOSTS: Record = { "bip122:000000000019d6689c085ae165831e93": "bitcoin.api.onfinality.io", - "bip122:000000000933ea01ad0ee984209779ba": "bitcoin-testnet.api.onfinality.io", + "bip122:00000000da84f2bafbbc53dee25a72ae": "bitcoin-testnet.api.onfinality.io", }; export async function btcOnfinalityHandler(c: Context<{ Bindings: Env }>) {