diff --git a/apps/explorer/src/routes/__root.tsx b/apps/explorer/src/routes/__root.tsx index 6925808c..c603a4e3 100644 --- a/apps/explorer/src/routes/__root.tsx +++ b/apps/explorer/src/routes/__root.tsx @@ -152,9 +152,11 @@ export const Route = createRootRouteWithContext<{ // Patch fetch/Request to strip basic auth credentials from same-origin URLs. // Required for TanStack Start server functions (/_serverFn/...) which use // relative fetches that inherit credentials from the page URL. - // Only activates when URL contains credentials (preview/staging environments). + // Always active because browsers hide URL credentials from JS (Location has + // no username/password, and Chrome strips them from location.href), making + // detection impossible. The patch is a no-op when no credentials are present. scripts.push({ - children: `(function(){var l=window.location;if(!l.username&&!l.password)return;var o=l.protocol+"//"+l.host;var F=window.fetch;var R=window.Request;var s=function(i){var u=i instanceof Request?i.url:String(i);try{var a=new URL(u,o);if(a.origin===o){a.username="";a.password="";}return a.href}catch(e){return null}};window.fetch=function(i,n){try{var h=s(i);if(h){if(i instanceof Request)return F.call(this,new R(h,i),n);return F.call(this,h,n)}}catch(e){}return F.call(this,i,n)};var W=function(i,n){var h=s(i);if(h)return new R(h,n||i);return new R(i,n)};W.prototype=R.prototype;window.Request=W;})();`, + children: `(function(){var o=window.location.protocol+"//"+window.location.host;var F=window.fetch;var R=window.Request;var s=function(i){var u=i instanceof Request?i.url:String(i);try{var a=new URL(u,o);if(a.origin===o){a.username="";a.password="";}return a.href}catch(e){return null}};window.fetch=function(i,n){try{var h=s(i);if(h){if(i instanceof Request)return F.call(this,new R(h,i),n);return F.call(this,h,n)}}catch(e){}return F.call(this,i,n)};var W=function(i,n){var h=s(i);if(h)return new R(h,n||i);return new R(i,n)};W.prototype=R.prototype;window.Request=W;})();`, type: 'text/javascript', }) diff --git a/apps/explorer/src/routes/_layout.tsx b/apps/explorer/src/routes/_layout.tsx index 4b47c9a9..ebb11b16 100644 --- a/apps/explorer/src/routes/_layout.tsx +++ b/apps/explorer/src/routes/_layout.tsx @@ -8,7 +8,13 @@ export const Route = createFileRoute('/_layout')({ validateSearch: z.object({ plain: z.optional(z.string()), }).parse, - loader: () => fetchLatestBlock(), + loader: async () => { + try { + return await fetchLatestBlock() + } catch { + return 0n + } + }, }) function RouteComponent() { diff --git a/apps/explorer/src/wagmi.config.ts b/apps/explorer/src/wagmi.config.ts index aac83d57..35483971 100644 --- a/apps/explorer/src/wagmi.config.ts +++ b/apps/explorer/src/wagmi.config.ts @@ -11,12 +11,12 @@ import { tempoActions } from 'viem/tempo' import { loadBalance, rateLimit } from '@tempo/rpc-utils' import { tempoPresto } from './lib/chains' import { - cookieStorage, - cookieToInitialState, - createConfig, - createStorage, - http, - serialize, + cookieStorage, + cookieToInitialState, + createConfig, + createStorage, + http, + serialize, } from 'wagmi' import { KeyManager, webAuthn } from 'wagmi/tempo' @@ -64,12 +64,10 @@ const getRpcProxyUrl = createIsomorphicFn() }) const getFallbackUrls = createIsomorphicFn() - .client(() => { - const chain = getTempoChain() - return { - http: chain.rpcUrls.default.http, - } - }) + .client(() => ({ + // Browser requests must never hit direct RPC fallbacks. + http: [] as string[], + })) .server(() => { const chain = getTempoChain() const key = process.env.TEMPO_RPC_KEY @@ -81,23 +79,25 @@ const getFallbackUrls = createIsomorphicFn() }) const getTempoTransport = createIsomorphicFn() - .client(() => { - const proxy = getRpcProxyUrl() - const fallbackUrls = getFallbackUrls() - const proxyTransport = rateLimit(http(proxy.http), { - requestsPerSecond: 20, - }) - const fallbackTransports = fallbackUrls.http.map((url) => - rateLimit(http(url), { requestsPerSecond: 10 }), - ) + .client(() => { + const proxy = getRpcProxyUrl() - return loadBalance([proxyTransport, ...fallbackTransports]) - }) - .server(() => { - const proxy = getRpcProxyUrl() - const fallbackUrls = getFallbackUrls() - return loadBalance([http(proxy.http), ...fallbackUrls.http.map(http)]) - }) + // Browser traffic should only hit the RPC proxy. Direct chain RPC endpoints + // may require credentials that are only available server-side. + return loadBalance([ + rateLimit(http(proxy.http), { + requestsPerSecond: 20, + }), + ]) + }) + .server(() => { + const proxy = getRpcProxyUrl() + const fallbackUrls = getFallbackUrls() + return loadBalance([ + http(proxy.http), + ...fallbackUrls.http.map((url) => http(url)), + ]) + }) export function getWagmiConfig() { if (wagmiConfigSingleton) return wagmiConfigSingleton